Fixes R4.7 round 2: a heading change or split is parsed again and refused as Misread when its text, formats or kind would change; closing hashes and block openers are escaped at split ends; task boxes below a marker stay; indented code is written from its value; alerts unwrap past blank and lazy lines; delete counts every gone opener. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
77 KiB
AGENTS.md
Ely GPUI Component. A component library for GPUI, in light and dark.
Stack
- Rust 1.95, edition 2024. One crate:
ely-gpui-component. - gpui, gpui_platform and gpui_web from crates.io's
gpui-presnapshots at=0.3.8, Zed at 279fe07, the pin GPUI Kit 0.7.1 holds; each keeps its crate name throughpackage. gpui takesstacker, which keeps deep layouts off the stack's end; gpui_platform takesfont-kit, without which macOS draws no text. Featureruntime_shaders, on by default, turns on gpui_platform's: machines without Xcode lack the Metal compiler. - Assets, embedded with
rust-embed: Lucide 1.48.0 icons (ISC), Inter 4.1 (regular, medium, semibold, italic), JetBrains Mono 2.304 and IBM Plex Sans Regular (OFL). Plex sits unmodified, its name reserved, where gpui's svg renderer loads it: gpui_web answers.SystemUIFontwith it, and svg text falls back to it on the web. - Catalog data:
emojis(Unicode emoji),isolang(ISO 639, native names),isocountry(ISO 3166),iso_currency(ISO 4217, also minor units for money). - Codes:
qrcode(QR) andbarcoders(Code 128, EAN-13), both MIT OR Apache-2.0, default features off.rqrr0.11 ((MIT OR Apache-2.0) AND ISC, default features off) reads QR codes from the host's frames. - macOS extras (tray icon, Dock badge) call AppKit through
cocoa0.26 andobjc0.2, the crates gpui already links. - Web views:
wry0.57 (Apache-2.0 OR MIT, default features off, macOS only) lays a child WKWebView over the window. - Terminal:
alacritty_terminal0.26 (Apache-2.0, default features off, native only) for the grid, its parser and the pseudo-terminal;futurescarries its events.vte0.15 (Apache-2.0 OR MIT), the parser it re-exports, readsAnsiText's codes alone, so colored output builds for wasm32. - Diffs:
similar3.2 (Apache-2.0) for line and word diffs and three-way merges; itsunicodefeature splits words at punctuation. - Markdown:
markdown1.0.0 (markdown-rs, MIT) reads GitHub's flavor and math into a syntax tree with byte positions forMarkdownRenderer,MarkdownEditor, the highlights, block ranges and outline.MarkdownRendererdraws display math throughtypography::Latex, a```mermaidblock throughdocuments::Diagram, and pictures from the owner's folder (base) ordata:addresses (base640.22, MIT OR Apache-2.0); web pictures stay unloaded. Inline math shows as TeX: gpui text carries no inline pictures. Inline it reads as Typora's defaults::shortcode:turns to its emoji as GitHub reads it (theemojiscrate's names, GitHub's today, so:cricket:is the insect; read after escapes and references, so\:smile:turns too; inside an autolink or<code>it stays text; within one DOM text node, which filtered tags do not split, the first:name:-shaped run decides for all: blank text or a space before it turns every shortcode, else none, sox:smile: :smile:stays), inline HTML'su,b/strong,i/em,s/del/strike,code/kbd,mark,branda hrefdraw as what they mean GFM's filtered tags (script,style,title,textarea,iframe,xmp,noembed,noframes,plaintext) show as text, and other tags, hyphenated names too, vanish, leaving their text; a tag's name runs to white space or/, as HTML5 reads it; attribute values split on ASCII whitespace alone and decode as HTML decodes them; each Markdown container is the element GitHub writes (em,strong,del,a), on one stack with the tags and repaired as HTML5 repairs: an end tag closes the innermost element of its name and all above it, of which only formatting elements (a,b,code,em,i,s,strike,strong,u) reopen, a newaends the open one and anawithouthreflinks nothing; other tags are not tracked, and</br>breaks as<br>does; and a footnote reference is its number in superscript, numbered by first reference, as its definition is; one~still strikes. At the root, blocks read as GitHub shows them: a quote whose first line is plain text reading[!NOTE],[!TIP],[!IMPORTANT],[!WARNING]or[!CAUTION]in any case, after escapes and references, spaces and tabs around it, with more after it, is an alert under its title (Important in the palette's violet,syntax.keyword, read at 4.5:1); a paragraph that is[toc]alone, as Typora reads it, lists every heading by depth, and a press on one reveals it as a link press does (MarkdownDocument'sreveal), the heading found whereCodeEditoranchors carried it through each edit since the read, gone if an edit or an undo took it, and a secondary press acts on press, since a block writing back moves the list; front matter shows in a box without its fences; an HTML block shows its text with the inline tags above, white space collapsed and owed as a browser lays it, block elements breaking lines and closing what opened inside them, HTML5's start tags ending an openp,fieldsetandmenutoo, and anlistart anli, addordtstart either, as HTML5 ends them, comments dropped, the filtered tags shown as text, and an unclosed tag dropping the rest; a rule is a 2pxborder_strongline; bullets go •, ◦, ▪ by list depth, ordered lists counted. Exports read a tree as the drawing does throughMarkdownReader(blockgives itsMarkdownBlocks,inlineits text,noteandlabelits footnotes,headingsthe[toc]list), one fresh stack per paragraph, heading or cell.MarkdownDocumentholds a whole file in oneCodeEditor(text and undo, colored byCodeEditor::highlighterwithmarkdown_highlights): visual draws the file's top-level blocks in a gpuilist, a pressed block opening as its source in aTextInputwhose typing is the document's text at once and whose write-back is one undo step; source is the editor; preview draws without presses. A written-back block, a mode switch or a new text is read at once up to 128 KiB, longer ones on the background executor; typing in source or split reads 300 ms after the last key, off the main thread (markdown-rs grows with the square of its nodes, not its bytes: in release, 128 KiB of one-letter paragraphs takes 1.7 s, 16 KiB 42 ms, while 128 KiB of prose takes 20 ms; a block-heavy text under the cutoff can still hold the UI thread past 100 ms, a known limit). Texts pastLARGEST_READ(2 MiB) are never read: no blocks, no outline. A new read keeps the list's blocks alike at both ends. One caret serves every mode: the editor's selection, which a written-back block moves;set_caretandset_top_lineset caret and view apart; a top line asked (numbered, so a late frame callback finishes only its own ask; new text places it again, clamped to the text as it then is) waits for the read, lands on its block, then a frame later at its share of the block's height;revealis such an ask with the caret at its line; a switch between source and the list keeps the top line, one between visual and preview keeps the list's scroll as it is. A block opens only when the read is of the text as it is now. A pressed block keeps its identity across the write-back of the one before it, and opens with the caret on the pressed character: each drawn piece keeps its written span and how it decodes (Markdown's escapes and references for text, as markdown-rs decodes them, autolink text as written, HTML's references for an HTML block's runs, none for code spans but a table cell's\|, cut as CommonMark cuts their end spaces), and is matched from its end, an equal character first, a drawn space next against a line end, decoded or not, so quote marks and indents at a line's start are never taken for text and an alert's text reads past its marker; text before an emoji lands at its piece's start; code, front matter, a link definition and an HTML block's runs read back line for line, LF, CRLF and CR alike, each drawn line ending its written one, a tab cut to spaces landing on the tab; a block's drawn positions count from its own start, so an edit before it keeps it alike. A secondary press opens nothing, an empty document's open on focus included: it writes back an open block, refuses the press if that changed the pressed block's kind, text or drawing (a definition's new address too), moves the caret there and emitsDocumentEvent::Pointed, whoseDocumentTargetnames the block's kind, a link's or picture's address, and whether it lies in a table or code.MarkdownDocument::formattoggles anInlineFormatat the selection as Typora's toggle does: inside that style it comes off; a selection is wrapped, its end spaces left out and the same style it partly covers taken in; a caret takes its word, or an empty pair between stop characters or on a blank line;Cleartakes every inline mark off the blocks it touches, inline math's$, footnote brackets and escape backslashes too, an autolink's kept as written; code, math, HTML and front matter refuse, as does an end inside a node's marks or a link address, or a selection crossing a block, a link or another mark (EditRefusal); the toggle always reads off the UI thread, one at a time (another refuses asBusy), and lands only if text and selection are unchanged and preview is not shown, a refusal met there coming asDocumentEvent::EditRefused; past the largest read it refuses; in an open block it edits the field, which stays open, and joins its one write-back step.MarkdownDocument::blockruns aBlockCommandat the caret as Typora's Paragraph menu: a heading level (the same level keeps it, as Typora's menu calls it), paragraph, promote and demote (a paragraph promotes to 6, 6 demotes to a paragraph) take the caret's line alone, a setext heading past level 2 going ATX, a closing#run or a block opener left bare escaped so the text stays; math, HTML or a table take an empty paragraph after instead, code refuses; quote and list toggles unwrap the holder (an alert's marker goes, an outer opener stays), retype a list of another kind with its lines re-indented, task boxes and code indent kept, or make each paragraph line an item, a selection past a list taking each touched sibling; rule, code, math,[toc], footnote and link definition split the paragraph at the caret, bold, italic and strike closed before and reopened after, a link, code, math or HTML refusing; front matter goes on top once; an empty line comes before (a list item's or footnote's text moving below its opener) or after; delete takes the block, or the holder of a lone paragraph, a blank line kept where neighbors would join; a task's first text, which GFM reads as text, takes no block. Edits keep each line's container prefix, put one on lazy lines that must stay, and add a blank line where text would join. A heading change or split is parsed again before it is written: if any shown character or inline format would change, or the block would read as another kind, it refuses asEditRefusal::Misread; a task list over code refuses asCode, and an alert whose marker shares its line asMisread. Formats and block commands share one runner and itsEditRefusal,DocumentEvent::EditRefusedand one-at-a-time rule: an edit inside the open block edits its field; one past it writes the block back first, a second undo step. Blocks are Tab stops that Enter or Space opens; preview takes no presses, undo or redo.CodeEditorkeeps at most 64 MiB of undo and redo text, dropping the oldest undo and then the farthest redo after every record, composition, undo and redo.BlockData::from_markdownand its export still read withpulldown-cmark0.13 (MIT, default features off); it adds GitHub's alerts, a callout's form, and reads each field's inline Markdown from the source by offset, as written. - Pictures:
image0.25 (MIT OR Apache-2.0), the crate gpui decodes with, default features off; it turns decoded frames and rims map tiles. - Math and diagrams: RaTeX 0.1.14 (
ratex-parser,ratex-layout,ratex-svgwithembed-fonts,ratex-types; MIT, KaTeX's fonts OFL) typesetsLatexto SVG with outlined glyphs;mermaid-rs-renderer0.3.1 (MIT, default features off) drawsDiagram. Both go through gpui'sSvgRendererat the window's scale, loaded as gpui assets off the main thread;usvg0.46, gpui's own, reads a diagram's size. A RaTeX or Mermaid panic on parsed input becomes the element's error. Each element holds its picture throughprimitives::held: a new source, or the element leaving the window, removes the old asset from gpui's cache and drops its image from the atlas after the frame. - Maps: Natural Earth's 110m countries (public domain).
assets/maps/countries.geojson, trimmed byscripts/countries.pyto an ISO code, a name and positions to 0.01°, backsmaps::WorldMap; the gallery's tiles are cut from the same file, z0 to z3, byscripts/tiles.pywith Pillow. - Web: on wasm32,
web-time1.1 (MIT OR Apache-2.0) gives theInstantgpui's executor returns, std's own natively; jiff takesjs, rust-embeddebug-embed, similarwasm32_web_time. The gallery's web entry takesgpui_web,wasm-bindgen,js-sysandweb-sysas wasm-only dev-dependencies, and a Hebrew cut of Noto Sans Hebrew 2.003 (OFL,examples/gallery/fonts): gpui_web asks the browser for emoji and CJK alone. Thewasm-bindgenCLI at Cargo.lock's version and binaryen'swasm-optfinish the bundle. - Gallery:
examples/gallery. Website:frontend/(Vite 8.3, pnpm 11, TypeScript, React 19.3, TanStack Router 1.170 in code-based routes, Lenis 1.3), in serro.ai's language: heavy titles over thin faint text, hairlines, one accent, full-screen sections, isometric line miniatures on a canvas. Pages name a story by its title's first part (src/data.ts'sname); scroll bars stay hidden; layouts step at 1024 and 768. Each story runs live from the gallery built for wasm32 through gpui_web, in a same-origin iframe, or, where a browser cannot run it, as a native capture. The Docs tab is Markdown infrontend/src/docs/, read in itsORDERand rendered bymarked18 (MIT) insidevite.config.ts'sdocsplugin, so a bad guide failspnpm buildand the browser gets HTML alone; a fenced block taggedexample=NAMEshowsexamples/docs/NAME.rs, an examplescripts/check.shcompiles asdocs_NAME, so every full Rust sample on the site builds. The Showcase tab lists apps built with Ely from the public repoZacharyZhang-NY/Ely-GPUI-Showcaseson git.zacharyzhang.com (Gitea): manifests, PNG previews and Markdown READMEs, checked by itsscripts/catalog.mjs(marked,pngjs,twitter-text), its stars refreshed by its CI. The mark is the letter E in iso blocks, its middle arm in the accent: one geometry insrc/iso/mark.ts, drawn by the engine the miniatures use, worn by the header in the page's iso colors and by the favicon. Below 768px the header shows the mark without its word, which stays the home link's name; below 414px it drops the sound switch. Geist and Geist Mono (OFL,@fontsource-variable), Reicon icons (MIT, based on Solar Icons, CC BY 4.0, credited in the footer), simple-icons' Gitea mark (CC0). It deploys to Cloudflare Workers static assets throughnpx wrangler(4.143).
Commands
cargo run --example galleryopens the gallery.-- --page <slug>starts on a page;--story <title or slug>with it draws that section alone, and--capturethen shoots it without the page's script.cargo run --example gallery -- --capture <dir>writes PNGs of every page, light and dark, top to bottom, then each page's scripted states, including windows the demos open. macOS only.cargo run --example gallery -- --narrow 280 --capture <dir>lays each page, header and body, at that width in a padded card and shoots it without its script, whose aims are measured at full width. Demos that set their own width run past the card.RUSTC_BOOTSTRAP=1 cargo check --lib --target wasm32-unknown-unknownchecks the web build; gpui_web'swasm_threadneeds the bootstrap, as Zed's web examples do.ELY_GALLERY_ASSETS=<address of out/assets/> scripts/web.sh <out>builds the gallery for the browser, profileweb, and fails past Cloudflare's 25 MiB. It opens at?page=<slug>&story=<title or slug>&theme=light|dark; the host page callsgallery.setTheme(mode)or posts{ ely: "theme", theme }, before the wasm runs or after, and the page posts{ ely: "ready" }to its parent once it runs.- In
frontend/:ELY_GALLERY_ASSETS=<site>/gallery/assets/ pnpm gallerybuilds the gallery intopublic/gallery,pnpm devserves the site,pnpm buildchecks types and writesdist,pnpm deployrunswrangler deploy,pnpm showcasewrites the showcase catalog intosrc/showcase.jsonandpublic/showcase(both ignored): it downloads the commitshowcase.lockpins, installs that commit's packages and runs its own validator, so a bad catalog fails the build;devandbuildrun it first, andELY_SHOWCASE=<checkout>reads a local catalog instead.pnpm testruns the iso engine's, the mark's and the showcase list's tests, andpnpm localbuilds the gallery for localhost, builds the site and serves both on http://localhost:4173/ (wasm-bindgen and wasm-opt on PATH, asscripts/web.shsays).node scripts/og.mjs, withpnpm previewup, renderspublic/og.pngfrom the hero and the header's mark.node scripts/favicon.mjswritespublic/favicon.svg, which follows the system's mode,public/apple-touch-icon.pngand the repo'slogo.png(2048px, transparent) from the mark, in the colorsstyles.csssets. The site readsexamples/gallery/stories.jsonfor its chapters, stories and search. python3 scripts/stories.pywritesexamples/gallery/stories.json, every page's sections in drawing order with their components, for the site: the names in a title, and in itsSHOWNtable what a story draws beyond them. It fails on a section opened inside another and on a ticked entry with a home of its own that no story names.scripts/check.shrefuses a stale one.cargo test --lib --features test-support,cargo clippy --all-targets --features test-support -- -D warnings,cargo fmt --check.scripts/check.shruns them with the house rules and lints the gallery for wasm32.cargo test --manifest-path compat/kit/Cargo.toml --target-dir target/kitruns Ely beside GPUI Kit in one window: Tab through both, each library's dialog over the other's fields, one asset source.scripts/check.shfirst resolves it afresh and holdsgpui-kitto the newest releasecargo searchnames, so a Kit release that moves its gpui pin fails resolution there, naminggpui-pre; Ely's pin then follows. Its lockfile stays out of git.rm -rf target/debug/incremental target/kit/debug/incrementalandcargo clean -p ely-gpui-componentafter each task item keep the disk lean. Cargo keeps every old build of the crate and its tests under a new hash; by T25 they had filled the disk.scripts/icons.sh <lucide-name>...adds icons. Then add a line toIconNameinsrc/primitives/icon.rs.python3 scripts/countries.py <ne_110m_admin_0_countries.geojson>writes the bundled world;python3 scripts/tiles.py <same file>writes the gallery's map tiles.
Layout
src/<chapter>/: one folder per chapter ofgpui-components.md.src/theme: palettes, tokens,ActiveTheme.src/motion: durations, easings, spring.examples/gallery/pages/<chapter>.rs: one page per chapter, in PRD order.compat/kit: a crate of its own, with its own workspace, that tests Ely beside GPUI Kit.TASKS.mdandtasks/: progress. Every PRD entry has a line and a tag;tasks/reviews.mdkeeps every review round.
Rules
-
Colors, control and icon sizes, text sizes, radii, shadows come from
cx.theme(). No rawpx()literals inside components. -
Gaps and padding use gpui's rem scale helpers (
gap_2,px_3). That scale is the spacing system. -
Borders use gpui's fixed-pixel helpers (
border_1is 1px). Hairlines do not scale. -
One component, one home. Duplicates point to it.
-
Fail fast. No silent fallbacks. Log state changes with
log. -
A handler a component needs is an argument of
new, so leaving it out fails to compile. A state live data or the outside world can reach, a key gone from a list that changed or a failed load, logs an error naming the owner and draws a visible state, nothing chosen or an error line, and the app runs on. An assert on a caller's argument or on a fact true by construction stays; one that runtime data or input can trip is fixed. Chapters move to this astasks/panics.mdticks them. -
Files stay at or under 500 lines. Comments are one short line, and rare.
-
A repeating animation (spinner, breath, sweep) holds still under reduced motion instead of shortening, or it would spin at 1 ms a turn. An effect set off by a change (flash, shake, a bar that fades at rest) reads elapsed time through
motion::since_changerather than restarting an animation id, so what it holds keeps its state: an animation's id passes to every descendant, and a new one rebuilds their focus, drags and hover. -
motion::duration()honors reduced motion. Springs overshoot. gpui hands an easing's value to the whole animator unclamped, somotion::springgoes inside the animator, on only what may overshoot (forms::Switchsprings its thumb's travel, not its colors). -
svg()paints only with its owntext_color.Iconalways sets one. -
Buttons stay out of focus on click (
prevent_defaulton mouse down). Tab still reaches them. -
The focus ring is a 1px focus-colored border (
FocusRing). gpui paints shadows under the element, so a shadow ring floods transparent elements. Focusable elements keep a 1px border, transparent at rest. -
ely_gpui_component::initbinds Tab and Shift-Tab toFocusNextandFocusPrevunderElyFocus, the key context everyFocusScopesets. gpui ranks a binding with no context at the deepest level, so a host's Tab in a deeper context, GPUI Kit's code editor among them, lost or won by whichinitran last; under a context, the deeper one wins. Wrap the root inFocusScopewith.root()and focus its handle. A root scope takes focus when the focused element leaves the tree, as a toast's or an alert's button does. -
The gallery scripts mouse input by posting
NSEvents to its own queue, so a press takes AppKit's path and reaches a native view over the window (misc::WebView).Window::dispatch_eventreaches only gpui's listeners. gpui's normal window takes mouse moves from a tracking area, which only a real cursor feeds, so the harness sets the window'sacceptsMouseMovedEventsfor posted moves. -
gpui starts a drag on the first move past its threshold, and only the moves after it reach
on_drag_move. gpui 0.2.2's mac window replayed a held drag every 16 ms; gpui_macos replays only whilepressedMouseButtonsshows a real button, which posted events never set. SoStep::DragToposts each move twice, as the replay did: the second post of the first step reacheson_drag_move, where a component that anchors on its first drag move sets its anchor (Scrollbar, Knob, Split). -
Every shot of a gpui window, a page's or a script's, waits three of its frames through
on_next_frame: gpui presents without waiting on the GPU, and a heavy page's draw outlasts a fixed pause, so a shot could show the frame before the last step. A page's pauses stay, for work off the main thread such as pictures decoding. A covered window draws no frames, so its capture fails after 5 s instead of shooting a stale frame. -
hover()needs an element id. -
gpui takes hover at layout only on an element with an id, from a state its mouse moves keep, so
.id(..).hover(..)recolors text. Without an id,hoverapplies at paint, after the text took its color, and a mouse move alone redraws nothing. After a key press paint drops the hover and layout keeps it until the mouse moves, so a hover that sets text and background shows only the text. What shows on hover restsinvisible()and turnsvisible()throughgroup_hover(chat::ConversationItem,messaging::ChatMessage). -
Whatever draws over the page goes through
primitives::raise: at the top it defers; inside something raised it lays out on its own and draws last there, so a list opened in a dialog lies over the dialog. gpui takes a deferred draw inside another but paints them all by priority alone, so a tooltip (0) deferred inside a fullscreen dialog (1) would draw under it. A raise takes its owner's id and draws under it, so its keyed state and focus hold while other raises come and go.scripts/check.shrefuses gpui'sdeferredanywhere else. -
gpui hides hover after a key press until the mouse moves. A hover that holds or shows something (the toast stack, a carousel, tooltips, hover cards, charts, scroll activity) sets
HoverListenerMode::InputModalityIndependent, so typing under a resting pointer keeps it. A hover preview the keyboard also moves keeps the default on its items; the box that clears it on leave listens independently, so a real exit still reports, and ignores a leave that follows a key throughlast_input_was_keyboard(forms::Calendar). -
occlude()blocks the pointer for everything painted before it, its own ancestors too. Put it on the outermost box that should stop the pointer, as the toast stack does, or the ancestors' hover never fires. -
A drag that pans, turns or slides goes through gpui's drag, whose moves reach the owner in or out of its box;
on_mouse_movestops at the box's edge. gpui sends a drag's moves to every listener of its type, so each payload carries its owner'sEntityIdand handlers check it. -
gpui paints pictures without a transform, so a picture turns by its pixels:
media::turnturns decoded frames by quarter turns off the main thread, an asset keyed by the picture and the turn. gpui's loader turns a photo upright by its EXIF orientation first, somedia::ImageViewerturns from the upright frame andmedia::ImageUploadmeasures that frame. An svg turns to any angle through itsTransformation, so a control that turns shows one (interaction::Rotatable). -
A box that keeps a cursor over its items rings the item the cursor is on, not the box, as SelectableList marks its row (
interaction::SelectionArea). -
Boxes that scroll together go through
interaction::ScrollSync: the pane a wheel turned leads, and a canvas laid before the panes copies its place in prepaint, before they draw, so no frame shows them apart. -
A component that takes a starting value from its owner keeps it in
layout::seeded: a new value from the owner replaces local drags. A field that reports its words as they change keeps what it sent, so the owner's echo leaves the caret alone (mail::SignatureEditor). A cursor inside a seeded view stays while it lies in what is shown, so the owner's echo of a turn leaves it (calendar::CalendarMonthView,YearView). A number field keys the owner's value as shown: a new one waits while the field is focused and lands before the blur commits, and an echo of its own commit is no new value (forms::NumberInput). A ranged number field holds room for its widest bound throughInput::least, since a text field has no width of its own. -
The capture harness turns on reduced motion, so shots are still: repeating motion rests on one frame, and a shimmer shows no band. gpui's mac window stops its display link while occluded, so a capture advances a moving picture only on the draws the harness forces.
-
Each capture pass starts its page fresh (
Gallery::fresh): page state lives under the pass, so the dark pass runs its script from the same start as the light one. -
The capture harness finds gpui windows by handle through
raw-window-handle. AppKit popups, which gpui does not own, come from the window list by process and level. -
Posted events cannot move a macOS window, so window drags are not scripted.
drag_regionfollows Zed's title bar. -
An overlay takes focus with
primitives::take_focusand returns it withgive_backon every close path. While open, it takes focus back whenever the focused element leaves the tree (gpui'son_focus_lost). -
A state change made outside render calls
cx.notify(), or nothing redraws. A notify made while drawing, as in a canvas's prepaint, marks the view but schedules no frame (gpui'sinvalidate_view), so it also callswindow.request_animation_frame(). -
The macOS share picker draws its content in another process; a capture shows only its frame.
-
NSColorSampler runs in another process too. The harness cannot pick or cancel with it;
read_srgbis tested on a constructedNSColorinstead. -
Every text field is a
forms::TextInputentity. Its keys live under theElyInputcontext, bound ininit. Wrappers take Up, Down, Enter and Backspace first withcapture_action. -
TextInput::bounds_forreads the last layout. Text changed this frame clamps to the laid-out end until the next paint. -
The harness types with
dispatch_keystrokethroughAnyWindowHandle, which leaves the root view free to redraw. -
The macOS open panel runs out of process too. Escape does not reach it; the harness sends it
cancel:. -
gpui's
test-supportandleak-detectionstay behind this crate'stest-supportfeature and are never on for the gallery: one records everydebug_selector's bounds each frame, the other tracks every entity handle, so a test that leaks one fails (tests::a_leaked_entity_fails_its_test). -
A tracked
FocusHandleis a Tab stop only when built with.tab_stop(true).tab_indexon the div reaches only handles gpui makes itself.primitives::tab_stopkeeps one per id. -
Floating lists go below their anchor, or above when only above has room (
forms::options::float). gpui's own switch keeps the anchor point, so a flipped list would cover its trigger. An option list is at least its trigger's width and at most the window's lesswindow_margineach side, so gpui can snap it inside and its rows end in an ellipsis; wider, gpui pins it left and the rest runs off. -
A trigger that opens a list goes through
forms::select::listing: toggle, keys, blur and popup live there, for Select, TabOverflowMenu and Breadcrumb alike. A field that suggests as you type floats its rows throughforms::select::Pickerandforms::options::Popup, as Combobox and TagInput do. -
A palette goes through
navigation::palette::Palette: query field, grouped rows, cursor, keys and focus live there. CommandPalette, QuickOpen, QuickSwitcher, SearchPalette and QuickLauncher only build rows. -
A menu goes through
menus::menu: rows, submenus, keys, focus and presses outside live there, for DropdownMenu, OverflowMenu, SplitButton, ContextMenu, SearchableMenu and MenuBar. A press on the host's own box is the host's to handle, so a trigger toggles and a right click moves a context menu without a close and reopen in one event. A row of icon buttons (Menu::buttons) is one entry: Up and Down reach it, Left and Right cross it, a reader presses a button through Click and hears the marked one, and a mark holds its item by label, so a row that changes under it runs nothing unasked. -
gpui focuses the nearest focusable ancestor on mouse down, and apps wrap everything in a
FocusScope. A press that must leave focus alone callsprevent_default, as buttons and context regions do. -
gpui runs the click of every pressed element under the pointer, a row's with its button's. A pressable row that holds buttons stops their press at their box on mouse down (
files::transfer), asoverlays::media_buttonstops its own. -
gpui draws a window's first frame inside
open_window. State the first frame reads, such ascx.set_menus, is set before it opens. -
gpui clicks a focused element when Enter or Space is released, and only if the press reached it and focus stayed there. An overlay that hands focus back picks on release too. An edit in place that hands focus back keeps its text on Enter's release (
forms::InlineEdit). The press itself bubbles, so a container that takes Space leaves a focused button its own:overlays::media_buttonstops it, and a button in aSelectableListrow stops Space and Enter at its box (mail::MailItem's star,messaging::PinnedMessages' Unpin). A key context that binds a key its children use acts only while its own handle is focused and propagates otherwise, or a focused child never arms (editor::CodeEditor's Newline). -
A focus handle a component hands back lives across all its modes: take it with
tab_stopbefore any early return, or keyed state drops it while the other mode draws and the handed-back focus lands nowhere. A thread's message keeps one handle folded and open, a Tab stop only while folded (mail::MailThreadView). -
A button whose words and action change with a mode keeps one handle through
Button::focus_handle, and the component moves focus to it or back to its field as the mode turns (project::TimeTracker). -
A button that waits on the owner shows it with
loading, which takes no press and stays a Tab stop, so focus holds through the wait;disabledis for what is not ready, and a disabled button is no stop (onboarding::ContactSupport). -
A control names its role, label and state on the element that takes focus, for gpui's AccessKit tree (a slider's thumb, a select's trigger). gpui sets no disabled flag, so a disabled control says
state.unavailablein its description. A thumb bounded by another reports the bounds it can reach. The test platform builds no tree; a live window read through UI Automation, orWindow::debug_a11y_tree_jsonwhile a client is connected, proves it. -
A help mark, ?, is
primitives::HelpTooltip; FormLabel's help shows it. -
A panel that opens from something other than a button goes through
overlays::Popover::with_opener, which hands the opener its toggle; focus, placement and every way out stay in the popover (calendar::EventPopover's chip). Its content gets a close that hands focus back: a press that ends the panel's work closes it before the owner acts, or a panel whose host leaves the tree strands focus (onboarding::Hotspot's Got it, EventPopover's Edit and Delete). -
A flow in steps goes through
navigation::Wizard: Back, Next, Finish and the step's motion live there. Next and Finish share one focus handle, and focus moves to it when Back reaches the first step or a finished step is picked.onboarding::OnboardingWizardleaves out its numbered steps (headless) and shows a count over a bar in as many parts. -
A dialog goes through
overlays::Dialog: scrim, focus trap, Escape and focus return live there. Its close runs on every way out, so a dialog with two answers marks the chosen one before it closes (messaging::IncomingCallDialog). AlertDialog, ConfirmDialog and PromptDialog build on it; a fullscreen dialog fills the window below its title bar. A dialog's card fits the window, a title bar's height clear at each end; its body scrolls and its actions stay. -
Controls on a dark layer over pictures are
overlays::media_button, light icons that dim when they have nowhere to go, for the lightbox and a video player's bar alike. -
A component's letter keys step aside while Command or Control is held, so the app's shortcuts reach the app (
media::ImageAnnotator,VideoPlayer,VideoThumbnailStrip). -
A tip over a track splits the free width around it by the pointer's share: it stays inside the track and under the pointer, and nothing is measured (
media::scrubber::time_tip, for the scrubber and the waveform). -
primitives::Severitynames info, success, warning and danger with their colors and icons, for alerts and feedback alike. -
Rows that move are keyed.
motion::Flipplaces them absolutely from last frame's heights, so no frame shows a row at its new place before it glides there, and drives the glide from elapsed time: an animation id that changes per move would rebuild the row's keyed state.Reorderholds a row by key, drags with an empty ghost and moves the held row itself. -
A marker that slides between items (segment thumb, tab line) measures them with
motion::slideand eases withglide. -
A picker hands focus to its popup on open and back to its trigger on pick or Escape; it closes once focus leaves both (
forms::date::picker).messaging::StatusSetter's emoji field reuses it. -
Motion for a value change keys its animation on
motion::changes, so it replays per change and stays still on first paint. Key only the part that moves: a keyed ancestor gives every descendant a new id, and focused buttons lose focus. -
A spotlight or tour takes its target's box from
primitives::Measure; the lit box glides between targets. -
Thumbs stay inside their component's box: the track is padded by half a thumb (
Slider,ColorPicker,GradientEditor,media::ImageCropper). -
gpui's
Hsla::opacityscales alpha;alphasets it. -
A rounded box does not clip its children. Each layer inside takes the radius itself;
checkertakes one. -
A window keeps one
feedback::Toasterand renders oneToastViewport; anything holding the entity pushes to it. Messages shareprimitives::Severity. -
Drop rules live in
forms::files::dropped.takenadds file kinds by extension, for drops and the dialog alike (DropZone::kinds). -
What a dropped file leads to is drawn by a function of what was read (
settings::importer::read_row). gpui's test platform buildsExternalPathswith paths and answers a file dialog throughsimulate_path_prompt_response. -
A stroke drawn with the pointer goes through
forms::pen: a press starts it, a drag extends it, and the lift hands it over, in or out of the pad (SignaturePad,generative::InpaintCanvas,calendar::TimeGrid's drag on empty time). -
gpui runs key bindings before key listeners. A container takes a child's bound key through the action with
capture_action, asFormtakesSubmit. -
ScrollHandle::scroll_to_itemruns in the container's prepaint against last frame's bounds and overflow, so a call before a list's first frame does nothing. Call it from a child's prepaint once the handle has bounds, then request one frame (navigation::editor_tabs). A floating list reveals its cursor, and a strip its focused item, throughforms::options::revealandrevealer. A focused Tab stop deeper in a scroll box sets the box's offset whole from its own painted bounds in prepaint, once per focus, throughlayout::bring_into_viewalong the box's axis; the canvas measures the stop with its ring (messaging::GifPicker).layout::reveal_when_focusedis that canvas; it lies over a box that holds the stop's ring (buttons::SegmentedControl,project::KanbanBoard). Never poll layout withrequest_animation_frame. -
gpui animations run on the wall clock, except
Animation::repeat_syncedloops, which read the executor's clock; timers run on the executor's clock, which tests advance by hand. A test that needs an animation settled turns on the theme's reduced motion. Clocks readbackground_executor().now()and wake only their own view. -
Machine-read marks, QR codes and barcodes, paint
inkonpaper: dark on light in both themes, so cameras read them. -
A layer over pressable content that must still pass scrolls to its ancestors uses
block_mouse_except_scroll;occludehides them from scrolls too. -
Trackpad momentum arrives as
TouchPhase::MovedafterEnded, so a gesture acts only betweenStartedandEnded(lists::SwipeableListItem). -
Quartz scroll events carry no window. The harness hands them to the window itself, placed in window points (
Step::Swipe). -
A chevron that opens and closes is
primitives::Disclosure. -
uniform_listdoes not stretch its rows; each row setsw_full, ormin_w_fullwhen the same row also sits in a flex column, where it stretches (tables::DataTable). gpui'slistlays rows at their content's width too;chat::MessageListboxes each row at the list's width. -
Chapters import only earlier chapters, so a select of tree nodes lives in
lists(TreeSelect) and reachesforms::listing. -
Script clicks aim at a probe's corner, so a scripted target must fit in the window; a tall probe puts its top off screen. The log names each probe's bounds as the script aims, and a probe around content with no width of its own measures smaller than what it draws, so a scripted demo sets its width.
-
scripts/check.shfails by its exit status; a pipe intotailhides it. -
Reduced motion shortens animations to 1ms on the wall clock. Tests sleep past it between refreshes to see where one ends.
-
The test platform runs next-frame callbacks only when a test calls
Window::simulate_next_frame. Its text metrics are simplified: each character takes a fixed advance, unless the test builds its context withTestAppContext::build_with_text_system. Tests refresh the window in place of the display link, and click where layout does not hang on text, such as padding and slots. -
An absolute element with no insets sits where it would flow: after the content in a block, inside the padding in a flex box. A canvas that measures its parent pins itself with
.top_0().left_0(). -
Keyed state lives while its element renders in consecutive frames. An overlay rendered only while open starts fresh each time.
-
Long grids scroll in a
uniform_list; a key move up scrolls withTopand down withBottom(forms::picks). A searchable grid of picks, emoji, icons or stickers, goes throughforms::picks'sGrid;uniform_listgives every row one height, so a grid of large cells shows one group at a time instead of titles (messaging::StickerPicker). -
Masks reshape edits inside
TextInput::set_fit, which sees the replaced range and the typed text. A diff after the fact cannot tell typed characters from kept ones. -
Ids inside a reusable component carry its owner's id or
EntityId, animation ids too. Twin ids share focus, click and animation state. A child keyed by its place inherits the state of what stood there before; key it by what it shows (files::FileExplorerkeys a column by its folder). -
Text wraps at the width its box had when measured. A cross-axis
max_wcomes too late, and a flex column counts wrapped text as one line. Put prose in a plain block inside a flex row withflex_1().max_w(..)(feedback::states). -
A line that may overflow is a
typography::Ellipsis, which shapes its cut at the width its box painted with. -
gpui's text answers a min-content probe with the width it last shaped at, often one line, so a set width around prose does not give way in a narrow flex row; a host that lets such a column shrink gives it
min_w_0(the gallery's stacked DescriptionList). -
Taffy 0.13 no longer floors a flex item's least width at its row's padding and border, and a cached content measure hides a
flex_1item's own width, so a text field's least width held no room to type.forms::Input's text slot keeps an empty strut under the text, its padding and transparent hairlines at no height, so the field's least width holds the frame's inset. -
gpui's
line_clamplays the rest of the text on its last line and cuts it hard; centered, it loses both ends. A name whose ends matter is atypography::MiddleEllipsison one line (files::FileGrid). -
Set an explicit line height on any box that clips text. gpui's default leading is taller than a tight box, and the clip eats descenders.
-
gpui's
imgtakes its picture's pixel size for anyAutoside, and its aspect ratio beats percent heights in flow.Imagepins it absolute at full size, so anImageneeds a sized box. -
A file on its way is a
files::Transferdrawn by one row infiles::transfer: TransferQueue and DownloadManager share it, and its actions follow its state. -
Paths with
/between folders become a tree throughlists::Folder::of, forFileTreeandfiles::ArchiveVieweralike. -
A folder's path over its entries is
lists::path_bar, and its first order islists::listed, folders first by name: DirectoryListing and FileExplorer share both, so every view shows one path and one order. -
A file's icon comes from
primitives::file_icon, one map from name to icon for every chapter, under the owner'sprimitives::IconTheme, whose rules by name and extension come first;files::FileIcondraws it. What fits across a width comes fromlayout::columns_for, andlayout::fitadds the inset that centers it. -
Badges, tags and avatars live in
data_display; forms and shell draw theirs from there, anddata_displayimports neither. A hue worn in a row, a label's or an event's, isdata_display::color_mark, andPalette::huegives the chart color by index. A tag is never wider than its container; a long label ends in an ellipsis beside its remove mark. -
A picture drawn in code reaches gpui as a
RenderImage. A host that replaces one frees the old throughApp::drop_image, or every frame stays in the sprite atlas (the gallery's model). -
Pixels a host draws at a box's size go through
rendering::RenderSurface: it asks for one BGRA frame at the box's device pixels in prepaint, draws again only for a new size or revision, and frees the old frame and, on leave, the last one. The revision covers all the frame reads, theme colors too. -
Tests type a shortcut as it is bound: a
secondary-binding is Command on macOS and Control elsewhere, so its test typessecondary-;cmd-is the platform key on every OS and reaches only handlers that read that modifier (lists::SelectableList). A Tab count runs throughforms::EyeDropperonly on macOS, where it is enabled. -
img()keeps loading state, and moves an animated picture's frames, only with an id; the frames hold still while its window is inactive or gpui's ownreduce_motionis on, which the theme's reduced motion does not set. Content masks are rectangles, butimg()draws only the part of its picture inside its box and rounds that part with its own radius, so aCovercrop keeps its corners. -
An app has one asset source:
Assets, orAssets::before(other)when it has files of its own or runs another library such as GPUI Kit; Ely's paths load from Ely, the rest fromother.initloads every file Ely bundles through the app's source and fails naming the first it cannot, since gpui's svg renderer reads IBM Plex there too. -
gpui reads a
&strpicture source with a scheme as a web address, and any other as an embedded asset. A file goes throughPath(documents::sourcein the crate,ui::picturein the gallery). -
Taffy sizes a flex column by the height a ratio box has at its set width, before its cap, so chat's
shapedpictures sit in a block inside such a column (chat::ImageMessage,VideoMessage,ImageGrid). -
Taffy 0.13 collapses a child's vertical margin through a block parent with no padding or border on that side, and its size cache keeps only that block's size, so a box that measures it again leaves the margin out. A component moves a box with a relative offset, or with padding where what follows moves too, not with such a margin:
layout::Drawerpulls and snaps its panel, and a fullscreenoverlays::Dialogslides its card, withtop;motion::Stagger,Transition, a centered Dialog and the palette's card rise with padding, so the box a scrim centers grows with them; the gallery's inspector sample is its probe's own child. A margin that stops at its parent is safe: a flex or grid item's own, or a child's of a flex item or of a box with padding or a border on that side. A flex wrapper stops the collapse but gives its items a definite height, so percent heights inside start to resolve (the Drawer'smax_h(relative(0.85))). -
Tiles in a picture's shape sit in flex rows of
flex_1cells (generative::GenerationGrid). A cell's padding counts in its flex base, so a row of fewer cells gives each more width; cells that match across rows keep their inset on a box inside (messaging::VideoCallGrid). -
Rise reads green and fall red unless a component's
red_upswaps them, as markets in East Asia read.finance::quotes::movespicks the pair. -
Charts lay out in
f32pixels of their own box, tested without gpui, and paint throughcharts::paint::at: a canvas's paint gets window coordinates, andwith_element_offsetworks only in prepaint. Their sizes come fromtheme.chart(). A chart value is finite and within ±charts::LIMIT(1e30), so sums of values and their pixels stay insidef32; the builders refuse others, and a scale works in halves so a domain across the limit maps. -
A label pinned to a point sits in a zero-size absolute box that centers it with flex (
charts::axes), so no offset guesses at text size. -
Tokens live in
src/theme; the rawpx()scan skips that folder only. -
An owner's palette, edited or imported, reaches the theme per mode through
Theme::set_palette, andTheme::palettenames the one the shown mode fades to; high contrast strengthens Ely's own palettes alone. A color goes by its field's name throughtokenandtoken_mut, onPaletteandSyntaxalike. Ely's palettes passtooling::A11yChecker's audit in both modes, with high contrast and without; a test holds them there. -
ScrollHandlekeeps its children's bounds unscrolled: a child paints at its bounds plus the offset. A press on a scrolled child subtracts both, and an offset that brings a child to a place is set whole, not added (documents::find::reveal). -
A find box is
editor::FindWidget, over code, a terminal or a document's pages. It shows the replace fold and the option toggles only when the owner handles them. -
Taffy hands negative free space to cross-axis auto margins and to centering alike, and gpui has no safe alignment, so a centered child wider than its scroll box starts past the scroll's reach. Center only what fits the measured box (
documents::DocumentViewer). -
A mode that leaves on Escape holds its own focus through
primitives::hold_focus: turned on while focus is elsewhere, it takes focus so Escape reaches it, andhand_backreturns it (documents::ZenMode,collab::FollowMode). -
A chat holds at its newest message through gpui's
listwithListAlignment::Bottom: it sticks while scrolled to the end, andscroll_toone past the last item sticks it again; that call sends no scroll event, so the owner clears its own away state (chat::MessageList). -
Anything pressable is a Tab stop: a row, card or toggle takes
tab_index(0)andfocus_ring, with a 1px border that stays transparent where it shows none; gpui presses it on the release of Enter or Space. -
A list of pressable rows goes through
lists::SelectableList, whose cursor reaches them by key: Up and Down move, Enter or a double press activates (media::Playlist). Enter activates on its release, armed by its own press, so an activation that moves focus leaves the release nothing to press (onboarding::HelpPanel). Typing a row's first letters moves the cursor there, the letters joining within a pause on the executor's clock; a letter held with Command, Control or Alt stays the app's. A bareListItempress is the pointer's only. Titled sections of such lists go throughlists::Sections, for ChannelList, MemberList and MailboxList; each owner builds its rows and keeps its own checks. -
Columns that must line up across rows sit absolutely at fractions of their row, as the bars over them do (
calendar::CalendarMonthView): with real text, gpui lets aflex_1cell's text widen it, and the test platform's fixed advances hide it. -
A row that holds text beside fixed parts lets the text give way: the text takes
flex_1().min_w_0()and anEllipsis; counts, icons, dates and chevrons takeflex_none. A label that must stay whole wraps in that block instead, asforms::Checkbox's does. -
gpui reports no text baselines, so under
items_baselinea leaf aligns by its bottom. A block takes its first in-flow child's baseline, so a box led by a rolling column or a padded badge aligns oddly: the digit column stays a block, and a row whose value may be a badge alignsitems_end(typography::AnimatedNumber,data_display::DescriptionList). -
gpui snaps every authored length to device pixels before layout, each on its own, so a stack moved by a multiple of a row keeps the row a whole number of device pixels, or the error grows with the multiple (
typography::AnimatedNumber). -
A mark over a text field reads the field at paint time, from a canvas laid over it (
collab::RemoteCursor): by thenTextInput::bounds_forholds this frame's layout, and a still page never renders again to catch up. -
A callback field holds a named
typealias; clippy's type_complexity rejectsOption<Rc<dyn Fn(..)>>. A chapter shares one alias per shape. -
A component that fills its container takes no width on its root: a block or a flex column stretches it, in a flex row the host gives it
flex_1, and as a window's root it fills the window, since gpui sets a window root's auto sides to the window's size. A percent width ignores the root's own margins, so they push it past its container, and in a flex row it takes the whole row as its basis and leaves aflex_1().min_w_0()neighbor nothing. A root centered under a cap keepsw_full, since a flex column stretches no child with an auto side margin, andmx_autoneeds a width to fill (layout::Container). So does a box that keeps a ratio: a block follows its ratio from a stretch, but a flex or grid box only from a set width, and callers may make one flex (layout::AspectRatio, andprimitives::framedunder VideoPlayer, CameraPreview, ModelViewer, ParticipantTile and InpaintCanvas).Button::full_widthis its host asking, since a button takes no style. Each chapter's test holds its filling roots at the full width of a flex column in a padded block throughlayout::tests::narrow_width, which reads a root by itsdebug_selector. -
A field's
Highlightsets color, wash, weight, slant and strike per span; measuring and painting share one set of runs, so wrapped heights match. -
A block editor keeps one undo for the document: text and structure together, as snapshots through
forms::History; it takes Undo and Redo before its fields do. -
A selection across a BlockEditor's fields lives in the editor (
blocks::span): a press in a field drags into others, Shift-press reaches from the caret's field. The pressed field selects its own part; the editor sets the other end's and washes blocks between. The editor watches the focused end throughTextInput::edits, which counts every edit, text changed or not: an edit there, once an input method commits, is rebuilt from the last snapshot as head, typed and tail in one undo step; a caret move or lost focus lets the span go, since gpui runs bindings before key listeners. Backspace, Delete, Cut and Enter take the span before the field acts; copy writes it as Markdown. -
gpui calls focus listeners only while the window is active. A test that needs focus events activates its window with
window.activate_window(). -
A terminal takes Tab and Shift-Tab through bindings under
ElyTerminal, ahead of focus moves; other keys go throughterminal::keysin a key listener, and typed text through its input handler. -
A shell in the gallery starts without startup files and with a fixed prompt, so captures show no user or host.
-
gpui has no
(ElementId, usize)id. A child id names its index:(self.id.clone(), format!("row-{ix}")). -
Clippy's
single_range_in_vec_initrejects[a..b]andvec![a..b]; bind the range first. -
A component built on
lists::Treefills its box; the host gives it a height. So doesdocuments::BlockEditor, which scrolls in aScrollAreathrough its own handle (ScrollArea::track_scroll) and brings the caret into view each time it moves, not each frame, so a wheel moves past a still caret. A fixed-width column beside aflex_1one takesflex_none, or it shrinks. -
Editor rows read their text through
CodeEditor::row_text: a multi-line ghost cuts its line, and the rest follows the ghost's last line. -
gpui clears
debug_boundseach frame: a test sees an element leave as well as appear. -
Time reads
web_time::Instant: std's compiles for wasm32 and panics in the browser.scripts/check.shrefuses std's insrcand the gallery. -
What cannot build for wasm32 is absent there through
cfg(not(target_family = "wasm")), never faked: the live terminal,TerminalandLaunchwith their grid, keys and search, and the gallery's capture harness. -
What a browser cannot run draws natively alone:
ui::native_onlyleaves a window's opener out on the web, and the web's menu bar keeps View alone. The site shows native captures instead, light and dark: a story whose demo needs the desktop (files, the terminal's shell, the tray, the Dock, window moves) as its capture, and a live story that opens windows with those windows below.python3 scripts/shots.pytakes them with the native gallery, window in front, intofrontend/public/shotsandfrontend/src/shots.json. -
A story is a gallery section: its title is a literal, one per page. Under
ui::Storythe one asked for draws without its title; the rest drop their demos (ui::Section), so nothing of theirs lays out, loads or moves. Their builders still run, so a demo that starts work on its own, a clock, a load or a shell, starts it throughui::live, which a story grants its own section alone and fails on a title no section has. A story the page lacks fails in the page. -
Embedded in a page, the web gallery keeps gpui_web's hidden text field from taking focus, and hands focus back to the host, until a press or a key reaches it (
examples/gallery/index.html); gpui_web focuses that field whenever its window is active. It retries a failed start twice, since frames that fetch the wasm at once can fail one, then posts{ ely: "failed", error }. The host shows its own state until{ ely: "ready" }or that failure, sends the mode on ready and on every turn, and puts a skip link before the frame, since Tab inside gpui cycles within it. -
The gallery's pictures go through
asset!andui::pictureorui::resource: a file natively, on the web an address underELY_GALLERY_ASSETS, since gpui reads no files there. -
On the web a paste reaches the focused field as the browser's paste event, through
EntityInputHandler::paste, so Paste stays unbound there (TextInput,CodeEditor). Code that reads the clipboard itself awaitsread_from_clipboard_async: gpui_web'sread_from_clipboardanswers at once and finds nothing (tables::DataGrid's Cmd-V). -
The first frame wears the host's mode through
Theme::set_mode_now, a mode sent before the window opens included;set_modefades from the colors on screen. -
The system time zone comes from
typography::format::system_zone: an unknown zone logs one error naming the first component that asked and reads as jiff'sTimeZone::unknown(), which is UTC. A time shown in the system zone goes throughtypography::format::datetime, which adds " UTC" in the unknown zone, so the screen never claims local time. jiff'sTimeZone::system()falls back to UTC in silence. -
A Select with nothing to pick stays shut: disabled, it shows its placeholder; open, it fails (
media::DeviceSelectorsays no device was found). -
A list opens with its cursor on the current choice: Select on the chosen row, Combobox on its value, MultiSelect on the first one ticked, SelectableList on its first selected row. A selection the owner makes, not the echo of the list's own pick, moves SelectableList's cursor there.
-
An event repeats as RFC 5545 has it: weeks count from the start's own week, and a month or a year without the start's date is passed over (
calendar::Recurrence). -
A search step, a tool call or an agent's step says where it stands with
chat::StepStateand draws it withchat::step_mark. -
A row that parts left and right groups its sides and uses
justify_between. An auto margin along a line keeps the line's gaps, but taffy also hands the space it took to the line's justify. A line with an auto margin keeps the default justify and runs forward (forms::MultiSelect's chevron); centered, ended, spaced, or reversed asi18n::Direction::rowis under a right-to-left locale, its items run out of its box. -
A header that holds a name beside counts and actions wraps:
flex_wrap, the nameflex_1withmin_w(label_width), the rest oneflex_nonegroup that drops below. -
Every header, row and strip works at 280px: a name keeps its minimum width, the rest wraps below or scrolls, and nothing draws past its box.
-
gpui takes a scroll box's reach from its direct children's bounds, and a block child stretches to the box. Content that scrolls sideways sets its least width on that child (
tables::body::least). -
gpui turns a wheel along the other axis onto a box that scrolls one way, and the page around it scrolls too. A box inside a scrolling page keeps its wheel to its axis with
layout::on_axis, and so does the page. gpui also hands a wheel to every scroll box under the pointer, so aScrollAreastops a wheel along an axis it has room on and passes one it cannot use, and a floating option list occludes (forms::options::Popup). -
The focus color belongs to the focus ring. A chosen or shown item takes accent, as
forms::picksandlists::ListItemdo. -
A box that shows a host's picture takes the picture's shape from the host (
ratio), so nothing is cropped and a point given as a share lands on the picture. A focusable one keeps its focus border on an outer box, so the picture keeps that shape exactly (media::ImageCropper). -
A default only the component can work out, such as a crop fitted to a picture it decodes, reaches the owner once through
window.deferafter render (media::ImageUpload). -
A ScrubInput shows its number in about 140px beside a short label. In a narrower column a pair hides it, so a long label takes a row of its own (
canvas::TypographyPanel). -
Design panels edit a whole value and hand the owner a changed copy through
canvas::panel::editing; the owner's handler is an argument ofnew. A swatch that opens a picker iscanvas::panel::color_well. -
A press that stops propagation also stops the ancestors' focus on press. A layer that takes keys focuses itself in its own press handler (
canvas::ToolLayer,canvas::MindMap), and a field over it stops its own presses. -
A button inside a pressable row goes through
lists::row_action, which keeps its press and its Space or Enter from the row (devtools::ConnectionManager). -
A field opened over a component hands focus back to that component on Enter and Escape, through
forms::Editing'sreturningandgive_back(canvas::writing); a blur keeps the words and leaves focus where it went. -
A focus ring on a box whose children measure themselves goes on an overlay child: a border insets the children and shifts what they measure (
canvas::ToolLayer). -
A menu of times goes through
mail::times: each row names its day and hour, a moment comes once (tomorrow is next Monday on a Sunday), and "Pick a time…" opens one dialog that waits for a time ahead of now (ScheduleSend,SnoozePicker). -
A list whose cursor a field drives marks the cursor only while the field has focus; at rest its first row would look hovered (
mail::LabelPicker). Its rows are option rows, andforms::marked_rowleads one with a mark such as a label's hue. -
A choice that names a person leads with their initials through
forms::Choice::avatar, in the list and in a Select's trigger alike (project::AssigneePicker). -
typography::Ellipsisshapes its line with the text style's own run, so a strike or an underline set on its box shows (lists::ListItem::struck, a closed task's title). -
Focus that must follow an item to another parent is taken by the owner and keyed by the item: a handle gpui makes for an element dies with its path (
project::KanbanBoard, whose cards keep focus across columns). -
The theme's
platform, the system's to start, decides the window buttons Ely draws, the title bar's layout and key caps,secondaryincluded (typography::keys::keystroke). A title bar whose buttons the OS draws follows the OS. -
A window whose sidebar runs to its top is
shell::ColumnShell: each column's header drags the window; the first leaves the traffic lights room, and drawn controls lead the first header in the macOS manner or end the last. A set width yields before a header's own parts; the host's header items keep one control's width and scroll when short. -
Taffy counts a flex item's text in its least width whatever width the item sets. A box that must add none lays its content absolutely inside a filling box (
shell::ColumnShell's title and body). -
A centered title sits between a start and an end that grow alike from a zero basis, their padding on a box inside them, since taffy counts padding in the flex base; their content is their least width, so the title gives way before either end (
shell::TitleBar). -
A pane the window's blur shows through is
shell::Vibrancy, filled with the palette'sglass, in a window openedWindowBackgroundAppearance::Blurred: vibrancy on macOS, acrylic on Windows. gpui'sMicaBackdropandMicaAltBackdropare DWM backdrops from Windows 11 build 22621; older builds get none, in silence, and macOS plain transparency. -
The chart's hues go by
theme::HUE_NAMES, for an event's color and a label's alike. -
Text whose every character counts, encoded data and keys, goes through
typography::literal: JetBrains Mono draws==and->as ligatures (devtools::Encoder). -
Messages go through
i18n::I18n, a global the app sets:cx.global::<I18n>().text(key, args)fills each{name}. Every catalog holds the first one's keys; a missing key, argument or catalog fails. A right-to-left locale mirrors a stack withDirection::rowand sets its text withDirection::align; CoreText orders mixed Hebrew and Latin runs on macOS, but gpui sets no paragraph direction, so a line takes its first strong character's:textleads a right-to-left message with U+200F, so its words suit labels, not a field's value, andLocale::numbera right-to-left minus with U+200E, as CLDR does. gpui wraps shaped glyphs in the order they are drawn, on the web as on macOS, so a right-to-left line that wraps comes out garbled: right-to-left text stays on one line, anEllipsis, which cuts by characters and sits by its text's alignment. -
gpui orders Tab stops by
tab_indexbefore paint order, and registers none for aninvisible()element. A stop that must come first where it sits, yet draw over what paints after it, stays in flow at zero size and raises what it shows (i18n::SkipLink): a lowertab_indexwould sort it before the root scope's own handle, so Tab from the root would pass it. -
Color-blind safe charts swap Ely's chart hues for a set fitted to stay apart under Machado's protanopia, deuteranopia and tritanopia; a test holds every pair 0.10 apart in Oklab and each hue 3:1 on its page (
Theme::set_color_blind_safe, in Ely's own palettes alone). -
gpui packs pictures in its atlas without padding and samples them with a linear filter, on the web as on macOS, so a scaled picture's edge takes color from its atlas neighbor, and pictures laid edge to edge show seams. A map tile carries a one-pixel rim copied from its own edge and draws one of its pixels larger each side, clipped to its box (
maps::tile). -
A wall clock reads
Timestamp::nowand wakes its view at each whole second through a keyed task, only while the owner passes no moment; a stopwatch reads the executor's clock, so tests advance it (misc::Clock,misc::Stopwatch). -
A number shown to so many significant digits goes through
typography::format::significant, which rounds through Rust's exponent form, so no binary noise shows at any size (misc::Calculator,misc::UnitConverter). -
A component that reads the host's frames reads one at a time off the main thread and passes over frames that come meanwhile; when a read ends it redraws, so the newest frame is read next (
misc::QrCodeScanner). -
Embedded, the web gallery ignores its own
focus()calls until a press or key reaches it, so a frame that starts never takes the host page's focus; the site keeps a focused frame mounted (frontend/src/pages/Home.tsx'sNear). -
A native view laid over the window takes AppKit's first responder on a press, and gpui's view never asks for it back, so typed text would go to the native view for good. The component hands the keyboard back on every press gpui gets and when the view leaves (
misc::WebView, through wry'sfocus_parent);Step::NativeKeyproves it, since gpui's own dispatch skips AppKit. -
A map keeps places in
f64: at zoom 19 the world is 134 million pixels across. Its wheel pans and Command or Control with the wheel zooms about the pointer, as the canvas's does. Its tiles stay in gpui's asset cache, as everyimg()picture does. Its popup is an overlay that is not modal: it takes focus when it shows, and the frame it stops showing, by any way out, hands focus back. Its layers, lines and areas unwrap across the date line and repeat on every world in view. Clustered pins gather by squares of the world, not of the view, so a pan never regroups them; a zoom may, so it hands a focused pin's or cluster's focus to the map. -
gpui's inspector (debug builds) runs no mouse listener while picking and holds a pick on a press without a redraw, so Ely's panel requests a frame each render while picking (
tooling::install_inspector). Its panel scrolls, so the box model it draws over the page goes throughprimitives::raise, which paints unclipped. The panel is a second root, out of the app's scope, so it owns Tab through aFocusScopeof its own. -
A tool that measures redraws cannot move: any animation redraws the view it counts (
tooling::RenderCounter).AnyView::cachedrenders again only when it or a view inside it notifies, its bounds, clip or text style change, or the window refreshes; gpui refreshes the window whenever input turns between the keyboard and the pointer. A meter that counts frames keeps its view drawing while shown (tooling::FpsMeter). -
A logger of input lays its canvas before what it holds, so its capture listeners run first and see what a child stops. Key downs come through
App::intercept_keystrokes, which runs before bindings, and a key context on its box tells it focus is inside (tooling::EventLogger). It also hears a lone modifier's release as a keystroke, which the logger skips.
Decisions
- 2026-09-24: official gpui 0.2.2 over the
gpui-presnapshot. Older API, first-party publisher. - 2026-09-24: one crate. Revisit if an incremental check passes 90 seconds.
- 2026-09-24: gpui 0.2.2 has no accessibility tree and no tray, badge, or notification API. Those entries carry
blockedorprove. - 2026-09-24: the gallery photographs its own window with
CGWindowListCreateImage. No Screen Recording permission needed. - 2026-09-24: license MIT OR Apache-2.0.
- 2026-10-05: license MIT alone, in
LICENSE, at the owner's word; replaces the 2026-09-24 choice. - 2026-09-25: layouts persist as versioned JSON through serde. Unknown fields are ignored. Newer versions, unknown panels and bad pane trees are refused, and restore is all or nothing.
- 2026-09-25: an entry built from a later chapter's parts lands with that chapter; its line points there. MenuBar goes to Menus, QuickLauncher to Navigation.
- 2026-09-25: SystemNotification is blocked. An unbundled app has no notification center (probed on macOS 27.2), and gpui 0.2.2 has no API.
- 2026-09-25:
unexpected_cfgsdeclaresfeature = "cargo-clippy", which the objc 0.2 macros test. - 2026-09-26: Terminal proven with
alacritty_terminal0.26: its grid, parser and pseudo-terminal, drawn by Ely. A replayed grid serves recorded output and tests. - 2026-09-27: ThemeSwitcher is
settings::ThemeSelector:src/themesits under every chapter and cannot draw with buttons. - 2026-09-28: QRCodeScanner proven with
rqrr0.11: the host hands in frames asRenderImages, read off the main thread; gpui has no camera. - 2026-09-28: WebView proven with
wry0.57: a child WKWebView over the window, placed from a canvas in prepaint at its box each frame and hidden once the box leaves the view. It draws above everything gpui paints and gpui's clipping does not reach it; gpui's test windows have no native handle, so the capture proves it. - 2026-09-28: InspectorOverlay is gpui's inspector drawn by Ely,
tooling::install_inspector, in debug builds only, as gpui has it. - 2026-09-28: every text pair the contrast audit holds meets 4.5:1:
fg_subtleis #696764 light and #94928f dark,syntax.commentwith it, and the light syntax's number, constant and attribute are #a3591b, #aa5529 and #9f5d12; the contrast audit found them under. - 2026-09-28: the site shows components live, as gpui-kit.com does, not as screenshots. gpui 0.2.2 cannot build for the browser: Zed added gpui_web on 2026-02-26 and has published no gpui since 0.2.2. So gpui moves to Zed's repo by git at 1a28cff (T44b), the code gpui-pre 0.3.7 republishes; the source stays first-party, and this replaces the 2026-09-24 choice. The terminal stays native: alacritty_terminal's polling does not build for wasm32.
- 2026-09-28: gpui builds an AccessKit tree on macOS, Windows and Linux, not the web, from elements with an id and a
role, so the accessibility entries turn fromblockedtoprove. It still has no tray or badge API, so TrayIcon and DockBadge call AppKit. - 2026-09-28: SystemNotification is
prove: gpui'sshow_system_notificationposts only from an app bundle; unbundled, ascargo runstarts the gallery, it logs and drops the call, and the web posts nothing. - 2026-09-29: the ported debug gallery draws its heaviest page about 1.6 times slower than on gpui 0.2.2 (tooling: 9 fps, worst frame 117 ms, against 14 fps and 72 ms), with gpui's accessibility tree on or off (
Application::new_inaccessiblemeasured the same). Ely's code did not change there; the frame cost is gpui's. - 2026-09-29: the web gallery runs on one thread, since a static site sends no cross-origin isolation, and draws through WebGL2 where WebGPU is missing. Its pictures are served beside the wasm, not embedded: the wasm stays under Cloudflare's 25 MiB, and a story fetches only its own.
wasm-opt -Oztakes the wasm from 23.0 to 20.0 MB; gzip and brotli stay near 7.9 and 5.5 MB. - 2026-10-04: the Showcase follows gpui-kit.com/apps: apps live in their own public repo and arrive by pull request. The site pins a commit of it in
frontend/showcase.lockand moves only when someone bumps the pin, so stars refresh on the site with each bump. READMEs take Markdown and PNG pictures alone, no HTML or video, so the site needs no sanitizer. The catalog started empty. - 2026-10-05: the pin moves by itself:
.github/workflows/showcase.ymllooks at the catalog's main every 30 minutes and, once that commit's checks pass, builds the gallery and the site, deploys throughCLOUDFLARE_API_TOKEN(secret) andCLOUDFLARE_ACCOUNT_ID(variable), then commits the pin; a run withdeployset redeploys at the current pin. A catalog commit that fails its checks keeps the pin. The catalog runs its checks on every pull request without approval, save from accounts new to GitHub; they get no secrets and a read-only token. - 2026-10-09: the repos moved from GitHub to Gitea at git.zacharyzhang.com; GitHub's copies are gone. The site links there,
pnpm showcasedownloads the catalog's archive from there, andshowcase.ymlruns on Gitea Actions, reading the catalog's head and combined commit status through Gitea's API. The catalog's stars still come from GitHub's API, signed in only with itsSTARS_GITHUB_TOKENsecret, since Gitea's own token means nothing there. TokenLedger, merged on GitHub after the copy was taken, was restored from the site's copy. - 2026-10-04: chapter 44, Rendering, holds
RenderSurfacealone. gpui at 1a28cff paintsRenderImages everywhere,CVPixelBuffers (NV12) on macOS's Metal renderer alone, and shares no device or shader hook, so shader, camera and GPU views are the host's, feeding the surface, and zero-copy or Metal, OpenGL, Vulkan and Direct3D surfaces are blocked (tasks/ch44.md). CI tests the crate and builds the gallery on Linux, Windows and macOS, arm64 and x86_64. - 2026-10-05: an overlay its owner removes without calling
Closestill hands focus back:take_focusrecords each holder and what it took focus from,give_backforgets it, and when the focused element leaves the tree the root scope walks back past every gone holder to the focus before them all, else takes it; nested overlays removed together return to what the outer one took focus from. Before, a dialog whose action set its owner's state to closed left focus on the root, and keys reached nothing until a click. - 2026-10-05:
Select::labelandTextInput::labelname the control for assistive technology without drawing anything; a text input with neither label nor placeholder had no name, and a select read only its value, so a field named by a description list beside it was anonymous in the AX tree. - 2026-10-05:
NumberInput::labelnames the field for assistive technology without drawing it, throughTextInput::set_labelon the field's inner input, which lives across frames; a number field named only by a description list beside it was anonymous in the AX tree. - 2026-10-05:
TemplatePicker's list is one Tab stop: up, down, Home and End move the highlight, Enter uses it; rows are list items with name, selection, place and count; its search placeholder, All chip, Use button and empty line read from the builtin catalog in English and Chinese. Before, the rows were reachable by mouse alone and anonymous in the AX tree. - 2026-10-05:
SelectableListis a list in the AX tree, named bylabel; eachListItemis a list item with its title as name, its description, its selection, and, in a selectable list, its place and count (ListItem::place). Before, the rows had no role and were missing from the AX tree. - 2026-10-05:
NumberInput::blankshows a placeholder, not a number, as for a mixed selection; Enter or blur on the empty field commits nothing, a typed number commits as usual. Before, a mixed field held a real number the owner had to invent, and Enter sent it.ScrubInputmoved tonumber/scrub.rs. - 2026-10-07: gpui comes from crates.io's
gpui-preat the exact snapshot GPUI Kit pins, not from Zed's git. Two sources build two gpui crates whose types do not mix, so Ely and Kit could not share a window. The code is still Zed's; the publisher is not. This replaces the 2026-09-28 choice of Zed's git, and the pin moves when Kit's does.