feat(composer): discovery-phase plugin from "I want an app that..." to tracker-loaded stories

Plugin pairs with Symphony — Composer's output (issues in the project's
tracker) is Symphony's input. State-aware /composer command walks the
user through 11 phases: concept, users (with workflow probing for hidden
actors), requirements/NFR/IA, design system, interaction design, flows,
mocks, architecture, stories, roadmap, load. Each phase produces a
reviewable artifact in docs/planning/ (or, for design and interaction,
a project-local skill — see below).

Architecture is intentionally LAST, after the spec is complete. Mocks
are JSX/TSX regardless of final stack and pass a "text feasibility"
audit — every visible string is categorized (static/computed/authored),
computed strings are traced to source data, and any string implying an
unspecified feature halts the commit.

Design system and interaction design are produced as PROJECT-LOCAL
SKILLS in .claude/skills/<slug>-design-system/ and
.claude/skills/<slug>-interaction-design/, not as docs in
docs/planning/. Skills auto-load via description matching whenever UI
work happens; a reference doc gets read once and drifts out of context.
The mocks skill imports tokens directly from the design-system skill
for a single source of truth across mocks and (eventually) real code.

ui-system is currently a PLACEHOLDER — captures the minimum needed for
mocks to proceed with <TBD: ...> markers as a return-pass worklist.
Full interview specification is the next pass.

Council touchpoints baked in: PM/skeptic/end-user after requirements,
end-user/a11y after mocks, full architecture council after architecture,
plus auto-additions (legal-triage on regulated data, privacy-advocate
on PII, finance-controller on money flows).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
movq
2026-04-29 21:03:40 -05:00
parent cd3032458c
commit 594fccc80a
25 changed files with 1595 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
# ADR-NNNN — <Title>
- **Status**: proposed | accepted | superseded by ADR-MMMM | deprecated
- **Date**: YYYY-MM-DD
- **Deciders**: <names / handles>
## Context
<!--
What's forcing this decision. Pull constraints from requirements.md,
nfr.md, ia.md, interaction-design.md. Two or three short paragraphs.
-->
## Decision
<!--
One sentence. The thing we are doing.
-->
We will …
## Alternatives considered
<!--
Each one with one paragraph: what it was, why we didn't pick it.
-->
### <Alternative A>
<why we didn't pick this>
### <Alternative B>
<why we didn't pick this>
## Consequences
<!--
What changes because of this decision. Positive AND negative. The
negative list is the more important one.
-->
**Positive**:
- <…>
**Negative / accepted costs**:
- <…>
**Open follow-ups**:
- <thing this decision implies we now have to figure out later>

View File

@@ -0,0 +1,42 @@
# Architecture
<!--
A response to the spec, not a setting. Written AFTER concept, users,
requirements, NFR, IA, design system, interaction design, flows, and
mocks are in place. Major decisions get their own ADR; this file is the
summary + index.
-->
## Constraints (extracted from upstream artifacts)
- <constraint> — *from <source>.md*
- <constraint> — *from <source>.md*
## Stack at a glance
| Layer | Choice | ADR |
|---|---|---|
| Frontend | <…> | [ADR-NNNN](adr/NNNN-<slug>.md) |
| Backend | <…> | [ADR-NNNN](adr/NNNN-<slug>.md) |
| Data store | <…> | [ADR-NNNN](adr/NNNN-<slug>.md) |
| Hosting / runtime | <…> | [ADR-NNNN](adr/NNNN-<slug>.md) |
| Auth | <…> | [ADR-NNNN](adr/NNNN-<slug>.md) |
| Observability | <…> | [ADR-NNNN](adr/NNNN-<slug>.md) |
## Key architectural decisions
<!--
One bullet per ADR with the heart of the decision. Full reasoning lives
in the ADR file.
-->
- **<Decision title>** — <one-line summary> ([ADR-NNNN](adr/NNNN-<slug>.md))
## Open questions
<!--
Decisions deferred. Each should have a trigger condition that flips it
into a real ADR.
-->
- <question> — *will decide when <trigger>*

View File

@@ -0,0 +1,16 @@
# Concept
<!--
One-paragraph north star. The thing this app *is*, in plain language.
Not features. Not architecture. The shape of the thing and who it's for.
-->
<TBD: one paragraph>
## Platform
<TBD: web / mobile / desktop / native iOS / native Android / CLI / library>
## Audience (one line)
<TBD: who, in one line — full personas live in users.md>

View File

@@ -0,0 +1,94 @@
---
name: <SLUG>-design-system
description: Design system for the <PROJECT NAME> project. Color tokens, typography, spacing, radii, shadows, motion durations, and component vocabulary. Load and apply whenever building UI for this project — generating mocks, writing components, picking colors, configuring Tailwind, styling native screens, building themes, designing CSS, or making any visual decision. The canonical visual reference for this project; if anything visible is being touched, this skill should be in context. Pairs with <SLUG>-interaction-design.
---
# <PROJECT NAME> design system
> **Status: <draft | reviewed | locked>**
> Generated by Composer's `ui-system` phase. Re-running that phase
> updates this skill (with confirmation).
## How to use this skill
Whenever you generate or edit UI for this project — a mock, a real
component, a theme, a CSS file — read this SKILL.md and the sidecar
files first, then use the tokens. Do not hardcode values. Do not
introduce new components without proposing them here first.
Sidecar files in this skill directory:
- `tokens.ts` — machine-readable token export. Import in mocks/code.
- `components.md` — component vocabulary with anatomy.
## Visual posture
<!-- One or two sentences describing the feel. "Quiet, dense, monochromatic with a single brand accent." Or "Playful, generous spacing, expressive type." -->
<TBD: visual posture>
## Color
### Brand
- Primary: `<#hex>` — <name>
- (Secondary, if any): `<#hex>` — <name>
### Neutrals
- Tone: <warm | cool | true>
- Scale: 50 / 100 / 200 / 300 / 400 / 500 / 600 / 700 / 800 / 900 / 950
### Semantic
| Role | Value |
|---|---|
| Success | `<#hex>` |
| Warning | `<#hex>` |
| Error | `<#hex>` |
| Info | `<#hex>` |
### Dark mode
<TBD: yes / no / system-following + token mapping>
## Typography
- **Display family**: <name>, weights <list>
- **Body family**: <name>, weights <list>
- **Scale base**: <px>, ratio <1.125 | 1.2 | 1.25 | 1.333>
- **Sizes**: xs / sm / base / lg / xl / 2xl / 3xl / 4xl
- **Line-height**: tight for display, normal for body, relaxed for long-form prose
See `tokens.ts` for exact px values.
## Spacing
- **Base unit**: <4px | 8px>
- **Scale (in base units)**: 0, 0.5, 1, 1.5, 2, 3, 4, 6, 8, 12, 16, 24
## Radii
- `sm`, `md`, `lg`, `pill`, `full` — values in `tokens.ts`.
## Shadows
- `xs / sm / md / lg / xl` — values in `tokens.ts`.
- (Or: "flat — no shadows" if the posture is flat.)
## Visual motion
- **Default duration**: <ms>
- **Default easing**: <named curve or cubic-bezier>
Behavioral motion (when things animate, what does and doesn't) lives
in the interaction-design skill.
## Components
See `components.md` in this directory for the full vocabulary and
anatomy. The TL;DR list:
- Button (primary / secondary / ghost / destructive / link)
- Input (text / textarea / select / checkbox / radio / toggle)
- Card / Surface
- (project-specific components from `ia.md`)
## TBD on return
- <TBD: anything the placeholder pass couldn't answer>

View File

@@ -0,0 +1,52 @@
# Component vocabulary
The named components for <PROJECT NAME>. Match names from `ia.md`
where applicable. Do not introduce new components without proposing
them here first.
This file is loaded automatically by the design-system skill — agents
generating UI should be aware of every component in this list and
prefer them to ad-hoc one-offs.
## Primitives
### Button
- **Variants**: primary | secondary | ghost | destructive | link
- **Sizes**: sm | md | lg
- **States**: default | hover | active | focus | disabled | loading
- **Anatomy**: optional leading icon · label · optional trailing icon
- **Motion**: <TBD>
### Input
- **Variants**: text | textarea | select | checkbox | radio | toggle
- **Sizes**: sm | md | lg
- **States**: default | focus | error | disabled | readonly
- **Anatomy**: label · field · helper text · error message
- **Validation pattern**: <TBD — see interaction-design skill>
### Card / Surface
- **Variants**: <flat | bordered | elevated — pick or list>
- **Anatomy**: <header? · body · footer?>
## Project-specific components
<!--
Pulled from ia.md. One block each, with anatomy and any state-specific
notes that should always apply when this component is rendered.
-->
### <Component name>
- **Purpose**: <one line>
- **Anatomy**: <slots / regions>
- **States**: <list>
- **Notes**: <anything load-bearing>
## Patterns to avoid
<!--
Things that come up often and should be vetoed. Saves the agent from
re-inventing them.
-->
- <TBD: e.g., "no nested cards", "no inline icons in body text", etc.>

View File

@@ -0,0 +1,101 @@
// Design tokens for <PROJECT NAME>.
// Generated by Composer's ui-system phase.
// Single source of truth for colors, type, spacing, radii, shadows, motion.
// Import this in mocks and (eventually) in real UI code; do not hardcode.
export const color = {
brand: {
primary: "<TBD: #hex>",
// secondary: "<TBD>",
},
neutral: {
50: "<TBD>",
100: "<TBD>",
200: "<TBD>",
300: "<TBD>",
400: "<TBD>",
500: "<TBD>",
600: "<TBD>",
700: "<TBD>",
800: "<TBD>",
900: "<TBD>",
950: "<TBD>",
},
semantic: {
success: "<TBD>",
warning: "<TBD>",
error: "<TBD>",
info: "<TBD>",
},
// dark mode tokens (if applicable):
// dark: { ... }
} as const;
export const fontFamily = {
display: "<TBD: name>, system-ui, sans-serif",
body: "<TBD: name>, system-ui, sans-serif",
mono: "ui-monospace, SFMono-Regular, monospace",
} as const;
export const fontSize = {
xs: "12px",
sm: "14px",
base:"16px",
lg: "18px",
xl: "20px",
"2xl":"24px",
"3xl":"30px",
"4xl":"36px",
} as const;
export const lineHeight = {
tight: 1.2,
normal: 1.5,
relaxed: 1.7,
} as const;
// Spacing scale in base units. Multiply by base (4 or 8) for px.
export const spacingBase = 4; // <TBD: 4 or 8>
export const spacing = {
0: 0,
0.5: 0.5 * spacingBase,
1: 1 * spacingBase,
1.5: 1.5 * spacingBase,
2: 2 * spacingBase,
3: 3 * spacingBase,
4: 4 * spacingBase,
6: 6 * spacingBase,
8: 8 * spacingBase,
12: 12 * spacingBase,
16: 16 * spacingBase,
24: 24 * spacingBase,
} as const;
export const radius = {
sm: "<TBD: px>",
md: "<TBD: px>",
lg: "<TBD: px>",
pill: "9999px",
full: "9999px",
} as const;
export const shadow = {
xs: "<TBD>",
sm: "<TBD>",
md: "<TBD>",
lg: "<TBD>",
xl: "<TBD>",
} as const;
export const motion = {
duration: {
fast: 150,
default: 250,
slow: 400,
},
easing: {
standard: "cubic-bezier(0.2, 0, 0, 1)",
accel: "cubic-bezier(0.4, 0, 1, 1)",
decel: "cubic-bezier(0, 0, 0.2, 1)",
},
} as const;

View File

@@ -0,0 +1,25 @@
# Flows
<!--
Named user journeys. One per workflow from users.md. The mocks skill
generates one mock per flow. Steps here are conceptual — every screen
is captured in the corresponding mock, not duplicated here.
-->
## <flow-id> — <Short title>
- **Actor**: <user-class from users.md>
- **Trigger**: <what kicks this off>
- **Preconditions**: <state that must be true>
- **Steps**:
1. <action / screen>
2. <…>
3. <…>
- **Success**: <observable outcome>
- **Failure modes**:
- <named failure> → <how it's handled>
- <…>
## <flow-id> — <Short title>

50
composer/templates/ia.md Normal file
View File

@@ -0,0 +1,50 @@
# Information architecture
<!--
The taxonomy file. Top-level concepts, how they relate, how they're
named. Mocks and architecture both look up here.
-->
## Top-level concepts
<!--
The nouns of the app. One block each.
-->
### <Concept name>
- **What it is**: <one-line definition>
- **Identifying field(s)**: <what makes one of these unique>
- **Key attributes**: <list>
- **Relationships**: <to other concepts — "has many X", "belongs to Y", "describes Z">
- **Lifecycle states**: <if it has lifecycle — draft / active / archived / etc.>
## Relationships
<!--
A small relationship diagram in plain text or list form. The shape of
the data, before any database is chosen.
-->
- <Concept A> 1—N <Concept B>
- <Concept B> N—N <Concept C> via <Concept Join>
## Vocabulary rules
<!--
Words used in two senses on the same screen will hurt. Names every
concept with one canonical word and lists synonyms to avoid.
-->
| Use | Don't use | Reason |
|---|---|---|
| <word> | <synonym> | <why we picked one> |
## Navigation top-level
<!--
The skeleton of how a user moves through the app. Not screens — the
conceptual map.
-->
- <section> → <subsection> → <subsection>

View File

@@ -0,0 +1,112 @@
---
name: <SLUG>-interaction-design
description: Interaction design for the <PROJECT NAME> project. State surfaces (modal vs inline vs drawer vs full-page), motion philosophy, input modes (touch/mouse/keyboard), breakpoints, i18n posture, empty/loading/error/offline state postures, time formatting, and keyboard policy. Load and apply whenever building UI behavior — modals, forms, navigation, transitions, gestures, error handling, async states, focus management. Pairs with <SLUG>-design-system.
---
# <PROJECT NAME> interaction design
> **Status: <draft | reviewed | locked>**
> Generated by Composer's `ui-system` phase. Re-running that phase
> updates this skill (with confirmation).
## How to use this skill
When building UI *behavior* — anything beyond static styling — read
this skill first. Don't invent new state-surface patterns, motion
durations, or empty-state postures. They're declared once here and
reused everywhere.
For visual tokens (color, type, spacing), see the paired
`<SLUG>-design-system` skill.
## Input modes
| Mode | Status |
|---|---|
| Touch | <TBD: primary | supported | not supported> |
| Mouse | <TBD> |
| Keyboard | <TBD> |
| Voice | <TBD: usually "not supported" in v1> |
## State surfaces
One pattern per action class. Used everywhere.
| Action class | Pattern | Notes |
|---|---|---|
| Quick edit (single field) | inline edit | <TBD> |
| Multi-field create / edit | <drawer | modal — pick one> | <TBD> |
| Confirm destructive | modal | always |
| Settings / configuration | full-page route | — |
| Top-level navigation | <sidebar | tab bar | header nav> | <TBD> |
| Detail-from-list | <full route | side panel> | <TBD> |
## Motion
- **Posture**: <minimal | restrained | expressive>
- **Default duration / easing**: see design-system tokens
- **Reduced motion**: respected via `prefers-reduced-motion` everywhere; fallbacks declared per pattern below
- **When things animate**:
- Page/route transitions: <yes/no, how>
- List item enter/exit: <yes/no, how>
- Modal/drawer open/close: <yes/no, how>
- State changes (toggle, expand): <yes/no, how>
## Breakpoints
| Breakpoint | Range | Primary? |
|---|---|---|
| Phone | < <px> | <TBD> |
| Tablet | <px> <px> | <TBD> |
| Desktop | ≥ <px> | <TBD> |
**Primary design target**: <which one — "mobile-first", "desktop-first", "fluid">
## i18n
- **Locales at launch**: <list — often just English in v1>
- **RTL support**: <yes | no | planned>
- **String length budget**: assume +30% over English for German/French translations
- **Pluralization**: <TBD: ICU MessageFormat / library / manual>
## State postures
One declared posture per state, used everywhere. No inventing on a per-screen basis.
### Empty
- **Visual**: <icon + heading + body + optional CTA>
- **Tone**: <encouraging | neutral | matter-of-fact>
- **CTA**: <when present, what kind of action>
### Loading
- **Default pattern**: <skeleton | spinner | blocking>
- **When to use which**: <criteria — "skeleton for list/card containers, spinner for inline buttons, blocking only for explicit user-initiated actions">
- **Threshold for showing loading at all**: <ms — usually 200300ms; below that, just let it pop>
### Error
- **Surface**: <inline | toast | dialog — by error class>
- **Recovery**: <retry button | clear path forward | "contact support" only as last resort>
- **Copy tone**: <plain language, name what happened, name what to do next>
### Offline
- **Behavior**: <feature subset that works | banner only | "go online to continue">
- **Sync semantics**: <when reconnected, what happens>
## Time formatting
- **Relative time** for events within: <threshold — e.g., < 7 days>
- **Absolute time** otherwise, format: <pattern — e.g., "MMM d, yyyy h:mm a">
- **Time zones**: <user-local | fixed (which?) | configurable>
- **Date-only vs date+time**: <when each is shown>
## Keyboard
- **Global shortcuts**: <list, or "none in v1">
- **Within-modal**: `Esc` closes; focus trap while open; restore focus on close
- **Tab order**: declared per-flow as needed; focus visible always (per design-system)
- **Forms**: `Enter` submits; `Esc` cancels destructive
- **Lists**: <arrow-key nav? | not in v1>
## TBD on return
- <TBD: anything the placeholder pass couldn't answer>

49
composer/templates/nfr.md Normal file
View File

@@ -0,0 +1,49 @@
# Non-functional requirements
<!--
Every entry needs a NUMBER or NAMED THRESHOLD. "Fast" is not an NFR;
"p95 interaction < 100ms on mid-tier mobile" is.
-->
## Performance
- <metric>: <threshold> at <load>
- <metric>: <threshold> at <load>
## Scale
- Users: <expected at launch> → <year-1 target>
- Data: <volume> at <year-1 target>
- Throughput: <req/s, events/s, etc.>
## Availability
- Target uptime: <%>
- RTO / RPO: <values, if applicable>
## Security
- Authentication: <method>
- Authorization model: <roles / capabilities / none>
- Sensitive data: <classifications>
- Compliance frameworks in scope: <SOC2 / GDPR / HIPAA / none / TBD>
## Accessibility
- Standard: WCAG 2.2 AA (floor — see interaction-design.md for specifics)
## Browser / device support
- <list>
## Offline behavior
- <does the app function offline? what does it do when it can't reach the network?>
## Data retention & deletion
- <policy: how long, who can delete, what's the cascade>
## Observability
- <what we log / measure / alert on, at the requirement level>

View File

@@ -0,0 +1,24 @@
# Design system
The design system for this project is implemented as a project skill so
it loads automatically into context whenever UI work is in progress —
not just when someone remembers to read a file.
**Skill location**: `.claude/skills/<SLUG>-design-system/`
```
.claude/skills/<SLUG>-design-system/
├── SKILL.md ← canonical visual reference
├── tokens.ts ← machine-readable tokens for mocks and code
└── components.md ← component vocabulary and anatomy
```
To revise: re-run `/composer` (it'll detect the design phase and ask
whether to update), or edit the files directly. They're version-controlled.
To verify the skill is loading: open a session in this repo, run
`/agents`, and confirm `<SLUG>-design-system` appears as a project-local
skill.
The paired interaction-design skill lives at
`.claude/skills/<SLUG>-interaction-design/`.

View File

@@ -0,0 +1,18 @@
# Interaction design
The interaction-design system for this project is implemented as a
project skill so it loads automatically into context whenever UI behavior
is being built (modals, forms, transitions, error states, focus, etc.).
**Skill location**: `.claude/skills/<SLUG>-interaction-design/`
```
.claude/skills/<SLUG>-interaction-design/
└── SKILL.md ← state surfaces, motion, input modes, i18n, state postures
```
To revise: re-run `/composer`, or edit the SKILL.md directly. It's
version-controlled.
The paired design-system skill lives at
`.claude/skills/<SLUG>-design-system/`.

View File

@@ -0,0 +1,34 @@
# Requirements
<!--
Hard / soft / non-goals. Functional requirements only — non-functional
thresholds live in nfr.md. Push back on "and" lists; every conjunction
is usually two requirements pretending to be one.
-->
## Hard
<!--
If the app doesn't do this, it isn't useful.
-->
- <one requirement per bullet, no "and">
## Soft
<!--
Would be nice. Deferable. Cut first when scope tightens.
-->
- <one per bullet>
## Non-goals
<!--
Explicit "we will say no when this comes up." This list is as
load-bearing as the hard list — it's how you cut scope without
re-litigating.
-->
- <thing we are not building>
- <another>

View File

@@ -0,0 +1,33 @@
# Roadmap
<!--
Clusters sequenced into milestones. The first milestone is the smallest
version that's still useful. Push back on bundling soft requirements
into M1 — they belong in later milestones.
-->
## M1 — <name>
**Goal**: <one-line. Smallest version that's useful.>
**Stories**:
- <story-id> — <title>
- <story-id> — <title>
**Out of scope for M1**:
- <thing that's tempting to include but isn't required for "useful">
## M2 — <name>
**Goal**: <…>
**Stories**:
- <…>
## Backlog
<!--
Stories not yet sequenced. Don't promise these to anyone.
-->
- <story-id> — <title>

View File

@@ -0,0 +1,31 @@
# Stories
<!--
Flat list ready to load into the tracker. Don't over-decompose at
this phase — granularity shifts as the team starts working. Each
story has acceptance criteria; the tracker is the source of truth
once loaded, and this file becomes the local↔tracker map.
-->
## <Cluster name>
### <story-id> — <Title>
- **As a** <user-class>
- **I want** <behavior>
- **So that** <outcome>
- **Acceptance criteria**:
- <observable criterion>
- <observable criterion>
- **Size**: <S / M / L / XL>
- **Depends on**: <other story-ids, if any>
- **Mock**: <mocks/<flow>.tsx if relevant>
- **Tracker ID**: _(filled in by /composer-load)_
### <story-id> — <Title>
## <Cluster name>

View File

@@ -0,0 +1,55 @@
# Users & workflows
<!--
Every actor that touches the system. Humans AND non-humans:
- other humans (collaborators, viewers, admins)
- AI agents
- integrations / webhooks
- scheduled jobs
- the data / export format itself
- future-self
- backups
Workflows extracted by asking "what do you do?" and then "who or what
is on the other side of each verb?"
-->
## Primary user
**<Name or role>**
- **Context**: <what they're doing when they reach for this app>
- **Job to be done**: <what success looks like for them>
- **Frequency**: <daily / weekly / occasional>
## Other user-classes
<!--
List every other actor surfaced by the workflow probe. One block each.
Non-human classes (agents, integrations, format) get the same treatment
as humans.
-->
### <user-class name>
- **Type**: <human / agent / integration / system / format>
- **Reads**: <what they read>
- **Writes**: <what they write, if anything>
- **Notes**: <anything special about their access pattern or constraints>
## Anti-personas
<!--
Who this app is NOT for. Naming these saves stories later, because
"can't we also support X?" gets a clean answer.
-->
- <type of user/use case explicitly excluded>
- <another>
## Workflows
<!--
Verb-keyed list. Each workflow is short. Detailed step-by-step lives
in flows.md.
-->
- **<verb>** — <one-line description, who's on each side>
- **<verb>** — …