# web-terminal Design System Single source of truth for every visual decision in this project. No component is written without reading this file first. Every color, space, size, duration, and radius below is a named token. Raw values are forbidden outside this document. --- ## 0. Research Log One line per research lane. A lane with no line did not run. - **Embedded refs (Layer A + Layer B)**: shortlisted `warp.md` (modern terminal, block-based command UI), `voltagent.md` (void-black, terminal-native, emerald), and `raycast.md` (dark macOS-native chrome). Picked **Layer A `taste-skill.md`** + **Layer B `raycast.md`**. - **Why `taste-skill.md` over `minimalist-skill.md`**: `minimalist-skill.md` is hard-locked to a warm monochrome *light* canvas (`#FFFFFF` / `#F7F6F3`), editorial serif display type, bento grids, and macro-whitespace. This product is a dark, full-viewport, single-surface tool with zero marketing copy and near-zero whitespace budget; adopting it would have required overriding its palette, its type architecture, and its layout thesis at once. `taste-skill.md` supplies exactly what this product needs and stays aesthetic-neutral where it must: the Dark Mode Protocol (Section 8: no pure `#000000`, no pure `#ffffff`, token strategy, hierarchy parity), the performance and a11y guardrails (Section 6: transform/opacity only, mandatory `prefers-reduced-motion`, z-index restraint, `100dvh` over `100vh`), the full interactive-state cycle requirement (Section 4.5: loading, empty, error, focus, tactile feedback), and the AI-tell bans that keep this from reading as generic hacker slop. Dials set below. - **Why `raycast.md` over `linear.app.md`**: three direct hits. (1) Its canvas is a near-black *blue-tinted* `#07080a` rather than pure black, which is precisely the "premium dark, not hacker-green" register requested, and it keeps the terminal's own ANSI colors as the only saturated thing on screen. (2) It documents a **keyboard key-cap component** (gradient cap, layered inset shadow, 4-6px radius) - this project's mobile touch key toolbar is the single highest-risk surface, and Raycast gives it a proven anatomy instead of an invention. (3) Its depth system is macOS-native ring + inset rather than blurry drop shadows, which is cheap to render and reads correctly on a surface that sits behind a WASM canvas. `linear.app.md` is excellent but its purple accent and marketing-page grammar carry less into a docked-panel tool shell. - **Adopted from Raycast**: blue-tinted near-black canvas, `rgba(255,255,255,0.06)` hairline containment borders, ring + inset depth over drop shadows, key-cap anatomy, opacity-based hover transitions, weight 500 as the UI body baseline. - **Deliberately rejected from Raycast**: positive letter-spacing (+0.2px) is a marketing-page readability trick and actively harms dense terminal-adjacent UI at 12-13px, so tracking is neutral-to-negative here; the 86px pill CTA (wrong register for a tool); Inter as the shipped webfont (violates this project's low-bandwidth ethos - system stack instead); Raycast Red as brand punctuation (reassigned to error-only, because a terminal already owns red semantically via ANSI). - **StyleGallery (`github.com/changeroa/StyleGallery`, user-mandated)**: fetched and read via `raw.githubusercontent.com` - `layout/index.md`, `motion/index.md`, `motion/vocabulary.md`, `patterns/viewport-shell/index.md`, `patterns/viewport-shell/scroll-body-shell.md`, `patterns/viewport-shell/panel-layout.md`, `patterns/split-sidebar/main-with-rail.md`, `patterns/overlay-exception/overlay-stack.md`, `patterns/overlay-exception/imposter.md`, `patterns/in-line-grouping/reel.md`, `patterns/in-line-grouping/tab-strip.md`, `patterns/containment/index.md`, `recipes/command-surface.md`, `quality/gates/layout.md`, and the `governed-local/terminal` button state + keyboard matrices. Patterns adopted and the constraints they impose are binding rules in **Section 4.4 (StyleGallery Layout Contract)** and **Section 6.3 (StyleGallery Motion Boundary)**. Headline constraints taken: named scroll ownership per region (Layout Principle 7), one primary spatial problem per primitive (Principle 3), no decorative or animated properties in reusable layout CSS (Principle 8 + the Motion domain's explicit scope boundary), `min-block-size: 0` / `min-inline-size: 0` on every grid child that owns scroll or must shrink, and the `command-surface` recipe's rule that fixed command regions live *outside* the body scroll container. - **Herdr live runtime (`herdr.dev`, user-mandated visual reference)**: captured in real Chromium at 1440px and inspected with `getComputedStyle`. Adopted: zero-radius structural cells, one-pixel neutral borders, shadowless chrome, compact mono metadata, explicit state dots, near-black layering, and accent-only hover/focus borders. Deliberately retained from this product instead of copying Herdr: the existing blue accent, the Section 2 accessible ANSI palette, 44px mobile targets, sans-serif navigation labels, fixed 320px desktop rail, and modest rounding only where a control must remain distinct from a structural cell. - **Lazyweb lane**: **skipped**. Reason: task-mandated network-cost skip. `lazyweb.md` is a curl-heavy real-product screen-research lane; the visual direction was already pinned by an explicit brand reference (Raycast-grade dark craft) plus a hard low-bandwidth ethos, so the marginal direction gained does not justify the fetch budget. - **Imagen drafts lane**: **skipped**. Reason: task-mandated network-cost skip, and the deliverable is a written contract with no component code. There is no reference-fidelity image to diff against; the ANSI palette and the ghostty theme object are the fidelity contract instead. - **ui-ux-db sanity check**: **skipped**. Reason: no network/CLI budget in this task scope. Palette and type pairing are instead sanity-checked mechanically against the WCAG floors recorded in Section 8.1, computed from the token values in Section 2. **Dials** (per `taste-skill.md` Section 1, reasoned not defaulted): `DESIGN_VARIANCE: 3` - this is a tool shell where the terminal owns the viewport; asymmetry would be noise, and the one structural asymmetry (right-docked panel) is functional. `MOTION_INTENSITY: 3` - motion exists only to explain state changes (drawer travel, toast arrival, latency shift); a terminal that animates is a terminal that feels slow. `VISUAL_DENSITY: 7` - cockpit register: hairlines instead of card boxes, mono for all numbers, tight paddings. --- ## 1. Atmosphere & Identity web-terminal feels like the inside of a precision instrument that someone left running. The chrome is deliberately almost invisible: a blue-tinted near-black shell that recedes so completely that the only saturated color on screen is the terminal's own output. You are not looking at an app that contains a terminal; you are looking at a terminal that happens to have a hairline of chrome around it. Everything else - the top bar, the sidebar, the key toolbar - is rendered as a thin structural membrane that reports state and then gets out of the way. **The signature is the hairline membrane.** Surfaces separate through 1px `rgba(255,255,255,0.06)` strokes and a single tonal step of background, never through shadows, never through cards, never through color. The result is a shell that reads as machined rather than designed: layers you notice only when you look for the seam. The one place this membrane thickens into something physical is the mobile key toolbar, where keys get a genuine cap treatment - a gradient face and an inset top highlight - because those are the only elements on the entire surface that a finger actually strikes. Chrome recedes; the things you press come forward. That is the whole visual thesis. --- ## 2. Color This product is **dark-only**. There is no light theme. Per `taste-skill.md` Section 4.11 (Page Theme Lock), the theme is locked at the root and no surface inverts. The Light column below is intentionally `n/a`: shipping a half-considered light theme for a terminal whose ANSI palette is tuned for a dark background would break contrast parity on all 16 ANSI colors at once. Recorded as accepted debt in Section 8.2. ### 2.1 Palette | Role | Token | Light | Dark | Usage | |---|---|---|---|---| | Surface / canvas | `--surface-canvas` | n/a | `#07080A` | App root, terminal backdrop, login page | | Surface / primary | `--surface-primary` | n/a | `#0D0F11` | Top bar, sidebar body, key toolbar base | | Surface / secondary | `--surface-secondary` | n/a | `#131518` | List rows at rest, tab strip trough, input fields | | Surface / elevated | `--surface-elevated` | n/a` | `#17191C` | Dialogs, drawer panel, toasts, popovers | | Surface / raised | `--surface-raised` | n/a | `#1B1E21` | Key caps, badges, chips, hovered list rows | | Surface / sunken | `--surface-sunken` | n/a | `#050607` | Terminal viewport, editor textarea well | | Surface / scrim | `--surface-scrim` | n/a | `rgba(5, 6, 7, 0.64)` | Drawer + dialog backdrop | | Text / primary | `--text-primary` | n/a | `#F2F3F5` | Headings, list labels, input text | | Text / secondary | `--text-secondary` | n/a | `#A8ADB4` | Tab labels, metadata, helper text | | Text / tertiary | `--text-tertiary` | n/a | `#80858D` | Placeholders, timestamps, disabled labels | | Text / inverse | `--text-inverse` | n/a | `#0D0F11` | Text on `--accent-primary` fills | | Border / default | `--border-default` | n/a | `rgba(255, 255, 255, 0.10)` | Inputs, key caps, dialog edges | | Border / subtle | `--border-subtle` | n/a | `rgba(255, 255, 255, 0.06)` | Region seams, list dividers, top-bar underline | | Border / strong | `--border-strong` | n/a | `rgba(255, 255, 255, 0.18)` | Hovered inputs, active tab underline | | Accent / primary | `--accent-primary` | n/a | `#5AB2FF` | Focus ring, active tab, primary button, links | | Accent / hover | `--accent-hover` | n/a | `#82C6FF` | Accent hover state | | Accent / muted | `--accent-muted` | n/a | `rgba(90, 178, 255, 0.14)` | Selected row wash, focus halo | | Status / success | `--status-success` | n/a | `#4ADE80` | Connected dot, save confirmation | | Status / warning | `--status-warning` | n/a | `#FBBF24` | Reconnecting dot, dirty indicator, rate-limit notice | | Status / error | `--status-error` | n/a | `#FF6B6B` | Offline dot, login error, destructive actions | | Status / info | `--status-info` | n/a | `#5AB2FF` | Informational toasts | | Status / success wash | `--status-success-wash` | n/a | `rgba(74, 222, 128, 0.12)` | Success toast fill | | Status / warning wash | `--status-warning-wash` | n/a | `rgba(251, 191, 36, 0.12)` | Warning toast fill, dirty chip | | Status / error wash | `--status-error-wash` | n/a | `rgba(255, 107, 107, 0.12)` | Error toast fill, login error field | | Status / neutral | `--status-neutral` | n/a | `#80858D` | Idle/unknown session dot | ### 2.2 Terminal ANSI Palette The 16 ANSI colors are their own token family. They are **not** interchangeable with the semantic tokens above, and semantic UI must never reference an ANSI token (or vice versa) - the user's shell owns these, and remapping them across surfaces would make `ls` output and a status dot lie about each other. Tuned for `--term-bg` at a 4.5:1 floor on the normal set and 7:1 on the bright set. | Role | Token | Value | Role | Token | Value | |---|---|---|---|---|---| | ANSI 0 black | `--ansi-black` | `#15181B` | ANSI 8 bright black | `--ansi-bright-black` | `#787F87` | | ANSI 1 red | `--ansi-red` | `#F4736F` | ANSI 9 bright red | `--ansi-bright-red` | `#FF9490` | | ANSI 2 green | `--ansi-green` | `#5FD68A` | ANSI 10 bright green | `--ansi-bright-green` | `#88E9AA` | | ANSI 3 yellow | `--ansi-yellow` | `#E7B455` | ANSI 11 bright yellow | `--ansi-bright-yellow` | `#F7CE7A` | | ANSI 4 blue | `--ansi-blue` | `#6AABF0` | ANSI 12 bright blue | `--ansi-bright-blue` | `#93C6F7` | | ANSI 5 magenta | `--ansi-magenta` | `#C79AF0` | ANSI 13 bright magenta | `--ansi-bright-magenta` | `#DCBBF8` | | ANSI 6 cyan | `--ansi-cyan` | `#5FC9D6` | ANSI 14 bright cyan | `--ansi-bright-cyan` | `#8CDEE8` | | ANSI 7 white | `--ansi-white` | `#C3C8CE` | ANSI 15 bright white | `--ansi-bright-white` | `#F2F3F5` | Terminal-specific surface tokens: | Role | Token | Value | Usage | |---|---|---|---| | Terminal background | `--term-bg` | `#050607` | ghostty-web canvas background (= `--surface-sunken`) | | Terminal foreground | `--term-fg` | `#D6DAE0` | Default glyph color | | Terminal cursor | `--term-cursor` | `#5AB2FF` | Block/bar cursor (= `--accent-primary`) | | Terminal cursor accent | `--term-cursor-accent` | `#050607` | Glyph color under a block cursor | | Terminal selection bg | `--term-selection-bg` | `rgba(90, 178, 255, 0.30)` | Selection fill | | Terminal selection fg | `--term-selection-fg` | `#F2F3F5` | Selected glyph color | | Terminal selection inactive | `--term-selection-inactive` | `rgba(255, 255, 255, 0.12)` | Selection when terminal is unfocused | | Terminal scrollbar | `--term-scrollbar` | `rgba(255, 255, 255, 0.14)` | Scrollbar slider | ### 2.3 ghostty-web / xterm `theme` object Exact object to pass as the terminal's `theme` option. Key names match the xterm.js `ITheme` interface, which ghostty-web's compatibility layer consumes. This is the fidelity contract: the app must derive these strings from the tokens above at build time, never hand-maintain a second copy. ```json { "background": "#050607", "foreground": "#D6DAE0", "cursor": "#5AB2FF", "cursorAccent": "#050607", "selectionBackground": "rgba(90, 178, 255, 0.30)", "selectionForeground": "#F2F3F5", "selectionInactiveBackground": "rgba(255, 255, 255, 0.12)", "scrollbarSliderBackground": "rgba(255, 255, 255, 0.14)", "scrollbarSliderHoverBackground": "rgba(255, 255, 255, 0.22)", "scrollbarSliderActiveBackground": "rgba(255, 255, 255, 0.30)", "black": "#15181B", "red": "#F4736F", "green": "#5FD68A", "yellow": "#E7B455", "blue": "#6AABF0", "magenta": "#C79AF0", "cyan": "#5FC9D6", "white": "#C3C8CE", "brightBlack": "#787F87", "brightRed": "#FF9490", "brightGreen": "#88E9AA", "brightYellow": "#F7CE7A", "brightBlue": "#93C6F7", "brightMagenta": "#DCBBF8", "brightCyan": "#8CDEE8", "brightWhite": "#F2F3F5" } ``` ### 2.4 Rules - Surface hierarchy is built from tonal steps plus hairline borders. No shadows for structure (see Section 7). - `--accent-primary` is used **only** for interactive affordance: focus, active tab, primary action, cursor. Never decorative, never a background wash on a large region. - One accent for the whole app (`taste-skill.md` Color Consistency Lock). There is no second accent hue. - Status colors are used **only** where they encode real machine state - connection status, save state, errors. Per `taste-skill.md` Section 9.F, decorative status dots are banned; the session dot, the herdr agent dot, and the herdr tab connection dot survive that ban because they report actual server state, and they are the only dots in the app. The herdr tab dot is fed by a background store that polls `/api/herdr/snapshot` from app start (5s while the panel is visible, 30s while hidden, paused while the document is hidden, backing off 5s to 60s while herdr is unavailable), so herdr connection state is live without the user opening the panel. - ANSI tokens are consumed exclusively by the terminal theme object and the session-picker's per-session color echo. They never style chrome. - Never introduce a color absent from these tables. Extend the table first. --- ## 3. Typography ### 3.1 Font Stacks Two families, zero webfonts. The low-bandwidth ethos is a hard constraint: shipping a webfont for a terminal that renders its own glyphs to a canvas is unjustifiable network cost, and it introduces a FOUT on exactly the surface that must feel instant. | Token | Value | |---|---| | `--font-sans` | `ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", "Pretendard", "Apple SD Gothic Neo", "Malgun Gothic", Roboto, sans-serif` | | `--font-mono` | `ui-monospace, "SF Mono", SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "D2Coding", "Roboto Mono", monospace` | Notes: - Korean faces are named explicitly in both stacks so that IME composition text and Hangul filenames render in the intended face rather than a fallback with a different metric box. See Section 8.1 for the IME rule. - The terminal canvas uses `--font-mono` so the ghostty-web cell metrics match the file-editor textarea; a user editing a file sees the same glyph shapes as the shell that printed it. - Webfont budget: **zero.** If a future requirement forces one, it is one file, `woff2`, `font-display: swap`, self-hosted, and it is recorded as debt in Section 8.2. ### 3.2 Scale Tool-shell scale. There is no display tier: nothing in this app is a hero. The largest type in the product is the login heading. | Level | Token | Size | Weight | Line Height | Tracking | Usage | |---|---|---|---|---|---|---| | Title | `--text-title` | 20px / 1.25rem | 600 | 1.3 | -0.01em | Login heading, dialog title | | Heading | `--text-heading` | 16px / 1rem | 600 | 1.4 | -0.005em | Sidebar section headers, session-picker group labels | | Body | `--text-body` | 14px / 0.875rem | 400 | 1.5 | 0 | List rows, file names, dialog body, input text | | Body strong | `--text-body-strong` | 14px / 0.875rem | 500 | 1.5 | 0 | Active list row, button labels, session name | | Caption | `--text-caption` | 13px / 0.8125rem | 500 | 1.4 | 0 | Tab labels, toast body, helper text | | Micro | `--text-micro` | 12px / 0.75rem | 500 | 1.35 | 0.01em | Latency chip, timestamps, file sizes, key-cap labels | | Mono body | `--text-mono-body` | 14px / 0.875rem | 400 | 1.5 | 0 | File-editor textarea, path strings | | Mono micro | `--text-mono-micro` | 12px / 0.75rem | 500 | 1.4 | 0 | Latency value, byte counts, session IDs | | Terminal | `--text-terminal` | 14px / 0.875rem | 400 | 1.2 | 0 | ghostty-web cell size, desktop default | | Terminal mobile | `--text-terminal-mobile` | 13px / 0.8125rem | 400 | 1.2 | 0 | ghostty-web cell size below `--bp-md` | Weight tokens: `--weight-regular: 400`, `--weight-medium: 500`, `--weight-semibold: 600`. Nothing heavier ships. ### 3.3 Rules - Two families maximum. No third family, no serif, no display face. - Smallest shipped size is 12px (`--text-micro`), reserved for metadata that is never the only carrier of meaning. Body floor is 14px. - Per `taste-skill.md` VISUAL_DENSITY 7: **every number renders in `--font-mono`.** Latency values, byte counts, line/column indicators, session IDs, timestamps. A latency chip that reflows its width as the number changes from `9ms` to `14ms` is a bug; mono digits prevent it. - Tracking is neutral or slightly negative. This deliberately departs from the Raycast reference's +0.2px: positive tracking on 12-13px dense UI next to a monospace canvas makes the chrome look loose against the terminal grid. - `font-variant-numeric: tabular-nums` on every mono numeric readout. - Truncation: file names and session names truncate with `text-overflow: ellipsis` and carry the full string in `title`. Never wrap a list row to a second line. --- ## 4. Spacing & Layout ### 4.1 Base Unit All spacing derives from a base of **4px**. | Token | Value | Usage | |---|---|---| | `--space-0` | 0 | Reset | | `--space-1` | 4px | Icon-to-label, key-cap inner | | `--space-2` | 8px | List row inner vertical, chip padding, key gap | | `--space-3` | 12px | Input padding, tab padding, toolbar edge | | `--space-4` | 16px | Sidebar padding, dialog inner, top-bar horizontal | | `--space-5` | 20px | Dialog header/footer padding | | `--space-6` | 24px | Login card inner, section gap in sidebar | | `--space-8` | 32px | Empty-state vertical breathing | | `--space-10` | 40px | Login card vertical rhythm | | `--space-12` | 48px | Maximum separation (login page top offset) | The scale stops at `--space-12`. A tool shell that needs 96px of separation has a layout problem, not a spacing problem. ### 4.2 Size Tokens Every fixed dimension in the app is named here. Nothing is a literal. | Token | Value | Usage | |---|---|---| | `--size-topbar` | 44px | Top bar height | | `--size-sidebar` | 320px | Docked sidebar width at `>= --bp-md` | | `--size-drawer-max` | 88vw | Drawer max width below `--bp-md` (caps at `--size-sidebar`) | | `--size-keybar` | 48px | Mobile key toolbar height | | `--size-tap` | 44px | Minimum touch target, both axes | | `--size-tap-lg` | 48px | Comfortable touch target (key caps) | | `--size-row` | 40px | List row height (desktop) | | `--size-row-touch` | 44px | List row height below `--bp-md` | | `--size-control` | 36px | Button / input height (desktop) | | `--size-control-touch` | 44px | Button / input height below `--bp-md` | | `--size-dot` | 8px | Status dot diameter | | `--size-dialog-max` | 720px | Desktop dialog max width | | `--size-login-max` | 360px | Login card max width | | `--size-toast-max` | 400px | Toast max width | | `--size-icon` | 16px | Standard icon box | | `--size-focus-ring` | 2px | Focus ring width | | `--size-focus-offset` | 2px | Focus ring offset | | `--size-hairline` | 1px | Border width, all borders | ### 4.3 Radii, Z-Index, Safe Area Radii - one system, applied consistently (`taste-skill.md` Shape Consistency Lock). No pill shapes on anything except the status dot. | Token | Value | Usage | |---|---|---| | `--radius-none` | 0 | Shell seams, tabs, sidebars, rows, mobile full-screen dialogs | | `--radius-xs` | 2px | Key caps, compact icon controls, status chips | | `--radius-sm` | 4px | Buttons, inputs, toasts, login card | | `--radius-overlay` | 4px | Desktop dialogs and drawer inline-start corners | | `--radius-full` | 9999px | Status dot only | Z-index - a single documented scale (`taste-skill.md` Section 6.F). No arbitrary values anywhere in the codebase. | Token | Value | Layer | |---|---|---| | `--z-base` | 0 | Terminal canvas, app shell | | `--z-sticky` | 10 | Top bar, sidebar tab strip | | `--z-keybar` | 20 | Mobile key toolbar | | `--z-scrim` | 30 | Drawer / dialog backdrop | | `--z-drawer` | 40 | Slide-over drawer panel | | `--z-dialog` | 50 | File editor, session picker dialog | | `--z-toast` | 60 | Toast stack | | `--z-focus` | 70 | Focus-visible ring escape (never a container) | Safe area - the notch and home bar are handled with named tokens, never raw `env()` calls scattered through components. | Token | Value | |---|---| | `--safe-top` | `env(safe-area-inset-top, 0px)` | | `--safe-right` | `env(safe-area-inset-right, 0px)` | | `--safe-bottom` | `env(safe-area-inset-bottom, 0px)` | | `--safe-left` | `env(safe-area-inset-left, 0px)` | Application rules: the top bar adds `--safe-top` to its block-start padding (never its height token). The key toolbar adds `--safe-bottom` to its block-end padding. The drawer adds `--safe-right` to its inline-end padding and `--safe-top` / `--safe-bottom` to its block padding. The terminal region never applies safe-area padding directly - it is inset by its siblings, so the cursor line can never slide under the home bar. ### 4.4 StyleGallery Layout Contract (binding) These rules are adopted from StyleGallery and are auditable. A violation is a defect, not a preference. **Scroll ownership** (Layout Principle 7 - "if a pattern scrolls, the scrolling element must be obvious"). Every scrollable region in this app is named, and no region scrolls that is not on this list: | Region | Scroll owner | Axis | Notes | |---|---|---|---| | App shell | **none** | - | `overflow: hidden`, `block-size: 100dvh`. The document never scrolls. | | Terminal viewport | ghostty-web internal scrollback | block | Owned by the VT engine, not by CSS. The wrapper is `overflow: hidden`. | | Sidebar panel body | sidebar body element | block | Independent of the terminal. Tab strip and sidebar header sit **outside** this container. | | File editor textarea | textarea | block | The dialog itself never scrolls; header and footer are fixed rows of the dialog grid. | | Session picker list | list body | block | Dialog header/footer outside the scroll container. | | Mobile key toolbar | toolbar track | **inline only** | The `reel` pattern. Block-axis overflow is `hidden`. | | Toast stack | **none** | - | Capped at 3 visible; the 4th coalesces. A scrolling toast stack is a defect. | Consequences that are non-negotiable: the terminal and the sidebar never share a scroll context; opening the drawer must not scroll the terminal; scroll chaining out of the sidebar into the shell is blocked with `overscroll-behavior: contain`; the document body gets `overscroll-behavior: none` so mobile rubber-band cannot detach the fixed chrome. **Adopted patterns and what each one binds:** - `viewport-shell/scroll-body-shell` - the app root and both dialogs. Contract: `display: grid`, `grid-template-rows: auto minmax(0, 1fr) auto`, `max-block-size: 100dvh`, and the body child carries `min-block-size: 0; overflow: auto`. The `minmax(0, 1fr)` and `min-block-size: 0` are load-bearing: without them the terminal row refuses to shrink and pushes the key toolbar off-screen. `100dvh`, never `100vh` (also `taste-skill.md` Section 3.E). - `viewport-shell/panel-layout` - the desktop terminal + sidebar split. Contract: both children get `min-inline-size: 0` so a long file name cannot widen the panel and squeeze the terminal. Adapted from the reference's symmetric `auto-fit` to an explicit `grid-template-columns: minmax(0, 1fr) var(--size-sidebar)` because the sidebar width is a fixed product decision, not a fluid one; the reference's wrap point is replaced by the documented `--bp-md` breakpoint, which satisfies the pattern's "change sizing values only when the new wrap point is documented" clause. - `overlay-exception/overlay-stack` - the drawer + scrim, and the dialog + scrim. Contract: `display: grid` with both children in `grid-area: 1 / 1`. This is why the scrim and panel cannot desynchronize. - `overlay-exception/imposter` - the centered desktop dialog and the login card. - `in-line-grouping/reel` - the mobile key toolbar. Contract: `grid-auto-flow: column`, `overflow-x: auto`, explicit `grid-auto-columns`. Per the `command-surface` recipe, `reel` is used **only** because horizontal scanning is genuinely part of this interaction model (a key row cannot wrap without destroying muscle memory); everywhere else, command rows wrap. - `in-line-grouping/tab-strip` - the Files / herdr tabs. - `recipes/command-surface` - the overall shell grammar. Binding clauses taken verbatim in spirit: *"Keep fixed command regions outside the body scroll container"* (the key toolbar and top bar are grid rows of the shell, never children of the terminal region), and *"Do not let command text resize the shell"* (the latency chip and session name are `min-inline-size: 0` + truncating; a 4-digit latency value must not move the top bar's layout). **Containment and structure rules:** - One primary spatial problem per primitive (Principle 3). A primitive that both scrolls and pins is split, or explicitly labeled a shell/composite. - Semantic structure first (Principle 2): layout classes never replace landmarks. `
` for the top bar, `
` for the terminal region, `