docs(desktop): add judgment-first AGENTS guide and align DESIGN/README

Capture durable Desktop engineering principles from recent sessions —
state by authority, workspace-switch shapes, resolver ladders, optimistic
UI — and point root AGENTS.md at the scoped guide with current filenames.
This commit is contained in:
Brooklyn Nicholson 2026-07-12 05:31:54 -04:00
parent 291eae63b7
commit 8bea079e2e
4 changed files with 400 additions and 15 deletions

View file

@ -6,12 +6,29 @@ concern, tokens over literals, flat over boxed.** If you reach for a raw color,
a one-off shadow, a bespoke button, or a hardcoded `px-*` on a control — stop,
there's already a primitive for it.
This file owns the visual and interaction contract. Read
[`AGENTS.md`](./AGENTS.md) for architecture, state, resolver, transport, and
testing rules.
This doc contains two kinds of content, maintained differently:
- **Principles** (flatness, intent, feedback, motion, cancellation) are durable.
They hold as components come and go.
- **Named contracts** (tokens, `Button` variants, primitive names) are the
design system's current API. They are maintained *with* the code: if you
change a primitive, token, or variant, update its entry here **in the same
change** — a stale name in this file is a bug, exactly like a stale type.
When a rule and the code disagree, fix whichever is wrong rather than forking a
one-off at the call site.
## Principles
1. **Flat, not boxed.** No card-in-card, no divider borders inside a panel.
Group with whitespace and a single hairline, never nested rounded boxes.
2. **Borderless + shadow for elevation.** Overlays float on `shadow-nous` + a
`--stroke-nous` hairline, not hard borders.
2. **Borderless elevation for floating panels.** Overlays float on
`shadow-nous` + a `--stroke-nous` hairline, not thick framed boxes. In-panel
structure may use token hairlines sparingly.
3. **One primitive per concern.** One `Button`, one set of control variants,
one `SearchField`, one `Loader`, one `ErrorState`. Migrate onto them; don't
fork.
@ -20,11 +37,40 @@ there's already a primitive for it.
5. **Style lives in the primitive.** Variants and sizes own padding, radius,
color, chrome. Call sites pass a `variant`/`size`, not `className` overrides
that re-specify those.
6. **Intent before automation.** Surface useful actions and previews, but do not
open panes, move focus, or navigate because a tool happened to produce
something.
7. **Immediate feedback.** Direct manipulation updates the view first. Network
or disk persistence reconciles afterward and rolls back visibly on failure.
## Information architecture
- **Chat is the home surface.** The transcript and composer stay primary; tools,
previews, files, review, and terminal complement the conversation.
- **Pages are durable destinations.** Chat, Skills, Messaging, and Artifacts
remain in shell chrome. Do not hide a distinct product noun inside an
unrelated page.
- **Route overlays are short tasks.** Settings, Command Center, Cron, Profiles,
Agents, and Starmap render as `OverlayView` cards and return to the previous
route on close. Model/session pickers and dialogs layer above the current
surface; they are not navigation stacks.
- **Panes are working context.** Preview, files, review, and terminal remain
attached to the current task. Their state survives temporary hiding and chat
switches where the underlying tool is meant to persist.
- **One action, one home.** A command may have keyboard, palette, and visible
affordances, but they invoke the same action and state. Do not fork behavior
per entry point.
- **Projects own workspace cwd.** Use Sidebar → Projects for local folders and
worktrees; do not reintroduce a per-session/right-sidebar folder-picker flow.
Navigation must preserve context. A background session finishing, a tool result
arriving, or a project refresh may update badges and cached data; it must not
replace the foreground transcript or steal focus.
## Surfaces & elevation
Every overlay / dialog / toast (boot-failure, install, notifications,
model-picker, onboarding, prompt-overlays, updates, base `Dialog`) uses:
Floating panels (base `Dialog`, route overlays, boot/install/update surfaces,
model-picker, onboarding, prompt overlays, notifications) use:
```
shadow-nous /* downward-weighted, layered contact→ambient falloff */
@ -35,6 +81,11 @@ Both are CSS vars in `src/styles.css` — tune in one place, everything inherits
Don't add per-overlay `shadow-[…]` or `border-(--ui-stroke-secondary)`
one-offs; if elevation needs to change, change the token.
Menus and popovers use their own shared `shadow-md` +
`--ui-stroke-secondary` primitive treatment. Drag affordances may use tokenized
dashed targets and local blur. These are semantic surface classes, not licenses
for call-site shadow or border inventions.
## Stroke & color tokens
| Token | Use |
@ -62,8 +113,9 @@ fill/shadow), `ghost`, `link`, `text` (boxless quiet inline — "Cancel",
"Open logs").
**Sizes:** `default`, `xs`, `sm`, `lg`, `inline` (flush, zero box — for buttons
that sit inside a heading/sentence; replaces `h-auto px-0 py-0`), and the icon
family `icon` / `icon-xs` / `icon-sm` / `icon-lg` / `icon-titlebar`.
that sit inside a heading/sentence; replaces `h-auto px-0 py-0`), `micro`
(status-stack/table-footers), and the icon family `icon` / `icon-xs` /
`icon-sm` / `icon-lg` / `icon-titlebar`.
Notes:
- Text buttons are square (no radius) and sized by padding + line-height (no
@ -107,11 +159,36 @@ Notes:
through for a11y.
- **Logs:** `LogView` — no bg, hairline border, tight padding, small mono.
Every place we surface raw logs uses it.
- **Empty:** `EmptyState` / `EmptyPanel` — don't hand-roll centered empties.
- **Empty:** `EmptyState` for plain page bodies; `PanelEmpty` for overlay
master/detail empties with an icon and action. Don't hand-roll a third
centered empty.
## Chat, tools & boot surfaces
- The transcript and composer are built on `@assistant-ui/react`. Extend the
existing components under `src/components/assistant-ui` and
`src/app/chat/composer`; do not fork a second markdown, message, tool-call, or
approval renderer for one feature.
- A tool result may expose an inline action that opens a preview. It must not
open the rail automatically.
- Install, onboarding, connecting, boot failure, and reauthentication are
distinct states with shared visual primitives. Preserve their recovery
semantics when unifying appearance.
- Respect `AppShell` overlay ownership. Persistent terminal/content layers,
route overlays, dialogs, and boot surfaces must not compete through ad-hoc
z-index literals.
## Iconography & brand
- **`Codicon`** is the icon set. No mixing icon libraries inline.
- **Tabler** is the default component/chrome set. Import its curated aliases and
`iconSize` scale from `src/lib/icons.ts`; do not import icon packages directly
in feature code.
- **`Codicon`** is the compact editor/tool/status vocabulary. Use
`src/components/ui/codicon.tsx`, including `codiconIcon()` where a
Tabler-shaped component is required.
- Pick the vocabulary by semantic context and reuse the existing icon for an
action. Do not introduce a third icon set or mix styles within one control
group.
- **`BrandMark`** (`src/components/brand-mark.tsx`) is the brand glyph — the
`nous-girl` mark on a white tile, softly rounded, identical in light/dark.
It replaced scattered Sparkles glyphs in updates / onboarding / about. Use it
@ -124,6 +201,43 @@ Notes:
- Choreographed exits (e.g. onboarding's "matrix" fade-down) stagger per-element
then settle the surface — the outer container's fade is *delayed* so it
doesn't swallow the inner animation. Don't let a global fade race the detail.
- Motion follows state; it never delays state. Selection, drag targets, cancel,
and pressed feedback paint in the current frame.
- Do not animate layout geometry with `transition-all` on a hot interaction.
Name the properties, avoid backdrop-filter repaints during movement, and
remove animation before masking a performance problem.
## Direct manipulation & performance
The app should feel instant under real load — long transcripts, several panes,
live streams. Design toward that:
- Direct manipulation paints first; persistence reconciles after and rolls back
visibly on failure.
- Keep interaction feedback cheap: hot-path state stays local or narrowly
derived, not wired into heavy trees; pointer work coalesces per frame.
- One drop region has one visual owner, and drop targets speak one affordance
language across files, sessions, tabs, and panes. Overlapping targets resolve
to the active one instead of stacking overlays.
- Forgiving geometry beats pixel-perfect triggers; edge actions live near their
edge, not clustered in the center.
- Expensive stateful surfaces stay mounted when hidden. Visibility is not
lifecycle.
Prove speed with realistic content. A fast empty-state demo says nothing about a
long transcript or a busy terminal.
## Keyboard & cancellation
- Keyboard ownership follows focus. The focused surface wins its keys; shell
shortcuts must not steal a terminal's or editor's bindings.
- Register global shortcuts through the shared layer, not ad-hoc listeners.
- One cancel gesture does one thing: cancel the active interaction, or close the
topmost dismissable surface — never both, never the control underneath.
- Cancellation is synchronous in the UI even if cleanup is async: overlays,
cursors, and pending gesture state clear at once.
- Flows that deliberately cannot be dismissed (install/onboarding, destructive
confirmation) must make that explicit.
## i18n
@ -135,12 +249,15 @@ Notes:
## State (TypeScript)
Mirrors the repo TS style (see root `AGENTS.md`):
The detailed state contract lives in the scoped
[`AGENTS.md`](./AGENTS.md). Visual code follows these essentials:
- Shared/cross-component state → small **nanostores**, not prop-drilling.
Each feature owns its atoms; shared atoms live in `src/store`.
- Rendering components subscribe with `useStore`; non-render actions read with
`$atom.get()`.
- Subscribe to derived coarse facts instead of high-frequency source atoms when
the component does not render the full value.
- Colocated action modules over god hooks. A hook owns one narrow job.
- Keep persistence beside the atom that owns it. Route roots stay thin.
- Prefer `interface` for public props; extend React primitives
@ -163,5 +280,13 @@ Mirrors the repo TS style (see root `AGENTS.md`):
- [ ] No `className` overriding a primitive's padding / size / radius / chrome?
- [ ] Overlay uses `shadow-nous` + `border-(--stroke-nous)`, no hard border?
- [ ] Flat — no card-in-card, no gratuitous row dividers?
- [ ] No automatic navigation, focus steal, or pane opening from background
events?
- [ ] Direct manipulation paints immediately and rolls back cleanly on failure?
- [ ] Hot interactions avoid broad subscriptions, layout thrash, and
`transition-all`?
- [ ] Keyboard ownership and single-action `Esc` behavior are correct?
- [ ] All four locales updated for any new/changed string?
- [ ] `cursor-pointer`, focus ring, and `Esc`-to-close behave?
- [ ] Touched a primitive, token, or variant? Its named-contract entry in this
file is updated in the same change.