mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-25 09:41:04 +01:00
design(decisions): page-level token sweep, stories coverage, DECISION-SHEET (6)
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 <noreply@paperclip.ing>
This commit is contained in:
1 parent
dbb1b57187
commit
904de491ff
3 files changed
+29
-27
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 → 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.
|
||||
|
||||
@@ -791,8 +791,8 @@ function CaughtUpNote({ filtered }: { filtered: boolean }) {
|
||||
function ZeroState() {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center rounded-xl border border-dashed border-border py-20 text-center">
|
||||
<div className="mb-4 rounded-full bg-green-500/10 p-4">
|
||||
<CheckCircle2 className="h-10 w-10 text-green-500" />
|
||||
<div className="mb-4 rounded-full bg-muted p-4">
|
||||
<CheckCircle2 className="h-10 w-10 text-muted-foreground" />
|
||||
</div>
|
||||
<p className="text-lg font-semibold text-foreground">You're all caught up</p>
|
||||
<p className="mt-1 flex items-center gap-1.5 text-sm text-muted-foreground">
|
||||
|
||||
@@ -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 = `<svg xmlns='http://www.w3.org/2000/svg' width='88' height='88'><rect width='88' height='88' fill='${hex}'/><text x='44' y='50' font-family='sans-serif' font-size='13' fill='white' text-anchor='middle'>${label}</text></svg>`;
|
||||
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<string, AttentionItem["detail"]> = {
|
||||
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<string | null>(firstInline?.id ?? null);
|
||||
@@ -350,8 +341,8 @@ function Queue({
|
||||
</div>
|
||||
{count === 0 && snoozed.length === 0 && dismissed.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center rounded-xl border border-dashed border-border py-20 text-center">
|
||||
<div className="mb-4 rounded-full bg-green-500/10 p-4">
|
||||
<CheckCircle2 className="h-10 w-10 text-green-500" />
|
||||
<div className="mb-4 rounded-full bg-muted p-4">
|
||||
<CheckCircle2 className="h-10 w-10 text-muted-foreground" />
|
||||
</div>
|
||||
<p className="text-lg font-semibold text-foreground">You're all caught up</p>
|
||||
<p className="mt-1 flex items-center gap-1.5 text-sm text-muted-foreground">
|
||||
@@ -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: () => (
|
||||
<PhoneFrame>
|
||||
<Queue items={SHOWCASE} groupBy="type" />
|
||||
|
||||
Reference in new issue
Block a user