Files
PaperClipAI/doc/design/DECISION-SHEET.md
T
scotttongandClaude Fable 5 c90e66bdd6 feat(ui): design-system component convergence — Card/Badge adoption, multiplicative radius ladder, unified list surfaces (#9240)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Its UI is governed by a design system (`DESIGN.md` + the token layer
in `ui/src/index.css`, merged in #9134) whose first principle is "one
way to say each thing" — one Card, one Badge, one nav row
> - After the token extraction landed, ~35 files still hand-rolled card
containers, ~45 files hand-rolled pill spans, the sidebar agents section
duplicated the nav-row chrome, and the inbox and tasks lists rendered
the same task rows two subtly different ways
> - Each divergence is a place where a future design change (radius,
hover language, status vocabulary) silently misses surfaces, defeating
the "edit tokens + run checks" model the design system exists for
> - This pull request converges those surfaces onto the shared
primitives, codifies the radius scale as the modern multiplicative
shadcn ladder, and unifies the row/hover/tree-guide language across the
inbox and tasks lists — every visible delta was human-reviewed
screen-by-screen against a live instance across nine feedback rounds
> - The benefit is that the app's look is now steerable from single
knobs (one `--radius` anchor, one Card, one Badge, one row component),
and the visual regression suite covers the result (514 snapshots
including a new AgentDetail page story)

## Linked Issues or Issue Description

No public GitHub issue exists for this work; describing per the feature
template:

- **Problem**: after the design-token foundation (#9134),
component-level drift remained — hand-rolled cards/pills, duplicated
sidebar row chrome, and two different renderings of task rows (inbox vs
tasks list) meant design changes had to be applied per-surface and
frequently missed spots (e.g. status glyphs rendered 16px in the inbox
but 20px in the tasks list because a slot override silently beat the
component default).
- **Proposed behavior**: all card-shaped containers render via `Card`,
all label pills via `Badge`, sidebar rows via `SidebarNavItem`, and both
task-list surfaces via one `IssueRow` configuration; the radius scale is
a single multiplicative ladder anchored at `--radius: 0.5rem`.
- **Alternatives considered**: converting interactive
`<button>`/`<Link>` cards to `Card` divs (rejected — breaks semantics;
documented inline with `design-allow` comments instead); keeping the
legacy additive radius ladder (rejected in favor of the standard shadcn
multiplicative mapping).

## What Changed

- `Card` adoption across ~35 files (settings pages, auth/board flows,
dashboards, list containers, KPI tiles); non-adoptable sites
(interactive cards, `<li>` rows, class-string props, chart tooltip)
carry documented `design-allow(card-pattern)` comments
- `Card` gains an `interactive` prop — one quiet hover affordance for
clickable cards (cursor, border darken, shadow lift, focus ring),
applied to skills tiles, artifact cards, and the company selector; cards
carry no resting shadow
- `Badge` adoption for 113 hand-rolled pill spans across ~45 files;
`PropertyChip` wraps `Badge` internally; `StatusBadge`, external-object
chips, and match chips stay bespoke by documented decision (WCAG-tuned
status mechanics)
- Radius ladder becomes the multiplicative shadcn mapping
(`sm/md/lg/xl/2xl/3xl/4xl = 0.6/0.8/1.0/1.4/1.8/2.2/2.6 × --radius`,
anchor `0.5rem`); every card surface unifies on `rounded-lg`; the
orphaned 8px literal token is deleted
- Sidebar: agent rows render via `SidebarNavItem` (new additive props:
`iconNode`, `active`, `trailing`, `liveAccessory`); live dots use
`--status-agent-running`; one row rhythm and inset rounded pill
highlight; right-aligned trailing badges; every labeled section is
collapsible
- Inbox + tasks lists unified: md status glyphs, `accent/50` rounded row
hovers, vertical tree guides under parent rows (opaque underlay so
dark-mode translucent borders don't stack), no horizontal dividers under
expanded parents; the swipe-to-archive reveal layer shows only
mid-swipe; board toggle uses the `SquareKanban` glyph
- Kanban: every column carries a status-hued tint; lanes default
expanded (including empty); compact mode collapses empty lanes to
labeled rails (fixes a clipped, label-less empty-column state)
- Storybook: new AgentDetail page story (realistic fixtures, light+dark)
joins the visual suite; suite captures with `reducedMotion: 'reduce'`
and the ux-lab reasoning ticker honors `prefers-reduced-motion`; a stale
lexical alias in `storybook/main.ts` is fixed (build was broken since
the lexical 0.46 bump)
- Keyboard navigation, from live review of the unified lists: inbox
navigation keys work on every tab (archive keys stay scoped to the
archivable tab); keyboard-driven scrolling no longer hands the selection
to whatever row lands under the stationary cursor (hover selects only
after real pointer movement); the tasks list view gains the same j/k /
arrows / Enter selection model as the inbox; and the `g` then `i`
go-to-inbox chord works app-wide instead of only on the issue detail
page
- Token gates restored to 3/3 CLEAN (tokenized a post-#9134 regression
in the recovery card); decisions recorded in
`doc/design/DECISION-SHEET.md` and `doc/design/COMPONENT-INVENTORY.md`
(investigation verdicts: FileTree vs WorkspaceFileBrowser and the four
entity pickers stay separate — evidence included)

## Verification

- `pnpm check:token-gates` → 3/3 CLEAN
- `pnpm typecheck` → green (all packages)
- `cd ui && npx vitest run` → 2106/2106 (assertions updated in lockstep
where they documented superseded decisions; new tests for the global
go-to-inbox chord)
- `pnpm --filter @paperclipai/ui build` → exit 0
- `pnpm build-storybook` → succeeds (also fixes the lexical-alias break
on master)
- Visual regression: 514-snapshot Playwright suite green against the
updated baseline (zero diffs from the keyboard-navigation round — those
changes are purely behavioral). Note: baselines live outside git per the
suite design and the baseline-manifest archive is not yet published, so
CI cannot run this suite — it was run locally throughout; every visible
delta was reviewed screen-by-screen in a live instance across nine
review rounds. Review evidence (before/after triplets) intentionally
kept out of the repo for size; available on request.
- Manual: exercised dashboard, tasks (list + board), inbox, agents,
skills, costs, settings, and artifact surfaces in light and dark themes

## Risks

- Wide but shallow visual surface: most changes are class-string
substitutions with behavior preserved (props, handlers, roles, test
ids). The riskiest areas — dnd-kit card refs (React 19 ref-as-prop),
inbox swipe-to-archive, and sidebar overlays — are covered by existing
unit tests (all green) and were manually exercised.
- Intentional visual deltas (rounded cards, tinted kanban columns, md
status glyphs, unified hovers) are design decisions recorded in
`doc/design/DECISION-SHEET.md`; each maps to a re-baselined snapshot set
locally.
- The visual suite cannot yet run in CI (unpublished baseline archive —
pre-existing condition from #9134); until that lands, snapshot coverage
is local-only.

## Model Used

- Claude Fable 5 (`claude-fable-5`, Anthropic) via Claude Code — agentic
coding with tool use (file editing, test execution, Playwright
screenshot verification); extended thinking enabled.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green (will confirm once CI runs)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(pending first review)
- [x] I will address all Greptile and reviewer comments before
requesting merge

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 17:15:45 -07:00

22 KiB
Raw Blame History

DECISION-SHEET — Run 1 human review

Every open question from TOKEN-AUDIT.md §8 + batch logs and COMPONENT-INVENTORY.md §6, each with a recommendation, blast radius, and where it lands. Statuses: PENDING → APPROVED / OVERRIDDEN (with note) / DEFERRED.

A. Quick wins — low risk, do in this review phase

# Decision Recommendation Blast radius Status
A1 agentStatusBadge vs brandChipBadge byte-identical maps (status-colors.ts) Collapse to brandChipBadge, re-point imports code dedup, zero pixels APPROVED — done 5ecc0f9e4: agentStatusBadge had ZERO importing call sites, so it was deleted outright (no re-pointing, no alias needed); brandChipBadge is the single map, AgentBadgeColor kept (subset of BrandChipColor)
A2 Contrast-pair triplication (color-contrast.ts / worktree-branding.ts / ThemeContext.tsx) One shared constant IF values are truly identical; verify per-pair first, keep semantically distinct ones separate 3 files, zero pixels if identical APPROVED — done 383460bb2: only #f8fafc/#111827 is byte-identical across files → exported as READABLE_TEXT_LIGHT/READABLE_TEXT_DARK from color-contrast.ts, imported by worktree-branding.ts; ThemeContext's #18181b/#ffffff meta-theme-color pair, the DARK_BG/LIGHT_BG rgb-object compositing backgrounds, and worktree-branding's #000000 parse fallback are semantically distinct → untouched
A3 Project-color fallbacks #6366f1 / #64748b (14 sites) Two semantic tokens (--project-seed, --project-none) — file pattern shows two intents rename-only, zero pixels APPROVED — done 7e3e59db2: pure rename --hex-6366f1→--project-seed (7 sites) and --hex-64748b→--project-none (9 sites incl. ActivityCharts 'backlog') + index.css definitions, values unchanged
A4 Test-file hardcoded hex (56 sites) Leave alone; update lockstep only when asserted values actually change none APPROVED — policy adopted, no code change
A5 FileViewerSheet half-migrated var(--paperclip-code-highlight-*, fallback) Mint the two vars in index.css at the fallback values (identical pixels today; makes the intended token real) 1 file APPROVED — done 7929aabeb: both vars minted in the extracted-tokens :root block at exactly the former fallback values; chose to SIMPLIFY the Batch 4 --code-highlight-*-resolved wrappers to plain var(--x) (fallbacks now redundant; nothing sets the vars at runtime); FileViewerSheet.tsx call sites unchanged
A6 "Liveness blue" chat bubble reusing --status-task-in_progress (semantic coincidence) Decouple: mint --liveness-blue with same value so a future status-hue change doesn't drag the chat bubble along 2 sites, zero pixels APPROVED — done b76a7955a: --liveness-blue: #2563eb minted; IssueChatThread.tsx bubble class + IssueChatThread.test.tsx lockstep assertion re-pointed

B. Policy calls

# Decision Recommendation Status
B1 One-off decorative gradients/shadows (5 production + UxLab; 38 arbitrary shadows, no --shadow-* tokens existed) Allowlist as documented "intentional one-off decoration" (extend allowlist criteria beyond third-party); DELETE the ~20 never-reused singleton --gradient-extract-* tokens back to inline, keep only reused ones. Spirit over letter of principle 2 APPROVED — middle path, executed: 27 demo-only tokens reverted inline (19 gradients: --gradient-extract-5,6,8-24; 8 shadows: --shadow-extract-15,16,17,19-23 — all consumed solely by *UxLab.tsx pages), 35 call sites restored to original bracket literals, definitions deleted, 4 UxLab pages allowlisted + criteria doc-comment extended to first-party intentional decoration. KEPT 22 production tokens (gradients 1,2,3,4,7,25,26 + 15 shadows) — NOTE: --shadow-extract-4/5 kept contrary to the audit's first cut because ChatComposer.tsx and IssueChatThread.tsx (production) consume them, not just ChatComposer.test.tsx
B2 Tailwind palette classes (bg-red-500 etc.) — 3,115 sites / 145 files Own future run (Run 4): cluster-by-cluster mapping to semantic tokens, starting with status-adjacent colors; NOT wholesale now, NOT permanent exemption. Update DESIGN.md principle 2 to name palette classes explicitly APPROVED — own Run 4 later
B3 Micro type cluster 9–15px (730 sites) + letter-spacing 9 values (202 sites) Adopt PRIOR-ART named ladder (map 9→10 nano, keep 10/11/12/13/14; 15→14; tracking → 3 steps) via contact-sheet review — executed in the preset-tune session, decided now EXECUTED (pending contact-sheet) — scripts/codemod-type-ladder.mjs (idempotent, committed): 730 font-size sites -> --text-nano (10px, incl. 9->10) / --text-micro (11px, incl. 0.7rem) / text-xs (12px) / --text-compact (13px) / text-sm (14px, incl. 15->14); 202 tracking sites -> --tracking-label (0.08em) / --tracking-eyebrow (0.14em) / --tracking-caps (0.2em); all --fs-* and --ls-* definitions deleted. NOTE: text-xs/text-sm sites also pick up Tailwind scale line-height (contact-sheet reviewable). PRIOR-ART "sm 13" tier renamed --text-compact (name collides with Tailwind text-sm=14px)
B4 Radius conflict (--radius-lg/xl = 0px vs stock 2xl/3xl) Defer to preset session — it's a brand question. Candidate: PRIOR-ART monotonic 6/8/10/14/16 DEFERRED to preset session
B5 Chart palette vs canonical status hues (ActivityCharts in_progress = violet, elsewhere blue) Re-point charts at --status-task-* (operator learns one vocabulary — DESIGN.md P5). Visible change → contact sheet APPROVED & CLOSED — user approved on before/after contact sheet (Jul 6); 2 chart snapshots re-baselined, suite 510/510 on new baseline. Known trade documented: To-Do amber vs priority-Medium amber adjacency, revisit at preset session

C. Component calls (from COMPONENT-INVENTORY.md §6)

# Decision Recommendation Status
C1 ChatComposer vs MarkdownEditor split Keep split; document as deliberate in COMPONENT-INVENTORY APPROVED — keep split, documented deliberate (user re-confirmed after visual review)
C2 FileTree vs WorkspaceFileBrowser parallel tree models Investigate data-shape needs in Run 3; refactor onto FileTree only if shapes align APPROVED — investigate data-shape needs in Run 3 prep
C3 Entity-picker family (4 components) Prop-by-prop diff as Run 3 prep task; no merge without it APPROVED — prop-by-prop diff as Run 3 prep task; no merge without it
C4 Finance card family (5 components) Keep; revisit only if a 6th appears APPROVED — keep all five; revisit only if a 6th appears
C5 Hand-rolled cards (~26 files) → Card; pills (~34 files) → Badge Run 3 shadcn-swap list, per-site snapshot verification DONE (Run 3, Jul 7) — C5a: ~30 files converted (Card supplies skin, sites keep interior layout; interactive <button>/<Link> cards, <li> rows, string props, and the chart tooltip carry design-allow(card-pattern) comments). Deltas sub-pixelmatch-threshold; manual evidence in run3-review/c5a-card-conversions. RESOLVED Jul 8: Card now carries rounded-lg on the multiplicative ladder (see 'Radius scale codified'). C5b: 113 pill spans across ~45 files converted to Badge (+PropertyChip wraps Badge internally); StatusBadge/ExternalObjectStatusSummary/MatchSourceChip stay bespoke per C8/§5.1/domain-tokens with pointer comments. 22 stories re-baselined (+2px pill box, font-medium), triplets committed.
C6 plugins/launchers.tsx overlay Dedicated review task; exclude from Run 3 APPROVED — dedicated review task, excluded from Run 3
C7 radio-card / toggle-switch custom primitives Document as deliberate custom; skip swaps APPROVED — documented as deliberate custom; skip swaps
C8 StatusBadge not wrapping Badge primitive Document as intentional exception (WCAG-tuned .status-chip mechanic) APPROVED — documented as intentional exception (WCAG-tuned .status-chip mechanic)
C9 Toast system (no shadcn primitive installed) Keep custom toast; document as permanent choice (working tone/variant system; sonner migration = churn without user-visible gain) DEFERRED to Run 4 — decide when toast palette colors get retokenized; sonner-behind-a-pushToast-facade is the alternative to evaluate
C10 FeatureGate wrapper pattern (3 near-identical gates) Nice-to-have shared primitive; backlog, not a run APPROVED — backlog nice-to-have, not a run
C11 Sidebar agents section: hand-rolled rows (own spacing/icon colors, palette-blue liveness dot) instead of SidebarNavItem; only section that is collapsible Run 3 item: unify rows onto SidebarNavItem, settle collapsibility policy across sections, liveness dot → canonical status blue. Wholesale shadcn Sidebar adoption REJECTED for now (app already has equivalent machinery incl. icon-rail height trick; highest-regression chrome) — re-evaluate as a dedicated item after Run 3 only if its behaviors (kbd shortcut, persisted state, mobile sheet) are wanted DONE (Run 3, Jul 7) — agent rows render via SidebarNavItem (new additive props: iconNode, active, dense, trailing, liveAccessory); all live dots (agents + nav rows, rail + expanded) now use --status-agent-running (#2563eb, was palette blue-500 #3b82f6 — small hue-darkening delta, per-C11 mandate; "N live" text keeps its palette classes for Run 4). Collapsibility policy (superseded Jul 8 by user review): EVERY labeled section (Work, Company, Projects, Agents) is collapsible, default-open, session-scoped state — one affordance everywhere. "See all agents" stays a plain muted Link on purpose (must not adopt active-route highlighting). 48 sidebar vitest assertions pass unchanged.

User rulings from the tune-session gallery review; all intentionally visible, snapshots NOT re-baselined (fresh before/after triplets regenerated in tests/storybook-visual/test-results/ against the old baseline):

  1. Dark destructive red reverted — .dark --destructive back to master's original oklch(0.637 0.237 25.331) (preset's softer oklch(0.704 0.191 22.216) rejected); light mode untouched.
  2. Budget/quota BAR FILLS reuse status colors — moving-fill elements only: healthy → bg-(--status-task-done), warning → bg-(--status-task-todo), exceeded/hard-stop → bg-(--status-task-blocked) in BudgetPolicyCard.tsx, QuotaBar.tsx (feeds ProviderQuotaCard/BillerSpendCard pages), Costs.tsx, CodexSubscriptionPanel.tsx (escalation tiers only). Inspected and deliberately LEFT: Org.tsx status dot (not a bar), BudgetPolicyCard chip washes/notice borders (not fills), CodexSubscriptionPanel healthy bg-primary/70 + null bg-zinc-700 (healthy tier uses brand primary by design — flagged as ambiguous, not emerald).
  3. RUNNING = status blue, not cyan/teal — IssueChatThread running chip now composes brandChipBadge.blue (layout classes unchanged); RunTranscriptView running label uses new runningLabelText export (text-[#1D4ED8] dark:text-[#2563EB], hexes kept in lib/status-colors.ts for gate cleanliness); statusBadge.running + agentStatusDot.running maps and AgentDetail runStatusIcons.running re-pointed cyan→blue. Deliberately LEFT + flagged: externalObjectStatusIcon/Badge.running (same-map collision — open is already blue there; documented UX-spec tone system), and the cyan "Live" branding family (LiveRunWidget theme, AgentDetail live-card border + Live pulse dots, DesignGuide Live sample) — "Live" is a distinct motif from RUNNING chips; note AgentDetail's mobile Live pill is already blue, so a dedicated Live-color decision is recommended.
  1. BudgetIncidentCard light-mode legibility (pre-existing bug) — the hard-stop card's eyebrow/title/description/banner used dark-tuned red-50/100/200 text with no light variants over the light pink gradient. Light mode now uses red-600..950-tier text (matching the app's existing text-red-700 dark:text-red-300 light-red-surface pattern); dark classes preserved verbatim behind dark:. Sibling fix: BudgetPolicyCard statusTone chips (hard_stop/warning/ok) + its red banner had the same dark-only text — same treatment. Gradient backgrounds (kept B1 tokens) untouched.
  2. Bar fills, remaining stragglers → status hues — ClaudeSubscriptionPanel fillClass ("Current week Opus only" salmon red) and ProviderQuotaCard quota-window fills: red-400→bg-(--status-task-blocked), amber/yellow-400→bg-(--status-task-todo), green-400→bg-(--status-task-done). Healthy bg-primary/70 + null bg-zinc-700 tiers unchanged (r1 ruling). Inspected, NOT a bar: BudgetSidebarMarker circular icon badge (left).
  3. Systematic cyan→status-blue liveness sweep (~50 sites / 20 files; supersedes r1's "Live family left" note per user ruling): running-status tones (CommentThread, interrupt-handoff, runRetryState, AgentDetail run chip + status maps), live dots/pings (AgentDetail, OnboardingChat, ArtifactsPanel generating, ActiveAgentsPanel, IssueDetail Live pill, RunTranscriptView, IssueRunLedger live chip, DesignGuide sample), Live surfaces (LiveRunWidget theme, ActiveAgentsPanel Live-now box, AgentDetail live-card border), scheduled-retry family (IssueScheduledRetryCard, runRetryState, IssueRunLedger retry-pending), externalObjectStatus icon/badge running (now shares blue with open; liveness pulse differentiates — flagged), timeline "now" marker #2dd4bf→#2563eb (1.5px line vs #5b9bf6 delegated bars, shape differentiates — flagged), and the liveness glow shadow tokens --shadow-extract-1/11/14 value-edited rgba(6,182,212,0.08)→rgba(37,99,235,0.08) (kept-token VALUES changed, call sites unchanged). Deliberately LEFT (non-liveness cyan, one-line reasons): xterm terminal cursor (CompanyEnvironments — terminal chrome); on_demand invocation-source chips (AgentDetail x2 + DesignGuide sample — source tag, not liveness); CompanySkills "Includes assets" chip (content-type tag); CompanyImport renamed-file mono text x2 (rename annotation); BlockedReasonChip recovery_required (blocked-reason category); IssueRunLedger "Advanced" outcome + "Silence snoozed" tones (outcome/pause semantics, not live); UxLab decorative gradients (B1 allowlisted decoration).

1+2. Toggle unification — hunt found exactly ONE second switch implementation: ToggleField in agent-config-primitives.tsx (hand-rolled h-5 w-9 pill, bg-green-600 track — the "other green"). It now renders the canonical ToggleSwitch (3da1bbcc5 capsule, on = var(--status-task-done)), same props/behavior/testid. All other named suspects (AgentConfigForm, Instance*Settings, RoutineDetail, PipelineSettings, story fixtures) already used ToggleSwitch; remaining bg-green-600 hits are buttons, not tracks. Every switch now renders the one capsule + one green. 3. Agent-status chips → canonical colors ("Org snippets and quick scan identity" = StatusBadge in control-plane-surfaces story): statusBadge agent keys now route through brandChipBadge families (bordered brand chips): running → blue, idle → GRAY (was yellow tint), paused → amber, active → green (no canonical agent status exists — user-ruled mapping to the brand green/done family); error already rides the shared run-status red. brandChipBadge block moved above statusBadge in status-colors.ts (declaration order). Org.tsx's hand-rolled status-dot ternary now routes through agentStatusDot (same hues + gains the blue running dot). Left: Companies.tsx company-status chip (company entity, not agent), AgentConfigForm "current" model tag (not a status). 4. Dark-text-on-light-wash sweep — the flagged BudgetPolicyCard banner was already fixed in round 2 (screenshot predated it). Systematic sweep (bare text-{red,amber,emerald,sky,green,yellow,cyan}-{50..300} without dark: protection): 58 sites fixed across 14 files (Dashboard budget alert, BudgetPolicyCard remaining-amount, AgentConfigForm banners x3, AgentDetail banners/chips x9, ProjectDetail x3, CompanyAccess, IssueDocumentsSection x6, RoutineHistoryTab x11, DocumentDiffModal x2, PipelineItemBodyDocument x2, RoutineSaveBar, DocumentFrameHeader, OutputFileTile x3, CompanySkills x10) — pattern: 50→950, 100→900, 200→800, 300→700 in light + original behind dark:, opacity suffixes preserved. Verified-safe leftovers: InviteLanding x4 (dark-styled standalone page), SidebarNavItem badge + DocumentAnnotationLayer tail + DevRestartBanner (solid dark/colored bg or dark:-protected).

Verification status (this review)

  • pnpm check:token-gates — re-run independently: 3/3 CLEAN (468 files, 31 allowlist entries).
  • pnpm typecheck + full pnpm test:storybook-visual — re-running independently (in progress).
  • Eyeball-pass note: the Phase 0 baseline was captured at the master fork point before any change, and the suite compares current rendering to it at maxDiffPixels: 0 — pixel-equality with master-at-fork is machine-proven; side-by-side Storybook remains available on request (pnpm storybook here + -p 6007 on master).

Baseline reconcile — Run 3 setup (Jul 7, 2026)

Fresh worktree from origin/master (3b16ac380) on design/component-convergence. The baseline-manifest archive is still unpublished (placeholder URL), so the 510 local baselines were seeded from the Run 1 worktree (~/Projects/DEV/paperclip-design-simplify, tests/storybook-visual/__snapshots__/, branch head 44ab1ad17) into gitignored tests/storybook-visual/.snapshots/. First run: 497/510 green; 6 stories (12 snapshots, both themes) diffed, each verified against a specific master commit that postdates the baselines, then re-baselined — nothing else touched:

  • pages-work-timeline--hour-zoom / --day-zoom / --with-human-activity ← 59092e85d "[codex] Fix work timeline actor avatars (#9152)" (diff = actor avatar column only)
  • product-navigation-layout--board-chrome-matrix / --sidebar-icon-alignment ← 83f5f5984 "[codex] Hide goals sidebar link behind experiment (#9189)" (diff = one sidebar row removed, rows below shift up)
  • ux-labs-converted-test-pages--issue-chat-review-surface ← 6d103b835 (system-comment attribution), 696c694a5 + 4f5abf600 (IssueChatThread reply composer / recovery card) (diff = attribution rows + inline reply composer)

Post-reconcile verification: full suite 509 passed clean + 1 known-pattern parallel-load flake (product-dialogs-modals--new-agent-external-invite [light], passed on retry), exit 0. Snapshot PNGs remain outside git by design; this entry is the durable record until the baseline archive publication flow exists.

Status glyph size reverted to md (Run 3 review feedback, Jul 8 2026)

Reverses PAP-243/PAP-245. Task-row status glyphs in the inbox and tasks list standardize on md (16px); the lg (20px) enlargement is withdrawn after user review. IssueRow fallbacks and both lists' slots (the PAP-246 slot-override gotcha) now agree on md; the size tests that documented the lg decision were updated in lockstep. 10 stories re-baselined (run3-review/feedback-round-6).

Radius scale codified (Run 3 review feedback, Jul 8 2026)

Multiplicative shadcn ladder adopted, closing B4. --radius: 0.5rem (8px) is the single anchor; sm/md/lg/xl/2xl/3xl/4xl = 0.6/0.8/1.0/1.4/1.8/2.2/2.6 x anchor (rounded-lg IS the anchor). Card now carries rounded-lg, and all card surfaces are unified on it: artifact cards (was 8px literal --rad-8, token deleted), skills tiles (was md), chart cards (already lg), the chat plan/interaction card shell (was sm — the "purple border follows the knob but isn't 8px" finding). Inputs/buttons stay at md (6.4px) — controls one step tighter than containers. Shadow decision still open (Design lab).

Interactive-card pattern (Run 3 review feedback, Jul 7 2026)

One Card, two modes. The Card primitive is a static container by default; when the whole card is a click target it takes the interactive prop (ui/src/components/ui/card.tsx): cursor-pointer + quiet hover (border darkens to foreground/20, shadow lifts to md) + keyboard focus ring. Non-Card interactive cards (skills tiles in CompanySkills, artifact Link-cards) carry the same class recipe verbatim. Chosen over the louder accent-border/tint hover after user review. Cards whose rows are the click targets (Costs "By agent") keep the card boundary static — the affordance belongs to the row.

Run 3 — component convergence — DONE pending review (Jul 7, 2026)

Executed on design/component-convergence (worktree focused-agnesi). Scope C2/C3/C5/C11 + AgentDetail story, per RUN3-PROMPT. Verification at close: token gates 3/3 CLEAN (also fixed the master regression in IssueRecoveryActionCard from 4f5abf600); pnpm typecheck green; pnpm --filter @paperclipai/ui build exit 0; ui vitest 2098/2098; visual suite 512/512 (510 prior + AgentDetail light/dark) against the updated local baseline. Remaining card/pill pattern matches all carry design-allow(...) inline comments (audited). Review materials: doc/design/run3-review/ (C11 dot recolor, C5a corner/shadow pairs, 43 C5b triplets). Two sub-threshold facts a reviewer should know: (1) Playwright pixelmatch's 0.2 per-pixel color threshold means maxDiffPixels: 0 tolerates low-contrast changes — the C11 dot recolor and C5a corner-squaring pass the suite without re-baselining; manual evidence committed instead. (2) The Card primitive has no rounded-* (square-era survivor) — converted cards render square; one rounded-* on card.tsx + re-baseline restores roundness everywhere if wanted.

Tune session — CLOSED (Jul 6, 2026)

User approved the complete new design language via gallery v4 + live test drive on the :3300 worktree instance ("ship it"). Merged origin/master (12 commits; one conflict — upstream deliberately removed the Wakes-on-confirm chip, deletion accepted). 296 snapshots re-baselined; gates 3/3 CLEAN; typecheck green; final suite verification run against the new baseline. Remaining roadmap: Run 3 (cards/pills/C11 sidebar + investigations + AgentDetail story), issue→task rename run, Run 4 (palette classes + toast), ESLint ratchet.