Decoration inventory, rebuild work, and the secondary-accent surface pass.
Read-mostly audit surface tracking keep/collapse/defer/delete decisions.
20 atoms in components/ui/decorations/** plus 3 compositions in board-decorations.tsx. (Code keeps the decoration identifier; human-facing copy uses embellishment.) Verdicts locked 2026-04-17 — see docs/embellishment-vocabulary.md.
17 callers across 15 files. Chat cluster dominates but 10 non-chat callers justify public access.
Batch 6 ChatSurface will consume PaperGrain internally for chat surfaces only. Non-chat callers (communities, polaroid-card, app routes) keep the direct import. No deprecation.
CategoryStamp + ConditionStamp → <Stamp variant=rectangular|circular>. CommunityStamp stays separate.
The SVG-generated pair shares displacement-filter logic and visual language. CommunityStamp is an image wrapper — different primitive class. Forcing it into the union would add a meaningless src prop to SVG variants.
WashiTape, FilmStrip, StringConnector, WoodGrainTexture, PhotoVignette retained.
Held for launch embellishment palette. Each represents a unique material vocabulary slot with no replacement candidate. Revisit in Batch 16 cleanup if still zero-caller post-launch.
Fasten one surface to another. Lives on top of the composition it anchors.
Core scrapbook attachment. Used across notebook steps, trade chains, chain discovery.
Post/offer detail sheets. Three variants already consolidated inside the atom.
Zero production callers. Held for launch embellishment palette.
Follow-up: Revisit in Batch 16 if still zero-caller post-launch.
Name, classify, or authenticate. Adds identity ink, not structure.
Trust tier badges + trade confirmation seals. Status prop handles the color story.
Distinct primitive class (postage indicia, serrated edges). Not part of Stamp collapse.
SVG rubber stamp with displacement filter. Collapses into <Stamp variant="rectangular"> in Batch 4.
Follow-up: Batch 4: ship unified <Stamp variant="rectangular|circular">, migrate 4 call sites.
SVG circular postmark with displacement filter. Collapses into <Stamp variant="circular"> in Batch 4.
Follow-up: Batch 4: retain condition→color map as a table consumed by the unified atom.
Image primitive (next/image wrapper for AI-generated PNGs). Different mechanism from SVG stamps — does NOT collapse.
Surface perimeter language. Defines where paper ends.
Action-strip pattern across 6 routes. Widest edge-language primitive.
Material fill. Runs underneath content, never on top.
Load-bearing. Chat cluster (5 files) + communities (4 files) + polaroid/app routes. Stays public — Batch 6 ChatSurface consumes it internally, non-chat callers keep direct access.
CorkBoard + CorkStrip primitives + MarketTableHero (broken-board hero). Material foundation for the board language.
Only cork-board.tsx imports it. Part of cork/wood material pair — held for launch palette.
Follow-up: Revisit in Batch 16 if still single-caller post-launch.
Only polaroid-card references it. Part of photo-material vocabulary — held for launch palette.
Follow-up: Revisit in Batch 16 if still single-caller post-launch.
Visual line between two points. Carries a relationship.
Zero production callers. Held for launch palette (connector vocabulary has no other candidate).
Follow-up: Revisit in Batch 16 if still zero-caller post-launch.
Standalone paper artefact that carries its own content block.
Filters, empty states, OfferingCard. Accent variants already consolidated.
trust-metrics.tsx only. Low callers but unique visual surface (index card material).
Only its own index entry. Held for launch palette (media fragment vocabulary).
Follow-up: Revisit in Batch 16 if still zero-caller post-launch.
Interactive affordance or app-wide scaffolding. Not strictly decorative.
GlobalAiWidget's erase affordance. Single functional consumer but load-bearing.
Infrastructure primitive — renders invisible <defs> once at the app root. Not visually testable in isolation.
App-wide <defs> injected by layout.tsx. Infrastructure, not a user-facing atom.
Pre-composed embellishments built from primitives. Audited for consumer count only.
Composition — interactive search surface. Requires state + handlers.
Exported from board-decorations.tsx but zero JSX callers. Composition around StickyNote.
Follow-up: Verify before Batch 16 whether a planned board-search surface still intends to consume this.
Composition — board banner with inline pushpins + handwriting headline.
Exported from board-decorations.tsx but zero JSX callers.
Follow-up: Verify before Batch 16 whether a planned board-title surface still intends to consume this.
Composition — empty-state surface with clear-filter CTA. Requires handler.
Exported from board-decorations.tsx but zero JSX callers.
Follow-up: Verify before Batch 16 whether a planned empty-board surface still intends to consume this.
Approved launch atoms after the inventory verdicts were locked.
Secondary-accent surface contract and its migrated usage patterns.
7 live sites share a 'secondary-accent tinted notice/panel' shape that lives outside Card's current 7-intent grammar. Two proposals: extend Card to 8 intents (Option A), or ship a dedicated <SecondaryPanel> named pattern (Option B). Each of the 5 representative shapes below renders in all 3 modes side-by-side.
Emphasized tint (10% bg / 20% border). Icon + strong title + badge row. Stacked layout. Anchors Option B's slot API strongly.
Subtle tint (5% bg / 20% border). Icon-start + strong title + bulleted list. Also fits Option B's slot API cleanly.
AI has some questions:
Emphasized tint. Icon + strong title + caption stack. Covers both SeekingDetailSheet sites' emphasized-tint usage.
Check your posts to see if you have what they want.
Check your posts to see if you have what they want.
Check your posts to see if you have what they want.
Subtle tint. Avatar + identity + trailing button — NOT an icon+title shape. This is the hardest case for Option B: the slot grammar collapses. Option A treats it as a plain Card container.
Wants this item
Wants this item
Wants this item
Emphasized tint. Compact horizontal row with icon + strong + muted copy. Padding is tighter than Card's cozy/compact densities — Option A needs a className override here; Option B could ship a dedicated 'banner' density.
Emphasized tint. Icon + single caption line, no strong title. Option B handles this with the title slot omitted.
This looks like a multi-party trade. Chain trading is available for complex swaps.
This looks like a multi-party trade. Chain trading is available for complex swaps.
This looks like a multi-party trade. Chain trading is available for complex swaps.
Per-criterion comparison, followed by a recommendation. Sign off on one option to proceed — the mocks above will be replaced by the real primitive code only after explicit approval.
Eight criteria, per-row winner. Weight each criterion yourself — the “winner” column is one reading, not a verdict.
| Criterion | Option A — Card | Option B — SecondaryPanel | Edge |
|---|---|---|---|
| File count | +0 files (extend existing Card) | +1 file (components/patterns/secondary-panel.tsx) | Option A |
| API cohesion | Follows existing pattern (accent/destructive/warning all work this way) | Introduces parallel type (tone=subtle|emphasized vs softness=solid|soft) | Option A |
| Closed enum growth | CardIntent: 7 → 8 | SecondaryPanelTone: 2 values, new enum surface | tie |
| Handles non-icon+title shapes (Shape 4 seeker row) | Natural — Card is a container, consumer fills it | Awkward — slot grammar collapses; icon/title become optional | Option A |
| Prevents future drift (raw bg-secondary-accent-*) | Card's anti-slop variant lint covers it | Stronger: closed-enum slot API blocks freestyle content | Option B |
| Consumer compression | intent='secondary' softness='soft' → 2 props | tone='subtle' icon={...} title='...' → 3-4 props (more declarative) | Option B |
| Padding flexibility (Shape 5 inline banner px-3 py-2) | Needs density hack or className override (erodes closed API) | Could ship with banner density tier | Option B |
| Material coverage (paper/cork/vintage) | Inherits — any material × secondary intent works | Would need to duplicate Card material grammar or skip it | Option A |
Secondary-accent is a color, not a layer or material. Card already owns the intent-as-color-tint pattern for accent / destructive / warning / success / info. Adding secondaryfills the exact slot this family occupies. It reuses Card's existing softness / material / density grammar and inheritsintentTintStyle() + INT_CLASS_SOFT/SOLID without duplicating them.
Option B wins two criteria — drift prevention strength and consumer compression — but pays for both by duplicating Card's material + softness work and falling apart on Shape 4 (avatar+name+cta row has no icon/title slot to fill). Those two Option B wins collapse if we ship strict lint for bg-secondary-accent-* alongside Option A, which is already cheap to add.
Proposed migration plan if Option A is chosen: (1) add secondary to CardIntent in card.tsx; (2) update intentTintStyle to route the --secondary-accent token; (3) migrate all 7 sites to <Card intent="secondary">; (4) add an ESLint rule blocking raw bg-secondary-accent-* / border-secondary-accent-* panel classes outside card.tsx + lib/brand.ts.