mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-27 11:20:42 +01:00
design(decisions): mono type signal — icon + label, accent bar removed (1a)
Commit 2/6 of the Quiet Ledger series (Concept A, anatomy 1a). - AttentionQueueRow: delete the left tone accent bar; source icon now renders in text-muted-foreground (inherited from the label span). Type identity reads through icon shape + text label alone. - lib/attention: delete attentionTone/attentionToneStyle/TONE_STYLE — repo-wide grep found exactly one consumer (this row), so the five-tone sky/violet/rose/amber/neutral map goes rather than lingering unused. - Tone unit tests deleted with the map they asserted (5 tests); the sourceMeta label/icon coverage still pins type identity per kind. - DECISION-SHEET entry records the delete-vs-scope judgment call. Verified: token gates 3/3 CLEAN, typecheck green, ui vitest 2532/2532. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
b2a9e09dab
commit
f833317588
4 files changed
+5
-142
No files matched your search
@@ -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).
|
||||
|
||||
@@ -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). */}
|
||||
<span className={cn("absolute inset-y-0 left-0 w-1", tone.accent)} aria-hidden />
|
||||
|
||||
<div className="flex items-start gap-2 py-3 pl-4 pr-3">
|
||||
{/* Expand affordance / spacer gutter — keeps headlines aligned across the list. */}
|
||||
{expandable ? (
|
||||
@@ -188,7 +183,7 @@ export const AttentionQueueRow = memo(function AttentionQueueRow({
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1">
|
||||
<span className="inline-flex items-center gap-1 text-xs font-medium text-muted-foreground">
|
||||
<Icon className={cn("h-3.5 w-3.5", tone.icon)} />
|
||||
<Icon className="h-3.5 w-3.5" />
|
||||
{meta.label}
|
||||
</span>
|
||||
{sevBadge && (
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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<AttentionTone, AttentionToneStyle> = {
|
||||
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
|
||||
|
||||
Reference in new issue
Block a user