LabelPicker puts labels on a message: a field finds them by name, arrows move and a press or Enter turns one on or off, each row with its check and its hue. A name no label has can be made into one, and the field starts over. Its rows are the option rows every list shares; forms::marked_row leads one with a mark. The cursor shows only while the field has focus. SnoozePicker offers later today, tomorrow, this weekend and next week, each with its day and hour, or a time picked in a dialog. It shares mail::times with ScheduleSend: the names, the dialog, which now waits for a time ahead of now, and a rule that a moment comes once, so on a Sunday tomorrow morning no longer repeats as Monday morning. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
28 KiB
28 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 0.2.2 from crates.io. Feature
runtime_shadersis on by default: 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) and JetBrains Mono 2.304 (OFL). - 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. - macOS extras (tray icon, Dock badge) call AppKit through
cocoa0.26 andobjc0.2, the crates gpui already links. - Terminal:
alacritty_terminal0.26 (Apache-2.0, default features off) for the grid, its parser and the pseudo-terminal;futurescarries its events. - Diffs:
similar3.2 (Apache-2.0) for line and word diffs and three-way merges; itsunicodefeature splits words at punctuation. - Markdown:
pulldown-cmark0.13 (MIT, default features off) for CommonMark with tables, tasks, strikethrough, footnotes and math. - Pictures:
image0.25 (MIT OR Apache-2.0), the crate gpui decodes with, default features off; it turns decoded frames. - Gallery:
examples/gallery. Website:frontend/(Vite 8, pnpm), built after the components.
Commands
cargo run --example galleryopens the gallery.-- --page <slug>starts on a page.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 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.rm -rf target/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.
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.TASKS.mdandtasks/: progress. Every PRD entry has a line and a tag.
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. - 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: use them inside animators, never as gpui easings, because gpui asserts eased values stay in0..=1.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 toFocusNextandFocusPrev. 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.- gpui's
Window::dispatch_eventreturns a private type, so the gallery scripts mouse input by postingNSEvents to its own queue. hover()needs an element id.- gpui applies hover and group-hover styles once an element has a hitbox, in prepaint and paint, but text takes its color at layout, so a hover cannot recolor text. What shows on hover rests
invisible()and turnsvisible()throughgroup_hover(chat::ConversationItem,messaging::ChatMessage). 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 0.2.2 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. - 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). - 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-supportswaps its executor:blockfails on a real dispatcher. It stays behind this crate'stest-supportfeature, never on for the gallery. - 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. - 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. An overlay that hands focus back picks on release too; a pick on press returns focus first, and the release clicks the opener again. 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 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 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). Its trigger acts on press and keys, not on click, so a pick made on Enter's press cannot reopen it on the release;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. - Only gpui can build
ExternalPathswith paths, so drop rules live informs::files::dropped, where tests reach them.takenadds file kinds by extension, for drops and the dialog alike (DropZone::kinds). - 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). - 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). Never poll layout withrequest_animation_frame.- gpui animations run on the wall 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 read
background_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.- 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.
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 never runs next-frame callbacks, and its text metrics are simplified: each character takes a fixed advance. 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. gpui has no nearest scroll, so 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). - gpui 0.2.2 keeps a no-wrap line's first measure, taken at full width inside flex and scroll boxes, so
.truncate()clips there instead of ending in an ellipsis. A line that may overflow is atypography::Ellipsis. - gpui 0.2.2's
line_clampwraps once and lays the rest on the last line, cut 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;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. Avatars take square pictures. 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). img()keeps loading state, and moves an animated picture's frames, only with an id. Content masks are rectangles, so rounded corners survive only when the image fills its box without cropping.- gpui reads a
&strpicture source as a web address. A file goes throughPath(documents::sourcein the crate,Path::newin the gallery). - Taffy 0.9's grid rows overgrow tiles that keep a ratio. 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 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. 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 0.9 hands negative free space to auto margins and centering alike, 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). 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. - 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. - 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 says
w_fullon its root. As a window's root, or in a flex row, it would shrink to its text's narrowest width. - 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. - 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 0.2.2 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. 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 styles a drag-over only on an element with its own hitbox. A veil that shows while files hover takes the drop itself (
chat::DragDropOverlay). - gpui 0.2.2 never clears
debug_bounds: a test sees an element appear, not leave. - The system time zone comes from
typography::format::system_zone, which fails loud; 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.
- A search step, a tool call or an agent's step says where it stands with
chat::StepStateand draws it withchat::step_mark. - A flex line whose free space goes to an auto margin loses its gaps in taffy 0.9. A row that parts left and right groups its sides and uses
justify_between;ml_autois safe only beside aflex_1item that leaves no free space. - 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. - 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 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.
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-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.