diff --git a/.agents/skills/frontend/information-architecture/SKILL.md b/.agents/skills/frontend/information-architecture/SKILL.md index 021ea868..df17c0e7 100644 --- a/.agents/skills/frontend/information-architecture/SKILL.md +++ b/.agents/skills/frontend/information-architecture/SKILL.md @@ -44,7 +44,7 @@ Rules: - **"Schema history" is not a sibling tab.** Version history lives inside the Schema tab, below the editor and beside the `VersionNavigator` seam. The two overlap on purpose: the navigator is the *reader* (one version, with what it changed), the history is the *ledger* (every version at once). `?tab=versions` remains as a redirect; it does not appear in the tab bar. - **The 4-step checklist is onboarding, not navigation.** It retires itself twice over: when the journey is finished (`hasReleases` makes `done` derivable) and when somebody dismisses it. Dismissal is **per project** and persisted — finishing one project does not teach you the pipeline for the next. It gates nothing and is never the sole path to a screen. `hasReleases` is derived in `useProjectReadiness` from the two-hop read (project → dataset → releases) rather than added to the project-stats wire model: the Overview dashboard already makes both requests for its own cards, so a third spelling of the fact on the server would be exactly the drift these rules exist to prevent. -- **Inference is a rail destination, not a project tab.** Model connections carry no `project_id`: one workspace is one SQLite file, every project uses the same connections, and navigation maps 1:1 to domain objects — so a project tab would state a scope the object does not have. This **supersedes the earlier rail rule** ("logo, collapse toggle, Home, Projects, account avatar — nothing else"); the rail now carries Home, Projects, Inference and the account control, and `DESIGN.md` carries the same membership in both places it states it. What earns a rail entry is a workspace-level object with nowhere else to live, never mere frequency of use. +- **Inference is a rail destination, not a project tab.** Model connections carry no `project_id`: one workspace is one SQLite file, every project uses the same connections, and navigation maps 1:1 to domain objects — so a project tab would state a scope the object does not have. This **supersedes the earlier rail rule** ("logo, collapse toggle, Home, Projects, account avatar — nothing else"); the rail now carries Home, Projects, Inference and the account control, and `docs/ui/navigation.md` carries the same membership. What earns a rail entry is a workspace-level object with nowhere else to live, never mere frequency of use. - **Home is the workspace's dashboard, and Overview is the project's.** They do not overlap, because they answer different questions: Home asks *what is waiting on me, anywhere*, which no single project can answer, and Overview asks *what does this diff --git a/.agents/skills/process/public-writing/SKILL.md b/.agents/skills/process/public-writing/SKILL.md index 5644b1bc..5cd3e4bc 100644 --- a/.agents/skills/process/public-writing/SKILL.md +++ b/.agents/skills/process/public-writing/SKILL.md @@ -75,13 +75,13 @@ issue number — inside a sentence, a quotation, or a denial. "Nothing here clos ## Paragraphs, not notation — and voice -Write the way `DESIGN.md` writes: complete sentences that explain reasoning. Telegraphic -fragments, bare citation chains and stacked parentheticals cost the reader more than they save -the writer. Structure still earns its place — a settled-options list stays a list, a comparison -stays a table. +Write complete sentences that explain reasoning, the way this repository's documentation +writes. Telegraphic fragments, bare citation chains and stacked parentheticals cost the reader +more than they save the writer. Structure still earns its place — a settled-options list stays +a list, a comparison stays a table. -`DESIGN.md`'s copy rules hold here too: **no exclamation marks, no "successfully", no "please"** -— plus no filler acknowledgment, no restating the request before answering, no announcing a +House style for public prose: **no exclamation marks, no "successfully", no "please"** — plus +no filler acknowledgment, no restating the request before answering, no announcing a conclusion the text does not support. Before posting, reread the draft asking: does it name a company or product, and is the mention a diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6f326007..ea586b99 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -280,10 +280,13 @@ If a change fights either boundary, the change is wrong — not the boundary. Any change to `@visionset/app` or `@visionset/ui-core` is governed by [`DESIGN.md`](DESIGN.md) at the repository root — **read it before building or changing a -screen**, not after. It owns the tokens, the type scale, the navigation rules, the tab -shapes, the annotation workspace, and (since #206) the rules for project-level data -surfaces: what a header carries, how numbers are formatted, and why a disabled button with -no explanation is forbidden. +screen**, not after. It owns the visual foundations: colour semantics, typography roles, +spacing, radius, materials, motion, action hierarchy, and accessibility. The product's own +UI rules live beside it: [`docs/ui/product-principles.md`](docs/ui/product-principles.md) +(what a screen must show and offer — headers, numbers, why a disabled button with no +explanation is forbidden), [`docs/ui/navigation.md`](docs/ui/navigation.md) (breadcrumbs, +the rail, URL state), and [`docs/ui/annotator.md`](docs/ui/annotator.md) (the annotation +workspace). It is prose over running code, not decoration. `frontend/ui-core/src/styles.css` carries the tokens and `tokens.ts` mirrors them, gated against each other in both directions by diff --git a/DESIGN.md b/DESIGN.md index db974a59..f6a2ab6e 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -1,1413 +1,424 @@ ---- -version: 1 -name: VisionSet -description: > - VisionSet frontend design system — the authoritative visual contract for the M5 app - (@visionset/app + @visionset/ui-core) and the annotator demo. GitHub-inspired, - content-first, accessible UI on Radix primitives + lucide icons (decision H), styled - with design tokens. Adapted 2026-07-30 from v1's DESIGN.md (computer-vision-lab-app), - whose values were verified in its source; the reference screenshots are described in - the 2026-07-30 design comment on issue #51. Implemented by #128 (M5 WS-1): - frontend/ui-core/src/styles.css carries these values as Tailwind v4 @theme tokens - and frontend/ui-core/src/tokens.ts mirrors them for callers that cannot read CSS, - with a test asserting the two agree in both directions. This file is the prose - half; those two are the running half. -colors: - # Primary action — a near-black with an indigo undertone. NOT the brand. - primary: "#1e2130" # filled buttons, active tab rule, active tool - primary-hover: "#2a2d40" - primary-foreground: "#ffffff" - # Brand — Robomous coral, TWO sites in the whole product (see "Where the brand is") - brand: "#e85d44" # the rail's wordmark, and the progress bar's fill - # Surfaces - background: "#fafafb" # the page - card: "#ffffff" # a surface sitting on it - popover: "#ffffff" - muted: "#f3f4f6" # subtle fills, hover backgrounds, secondary surfaces - disabled: "#f3f4f6" # a disabled control's fill - stage: "#e4e6ec" # the annotator's surround — the neutral a picture is judged against - # Text - foreground: "#1b1d28" # primary text - muted-foreground: "#6b6e7e" # secondary / meta text, inactive tabs - disabled-foreground: "#a0a3b1" - # Lines & focus - border: "#e7e8ec" # hairlines, dividers - input: "#dcdde4" # input and secondary-button borders — a step darker - ring: "rgba(30, 33, 48, 0.35)" # the action colour at 35% - # Status — desaturated, to sit on a near-monochrome page - success: "#2e7d5b" # completed states - warning: "#b98217" - destructive: "#c93b3b" # errors, destructive actions - # Dark chrome (app rail / sidebar) — the same near-black as `primary` - sidebar: "#1e2130" - sidebar-accent: "#2a2d40" # rail hover, and the ACTIVE rail item's fill - sidebar-strong: "#161823" - sidebar-muted: "#8b8fa3" # inactive rail icons and labels - sidebar-foreground: "#ffffff" -typography: - page-title: - fontFamily: system-ui - fontSize: 1.5rem # text-2xl (1.875rem on md+) - fontWeight: 600 - letterSpacing: "-0.025em" - section-title: - fontFamily: system-ui - fontSize: 1rem - fontWeight: 600 - body: - fontFamily: system-ui - fontSize: 0.875rem # 14px base - lineHeight: 1.6 - meta: - fontFamily: system-ui - fontSize: 0.75rem - fontWeight: 400 - label: - fontFamily: system-ui - fontSize: 0.875rem - fontWeight: 500 -rounded: - sm: 4px # chips, small controls - md: 8px # buttons, inputs, badges (default; --radius 0.5rem) - lg: 12px # alerts, dialogs, sheets, tab lists - xl: 16px # cards - full: 9999px # avatars, progress bars, pills -spacing: - xs: 4px - sm: 8px # inline groups, field gap - md: 16px # default layout unit - lg: 24px # grids and page sections - xl: 32px # form section padding - page: 24px -elevation: - none: "none" - card: "shadow" # resting cards - raised: "shadow-md" # card hover - overlay: "shadow-lg" # dialogs, sheets, toasts, dropdowns -layout: - sidebar-width: 240px - sidebar-width-collapsed: 60px - sidebar-width-mobile: 280px ---- - -## Overview - -VisionSet's interface is **neutral-first, content-first, and accessible**. The screen is -almost monochrome — cool greys with a faint indigo undertone — and the colour a person can -act on is a **near-black** (`#1e2130`): filled buttons, the active tab's rule, the active -tool. A dark rail in that same near-black frames a bright content area. - -**Robomous coral (`#e85d44`) is not the interface's colour.** It appears in exactly two -places (see *Where the brand is*, below), and everything else that used to wear it now -wears the near-black. The reasoning is in the token file and worth repeating here: an -interface whose every button is brand-coloured spends the brand on "Cancel". Making colour -scarce is what lets the two places it survives actually mean something. - -**Design principles** (inherited from v1, kept): - -1. **Token-driven, not hardcoded.** Color, radius, and spacing come from the token - registry above, surfaced as semantic utilities. Never a hex, `rgb()` or raw `var()` - color in a class string — v1 spent its life migrating away from that; VisionSet starts - clean and stays clean. **Enforced**: `tests/scripts/design_tokens.test.mjs` scans every - tracked frontend source for a Tailwind arbitrary colour (`bg-[#eb5a47]`, - `text-[var(--x)]`) and fails the build. An *inline* style carrying a **schema-supplied** - colour is the sanctioned exception and the only one — `classColor` answers with whatever - the kernel stored, and Tailwind has never seen it, so no utility could name it. -2. **Radix + lucide only for primitives** (decision H, epic #51). v1 built on shadcn/ui, - which is Radix + Tailwind — the same constraint from the other end. FontAwesome is v1 - legacy and does not come along. -3. **Content over chrome.** Subtle borders, minimal shadows, no gradients, quiet - hover/focus effects. The interface is neutral; the brand is not a surface fill and is not - an interaction colour at all. Whitespace is generous - on forms and lists, where the content is a few things a person reads one at a time — - and **is not a substitute for information**: see principle 7, which governs the - surfaces whose content is a dataset. -4. **Accessible by default.** Real `