Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Polykrom UI kit

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.

Composition and rules

  • 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.

  • Colours, typography, dimensions and spacing come from theme::Tokens.

  • 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.

  • 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.

  • 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 has 32 pt icon/label rows, selected tint/ink and a divider. 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.

  • 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.

Elements

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 / elementUse and optionsMain tokens
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, AssistantToneShared 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: HistoryChip, HistoryStyle, history_drop_bar, history_marker, HistorySeam, history_seamOne 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. 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
search: CommandSearch, MenuItem::search_rowRibbon 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, MenuItemShared 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 retaineddialog_body/border/rule, radius_xl, control_height/radius, form_ink/dim/primary/error, accent_soft, typography and spacing
radial: radial_menuEight rounded annular sectors, parallel 4 pt gaps, 9 pt circular corner joins, tab-accent artwork and hover, centre label/key; existing hold/flick/release dispatchdialog_body/border, form_ink/dim, TabAccent, icon palette, font/spacing tokens
panel: Window, WindowPageShared card frame, header, optional page list, bounded content and footer with a left actionform_header/footer_height, form_card_width, form_label_width, control_height, dialog_footer/rule, selected_bg/ink, spacing
confirm: Confirm, ToneModal warning/danger/info, title/message, mono detail and ghost/cancel/primary actionswarning, form_error/fill, accent_soft, form_primary, on_accent, tab_on_fill_dark, font_subtitle, spacing
metrics: row_height, table_row_height, header_heightShared control/header heightscontrol_height, space_8, form_header_height
layout: scope, rowForm styling and aligned label/control columnsform_label_width, form_gap_y, control_*, font_label, dialog_label, space_4/8
captionDim caption aligned beneath a controlfont_small, text_dim, label-column tokens
section, section_count, dividerPersisted small-caps disclosure with chevron, optional count badge and trailing hairline; full-width rulecontrol_height, dialog_rule, font_caption, tracking, spacing
list_rowPicked item with a remove targetcontrol_height, font_label, icon/spacing tokens
text, TextHint, note, report, inline or cell text; see variants belowfont_small/label, text/dim/error, control_height, space_4
text_linesVirtualized read-only licence/report lines, horizontal overflowfont_small, text, control_height
fields: field, hint_field, focused_fieldExpression editors, optional empty hint or retained focus; returns accepted Entercontrol_height/fill, form_inset, font_label
text_field, focused_text_fieldPlain name editor, optionally retaining focusfield tokens
multiline_text_fieldThree-line plain-text row with stable focus IDfield/row tokens
password_fieldStable secret editor; excludes the secret from scenario/accessibility metadatafield tokens
message_fieldGrowing message editor; owner retains scroll and Enter routingfield tokens
cell_editor, bounded_cell_editor, search_field, library_cellStable-ID inline/search editors; bounded variant caps Unicode characters; library_cell attaches a Library dropdown inside the editor borderfield tokens, text/error
text_areaBounded multiline JSON/data editorfield tokens
dropdown, dropdown_menuIndexed choices or caller-supplied menu controlscontrol_height, text_dim, form tokens
searchable_dropdown, SearchOptionFiltered choices, custom-value action and pending messagedropdown tokens
selection_field, SelectionSelection chip with explicit active and filled stateselected_bg/ink, accent, control_*, form_inset, space_8/24
value_pairPaired captions/expressions; optional independent field namesrow/field tokens, space_8
fraction_field, scalar_fieldLocale-aware bounded dimensionless values; scalar fields fill their control columnform control/font tokens
number_fieldInteger/decimal field with caller range, drag speed and suffixform control/font tokens
length_field, angle_fieldLength in design units, angle in degreesform control/font tokens
slider + Slider optionsOne token-painted track/thumb over native slider/value interaction; optional step, caller formatting, whole-step manipulation and hidden value editorcontrol_height, space_24/8/4/2, form control/font tokens
placement_fieldStable focus ID; drawing tool owns Enter/Tab validation and completioncontrol_fill, font_body, text, control_height, spacing
color_field, color_popoverOpaque document RGB swatch and kit popover; global U8/F segments, copy, gamma-byte or linear RGB fields, painter-drawn HSV square/hue and current strip; outside click dismissescontrol_*, form_*, border, on_accent, space_*
choice: segmented, SegmentsEqual text choices; states supplies mixed selection, compact clips instead of falling back to a dropdown, primary uses accent/on-accent for the selected segmentcontrol_*, selected_bg/ink, font_label, space_4/8
segmented_icons, IconSegmentEqual icon choices and mixed statessegment tokens, icon palette
checkbox, checkbox_indeterminate, checkbox_row, checkbox_grid, optionsOne box/tick painter for inline, mixed, full-row, grid and menu choicesspace_14/3/2, radius_sm, control_fill/border, form_primary/ink, hover, on_accent
text_emphasisBold/italic with translated accessible namesform control/font tokens
tool_choices, ToolChoice, toggle_iconWrapped command modes or independent icon togglessketch mode/icon tokens, accent
list_pickerSelectable row with caller identity and native checked statecontrol_height, form selection tokens
popup_list, PopupOptionFocus-neutral suggestions; owner retains keyboard routingspace_2, form_label_width, control_height, form tokens
color_swatch, swatch_grid, color_thumbnail30 pt draggable samples, 6 pt grid step gap, selected outer ring and checker pattern under transparent colours; caller payloads retained; local-library thumbnails use 16 ptcontrol_height/radius, border, form_primary, control_fill, space_2/6/8
assistant_entry, AssistantEntryStateApplication-bar chat icon with quiet, ready, busy, auth, stopped and approval count marks; caller owns state and targetapp_bar, agent_ok/busy, warning/error, font_badge
actions: button, ButtonSecondary action: Normal, Small, or Enabled(bool)native sizing, form font/control tokens
primary_buttonAccent action outside a footeraccent, on_accent, form tokens
icon_button_atIcon action inside a supplied rectangleform_icon_inset, text/dim, icon palette
button_pair, PairButtonEqual secondary actions, optional icons and selected stateform_button_height, control/selection/icon tokens
footer, Footer, ActionOK/Cancel with optional Apply, compute status or selected actionform_footer/button_height, dialog_footer/rule, control_*, accent, spacing
keyboardEsc cancels, unowned Enter submits when validnone
action_menu, command_menu_buttonNested dense action menu or ribbon overflow action retaining its icon, shortcut and enabled stateform control/font tokens, space_16, icon palette
table: table, table_custom, Column, Table::headers, Table::row, TableRow::cell, Table::add_promptLeft-aligned headers/cells, horizontal overflow, selected/hover rows and empty state. table_custom supplies header actions through headers. Column::flex shares remaining width by weight, freezing columns at their control minimums before redistributing the remaining space; Column::icon_only hides the heading while retaining its automation identity. add_prompt is the final slim dashed line and requests a regular draft row from its owner; Column::new(label, width) uses label-column multiples, min_content_width(points) reserves measured control content; ContentWidth::name measures strong configuration nameslabel-column tokens, control_height, dialog_rule/footer, selected_bg, spacing
removable_header, plus_header, bin_button, active_pillHover-only column removal, anchored Add Column menu with insertion brackets, icon-only immediate deletion and active-row status. Bins preserve dialog:button:Actions:Delete and :×; header removal preserves dialog:button:LABEL:×.control_*, form_*, selected_bg, spacing
compact_button, summary_rowBorderless stars and chevrons with enabled/selected states; named two-line rule summary beside an enable checkbox and compact actions.form_close_size, control_*, form_*, typography and spacing
label_actionsClipped row name beside compact actionscontrol_height, form font/spacing tokens
status: status_header, StatusHeaderSupplied icon/name/status and constraint meterfont_label, text/dim, accent, sketch/header tokens
progressClamped determinate progressspace_3/6, dialog_rule, accent

History contents are selected from measured panel width. A 56 pt thin strip shows icons and four vertical transport controls: beginning, back, forward and end. The minimum named width is 130 pt; it adds horizontal transport and the same six-dot window grip as Assistant. When the remaining header space fits its measured text, History appears between grip and transport. No names-mode flag or command selects these layouts. The seam snaps between 56 and 130 pt; widths from 130 through 624 pt remain continuous and persist as timeline_left_width.

Drag the grip or title to dock along the bottom, at the full-height left edge, or beneath the browser. Either card’s right edge resizes their shared width. This position divides available column height equally. Folding either card leaves its header visible and gives the remainder to the other. If both are folded, their headers sit together at the top of the column. Expansion and collapse share a smooth cubic transition capped at 120 ms, respecting the interface’s instant-animation setting. Their collapse controls share the same active-tab blue hover tint. Browser resizing also resizes History. The view.history_layout command validates width, docking and collapse requests without altering the design or opening dialogs. Floating mode preserves viewport projection.

The seam handle overlays the strip’s right edge without taking layout space. Its grip is visible at rest; the arrow appears only on hover. Click, double-click the edge, or use Enter/Space on the focused handle to resize between thin and minimum named widths. Drag freely above the named minimum; resizing draws no dotted guide or name label. Header/title dragging and the edge width gesture are separate from feature/folder and rollback dragging. The rollback marker is 12 pt high. Its 2 pt line uses the active tab fill and ends in a 10 pt hollow circle with a 2 pt accent border and the card/panel surface inside. The circle overlaps the line end by 1 pt. The bottom strip uses the same painter, rotated. Folder selection, range selection, context menus, sketch-pattern children, drag reordering and command JSON retain their existing behavior and handles. history_icons, history_names, history_marker and the history_seam_rest/hover/drag states have light/dark gallery references. The CPU board test captures both strip widths with expanded and collapsed browsers (SOLIDCRAFT_HISTORY_EVIDENCE).

text(ui, text, options) supports the existing text behaviors and the new error band:

OptionLayout and scenario publication
Text::Hint(key)Wrapping 12 pt hint; rect and handle dialog:hint:KEY
Text::Note(key), Text::MonoNote(key)Wrapping small dim text (optional monospace) with vertical padding; rect dialog:note:KEY
Text::Error(key)Wrapping full-width error band with tinted background and padding; retains dialog:report:KEY
Text::Report(key, error)Native wrapping label, optional error colour; rect/handle dialog:report:KEY
Text::Heading(key)Semibold body heading; retains dialog:report:KEY
Text::NameCell(strong)Medium/semibold clipped clickable name; retains dialog:cell:TEXT
Text::MonoCell, Text::Value(key, error)Monospace unit/value cells; clipped to one row with full tooltips. Value/error cells retain dialog:report:KEY.
Text::InlineNative truncated label; rect/handle dialog:inline:TEXT
Text::CellFixed-height clipped clickable cell with tooltip; rect/handle dialog:cell:TEXT

Examples:

#![allow(unused)]
fn main() {
segmented(ui, &mut mode, &["One", "Two"], Segments::default());
selection_field(ui, "Select", Selection { active: true, filled: false });
value_pair(ui, "Position", [&mut x, &mut y], ["X", "Y"], Some(["Distance X", "Distance Y"]));
button(ui, "Add", Button::Enabled(valid));
footer(ui, "OK", "Cancel", valid, Footer { apply: Some("Apply"), ..Default::default() });
text(ui, report, Text::Report("validation", invalid));
}

Icon surfaces that explicitly choose a palette ramp use icons::paint_accented. None selects base blue, while unknown names retain the icon’s semantic accent. Only active roles change: ink/line outlines, passive faces, guides and knockouts keep normal artwork colours. Existing icon painters retain their semantic-accent behaviour. Danger menu items and danger confirmation glyphs both use icons::paint_danger. It additionally maps passive-1/2/3 faces to constraint active-1/2/3, retaining the shading steps, ink outlines, handle and transparent slat details. The cache distinguishes this whole-glyph treatment from ordinary accent selection and monochrome ink masks. Radial icons follow the active tab (Sketch, Surface, Mesh, Form; other tabs use base blue) in both normal and hovered sectors; the hovered background retains its tab tint.

Extending and checking the kit

Add one implementation, document its tokens/options here, re-export it through mod.rs, and add one table entry in gallery::ELEMENTS. Keep implementation-only helpers private. Add interaction tests when the element introduces an action.

The gallery checks geometry, label alignment, handles and effective paint bounds in both themes. Field/footer/panel tests retain focus, completion and action coverage. ui_kit_guard_tests scans every UI feature module recursively, rejects raw control constructors and methods (including aliases), and rejects removed legacy helpers. The raw-control allowance table is empty and guarded against additions. Standard colour/font guards, Clippy, wasm, i18n, assets, icons and layers still apply.

See remaining coverage boundaries for specialised surfaces that remain outside the kit and why.

Panel and Window use 28 pt controls, a 40 pt title band (14 pt semibold title, 19 pt command icon and 26 pt close/fold targets), and a 48 pt footer with 28 pt actions. Table rows are control_height + space_8 (36 pt), independent of control height. Bodies use 12 pt insets. Width limits and scroll boundaries remain owned by Panel/Window/Confirm. Titles retain the supplied case.

The form_ink, form_dim, form_primary, form_error and form_error_fill colour keys match the approved light cards. Dark values follow existing text, accent and error palette roles; every role is exported by theme files and measured by the colour-vision checks. Invalid fields include both error text and a tinted surface. Long variable-height lists can use Window::placed with a top-left pivot to keep their painted controls and scenario targets aligned as they grow. Completion lists use the approved 230 pt minimum width (derived from card and spacing tokens), show kind labels on the right, selected details beneath the list, and the existing keyboard hint.

Parameters and Configuration Table use this one table grid, including Favorites and each model group. The grid has space_8 horizontal insets within the row highlight, shared by headers and cells, plus space_8 column gaps. Flexible widths are measured inside the scroll viewport and rounded by cumulative column edges; fitting content does not acquire a horizontal scrollbar from independent rounding or inherited scroll margins. Explicit minimum widths still allow genuine overflow. The viewport and measured content width publish dialog:table:TABLE:viewport and dialog:table:TABLE:content_width for geometry checks. Hover uses the clipped row rectangle, including interactive children, while preserving child click ownership. Their add prompts publish dialog:table:TABLE:add, use control_height - space_4 height, a dashed control_radius outline and a pointing-hand cursor. Clicking creates a regular unsaved row, focuses its name and exposes the existing field controls and bin. Enter or valid focus loss commits through the existing commands; abandoning a nameless row drops it. Configuration Enter with an empty name retains ConfigurationN naming and copies the active row. Parameter validity is published as param:new:valid; invalid expressions cannot commit on focus loss. The natural scrolling body height is published as dialog:body:content_height, so geometry tests distinguish content size from the viewport cap. After a successful parameter add, a fresh empty draft retains Name focus for continuous keyboard entry; leaving that empty row drops it. Parameter draft fields retain param:new:name/unit/expression/comment. Configuration drafts publish config:new:name; existing config:cell:ROW:COLUMN, activation, rule and preview handles remain. The header plus retains dialog:menu:Add Column, including the existing menu nesting. The old separate Add forms and column-removal band have been removed.

Crash Reports uses report_list (bordered bounded scroll), report_header (kind, count pill and mono UTC time), path_chip (clipped mono path and adjacent action), empty_box (dashed empty state), and icon_label_button (secondary icon/text, optional danger ink). These use dialog_rule/footer, control_*, form_*, font_label/small and spacing tokens. The report header retains the combined-title cell alias; bins retain the previous Delete handle. Report actions remain immediate. list_frame supplies the same border to virtualised library rows. The list frame owns its border and horizontal padding; the report viewport adds no inherited scroll margin. Report list measurements publish dialog:list:KEY:viewport and dialog:list:KEY:content_width. Reports use vertical scrolling; the horizontal rule beneath each report remains a divider. Appearance uses a 340 pt card derived from card/label/spacing tokens, with the 104 pt label basis where space permits, 10 pt body gaps, and a count on In This Design.

Colour popovers retain dialog:color:ROW and add color:format:U8/F, color:copy, color:channel:R/G/B, color:sv, color:hue, color:current and the color:popover rectangle. Format changes update egui’s global numeric colour presentation without changing RGB. Linear fields edit linear RGB, not normalised gamma bytes. HSV state preserves hue at zero saturation/value and sub-byte precision during drags. Gradient colours are document colour data; all interface chrome uses existing paired light/dark tokens.

Sliders only via ui_kit::slider, everywhere in crates/ui-egui/src and apps. The element owns its form styling even when called from a viewport menu. Slider options select behaviour, never appearance: range and values remain in caller units, step preserves stepping, format preserves suffixes/display mapping, integer rounds manipulation and show_value: false hides the editor. Disabled sliders use ui.add_enabled_ui; tooltips attach to the returned response. Preferences UI scale still applies only after the pointer is released. There are no alternative slider helpers, raw egui::Slider callers, or custom track/thumb painters outside the kit. The source guard scans both trees without an allowance table; the gallery covers continuous, percent/stepped, integer/no-editor and disabled states in both themes. Numeric fields use the existing kit scalar_field, length_field, angle_field or number_field rather than constructing a raw egui::DragValue at a caller.

Parameters retains its bounded header drag offset while the manager is open, including frames without a regular feature dialog. The shared Panel bound reserves header and footer space at the bottom edge. Import and both export actions use icon_label_button with the existing import/export arrow icons and unchanged command routing and scenario identities. The gallery includes all three actions. The Add Column target is form_close_size - space_4 (22 pt) square: idle paint is only form_dim ink; hover adds the selected accent fill and insertion guides. HSV gradients use a rounded mesh silhouette sampled along control_radius arcs; the current-colour strip uses the same rounded radius. Render tests sample all four corner pixels and opaque interiors of all three surfaces at 1× and 2× in both themes.

Checkboxes only via ui_kit::checkbox, everywhere in crates/ui-egui/src and apps. checkbox_row, checkbox_grid and menu checkbox rows share its single box painter and paint_check_glyph path. The box is space_14 square with radius_sm corners, a control_border outline and control_fill background. Checked/mixed boxes use form_primary and on_accent; hover uses hover and an accent border, focus adds a space_2 outset ring, and disabled controls inherit egui’s disabled opacity. Geometry never depends on caller font or native icon size. checkbox_indeterminate represents a mixed selection with a bar and activates to checked. Space/Enter, accessible checked state, tooltips and scenario handles are retained. Exclusive choices use existing kit segments/dropdowns/list pickers; there are no radio controls. The recursive guard rejects native checkbox/radio constructors (including aliases), raw toggle methods and duplicate check glyph painters across both trees without allowances. Offscreen checks compare actual box and tick geometry/pixels across form, grid, palette and menu call sites at 1×/2× in both themes.

Disclosure chevrons point down when open (content visible) and right when closed (content hidden). Only ui_kit::disclosure draws them, using the original chevron / chevron_right SVGs at form_section_icon_size, without animation (as in kit section headers). Sections, nested groups, Browser trees, card headers, window folds and the Assistant strip all follow this rule. Keep existing scenario handles, including dialog:collapse, and translated Collapse/Expand tooltips. Dropdown arrows always point down; submenu arrows point right. Playback, reorder, window management and overflow controls are not disclosures. The source guard scans UI, apps and compiled asset glyphs without caller allowances; render tests inspect the actual icon textures used by sections, card folds and window folds.

Per-tab accent tokens

TabAccent::for_tab supplies three independent theme colours: color (fill) for pills, active-tool borders and the history marker; ink for small text and selected rows; soft for tinted selection/tool backgrounds. These are palette entries, not blends. Browser rows and selected ribbon labels use ink directly.

Tab idFill tokenInk tokenSoft token
MODEL / defaultaccentselected_inkaccent_soft
SKETCHsketch_accentsketch_inksketch_soft
SURFACEsurface_fillsurface_inksurface_soft
MESHmesh_fillmesh_inkmesh_soft
FORMform_fillform_tab_inkform_soft
SHEET METALsheet_fillsheet_inksheet_soft
RENDERrender_fillrender_inkrender_soft

form_tab_ink is separate from the existing form_ink used for form controls. Sketch reuses its existing fill in both themes. Surface and Form fills match their existing accents in light mode but need independent dark values; Mesh and Render fills also differ from their existing mesh_accent and render_accent values. All existing tokens, including Model’s fill, ink and tint, retain their values. The Model/default mapping preserves its existing appearance rather than adopting the proposed Model colours from the board.

The new palette entries (fill / ink / soft) are:

TabLightDark
Sketch#057F45 / #057F45 / #EDF7F1#128D45 / #46BE78 / #1F3B2B
Surface#A45000 / #A45000 / #FBEBD8#B26A14 / #E0914A / #45331E
Mesh#E0B200 / #8A6D00 / #FFF8DC#C6A800 / #D4B726 / #41391A
Form#7349CD / #7349CD / #EEE8FA#8A6FCC / #B39BEE / #372F4F
Sheet Metal#1E6F96 / #1E6F96 / #E1F0F7#2A7FA8 / #4FB3D9 / #1F3A47
Render#A8326E / #A8326E / #F8E6EF#C8588F / #E074AE / #402336

Each key is supported by solidcraft-theme/1 import/export, including tab_on_fill_dark (#000000 in both themes). TabAccent::on_fill uses on_accent (white in the built-in palettes) when it reaches 4.5:1, otherwise that dark ink. The fallback is black because the board’s dark ink does not reach 4.5:1 on several of the approved fills. It only styles tab pills; global primary actions still use on_accent. Built-in palette tests require WCAG contrast of at least 4.5:1 for ink on the card and panel, ink on soft, and pill text on fill in both themes. Only Mesh separates ink from fill in the light theme.

Context menu adapters retain item IDs, enabled logic and command parameters. Menu owns popup elevation (10 pt corners, 1 pt border), headings, separators, adjacent submenus and bounded arrow navigation. Enter opens a submenu or chooses its row; Right opens, Left returns, and Esc dismisses the menu. Appearance includes document swatches and Remove override through appearance.assign; its original action still opens the full editor. Workspace retains its descriptive cards and scenario handles on the shared surface. Ribbon rows reuse MenuItem::paint while owning drag, focus and pin interactions. History context menus and ribbon overflow use kit popup chrome. Rename uses the shared caption, field policy and Cancel/OK actions with its stable editor ID. No additional translation keys or colour roles are needed.

The menu gallery includes normal, hovered, open, disabled, checked/unchecked, swatch and danger rows, a submenu and rename. Radial geometry tests measure the actual side endpoints at both radii and sample all four rounded corners. Optional CPU-rendered menu evidence can be produced without a window with SOLIDCRAFT_MENU_EVIDENCE=/tmp/menus cargo test -p solidcraft-ui-egui --lib menu_board_in_both_themes. Desktop interaction and visual acceptance still require a reviewer capture.

Command search uses the ribbon field itself (sc_sbox_text); ribbon:search, shortcut:search and palette:search all identify that same field. Opening gives it the accent focus ring, cursor and Esc keycap. The field and the attached results sheet unfold in 120 ms with egui cubic ease-out, capped by egui’s animation time so instant settings remain instant. Esc, click away and command dispatch reverse the animation. Only the visible sheet height participates in layout and hit testing; result content is clipped while unfolding. The sheet’s ribbon:search_popup / palette:outer handles exclude the field. Its footer (ribbon:search_footer) fills the bottom strip edge to edge, with rounded bottom corners and a 1 pt top rule. Ranking, command mode and keyboard routing remain with the caller.

Generate CPU-rendered evidence of all five search states and the actual ribbon’s closed/opening/open/closing transition in both themes with SOLIDCRAFT_SEARCH_EVIDENCE=/tmp/search cargo test -p solidcraft-ui-egui --lib command_search.

Compact workspace composition

Container::gap and Container::frame supply the layout policy for every adaptive surface. Compact has no gaps or shadows between edge-bound panels; floating uses the shared card spacing and frame. Panel bounds draggable compact sheets to the workspace. The shared frame rounds all corners away from the right dock and only the left corners while right-docked; the footer inherits that frame’s bottom corners. Both layouts retain drag offsets. Browser uses the same content renderer and width in both modes, with a square, flush ribbon join in Compact. Non-workspace surfaces keep their roles. DockEdges declares touching edges. The container measures content-hugging cards before rounding their exposed corners, and aligns touching outlines onto a single border strip without changing logical panel allocations. The application bar has an independent surface role. The saved Prefs::layout is changed through view.layout; ui.set may still make a transient automation override.

File/start presentation

FileView owns File surfaces and controls. It accepts translated strings, existing icon ids and cached thumbnail textures; callers retain commands, document state, persistence, thumbnail jobs and recovery decisions.

  • page, content and rail own the page background, responsive insets and full-height rail fill/clip. navigation also supports the narrow navigation menu. Callers supply layout rectangles, scrolling and entry order.
  • nav_row accepts a stable id, label, shortcut, selection and enabled state. back_to accepts a translated {name} template, paints the document name semibold and reuses the mirrored chevron and outlined keycap. separator supplies grouped rules. Disabled rows cannot activate.
  • title, subtitle, detail and section own typography. section accepts a token-based heading/body gap: space_12 for New, space_10 for Recent/Samples. Callers retain semantic section order and the larger file_section_gap.
  • grid provides responsive columns with 14 pt gutters and no trailing row gap. action_tile supplies an 84 pt card with 44 pt original artwork and vertically centered label/subtitle. tile_min_width informs responsive columns.
  • recent_list owns the rounded border and zero row gap. recent_row accepts a RecentRow, paints internal separators, medium names and workspace dots, and retains title/path at narrow widths. Callers handle unavailable-file actions.
  • sample_grid(ui, id, &[SampleCard]) measures each row’s tallest content and stretches cards to equal bottoms without clipping descriptions. Responses stay in sample order, with stable {id}:{index} ids. Artwork inputs remain unchanged.
  • recovery_banner accepts RecoveryMessage segments for a bold title, normal message, monospace filename and normal suffix. Actions align right or wrap below at narrow widths. Discard is optional for summaries; the return value is (recover_response, optional_discard_response). recovery_review uses the same geometry with per-design Discard. Callers provide truthful metadata.

The exported file_* colour tokens provide readable light/dark foreground and background pairs, including File-specific secondary text. Thumbnails retain the neutral file_image_tint; icon and text colours do not change their pixels. Spacing, dimensions, fonts and radii come from theme::Tokens.

Gallery and geometry tests cover these components. To capture the actual File view using public temporary fixtures, set SOLIDCRAFT_FILE_EVIDENCE to an output directory and run file_view_application_evidence_in_both_themes_and_widths with --ignored. It produces desktop and 420 pt narrow PNGs in both themes.

button_at(ui, id, rect, label, Button::Primary/Normal) places the same shared native button used by button inside a bounded, centred layout. It preserves stable identity, enabled state and exact caller-owned geometry. Recovery actions use this component rather than custom button painters. The recovery well renders the existing restore artwork through its construction accent ramp.