Remove internal project files from tracking and update .gitignore

Stop tracking .claude/, CLAUDE.md, ARCHITECTURE.md, and plans/ — these are
local-only working files for Claude Code and should not be on the shared
Gitea remote. Files remain on disk, only removed from git index.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-21 14:05:36 +10:00
parent 07be9d7314
commit 3e7de78721
16 changed files with 6 additions and 1498 deletions

View File

@@ -1,67 +0,0 @@
{
"permissions": {
"allow": [
"Bash(npm run dev)",
"Bash(npm run build)",
"Bash(npm run storybook)",
"Bash(npm run lint)",
"Bash(npx tsc --noEmit)",
"Bash(npx storybook *)",
"Bash(npx prettier *)",
"Bash(npx vitest *)",
"Bash(node --check *)",
"Bash(npm install *)",
"Bash(npm list *)",
"Bash(npm outdated *)",
"Bash(git status *)",
"Bash(git log *)",
"Bash(git diff *)",
"Bash(git add *)",
"Bash(git commit *)",
"Bash(git branch *)",
"Bash(git checkout *)",
"Bash(git push *)",
"Bash(git pull *)",
"Bash(git remote *)",
"Bash(kill %*)",
"Bash(lsof -i *)",
"WebSearch",
"WebFetch(domain:storybook.js.org)",
"WebFetch(domain:tailwindcss.com)",
"WebFetch(domain:react.dev)",
"WebFetch(domain:vitejs.dev)",
"WebFetch(domain:vite.dev)",
"WebFetch(domain:github.com)",
"WebFetch(domain:npmjs.com)",
"WebFetch(domain:figma.com)",
"WebFetch(domain:help.figma.com)",
"WebFetch(domain:developers.figma.com)",
"WebFetch(domain:developer.mozilla.org)",
"mcp__figma__whoami",
"mcp__figma__get_metadata",
"mcp__figma__get_libraries",
"mcp__figma__use_figma",
"mcp__figma__get_screenshot",
"mcp__figma__search_design_system",
"mcp__figma__get_variable_defs",
"mcp__figma__get_design_context",
"mcp__figma__get_code_connect_map",
"mcp__figma__add_code_connect_map",
"mcp__figma__get_code_connect_suggestions",
"mcp__figma__send_code_connect_mappings"
]
},
"hooks": {
"PostToolUse": [
{
"matcher": "mcp__figma__use_figma",
"hooks": [
{
"type": "command",
"command": "echo 'QA: Verify style/variable bindings on all returned node IDs.'"
}
]
}
]
}
}

View File

@@ -1,71 +0,0 @@
---
name: component-rules
description: "Triggers when building any UI element in Figma — 'create a card', 'build a nav', 'add a section', 'make a component'. Enforces library-first component lookup, correct Auto Layout structure, and semantic node naming. For visual property binding (colors, text styles, spacing values), defer to figma-style-binding."
disable-model-invocation: false
---
# Component Rules
Governs how Claude constructs UI in Figma. Supplements `figma-generate-design`. For visual property binding, defer to `figma-style-binding`.
---
## Rule 1 — Library-first hierarchy
Before building anything, resolve the component source in this order:
```
1. search_design_system → importComponentByKeyAsync → createInstance()
2. Local file scan → createInstance()
3. Build from scratch — ONLY if nothing matches
```
Never rebuild primitives the DS provides: Button, Input, Checkbox, Toggle, Badge, Tag, Avatar, Icon, Tab, Breadcrumb, Toast, Alert, Spinner.
```js
// Library import
const comp = await figma.importComponentByKeyAsync("key_from_search");
const instance = comp.createInstance();
parent.appendChild(instance);
// For component sets (variants)
const set = await figma.importComponentSetByKeyAsync("key");
const instance = set.defaultVariant.createInstance();
```
---
## Rule 2 — Auto Layout
Every container must use Auto Layout. Property order matters:
1. Set `layoutMode` FIRST (`"VERTICAL"` or `"HORIZONTAL"`)
2. Set `layoutSizingHorizontal` / `layoutSizingVertical` AFTER `appendChild`
3. Set `layoutMode` BEFORE any `setBoundVariable` call
| Goal | primaryAxisSizing | counterAxisSizing | layoutSizingH | layoutSizingV |
|---|---|---|---|---|
| Hug both | AUTO | AUTO | HUG | HUG |
| Fixed card | FIXED | FIXED | FIXED | FIXED |
| Full-width section | AUTO | FIXED | FILL | HUG |
---
## Rule 3 — Node naming
Name every node semantically with slash hierarchy. Never leave defaults.
```
Card / Container Card / Title Card / Body Card / Action Row
Hero / Background Nav / Link Group Button / Primary
```
---
## Rule 4 — Incremental building
One section per `use_figma` call. Validate via screenshot after each step. Return all created/mutated node IDs:
```js
return { created: { card: card.id, title: title.id } };
```

View File

@@ -1,117 +0,0 @@
---
name: figma-preflight
description: "Triggers on 'let's start', 'begin', 'preflight', 'start the session', or when a Figma file URL is first shared. Verifies MCP connection, reads CLAUDE.md, audits connected libraries, and loads a Token Map of all Styles and Variables — required before any design work."
disable-model-invocation: false
---
# Figma Preflight
Run at the start of every design session. Do NOT start design work until all steps pass.
**Prerequisite:** Load `figma-use` skill before any `use_figma` call.
---
## Step A — Connection + Config (parallel)
Run these two in parallel:
1. **MCP Connection:** Call `mcp__figma__whoami`. Must return user email and plan. Fail → stop, re-authenticate.
2. **CLAUDE.md:** Read CLAUDE.md. Extract Figma file URL (required — stop if missing), font families, session goal. If fonts field is a placeholder, auto-populate after Step C using STRING variables starting with "Family".
---
## Step B — File + Libraries (parallel)
Parse `fileKey` from the Figma URL, then run in parallel:
1. **File Access:** Call `get_metadata` with extracted nodeId and fileKey. Must return file name and pages.
2. **Libraries:** Call `get_libraries` with fileKey. Store subscribed libraries as **Library Registry** (name, libraryKey, description). These enable `search_design_system` to find library styles and components during design work.
---
## Step C — Styles + Variables + Components (single use_figma call)
Combine all three inventories in one script:
```javascript
const textStyles = await figma.getLocalTextStylesAsync();
const paintStyles = await figma.getLocalPaintStylesAsync();
const collections = await figma.variables.getLocalVariableCollectionsAsync();
const variables = await figma.variables.getLocalVariablesAsync();
const grouped = {};
for (const v of variables) {
const key = v.resolvedType;
if (!grouped[key]) grouped[key] = [];
grouped[key].push({ name: v.name, scopes: v.scopes });
}
const components = {};
for (const page of figma.root.children) {
await figma.setCurrentPageAsync(page);
const sets = page.findAll(n => n.type === "COMPONENT_SET");
const solos = page.findAll(n => n.type === "COMPONENT" && n.parent.type !== "COMPONENT_SET");
if (sets.length > 0 || solos.length > 0) {
components[page.name] = {
sets: sets.map(c => c.name),
solos: solos.map(c => c.name).slice(0, 15),
};
}
}
return {
textStyles: textStyles.map(s => s.name),
paintStyles: paintStyles.map(s => s.name),
collections: collections.map(c => c.name),
variableCount: variables.length,
byType: Object.fromEntries(
Object.entries(grouped).map(([type, vars]) => [type, vars.map(v => v.name)])
),
components
};
```
Store **names only** in context. IDs are looked up on-demand during design work. Library styles/variables are discovered via `search_design_system`.
---
## Token Map
After Step C, derive a semantic index from variables grouped by `scopes`:
| Role | Scope | Example names |
|---|---|---|
| Background fill | `FRAME_FILL`, `SHAPE_FILL` | `background/surface`, `color/neutral-100` |
| Text color | `TEXT_FILL` | `text/primary`, `color/neutral-900` |
| Border / stroke | `STROKE_COLOR` | `border/default`, `color/neutral-300` |
| Gap | `GAP` | `gap/sm`, `spacing/xxs` |
| Padding | `PADDING` | `padding/md`, `spacing/section-xl` |
| Border radius | `CORNER_RADIUS` | `radius/sm`, `radius/full` |
---
## Status Report
```
✅ MCP Connection — [name] ([email]) · [plan]
✅ CLAUDE.md — Font: [primary] / [code] · Goal: [goal]
✅ Figma File — [file name] · [N] pages
✅ Libraries — [N] connected: [names]
✅ Styles — [N] text + [N] paint
✅ Variables — [N] across [N] collections
✅ Components — [N] sets across [N] pages
── Token Map ──────────────────────────────
Background : [names]
Text : [names]
Border : [names]
Gap : [names]
Padding : [names]
Radius : [names]
────────────────────────────────────────────
Ready to design.
```
If any step fails, output ❌ with error and stop.

View File

@@ -1,137 +0,0 @@
---
name: figma-style-binding
description: "Triggers on any visual property change in Figma — creating text, setting colors, adjusting spacing/padding/gap/radius. Enforces that ALL values bind to Figma Styles or Variables, never hardcoded. Includes post-write QA verification."
disable-model-invocation: false
---
# Style Binding + QA
Every visual value must come from the design system. Supplements `figma-generate-design`. Prerequisite: `figma-preflight` must have run this session.
---
## Binding Hierarchy
For any visual property, follow this order. Stop at the first match.
```
1. Connected Library → search_design_system → import → apply
2. Local Style → Style Registry → apply by ID
3. Local Variable → Variable Registry → apply by ID
4. Gap found → Report to user, wait for decision
```
---
## Text
Every text node must use `textStyleId`. Individual font properties (`fontSize`, `fontFamily`, etc.) are forbidden.
```js
const style = await figma.getStyleByIdAsync("<id>");
await figma.loadFontAsync(style.fontName);
node.textStyleId = "<id>";
```
If no local style matches, search libraries via `search_design_system`. If no match anywhere:
```
⚠️ Text style gap: no style for "[role]". Available: [top 5]. Use closest, or add missing style?
```
---
## Color Fills
Every fill/stroke must bind to a COLOR Variable (preferred, supports theming) or Paint Style.
```js
// Variable binding (preferred)
const variable = await figma.variables.getVariableByIdAsync("<id>");
const fill = { type: "SOLID", color: { r: 0, g: 0, b: 0 } };
node.fills = [figma.variables.setBoundVariableForPaint(fill, "color", variable)];
// Paint Style binding
node.fillStyleId = "<id>";
```
Never use raw `{ r, g, b }` without a binding.
---
## Spacing, Padding, Gap, Radius
Bind to FLOAT Variables. `layoutMode` must be set BEFORE `setBoundVariable`.
```js
node.setBoundVariable("paddingTop", spacingVar);
node.setBoundVariable("paddingBottom", spacingVar);
node.setBoundVariable("paddingLeft", spacingVar);
node.setBoundVariable("paddingRight", spacingVar);
node.setBoundVariable("itemSpacing", spacingVar);
node.setBoundVariable("cornerRadius", radiusVar);
```
Spacing can fall back to raw values temporarily with user confirmation. Color and text cannot.
---
## Forbidden / Required
| Forbidden | Required |
|---|---|
| `node.fontSize = 24` | `node.textStyleId = id` |
| `node.fills = [{ type: "SOLID", color: { r: .2, g: .4, b: 1 } }]` | Variable or Style binding |
| `node.paddingLeft = 16` | `node.setBoundVariable("paddingLeft", var)` |
| `node.cornerRadius = 8` | `node.setBoundVariable("cornerRadius", var)` |
| Creating a Button from scratch | `importComponentByKeyAsync` from library |
---
## QA Verification
After every `use_figma` call that creates or modifies nodes, run this verification on the returned node IDs:
```javascript
const nodeIdsToAudit = [/* paste returned IDs */];
const results = [];
for (const id of nodeIdsToAudit) {
const node = await figma.getNodeByIdAsync(id);
if (!node) { results.push({ id, status: "NOT_FOUND" }); continue; }
const checks = [];
if (node.type === "TEXT") {
checks.push({ prop: "textStyleId", bound: !!node.textStyleId });
}
if ("fills" in node && Array.isArray(node.fills) && node.fills.length > 0) {
const bound = !!node.fillStyleId || (node.boundVariables?.fills?.length > 0);
checks.push({ prop: "fills", bound });
}
if ("layoutMode" in node && node.layoutMode !== "NONE") {
for (const p of ["paddingLeft","paddingRight","paddingTop","paddingBottom","itemSpacing"]) {
if (p in node) checks.push({ prop: p, bound: !!(node.boundVariables && p in node.boundVariables) });
}
}
if ("cornerRadius" in node && node.cornerRadius > 0) {
checks.push({ prop: "cornerRadius", bound: !!(node.boundVariables && "cornerRadius" in node.boundVariables) });
}
const failed = checks.filter(c => !c.bound);
results.push({ id, name: node.name, type: node.type, status: failed.length === 0 ? "PASS" : "FAIL", failed: failed.map(c => c.prop) });
}
return { auditResults: results };
```
**If FAIL:** Fix each unbound property using the binding rules above, then re-audit. Do not proceed to the next design step until all pass.
**Report format:**
```
✅ All [N] nodes passed.
// or
❌ FAIL "Card" (FRAME) — paddingTop, cornerRadius unbound. Fixing...
```

View File

@@ -1,88 +0,0 @@
---
name: reference-interpreter
description: "Triggers when user shares a screenshot, image, URL, or design description — or says 'analyze this', 'make a brief', 'interpret this reference'. Outputs a structured Design Brief mapping visual intent to design system tokens. Waits for 'confirmed' before designing."
disable-model-invocation: false
---
# Reference Interpreter
Analyze a reference (screenshot, URL, or description) and produce a **Design Brief** that maps visual observations to design system tokens. Output the Brief, then **stop and wait for user confirmation** before building anything.
Prerequisite: `figma-preflight` should have run so the Token Map and Style Registry are available.
---
## Phase 1 — Analyze
Examine the reference across these dimensions:
1. **Layout** — structure, columns, section heights, grid width, alignment
2. **Typography** — heading/body hierarchy, weight contrast, tracking
3. **Color** — dark/light sections, accent usage, neutral dominance
4. **Spacing** — generous vs compact, section padding, internal gap
5. **Visual Anchor** — what draws the eye: large type, hero image, illustration
6. **Components** — what UI elements are visible: cards, buttons, forms, nav
---
## Phase 2 — Map to Design System
For each observation, identify the specific Token or Style from the session's Token Map:
```
"Large dark headline" → Text Style: heading/h1 · Color: text/primary
"Neutral section bg" → Variable: background/surface-2
"Tight card spacing" → Gap: gap/xs · Padding: padding/sm
```
If no token exists, flag it:
```
⚠️ Gap: [observation] — no matching token. Options: (a) nearest match: [name], (b) add token first.
```
---
## Phase 3 — Output the Design Brief
```
## Design Brief
**Reference**: [source]
**Section**: [what this covers]
**Aesthetic**: [3-5 keywords]
### Layout
[structure, height, alignment]
### Typography
- Heading: [Style name] — [why]
- Body: [Style name]
### Colors
- Background: [Variable] — [context]
- Text: [Variable]
- Accent: [Variable] — [used for]
### Spacing
- Section padding: [Variable]
- Internal gap: [Variable]
### Components needed
- [Name]: [from library? source]
### Gaps
- [Gap description] — awaiting decision
- (none) [if all mapped]
```
---
## Phase 4 — Wait
Output exactly:
```
Brief complete. Type `confirmed` to begin building, or tell me what to adjust.
```
Do NOT call `use_figma` or place any nodes until user types `confirmed`.

6
.gitignore vendored
View File

@@ -25,3 +25,9 @@ dist-ssr
*storybook.log
storybook-static
# Claude Code (local-only project files)
.claude/
CLAUDE.md
ARCHITECTURE.md
plans/

View File

@@ -1,148 +0,0 @@
# ARCHITECTURE.md — SDC-Frontend
This is the living architecture document for the SDC-Frontend design system. All structural decisions are recorded here. Update this document when the architecture evolves — never let the codebase and this document drift apart.
---
## 1. Overview
SDC-Frontend is a React component library and design system that will serve as the frontend for the Research Synthesiser. It is built in phases:
1. **Design system** — tokens, primitives, composite components
2. **Application screens** — the synthesiser UI rebuilt on top of the design system
---
## 2. Token Pipeline
```
Figma (design tool)
↓ Figma MCP / get_variable_defs
src/tokens/tokens.css (@theme block)
↓ Tailwind CSS v4 reads @theme
↓ Generates utility classes + CSS custom properties
Components (use Tailwind utilities or var() references)
↓ Rendered in
Storybook (visual verification)
```
### Token Layers
Tokens are structured in three layers:
1. **Palette** — raw colour values (`--color-blue-01`, `--color-grey-03`). Not used directly in components.
2. **Semantic** — purpose-based aliases (`--color-primary`, `--color-error`, `--color-text`). General UI usage.
3. **Form control** — shared interactive-state tokens for all form components (`--color-control-border`, `--color-control-checked`, `--color-control-label`, etc.). Ensures consistent styling across Input, Checkbox, Radio, Switch, Select, and future form primitives.
### Token Categories
- **Palette colours**: `--color-{palette}-{shade}` (e.g., `--color-blue-01`, `--color-grey-03`)
- **Semantic colours**: `--color-{purpose}` (e.g., `--color-primary`, `--color-error`, `--color-text`)
- **Form control colours**: `--color-control-{role}` (e.g., `--color-control-border`, `--color-control-checked`)
- **Radii**: `--radius-*` (sm, default, lg, full)
- **Shadows**: `--shadow-*` (default, md)
### How Tailwind v4 @theme Works
Declaring `--color-primary: #2563eb` inside `@theme` in `tokens.css` automatically generates utilities like `bg-primary`, `text-primary`, `border-primary`. No JavaScript config file needed — the CSS file is the config.
---
## 3. Component Taxonomy
### `src/components/ui/` — Primitives
Atomic, reusable building blocks. Each is self-contained with no domain logic.
- Button, Input, Checkbox, Radio/RadioGroup, Switch
- Textarea, Select
- Card, Badge, Tag
- Dialog, Tooltip, Popover
### `src/components/composite/` — Composed Components
Built from primitives, may carry light domain semantics.
- StatusMessage (success/error/warning/info)
- TabBar, TabPanel
- ThemeCard (maps to synthesiser's theme display)
### `src/components/layout/` — Layout
Page-level structural components.
- AppShell (header + sidebar + content area)
- PageHeader
### Which Tier Does a Component Belong To?
| Question | If yes → |
|---|---|
| Does it wrap a single native element or a single interaction pattern (button, input, toggle)? | **ui/** (primitive) |
| Does it compose 2+ primitives into a reusable unit (e.g., a search bar = Input + Button)? | **composite/** |
| Does it carry domain-specific naming or logic (e.g., ThemeCard, ParticipantRow)? | **composite/** |
| Does it define a page-level region or shell (header, sidebar, content area)? | **layout/** |
When in doubt: start in `ui/`. Promote to `composite/` when a component begins importing other `ui/` components. If the `composite/` directory grows beyond ~15 components, consider splitting it into `molecules/` (generic compositions) and `organisms/` (domain-aware compositions).
---
## 4. Styling Approach
- **Primary**: Tailwind utility classes
- **Conditional classes**: `cn()` from `@/lib/utils` (clsx + tailwind-merge)
- **Token values**: Always from `src/tokens/tokens.css`, never hardcoded
- **Token discipline**: Components reference semantic or form-control tokens, not palette tokens. If the needed semantic token doesn't exist, add it to `tokens.css` before using a raw palette value.
- **No CSS modules, no styled-components, no inline styles** (except truly dynamic values)
- **Class ordering**: Enforced by `prettier-plugin-tailwindcss`
---
## 5. Storybook Conventions
- Every component has a co-located `.stories.tsx` file
- All stories use `tags: ['autodocs']` for auto-generated docs
- Stories cover: default state, all variants, edge cases, disabled/error states
- A11y addon runs on all stories — violations should be addressed
- MCP addon enabled at `localhost:6006/mcp` for AI-assisted development
---
## 6. Project Structure
```
src/
├── components/
│ ├── ui/ # Primitives
│ ├── composite/ # Composed components
│ └── layout/ # Layout components
├── tokens/
│ └── tokens.css # Design tokens (@theme block)
├── styles/
│ └── global.css # Tailwind imports + base styles
├── lib/
│ └── utils.ts # cn() utility
├── hooks/ # Custom React hooks
├── pages/ # App screens (Phase 2)
├── App.tsx # Root component
└── main.tsx # Vite entry point
```
---
## 7. Design Tool Integration
### Figma
- Project file: https://www.figma.com/design/mrabO6AtxN3ektGiTk0I9c/ResearchInsights (file key: `mrabO6AtxN3ektGiTk0I9c`)
- MCP server: Official Figma Remote MCP at `https://mcp.figma.com/mcp` (HTTP transport, OAuth auth)
- Key tools: `get_design_context`, `get_variable_defs`, `get_screenshot`, `search_design_system`, `use_figma`
- Design tokens extracted via `get_variable_defs` → mapped to `@theme` values in `tokens.css`
### Code Connect
- Links Figma components to their React implementations
- Once linked, `get_design_context` returns actual component code instead of generic markup
- Mapped as we build each component via `add_code_connect_map` (label: "React")
### Storybook MCP
- Available at `localhost:6006/mcp` when Storybook dev server is running
- Provides: component listing, documentation retrieval, story generation, a11y testing
- `@storybook/addon-designs` embeds Figma frames in the story panel for side-by-side comparison
- `@storybook/addon-mcp` serves the MCP endpoint
### claude2figma Skills
- 4 skills in `.claude/skills/` that enforce design system compliance when writing to Figma
- **figma-preflight**: Validates MCP connection, audits libraries, builds a Token Map of all styles/variables
- **component-rules**: Library-first lookup, Auto Layout conventions, semantic node naming
- **figma-style-binding**: All visual values must bind to Figma Styles or Variables, never hardcoded; includes post-write QA
- **reference-interpreter**: Converts screenshots/references into structured Design Briefs mapped to design tokens

169
CLAUDE.md
View File

@@ -1,169 +0,0 @@
# CLAUDE.md — SDC-Frontend Design System
You are the lead engineer on **SDC-Frontend**, a React component library and design system for the Research Synthesiser, built for UX researchers at the NSW Department of Education.
---
## How I Want You to Work
These are non-negotiable working principles for this project.
### Plan Before You Build
Before beginning any significant piece of work, write a plan as a markdown file in `plans/`. Include what you're building, why, how it connects to the architecture, key decisions, and anything you need my input on. Share it with me before starting. Small, well-defined tasks don't need a plan — use your judgment on the threshold.
### Evaluate Before You Commit
Before choosing a library, pattern, or approach: identify your options, research the latest documentation (don't rely on training knowledge for version-specific details), verify it works within the project constraints, and state your reasoning — especially when you considered and rejected alternatives.
### Ask, Don't Assume
If you need information you don't have — about infrastructure, the team's workflow, the Figma setup, design intent, or anything unclear — ask me. Do not fill gaps with assumptions. A question costs a moment; a wrong assumption costs hours.
### Architecture Governance
`ARCHITECTURE.md` is the living contract for this project. Before starting any task, re-read the relevant section. If you need to deviate, propose the change to me first, then update the document. If you discover something new, add it. Never let the codebase and architecture drift apart.
### Fail Gracefully, Build Honestly
Test components with realistic props and edge cases, not only happy-path defaults. When something breaks, surface clear errors. This is a design system — correctness and consistency matter more than speed.
---
## Technical Stack
| Tool | Version | Notes |
|---|---|---|
| React | 19.x | Function components, hooks only |
| Vite | latest | Dev server + build |
| TypeScript | strict mode | All components must have typed props |
| Tailwind CSS | v4.3+ | CSS-first config via `@theme` in `src/tokens/tokens.css` |
| Storybook | 10.x | Component dev, docs, visual testing, MCP addon |
| clsx + tailwind-merge | latest | Combined as `cn()` in `src/lib/utils.ts` |
| ESLint + Prettier | latest | With `prettier-plugin-tailwindcss` for class ordering |
---
## Design Tokens
All design tokens live in `src/tokens/tokens.css` as a `@theme` block. This is the **single source of truth**. Do not hardcode colour values, radii, or shadows anywhere else.
Use Tailwind utilities (`bg-primary`, `text-error`, `rounded-default`, etc.) or CSS variables (`var(--color-primary)`) when utilities don't cover the case.
### Token Layers
Tokens are organised in three layers, from raw to consumable:
1. **Palette** — raw colour values: `--color-blue-01`, `--color-grey-03`, etc. Never reference these directly in components.
2. **Semantic** — purpose-based aliases: `--color-primary`, `--color-error`, `--color-text`, etc. Use these for general UI.
3. **Form control** — shared interactive-state tokens for all form components (Input, Checkbox, Radio, Switch, Select, etc.):
- `--color-control-border` / `--color-control-border-hover`
- `--color-control-checked` / `--color-control-checked-hover`
- `--color-control-focus-ring`
- `--color-control-label` / `--color-control-description` / `--color-control-error`
- `--color-control-bg` / `--color-control-bg-readonly`
**Rule**: Components must reference semantic or form-control tokens, not palette tokens. If you need a colour that has no semantic token, add one — don't reach for the palette directly.
### Token Naming Convention
- Colours: `--color-{name}` (e.g., `--color-primary`, `--color-text-secondary`)
- Form controls: `--color-control-{role}` (e.g., `--color-control-border`, `--color-control-checked`)
- Radii: `--radius-{size}` (e.g., `--radius-default`, `--radius-lg`)
- Shadows: `--shadow-{size}` (e.g., `--shadow-default`, `--shadow-md`)
---
## Component Conventions
### File Structure
Each component lives in its own directory:
```
src/components/ui/Button/
Button.tsx — Component implementation
Button.stories.tsx — Storybook stories
index.ts — Barrel export
```
### Component Taxonomy
- `src/components/ui/` — Primitive components (Button, Input, Card, Badge)
- `src/components/composite/` — Composed components (StatusMessage, TabBar)
- `src/components/layout/` — Layout components (AppShell, Sidebar)
### TypeScript
- Export a named props interface: `ButtonProps`, `CardProps`, etc.
- Use `React.forwardRef` for components that wrap native elements.
- Extend native element props where appropriate (e.g., `ButtonHTMLAttributes<HTMLButtonElement>`).
### Styling
- Use Tailwind utility classes as the primary styling method.
- Use the `cn()` utility from `@/lib/utils` for conditional classes.
- Never use inline styles except for truly dynamic values.
- Never use CSS modules or styled-components.
- **Token discipline**: Reference semantic or form-control tokens (`text-control-label`, `border-control-border`), never palette tokens (`text-blue-01`, `border-grey-03`) in component code. If the right semantic token doesn't exist, add one to `tokens.css` first.
### Stories
- Every component MUST have a Storybook story file.
- Include at minimum: a `Default` story, and stories for each significant variant/state.
- Use Storybook Controls for interactive prop exploration.
- Use `tags: ['autodocs']` for automatic documentation generation.
### Accessibility
- All interactive components must be keyboard-navigable.
- Use semantic HTML elements (`<button>`, `<nav>`, `<main>`, not `<div onClick>`).
- Include `aria-` attributes where needed.
- Storybook a11y addon is configured — check for violations.
---
## Code Patterns
### Conditional Classes
```tsx
import { cn } from '@/lib/utils'
<button className={cn(
'px-4 py-2 rounded-default font-medium',
variant === 'primary' && 'bg-primary text-white hover:bg-primary-hover',
variant === 'secondary' && 'bg-surface border border-border text-text',
disabled && 'opacity-50 cursor-not-allowed',
)}>
```
### Barrel Exports
Every component directory has an `index.ts`:
```ts
export { Button } from './Button'
export type { ButtonProps } from './Button'
```
### Path Alias
Use `@/` for imports from `src/`:
```ts
import { cn } from '@/lib/utils'
```
---
## MCP Integrations
MCP servers are configured via `claude mcp add` (stored in `~/.claude.json`) or `.mcp.json` in the project root. **Do not** put `mcpServers` in `.claude/settings.json` or `.claude/settings.local.json` — Claude Code ignores MCP config in those files.
- **Figma MCP** — Official Figma Remote MCP server at `https://mcp.figma.com/mcp`. Uses HTTP transport with OAuth authentication. Add via: `claude mcp add --scope user --transport http figma https://mcp.figma.com/mcp`, then authenticate when prompted. Project file: `https://www.figma.com/design/mrabO6AtxN3ektGiTk0I9c/ResearchInsights` (file key: `mrabO6AtxN3ektGiTk0I9c`).
- **Storybook MCP** — runs at `localhost:6006/mcp` alongside the Storybook dev server. Provides component docs, story generation, and a11y testing. Add via: `claude mcp add --transport http storybook http://localhost:6006/mcp`
### Design-to-Code Workflow
1. Use Figma MCP `get_design_context` to inspect a selected component or frame
2. Use `get_variable_defs` to extract design variables (colours, typography, spacing)
3. Map variables to design tokens in `src/tokens/tokens.css`
4. Build React component using design tokens
5. Verify in Storybook that the component matches the Figma design
---
## Reference Project
The existing synthesiser at `../SDC-Synthesiser/` contains the UI patterns, design tokens, and component designs this system is based on. Consult it when building components that map to the existing interface.
---
## Useful Subagents and Tools
- **Explore agent** — for codebase research spanning multiple files
- **webapp-testing skill** — for browser-based UI testing via Playwright
- **WebSearch/WebFetch** — for checking latest library documentation before making dependency decisions
- **Storybook MCP** — for AI-assisted story generation and component verification

View File

@@ -1,73 +0,0 @@
# Plan: Brand Colour Tokens
## What
Replace the placeholder design tokens in `src/tokens/tokens.css` with the actual brand colour palette from Figma, and extrapolate additional variants for extended UI use.
## Why
The current tokens use generic blues/greens — they don't match the project's brand. The Figma palette defines the canonical colours; we need to mirror those names and extend them with hover/active/background variants for component development.
## Approach
### 1. Palette layer — mirror Figma names exactly
These are the base brand colours, named to match Figma:
| Token | Hex (estimated from screenshot) | Notes |
|---|---|---|
| `--color-blue-01` | `#002664` | Deep navy (primary dark) |
| `--color-blue-02` | `#146CFD` | Bright blue (primary) |
| `--color-blue-03` | `#69B3E7` | Sky blue |
| `--color-blue-04` | `#CBEDFD` | Ice blue |
| `--color-red-01` | `#3E0014` | Dark burgundy/maroon |
| `--color-red-02` | `#D7153A` | Crimson (primary red) |
| `--color-red-03` | `#F5C5D0` | Soft pink |
| `--color-red-04` | `#FDDDE5` | Pale pink |
| `--color-grey-01` | `#3D3D3D` | Darkest grey |
| `--color-grey-02` | `#6D7278` | Medium grey |
| `--color-grey-03` | `#C0C0C0` | Light grey |
| `--color-grey-04` | `#E0E0E0` | Lighter grey |
| `--color-off-white` | `#F4F4F4` | Off white |
| `--color-surface` | `#FAFAFA` | Surface |
| `--color-white` | `#FFFFFF` | White |
| `--color-orange-02` | `#EC6608` | Warning orange (light mode) |
| `--color-orange-03` | `#F5B98A` | Warning orange (dark mode) |
| `--color-green-02` | `#00A651` | Success green (light mode) |
| `--color-green-03` | `#89E5B3` | Success green (dark mode) |
### 2. Extrapolated variants
For each colour family, generate useful variants not in Figma:
- **Orange 01** (dark) and **Orange 04** (light bg) — fill the gaps in the orange scale
- **Green 01** (dark) and **Green 04** (light bg) — fill the gaps in the green scale
- **Blue 05** — ultra-light blue for backgrounds
- **Red 05** — ultra-light red for backgrounds
- **Hover/active states** — slightly darker/lighter versions of primary interactive colours
### 3. Semantic mapping layer
Map palette colours to semantic roles so components use intent-based tokens:
```
--color-primary → blue-01
--color-primary-hover → derived (darker blue-01)
--color-primary-light → blue-04
--color-text → grey-01
--color-text-secondary → grey-02
--color-border → grey-04
--color-bg → off-white
--color-error → red-02
--color-error-bg → red-04
--color-warning → orange-02
--color-warning-bg → orange-04
--color-success → green-02
--color-success-bg → green-04
--color-info → blue-02
--color-info-bg → blue-04
```
## Key decisions needed from you
1. **Hex accuracy** — These are my best estimates from the screenshot. Can you verify any of these, or should I proceed and we refine later?
2. **Dark mode** — The Figma shows light/dark mode status colours. Should we set up CSS custom property dark mode switching now, or defer and just define all the values?
3. **Semantic vs palette** — Do you want components to reference palette names (`bg-blue-01`) or semantic names (`bg-primary`), or both?

View File

@@ -1,68 +0,0 @@
# Button Component Plan
## Source
Example pasted into Figma Examples page (node 10:20). Original has 5 properties × many values = 360+ variants. We're building the light-mode subset.
## Props (React)
```tsx
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'tertiary'
color?: 'navy' | 'red' | 'light' | 'surface'
size?: 'default' | 'comfortable' | 'compact'
leftIcon?: React.ReactNode
rightIcon?: React.ReactNode
}
```
- `children` is the label text
- States (hover, active, focus, disabled) handled via CSS pseudo-classes
- Uses `forwardRef` to wrap `<button>`
## Colour Scheme Mapping
| Prop value | Example name | Primary bg | Primary text | Secondary border | Our tokens |
|---|---|---|---|---|---|
| `navy` | Navy | #002664 | white | #002664 | `blue-01`, `white` |
| `red` | Red | #D7153A | white | #D7153A | `red-02`, `white` |
| `light` | On Primary (Teal) | #CBEDFD | #002664 | #002664 | `blue-04`, `blue-01` |
| `surface` | On Surface | #22272B | white | #22272B | `grey-01`*, `white` |
*Note: source uses #22272B, our grey-01 is #3D3D3D. Close enough for now — revisit if the NSW DS specifies #22272B.
### Variant × Colour behaviour
- **Primary**: filled background, white or dark text
- **Secondary**: transparent bg, 2px border, coloured text
- **Tertiary**: no fill, no border, coloured text (ghost)
## Sizes
| Size | Height | Padding | Typography |
|---|---|---|---|
| Default | 48px | 24px horizontal | Body Strong (16/24 bold) |
| Comfortable | 40px | 20px horizontal | Body Strong (16/24 bold) |
| Compact | 36px | 16px horizontal | Small Strong (14/19 bold) |
## Shared Styling
- Border radius: `rounded-full` (pill shape, 9999px)
- Icon size: 24px (default), 20px (compact)
- Content gap: 8px
- Focus ring: 2px border offset (matches example's "Border" layer)
- Disabled: opacity 50%, cursor not-allowed
## Hover/Active States
- **Primary**: overlay with white at ~10% opacity (hover), ~20% (active)
- **Secondary/Tertiary**: bg fill at ~5% opacity (hover), ~10% (active)
- Implementation: CSS `hover:` and `active:` with bg-opacity modifiers
## Build Order
1. Add tokens (if needed) to tokens.css
2. Build React component + stories
3. Build Figma component on Components page (rebind to our variables)
4. Code Connect link
5. addon-designs embed in story
## Questions resolved
- All four colour schemes included
- Icon slots as ReactNode
- No dark mode

View File

@@ -1,55 +0,0 @@
# Input Component Plan
## Figma Reference
https://www.figma.com/design/mrabO6AtxN3ektGiTk0I9c/ResearchInsights?node-id=22-3845
## Design Style (from Figma)
- Outlined text field with label overlapping top border (Material-style notch)
- Label: 14px bold blue-01, sits on top of border with white background
- Input: 16px regular grey-01, placeholder at 50% opacity
- Border radius: 4px (rounded-sm)
- Optional left/right icon slots (24px)
- Supportive text below: hint (left) + character count (right), 14px grey-02
## Sizes
| Size | Input height | Text | Maps to |
|------|-------------|------|---------|
| default | 48px (h-12) | text-body (16px) | Figma "Comfortable" |
| compact | 40px (h-10) | text-small (14px) | Figma "Compact" |
## States (best practice, not 1:1 Figma)
| State | Border | Label | Hint |
|-------|--------|-------|------|
| Default | grey-03 (1px) | blue-01 | grey-02 |
| Hover | grey-01 (1px) | blue-01 | grey-02 |
| Focus | blue-01 (2px) | blue-01 | grey-02 |
| Error | red-02 (1px) | red-02 | red-02 (shows error message) |
| Error+Focus | red-02 (2px) | red-02 | red-02 |
| Disabled | grey-03 at 50% opacity | grey-02 | grey-02 at 50% opacity |
## Props
```ts
interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
label: string
hint?: string
error?: string
size?: 'default' | 'compact'
leftIcon?: React.ReactNode
rightIcon?: React.ReactNode
}
```
- `error` takes precedence over `hint` — when set, replaces hint text and turns red
- `maxLength` (native attribute) triggers character counter display
- `disabled` and `readOnly` use native attributes, styled accordingly
## Accessibility
- `<label>` bound to `<input>` via `useId()` fallback
- `aria-describedby` links hint/error text to the input
- `aria-invalid="true"` when error is set
- Keyboard fully native (no custom handling needed)
## Figma issues mitigated
1. Default border 38% opacity too faint → using grey-03
2. 10 redundant state variants → CSS pseudo-classes
3. Dark mode variants → skipped
4. Chips/file input states → separate components

View File

@@ -1,49 +0,0 @@
# Penpot → Figma Migration Plan
## Context
Penpot MCP has been unreliable (plugin disconnects, "Server not initialized" errors on session resumption). Pivoting to Figma for the design-to-code workflow.
- **Figma project**: https://www.figma.com/design/mrabO6AtxN3ektGiTk0I9c/ResearchInsights
- **File key**: `mrabO6AtxN3ektGiTk0I9c`
## Figma MCP Server Choice
**Official Figma Remote MCP Server** (`https://mcp.figma.com/mcp`)
| Aspect | Detail |
|--------|--------|
| Transport | HTTP (Streamable HTTP) |
| Auth | OAuth browser flow (no PAT support) |
| Rate limits | 200 calls/day, 10-15/min on Pro; free during beta |
| Read tools | `get_design_context`, `get_metadata`, `get_screenshot`, `get_variable_defs`, `get_code_connect_map`, `search_design_system`, `get_libraries` |
| Write tools | `use_figma`, `create_new_file`, `upload_assets` |
| Setup | `claude mcp add --scope user --transport http figma https://mcp.figma.com/mcp` |
**Why official over community options**: We need `get_variable_defs` (token extraction), `get_design_context` (component specs), and `search_design_system` (library search). The Framelink community server only has 2 read-only tools. The official server covers our design system workflow end-to-end.
**Fallback**: If rate limits or OAuth issues arise, Framelink (`figma-developer-mcp`) uses a PAT with no daily call limits.
## Changes
### Documentation updates (automated)
- `ARCHITECTURE.md` — Replace Penpot section with Figma, update token pipeline diagram
- `CLAUDE.md` — Replace Penpot MCP section with Figma MCP, update design-to-code workflow
### Settings cleanup (automated)
- `.claude/settings.json` — Replace Penpot WebFetch domains with Figma domains
- `.claude/settings.local.json` — Remove Penpot debugging artifacts and exposed MCP token
### Memory updates (automated)
- Replace `project-penpot-setup.md` with `project-figma-setup.md`
- Update `MEMORY.md` index
### User actions (manual)
1. Remove Penpot MCP: `claude mcp remove penpot`
2. Add Figma MCP: `claude mcp add --scope user --transport http figma https://mcp.figma.com/mcp`
3. Authenticate when prompted (OAuth browser flow)
## What stays the same
- All CSS/token work (tokens.css, typography.css, global.css) — design-tool-agnostic
- Storybook MCP — unchanged
- Component conventions, file structure, TypeScript patterns — unchanged
- Brand colours already in tokens.css — unchanged

View File

@@ -1,177 +0,0 @@
# SDC-Frontend Project Setup Plan
## Context
The existing Research Synthesiser at `../SDC-Synthesiser/` is a vanilla HTML/CSS/JS app. This new project creates a React-based design system and component library that will eventually become the full frontend replacement. The design system is driven by Penpot (self-hosted at `192.168.50.211:9001`) via MCP, with Storybook for component development and documentation.
The project carries forward the same working principles (plan before building, evaluate before committing, ask don't assume, architecture governance) and the existing design tokens from the synthesiser's CSS.
---
## Tech Stack
| Tool | Version | Notes |
|---|---|---|
| React | 19.x | Function components, hooks only |
| Vite | latest | Dev server + build |
| TypeScript | strict | All components have typed props |
| Tailwind CSS | v4.3+ | CSS-first `@theme` config — no JS config file |
| Storybook | 10.x | Component dev, docs, MCP addon |
| ESLint + Prettier | latest | With `prettier-plugin-tailwindcss` for class sorting |
| clsx + tailwind-merge | latest | Combined as `cn()` utility |
---
## Steps
### 1. Scaffold Vite project
```bash
cd /home/richie/Nextcloud/Projects/Coding/
npm create vite@latest SDC-Frontend -- --template react-ts
cd SDC-Frontend
npm install
```
### 2. Install Tailwind CSS v4
```bash
npm install tailwindcss @tailwindcss/vite
```
Update `vite.config.ts` to add the Tailwind plugin alongside React.
### 3. Create design tokens and global styles
- `src/tokens/tokens.css``@theme` block with all design tokens ported from the synthesiser's CSS variables
- `src/styles/global.css` — imports Tailwind and tokens, sets base typography
- Update `src/main.tsx` to import `global.css`
- Delete Vite boilerplate (`App.css`, default `App.tsx` content, `src/assets/react.svg`)
### 4. Install utility dependencies
```bash
npm install clsx tailwind-merge
```
Create `src/lib/utils.ts` with `cn()` helper.
### 5. Configure path alias
Set up `@/``src/` in both `tsconfig.app.json` and `vite.config.ts` for clean imports.
### 6. Install and configure Storybook 10
```bash
npx storybook@latest init
npm install @storybook/addon-mcp
```
- Update `.storybook/main.ts` to add MCP addon
- Update `.storybook/preview.ts` to import `global.css` (so Tailwind works in stories)
### 7. Install and configure ESLint + Prettier
```bash
npm install -D prettier prettier-plugin-tailwindcss eslint-config-prettier
```
- Create `.prettierrc` with Tailwind plugin
- Update ESLint config to extend `prettier`
### 8. Create initial Button component
Validates the full pipeline: tokens → Tailwind → TypeScript → Storybook.
- `src/components/ui/Button/Button.tsx` — primary/secondary/danger variants, sm/md/lg sizes
- `src/components/ui/Button/Button.stories.tsx` — stories for each variant + disabled state
- `src/components/ui/Button/index.ts` — barrel export
### 9. Write CLAUDE.md
Carry over the five working principles from SDC-Synthesiser, adapted for React/TS/Tailwind/Storybook. Add:
- Component conventions (folder structure, typed props, forwardRef, stories required)
- Design token rules (single source of truth in `tokens.css`, never hardcode)
- Styling rules (Tailwind utilities via `cn()`, no inline styles, no CSS modules)
- Accessibility requirements (semantic HTML, keyboard nav, ARIA)
- MCP integrations (Penpot, Storybook)
- Reference to existing synthesiser
### 10. Write initial ARCHITECTURE.md
Lightweight living document covering:
- Token pipeline (Penpot → tokens.css → Tailwind → components)
- Component taxonomy (ui/composite/layout)
- Styling approach
- Storybook conventions
### 11. Create .claude/settings.json
**MCP servers:**
- `storybook``http://localhost:6006/mcp` (available when Storybook is running)
- `penpot``http://localhost:4401/mcp` (Penpot MCP server, runs via `npx @penpot/mcp@stable`)
Note: The Penpot UI is at `http://192.168.50.211:9001/`. The MCP server runs locally on the dev machine and connects to Penpot via the browser plugin.
**Permissions:** Pre-allow npm scripts, TypeScript checks, git operations, WebSearch, and WebFetch for documentation domains.
### 12. Create plans/ directory
Add this plan as the first entry.
### 13. Git init + initial commit
- `git init`
- Verify `.gitignore` covers `node_modules/`, `dist/`, `storybook-static/`, `.claude/settings.local.json`
- Stage all files, commit with descriptive message
- User provides Gitea remote URL to push
### 14. Verify end-to-end
1. `npm run dev` — Vite serves without errors
2. `npm run storybook` — Storybook loads, Button stories render with correct token colours
3. `npx tsc --noEmit` — TypeScript compiles clean
4. `npm run build` — produces `dist/` output
5. Storybook Controls work interactively on Button
6. Autodocs generate for Button
---
## Files created/modified
| File | Purpose |
|---|---|
| `vite.config.ts` | Add Tailwind plugin + path alias |
| `tsconfig.app.json` | Add `@/` path alias |
| `src/tokens/tokens.css` | Design tokens as `@theme` block |
| `src/styles/global.css` | Tailwind imports + base styles |
| `src/main.tsx` | Updated import path |
| `src/App.tsx` | Cleaned boilerplate |
| `src/lib/utils.ts` | `cn()` utility |
| `src/components/ui/Button/Button.tsx` | First component |
| `src/components/ui/Button/Button.stories.tsx` | First stories |
| `src/components/ui/Button/index.ts` | Barrel export |
| `.storybook/main.ts` | MCP addon added |
| `.storybook/preview.ts` | Global CSS import |
| `.prettierrc` | Tailwind class sorting |
| `CLAUDE.md` | Project principles + conventions |
| `ARCHITECTURE.md` | Living architecture doc |
| `.claude/settings.json` | MCP servers + permissions |
| `plans/project-setup.md` | This plan |
---
## Penpot MCP Setup (user action required)
After the project is scaffolded, the user needs to:
1. Install the Penpot MCP plugin inside their Penpot instance at `192.168.50.211:9001`
2. Run `npx @penpot/mcp@stable` on their dev machine
3. Open Penpot in the browser with the MCP plugin active
4. Claude Code can then use the `penpot` MCP tools
---
## Open items
- **Gitea remote URL** — user will provide when ready to push
- **Penpot MCP plugin installation** — user needs to install in their Penpot instance

View File

@@ -1,90 +0,0 @@
# Toggle Controls: Checkbox, Radio, Switch
## Figma References
- Checkbox: `node-id=33-5043`
- Radio: `node-id=33-5188`
- Switch: `node-id=33-5337`
## What I'm Taking from Figma
- **Sizing**: 20px checkbox/radio icon inside a clickable area, 16px body text labels
- **Colours**: blue-01 (`#002664`) for checked state, grey-03 for unchecked border, grey-01 for labels
- **Layout**: icon + label in a horizontal row with 8px gap
- **Disabled**: 60% opacity
## What I'm Changing (Per Your Brief)
### Pressed/Active States (Checkbox & Radio)
The Figma designs use a large circular background highlight on press — feels heavy for a form control. Instead:
- **Hover**: subtle border colour shift to blue-01 (unchecked) or slight darkening (checked)
- **Active/pressed**: brief scale-down (`scale-95`) on the control — tactile without a big splashy highlight
- **Focus-visible**: 2px blue-04 ring offset for keyboard nav (accessibility)
### Switch
Figma has icons inside the thumb (check/X). Removing those per your request. My approach:
- **Track**: 44px wide x 24px tall, rounded-full. Off = grey-03, On = blue-01
- **Thumb**: 18px white circle, smooth slide transition (150ms)
- **Hover**: track lightens/darkens slightly
- **Focus-visible**: ring around the track
- Clean, minimal look that matches the Input component's rounded-[4px] + blue-01 brand
### Indeterminate (Checkbox Only)
Keeping the indeterminate state (dash icon) — useful for "select all" patterns. Same blue-01 fill as checked.
## Props
### Checkbox
```ts
interface CheckboxProps {
label?: string
description?: string // secondary text below label
checked?: boolean
indeterminate?: boolean
disabled?: boolean
error?: string
onChange?: (checked: boolean) => void
}
```
### Radio
```ts
interface RadioProps {
label?: string
description?: string
value: string
disabled?: boolean
}
interface RadioGroupProps {
label?: string // group legend
description?: string
value?: string
defaultValue?: string
error?: string
disabled?: boolean
orientation?: 'vertical' | 'horizontal'
onChange?: (value: string) => void
children: React.ReactNode // Radio items
}
```
### Switch
```ts
interface SwitchProps {
label?: string
description?: string
checked?: boolean
disabled?: boolean
onChange?: (checked: boolean) => void
}
```
## Accessibility
- Checkbox: hidden native `<input type="checkbox">` with visual overlay, `aria-checked`, supports indeterminate
- Radio: native `<input type="radio">` within a `<fieldset>`/`<legend>` group, arrow key navigation
- Switch: `role="switch"` with `aria-checked`, toggled via Space/Enter
- All: focus-visible ring, disabled state, proper label association
## Stories (per component)
- Default, Checked, WithDescription, Disabled, DisabledChecked, WithError, AllStates
- Radio adds: RadioGroup, Horizontal, WithError
- Switch adds: Default, On, WithDescription, Disabled, AllStates

View File

@@ -1,74 +0,0 @@
# Typography System Plan
## What we're building
A complete typography token set for the design system, sourced from the NSW Design System style guide (desktop, >= 960px), expanded with missing styles for a well-balanced type system.
## Font Family
**Public Sans** — the NSW Government typeface. Self-hosted via `@fontsource-variable/public-sans` (weights 100900, normal + italic).
Fallback stack: `'Public Sans', system-ui, sans-serif`
## Typography Scale
### From the style guide (12 styles)
| Style | Class | Weight | Size | Line Height | Notes |
|-------|-------|--------|------|-------------|-------|
| Heading 1 | text-h1 | 700 | 48px | 1.25 | |
| Heading 2 | text-h2 | 700 | 32px | 1.25 | |
| Heading 3 | text-h3 | 700 | 24px | 1.333 | |
| Heading 4 | text-h4 | 700 | 20px | 1.4 | |
| Heading 5 | text-h5 | 700 | 16px | 1.5 | |
| Intro | text-intro | 400 | 20px | 1.4 | Lead paragraph |
| Body Strong | text-body-strong | 700 | 16px | 1.5 | |
| Body | text-body | 400 | 16px | 1.5 | |
| Small Strong | text-small-strong | 700 | 14px | 1.357 | |
| Small | text-small | 400 | 14px | 1.357 | |
| Body Link | text-body-link | 700 | 16px | 1.5 | Underlined, Blue 02 |
| Small Link | text-small-link | 700 | 14px | 1.357 | Underlined, Blue 02 |
### New additions (4 styles)
| Style | Class | Weight | Size | Line Height | Rationale |
|-------|-------|--------|------|-------------|-----------|
| Heading 6 | text-h6 | 700 | 14px | 1.43 | Completes heading hierarchy; same size as Small but bold, sits between H5 and body |
| Label | text-label | 500 | 14px | 1.43 | Form labels — medium weight distinguishes from body text without being as heavy as bold. The user called this out as missing |
| Caption | text-caption | 400 | 12px | 1.5 | Metadata, timestamps, helper text. Introduces 12px to the scale |
| Overline | text-overline | 700 | 12px | 1.5 | Category tags, section labels. Uppercase + 0.05em letter-spacing |
**Total: 16 styles**
## Size scale summary
48 → 32 → 24 → 20 → 16 → 14 → 12 (px)
## Implementation
### 1. tokens.css — Add to @theme block
- `--font-sans` for the font family
- `--text-{name}` + `--text-{name}--line-height` for each size/line-height pair (Tailwind v4 convention — gives us `text-h1`, `text-body`, etc. utilities automatically)
- Font weights don't need custom tokens — Tailwind's built-in `font-normal` (400), `font-medium` (500), `font-bold` (700) cover our needs
### 2. typography.css — Composed utility classes
Create `src/tokens/typography.css` with `@utility` directives for styles that combine multiple properties:
- `text-body-strong`, `text-small-strong` (size + weight)
- `text-body-link`, `text-small-link` (size + weight + underline + colour)
- `text-overline` (size + weight + uppercase + letter-spacing)
- `text-label` (size + weight)
This keeps the @theme block as raw tokens and the utilities as compositions.
### 3. Load Public Sans
Add Google Fonts import to `index.html` or `src/index.css`. Google Fonts is fine for dev; can revisit self-hosting later if needed for production/privacy.
### 4. Figma — Typography styles
Create typography styles in the Figma design file via MCP so designers can apply them from the styles panel.
## Key decisions
- **Public Sans via Google Fonts** — simplest path; the font is open source (OFL) so self-hosting later is trivial.
- **Tailwind v4 `--text-*` naming** — gives us free utility classes. Composed styles go in a separate file to keep tokens.css clean.
- **No `text-xs`/`text-sm`/`text-lg` scale aliases** — the semantic names (`text-body`, `text-caption`) are clearer for a design system. Tailwind defaults still work if needed.
- **Medium (500) for Label** — distinguishes from Regular body text without being as heavy as Bold headings. Common pattern in Material Design, Atlassian DS, etc.
## Questions for Richie
1. **Font loading**: Google Fonts import in `index.html`, or self-host from the start?
2. **Does this expanded set look right?** Any styles you want to add or remove?

View File

@@ -1,115 +0,0 @@
# Workflow Plan — Figma + Code + Storybook
## Problem
We have three tools — Figma (design), code (React/Tailwind/TypeScript), and Storybook (component dev/docs/testing) — plus MCP servers for both Figma and Storybook. We need a clear process for how these connect as we build components, and what tooling/skills are worth adding to streamline things.
---
## Workflow: Code-First with Figma Reference
Components are built in code and verified in Storybook. Figma serves as a **visual reference** — Richie may provide Figma links or screenshots to guide design intent, but we don't create or maintain Figma representations of each component as part of the build process. Figma design can be added later if needed.
### Build Steps
1. **Reference** — Richie provides design intent: a Figma link, screenshot, description, or existing component to match. Use `get_design_context` / `get_screenshot` to inspect Figma references when provided.
2. **Check tokens** — Cross-check the reference against `tokens.css`. Flag any gaps (missing colours, spacing, etc.) before building.
3. **Check existing components** — Storybook MCP `get-documentation` lists what we've already built. Reuse before rebuilding.
4. **Build** — React component with TypeScript props, Tailwind utilities via `cn()`, tokens from `tokens.css`.
5. **Story** — Write stories covering default, variants, and edge cases. Use `tags: ['autodocs']`.
6. **Test** — Storybook MCP `run-story-tests` for rendering + a11y. Fix violations before moving on.
7. **Visual verify** — Playwright screenshots of Storybook stories to confirm rendering.
8. **Embed** — If a Figma reference exists, add `addon-designs` parameter to the story for side-by-side comparison.
### The Finished State
Every completed component has:
- A React implementation with typed props and Tailwind styling
- Storybook stories with autodocs, variants, and a11y coverage
- An `addon-designs` embed in its story if a Figma reference exists
---
## Where Storybook Fits in Both Directions
Storybook isn't just for docs — with its MCP server it becomes the **verification layer** in both directions:
| Capability | MCP Tool | When to use |
|---|---|---|
| List existing components | `list-all-documentation` | Before building anything — check what exists |
| Get component API | `get-documentation` | When composing or extending existing components |
| Story conventions | `get-storybook-story-instructions` | When writing new stories — ensures consistency |
| Visual preview | `preview-stories` | After building — visual confirmation in-context |
| Test + a11y | `run-story-tests` | After every component build — catches violations early |
### Addon: `@storybook/addon-designs`
Embeds Figma frames directly in the Storybook addon panel. Each story can link to its Figma source:
```tsx
parameters: {
design: {
type: 'figma',
url: 'https://www.figma.com/design/mrabO6AtxN3ektGiTk0I9c/ResearchInsights?node-id=...',
},
},
```
This creates a permanent link between implementation and design intent — developers see the spec alongside the rendered component without switching tools.
Both addons are installed and registered in `.storybook/main.ts`.
---
## Skills and Tooling
### Adopt now
| Skill | What it does | Why |
|---|---|---|
| **claude2figma** (4 skills) | Preflight reads all tokens/styles/components before writing; Component Rules enforces library instance usage; Style Binding binds colours/text/spacing to variables with QA; Reference Interpreter parses design references into structured briefs | Directly solves token fidelity when writing to Figma. Prevents hardcoded values. |
| **Built-in /simplify** | Three parallel review agents check reuse, quality, efficiency | Already available. Run after building each component. |
| **Built-in /review** | Code correctness review | Already available. Run before committing. |
| **Built-in /verify** | Launches the app and confirms the change works visually | Already available. Use for visual confirmation. |
### Evaluate
| Skill | What it does | Verdict |
|---|---|---|
| **Storybook docs skill** (thebushidocollective) | Generates comprehensive Storybook docs with MDX, autodocs, JSDoc | Test whether it adds value beyond Storybook MCP's built-in `get-storybook-story-instructions`. May be redundant. |
| **A11y audit skill** | WCAG compliance, screen reader support, keyboard navigation | The Storybook a11y addon + `run-story-tests` already covers per-component checks. An audit skill would add value for periodic full-system sweeps. |
### Skip
| Skill | Why skip |
|---|---|
| **frontend-design** (Anthropic) | We're implementing an existing NSW design system, not making creative aesthetic choices |
| **awesome-design-md** | We have our own design language from Figma |
| **React specialist subagent** | CLAUDE.md already covers these conventions |
---
## The Component Build Checklist
Regardless of entry point, every component should pass through these gates:
```
[ ] Design reference provided (Figma link, screenshot, or description)
[ ] Tokens checked (reference vs tokens.css — flag gaps)
[ ] Existing components checked (Storybook MCP list-all-documentation)
[ ] React component built (TypeScript, Tailwind, cn(), forwardRef)
[ ] Stories written (default + variants + edge cases + autodocs)
[ ] Tests pass (Storybook MCP run-story-tests, including a11y)
[ ] Visual verified (Playwright screenshots of Storybook)
[ ] Figma embed in story (addon-designs parameter, if reference exists)
```
---
## Decisions Made
- **Code-first workflow** — Build in React + Storybook, use Figma as visual reference only (not as a build target)
- **claude2figma skills** — Installed but deferred; will use when/if pushing designs to Figma later
- **Storybook addons** — `addon-designs` + `addon-mcp` installed and registered
- **Code Connect** — Deferred (requires Org/Enterprise Figma plan)
- **Build order** — Start with primitives, work up to composites then layouts