From 904de491ff032a2b92caf5bedafa1603ca43d4b7 Mon Sep 17 00:00:00 2001 From: scotttong Date: Tue, 14 Jul 2026 03:02:29 -0700 Subject: [PATCH] design(decisions): page-level token sweep, stories coverage, DECISION-SHEET (6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Zero state green celebration mark moves to tokens (bg-muted circle, text-muted-foreground check) — the glyph shape carries the all-clear meaning; no sky/violet/rose/amber/orange/green class remains on the Decisions page. Spot-checked: selection ring (ring-ring), curtains (bg-muted/30), resolver band (bg-muted/20) were already token-only. Stories updated as the coverage map: dead thumbnail fixtures dropped (5b retired the row), TypeColorsAndDetail renamed to Card anatomy & detail, stale tone-map comments rewritten, new KeyboardSelected story covers the selection ring. No snapshot runs or re-baselining (suite dormant per DECISION-SHEET). DECISION-SHEET closes the Quiet Ledger series: palette retirement entry + cross-refs to the six-verb and red-means-Critical records; embedded IssueThreadInteractionCard palette flagged as out-of-scope shared-component debt. Co-Authored-By: Paperclip --- doc/design/DECISION-SHEET.md | 4 ++ ui/src/pages/WhatNeedsMe.tsx | 4 +- .../stories/what-needs-me.stories.tsx | 48 +++++++++---------- 3 files changed, 29 insertions(+), 27 deletions(-) diff --git a/doc/design/DECISION-SHEET.md b/doc/design/DECISION-SHEET.md index dc46cf1454c..ed5e8f88561 100644 --- a/doc/design/DECISION-SHEET.md +++ b/doc/design/DECISION-SHEET.md @@ -39,6 +39,10 @@ Every open question from TOKEN-AUDIT.md §8 + batch logs and COMPONENT-INVENTORY | 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. | +## Decisions page → palette classes retired, series closed (PAP-319 / PAP-313 sweep, Jul 14 2026) + +The Quiet Ledger series (anatomy 1a·2a·3a·4a·5b) closes with a page-level sweep: **no `sky/violet/rose/amber/orange/green` class remains anywhere on the Decisions page** — its color budget is the 12 shadcn core tokens plus `destructive`, which is reserved for the Critical badge alone (see the PAP-314 entry: red means Critical, never decoration; and the PAP-316 entry: the six-verb CTA vocabulary Approve / Reject / Answer / Review / Open / Restore). Spot-checks confirmed the keyboard-selection ring (`ring-ring`), curtain rows (`bg-muted/30`), and the expanded resolver band (`bg-muted/20` + `border-border/60`) were already token-only. Judgment call: the zero-state's green celebration mark (`bg-green-500/10` circle + `text-green-500` check) moves to `bg-muted` + `text-muted-foreground` — the CheckCircle2 glyph carries the "all clear" meaning by shape, and a page whose only red is an alarm shouldn't keep a green that means nothing is happening. Stories updated as the coverage map in the same commit (dead thumbnail fixtures dropped, `TypeColorsAndDetail` → "Card anatomy & detail", new `KeyboardSelected` ring story); snapshot suite untouched (dormant per this sheet). Flagged, not changed: the expanded resolver embeds the shared `IssueThreadInteractionCard` (violet plan chip et al.), which renders on issue threads too — resolver internals are out of scope per the approved plan, so its palette is that component's decision, not this page's. + ## Decisions-page cards → thumbnail row dropped (PAP-318 / PAP-313 anatomy 5b, Jul 14 2026) Evidence thumbnails (`ThumbnailStack`: overlapping 44px screenshots + "+N" chip) no longer render on Decisions cards — every card is now title + detail line + project chip + footer, so evidence lives one click away behind the 4a footer-left context link instead of competing with the headline for card height. Judgment calls in the same commit: (1) **the now-dead client helpers `attentionDetailImages`/`attentionImageUrl` are deleted** from `lib/attention.ts` (this row was their only consumer — same dead-code-dies-with-its-consumer rule as the 1a tone-system deletion); the server keeps sending `detail.images` and the shared type keeps the field, so richer surfaces (e.g. an Inbox-Split detail pane) can re-adopt them later; (2) the thumbnail test inverts to assert images never render inline even when the payload carries them, so a regression re-introducing the row fails loudly. diff --git a/ui/src/pages/WhatNeedsMe.tsx b/ui/src/pages/WhatNeedsMe.tsx index 2138e262cb0..09aeb4eae93 100644 --- a/ui/src/pages/WhatNeedsMe.tsx +++ b/ui/src/pages/WhatNeedsMe.tsx @@ -791,8 +791,8 @@ function CaughtUpNote({ filtered }: { filtered: boolean }) { function ZeroState() { return (
-
- +
+

You're all caught up

diff --git a/ui/storybook/stories/what-needs-me.stories.tsx b/ui/storybook/stories/what-needs-me.stories.tsx index 05bc618b146..2e0bee15e0c 100644 --- a/ui/storybook/stories/what-needs-me.stories.tsx +++ b/ui/storybook/stories/what-needs-me.stories.tsx @@ -81,19 +81,6 @@ function item( }; } -/** A visible colored tile as a data URI so thumbnails render in static screenshots. */ -function thumb(hex: string, label: string): string { - const svg = `${label}`; - return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`; -} - -const IMAGES = [ - { assetId: thumb("#0ea5e9", "1"), alt: "screenshot 1" }, - { assetId: thumb("#8b5cf6", "2"), alt: "screenshot 2" }, - { assetId: thumb("#f43f5e", "3"), alt: "screenshot 3" }, - { assetId: thumb("#f59e0b", "4"), alt: "screenshot 4" }, -]; - const POPULATED: AttentionItem[] = [ item( "recov-1", @@ -199,9 +186,9 @@ const DETAILS: Record = { kind: "questions", questionCount: 2, firstQuestionText: "Which auth provider should we standardize on?", - images: [IMAGES[0], IMAGES[1]], + images: [], }, - "review-1": { kind: "generic", summaryExcerpt: "3 files changed · +212 / −41", images: [IMAGES[0], IMAGES[1], IMAGES[2], IMAGES[3]] }, + "review-1": { kind: "generic", summaryExcerpt: "3 files changed · +212 / −41", images: [] }, "fail-1": { kind: "failed_run", agentName: "Deployer", failureReasonExcerpt: "exit code 1 running migrate", images: [] }, "budget-1": { kind: "budget", observedPercent: 85, amountObserved: 425, amountLimit: 500, images: [] }, }; @@ -213,8 +200,10 @@ const POPULATED_DATED: AttentionItem[] = POPULATED.map((it) => ({ detail: DETAILS[it.id] ?? it.detail, })); -// A dedicated set exercising the §4 color map (a plan approval = violet next to a -// sky confirmation), §7 detail lines, §8 project chips and §10 thumbnail stacks. +// A dedicated set exercising the Quiet Ledger card anatomy: mono type icon + +// label (1a), detail lines, project chips, and the persistent footer CTAs +// drawn from the six-verb vocabulary (3a). Type colors and thumbnail stacks +// are retired (PAP-313); evidence lives behind the footer-left context link. const SHOWCASE: AttentionItem[] = [ { ...item("plan-1", "issue_thread_interaction", "high", "Approve plan: Attention queue redesign", "A plan is awaiting your approval.", { @@ -227,7 +216,7 @@ const SHOWCASE: AttentionItem[] = [ project: { id: "proj-alpha", name: "Alpha", urlKey: "alpha", color: "#0f766e", icon: "rocket" }, }), activityAt: new Date(NOW - 20 * 60 * 1000).toISOString(), - detail: { kind: "plan_approval", issueTitle: "Attention home", planTitle: "Row/card redesign — 8 sections", summaryExcerpt: null, images: [IMAGES[1]] }, + detail: { kind: "plan_approval", issueTitle: "Attention home", planTitle: "Row/card redesign — 8 sections", summaryExcerpt: null, images: [] }, }, { ...item("conf-1", "approval", "medium", "Confirm: publish release notes", "A confirmation is pending.", { @@ -251,7 +240,7 @@ const SHOWCASE: AttentionItem[] = [ project: PROJECTS["intx-1"], }), activityAt: new Date(NOW - 90 * 60 * 1000).toISOString(), - detail: { kind: "questions", questionCount: 2, firstQuestionText: "Which auth provider should we standardize on?", images: [IMAGES[0], IMAGES[2]] }, + detail: { kind: "questions", questionCount: 2, firstQuestionText: "Which auth provider should we standardize on?", images: [] }, }, { ...item("fail-2", "failed_run", "critical", "Deploy pipeline failed after 3 retries", "Retries exhausted.", { @@ -259,7 +248,7 @@ const SHOWCASE: AttentionItem[] = [ relatedIssue: null, }), activityAt: new Date(NOW - 3 * HOUR).toISOString(), - detail: { kind: "failed_run", agentName: "Deployer", failureReasonExcerpt: "exit code 1 running migrate", images: [IMAGES[3]] }, + detail: { kind: "failed_run", agentName: "Deployer", failureReasonExcerpt: "exit code 1 running migrate", images: [] }, }, { ...item("budg-2", "budget_alert", "low", "Company budget crossed 85%", "Budget threshold crossed.", { @@ -314,6 +303,7 @@ function Queue({ snoozed = [], dismissed = [], openCurtains = false, + selectedId = null, }: { items: AttentionItem[]; groupBy?: AttentionGroupBy; @@ -321,6 +311,7 @@ function Queue({ snoozed?: AttentionItem[]; dismissed?: AttentionItem[]; openCurtains?: boolean; + selectedId?: string | null; }) { const firstInline = items.find((i) => i.inlineResolvable && (i.sourceKind === "approval" || i.sourceKind === "join_request")); const [expandedId, setExpandedId] = useState(firstInline?.id ?? null); @@ -350,8 +341,8 @@ function Queue({

{count === 0 && snoozed.length === 0 && dismissed.length === 0 ? (
-
- +
+

You're all caught up

@@ -379,6 +370,7 @@ function Queue({ key={it.id} item={it} companyId={companyId} + selected={selectedId === it.id} expanded={expandedId === it.id} onToggleExpand={() => setExpandedId((p) => (p === it.id ? null : it.id))} onDismiss={(d) => setCleared((prev) => new Set(prev).add(d.id))} @@ -468,10 +460,16 @@ export const WithCurtains: Story = { args: { items: POPULATED_DATED.slice(0, 3), groupBy: "date", snoozed: SNOOZED, dismissed: DISMISSED, openCurtains: true }, }; -export const TypeColorsAndDetail: Story = { +export const CardAnatomyAndDetail: Story = { + name: "Card anatomy & detail", args: { items: SHOWCASE, groupBy: "type" }, }; +/** The keyboard-selection ring (`ring-ring`, token-only) on the first card. */ +export const KeyboardSelected: Story = { + args: { items: POPULATED_DATED, groupBy: "date", selectedId: "recov-1" }, +}; + /** The ~8s undo toast shown after dismissing a row (plan §6). */ function DismissUndoDemo() { const { pushToast } = useToastActions(); @@ -530,9 +528,9 @@ export const MobilePopulated: StoryObj = { ), }; -/** Mobile: the type-color + detail + thumbnail showcase at phone width. */ +/** Mobile: the card anatomy + detail showcase at phone width. */ export const MobileShowcase: StoryObj = { - name: "Mobile · Type colors & detail", + name: "Mobile · Card anatomy & detail", render: () => (