Coverage
What a general-purpose desktop UI library is expected to provide, what this
one provides, and what is deliberately out of scope. GPUI Box is the
application substrate: if a downstream desktop or browser-hosted product
needs a surface to exist, the surface belongs here unless it is a host
fact, a locale fact, a transport, or a platform chrome the OS already
owns. docs/components.md describes the components themselves; this file
exists so a gap is a recorded decision rather than an oversight.
Frozen visual retirement has a paint-only framework primitive: PaintRecording
owns quads/gradients, paths, shadows, underlines, text/image sprites and their
capture-visible clips/resources. A dynamic offscreen demonstrator lives in
tools/headless-visual/src/paint_recording_tests.rs; it is infrastructure
evidence, not a Kit component exhibit. Live registrations disappear at removal,
even while the same visible pixels are replayed. Callers own exit timing,
recording budgets, reinsertion and reduced-motion disposal.
The remaining generic gap is composited last-live snapshots for glass,
native hosted content/platform surfaces, and deferred overlays. Paint recording
refuses those capabilities explicitly; it cannot freeze a historical backdrop,
recover clipped pixels, or replay an AnyElement inertly. Families must retire
unsupported content immediately rather than present shell-only continuity.
Glass optics use a shared height field, Snell refraction, spectral indices and
Fresnel reflection in all three shader backends. glass-optics isolates these
parameters over a ruled fixture, including fused panes. The implementation is
single-interface screen-space optics, not full volume transport: no second
interface, internal bounces, caustics, hidden-background recovery or real
environment reflection is claimed. Scattering remains a uniform Gaussian;
background-busyness probes and fused-outline shadows remain deferred. See
docs/compatibility.md for the material contract and platform evidence boundary.
A component counts as covered only when it has all four of: a public builder or
view, a scene in gpui_kit::scenes, behaviour tests driven through simulated
input, and an entry in docs/components.md.
Covered
The isolated JS host does not grant external-file or pasted-image read access
from ExternalPaths or paste events. These require an explicit refusal, not
host paths or apparently readable references. A future host capability must
define consent, mount/revision scope, read-only operations, expiry/revocation,
size limits, path privacy, and negative tests. Internal DragItem events are
data-only identity/label/kind/built-in-icon values, not file capabilities.
| Family | Components |
|---|---|
| Action | Button, IconButton, ButtonGroup, SplitButton, Toggle, ToggleGroup, CopyButton |
| Text entry | TextInput, PasswordInput, OneTimeCodeInput, TextArea (including bounded autosize), MentionInput, RichTextEditor, NumberInput, TagInput, InlineEdit, SearchField, FindReplace, UploadList |
| Choice | Select, MultiSelect, TransferList, Cascader, Combobox, Checkbox, Radio, Switch, Slider (horizontal and vertical), SegmentedControl, ColorPicker, ColorSwatch |
| Form | FormField, SettingsRow, SettingsSection |
| Navigation | Tabs, Accordion, Collapsible, Breadcrumb, Sidebar, AnchorList, Pagination, Wizard, UndoHistory, Carousel |
| Data | List (virtualized), Flow (virtualized), Table, DataGrid (virtualized), TreeGrid (virtualized), BulkBar, Tree, KanbanBoard, DiagnosticsList, ImageList, Masonry |
| Date and time | Calendar, DateInput, RangePicker, TimeInput |
| Content | Markdown, AgentDocument, MessageList, ImageViewer, CodeView, TransportBar, BrowserPanel (shell only), LogStream, DiffView, ArtifactPreview, Terminal |
| Display | Icon, Badge, Tag, Avatar, AvatarGroup, Card, ListRow, Divider, ProgressBar, EmptyState, FailurePanel, StatusDot, StatusLine, Callout, Banner, StaleMark, PulseLoader, Skeleton, Spinner, BarLoader, LoadMore, RefreshVeil, ProgressCircle, StageProgress, StateView, OutcomePanel, DescriptionList, Timeline, HighlightedText, AnimatedNumber, MetricCard, Sparkline, PerformanceHud, MicroMark, Rating, Bubble, Plot, CandlestickChart, SankeyChart, LineChart, BarChart, AreaChart, ScatterChart, PieChart, StackedBarChart, RadarChart, GaugeChart, ChartLegend, Heatmap |
| Overlay | Overlay, Frost, Glass, Dialog, Drawer, Popover, Menu, ContextMenu, Menubar, CommandPalette, Tooltip, HoverCard, Toast, ToastLayer, NotificationCenter, Kbd |
| Layout | DesktopTitlebar, SplitPane, SplitTree, ScrollArea, ScrollFade, Toolbar, AspectRatio, Responsive, Grid, Container |
| Shell | Dock, StatusBar |
| Keymap | KeybindingRecorder, KeymapEditor |
| Interaction | Dropzone |
| Filtering | FilterBar |
| Agent run and persona | AgentAvatar, AgentActivityLine, AgentCard, AgentGroup, AgentRunIssues, ToolCall, ThinkingBlock, NodeGraph, GraphNode, NodeGroup, CanvasToolbar, Minimap, TraceView, SpanTimeline, AgentRoster, SubagentTree, AgentRunCanvas, PersonaPortrait, VoiceReactive, PersonaDialogue, FeedbackRating, PromptBuilder |
| Permission and cost | ApprovalPrompt, PermissionMatrix, CostMeter, ContextGauge |
| Game experience | PartyRoster, ObjectiveTracker, AbilityBar, RewardReveal |
| Visual effects | EffectParticles, CinematicEffect |
| Structured data | JsonView, Outline, SchemaForm |
| Connections | ServerList, OfferingCatalog |
| Media | AudioPlayer, AudioWaveform, VideoPlayer, ModelViewer |
Tooltipped is an extension trait rather than a component: it attaches a
Tooltip to any element, and is covered wherever that Tooltip is.
PasswordInput and OneTimeCodeInput cover only product-neutral sensitive
entry. The auth-sign-in and auth-verification scenes show password,
one-time verification, passkey, organization sign-on, and recovery actions as
composition from generic primitives. Account models, provider policy,
credential storage, validation, networking, RPC, and authentication outcomes
remain with the caller.
BrowserPanel is listed with a qualification because it is one. It is the
chrome and the states around an embedded web view and it renders no web
content: that needs an engine, and a component library that pulled one into
every binary would be charging every host for a feature almost none of them
use. The host supplies the engine and the surface. What the panel does own is
the part hosts otherwise each get wrong — a build with no engine says so
instead of drawing a blank page, and Loading, Empty, Unavailable, Error, and
Ready remain five distinct answers.
NodeGraph renders caller-owned positions. The caller may compute those with
the optional layered_layout / dimension-aware layered_layout_sized helpers
or a different layout policy; accepting a
layout result never transfers topology or position ownership to the component.
A node may carry a caller-rendered thumbnail, whose pixels the graph neither
fetches nor decodes. GraphInteraction::Inspect permits only pan, zoom, and
selection proposals; Arrange additionally permits movement; Edit adds
deletion, connection, and disconnection. The caller remains authoritative for
the selection, topology, positions, and viewport shown on the next frame.
CinematicEffect is covered without making an animation runtime mandatory.
The cinematic-effects scene stages a resolved deterministic sample, explicit
runtime and invalid-archive fallbacks, and a reduced-motion poster. Component
tests drive exact sampling, frame ownership, RTL mirroring, typed semantics,
and diagnostic redaction. The optional pure-Rust adapter is separately tested
against archive and animation limits under --all-features; a default build
retains identical recipes and always-available particle fallback behavior.
Systems, which span more than one component
A system is not a component. It has no builder of its own to place on a screen; it is a contract several families implement, so it is covered when the contract is documented, staged in a scene, and driven through simulated input against every surface that implements it.
| System | Contract | Implemented by |
|---|---|---|
Drag and drop (gpui_kit::interaction::dnd) | docs/interaction.md | List, Tree, Tabs, Dropzone, DataGrid (column headers), Dock/DockTree (panel headers, groups, split edges) |
Drag and drop is covered: the contract is written down, the scenes drag-list,
drag-tree, and dropzone stage it, and crates/gpui-kit/tests/it/dnd.rs
drives a simulated pointer through every surface above. DataGrid reorders its
column headers through the same system, driven in
crates/gpui-kit/tests/it/grid.rs, and Dock moves panels between regions
through it, driven in crates/gpui-kit/tests/it/shell.rs; DockTree
additionally reports centre merges into preserved empty stacks and four-way
recursive split placement, driven in crates/gpui-kit/tests/it/dock_tree.rs.
One resize implementation
SplitPane is two panes and a divider; SplitTree is however many of those the
caller nests; Dock builds a SplitLayout from the regions that hold panels
and hands it to a SplitTree; DockTree projects the caller's recursive
DockTopology to that same layout. So every dock divider is the same divider a
plain split gives, with the same minimums and published travel range, and there
is one place where dragging a divider is implemented. Every dock stack header
is a Tabs strip for the same reason: dragging a panel is the drag system, not
a second one.
The Longbridge GPUI Kit 0.6 dock was reviewed here rather than copied. Its
PaneTree, normalization reducer, panel entities, registry, and reconciliation
cache solve ownership inside a retained DockArea; importing them would create
a second layout authority and make an action mutate state the Box contract says
the caller owns. DockTree therefore keeps its controlled record-and-intent
boundary. Its tests pin the corresponding invariants: load/dump/load reaches a
fixpoint while empty stacks persist, every malformed record shape is refused,
selection and collapse do not move unrelated ratios, a nested divider names
only its own split, one completed drop emits one move intent, and ratios remain
dimensionless when the container changes size. The host still applies and
normalizes accepted structural changes.
Table or DataGrid
Both are covered and neither replaces the other. Table takes materialized
rows and lays all of them out; DataGrid takes a render closure and lays out
only the rows the viewport holds, which is what buys it column resizing and
reordering, a pinned group, selection over an incompletely loaded set, opened
rows, and cell editing. docs/components.md has the guidance on which to
reach for. A wide DataGrid uses one horizontal viewport for its header,
virtualized body, and summary, while a pinned leading group remains frozen at
the reading edge. Its remaining fit-to-content limit is stated rather than
faked: a double click on a column edge reports the request and lets the host
answer.
TreeGrid reuses that virtualized machinery for a caller-flattened hierarchy.
Its tree-grid scene and crates/gpui-kit/tests/it/tree_grid.rs cover bounded
materialization, TreeGrid/Row/GridCell semantics, row hierarchy metadata, and
logical disclosure/parent keyboard intents. It deliberately adds no second
flattening or cross-tree layout system; horizontal scrolling and a frozen
hierarchy column are inherited from DataGrid rather than reimplemented.
Helpers, which the four-part rule does not reach
field_shell and FieldState draw the one border, background, and focus
treatment every editable control wears. FocusTrap keeps the keyboard inside
an open overlay. Neither renders on its own, so neither has a scene of its
own; both are exercised through every control and overlay that uses them.
Out of scope, and why
- The calendar itself.
Calendar,DateInput,RangePicker, andTimeInputare covered; the calendar system, the time-zone database, the locale, and the notion of today underneath them are not, and never will be. Correctness there is calendar, time-zone and locale work, not UI work, and a library that shipped a half-correct calendar would be worse than one that shipped none. So the components own no date arithmetic at all and read every fact from a host-implementedDateAdapter;docs/datetime.mdis the contract. The reference calendar the scenes and tests run on is behind thefixturesfeature, off by default, so it cannot be mistaken for a default. - Time formatting.
Timelinedisplays times and day headings, and formats neither: it takes strings the host has already put into words. The same reasoning that keeps the calendar out of this crate keeps “two minutes ago” out of it, and the seam is the adapter above the component. - Judging or applying a keybinding.
KeybindingRecordercaptures a keystroke andKeymapEditorcoordinates it with caller-owned command and binding identities. Whether it clashes with something, what provenance it has, and whether add, remove, or reset should be accepted needs the keymap, which the host owns. Both components render the caller's answer rather than inventing or persisting one. - Persisting a layout.
SplitLayoutconverts to and from plain records so a host can write it out, and this crate takes no serialization dependency to do it for them. - Language intelligence and document policy.
Editorfixes the sharedTextAreabuffer and geometry to a no-wrap source projection, then accepts revision-tagged caller highlights and one synchronous caller-owned indentation replacement.RichTextEditorprojects the same editing invariants through styled blocks, alignment, lists, diagnostics, semantics, and a formatting toolbar. Grammar/LSP facts, folding and multi-caret policy, persistence, collaboration, URL policy, and conversion to a product document format remain host work. Syntax colouring still stops at four built-in classes on the eight languagescontent::highlightknows. A host that has a grammar installs its own spans, and those facts win. - Doing what a document says.
Markdowndraws HTML as the characters somebody wrote, reports a link rather than opening it, and names an image rather than fetching it. There is no HTML renderer here, no URL policy, and no network;docs/content.mdstates why each of those is a refusal rather than a gap, and what a host has to supply instead. - Delivering a message.
MessageListrenders five delivery states and reports a retry. Sending anything, deciding what a resend means, and knowing whether a message was really read are the transport's, and this crate has no transport. - Capturing a voice or advancing dialogue.
VoiceReactivemaps a finite normalized host sample to a complete meter andPersonaDialogueowns the portrait, safe Markdown, streaming, and choice composition. Microphone access, recognition, synthesis, playback, expression inference, and choosing or applying the next turn remain host facts and capabilities. - Running a game or deciding an outcome.
PartyRoster,ObjectiveTracker,AbilityBar, andRewardRevealown reusable character/game presentation, malformed-topology refusal, typed intents, RTL, reduced motion, semantics, and policy-resolved effects. Combat formulas, cooldown clocks, input maps, objective progression, reward eligibility, inventory mutation, persistence, networking, and asset fetching remain authoritative host systems. The UI never converts a click into a successful action or an item into an owned one. - Fetching or decoding an image.
ImageViewerframes, zooms and pans an element the host hands it, and names the source when the host hands it nothing. There is no network here and no decoder, so the pixel size of a source is a caller input like every other fact this library cannot hold; a viewer given none says the size is unknown rather than reporting the box it drew. - Media policy.
TransportBarreports play, pause, seek, volume, mute, speed and a track step, and applies none of them.AudioPlayerandVideoPlayerask aMediaTransport;PlatformMediaTransportcurrently implements it with AVFoundation on macOS and Media Foundation on Windows. Linux GStreamer and Web HTML media adapters are planned work indocs/foundation-roadmap.md, not permanent no-backend policy. Playlist and queue ownership, URL/auth policy, DRM, subtitle/track policy, output-device policy, custom cache/retry and capture remain host responsibilities. - Reading a 3D model that is not glTF.
ModelViewerreads the subset of glTF 2.0 stated indocs/components.mdand refuses everything else, without a scene-graph dependency, a material system, or a texture pipeline. Other formats, materials, animation and skinning are not gaps to be filled here: a document that needs them is one an application converts before it arrives. - Owning application data or interpreting its business meaning. Charts read caller-owned observations and report caller-owned actions. Reusable scales, ticks, stacking, statistical layouts, reference marks and controlled range interactions are in scope; they do not require queries or persistence. Hosts still choose the data, domain policy, units, locale/calendar formatting and business transformations. Existing normalized charts retain their caller-prepared coordinates and exact text. Their keyed geometry, crosshair navigation and stale-data retention do not imply a complete raw-data engine. The visualization delivery plan records that expansion separately from delivered coverage. Query execution, stored reports, trading logic and data-source orchestration remain application work.
- Owning a platform picker. Colour, file, and print dialogs that replace the operating system stay out. In-window colour wells, dropzones, and print-preview chrome that report a choice are in scope; they do not become the system dialog.
- Menu bar and window chrome. Owned by the platform window, not by a component tree.
- Product-specific marketing policy.
Carousel,Rating, andBubbleare now neutral, caller-owned primitives. Product-specific autoplay, recommendation, delivery, and conversation policy remains outside the Kit.
Framework and platform limitations
The component coverage review above has no remaining MUI or shadcn component gap. The limitations below are intentionally kept separate: they are framework, renderer, platform, or host-policy boundaries, not missing public components. Out of scope above means "will not be built, and here is why"; these entries record capabilities that need a framework or platform owner without pretending that a component-local workaround is complete support.
Backdrop work regions preserve the established pixels, 2026-09-04
The WGPU frost and glass scenes were captured with Linux llvmpipe
immediately before and after bounding their snapshot, blur, and composite
passes. All four PNGs are byte-identical (AE = 0):
| Image | SHA-256 before and after |
|---|---|
frost-studio-dark.png | fd936b094521bc9fe91149f7fc6a0365fddd63554ebf13fe328c07d295ab1ec9 |
frost-studio-light.png | 11dd9814eddb4097c675f2c793b530017eeb59b8dc08620a52587b84aa743418 |
glass-studio-dark.png | 37387fe70f5a9a5f0e86908bf968867cea9d0513c18966df36d6cae02d686ad8 |
glass-studio-light.png | 604cdf8137acedb1fbb2dece495189f5310e7025273e37fa95a183a344a23ff1 |
The shared region calculation retains every Gaussian dependency, the maximum
refracted and dispersed reach, and requested probe samples while leaving
texture coordinates and material math unchanged. Direct3D's all-feature crate
also cross-compiles for x86_64-pc-windows-gnu. Metal cannot run in the Linux
orb; its check is the dispatched Platforms workflow. This is a pixel-equivalent renderer
optimization, so no macOS or Windows baseline is recaptured or changed; their
existing headless checks remain the acceptance authority.
Regular glass text contrast
Kit constrains the Regular material's shader wash to maintain the WCAG 4.5:1
normal-text ratio against the opposing neutral backdrop, including transmission
gain and optical lift. This applies before any probe response as well: a mean
luminance cannot guarantee contrast at every text position. It does not add an
opaque or source-over face, change large-panel appearance, or affect Clear.
Caller-supplied text colours and optical overrides still require caller review.
Glass::protect_text_contrast(false) and the matching group option explicitly
retain the material's wash instead; the caller then owns foreground legibility.
This is used for native material calibration, not enabled silently on existing
reading surfaces. Explicit tints compose into the RGB material wash (including
fused bridges) before its rim light, rather than disappearing behind a zero-alpha
fill or being converted to an achromatic pole by framework sanitization.
tools/liquid-glass-reference records native SwiftUI references and validates
GPUI candidates with independent colour, rim, bridge and held-out-size scores.
The persistent menu producer now resizes its real layout at simulated times,
records measured geometry, and rejects unchanged menu pixels claiming a resize.
This does not reproduce SwiftUI content crossfades or prove native dynamics.
Foreground press scaling uses paired framework transforms without reflow;
the material outline stays fixed, and reduced motion suppresses the scale.
The 1.014 default is Kit policy informed by one native label specimen, not a
universal native constant. Rounded foreground clipping and explicit window-overlay
mask escape are implemented. Calibration trials are not accepted catalog
baselines or proof of Apple's private implementation.
Application-wide Reduce transparency belongs in ThemeRegistry, alongside
density: call set_reduce_transparency(reduce, cx) to update it and repaint all
windows. ThemeRegistry::set_reduce_transparency and reduce_transparency
provide the direct setter/getter. Activation, density changes, registration and
counterpart resolution preserve this preference; an unchanged value does not
rebuild the resolved theme. Scoped Theme::with_reduce_transparency remains
available for individual exhibits; no root ThemeOverlay workaround is needed.
Rounded glass profile reach, 2026-09-08
The menu's 12 px corner and requested 36 px bevel exposed a second optical
singularity, distinct from finite-difference medial-axis normals: the analytic
arc normal still had nonzero dome slope when its parallel curve collapsed at
the arc centre. Both Metal and Linux WGPU showed the resulting pointed highlight.
BackdropGlass::optical_bevel now bounds the complete optical profile by positive
corner radii and lobe half-extents before all three renderers pack it. The same
depth across a union prevents seams; specular strength, hairline, wash and blur
are unchanged. Square corners retain their intentional incident-face crease.
CPU and native pixel regressions include the menu radius and sample both before
and after the arc centre, while requiring a real rim highlight to remain.
Clear media reading
Clear is an appearance-independent on-media material: its subtree inherits
color.onMediaForeground and color.onMediaHairline, never an adaptive flip.
Reduced transparency keeps that light content on dark Frosted
color.onMediaBackground, including in light themes. dimmed(true) applies
the effect.glassDimming 35% backing only to Clear, and retains it when reduced;
all other presets ignore dimming. This policy is inside Glass/GlassGroup, not a
caller-installed theme override. Deliberately coloured child elements still
own their explicit colours, as on other surfaces.
The current glass integration has Linux offscreen and native Metal evidence. Windows/WARP is unverified for this revision: no Windows runner was available; the existing Windows baseline is not claimed as acceptance of these changes.
Downsampled backdrop blur deliberately deferred, 2026-09-04
Metal, Direct3D, and WGPU keep full-resolution backdrop textures and preserve the established Gaussian and refraction samples while clipping work to the surface's conservative sampling region. Rendering the blur through a smaller intermediate texture could reduce bandwidth further, but resampling changes the material's pixels and may change the apparent radius, rim detail, and paint-order boundaries. It is therefore not hidden behind a renderer-local shortcut. A future downsampled path needs one shared quality contract, focused renderer tests, and reviewed replacement captures from both active baseline renderers (Metal on macOS and WARP on Windows).
Metal and WARP baselines outstanding for this wave, 2026-09-03
Three light-appearance fixes landed from a Linux orb, which can run neither active renderer. Twenty-two images across eleven scenes are therefore stale on macOS and Windows until someone captures them there:
| Change | Scenes, in both Studio themes |
|---|---|
| Sprite corner coverage | visual-effects, cinematic-effects, game-ui |
| Washed panel surface | failure-panel, outcome-panel, state-ladder, kanban, heatmap, audio-waveform, node-graph |
| Scrolling menu edge fade | form |
Every claim recorded for them is a Linux llvmpipe claim and says so. The
renderers were compared with a full catalog capture taken immediately before
each change and re-checked after it, so the counts above are what those changes
moved and not accumulated drift; snapshots/headless/linux itself stays as it
was, retired and stale, rather than being refreshed for three scenes and left
alone for the rest.
Nothing here asserts what Metal or WARP drew before the change or draws after it. Whoever captures on those platforms reviews the images against the entries below rather than accepting them unseen.
Sprite corner coverage reviewed on a retired renderer, 2026-09-03
The polychrome sprite corner mask's dependence on fwidth(distance) was found
downstream in a light appearance and reproduced here in direct offscreen
readback under Linux llvmpipe — no compositor took part, so it is a renderer
defect rather than a capture artifact. visual-effects and cinematic-effects
carried the dashed seam through their own sprite batches and particle recipes,
and game-ui through its item art; the three scenes are the whole of the
catalog's polychrome sprite coverage, and all six of their images move with the
correction.
Whether Metal and WARP ever showed the same seam is not recorded, because
neither renderer can be run here. Both carried the identical fwidth-sized
mask, so both were exposed to the same class of failure; the correction removes
the derivative rather than compensating for one backend, and reproduces the
width a conforming fwidth reports. Whoever next captures on macOS and Windows
should review those six images against this entry rather than accepting them
unseen.
An overflowing menu with no active baseline, 2026-09-03
No catalog scene fills a menu past its own height. command-palette,
choice, multi-select, cascader, and mention-input each stage a list
that fits, which is why the boundary treatment every one of those menus lacked
— a row sliced through its glyphs at the viewport edge, with the card's
rounded corner cutting across the same row — was never in a reviewed picture.
A temporary llvmpipe exhibit rendered a CommandPalette holding fifteen
commands in three sections at the same 480-pixel width and menuMaxHeight the
scene uses, in both Studio themes, before and after the fade. Before, the
eighth row was cut horizontally through its cap height with its shortcut chips
halved; after, that row fades out over the band and the boundary reads as
"there is more below". The exhibit was then removed, because Linux's baseline
set is retired and cannot author the active Metal and WARP images.
Behavior coverage keeps the permanent invariant: overlay::popover's edge
decision fades an end only while it hides a row, fades neither end for a list
that fits, and does not mistake a sub-pixel resting offset for travel.
Two gaps stay open for whoever next captures on macOS and Windows. The first
is the exhibit: a palette long enough to overflow belongs in the
command-palette family so the boundary has a picture. The second is that
CommandPalette still does not scroll its keyboard highlight into view the
way Select and Combobox do, so arrowing past the fold moves a highlight
nobody can see; the fade now says the rows are there, and the reveal is the
separate change that lets the keyboard reach them.
Edge-faded crossing surfaces with no active baseline, 2026-09-03
A tall SettingsSection whose card crossed the bottom of a ScrollArea was
rendered before and after the primitive-aware edge-fade correction in a
temporary scroll-fade exhibit. Both Studio themes were inspected with Linux
llvmpipe: before the correction the card shadow was absent while its visible
rows remained; afterwards the shadow retained the card's visible grouping and
the fill continued to fade over the band. The temporary exhibit was then
removed because Linux's baseline set is retired and cannot author the active
Metal and WARP images.
Framework behavior coverage keeps the permanent invariant: a shadow and a
filled path taller than the band remain non-transparent when they cross the
region edge, the path carries a transparent-to-opaque shader gradient, and a
primitive smaller than the band retains nearest-edge alpha. Whoever next
captures on macOS and Windows should add the crossing SettingsSection state
to the scroll-fade family and review both active renderer images rather than
accepting either unseen.
Three opt-in states with no active baseline, 2026-09-03
An InlineEdit reading a long multi-line value, a NodeGraph that keeps its
dot grid but refuses the origin axes, and neutral controls declaring an
overlay ground now have complete layout or paint behavior but no permanent
catalog state. Existing scenes intentionally keep their defaults, so their
Metal and WARP pixels do not move; Linux's llvmpipe baseline set is retired
and cannot author either active renderer's new images.
The reading layout is covered by measured bounds, including the pen remaining
inside a bounded single-line row. The graph's default-on axes and independent
grid/ground settings are covered by behavior. Overlay-grounded Secondary,
Default, refused, icon, and toggle paths share one resolver whose composited
resting fill clears the 2 L* surface floor in both bundled themes, while the
implicit panel ground resolves to the historical raised/active paints.
A temporary llvmpipe exhibit put a Secondary button, an unpressed Toggle, and
a Secondary IconButton on OverlaySurface::FLOATING; resting and hovered
captures in both Studio themes were reviewed before the exhibit was removed.
Whoever next captures on macOS and Windows should add a long multi-line
reading value to inline-edit, an axes-free state to node-graph, and the
three grounded controls to the overlay or button family, then review both
active renderer images rather than accepting them unseen.
Two colour settings with no scene of their own, 2026-09-02
TabItem::tint and NodeGraph::ground_light change what a strip and a canvas
look like, and neither is drawn by any scene in the catalog. That is a
deliberate deferral, not an oversight, and it is a renderer boundary rather
than a component one: an active visual baseline can only be captured on a
machine with that renderer, and the two active sets are Metal on macOS and
WARP on Windows. A scene added without both is a headless check that fails
for missing baselines on the two platforms that gate a release, so the picture
would be added by breaking the thing that reviews pictures.
Both are covered by behaviour: a tinted strip publishes the same tree as an untinted one down to the measured bounds, a refused ground paints no gradient at all, and a canvas that has not asked is still lit. What is not covered is what they look like. Whoever next captures baselines on macOS and Windows should add a tinted strip beside the untinted one in the navigation family and a flat ground to the canvas family, and review the images rather than accept them.
Fit clearance with no scene of its own, 2026-09-02
NodeGraph::fit_clearance reserves caller-owned canvas edge bands when the
graph frames its measured world. No gallery state was added from Linux: the
active visual baselines are Metal on macOS and WARP on Windows, and adding a
state without captures from both would leave the release gate incomplete.
The framing arithmetic instead covers zero, asymmetric, composed and oversized clearances, and a mounted graph with clearance publishes the same semantic tree as one without it. Whoever next captures baselines on macOS and Windows should add a fit-clearance state to the canvas family and review both active images.
Shared presentation tiers and semantic token authority, 2026-08-27
Theme::variant_colors resolves the seven shared tiers (Filled, Light,
Outline, Subtle, Default, Transparent, White) against a palette
group, a semantic role, or an explicit paint, and Button, IconButton,
Badge, and Tag accept .variant(..) / .color(..) on top of their
existing vocabularies without moving a pixel of the defaults. Other coloured
surfaces — Callout, Banner, StatusDot, StatusLine, ProgressBar, and
ProgressCircle — use the same semantic theme roles through their own
state-oriented APIs. They intentionally do not expose arbitrary per-instance
variant colours: tone is a fact, while variant tiers are a reusable choice for
surfaces such as buttons, badges, and tags. This keeps all actual paint values
in the theme token authority without creating a second override vocabulary.
A product backdrop that the bundled ramp still cannot 1:1, 2026-08-18
Surface::Backdrop and multi-layer elevation close the two kit-side holes a
downstream prototype hit: there is now a plane darker than canvas for a
card to sit on, and an elevation step can carry both a contact shadow and a
wide one. The bundled Studio themes take the new roles without retuning the
five existing surfaces, so the gallery does not move.
What is still not 1:1 is the light appearance. The prototype paints the page
#c9c9d1, the well #dcdce2, and the card #ffffff. A white panel
leaves raised nowhere to go — it would need L* ≥ 103, which is outside
the colour space — and dropping the page to #c9c9d1 forces sunken
darker still, at which point the light text and semantic rungs (tuned for a
shallow well) fail the contrast floor in eleven pairs. Retuning that light
foreground and semantic scale is a separate change; this one does not do
it. The library still groups with colour rather than with a line, and a
card still does not draw a border: lines stay reserved for focus, invalid,
and drop.
Typography and visual polish audit, 2026-08-11
Before changing typography, eight independent reviewers inspected all 98 Linux
headless scenes in both Studio themes: 196 images, each 1840×2000. Every image
was checked for (1) type hierarchy and accidental GPUI-default text, (2) line
height and icon/chip baselines, (3) token spacing rhythm, (4) dark-theme
contrast, (5) visibly distinct Loading, Empty, Unavailable, Error, and Ready
states, and (6) clipping, overflow, and unconstrained full-width content. This
is the named pre-change inventory; clean means no defect was visible under
that rubric, not that every possible interaction was exercised.
| Scenes | Pre-change finding |
|---|---|
accordion | Medium: disabled title and detail are too faint in both themes. |
actions | High: dark toolbar icons, shortcuts, menu outline, and separators nearly disappear. |
agent-roster | Clean. |
agent-run-canvas | Clean. |
anchor-list | Medium: dark explanatory copy is nearly invisible. |
animated-number, aspect-ratio | Clean. |
approval | Medium: dark secondary action is weak; the focus ring and labelled divider need clearance. |
audio-player | High: media-control baselines diverge and inactive rails/separators disappear, especially in dark. |
auth-sign-in, auth-verification | Medium: dark banner, placeholder, link, and verification-cell boundaries are too weak. |
badge | Medium: several dark semantic foreground/background pairs are hard to distinguish. |
breadcrumb | Low: the light-theme slash sits above the text baseline. |
browser-panel | Medium: the five states are distinct, but long Ready content is hard-clipped. |
button | Medium: control sizes rely on boxes more than a coherent type ladder; dark disabled labels are weak. |
calendar | Medium: dark adjacent/disabled dates and navigation are too faint; fixture explanation uses default-sized text. |
card | Medium: a two-line card stretches across nearly the full canvas. |
cascader | High: an unconstrained trigger and narrow popup disagree; dark disabled rows disappear. |
choice | High: dark unchecked and disabled controls are almost invisible. |
code-view | Medium: an intentionally long line clips without an equally visible horizontal-scroll affordance in both themes. |
collapsible | High: the dark managed section is nearly invisible. |
command-palette | Medium: dark section labels, secondary commands, and keycaps are too faint. |
content | High: the states are truthful, but dark pinned/filter/empty affordances and progress rails disappear. |
context-menu | Medium: fixture copy falls back to large default text; dark metadata is weak. |
conversation | High: duplicated long message groups and an unconstrained reading column obscure the intended scroll states. |
copy-button | Low: success is repeated as adjacent Copied labels. |
cost-meter | High: dark labels, refusal reasons, and unknown values are nearly invisible. |
data-grid | Medium: chip baselines and compact type are inconsistent. Dark row hover/selected are now separate washes; managed rows no longer fade the whole row. |
data-grid-editing | Medium: editor and cell baselines differ and the focus edge collides with a grid line. |
date-range | Medium: disabled/range dates lack contrast and adjacent-month dates lack hierarchy. |
date-time | Medium: light separators sit low and error copy touches the field. |
detail | High: labels, unknown/not-applicable values, and dark metadata are too faint. |
diagnostics-list | High: Hint and explanatory text nearly disappear; chip icon baselines diverge. |
dialog | Low: light is clean; only masked background copy is extremely faint in dark. |
diff-view | Low: top clearance and the dark split divider are weak. |
document-tabs | Medium: light helper copy and dark inactive close controls are too faint. |
drag-list | Clean. |
drag-tree | Medium: light is clean; dark status copy nearly disappears and preview content sits high. |
drawer | Medium: light is clean; the dark unchecked box is difficult to find. |
dropzone | Medium: helper copy is too faint and states move vertically when a detail line is absent. |
failure-panel | Medium: top copy clips in light; secondary failure detail is weak and icon/text centring differs. |
filter-bar | Low: light Add-filter/chip baselines differ; dark is clean. |
find-replace | High: dark explanatory copy disappears, controls are over-wide, and count/arrows are crowded. |
form | Medium: the Region popup covers field help; dark unselected text is still faint. Errors now clear the field. |
frost | High: dark frost lacks enough edge/surface evidence; light needs bottom clearance and consistent stripes. The edge evidence a frosted surface is missing is what glass carries; Frost stays frosted deliberately, being the material every renderer can produce. |
glass | Regular Liquid separates reading surfaces with material-owned blur, saturation, achromatic wash, lensing and hairline. Rim refraction samples the scattered source; Clear stays sharp and media-only, with light content and optional 35% dimming. Reduced transparency resolves presets and overlays to Frosted. Small non-Clear surfaces may flip appearance through hysteresis; large and unmeasured surfaces keep the window appearance. Ring shadows use the five optical-source samples' mean and variance, retaining the last completed statistics; this is a sampled separation cue, not exhaustive backdrop analysis. Foreground press scale uses paired framework transforms without reflow and is suppressed under reduced motion. Caller content is rounded-clipped; window overlays explicitly escape ancestor masks. Fused-outline shadows still need a generic SDF shadow primitive. Over-budget surfaces/groups use opaque fallbacks. The exhibit covers text/media backdrops, compact controls, non-flipping surfaces and admission/lobe budgets. scroll-edge-effect owns the transcript-ramp exhibit. Native renderer evidence is recorded in compatibility.toml; no Apple optical/dynamic equivalence is claimed. |
hover-card | Medium: helper and body copy are too faint, especially in dark. |
ide-shell | High: dark shell height leaves a large void and Empty/Unavailable claims conflict. |
image-viewer | High: metadata and disabled controls are too faint; the third viewer breaks the first two viewers' grid/container rhythm. |
inline-edit | High: dark helper text disappears, errors touch fields, and the short value gets an over-wide editor. |
input | High: read-only/disabled values are made unreadable rather than distinctly unavailable. |
json-view | Low: comment columns and selected-row extent are inconsistent; dark null/comment values are weak. |
kbd | Clean. |
keybinding | Medium: unbound/help/error text is faint and keycap/icon/row baselines vary. |
keymap-editor | High: Defaults and host-managed copy use GPUI's large default; chips, conflict/source text, and actions do not share a row grid. |
list | Light is clean; low in dark for title/list clearance and missing continuation affordance. |
loading | Clean. Every indicator paints from the neutral color.loader.* roles, the placeholder is held inside a loudness band by the token gate, and each of the six is a different picture rather than a different arrangement of the same one. |
log-stream | Medium: level/inline chips sit off baseline and small status copy is faint. |
markdown | Medium: the dark unfetched state disappears; truncated repetition and embedded-block widths are unclear. |
menu | Low: shortcut gaps/insets vary and the dark section label is weak. |
menubar | Medium: shortcuts, anchors, item gaps, and dividers do not share one baseline/rhythm. |
model-viewer | High: a dark labelled divider crosses its label; metadata and disabled controls are too faint. |
motion-flip | Low: light caption contrast/button padding and dark badge centring need correction. |
motion-state | Medium: progress tracks disappear and the dark segmented strip stretches beyond its content. |
node-graph | High: edges cross labels, loose labels lack anchors, and dark node metadata is too faint. |
notification-center | High: unread markers move between action/close slots and rows without detail collapse. |
offering-catalog | Medium: type badges and source columns are inconsistent; source/banner copy is faint. |
overlay | Low: dark secondary-button separation is weak. |
pagination | High: light previous/first controls look disabled away from the first page. |
permission-matrix | Low: wrapped policy detail breaks row rhythm and dark secondary copy is weak. |
popover | Low: light unchecked-box contrast and optical centring are weak. |
progress-circle, reading-direction | Clean. |
schema-form | Medium: dark fields and placeholders disappear; required markers drift off baseline. |
scroll-area | Medium: the final line is cut in half and the light thumb is faint. |
scroll-fade | Clean. |
scroll-shadow | Medium: the first line is cut in half and the dark bottom shadow is weak. |
search-field | Low: light hit-chip outlines are too faint. |
server-list | Low: expanded error spacing is inconsistent and light helper copy is weak. |
settings | Medium: unavailable explanation is made unreadable, especially in dark. |
sidebar | Medium: dark managed and workspace metadata nearly disappear. |
sparkline | Medium: dark minimum/maximum values are too faint. |
split-pane, split-tree | High: dark dividers and drag handles nearly disappear. |
status | Medium: two short status messages stretch nearly the full canvas. |
table | Low: the scene now shows ready, a stale refresh, and empty. Managed rows keep their badges; hover and selected are separate washes. |
tabs | Medium: scene body falls back to large default text and dark disabled tab is too faint. |
textarea | High: dark placeholders, disabled text, and field surfaces disappear. |
thinking | Medium: dark secondary/empty copy is weak and short rows stretch full-width. |
toast | Low: bottom clearance and dark close-control contrast are marginal. |
toggle | High: dark disabled and unselected states nearly disappear. |
tool-call | Clean. |
toolbar | Medium: scene body falls back to default text; dark controls/boundaries and group spacing are weak. |
tooltip | Medium: trigger and tooltip have nearly the same visual role and no strong pointing relationship. |
transport | High: dark rails, speeds, status copy, and unknown duration disappear; control baselines and mute forms diverge. |
tree | Medium: dark ignored state is too faint and selected-row extent is ambiguous. |
tree-grid | Medium: dark structure disappears and disclosure/icon columns do not reserve consistent slots. |
upload-list | High: dark queued/refusal explanation disappears; progress/action columns and icon sizes vary. |
video-player | Medium: volume controls do not share a baseline; dark rails/help disappear and the player lacks a balanced width strategy. |
wizard | High: dark step details and Back boundary disappear; horizontal steps lack a shared title baseline. |
This pass does not accept a root-level inherited font as a fix. A component can
be embedded beneath any host text style, so each element that emits text must
choose its own complete TypeScale step. The typography gate records that
contract. It also does not turn disabled content invisible, treat a refusal as
empty data, invent a decoder/browser/graph layout, or add application policy to
make a fixture look fuller.
What closed the table
The table above stays as the pre-change inventory. What closed most of it was not per-scene patching but two machine-checked rules in the token layer, each of which had been failing silently in every theme:
- Surface separation. Every nesting step — canvas to panel, panel to card,
card to raised, and the overlay against what it covers — now has to differ by
at least three CIE L* points, in the right direction, or
TokenDocument::validaterefuses the theme. The dark neutral ramp was respaced and the light one retuned to pass it. That is what made the card, table, dock and popup boundaries in dozens of theHighrows visible;contrast.rsanddocs/token-model.mdcarry the contract. - Tone distinction.
muted,faint,placeholderanddisabledwere three different facts wearing one grey in dark, and an inverted ladder in light, while every foreground/background contrast pair passed. Each rung now has to differ by three L* measured as distance from the canvas, so one rule holds in both appearances. That is what made the disabled control, the placeholder, the divider and the explanatory line in the remainingHighandMediumrows tell themselves apart.
Card, CardHeader and StyledExt::card_surface replaced three incompatible
hand-rolled card shells, which is what closed the surface-rhythm findings in
the agent, game and notification rows.
After recapturing all 216 macOS baselines against the retuned themes, the rows that still showed a defect were fixed individually:
| Scene | What was left, and what was done |
|---|---|
keymap-editor | Defaults was a step larger than its peer Current bindings; both are now Label. |
ide-shell | The Problems panel carried a badge counting three problems and one string that claimed Unavailable and Empty at once. It now makes one claim and counts nothing it cannot list. The shell also fills the scene rather than leaving the lower third empty. |
notification-center | The unread marker sat in the trailing flow, so it landed beside the action on one row and beside the close control on the next. It is now a fixed slot beside the title. |
node-graph | An edge label was drawn six pixels from the midpoint that also carries the disconnect chip, so the two overlapped. The label now clears the chip's radius. |
cascader | The trigger stretched to the scene while its popup kept its own width. The scene now gives the trigger the width of the surface it opens. |
This re-review was one reviewer over the 216 recaptured macOS images, not a repeat of the eight-reviewer pass, and it read the rows the table had flagged most closely. Three findings were looked at and deliberately left: a conversation and a diff still fill the width they are given, because the reading measure is the caller's; the transport's mute control keeps a different form when muted, because that difference is the state; and a scrolled popup still shows a partial row at its clip, because that is what being scrolled looks like.
Motion framework boundaries
The primitives in docs/motion.md cover a value moving from one state to
another, motion that is interrupted, composed or driven by a gesture, and
springs described as a duration and a bounce. The remaining entries are
renderer/framework boundaries, not missing catalog components.
The spring itself is no longer one of those boundaries: GPUI owns analytic fixed- and moving-target evolution, velocity-preserving element retargeting, playback state, projected targets, and finite overshoot. Kit owns the theme and component policy above it. This is an invisible framework primitive and does not add a scene merely to increase catalog coverage.
Touch recognition is no longer a local component approximation either.
When a platform supplies raw TouchEvents, GPUI resolves tap/multi-tap, phased
axis-locked pan and frame-rate-independent momentum, prediction correction,
touch-drag, long press, multi-contact pinch, exclusive ID-based pan/pinch
ownership, cancellation and fling interruption through window dispatch.
Tests cover contact reordering/degeneracy, either pinch contact cancelling,
captured ownership across redraw and a centroid crossing another view, and
scroll consumption plus residual handoff/reversal without double movement.
Platform inactivity cancels pending recognition and post-release momentum.
These tests do not execute native devices, native keyboards or system gestures.
Rotation and arbitrary simultaneous recognizer graphs are not provided;
captured owners must cancel before unregistering, and manipulation reversal
does not transfer ownership back to scrolling. Native geometry forwarding
returns unavailable for unsupported queries; its shared tests prove transport
and refusal, not platform text geometry. Platform-generated trackpad pinch
remains a separate input path.
| Framework boundary | Why it matters |
|---|---|
Shape in flip | Flip::shape interpolates radius, border width, border colour and background over the same spring that carries position and size, and Shaping::shaped applies the result, so a row becoming a card travels between the two forms. The caller states both forms and applies what comes back, because Flipped wraps an element it did not build and cannot reach the style inside it. What is still not interpolated is anything with no numeric path between the two forms — a shadow set, a gradient, or a change of element kind. |
| Overscroll | motion::rubber_band damps a pull past a boundary, but nothing in the library overscrolls: a ScrollArea stops dead at its end, so the band is available to a caller and used by no component here. |
Media capabilities beyond native playback
PlatformMediaTransport closes the ordinary macOS/Windows decoder, output,
clock and native-view gap. MediaCapabilities now makes audio/video, seek,
volume, rates, native-track and output-selection support explicit at runtime;
unsupported controls are absent or inert. MediaErrorKind preserves no-backend,
invalid-source, open, playback and refusal categories through the Kit seam, so
downstream code never parses a platform diagnostic. The remaining media work is additive capability,
not basic playback: playlist/queue ownership; audio, subtitle and accessibility
description track selection; audio output-device switching; DRM; application
cache/retry and authentication policy; picture-in-picture and fullscreen;
recording/capture; frame-accurate extraction plus waveform/thumbnail generation;
and Linux/Web backends. A platform codec refusal remains no-backend, while an
unreadable source remains failed; neither is converted to empty media.
Components
The internal inventory is not enough to discover a family that does not exist,
so the component catalog is also compared against a mature external baseline.
The 2026-08-27 primary baseline is the official
Material UI component overview.
That page lists 59 entries: Inputs (13), Data display (10), Feedback (6),
Surface (4), Navigation (9), Layout (5), Lab (2), and Utils (10). The current
MUI component index also exposes newer entries
such as Number Field and InitColorSchemeScript; because the two official
indexes are not yet identical, this document records the dated overview rather
than pretending that a component count is a stable quality score.
MUI X is tracked separately: its current stable advanced families are Data Grid, Date and Time Pickers, Charts, and Tree View. Scheduler is listed as Preview, and advanced Pro/Premium features are not silently counted as free Core coverage. The mapping compares product-neutral behavior, not React, DOM, CSS, or API spelling:
The second coverage baseline is shadcn/ui, whose official description is a set of accessible components and a code distribution platform. Its current component catalog lists 64 first-party entries. That is a valid open-source component coverage baseline alongside MUI: the source-distribution model changes ownership and customisation, not whether an input, overlay, data, or layout capability needs to exist. Community registries remain outside this dated first-party count. GPUI Box already follows the important part of that model — callers own data and actions, while the Kit owns reusable visual and interaction contracts — but those contracts must still be complete and machine-tested like a maintained library. The count is not added to MUI's count because names and granularity differ; every entry is instead mapped by behavior below.
| shadcn family | Entries | Strong equivalent | Partial / foundation | Unimplemented |
|---|---|---|---|---|
| Interaction and forms | 20 | Button, Button Group, Checkbox, Combobox, Calendar, Date Picker, Field, Input, Input OTP, Radio Group, Select, Switch, Textarea, Toggle, Toggle Group, Label, Slider | Input Group, Native Select, Questionnaire | — |
| Feedback and surfaces | 20 | Accordion, Alert, Aspect Ratio, Avatar, Badge, Bubble, Card, Carousel, Collapsible, Dialog, Empty, Hover Card, Message Scroller, Progress, Sheet, Skeleton, Toast | Alert Dialog, Attachment, Message | — |
| Navigation and overlays | 13 | Breadcrumb, Command, Context Menu, Drawer, Dropdown Menu, Menubar, Pagination, Popover, Sidebar, Tabs, Tooltip, Kbd | Navigation Menu | — |
| Data, layout, and foundation | 11 | Chart, Data Table, Direction, Item, Separator, Table, Typography, Resizable, Scroll Area, Spinner | Marker | — |
| Total | 64 | 56 | 8 | — |
This normalized shadcn mapping is intentionally stricter than a name search.
For example, Message Scroller maps to the virtualized MessageList,
Resizable maps to the shared SplitPane divider, and Direction maps to the
layout-direction foundation. Native Select is only partial because GPUI's
Select is a themed popup control. Input Group and Questionnaire remain
composition patterns whose product-specific policy is intentionally
caller-owned. Slider and multiple selection are full behavior matches
through the orientation-aware Slider and controlled MultiSelect contracts.
MultiSelect is an intentional GPUI extension for the multiple-selection use
case; it is listed in the library coverage above and in the completed
capabilities below, but it is not counted as an additional shadcn entry.
| MUI family | Entries | GPUI Box mapping | Verdict |
|---|---|---|---|
| Inputs | 13 | Autocomplete → Combobox (single-answer); Button/Button Group → Button/ButtonGroup; Checkbox → Checkbox; Radio Group → caller-composed Radio; Select → Select (single-answer); multiple selection → MultiSelect; Slider → orientation-aware Slider; Switch → Switch; Text Field → TextInput/NumberInput/TextArea; Toggle Button → Toggle/ToggleGroup | Broad coverage; Floating Action Button remains a deliberate desktop composition. |
| Data display | 10 | Avatar, Badge, Tag, Divider, Icon, List, Table, ImageList, Tooltip, foundation text/type scale | Strong coverage; typography and icon families are correctly foundation/catalog concerns rather than duplicate leaf components. |
| Feedback | 6 | Callout/Banner/StateView, Overlay, Dialog, ProgressBar/ProgressCircle, Rating, Skeleton, Toast/ToastLayer | Strong product-neutral coverage, with feedback states retained as caller-owned facts. |
| Surface | 4 | Accordion, DesktopTitlebar/Toolbar, Card, theme surface recipes plus Frost/Glass | Strong coverage by composition and complete surface recipes; no need for a second Paper shell. |
| Navigation | 9 | Tabs, Breadcrumb, Drawer, Menu, Pagination, Wizard, Sidebar | Strong desktop coverage; Bottom Navigation and Speed Dial are mobile-oriented patterns, not missing desktop primitives. |
| Layout | 5 | Responsive, SplitPane, SplitTree, AspectRatio, Grid/Container | Strong coverage; measured breakpoints preserve source order and caller-owned content. |
| Lab | 2 | Timeline, Masonry | Strong coverage with caller-measured tile heights and a documented non-virtualized boundary. |
| Utils | 10 | Popover, Overlay, positioner/focus/portal internals, TextArea, motion system, Responsive | Foundation coverage is present; these are not all user-facing components and should not inflate the public component count. |
| MUI X | 4 stable families | DataGrid, Calendar/DateInput/RangePicker/TimeInput, chart family, Tree/TreeGrid | Strong advanced coverage, with the documented non-virtualized Table/Tree limitation. |
Several MUI names intentionally resolve to existing primitives instead of new
public types. Paper is a surface recipe, Typography is the typed text
foundation, Stack and Box are ordinary GPUI composition, Radio Group is
the caller's group of Radio controls, and Modal/Popper/Portal/Transitions
are overlay and motion infrastructure. This is not a gap: duplicating them as
thin wrappers would create another style system. Conversely, a component is
not marked covered merely because a similarly named primitive can be composed;
the state, input, semantic, and caller-owned event contracts must also line up.
The MUI- and shadcn-derived component review is complete as of 2026-08-27. Every component below has a public contract, a scene exhibit, simulated behaviour coverage, semantic ids, and a documentation entry:
| Completed capability | Implementation boundary |
|---|---|
Multiple selection / MultiSelect | Searchable controlled listbox with stable option identities, removable chips, disabled options, keyboard toggling, and caller-owned selected values. |
Scalar Rating | Controlled whole/half precision rating with pointer and keyboard input, clearable/unrated and disabled states, and accessible value semantics. |
TransferList | Controlled source/target assignment panes with filtering, truthful counts, individual selection, disabled items, move intents, and no component-owned mutation. |
Horizontal and vertical Slider | One orientation-aware contract keeps track geometry, pointer mapping, keyboard direction, marks, range fill, and AccessKit orientation aligned. |
Declarative Grid / Container | Token-backed measured breakpoints, columns, spans, gaps, readable widths, and source-order semantics. |
ImageList | Stable selectable media tiles with measured responsive columns and token-backed layout; media phase remains caller-owned. |
Masonry | Stable variable-height tiles placed into the shortest measured column with responsive token-backed columns and gaps. |
Auto-growing TextArea | Bounded autosize measures shaped visual rows, preserves editor state, and scrolls after the maximum row bound. |
Source Editor | One no-wrap TextArea projection adds hard-line numbers/geometry, revision-safe caller highlights, and caller-owned indentation without duplicating buffer, caret, IME, history, paint, or hit testing. |
Bubble | Neutral caller-owned message surface with placement, grouping, max width, safe content, and optional actions. |
Carousel | Controlled stable-item track with previous/next/direct selection, keyboard navigation, clipping, reduced motion, and truthful state phases. |
Ordinary MultiSelect, TransferList, ImageList, and Masonry instances
are intentionally non-virtualized components. Their option/tile sets are
caller-owned and should be kept to a bounded presentation size; very large
datasets must use List, DataGrid, or another caller-owned virtualized
surface. This is a documented performance boundary, not an unimplemented
component API.
These are distinct from cross-library or product patterns such as a confirmation popover, product tour, sticky/affixed content, QR code, or watermark. They may be useful additions, but they are not MUI Core entries and must not be reported as MUI coverage gaps. Their priority should be decided by desktop product demand and by whether GPUI has the required framework primitive, not by inflating a benchmark score.
Document tabs, SearchField, FindReplace, NotificationCenter, CodeView,
and UploadList are covered above. FailurePanel presents an ordinary
caller-owned failure; a render error boundary is not implementable while GPUI
rendering is infallible and its draw arenas are not unwind-safe.
Toggle, ToggleGroup, Collapsible, HoverCard, Menubar, CopyButton
and AspectRatio are covered above. Three of them were built on top of what
was already here rather than beside it, which is the whole reason they are
small: Collapsible is an Accordion with one section, Menubar is a row of
Menu views with the row's own three behaviours added, and Toggle is a
Button that publishes a checked state. Two of them state a limit rather than
inventing an answer, and docs/components.md carries both — what CopyButton
can and cannot know about the clipboard, and what a hover card's grace period
is for. Cascader, AnchorList, and DiagnosticsList are also covered above;
they compose the existing popover/menu, navigation, list, filter, badge, and
status vocabulary instead of creating parallel application infrastructure.
Mentions in a text field and settings search now share Kit contracts.
MentionInput owns trigger detection, querying, stable candidate focus,
caret-anchored presentation, and completion insertion while callers own
candidate retrieval, stable identity, exact replacement text, and the semantic
attachment of accepted identity to plain text. SettingsList filters sections
and rows through the installed locale matcher, includes visible row copy and
explicit caller-authored aliases/control vocabulary, preserves the settings
page's familiar order, counts the result, and presents a distinct no-match
state. UndoHistory covers the caller-owned revision list and reports restore
intents without keeping or mutating an undo stack. The non-rendering
reactive::History<T> is the bounded undo/redo record store beneath callers
that need one: ignored records are refused by the model, a divergent push
clears redo, and the caller remains responsible for applying every record.
PerformanceHud covers live diagnostics without turning the observer into the
workload: framework FrameTimingMonitor filters and bounds existing per-window
submissions, while the controlled Kit view presents the caller's latest summary
and never schedules another frame. Framework records preserve every draw for
benchmarks, then pair the latest newly drawn scene with platform submission and
its coalesced top-level input. The pairing survives concurrent trace leases and
cannot cross a disable/re-enable boundary. It intentionally stops when the
synchronous platform draw call returns: compositor/display presentation,
upstream journal/hang reporting, and the debug overlay remain outside this
port.
Application forms now cover date, time, range, files, and repeating sections.
Date facts come from DateAdapter; file admissibility and display names come
from SchemaFilePolicy, while the host still opens the OS picker. Repeating
sections keep stable visual identity and nested values without owning product
data. Host-declared conditional rules resolve into FieldVisibility: visible,
or hidden with Omit / Include submission policy. Hidden fields and subtrees
cannot become invisible validation blockers. values() remains the complete
held-value inventory while submission_values() applies that explicit policy;
the form never owns the condition or removes caller data.
LineChart and BarChart now cover the cartesian presentation gap with keyed
motion, area fills, pointer and keyboard crosshairs, exact host-formatted text,
and stale-data retention. Their existing normalized entry points require
caller-prepared coordinates and axis wording. CartesianChart additionally
provides raw f64 numeric/category/time scales, mixed layers and separate value
axes, orientation, caller ticks, bounded custom glyphs, controlled exploration
and keyed raw motion. Shared immutable input, explicit extrema path sampling
and exact rectangle hit lookup reduce CPU work without replacing source
identities. Full semantic publication and enabled motion remain linear in
input size; the chart contract records measured limits.
Plot supplies the lower generic measured frame and semantic
mark traversal. CandlestickChart and SankeyChart render normalized OHLC and
flow geometry through that boundary. SankeyData::layout additionally provides
an optional validated DAG layout with one weight-to-height scale and explicit
alignment and optional deterministic crossing-order improvement. RawOhlc
adapts validated open/high/low/close readings to shared Cartesian intervals;
SpecializedChart provides raw hierarchy/distribution/range/waterfall layouts,
and ContinuousHeatmap supplies explicit continuous/diverging domains with
missing-value and localized status handling. None owns market data or queries.
TraceView and SpanTimeline add controlled raw-time viewports and virtual row
windows while retaining an input-sized hierarchy scan. Sized graph layout and
complete route-cache inputs preserve controlled geometry; large sparse layout
tests do not establish a responsive 10k-node editor. See graph/trace evidence.
GeoMap renders immutable caller-supplied projected polygons/holes and points,
with controlled selection/camera, localized refusal and measured geometry
targets. Its projection subset excludes tile/network services,
geodesic topology, antimeridian cutting and a general GIS engine.
Agent and game applications now have product-neutral run, persona, party,
objective, ability, and reward families rather than one-off downstream cards.
ApprovalPrompt, PermissionMatrix, CostMeter, and the context gauge keep
approval, scope, and estimated costs explicit. OfferingCatalog covers Tool,
Skill, and Resource results together rather than creating separate
ToolCatalog and SkillCard APIs; per-server attribution is part of every
result because two servers may offer the same name. LogStream, DiffView,
JsonView, SchemaForm, ServerList, OfferingCatalog, and NodeGraph are
covered above.
NodeGraph remains the lower-level graph whose caller supplies coordinates.
AgentRunCanvas is the high-level run composition for a caller that has only
typed topology: AgentRunLayout supplies its deterministic layered,
RTL-aware placement. A product-specific force layout or manually persisted
coordinates can still use NodeGraph directly without creating a second run
presentation contract downstream.
Controlled interaction boundaries
Tree and TreeGrid horizontal navigation only selects enabled direct relatives: entering an expanded branch skips disabled direct children but never selects a grandchild or a neighboring root; a disabled parent refuses upward selection. These rules are exercised in both reading directions. Disabled MultiSelect publishes disabled root and chip semantics and mounts no chip remove buttons.
DockTree accepts a drag only when its exact source tab surface exists in the caller's topology and the dragged panel belongs to that stack. Dock roots may be prefix-related; their fully composed semantic surface ids must still be unique within the window, like other semantic identities.
The window modal stack owns Dialog/Drawer focus restoration, including non-LIFO close and the return chain after a covered modal closes. Drawer releases modal ownership at close; its delayed exit removes paint without restoring focus a second time. Callers must close a modal before removing its entity: arbitrary unmount cleanup is not yet an implemented lifecycle contract.
NodeGraph reconciles active gestures against current caller-owned nodes and interaction mode on redraw. Deleted peers are removed without rebasing surviving drag origins; deleting the active target or revoking its interaction cancels the gesture. Fit prefers native scale but the configured zoom range prevails over that preference, and the same legal zoom determines its offset and event.
Kit's shared paint-only pointer-cancel listener uses GPUI's MouseCancelEvent, not a synthetic release. Custom graph, grid range/resize, split, drawer resize, text-selection, image/model, transport, scrollbar, and drag-session state is abandoned without drop, click, or seek completion. Deterministic regressions dispatch cancellation twice and verify subsequent gestures; they do not claim physical-device/browser delivery coverage beyond the framework platform lanes.
Capabilities that are not components
| Gap | Why it matters |
|---|---|
| Grayscale glyph compositing | Metal now applies the same DirectWrite-derived contrast/gamma alpha correction as Direct3D and WGPU when tinting atlas coverage. Glyphs remain reusable grayscale masks, so destination-aware Core Text / AppKit smoothing is still not available. macOS stays grayscale-only. Linear atlas sampling can still soften 12–13px geometric faces; changing that filter would also affect monochrome SVG icons that share the same sprite path. |
| Complete copies across unmounted text | GPUI now coordinates one grapheme-safe selection across separately mounted StyledText participants in caller-declared reading order, with pointer capture, keyboard copy/select-all, AccessKit text runs, and overlay scope isolation. AgentDocument, CodeView, Markdown, LogStream, DiffView, and HighlightedText participate. A selection crossing virtualized rows copies the mounted text and reports that it is incomplete; whole-value component copy actions remain the path to content GPUI never laid out. |
| Text range highlighting | HighlightedText, LogStream, CodeView and DiffView render caller-supplied ranges while constructing their text. GPUI still has no API that marks a substring of an arbitrary already-rendered text element, which blocks a generic find-in-page overlay. |
| Writing direction | LayoutDirection supplies logical row order, start/end spacing and borders, text alignment, directional glyph mirroring, and reading-order keyboard traversal across controls, navigation, menus, calendars, trees, structured views, and schema forms. Unicode bidi shaping keeps mixed Arabic/Hebrew, Latin, punctuation, and numbers in logical order. Host-owned localized copy, locale formatting, and a larger language-specific bidi corpus remain integration work rather than component geometry. |
| Number, date, and quantity formatting | NumberAdapter owns every library-authored numeric shape — grouped counts and decimals, editable parsing, plural category, count-of-total, percent, multiplier, dimensions, ordinals, signed deltas, lower bounds, and affix placement. Strings owns every phrase and its zero/one/two/few/many/other variants. Dates remain the parallel DateAdapter contract. See "Numbers a catalogue cannot fix alone" below. |
| Assistive technology gaps | Basic semantics, grapheme-based editable and read-only text runs, shared shaped character/caret geometry, selection actions, explicit live-region properties, same-window labelled-by/described-by relationships, and deferred-overlay active descendants now reach GPUI's AccessKit platform tree. macOS and Windows natively verify relationship-derived field name/help and editable character/caret geometry; Windows additionally verifies ValuePattern editing and MenuItem focus/invocation/lifetime. Cross-tree completion focus is deterministic only. Native-child handoff, platform live-event verification, remaining Windows overlay/event sessions, and Linux AT-SPI validation are active foundation work; see docs/accessibility.md and docs/foundation-roadmap.md. |
| Validation vocabulary | ValidationState is the caller-owned Pending / Validating / Invalid { reason } / Valid ladder. FormField presents it without painting in-flight work as failure; SchemaForm keeps field and whole-form validation separate and blocks submission while an explicitly managed check is pending or validating. Rules and timing remain host-owned. |
| Schema field participation | FieldVisibility records the result of a host-owned condition without evaluating it. Hidden fields are absent from rendering and field validation; HiddenSubmission::Omit removes the subtree only from submission_values, while Include preserves its complete held subtree. values stays lossless, and a hidden object or repeated-list parent governs every descendant. |
| Settings search | SettingsList takes the query a host commonly receives from SearchField and owns matching, filtering, result counting, and the no-match state for complete SettingsSection builders. Label, description, badge, displayed value, management reason, section context, and SettingsRow::search_terms all use the installed SearchMatcher; text hidden inside an arbitrary caller control must be named explicitly. Matches retain section and row order rather than turning preferences into a ranked command palette. |
| Settings grouping | SettingsSection is a headed group of rows on the page plane. It no longer paints a raised card around the rows; heading, spacing, and each row's own padding carry the group. Only the control the typist can operate keeps a bounded surface. |
| Composition | Slotted lets a caller replace a node a component authored rather than only configure it. A component publishes only positions its public state model can actually reach as SLOTS, and a name outside that list panics rather than silently rendering nothing. Surfaces with loading and failure phases offer those distinct slots; an empty-only collection offers only empty. No component yet slots a node that is not a whole-region state. |
| Size response | Responsive builds its content from its own measured width, so a component laid out in a sidebar and in a full-width page arranges itself differently without either of them consulting the window. ContainerSize reports Unmeasured for the one frame before there is a width rather than guessing at one. Grid and Container now provide the shared token-backed breakpoint vocabulary; Toolbar measures its cut from the widths it recorded last frame; overflow_after remains for a caller who already knows. The remaining framework boundary is response to a size a component cannot itself be given, such as the width of a sibling. |
| Style escape hatch | ThemeOverlay installs a caller-adjusted Theme for one subtree and pops it afterwards, in every element phase, so an override cannot reach a sibling. What comes back is a whole Theme, so the subtree still reads a complete token set and a component inside it cannot tell it was overridden. There is still no way to override one property of one instance without constructing a theme for it, which is deliberate: a per-instance colour is how a library stops being one. |
Non-virtualized Table and Tree | List, DataGrid, and MessageList virtualize. These two lay out every row. |
Numbers a catalogue cannot fix alone
gpui_kit::strings closes both halves of localization without making a
component infer either one. Strings places facts inside phrases and selects
host-installed zero, one, two, few, many, or other variants.
NumberAdapter shapes the facts themselves: grouped counts and decimals,
editable decimal parsing, count-of-total, percentages, quantities and affixes,
image dimensions, ordered-list markers, signed deltas, lower bounds, and
playback multipliers. NumberInput writes and reads through the same adapter,
so a localized value never becomes unparsable merely because the control drew
it. Pagination, search, grids, document views, media, navigation, forms,
canvas, game, and agent surfaces all use that boundary for the numeric facts
they author.
That does not make the library the owner of every number it receives.
Caller-authored labels, clock readouts, currency and cost strings, source text,
terminal output, paths, identifiers, and diagnostics remain verbatim because
re-parsing them would change caller-owned meaning. Numeric geometry used only
for layout, rendering, hit testing, stable ids, and debug output is not reader
copy and is not localized. Date facts remain on the parallel DateAdapter;
the host still owns its calendar, locale, and time zone. The built-in English
adapter is a complete fallback, not a claim to discover the host locale.
Delivery
CHANGELOG.md and the versioning policy in README.md now say what a consumer
pins and what breaks them, including the two breaks the compiler cannot see: a
token key and a semantic id. The publishable crates are a crates.io cohort;
GPUI Box is no longer a git dependency of itself. An enforceable structural
and calibrated timing budget is active foundation work in
docs/foundation-roadmap.md; until that phase lands, virtualization behavior
tests still do not fail on every class of slowdown.
The hosted catalog at gpui-box.origingame.dev is the published documentation;
it is deployed from a checkout and is not itself a crates.io release.
The visual regression gate is headless check. It renders offscreen at a
fixed device-pixel size, so it does not depend on a composited, frontmost
window or the host display. Linux (llvmpipe) is compared at every commit by
gate full on the orb; macOS (Metal) and Windows (WARP) are compared when the
dispatch-only Platforms workflow runs, which is on demand rather than on
push. docs/screenshot-testing.md describes the gate and review workflow.
Rules every covered component follows
- The answer belongs to the caller. A component holds hover, focus, open, and animation state; a value, a selection, a sort, and an expansion belong to the host, which is why every one of them reports an intent instead of applying it. Where a component needs a fact it cannot hold either — what day it is, what a month is called, whether a range's days can be listed — it takes an injected reader for that fact rather than deriving one, and a reader that answers "I don't know" is answering.
- A refused or disabled control installs no handler at all.
- Loading, empty, unstarted, unavailable, and failed are distinct, and a refusal is never rendered as an absence of data. A question nobody could answer is distinct again: a calendar with no month to show renders unavailable rather than blank, and a range whose days could not be enumerated reports unchecked rather than clear.
- Ids come from business identity, never from list position.
- Anything visible comes from tokens. Wording that belongs to the host — a
refusal's reason, a month's name, a message saying why text could not be
read — is shown verbatim and never authored by a component. Wording the
library does author comes from
gpui_kit::strings, so a host replaces it without forking the component;cargo run -p xtask -- strings checkfails the build if a component grows a literal a reader could read. - A component that can carry a credential publishes its shape, never its text.
Headless capture order dependence — glass probes
Metal review on 2026-09-08 found a reproducible difference between the complete
catalog and the selected sequence actions cascader context-menu dialog drawer form glass mention-input menu menubar multi-select notification-center overlay toast (executed in catalog order). In the glass budget capsules/shadows,
dark has 80 pixels differing by more than one channel step (maximum 2), and
light has 1,623 (maximum 4). Repeated complete runs agree byte-for-byte;
headless check glass alone also agrees with the complete run. Full-catalog
captures remain authoritative; the one-step comparison tolerance is unchanged.
Temporary input traces ruled out surface/lobe admission drift: all 16 admitted
surfaces have identical bounds, lobe counts and ordering in both runs, and only
budget capsules A–G are admitted. H/I are opaque fallbacks in both. The difference
is their probe ownership: complete/standalone runs assign H/I slots 14/15 with
None, while the selected sequence reuses slots 3/4 and reads the previous
scene's Some(0.92572516) in both themes. That value changes glass_shadows.
The former Kit ProbeLease::slot/Drop allocated/released slot numbers
without invalidating renderer samples. Keyed state has a two-frame retention
grace. Metal read_probe_values overwrites only requested slots, so an
unadmitted replacement surface never refreshes its inherited value. The shared
headless window/renderer and two-identical-frame settling test cannot detect a
stable stale sample. This is a located product probe-lifetime defect, not merely
atlas rounding: opening and closing overlays can give a replacement fallback
surface another surface's shadow strength.
The product fix moves ownership to framework LuminanceProbeLease and
LuminanceProbeCache. The existing u32 material probe is an opaque ID with
4 physical-slot bits and 28 generation bits. Reacquisition changes generation;
exhausted slots retire permanently instead of wrapping or issuing the all-ones
sentinel. Renderers decode only GPU offsets, retaining full IDs for cached values.
Each submitted frame registers its actually encoded probes, including an empty
set: fallback/unsubmitted surfaces read None. Callbacks carry the submission
sequence and full ID, rejecting old owners, previous activations and out-of-order
older samples. Continuously active owners keep their latest completed reading;
Metal adds no GPU wait. All three backends use this shared CPU cache without
changing shader or GPU buffer layouts. No renderer-per-scene reset or tolerance
increase is used. Portable tests cover reuse, fallback, late callbacks and
generation exhaustion; a Metal test covers actual GPU submission and slot reuse.
Metal verification: the 14-scene run and complete catalog both pass the existing
one-step gate, and the complete catalog is 312/312 without replacement baselines.
The original H/I shadow differences are gone. Whole glass frames are not
byte-identical: the separate image-atlas rounding below remains.
The strict cross-platform reproduction uses the native Metal renderer on macOS and real software WGPU adapter on Linux/Windows, with no baseline writes:
cargo run --manifest-path tools/headless-visual/Cargo.toml -- check-order glass \
actions cascader context-menu dialog drawer form mention-input menu menubar \
multi-select notification-center overlay toast
cargo test -p gpui-box-wgpu a_reacquired_probe_requires_its_own_admitted_wgpu_submission -- --nocapture
check-order compares raw RGBA bytes, reports exact equality, differing-pixel
counts, maximum channel step and inclusive bounds as JSON, and saves both target
frames under target/headless-order-check/{full,scoped} and an amplified difference
map under diff-x64. It fails even on the
known one-step atlas difference: it is a diagnostic, not the tolerant catalog
gate. The WGPU lease regression requires an adapter on Linux/Windows rather than
silently skipping there; only a Metal host may skip the software WGPU execution.
Image-atlas placement precision — separate from probe ownership
After the lease fix, selected/full glass frames differ at 80 dark pixels and
84 light pixels, all by one channel step (none above one). Dark bounds are
(1179,504)–(1430,534), light (1158,504)–(1474,538), in the Regular-on-media
card's top rim/shadow, not budget capsules H/I. The comparison tolerance is not
increased and these frames are not described as byte-identical.
Captured CPU inputs rule out probe ownership: both runs publish the same
Regular-media luminance sequence, ending at 0.49284562 in dark and 0.5068491
in light. Sprite bounds are identically (816,448)+(768×288) device pixels.
The identical 384×144 image occupies polychrome texture 0 at tile (48,0)
(TileId 4096) in the selected run, versus (48,368) (TileId 4100) in the full
run, with zero padding in both. Metal's polychrome vertex shader adds this
atlas origin before normalized-UV interpolation; its fragment linearly samples
those UVs. This is placement-dependent image sampling precision, not a leftover
lease value. It is visible at the material/shadow compositing rounding boundary.
Atlas placement is already integral. Removing the half-texel source-rectangle inset would break texel-centre semantics, not correct this precision contract. A future fix needs tile-local interpolation and translation-invariant sampling, with source rectangles, transformed sprites, edge filtering and all three shader backends verified. It potentially changes every image/textured-sprite baseline on Metal, Linux WGPU and Windows; it is not hidden in probe lifetime or addressed by a headless-only atlas reset.
Clear Pill geometry and dimming order
A 200×41 Clear Pill above an image reproduced the downstream failure on Metal:
raw and interior pixels were both [100,80,60]. Kit passed the Pill token's
999 radius directly to raw quad/backdrop paint, while the styled child fitted
it to half the shortest side (20.5). The oversized SDF discarded both dimming
and optics. BackdropLayer and collected GlassPane lobes now use the framework's
existing clamp_radii_for_quad_size, matching their styled surfaces without
changing raw framework paint semantics.
Fitting the radius exposed a separate ordering dependency: the same-layer
black quad attenuated the completed optics, producing [80,66,53]. Clear
dimming now multiplies transmission gain by 1 - effect.glassDimming inside
the material, before wash/lift/highlights. No extra quad, shader ABI change or
product workaround is needed. The pixel regression renders a clipped rounded
card, Cover image, absolute Pill and ghost Xs button with the platform's native
headless renderer. It checks the independent equation
raw × 0.65 × 1.042 + 255 × 0.075, yielding [87,73,60], the retained rim,
and untouched pixels outside the rounded arc. Flat elevation isolates material
math from shadows. Run it on Metal or Linux/WGPU with:
cargo test --manifest-path tools/headless-visual/Cargo.toml \
clear_pill_dims_media_inside_a_clipped_card -- --nocapture
Unicode line breaks across text and inline objects
Both LineWrapper::wrap_line and shaped LineLayout use the Unicode 15.0
UAX #14 opportunities from the existing unicode-linebreak 0.1.5 package.
Wrapped truncation obtains its line starts and continuation indentation from
wrap_line, rather than keeping another word-character table. Single-line
ellipsis fitting still measures characters; it does not choose word breaks.
Text fragments are concatenated only for classification, with each inline
element represented by U+FFFC. The width walk keeps separate normalized-text
and original byte offsets, so an element's arbitrary len_utf8 never shifts
the returned caller indices. Shaped glyph runs query the same Unicode rules
at their logical byte indices; a font/run boundary is not a break opportunity.
Tests force overflowing closing punctuation and opening brackets, cross text
and element boundaries, preserve Latin words and nonbreaking glue, exercise
CJK/Latin mixing, and check mandatory breaks and ellipsis run lengths.
Emergency character/item wrapping remains the fallback when no legal break
fits: extremely narrow widths can still split an otherwise unbreakable unit.
This is not punctuation hanging, width expansion, dictionary-based breaking
for complex scripts, or a locale-tailored Japanese typography engine. The
markdown exhibit includes a narrow mixed Chinese/Latin paragraph, rendered
with the bundled Noto Sans SC fallback, to review punctuation and word wrapping
in both themes without depending on system fonts.
Intrinsic image sizing is separate from object fitting
The apparent missing image corner mask was a layout error shared by Metal and
WGPU: a 480×144 image in an 880×220 size_full card acquired an 880×264
layout box. Its lower rounded corners fell below the parent's rectangular
clip. The atlas upload and shader coverage retained the correct radii.
Window::request_intrinsic_layout supplies natural dimensions separately
from authored styles. An Intrinsic node is a replaced leaf: its content has
a natural size independent of text flow, currently an already-loaded Img.
Loading/error image replacements remain ordinary elements; text and other
arbitrary measure callbacks remain stock measured leaves. The GPUI layout
view uses Taffy's existing container
algorithms and cache, but resolves intrinsic leaves before the stock leaf's
aspect-ratio height floor can overwrite a determined axis. Ordinary measured
text still uses the stock leaf. There is one active result map, retained with
the tree cache and cleared with it; no stock high-level layout call runs in
parallel with this view. No renderer format or dependency authority changes.
The sizing matrix exercises block, row/column flex, grid, absolute insets, resolved and unresolved percentages, explicit aspect ratio, natural sizes, padding and size constraints. A pure measure callback was insufficient in block final layout: width 240 with auto height became 240×144 instead of 240×72. Injecting the natural ratio only for one auto axis fixed that case, but width 240 with max-height 36 became 120×36. Both are regression targets, not accepted approximations. A zero-size asset has no natural ratio.
The cross-platform headless pixel regression uses 480×220 and 880×220 Cover
cards with rounded images and a bottom Clear caption. It checks all four
corners against the outside ground, and passes on both Metal and Linux WGPU
real adapters. Kit adds no mask or component-specific sizing workaround.
cinematic-effects additionally reviews
Contain: a 240×140 frame in a 372×154 slot remains centered at 264×154 rather
than forcing the slot to the image's intrinsic ratio.
Incremental source-text foundation
The shared EditBuffer uses persistent storage and indexed LF lines/UTF-16
conversion. Grapheme clamping borrows rope chunks, including unbounded
combining and regional-indicator context. Tests exercise 200,000 source lines
and a four-million-byte single line without materializing a compatibility
string on edits. Legacy text() still explicitly materializes a whole value.
No-wrap TextArea and Editor shape visible rows, with exact offscreen geometry
shaped on demand. Whole-document selection paints only visible rows, and
accessibility cell geometry captures only paintable bytes. Shaping budgets
measure actual input bytes and lines (EditableTextWork), not allocation
counts. Dense and lazy Unicode/bidi geometry are compared directly.
TextArea Change and Editor Changed carry persistent EditSnapshot values;
subscribers opt into contiguous whole-document text with snapshot.text().
The internal Editor subscription preserves the snapshot rather than forcing
legacy string materialization. This is an API migration for consumers that
previously accepted strings. painted_lines() returns owned shared lines;
new event variants and work-counter fields also require consumers with
exhaustive matches or struct literals to migrate.
Soft-wrap retains exact paragraph shapes across revisions and viewport changes. Cold layout and font/width reflow shape all paragraphs; a local edit reshapes the changed paragraph, but rebuilds document-wide wrapped and row indexes. Warm scrolling reuses both shapes and indexes. Accessibility receives rows from that same prepaint, avoiding a hard-row to wrapped-row topology reset after an edit. Folded layouts retain full-source accessibility rows, including hidden text. Mounted tests distinguish first-edit publication from the next settled draw rather than hiding rebuild work behind a warm frame.
Static native parents retain complete Value, and identical visible TextRuns are reused. Row arrays and grapheme representability are revision-keyed. These caches do not bound total frame or edit work: stock native tree metadata, changed selection/value publication, explicit full-value queries, and source snapshot materialization still have document-sized costs. A very long visible hard line or wrapped paragraph is shaped whole. No native Value/AXValue is omitted to meet a budget, and no local AccessKit fork is assumed.
Multicursor/rectangle edits, source folding, and caller-owned language services have their own behavior tests and exhibits; their presence does not establish a viewport-bounded large-file editor. Shaping, parser, row-index and publication counters report their named work only, not total allocations or native backend costs. Linux headless evidence does not replace the native platform lanes.
Native browser engines remain independent of Kit
gpui-box-webview supplies caller-owned Wry engine hosts: WKWebView, WebView2,
and WebKitGTK on X11/XWayland. It renders full browser HTML/CSS, independently
of native Markdown/HTML text rendering. BrowserPanel remains a caller-owned
shell; no browser transport enters Kit. The native example and executable
smoke live in crates/gpui-webview/examples; see the host contract.
The Linux framework now attaches real X11 children, crops native pixels and input through a separate rectangular parent, allocates the full toolkit viewport, and restores the child on detach. X11 native tests cover geometry, DPI, pointer hit testing, stacking and ownership. Native Wayland embedding, X11 GPUI scene overlays above native children, arbitrary subtree transforms, rounded native masks and alpha compositing are deferred framework gaps, not emulated browser support. macOS/Windows native focus, IME, accessibility and overlay behavior still need platform-run evidence. Origin-aware asynchronous permission approvals are not represented by Wry's kind-only callback; current host policy refuses surfaced permission, popup and download requests.
Native editable geometry preserves affinity, not missing font data
EditableTextLayout exposes selection fragments, cluster-edge carets, visual
navigation and Unicode paragraph base-direction queries over the retained
WrappedLine glyph cells and soft-wrap indices. NativeTextPosition retains
both incident edges at bidi and wrap boundaries; NativeTextSelection stores
the primary anchor/head affinities atomically in EditBuffer and its history.
Legacy logical selection setters reset both affinities to downstream. Secondary
cursors remain logical-only; native primary selection clears them. Active IME
cancellation restores the original selection and text, including in secret
fields, without enabling completed secret undo/redo history.
The asymmetric geometry tests supply explicit shaped glyph positions and exercise mixed bidi travel in both directions, wrap affinity, actual row heights, graphemes, UTF-16 surrogate rejection, reversed selections and history. They prove platform-independent geometry/model behavior, not UIKit or Android execution. Native adapters and the macOS/Windows lanes need their own evidence. No renderer or component painting changes are implied by these additive APIs.
ShapedGlyph has no font-provided ligature caret table or per-grapheme advances
inside a multi-grapheme shaping cluster. An interior caret query, or horizontal
movement requiring that caret, returns None; selection covers the complete
painted cluster. Supplying GDEF/platform caret data at the shaping boundary is
the prerequisite to lifting that limitation; interpolating a width is not a
substitute. The current layout paints uniform-height horizontal rows. Paragraph
direction overrides are not in the editable model, so mutation stays explicitly
unsupported. Native caret bounds resolve only the incident hard paragraph,
using indexed UTF-16 conversion for source-backed layouts; painting a visible
caret preserves the viewport shaping budget. Point, selection-fragment,
farthest-position and movement queries still enumerate the document and may
shape offscreen lazy lines; those are not viewport-bounded operations.
Reference contract: Apple UITextInput.
Virtual-root reflow and ambient offsets
UniformList declares its measured row placement and scrolling separately through Window's placement scope. Position FLIP observes slot changes while ignoring scroll and ancestor slides; paint/input/accessibility still share the original element placement. Framework tests cover padded nonzero-origin slots, scrolling, nested placement and transient offsets. Trace mounted regressions cover accepted and refused hierarchy changes, interrupted reflow, ordinary/virtual row paths, new virtual identities, reduced motion and unchanged current readouts.
Variable-height List and custom root adapters retain their prior offset semantics until they declare authoritative content-space placement. This is a documented motion-coverage gap, not a claim of generic virtualizer reflow. Trace removed rows have no paint-retained exit; their live authority retires at once.