# SolidCraft UI kit The internal opt-in [universal panel foundation](panel-foundation.md) separates engine layout/specs from retained UI controllers and host-local services. `panel_runtime::EguiPanelHost` mounts bounded Header/Body/Footer slots through one existing `ui_kit::Container`; content controls its own scrolling. This reference adapter requires a full rectangular paint/input surface, rejects sparse geometry, and preserves parent clipping. `SolveApp::mount_panels` uses the enabled runtime's injected services. It adds no default placement/content change. Production docking and panel migrations remain separate. `viewport_status_item` and `viewport_status_row` draw persistent viewport pills on `Surface::ViewportStatus`. Callers supply translated names, formatted settings, kind, stable identity and key availability, and dispatch returned clear/edit actions. The row keeps supplied order and anchors to the top centre of the free viewport. Its presentation state remembers only open cards, focus, scroll and the selected diagnostic record, scoped to the current document. The `ui.status_fixture` control method provides named specimens for UI scenarios and live visual review. `details` renders a `Details` description in the shared host: optional caption, live settings, key, findings or readout, then at most two text actions. Tools supply data and handle `DetailEvent` requests through their normal commands. The caption and actions stay fixed around a body bounded to 60% of the free viewport height. Findings/readouts preview six rows; Show all expands the same scrolling body. `patterned_swatch` supports an optional second semantic colour, so a grouped key can honestly represent both existing scene colours. `viewport_status_details` defaults to live cards. Its false value restores read-only keys and the Section dialog independently of the analysis/Section HUD and Summary preferences. Continuity edits explicitly select one retained body/edge record; its own successful limits and sample count accompany every live command. Stale geometry disables editing and targets. `view.status_details` reads current effect settings/capabilities and the retained per-seam diagnostics without preparing or evaluating geometry. `details_findings` and `details_readout` fixture presets are labelled component specimens, not production results. | Component | Use | | --- | --- | | `method_section` | Section with a GPU/CPU tag; unsupported contents stay faded with a readable reason. | | `read_only_text` | Bounded, selectable code box for read-only diagnostic JSON. | `crates/ui-egui/src/ui_kit/` contains the tokenised controls used by feature dialogs, preferences, manager cards, prompts, context menus and the sketch palette. Import through `crate::ui_kit::{...}`. Most controls are crate-visible; the container API is public so platform apps use the same hosts. ## Stage bars `StageBar` composes two clipped, horizontally scrollable rows inside the shell's shared floating `Container` (`Surface::Chrome`). Its 36 pt first row contains numbered stage chips with the current workspace accent, shared primary/normal buttons and the existing command-search editor. The second row fills the remaining card height. The feature supplies labels, enabled reasons and command dispatch; the kit owns chip geometry, row spacing, typography and colour. Stage selection is navigation; the kit never invents completion badges. The shared compact workspace card is 104 × 88 pt; its icon, label and caption are centred vertically. The stage bar is 88 pt high with an 8 pt gap from that card. Its 36 pt header and 52 pt tools band share a 1 pt divider. Tools use 8 pt side padding, icon-text segments, icon-label actions and an information chip, all with `theme::Tokens` colours in both themes. Render retains its Inspector controls outside the bar. `Panel::workspace` gives paired floating docks the same bounded vertical span and limits each dock to half the available width with an 8 pt gap. Content clips inside the allocated host, so a small window cannot grow a dock into the stage bar or Shots strip. Docks without usable space are omitted. Render's default outer widths are 266 pt for Browser and 305 pt for Inspector, retaining the shared minimum/maximum resize rules. Its compass is 110 pt; the 48 pt Shots strip clears the docks by 8 pt. ## Composition and rules | Component | Contract | | --- | --- | | `ReviewAndApply` | Shared resource card, wrapping Replaces/Kept rows, variant content, acknowledgement, Cancel and a gated apply action. Physical plans offer Copy plan. Composes `Confirm`. Gallery: `review_and_apply`. | | `SphereTile::badge` | Optional availability text on a three-column library tile, alongside the user-count badge. Uses theme tokens. | | `catalog_footer`, `catalog_footer_with_tip`, `catalog_footer_height` | One-line information note with ellipsis and full guidance on hover; optional separate tooltip text. | | `CatalogLayout` | Fills the remaining panel height with a scroll body above a measured bottom footer. Optional minimum search-row count keeps lists usable while a long detail card scrolls independently. | - Every outer host uses `ui_kit::Container` with a semantic `Surface`. The shell publishes the layout once per frame; `Container::floating()` and `frame()` own adaptive placement policy and chrome. `Panel`, `Window`, `Confirm` and `Menu` compose it. Raw egui hosts and outer shadows are rejected recursively, including platform apps. The mandatory `cargo xtask ui-containers` gate runs in fast CI and scans new crates/apps automatically, including macro bodies and alias chains. See the [container audit and migration inventory](ui-container-audit.md). - Colours, typography, dimensions and spacing come from `theme::Tokens`. - `Container::selection_card` extends the navigation host with a bounded, rounded selection pill. `selection_actions` composes its name chip and quiet icon actions; callers supply command data and dispatch the chosen action. `Container::tooltip` shares the library samples' tooltip frame and padding. - Sliders only via `ui_kit::slider`; `Slider` options change behaviour, never appearance. Raw sliders and duplicate track/thumb painters are forbidden in the UI and apps. - `scope(ui, add)` supplies form styling. `row(ui, label, add)` aligns labels and controls at `control_height` (28 pt). Wide manager rows use a 104 pt label basis with a 10 pt gutter, derived from the compact 70 pt basis and spacing tokens. Compact rows retain their 8 pt gutter. Labels widen from the appropriate basis to at most 1.6 times that width and never exceed half the row. - `expr_row`, `hint_row`, `text_row` and `choice_row` compose `row` with the existing expression, hinted expression, plain text and dropdown controls. They preserve row order, labels, focus IDs and completion; callers keep Enter/change ownership and explicit dropdown IDs. `count_row` composes `row` with `count_field` for pattern quantities. `checked_row` is `hint_row` for a value the form checks before it runs: with a warning, the field is outlined in the warning colour and the message sits under it (`warning_caption`), so colour is never the only cue. - `Panel::workspace` owns grip-based left/right placement and bounded width resizing for floating workspace cards, including in Compact ribbon layouts. It reuses the 40 pt Panel/Window header and padded body, with no second top inset below the ribbon. Persistent workspace cards omit the close action. `Panel::workspace_with_header` lets tree cards compose the shared `tree_header` (title, compact lens and quiet search action). Its tree policy caps the card at ten 28 pt rows and lets rows own their 8 pt side insets; header navigation retains a separate title grip. Optional header actions share the same title band. `stack_first_bounds` reserves a bounded Browser above its companion; `stack_next_bounds` places that companion with the shared gap. `Panel::bottom_strip` uses `Container::bottom_strip_rect`, the same placement as Design History. `Container::navigation_card` supplies the shared Navigation surface for anchored HUDs. Register measured card rectangles and reserved edges with `shell::register_panel` so fitting and pointer shielding agree. Feature modules provide content, never outer chrome. - `Panel` owns bounded windows, feature/palette composition, scrolling bodies, pinned actions, compute status and keyboard routing. `wide()` uses three card widths. `feature_frame` and `palette_frame` retain docked/floating surfaces. `FeatureOptions::apply_label` adds Apply; `Panel::actions` supplies custom labels. With `DialogButtons::Top` published (Preferences › Interface › Dialog buttons), `feature` puts the actions in the header row through `HeaderActions` instead of a footer: the same buttons, enabled states, keyboard and handles, each as wide as its bold label, with Cancel standing in for the close button and the compute status in the title's place. `palette` (the Sketch panel) follows the same preference: its footer row moves under the header, above the options. `body_rows` caps a manager's scrolling body independently of its position; short viewports still keep the header and actions visible. - `Window` composes the shared Panel frame with a 40 pt icon/title/close header, padded content and optional 48 pt footer. `show_with_left` pins a left action beside the right-aligned buttons. `show_pages` accepts declarative `WindowPage` entries (icon, i18n label key, optional group heading, stable handle, scroll policy and draw callback). Its 188 pt soft page list (150 pt below a 700 pt window width) has 32 pt icon/label rows, selected tint/ink and a divider. Page windows use the navigation board's border/radius and the shared menu shadow; other windows retain their shared card surface. Preferences has nine grouped pages; legacy control-channel page names remain aliases. `page_rows` opts into a taller body while preserving viewport bounds. `actions`, `anchor`/`placed`, `cards`, `fields` and `keep_open_on_escape` retain existing window behavior. Empty action labels omit the footer. - `Confirm` is an input-blocking modal using the same card frame and footer controls. It shows a 36 pt icon disc in warning, danger or info tone, a title, caller-supplied message and optional monospace `detail`, above a 52 pt footer. Glyphs use the construction ramp for warning, the shared shaded danger treatment for danger, and base blue for info; the disc keeps its tint. `actions(primary, cancel, left_ghost)` places the optional third action at the left. Danger uses `form_error` with a contrast-selected on-colour; other primaries use blue. Enter invokes the selected answer (primary by default) when valid; Esc cancels. It retains close, answer and message scenario handles. The old `Prompt` element is removed. - Callers supply data and enabled state, then dispatch existing commands. Editors leave their established completion, Enter, Tab and commit ownership unchanged. - Every text input uses the kit's shared `text_input` policy: symmetric horizontal `form_inset`, zero vertical margin and centred galley alignment. Font metrics therefore centre both mono and proportional text within the allocated field, including invalid and attached Library fields, without per-caller offsets. Canvas placement editors retain their native line-height plus `space_4` hit target so nearby sketch picks are not intercepted; alignment stays shared. - Every kit text field has one right-click menu, `text_menu::menu` (#8875): Cut, Copy, Paste and Select All, disabled when there is nothing selected, the text is read-only or a password, or the clipboard holds no text (always in the web app, where Ctrl+V pastes). A kit helper calls it right after adding its `TextEdit`; the edit marks the response changed and gives the focus back, so owners that commit on lost focus keep the field. Filters inside menus and drop-downs have no menu of their own. Rows publish `menu:text_cut`, `menu:text_copy`, `menu:text_paste` and `menu:text_select_all`. - Scenario handles and accessible labels are API. Consolidated options preserve the former geometry, wrapping, native metadata and publication keys. Preference compositions use `settings_heading`, shared disclosure `settings_section` headings and `settings_pair` for responsive controls beside one specimen. `option_card` supplies the shared illustrated radio control. `settings_form` and `settings_block_hint` define aligned label grids; `theme_rail` keeps the import tile fixed beside a scrollbar-free scrolling row. `PreferenceFigure` owns theme swatches, orbit pivots, quality, anti-aliasing, contact shading and up-axis figures. `layout_preview` follows the four workspace choices without opening another viewport. Scale, palette, autosave, mouse and live-axis specimens share tokens and the gallery; live bars show actual calibrated input and introduce no repaint timer. `theme_options_list` renders generic `ThemeOptionRow` definitions without standard-option branches. Its pure `preferences_layout::layout` packs measured narrow/composite/wide entries in order, with ten non-advanced top-level cells before More. A composite retains its child in one cell; advanced descendants move the entire cell under More. Packing and allocation share measured localized label/readout widths, the form gutter and a compact slider minimum; short composites can share a row with narrow entries. A composite takes a full row when needed and stacks its controls only if they exceed that entire row's width. `theme_options_badge` paints inside an existing theme card and is called only for offered options. The small registry in `theme_options.rs` supplies standard label/help catalog ids, availability and behaviour; only opacity is registered today. Missing providers are absent; unavailable registered providers keep their controls and show their translated reason. Callers submit list edits and Reset through the existing atomic preference merge/control path. The scoped `i18n::resolved_labels` adapter keeps theme language-map text and fallback labels verbatim through shared controls, while stable catalog messages continue to translate. Review fixtures are in `crates/ui-egui/tests/fixtures/theme-options.json`, keyed by `light-0/1/3/6/12` and `dark-0/1/3/6/12`. Each value is a complete importable theme file: pass it as `ui.prefs` `theme_import` together with `open: "Interface"`. The 12-entry files include a four-choice wide row, Shadow/Softness composite, six-choice dropdown, an advanced entry and overflow. More contains two top-level entries. Set `theme_options: {"options-dark-12": {"opacity": 60}}` (or the selected fixture's id) to compare with 100 on a busy model. Reset uses `prefs:theme_options_reset`; More uses `prefs:theme_options_more`; controls and cells publish `prefs:theme_option:` and `prefs:theme_option_cell:`. Unavailable-provider specimens are internal to the existing `prefs::tests::theme_options_provider_registry_filters_unknown_and_retains_unavailable` test; they are never installed in the live registry or exposed as preferences. The manager captures and reviews live-app screenshot evidence separately from the headless harness tests. ## Elements `aspect_grid` composes three-column `AspectTile` choices with token-sized aspect glyphs, selection tint and stable targets. `frame_guides` paints a bounded viewport mask, outline, thirds, a 90% safe area and a centre mark from `FrameGuides`. These are viewport decorations; callers exclude them from exported images and result canvases. Module names below describe implementation locations, not caller import paths. Every inline element/variant has one entry in the light/dark `gallery::ELEMENTS` table. Panel/Window/Confirm compositions also have dedicated geometry and interaction tests. | Module / element | Use and options | Main tokens | | --- | --- | --- | | compare: `comparison_divider`, `comparison_divider_chrome`, `comparison_badge` | Clamped vertical image wipe handle and bounded corner labels. The caller owns commands, shared framing and navigation suppression while the handle captures input; the paint-only chrome can be raised above source callbacks. | `control_height/radius/border`, `space_2/6/8/16/24`, `font_label`, `form_ink`, `panel`, active `TabAccent` | | assistant: `assistant_header`, `assistant_controls`, `assistant_badge`, `assistant_bubble`, `assistant_prose`, `assistant_tool_frame/row/detail/status`, `AssistantToolState`, `assistant_approval/actions`, `assistant_composer/footer`, `assistant_input`, `assistant_send`, `assistant_empty`, `assistant_banner`/`banner`, `AssistantTone` | Shared Assistant presentation: 40 pt header, 22 pt state pill, right-aligned user messages limited to 86% width, unboxed assistant prose, bordered tool groups and command detail, tinted approval heading, rounded focused composer with one unframed growing editor and pinned footer, centred setup copy and semantic notices. Closures preserve existing permission choices, protocol routing, queue actions and scenario handles. | `form_header_height/close_size`, `font_label/small/body`, `dialog_body/footer/rule`, `control_fill/border/radius`, `form_ink/dim/primary/success/error`, `accent_soft/selected_ink`, `warning`, radii and spacing | | history: `StripHeading`, `HistoryChip`, `HistoryStyle`, `history_drop_bar`, `history_marker`, `HistorySeam`, `history_seam` | Shared strip headings use semibold `font_heading`, `text`, a centred baseline and `space_12` trailing gap. One painter family for bottom chips, 38 pt vertical icon chips and single-line 26 pt name rows (the browser's shared `TREE_ROW_HEIGHT`). Selected items use the active tab tint/ink/border; all variants retain multi-tone feature artwork using one shared kind-to-accent rule (Sketch green, Surface orange, Mesh yellow, Form purple, solids base blue); rolled-back items have 42% opacity; a reference row (a sketch listed under another feature, #6489) has its name in secondary text. Error/warning badges, suppression strikes, horizontal/vertical drop bars and rollback line/hollow circle are shared. No connector lines. | `control_height`, `space_2/3/4/6/8/10/12/14/16/24`, radii, typography, `TabAccent`, `timeline_item`, `control_border`, `text/dim`, `form_error/fill` | | tree: `tree_row`, `Row`, `TreeAction` | Shared Browser rows with disclosure, visibility (an eye column at the row's end, clear of a floating scroll bar, with a gap between the disclosure and the activation radio; or beside the disclosure with `eye_left`), activation, selection, appearance swatches, optional subtitle and trailing actions. Compact scene rows are 28 pt, with inline right-aligned summaries, circular swatches, trailing eyes and full-width selection tint. Actions use quiet icons and can appear on hover. Disabled actions retain their reasons and cannot select or fold the row. Browser automation records the same row geometry. | `TREE_ROW_HEIGHT`, `font_label/small`, spacing, control radius/border, `text/dim`, workspace selection tint | | search: `CommandSearch`, `MenuItem::search_row` | Ribbon search composition: one persistent ribbon editor expands from 28 to 40 pt and to 520 pt wide with its right edge anchored, magnifier and S/Esc keycap, 38 pt two-line menu row with 26 pt icon tile, accent bold substring, path, shortcut/kind keycap, counts, JSON detail box and footer. The ordinary 30 pt menu row cannot fit a second line; this variant keeps its enabled/selected states and scenario IDs. Ranking and dispatch stay with the caller. | `form_card_width × 2 − space_8` (520 pt), `control_height`, `space_2/3/4/6/8/10/12/14/16/24`, `radius_xl/control_radius`, `control_fill`, `dialog_body/footer/rule/border`, `form_ink/dim/primary/error/error_fill/success/success_fill`, `selected_ink`, `accent_soft`, typography | | menu: `Menu`, `MenuItem` | Shared popup, 30 pt rows, 28 pt icon column, clipped labels, right-aligned mono shortcuts, submenu chevrons, checked/unchecked and document swatches, inert 40% disabled rows and danger ink; headings and separators; ribbon drag/pin targets retained | `dialog_body/border/rule`, `radius_xl`, `control_height/radius`, `form_ink/dim/primary/error`, `accent_soft`, typography and spacing | | radial: `radial_menu` | Eight rounded annular sectors, parallel 4 pt gaps, 9 pt circular corner joins, tab-accent artwork and hover, centre label/key; existing hold/flick/release dispatch | `dialog_body/border`, `form_ink/dim`, `TabAccent`, icon palette, font/spacing tokens | | panel: `Window`, `WindowPage` | Shared card frame, header, optional page list, bounded content and footer with a left action | `form_header/footer_height`, `form_card_width`, `form_label_width`, `control_height`, `dialog_footer/rule`, `selected_bg/ink`, spacing | | confirm: `Confirm`, `Tone` | Modal warning/danger/info, title/message, mono detail and ghost/cancel/primary actions | `warning`, `form_error/fill`, `accent_soft`, `form_primary`, `on_accent`, `tab_on_fill_dark`, `font_subtitle`, spacing | | status: `completion_text` | Compact strip readout with an optional green completion tick and caller-supplied result identity | `font_small`, `control_height`, `form_ink/success/dim`, `space_4/14` | | metrics: `row_height`, `table_row_height`, `header_height` | Shared control/header heights | `control_height`, `space_8`, `form_header_height` | | layout: `scope`, `row` | Form styling and aligned label/control columns | `form_label_width`, `form_gap_y`, `control_*`, `font_label`, `dialog_label`, `space_4/8` | | `caption`, `warning_caption` | Dim caption aligned beneath a control; the warning one says what is wrong with its value | `font_small`, `text_dim`, `warning`, label-column tokens | | `status_line`, `StatusTone` | Full-width, wrapping status or refusal text in dim, warning or error ink. Translates source labels; already translated messages also work. Place directly in the form, outside `row`, so its height grows with the whole message. Rect: `dialog:status:TEXT`. | `font_label`, `text_dim`, `warning`, `error` | | `figure_rows`, `FigureSlot` | A caller-painted figure in the label column beside a stack of captioned expression fields (the Hole dialog's section); the caller gets each field's rect and hover/focus state to draw leaders and light the part it sizes. Fields keep their `dialog: