diff --git a/doc/design/DECISION-SHEET.md b/doc/design/DECISION-SHEET.md index 367dc762a43..1239ff2bbe8 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 type signal → mono icon + label, accent bar removed (PAP-315 / PAP-313 anatomy 1a, Jul 14 2026) + +The five-tone type→color system (sky/violet/rose/amber/neutral accent bar + tinted source icon) is retired from the Decisions feed: the left accent bar is deleted and the source icon renders in `text-muted-foreground`, so a row's type reads through icon shape + text label alone and color is freed up for severity (Critical `destructive`, per 2a) and CTAs. Judgment call in the same commit: `attentionTone`/`attentionToneStyle`/`TONE_STYLE` (lib/attention.ts) were deleted rather than kept as an unused map — a repo-wide grep found exactly one consumer (AttentionQueueRow itself), so no other surface loses the palette and no debt remains; the tone unit tests went with them (type identity is still covered by the `sourceMeta` label/icon tests). + ## Decisions-page severity badges → outline, red = Critical only (PAP-314 / PAP-313 anatomy 2a, Jul 14 2026) Severity on the Decisions feed now renders as an *outline* badge — Critical on the `destructive` tokens (`border-destructive/60 text-destructive`; manual AA check: 4.76:1 light / 4.69:1 dark vs `--card`, WCAG 2.1 small-text pass with no dark override), High as a neutral outline (`border-border text-muted-foreground`), Medium/Low no badge (unchanged). Red is reserved for Critical alone so it reads as an alarm, not decoration. Judgment call in the same commit: the exported `severityStyle`/`SEVERITY_STYLE` map (filled red/orange/yellow/blue accents + dots) was deleted rather than retokenized — it had zero component consumers (only its own unit test) and its filled-accent model is exactly what the plan retires; severity keys a badge, never card color (invariant test preserved). diff --git a/ui/src/components/AttentionQueueRow.tsx b/ui/src/components/AttentionQueueRow.tsx index 6773f589075..73bc60223d7 100644 --- a/ui/src/components/AttentionQueueRow.tsx +++ b/ui/src/components/AttentionQueueRow.tsx @@ -21,7 +21,6 @@ import { attentionDetailImages, attentionDetailLine, attentionImageUrl, - attentionToneStyle, isInlineResolvable, severityBadge, sourceMeta, @@ -107,7 +106,6 @@ export const AttentionQueueRow = memo(function AttentionQueueRow({ selected = false, }: AttentionQueueRowProps) { const meta = sourceMeta(item.sourceKind); - const tone = attentionToneStyle(item); const sevBadge = severityBadge(item.severity); const Icon = meta.icon; const isHidden = variant === "hidden"; @@ -161,9 +159,6 @@ export const AttentionQueueRow = memo(function AttentionQueueRow({ data-attention-source={item.sourceKind} data-attention-severity={item.severity} > - {/* Type accent bar (canonical color map — never severity). */} - -
{/* Expand affordance / spacer gutter — keeps headlines aligned across the list. */} {expandable ? ( @@ -188,7 +183,7 @@ export const AttentionQueueRow = memo(function AttentionQueueRow({
- + {meta.label} {sevBadge && ( diff --git a/ui/src/lib/attention.test.ts b/ui/src/lib/attention.test.ts index 9f9eeb650ee..406c782b6fc 100644 --- a/ui/src/lib/attention.test.ts +++ b/ui/src/lib/attention.test.ts @@ -6,8 +6,6 @@ import { attentionBadgeCount, attentionDateBucket, attentionDetailLine, - attentionTone, - attentionToneStyle, buildAttentionFilterOptions, countActiveAttentionFilters, defaultAttentionFilterState, @@ -131,57 +129,6 @@ describe("sourceMeta", () => { }); -describe("attentionTone + attentionToneStyle (canonical color map §4)", () => { - it("colors plan approvals violet regardless of source kind", () => { - const fromApproval = buildItem({ - sourceKind: "approval", - detail: { kind: "plan_approval", issueTitle: "I", planTitle: "P", summaryExcerpt: null, images: [] }, - }); - const fromInteraction = buildItem({ - sourceKind: "issue_thread_interaction", - detail: { kind: "plan_approval", issueTitle: "I", planTitle: "P", summaryExcerpt: null, images: [] }, - }); - expect(attentionTone(fromApproval)).toBe("violet"); - expect(attentionTone(fromInteraction)).toBe("violet"); - expect(attentionToneStyle(fromApproval).accent).toContain("violet"); - }); - - it("colors confirmations / questions / verdicts in the sky family", () => { - expect(attentionTone(buildItem({ sourceKind: "approval" }))).toBe("sky"); - expect(attentionTone(buildItem({ sourceKind: "issue_thread_interaction" }))).toBe("sky"); - expect( - attentionTone( - buildItem({ - sourceKind: "issue_thread_interaction", - detail: { kind: "questions", questionCount: 2, firstQuestionText: "?", images: [] }, - }), - ), - ).toBe("sky"); - }); - - it("colors failures rose and blocked/recovery/budget amber", () => { - expect(attentionTone(buildItem({ sourceKind: "failed_run" }))).toBe("rose"); - expect(attentionTone(buildItem({ sourceKind: "agent_error_alert" }))).toBe("rose"); - expect(attentionTone(buildItem({ sourceKind: "blocker_attention" }))).toBe("amber"); - expect(attentionTone(buildItem({ sourceKind: "recovery_action" }))).toBe("amber"); - expect(attentionTone(buildItem({ sourceKind: "budget_alert" }))).toBe("amber"); - }); - - it("colors join requests neutral", () => { - expect(attentionTone(buildItem({ sourceKind: "join_request" }))).toBe("neutral"); - }); - - it("gives every tone a distinct accent and never keys color off severity", () => { - const rose = buildItem({ sourceKind: "failed_run", severity: "low" }); - const amber = buildItem({ sourceKind: "budget_alert", severity: "critical" }); - // Same-source rows with opposite severities share one accent (color ≠ severity). - expect(attentionToneStyle(buildItem({ sourceKind: "failed_run", severity: "critical" })).accent).toBe( - attentionToneStyle(rose).accent, - ); - expect(attentionToneStyle(rose).accent).not.toBe(attentionToneStyle(amber).accent); - }); -}); - describe("severityBadge (anatomy 2a — outline badges, red = Critical only)", () => { it("only surfaces a badge for Critical/High", () => { expect(severityBadge("critical")?.label).toBe("Critical"); diff --git a/ui/src/lib/attention.ts b/ui/src/lib/attention.ts index 55513ef6e30..ca64d043711 100644 --- a/ui/src/lib/attention.ts +++ b/ui/src/lib/attention.ts @@ -60,89 +60,6 @@ export function sourceMeta(kind: AttentionSourceKind): SourceMeta { return SOURCE_META[kind] ?? { label: kind.replaceAll("_", " "), icon: AlertTriangle }; } -// --------------------------------------------------------------------------- -// Canonical type → color map (PAP-13409 §4) -// -// The row color is driven by the *kind of decision*, never by severity — one -// map, sourced from `IssueThreadInteractionCard`'s palette so a plan approval or -// confirmation reads identically in the queue and on the issue thread: -// • confirmations / questions / suggested-tasks / verdicts / reviews → sky -// • plan approvals → violet -// • failures (failed run, agent error) → rose -// • blocked / recovery / budget → amber -// • join request → neutral -// Severity only ever surfaces as a small Critical/High badge (never the accent). -// --------------------------------------------------------------------------- - -export type AttentionTone = "sky" | "violet" | "rose" | "amber" | "neutral"; - -export interface AttentionToneStyle { - /** Left accent bar background. */ - accent: string; - /** Source-icon tint. */ - icon: string; - /** Chip / badge border+bg+text (matches the interaction card badge palette). */ - chip: string; -} - -const TONE_STYLE: Record = { - sky: { - accent: "bg-sky-500", - icon: "text-sky-600 dark:text-sky-400", - chip: "border-sky-500/60 bg-sky-500/10 text-sky-900 dark:bg-sky-500/15 dark:text-sky-100", - }, - violet: { - accent: "bg-violet-500", - icon: "text-violet-600 dark:text-violet-400", - chip: "border-violet-500/60 bg-violet-500/10 text-violet-900 dark:bg-violet-500/15 dark:text-violet-100", - }, - rose: { - accent: "bg-rose-500", - icon: "text-rose-600 dark:text-rose-400", - chip: "border-rose-500/60 bg-rose-500/10 text-rose-900 dark:bg-rose-500/15 dark:text-rose-100", - }, - amber: { - accent: "bg-amber-500", - icon: "text-amber-600 dark:text-amber-400", - chip: "border-amber-500/60 bg-amber-500/10 text-amber-900 dark:bg-amber-500/15 dark:text-amber-100", - }, - neutral: { - accent: "bg-muted-foreground/40", - icon: "text-muted-foreground", - chip: "border-border/70 bg-muted/50 text-muted-foreground", - }, -}; - -/** - * Resolve the canonical tone for a row. A plan approval is violet regardless of - * which surface tagged it (approval flow *or* issue-thread confirmation), so we - * check the T1 detail discriminant first, then fall back to the source kind. - */ -export function attentionTone(item: AttentionItem): AttentionTone { - if (item.detail?.kind === "plan_approval") return "violet"; - switch (item.sourceKind) { - case "failed_run": - case "agent_error_alert": - return "rose"; - case "blocker_attention": - case "recovery_action": - case "budget_alert": - return "amber"; - case "join_request": - return "neutral"; - case "approval": - case "issue_thread_interaction": - case "review": - case "productivity_review": - default: - return "sky"; - } -} - -export function attentionToneStyle(item: AttentionItem): AttentionToneStyle { - return TONE_STYLE[attentionTone(item)]; -} - /** * Severity is demoted to a small badge — and only when it is genuinely * escalated (Critical/High). Medium/Low return `null` so most rows carry no