# gpui-base Architecture ## Status and Scope This document describes the architecture implemented by `crates/base`. It is a source-derived reference, not a migration plan. Public exports in `crates/base/src/lib.rs` and the Rust API documentation remain authoritative for individual methods. `gpui-base` is the reusable foundation below the styled `gpui-component` crate. It is designed for both of these callers: - `crates/component`, which adapts base behavior into GPUI Component's complete visual system; - applications that build and own a different visual system directly on top of base behavior. ## Architectural Thesis The durable rule is: > Base owns reusable behavior and the geometry required to implement it. The > presentation layer owns the product's visual language. “Headless” does not mean that every base module is a single empty `Div`. Keyboard navigation, text editing, popup collision handling, virtualization, calendar grids, resizable panels, and toast stacking require internal structure, measurement, and retained state. Base owns that complexity when moving it to callers would duplicate difficult behavior. Base does not own product-level choices such as brand colors, typography, control density, borders, radii, icons, variants, or final composition. ## Dependency Direction ```text application / \ ▼ ▼ application-owned UI gpui-component \ / └──────┬──────┘ ▼ gpui-base ▼ GPUI ``` Dependencies point downward. `gpui-base` must not import `gpui-component` themes, assets, or façade types. `gpui-component::init` may initialize and theme the base layer, but the base layer must also work when initialized directly. There is no Registry or CLI crate in the current workspace. Source distribution can be added above this seam without changing the ownership model, but it is not part of the implemented architecture documented here. ## Module Families The public surface contains four distinct module families. Treating all of them as identical “primitives” hides important interface differences. ### 1. Semantic elements Examples include Button, Checkbox, Radio, Switch, Toggle, Link, Tabs, Progress, Avatar, and the semantic Table parts. These modules typically implement GPUI interfaces directly: ```text IntoElement + Styled + ParentElement where composition is meaningful + InteractiveElement for interactive roots ``` They provide a stable element identity, event normalization, focus behavior, keyboard activation, accessibility semantics, controlled values, and optional semantic-state styles. The caller supplies visible children and presentation. `Button::new("save")`, for example, receives an `ElementId`, not a label. It has no default height, padding, background, border, or radius. ### 2. Compound behavior roots Examples include Accordion, Dialog, AlertDialog, Sheet, Popover, HoverCard, Select, Combobox, DatePicker, and Popup. These modules coordinate multiple parts or application-owned children. Their interfaces encode behavior that would be fragile if every caller rebuilt it: - open-state requests and change reasons; - trigger and content focus transfer; - Escape, Confirm, and directional key actions; - dismissal ordering; - backdrop hit testing; - focus trapping; - trigger measurement and popup placement. Parts such as `DialogTitle`, `DialogDescription`, and `DialogClose` are explicit semantic seams. Base does not walk arbitrary descendant trees to discover them. ### 3. Stateful systems Examples include InputState, TextareaState, EditorState, CalendarState, TreeState, SliderState, ResizableState, OtpState, ColorPickerState, QuestionnaireState, ToastManager, ToastStackState, NavStackState, DockArea, and TabGroup. These modules retain data because their behavior spans frames or requires measurement, subscriptions, history, focus, or incremental updates. State is usually stored in a GPUI `Entity`, a keyed element state, or an application-owned model passed back to the element. Stateful systems expose application rendering seams rather than leaking their implementation. Calendar provides a pre-wired `CalendarItem` and semantic `CalendarItemState` to an item renderer. Tree owns flattening, expansion, selection, keyboard movement, and virtualization while the caller renders each visible `TreeEntry`. ### 4. Infrastructure and utility modules Examples include Positioner, Scrollbar, VirtualList, FocusTrapElement, AutoScroll, motion, History, geometry helpers, measurement, theme tokens, and global initialization. These are deep modules: a small interface hides layout, lifecycle, or data structure complexity used by many controls. Their interface is also their test seam; callers should not need to reproduce the hidden algorithm to verify it. ## State Ownership State ownership follows behavioral needs rather than one universal pattern. ### Controlled values Checkbox, Radio, Switch, Toggle, Select, and similar roots accept the current value and report requested changes. They do not silently mutate application state. ```text application value │ ▼ base element ── activation ──▶ on_change(next_value) ▲ │ └──── next render ───────────┘ ``` Callbacks describe intent. Pointer-originated value changes include the `ClickEvent` when modifier keys are useful. Model-driven changes, such as pagination requests, do not invent a pointer event. ### Handles and explicitly shared state Dialog supports `DialogHandle` for imperative open and close requests while still reporting `DialogChangeReason`. Scrollbar adapters share an underlying scroll handle. Toast stack geometry is shared through `ToastStackState`. Handles coordinate one logical behavior. They must not be reused across unrelated viewports or component instances. ### Entity-backed state Complex modules use `Entity` when they need GPUI observation, subscriptions, focus handles, or incremental notification. The state entity owns behavioral data; the presentation layer still owns how that data looks. ### Keyed element state Small pieces of ephemeral state tied to an element identity use `window.use_keyed_state`. Stable `ElementId` values are therefore part of the interface, not an implementation detail. Changing an ID can reset focus, measurement, animation, or open-state bookkeeping. ## Styling Model Base separates three styling mechanisms: 1. ordinary GPUI `Styled` calls for instance presentation; 2. typed semantic-state style builders such as checked, selected, or disabled; 3. GPUI native runtime modifiers such as hover, active, and focus-visible. The shared `state_style::resolve_style` function makes semantic precedence consistent across controls. See [Styling and Motion](STYLING-AND-MOTION.md) for the complete contract. Compound and stateful modules expose explicit presentation seams: - GPUI `Styled` on the root or part; - application-provided children; - typed part elements; - item renderer callbacks; - style refinements for internal virtualized containers; - presentation snapshots such as `InputPresentation`. ## Public Data Types Across the Seam A public struct that crosses the base/application seam must not expose `pub` fields. Every field a caller can name is a field that cannot be added later: adding one breaks any struct literal, and removing or renaming one breaks every reader. The seam types are the ones that grow the most, because each new capability of a control shows up as another flag in the state it hands out. The shape to use instead: - private fields; - a builder for construction — `new()` plus one chained setter per field; - a reader per field. Setters and readers must not collide, which decides the naming: - a type whose fields are all boolean names its setters after the field and its readers `is_`/`has_`, never `can_`, matching how elements read — `CalendarItemState`, `InputContextMenuCapabilities`; - a type carrying non-boolean fields prefixes every setter with `with_`, so the readers keep the plain field name — `RenderOptions::with_item_ix` against `RenderOptions::item_ix`. This follows `Sizable::with_size`. A `with_`-style setter that takes `self` by value also replaces functional update syntax, which stops compiling once the fields are private: ```rust // was: RenderOptions { item_ix, ..*options } item.render_item(&options.with_item_ix(item_ix), window, cx) ``` A type that is only ever built inside its own module — `InputPresentation` is built by `InputBaseState::presentation` and nowhere else — needs the private fields and the readers, but not a public builder. Private fields already close the breaking-change hole, and a public builder there would hand out a construction path the seam does not want. Such a type reads its non-boolean fields under the plain field name, which is why it cannot also carry field-named setters. ```rust let capabilities = InputContextMenuCapabilities::new() .code_editor(true) .selection(true); if capabilities.is_editable() && capabilities.has_selection() { /* ... */ } ``` Derived answers belong on the type rather than at each call site. When several readers are always combined the same way — `!disabled && !readonly` — publish that combination as its own reader (`is_editable`) so the rule has one definition and new inputs to it stay invisible to callers. Name such a type in full: `ComboboxTriggerContext`, never `…Ctx`. In a GPUI codebase `cx` is reserved for `App`, `Context`, and `AsyncApp`, so an abbreviated `ctx` for anything else reads as a second, competing context. A callback that receives both takes the GPUI one as `cx` and gives the other a name describing what it holds. This applies to state snapshots (`InputPresentation`, `CalendarItemState`), capability sets (`InputContextMenuCapabilities`), render contexts (`ComboboxTriggerContext`), and option sets (`RenderOptions`). It does not apply to value types whose fields _are_ the definition and cannot grow, such as `Point`, `Selection`, `Edges`, `IndexPath`, or `FoldRange`, nor to types that mirror an external schema, such as the LSP `Diagnostic`. Design-token records (`ColorTokens`, `RadiusTokens`, and the rest of `theme_tokens`) keep `pub` fields: themes read and write them field by field. They carry `#[non_exhaustive]` under the rule below. Where `pub` fields are deliberate — themes, settings records, design tokens, serialized schemas — the struct must carry `#[non_exhaustive]` and implement `Default` or a constructor. Adding a field is then not a breaking change. Outside the defining crate, callers build the value from `Default::default()` and assign fields; a struct literal, including `..Default::default()`, does not compile there, so other workspace crates use the same form: ```rust let mut theme = gpui_base::Theme::default(); theme.tokens = tokens; theme.cursor_blink = cursor_blink; ``` Apply this to every new type and to any existing type whose fields a change adds or alters. Every theme record already carries it: both `Theme` structs, the Base token records, the Component theme schema (`ThemeConfig`, `ThemeColor`, `ThemeConfigColors` and the `Semantic*Config` types), the highlight theme (`HighlightTheme`, `HighlightThemeStyle`, `SyntaxColors`), `MotionTokens` and the theme settings records (`ListSettings`, `NotificationSettings`, `SheetSettings`). Value types exempt above stay exempt, and remaining existing types migrate in their own changes. The Coding Guides state the same rule. ## Input, Textarea, and Editor Architecture Text editing is intentionally deeper than the semantic elements, but callers do not need to learn the complete editor interface for every text field. ### Public forms Both `gpui-base` and `gpui-component` expose three purpose-specific forms: | Form | State | Intended interface | | ---------- | --------------- | --------------------------------------------------------------------------------------------------------- | | `Input` | `InputState` | Single-line values, placeholders, masks, validation, and submission | | `Textarea` | `TextareaState` | Ordinary multi-line text, fixed rows, soft wrapping, and optional auto-grow limits | | `Editor` | `EditorState` | Source text, language-aware highlighting, line numbers, folding, search, diagnostics, and LSP integration | `gpui-base` provides unstyled forms. `gpui-component` adapts the same behavior into the product theme and sizing system. `InputBase` is the foundational frame used for input semantics, state styling, accessibility, and application-owned content; it is not one of the three editing forms. Existing `gpui-component::Input::new(&Entity)` call sites remain the single-line compatibility path. `InputState` is a real facade, not a type alias for the editing engine: multiline, auto-grow, gutter, folding, diagnostics, and LSP configuration are absent from its API. Multi-line code must construct `TextareaState` or `EditorState` instead. ### Shared engine `InputBaseState` owns mechanics shared by all three states: - Rope-backed text and edit history; - cursor, selection, IME, clipboard, and focus; - shaping, layout, hit testing, selection and caret painting; - auto-scroll, viewport scrolling, and cursor visibility; - native text-content integration where supported. The implementation under `crates/base/src/input` is organized by responsibility: - `base/` contains the shared editing engine and foundational mechanics; - `input/` contains the single-line control and state facade; - `textarea/` contains the multi-line control and state facade; - `editor/` contains the editor control and state facade, plus display mapping, highlighting, search, diagnostics, decorations, indentation, and LSP. These are implementation folders rather than public Rust module segments. The external seam remains `gpui_base::input`, with stable re-exports in `mod.rs`. Purpose-specific state facades configure the shared engine and forward `InputEvent` without duplicating those mechanics. `InputState`, `TextareaState`, and `EditorState` are distinct GPUI entity types. Their private bridge to `InputBaseState` exists for component composition; it is not the application API. Editor-only implementation includes indentation, folding, decorations, diagnostics, search, LSP providers, overlays, line-number/gutter painting, and syntax highlighting. Textarea owns rows, soft wrapping, Enter submission, and auto-grow policy. Masking, validation, and number stepping remain input-only concepts. ### Presentation and geometry Presentation is injected through `InputEditorStyle`, highlighter interfaces, fold-icon renderers, context-menu adapters, and the higher-level UI forms. `gpui-component` supplies editor insets from its size system. Base consumes those insets only as geometry so text, the fixed gutter, and scrollbars share a coordinate system: - text remains inset from the frame; - vertical and horizontal scrollbars terminate at the frame edge; - the gutter background covers the complete fixed column, including top, bottom, and leading insets; - editor focus does not add the single-line input focus-border treatment. This keeps product values in the presentation layer while keeping coupled text, gutter, and scrollbar geometry local to the editing engine. ### Input groups `gpui-component::input::InputGroup` composes one `Input` or `Textarea` with typed addons and buttons in a shared frame. It owns no editing state: the control is the ordinary styled `Input`, rendered without its own border, background and focus ring, so every capability the input gains — paste hooks, touch selection, native menus, accessibility — reaches a group without being mirrored. `InputGroup` owns the outer border, radius, focus and error treatment, and four logical addon regions; `InputGroupButton` wraps a `Button` with compact presentation and sizes through `Sizable`. Textarea viewport geometry remains with the editing engine. Validation appearance is caller-controlled, separately from edit acceptance. Platform-specific behavior is isolated behind adapters. For example, folding is disabled on WebAssembly, time uses `web_time` where needed, and native text content support is conditionally compiled. ## Text Rendering for External Components `gpui_base::text::editor_host` adapts the same renderer used by `TextView`. It is a rendering boundary for externally owned rich-text components, not an editing engine. The host projects its own document into `BlockNode` values; Base owns shaping, wrapping, inline-object layout and geometry. The host owns document identity and revisions, editing semantics, commands, history, selection, focus, accessibility integration, native input and scrolling policy. ### Identity and lifecycle There are two independent identities. A stable GPUI element ID scopes retained render state; `BlockNode::render_with_id` provides that scope for top-level blocks. Leaf keys identify geometry and decorations within one collection. Keys must be unique in that collection. Cloning the AST shares runtime state; calling `with_leaf_key` detaches the leaf's runtime text/selection state so rekeying a clone does not rename the original leaf. Independently rendered instances need distinct keys. Run `collect_leaf_geometry` around child prepaint, not rendering or painting. The scope is synchronous, nests with isolation and restores its predecessor during unwinding. Only prepainted leaves are reported. A virtualized or cached subtree that does not prepaint is absent; a missing entry does not remove a leaf from the host document. `LeafGeometry` copies bounds and retains immutable shaped lines. It does not retain mutable `TextLayout` handles. A later frame cannot change an existing snapshot, but that snapshot describes only its original content, typography, width and scroll offset. Hosts must associate geometry with their own document revision and replace it when those inputs change. ### Coordinates and presentation Leaf offsets are UTF-8 bytes. Each inline image or custom inline node occupies one U+FFFC (three bytes), independent of its fallback text. Window coordinates are logical pixels, before display scaling. Hard breaks and empty lines retain caret positions, including the final empty line. Native input APIs that use UTF-16 require conversion in the host. Decorations are ordered half-open byte ranges. Text decorations paint behind glyphs. Any nonempty overlap with an atom decorates its entire box, over the content, so an opaque image can show its selected state. The host supplies the color and should use translucency for an atom overlay. Empty ranges paint nothing. Decorations do not change layout. `RenderContext::new` accepts local presentation without application globals; `from_app` uses TextView defaults. A host can override syntax highlighting, image resolution and link activation locally. Reference links need definitions from the entire document, including blocks outside the viewport. Register them through `with_link_reference`. Host foregrounds are render-local presentation, supplied through `RenderContext::with_foreground_ranges` for keyed paragraph leaves. They use the same UTF-8 byte space as geometry, with U+FFFC for each inline object. They override glyph colors from marks and links; later supplied ranges win overlaps. Other mark properties and link activation remain intact. Uncovered text keeps normal inheritance. The node tree and its cached base mark styles never retain these colors: a host can change its theme/surface or remove the ranges next frame without rebuilding the document. Custom objects inherit an override only when their entire placeholder is covered; images are unchanged. ### Evolution boundary Keep the sink, layout caches, flow fragments and shaping implementations private. The public AST is a projection into the renderer, not a canonical editor model or persistence format. Do not add editor-specific document operations, history, collaboration or serializer policy to Base merely to serve one consumer. This boundary currently supports horizontal text rendered through TextView's block flow. It is not a complete contract for every future editor. In particular, a future extension for visual navigation must represent caret affinity at soft wraps and bidirectional runs; a byte index alone is insufficient. Per-position caret metrics and range rectangles should be designed together with that contract, not inferred from one leaf-wide line height. Pagination, arbitrary block-level widgets, native accessibility text ranges and vertical writing need their own demonstrated requirements and contract tests before being promised. The newly public `BlockNode` enum is non-exhaustive so variants may be added. Its existing named variant fields still constrain changes to those variants; non-exhaustiveness is not permission to rename fields or add required fields. Prefer new private-field option types and additive constructors when extending the seam. Preserve existing public types and import paths; a pre-1.0 disclaimer does not replace this project's compatibility requirement. ## Overlay and Positioning Architecture Overlay modules separate lifecycle from placement and presentation. ### Positioner `Positioner` is the shared placement implementation. It supports: - side placement with preferred-side selection, flipping, alignment, offset, and viewport clamping; - corner placement compatible with anchored trigger geometry, with viewport clamping but no side flip. Popup measures its trigger during prepaint, stores the captured bounds in keyed state, and renders positioned content through a deferred element on the next frame. Tooltip reuses Positioner rather than maintaining another collision algorithm. ### Modal hosts Dialog and Sheet create viewport-sized hosts. They own focus trapping, keyboard actions, backdrop or overlay dismissal, and callback ordering. They do not own the final popup or panel placement: the application styles the supplied popup or surface as centered, right-aligned, bottom-aligned, or another product-specific layout. Overlay visuals and overlay hit targets are separate layers. The surface is painted above the dismissal target so a click inside content does not dismiss the modal. ## Scrolling and Virtualization `ScrollbarHandle` is the seam between scrolling implementations and the shared Scrollbar element. An adapter reports: - viewport bounds; - current offset; - content size; - offset updates; - optional drag lifecycle notifications. Adapters exist for GPUI `ScrollHandle`, `UniformListScrollHandle`, and `ListState`, plus base `VirtualListScrollHandle`. Scrollbar normally overlays the viewport reported by its handle. Two explicit alternatives support deeper widgets: - `viewport_bounds` supplies custom-painted bounds, as the editor does; - `viewport_from_layout` uses same-frame layout bounds, as DataTable does when excluding fixed headers and columns. VirtualList owns variable-size item layout, visible-range calculation, content masking, deferred scroll-to-item requests, and clamped offsets. The caller owns item sizes and renders only the requested range. Tree builds on GPUI's uniform list because its visible entries share a row height. `ScrollableMask` owns wheel dispatch, not scrolling. GPUI's own overflow listener runs in the bubble phase and never stops propagation, so a scroll area nested inside another one cannot win: `gpui::list` registers its listener after its items paint, and bubble runs in reverse registration order. The mask is a sibling of the scrolled element that consumes axis-dominant wheel events in the capture phase, locks each gesture to the axis it started on, and stays inert while occluded. Edge semantics differ per axis, matching platform scrollers: a vertical mask chains to the ancestor at the edge, a horizontal one keeps consuming. `horizontal_scroll_area` is the paired viewport and mask, used by the Markdown scrolling table. A Markdown code block whose style sets `overflow.y` to `Overflow::Scroll` pairs its viewport with a vertical mask and a `Scrollbar`. A plain mouse wheel has only a vertical axis, which a native horizontal scroller with a locked axis ignores. `ScrollableMask::vertical_wheel` sits over such a scroller and handles only that wheel: its lines move the scroller sideways and, at the edge, chain to the ancestor; trackpad gestures and horizontal wheels go on to the scroller with its axis lock and bounce. TabBar, the attachment row, the app menu bar and a horizontal `Scrollable` use it. The mask is pinned to its parent's top-left corner, so it covers the parent in a row as in a column. One handle represents one logical viewport. Sharing a handle between nested or unrelated scroll areas causes offsets, hitboxes, and scrollbar geometry to interfere. ## Dock Layout Architecture `crates/base/src/dock` owns the layout tree, persistence, drag hit-testing, resize arithmetic, the active-panel state machine, zoom, focus, and the panel registry. `crates/component/src/dock` is a skin: `DockSkin` implements the renderer traits below to supply the tab bar, toolbar, drop-indicator, and dock-toggle appearance. A `DockArea` built without a renderer still docks, drags, and persists — it draws no chrome at all. ### The layout tree `PaneTree` is the single source of truth for one region — the center, or one of the left/bottom/right docks. It stores no GPUI entity handles: containers are addressed by `NodeId`, panels by `PanelId` (the panel entity's `EntityId`), and a container's `NodeKind` is one of `Split` or `Tabs`. There is no leaf variant, so a panel can only ever live inside a `Tabs` node — the invariant a runtime assertion checked in the old implementation is expressed in the type instead. `NodeKind` stays private. Callers read a node through the borrowed `PaneRef` projection and never construct one directly; every mutation goes through `PaneTree`'s edit methods (`insert_panel`, `remove_panel`, `move_panel`, `split`, `set_active`, `set_sizes`), each of which runs `normalize` before returning. When base needs a fact about a panel — its name, its visibility, its dump — it asks a `PanelSource` rather than the entity directly, which is what makes the layout algebra testable as pure functions with no `TestAppContext`. Building a layout is entity-free too: `DockLayout` (`h_split`, `v_split`, `tabs`, chained with `.child(...)`, `.panel_view(...)`, and `.active_index(...)`) produces a tree rather than constructing containers. `DockArea::set_center` and `set_dock` reconcile a `DockLayout` into live entities when it is installed. Panels go in wrapped: `gpui_component::dock::panel_handle(panel)` is what carries a panel's presentation across the renderer seam, and every entry point takes one — `DockLayout::panel_view` when describing a layout, `DockArea::add_panel_view` when adding to a live one, and the closure a `register_panel` builder returns. ```rust,ignore let center = DockLayout::h_split() .child(DockLayout::tabs().panel_view(panel_handle(files), cx), Some(px(240.))) .child(DockLayout::tabs().panel_view(panel_handle(editor), cx), None); dock_area.update(cx, |area, cx| area.set_center(center, window, cx)); ``` Base's own `DockLayout::panel` and `DockArea::add_panel` take a bare `Entity

` instead. They are the base-only forms: the panel docks, drags and persists exactly the same, but base stores the bare entity and the skin cannot recover presentation from it, so **every tab draws the panel's `panel_name` where its title belongs**. The only signal is a one-off `tracing::warn!`. Use them only when there is no skin over the dock at all. ### Normalization One post-order pass, repeated to a fixpoint, replaces the mutually recursive parent-pointer collapse the old `StackPanel`/`TabPanel` pair used: 1. An empty `Tabs` or `Split` is removed from its parent; the root is exempt. 2. A `Split` with one child is replaced by that child, which keeps its own `NodeId` and inherits the split's slot size. 3. A `Split` containing a `Split` of the same axis splices the inner children into the outer node. 4. `active_ix` is clamped to the panel count. 5. Root shape is enforced per `RootKind` — the center's root is always a `Split`, so an empty center still serializes as a stack; a dock's root is unconstrained. `normalize` is idempotent and needs no parent pointers or deferred work: the tree is self-consistent the instant an edit operation returns. ### Reconciliation `DockArea` holds the tree plus a cache of container and panel entities keyed by `NodeId` and `PanelId`. After any edit that reports a change, it walks the tree, creates an entity for each container id the cache does not yet have, drops cache entries for ids no longer present — calling `on_removed` on the panels that departed — pushes sizes and `active_ix` into the surviving entities, and emits `DockEvent::LayoutChanged`. Because `NodeId` survives every edit operation and every `normalize` rule, a steady-state reconciliation pass creates and drops nothing: only genuinely new or dead containers churn. That is what keeps a drag from resetting the state of panels it did not touch. ### Rendering seam `TabGroup` owns the panel list mirrored from the tree, the active index, the focus handle, drag-and-drop hit state, and the zoom flag; it renders a skeleton and delegates all appearance to a `TabGroupRenderer`. `DockAreaRenderer` does the same for the area frame and the docks. Base attaches the drag source, drop-target hit testing, keyboard actions, and focus handling — a renderer implementation never sees a drag event, only resolved state through `TabGroupContext` and `DockContext`. `Panel` splits at the seam the same way: `gpui_base::dock::Panel` covers behavior (`panel_name`, `visible`, `closable`, `zoomable`, `set_active`, `set_zoomed`, `on_added_to`, `on_removed`, `dump`), and `gpui_component::dock::Panel` extends it with presentation (`title`, `tab_name`, `toolbar_buttons`, `dropdown_menu`, `zoom_control`). A panel type implements both. ## Theme Projection The base `Theme` contains: - `SemanticThemeTokens` for colors, radius, spacing, typography, and shadows; - global Scrollbar defaults; - the minimal Resizable handle colors required by its infrastructure. Semantic tokens describe roles and scales, never component names. They do not automatically style base controls. A presentation layer reads and applies them. `gpui-component` projects its active theme into the base theme during initialization and theme changes. Direct base users can mutate `gpui_base::Theme::global_mut(cx)` themselves. ## Motion and Lifecycle Ordinary semantic controls do not install product motion. The generic `motion::transition` function manages keyed interpolation, timing, reversal, animation frames, and reduced-motion behavior while the caller chooses the animated property. Some deep behavior modules own configurable motion that is inseparable from their layout lifecycle. `ToastStack`, for example, owns stack expansion, collapse, measurement, and overlap motion through `ToastMotion`. Its child `Toast` remains an unstyled semantic root, and the application owns toast content and visual styling. This distinction is intentional: - product decoration and choreography belong to presentation; - motion required to keep a deep behavioral layout coherent may live in that module and must be configurable. ## Initialization Call `gpui_base::init(cx)` before constructing base controls. Initialization: - installs the base global theme if absent; - initializes shared global state; - reads the operating system's reduced-motion preference into `App::set_reduce_motion` (see `reduce_motion.rs` for the per-platform sources and the rule that an application's own setting wins); - registers key bindings and infrastructure for dialog, focus traps, popover, sheet, combobox, color picker, select, number input, input, and tree. Applications that call `gpui_component::init(cx)` must not initialize base a second time; the styled crate includes base initialization. ## Relationship to `gpui-component` `gpui-component` is a presentation adapter and compatibility layer above base. It may: - map its Theme into base tokens and infrastructure defaults; - wrap base elements with labels, icons, sizes, variants, and product layout; - provide application-specific popup menus, LSP views, and native integrations; - preserve historical public interfaces while delegating behavior to base. The migration of a UI control to a base module is complete only when behavior, focus, keyboard interaction, accessibility, overlay geometry, and visual output remain correct. Sharing a type name or compiling an adapter is not sufficient. ## Design Invariants Changes to `gpui-base` should preserve these invariants: 1. Base remains independent of `gpui-component` presentation and assets. 2. Reusable behavior is implemented once behind the lowest useful interface. 3. Applications can replace the visual language without reimplementing the behavior module. 4. Behavioral geometry stays with the module that must keep it correct. 5. Controlled elements report intent and do not own application values. 6. Stable element identity is documented wherever keyed state is used. 7. Pointer and keyboard paths converge on the same semantic action. 8. Accessibility is behavior, not optional decoration. 9. Overlay coordinates, paint order, and hitboxes use one viewport model. 10. One scroll handle represents one logical viewport. 11. Platform differences are isolated behind explicit adapters or conditional implementations. 12. Public types that cross the seam keep their fields private, are built with a builder, and are read through methods, so a new field is not a breaking change. 13. Long-lived architectural facts belong here; progress logs and temporary reviews belong in issues and pull requests.