CodeNomad/dev-docs/UI_CONVENTIONS.md
Pascal André a868a8e720
feat(missions): blue prepared missions, quiet interrupted chevron, full-panel create form (#1155)
## Changes

- **Prepared missions are blue.** A created, never-started mission gets
a blue mark (`--accent-primary`), like a prompt ready to send.
- **Interrupted no longer bubbles into the collapsed chevron.** Like a
collapsed session parent, the chevron only counts answers, running work
and unseen completions; interrupted, paused and prepared missions colour
only their own rows. The unused `missionsPanel.attention.interrupted.*`
keys are removed from all locales.
- **The creation form replaces the list and detail.** While creating,
the current-mission line, list, schedules and selected detail are hidden
(still mounted, so pending controls and retries survive) until Create or
Cancel.
- **Nesting depth in the creation form.** Options embed the existing
project depth editor (`MissionSubagentDepth`). OpenCode only reads
`experimental.subagent_depth` per config file, so the value is the
project's and applies to every conversation there; the editor says so.
- **Button templates.** `.window-text-button` had no rule anywhere,
leaving ~18 mission buttons unstyled. It is now a shared primitive in
`styles/components/window.css` (borderless accent text, hover tint,
muted when disabled). The creation footer's **Save as brief** uses the
footer's `button-secondary` template; the bordered **Answer** button
uses `.window-action`.

## Docs

`dev-docs/MISSIONS.md`, `dev-docs/UI_CONVENTIONS.md` (window button
primitives), and `docs/help/*/missions.md` in English plus the nine
translations.

## Validation

- UI typecheck; mission and i18n unit tests; `npm run test:help`.
- All 34 `tests/browser/mission*.test.ts` files pass. Updated:
`mission-status-marks` (blue computed colour, quieter chevron label),
`mission-editor-profiles` and `mission-editor-lifetime` (the form
replaces the list; leave it before browsing).
- Visual check of the real components through the mission-control
fixture: prepared mark, collapsed chevron, full-panel form with depth,
footer buttons, selected detail.
2026-10-27 14:51:10 +01:00

5.1 KiB

UI conventions

Shared renderer rules for dialogs, utility windows, icon toggles, surfaces and scrollbars.

Agent conventions

  • Authentication recovery uses lib/auth-recovery.ts and components/auth-recovery-dialog.tsx: confirm CodeNomad auth status before showing an expired-login form, reconnect in place to preserve drafts, and never replay failed mutations. Its shared window styles live in styles/components/auth-recovery.css.
  • Confirmation dialogs (components/alert-dialog.tsx) keep Kobalte's initial focus on Cancel, so Enter never confirms by reflex, while the confirm button stays the filled primary action. Alert-dialog buttons show the accent focus ring on any :focus (styles/components/alert-dialog.css), including the initial focus of a mouse-opened dialog, so the ring marks the button Enter acts on. Prompts focus their input, where Enter submits. Covered by tests/browser/alert-dialog.test.ts.
  • Persistent command/search utility windows use components/dismissible-window.tsx: non-modal, no scrim, outside interactions keep them open, and explicit toggles use .icon-toggle with aria-expanded/aria-controls. Keep search state scoped to its instance/session and close it when that view becomes inactive.
  • The .icon-toggle active state (aria-pressed/aria-expanded/data-active) is one shared rule in styles/utilities.css: one uniform partial accent tint for every palette (an ink of accent leaning 20% toward --text-on-accent, then 10% toward --surface-base, at 40% opacity, 50% on hover) over the surface beneath, primary-text icon, disabled toggles excluded. It is for icon toggles only: text list rows select through --list-item-highlight-* instead (Mission past runs). The only active override is .tool-call-output-rail, which keeps its compact output-rail toggles transparent. tests/browser/icon-toggle-active.test.ts samples real active toggles on every ready-made palette over the session header (search), sessions sidebar header, right-panel tab bar, user and assistant message cards (speech button), interruption dock header and transcript canvas (overlay windows share the canvas colour): OKLab ΔE ≥ 8 from the surface on the header and ≥ 5 elsewhere, ≥ 6 from the header's idle hover, ≥ 1.4 from active to active hover, icon ≥ 3:1 on both fills.
  • Window and panel buttons come from styles/components/window.css: .window-icon-button for icon-only commands, .window-action for bordered actions, .window-text-button for borderless inline text commands (accent text, hover tint, muted when disabled: open a reader, request an update, name a brief). A command placed in a .window-footer next to button-secondary/button-primary takes that footer's template instead. Contextual layout (width, alignment) may be scoped to a panel; colours, borders and states stay in the primitive.
  • Movable utility windows opt into DismissibleWindow.draggable and mark their existing top bar with data-window-drag-handle; keep drag geometry shared and controls interactive. --window-visible-height bounds their height to the visual viewport; native scrolling and a sticky existing top bar keep results, pagination and drag/close controls reachable.
  • Appearance mode and the saved light/dark selections are independent (lib/appearance-preferences.ts). Message/tool cards use the muted surface; preferences use the same secondary surface as the main panels. Show a subtle local rollover with --surface-hover-overlay over the preserved selected background, never by substituting a generic panel or the secondary surface; right-panel canvas button rollover overrides live in styles/panels/control-hover.css.
  • index.css re-anchors box-sizing: border-box on every details > :not(summary): the component library resets elements to box-sizing: inherit, so folded content would inherit content-box from the browser's own details slot and overflow full-width controls. Keep that rule rather than patching individual disclosures.
  • In-app logos render components/brand-logo.tsx: the hexagon cropped to its bounds, black (CodeNomad-Logo-OnLight.svg) on light palettes and white (CodeNomad-Logo-OnDark.svg) on dark ones, picked in CSS from the palette's appearance; the always-dark loading screen uses the white one. The square app icon (CodeNomad-Icon.png) stays out of the page; it serves the browser tab and its badge. Large logos (home page, empty states, loading screen) follow their title, never the window: 4rem tall beside a text-3xl title, 3.2rem beside the narrow session column's 1.5rem title, and 5.3rem beside the loading screen's larger title; the title bar's is 20px, the smallest size at which its letters read, which still leaves room in macOS's 28px bar. Resize the logo whenever its title's size changes.
  • Keep the global scrollbar-width default at zero specificity (:where(...) in src/index.css) so component visibility rules win. The timeline shares the transcript's standard native scrollbar (geometry, rail ownership and thumb drags: dev-docs/SESSION_HISTORY_NAVIGATION.md); validate with the full stylesheet and real thumb gestures on mixed-height transcripts.