@pavp/wavefront 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +226 -0
  3. package/agents/i18n-tokens.md +58 -0
  4. package/agents/mapper.md +64 -0
  5. package/agents/module-builder.md +90 -0
  6. package/agents/reviewer.md +65 -0
  7. package/agents/tester.md +84 -0
  8. package/bin/wavefront.js +91 -0
  9. package/commands/wavefront-change.md +26 -0
  10. package/commands/wavefront-execute.md +28 -0
  11. package/commands/wavefront-feature.md +25 -0
  12. package/commands/wavefront-fix.md +28 -0
  13. package/commands/wavefront-init.md +27 -0
  14. package/commands/wavefront-plan.md +30 -0
  15. package/commands/wavefront-ship.md +26 -0
  16. package/commands/wavefront-state.md +23 -0
  17. package/commands/wavefront-verify.md +24 -0
  18. package/hooks/lint-after-edit.sh +43 -0
  19. package/hooks/typecheck-on-stop.sh +27 -0
  20. package/install.sh +83 -0
  21. package/package.json +67 -0
  22. package/planning-templates/PHASE_PLAN.md +37 -0
  23. package/planning-templates/PROJECT.md +18 -0
  24. package/planning-templates/REQUIREMENTS.md +27 -0
  25. package/planning-templates/ROADMAP.md +12 -0
  26. package/planning-templates/STATE.md +21 -0
  27. package/settings.template.json +29 -0
  28. package/skills/clean-architecture/SKILL.md +46 -0
  29. package/skills/component-composition/SKILL.md +67 -0
  30. package/skills/component-composition/examples/compound-component.md +62 -0
  31. package/skills/data-fetching-react-query/SKILL.md +68 -0
  32. package/skills/design-intake/SKILL.md +69 -0
  33. package/skills/design-system-inventory/SKILL.md +38 -0
  34. package/skills/forms-rhf-zod/SKILL.md +75 -0
  35. package/skills/gateway-pattern/SKILL.md +79 -0
  36. package/skills/hook-patterns/SKILL.md +74 -0
  37. package/skills/i18n-next-intl/SKILL.md +59 -0
  38. package/skills/module-structure/SKILL.md +98 -0
  39. package/skills/mui-design-tokens/SKILL.md +60 -0
  40. package/skills/naming-conventions/SKILL.md +65 -0
  41. package/skills/repository-pattern/SKILL.md +128 -0
  42. package/skills/requirement-intake/SKILL.md +65 -0
  43. package/skills/responsive-layouts/SKILL.md +64 -0
  44. package/skills/selector-pattern/SKILL.md +59 -0
  45. package/skills/store-zustand/SKILL.md +63 -0
  46. package/skills/sync-audit/SKILL.md +52 -0
  47. package/skills/testing-jest-rtl/SKILL.md +83 -0
  48. package/uninstall.sh +36 -0
@@ -0,0 +1,12 @@
1
+ # ROADMAP
2
+
3
+ > Project work-item backlog/sequence. Maintained across `/wavefront-feature|change|fix` runs.
4
+
5
+ ## Now
6
+ - <work item in progress>
7
+
8
+ ## Next
9
+ - <queued work items, ordered>
10
+
11
+ ## Done
12
+ - <shipped items, newest first>
@@ -0,0 +1,21 @@
1
+ # STATE
2
+
3
+ > Current position. Updated by every wavefront command. Lets a run resume after interruption.
4
+
5
+ ## Active work item
6
+ <title> · type · status: intake|planned|executing|verifying|shipping|done
7
+
8
+ ## Last step completed
9
+ <command> — <result> — <timestamp>
10
+
11
+ ## Next step
12
+ <command to run next>
13
+
14
+ ## Execution progress
15
+ <which PHASE_PLAN tasks are done; which is next>
16
+
17
+ ## Decisions log
18
+ - <date> <decision> — <why>
19
+
20
+ ## Blockers
21
+ <anything stopping progress; empty if none>
@@ -0,0 +1,29 @@
1
+ {
2
+ "hooks": {
3
+ "PostToolUse": [
4
+ {
5
+ "matcher": "Edit|Write|MultiEdit",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/lint-after-edit.sh",
10
+ "timeout": 60,
11
+ "statusMessage": "wavefront: lint --fix"
12
+ }
13
+ ]
14
+ }
15
+ ],
16
+ "Stop": [
17
+ {
18
+ "hooks": [
19
+ {
20
+ "type": "command",
21
+ "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/typecheck-on-stop.sh",
22
+ "timeout": 120,
23
+ "statusMessage": "wavefront: typecheck"
24
+ }
25
+ ]
26
+ }
27
+ ]
28
+ }
29
+ }
@@ -0,0 +1,46 @@
1
+ ---
2
+ name: clean-architecture
3
+ description: Clean Architecture layers, data flow, and dependency rules for scaffold-nextjs-app modules. Read before implementing or reviewing any feature.
4
+ source: scaffold-nextjs-app/docs/module-architecture.md
5
+ source_version: 8edaa0b
6
+ ---
7
+
8
+ # Clean Architecture (frontend)
9
+
10
+ Feature modules use Clean Architecture: layered separation, dependency inversion, testability. Three layers.
11
+
12
+ ## Layers (top depends on bottom, never reverse)
13
+
14
+ ```
15
+ Views Layer → Components · Business hooks · Controller hooks
16
+ Application Layer → Selectors · Repositories · Stores
17
+ Infrastructure Layer → API · Gateways · Helpers
18
+ ```
19
+
20
+ - **Views** — UI render + interaction. Components are pure render. Business hooks = app logic/transforms/rules. Controller hooks = UI state/interaction.
21
+ - **Application** — Selectors = derived state. Repositories = data access + React Query. Stores = local state (Zustand).
22
+ - **Infrastructure** — API = HTTP + Zod validation. Gateways = data source abstraction (`http | localStorage | mock`). Helpers = pure utils.
23
+
24
+ ## Data flow (one direction)
25
+
26
+ ```
27
+ User → Controller hook → Business hook → Repository → Gateway → API → HTTP
28
+ ↓ ↓ ↓ ↓
29
+ UI state business logic React Query data source
30
+ ```
31
+
32
+ ## Dependency rules (hard)
33
+
34
+ 1. **Direction:** Views → Application → Infrastructure. Never skip a layer (no View calling API/gateway directly; always through repository).
35
+ 2. **Dependency inversion:** business layer defines interfaces; infrastructure implements them. Business logic never imports UI or infra concretes.
36
+ 3. **Boundaries are typed:** every cross-layer contract is a TypeScript interface. Zod validates at the API boundary only.
37
+ 4. **Pure where possible:** helpers and business logic are pure/testable; side effects live in hooks/controllers, never in render.
38
+ 5. **Hook split is mandatory:** separate business hook (data/logic) from controller hook (UI state). See [[hook-patterns]].
39
+
40
+ ## When this architecture applies
41
+
42
+ Use for: complex features, multi-data-source, long-lived apps, multi-dev teams, heavily-tested features.
43
+ Simpler approaches OK for: trivial CRUD with no business logic, throwaway prototypes.
44
+
45
+ ## Related skills
46
+ [[module-structure]] · [[naming-conventions]] · [[repository-pattern]] · [[gateway-pattern]] · [[selector-pattern]] · [[hook-patterns]] · [[store-zustand]]
@@ -0,0 +1,67 @@
1
+ ---
2
+ name: component-composition
3
+ description: Build UI by COMPOSITION not inheritance in scaffold-nextjs-app — decompose views into small components, compose via children/props, use compound components (the @/ui Modal pattern). Read before writing or modifying any view/component.
4
+ source: scaffold-nextjs-app/src/ui/modal, src/modules/todo/components
5
+ source_version: 8edaa0b
6
+ ---
7
+
8
+ # Component composition (over inheritance)
9
+
10
+ UI is built by **composing small components**, never by inheritance. Views don't cram form+list+states into one file — they compose focused subcomponents. React has no class inheritance here (functional only); reuse comes from composition: children, props, and compound components.
11
+
12
+ ## Rule 1 — decompose, don't monolith
13
+ A view orchestrates; it does NOT contain all the markup. Split by responsibility into `components/`:
14
+ ```
15
+ src/modules/<m>/
16
+ ├── components/
17
+ │ ├── <m>-form/<m>-form.component.tsx # the create/edit form
18
+ │ ├── <m>-item/<m>-item.component.tsx # one row/card
19
+ │ ├── <m>-list/<m>-list.component.tsx # the list (maps items)
20
+ │ └── index.ts
21
+ └── views/<m>-management/<m>-management.view.tsx # composes the above + hooks
22
+ ```
23
+ Reference: `src/modules/todo/components/{todo-form,todo-item,todo-list,todo-filters}` — each its own dir, `*.component.tsx`, `*.test.tsx`. The view wires them to the business/controller hooks.
24
+
25
+ ## When to extract a subcomponent (decision rule)
26
+
27
+ Apply to BOTH views and components — a component can (and often must) have its own subcomponents. Extract when ANY of these holds:
28
+
29
+ 1. **Mapped/repeated JSX → ALWAYS extract.** Anything rendered inside `.map()` is a subcomponent. A list that maps rows extracts the row: `contact-list` maps → `contact-item` is its subcomponent, never inline JSX in the `.map()`. (Hard rule — the most common miss.)
30
+ 2. **Distinct responsibility.** Form vs list vs item vs toolbar vs card = separate concerns → separate components.
31
+ 3. **Reused in >1 place** (or clearly will be) → extract so both consume one component.
32
+ 4. **Own state or handlers.** A chunk with its own `useState`/event logic → its own component (keeps the parent thin).
33
+ 5. **Size/complexity.** A JSX block past ~30–40 lines, or with nested conditionals, → extract a named piece.
34
+ 6. **Independently testable & nameable.** If you can give it a clear name and write a meaningful isolated test → it deserves to be a component.
35
+
36
+ **Do NOT extract** (keep inline): trivial markup (1–2 elements), no reuse, no own logic, no map. A lone `<Typography>` or a single `<Button>` is inline. Don't over-fragment.
37
+
38
+ **Components have subcomponents too** — extraction is recursive, not view-only. `contact-list` → `contact-item`; a `contact-item` with a complex actions cluster → `contact-item-actions`. Stop when the leaf is trivial markup.
39
+
40
+ Quick examples:
41
+ - list mapping rows → extract the row (rule 1). ✅ `contact-list` + `contact-item`.
42
+ - form with several fields + validation → `*-form.component` (rules 2,4,5).
43
+ - a single label + icon, used once → inline (no rule triggers).
44
+
45
+ ## Rule 2 — compose via children + props
46
+ - Pass data/handlers down as props; pass slots as `children`.
47
+ - A parent composes children; it does not extend them.
48
+ - Presentational components are pure (props in → UI out), `memo` + `displayName`.
49
+
50
+ ## Rule 3 — compound components for multi-part UI
51
+ When a component has structural parts (header / body / footer, etc.), use the **compound component** pattern: parent + sub-parts as separate components, attached to the parent with `Object.assign`, composed by the CONSUMER as children. The parent owns the shell; the consumer chooses which parts and in what order — no rigid all-in-one prop API, no inheritance. Each sub-part lives in its own dir (`components/panel-header/panel-header.component.tsx`) with its own colocated test; adding a part later doesn't change the parent's API.
52
+
53
+ Shape: `export const Panel = Object.assign(memo(PanelRoot), { Header, Body });` then `<Panel><Panel.Header/><Panel.Body/></Panel>`.
54
+
55
+ **Full worked example** (parent + parts + usage): see [examples/compound-component.md](examples/compound-component.md). In the scaffold, `@/ui` Modal implements this exact pattern (`Object.assign(memo(Modal), { Header, Content, Footer })`) — a live in-repo reference.
56
+
57
+ ## Anti-patterns (never)
58
+ - A 200-line view holding form + list + every state inline (decompose it).
59
+ - Boolean-prop explosion to switch variants (`isHeader`, `isFooter`, `showX`) → use composition/children instead.
60
+ - Trying to "extend" a component (no inheritance) — wrap or compose.
61
+ - Deeply coupled subcomponents that can't be tested alone — each component must be independently renderable + testable.
62
+
63
+ ## Each component is independently testable
64
+ Decomposition exists partly so every piece gets its own colocated test (see [[testing-jest-rtl]]). If a component can't be rendered/tested in isolation, it's too coupled — split responsibilities.
65
+
66
+ ## Related
67
+ [[design-system-inventory]] [[mui-design-tokens]] [[responsive-layouts]] [[hook-patterns]] [[testing-jest-rtl]] [[naming-conventions]]
@@ -0,0 +1,62 @@
1
+ # Compound-component example
2
+
3
+ Parent owns the shell; sub-parts are separate components attached via `Object.assign`; the CONSUMER composes them as children. No rigid all-in-one prop API, no inheritance. Each sub-part lives in its own dir with its own colocated test.
4
+
5
+ ```tsx
6
+ // panel/components/panel-header/panel-header.component.tsx
7
+ import { memo, type ReactNode } from 'react';
8
+ import { Box, Typography } from '@/ui';
9
+ import tokens from '@/styles/tokens';
10
+
11
+ interface PanelHeaderProps {
12
+ title: string;
13
+ action?: ReactNode;
14
+ }
15
+
16
+ export const PanelHeader = memo(({ title, action }: PanelHeaderProps) => (
17
+ <Box display="flex" justifyContent="space-between" sx={{ mb: tokens.spacing.scale2 }}>
18
+ <Typography variant="h6">{title}</Typography>
19
+ {action}
20
+ </Box>
21
+ ));
22
+ PanelHeader.displayName = 'PanelHeader';
23
+ ```
24
+
25
+ ```tsx
26
+ // panel/components/panel-body/panel-body.component.tsx
27
+ import { memo, type PropsWithChildren } from 'react';
28
+ import { Box } from '@/ui';
29
+ import tokens from '@/styles/tokens';
30
+
31
+ export const PanelBody = memo(({ children }: PropsWithChildren) => (
32
+ <Box sx={{ py: tokens.spacing.scale2 }}>{children}</Box>
33
+ ));
34
+ PanelBody.displayName = 'PanelBody';
35
+ ```
36
+
37
+ ```tsx
38
+ // panel/panel.component.tsx — parent shell + attached parts
39
+ import { memo, type PropsWithChildren } from 'react';
40
+ import { Card, CardContent } from '@/ui';
41
+ import { PanelHeader } from './components/panel-header/panel-header.component';
42
+ import { PanelBody } from './components/panel-body/panel-body.component';
43
+
44
+ const PanelRoot = memo(({ children }: PropsWithChildren) => (
45
+ <Card>
46
+ <CardContent>{children}</CardContent>
47
+ </Card>
48
+ ));
49
+ PanelRoot.displayName = 'Panel';
50
+
51
+ export const Panel = Object.assign(PanelRoot, { Header: PanelHeader, Body: PanelBody });
52
+ ```
53
+
54
+ ```tsx
55
+ // usage — consumer COMPOSES the parts (not a giant prop list)
56
+ <Panel>
57
+ <Panel.Header title={t('settings.title')} action={<Button>{t('common.save')}</Button>} />
58
+ <Panel.Body>{/* fields */}</Panel.Body>
59
+ </Panel>
60
+ ```
61
+
62
+ Adding a part later (e.g. `Panel.Footer`) doesn't change the parent's API. In the scaffold, `@/ui` Modal implements exactly this: `Object.assign(memo(Modal), { Header, Content, Footer })`.
@@ -0,0 +1,68 @@
1
+ ---
2
+ name: data-fetching-react-query
3
+ description: API layer (httpClient + Zod request/response validation + endpoints) and React Query config in scaffold-nextjs-app. Read before adding HTTP calls.
4
+ source: scaffold-nextjs-app/docs + src/modules/todo/api/todo-api.ts, src/api, src/core/lib/react-query
5
+ source_version: 8edaa0b
6
+ ---
7
+
8
+ # Data fetching: API layer + React Query
9
+
10
+ Infrastructure layer. The api file is the only place HTTP happens; gateways call it; repositories wrap gateways with React Query.
11
+
12
+ ## API service (per entity)
13
+
14
+ ```ts
15
+ // modules/[entity]/api/[entity]-api.ts
16
+ import { endpoints } from '@/api/endpoints';
17
+ import { httpClient } from '@/api/http-client';
18
+ import type { ApiOptions } from '@/api/api.types';
19
+ import { TodoSchema, TodoArraySchema, CreateTodoSchema, /* ... */ } from '@/modules/todo/todo.types';
20
+
21
+ export interface TodoApiContract {
22
+ getAll(filters?: TodoFilters, options?: ApiOptions): Promise<Todo[]>;
23
+ getById(id: string | number, options?: ApiOptions): Promise<Todo>;
24
+ create(todo: CreateTodoRequest, options?: ApiOptions): Promise<Todo>;
25
+ update(id: string | number, todo: UpdateTodoRequest, options?: ApiOptions): Promise<Todo>;
26
+ delete(id: string | number, options?: ApiOptions): Promise<void>;
27
+ }
28
+
29
+ const createTodoApiService = (): TodoApiContract => ({
30
+ async getAll(filters = {}, options) {
31
+ const response = await httpClient.get<Todo[]>(endpoints.TODO.BASE, {
32
+ params: filters,
33
+ requestSchema: TodoFiltersSchema, // validates outgoing
34
+ responseSchema: TodoArraySchema, // validates incoming
35
+ signal: options?.signal,
36
+ });
37
+ return response.data;
38
+ },
39
+ async create(todo, options) {
40
+ const response = await httpClient.post<Todo>(endpoints.TODO.BASE, todo, {
41
+ requestSchema: CreateTodoSchema, responseSchema: TodoSchema, signal: options?.signal,
42
+ });
43
+ return response.data;
44
+ },
45
+ // ...
46
+ });
47
+
48
+ export const todoApi = createTodoApiService(); // singleton
49
+ ```
50
+
51
+ ## Key conventions
52
+ - **Define a `XApiContract` interface**, implement via `createXApiService()`, export a **singleton** `xApi`.
53
+ - **Zod at the boundary**: pass `requestSchema` + `responseSchema` to `httpClient`. Schemas live in `[entity].types.ts` (co-located with types). This is the ONLY validation point for shape; domain rules go in the gateway.
54
+ - **Endpoints centralized**: `endpoints.TODO.BASE`, `endpoints.TODO.BY_ID(id)` from `@/api/endpoints`. Never inline URL strings.
55
+ - **httpClient** from `@/api/http-client` (axios-based wrapper). Forward `signal: options?.signal` for cancellation everywhere.
56
+ - `ApiOptions` (`@/api/api.types`) ≈ `{ signal?: AbortSignal }`.
57
+
58
+ ## React Query config / helpers
59
+ - Provider + client in `@/core/lib/react-query` (`getQueryClient`, `createPrefetchFunction`). Use `createPrefetchFunction` for server-component prefetch; `getQueryClient()` for imperative cancel.
60
+ - Query defaults set per-query in query-options (`staleTime`, `gcTime`, `enabled`, `retry`).
61
+ - Repository composes all this — see [[repository-pattern]].
62
+
63
+ ## Rules
64
+ - Components/hooks NEVER import `httpClient` or `xApi` directly → go through repository → gateway → api.
65
+ - One schema source of truth in `[entity].types.ts`; derive TS types from Zod where practical.
66
+
67
+ ## Related skills
68
+ [[repository-pattern]] · [[gateway-pattern]] · [[forms-rhf-zod]]
@@ -0,0 +1,69 @@
1
+ ---
2
+ name: design-intake
3
+ description: Normalize any design source (Figma — live via MCP or as an export — screenshot, mockup, HTML/CSS, a reference screen, a text description, or NOTHING) into a design-spec mapped to this app's @/ui + tokens. Read when a view/component has a visual to match or needs to stay continuous with the app.
4
+ source: wavefront framework meta — pairs with design-system-inventory
5
+ source_version: 8edaa0b
6
+ ---
7
+
8
+ # Design intake
9
+
10
+ Turn any design input into a **design-spec** the builder can implement. Every source converges to the same destination: `@/ui` components + `@/styles/tokens`, composed like the existing app. Intake does NOT pixel-perfect-render arbitrary markup — it **translates** to the design system. Always read [[design-system-inventory]] first.
11
+
12
+ ## Accepted sources (8) and how to read each
13
+ 1a. **Figma export (image/SVG/PDF)** — `Read` it (multimodal). Infer layout, hierarchy, spacing, component intent. Use when no Figma MCP is configured (the fallback for #1b).
14
+ 1b. **Figma via MCP (live)** — when a Figma MCP server is configured, pull exact frames + variables instead of inferring from an image. **Preferred over #1a whenever available** — see the *Figma source detection* rule in Procedure and the *Figma variables → tokens* section below.
15
+ 2. **Screenshot (PNG/JPG)** — `Read` (multimodal) → infer components + hierarchy. Estimate spacing → nearest `tokens.spacing.scaleN`.
16
+ 3. **Mockup / wireframe (low-fidelity)** — structure only; take layout, apply app's styling.
17
+ 4. **HTML/CSS pasted** (v0, codepen, etc) — parse markup/styles → MAP each element to its `@/ui` equivalent + tokens. Discard raw tags/inline styles.
18
+ 5. **Reference screen** ("like the todo page") — read that screen's code → replicate its pattern.
19
+ 6. **Text description** ("card with title, list, add button") — infer components from description + inventory.
20
+ 7. **Existing-app continuity** — no explicit design → derive from inventory + closest existing screen (the no-design path; see [[design-system-inventory]] continuity rule).
21
+ 8. **None at all** — same as #7.
22
+
23
+ ## Output: design-spec
24
+ ```
25
+ ## Design spec: <view/component>
26
+ Source: <which of the 8 — for Figma, note "MCP-live (<server>)" or "image-fallback">
27
+ Layout: <containers/grid/flex structure>
28
+ Components (intent → @/ui): <e.g. "search box → TextField; priority → Selector; add → Button">
29
+ Tokens: <spacing/color/typography tokens to use>
30
+ States: loading | empty | error | populated — <how each renders>
31
+ Interactions: <what triggers what; goes to controller hook>
32
+ Responsive: <MANDATORY — behavior at minMobile/minTablet/minDesktop. If the source is one viewport, ASK the user first, then record it here.>
33
+ Gaps: <design elements with NO @/ui equivalent — see below>
34
+ ```
35
+
36
+ ## Procedure
37
+ 1. Read [[design-system-inventory]] (component/token catalog + composition patterns).
38
+ 2. Identify the source type. If it's an image/Figma export/screenshot → `Read` the file (multimodal). If HTML → read the markup. If a reference screen → read its code. If none → continuity path.
39
+ 2b. **Figma source detection (live MCP vs image):** if the source is Figma, check whether Figma MCP tools are available this session:
40
+ - **Official Figma Dev Mode MCP** — `get_code` (structure/components), `get_variable_defs` (color/spacing/typography variables), `get_image` (visual cross-check). Selection/node-based; needs the Figma desktop app with Dev Mode.
41
+ - **Framelink (community) figma-mcp** — `get_figma_data` (structure + variables in one call), `download_figma_images` (assets). Figma-link/node-based; uses the Figma API.
42
+ - **If available → live path (#1b):** pull structure (`get_code` / `get_figma_data`) for layout + component intent, pull variables (`get_variable_defs` / from `get_figma_data`) for exact tokens, optionally pull an image (`get_image` / `download_figma_images`) only to cross-check. Map per *Figma variables → tokens* below. Record `Source: MCP-live (<server>)`.
43
+ - **If NOT available → image fallback (#1a):** treat the Figma export as an image; infer + estimate. Record `Source: image-fallback`. Never error on missing MCP.
44
+ 3. Map every visual element to a real `@/ui` component + tokens. Estimate spacing to the nearest scale token (for #1b, prefer the EXACT variable from `get_variable_defs`/`get_figma_data` over estimation).
45
+ 4. Define the four states (loading/empty/error/populated) even if the source only shows the happy path — propose them.
46
+ 4b. **Responsive (mandatory):** define behavior at minMobile/minTablet/minDesktop. Most sources are ONE viewport — when responsive isn't specified, ASK the user how key elements adapt (stack/wrap/hide, column counts, nav pattern), offering mobile-first defaults; record the answer. Never ship a single-viewport layout. See [[responsive-layouts]].
47
+ 5. **Gap-detection:** list anything the design shows that `@/ui` can't express. For each gap, propose: (a) approximate with existing components, or (b) add a new `@/ui` component (Ask-first — touches the design system). Never hand-roll a raw `<div>`/`<input>` to match a pixel.
48
+ 6. Output the design-spec into the work item's plan; the builder implements from it.
49
+
50
+ ## Rules
51
+ - Translate to the design system, don't replicate foreign markup.
52
+ - Spacing/color/typography → tokens, never literals (even if the source has exact px — snap to scale).
53
+ - Always specify the four states.
54
+ - Continuity over fidelity: when in doubt, match the existing app, not the mockup's incidental styling.
55
+ - Gaps are Ask-first if they imply new `@/ui` components.
56
+
57
+ ## Figma variables → tokens (live MCP path, #1b)
58
+ When a Figma MCP yields variables (via `get_variable_defs` or inside `get_figma_data`), map them to `@/styles/tokens` (Style Dictionary; see [[mui-design-tokens]]) — exact, not estimated:
59
+ - **Spacing/size variables** → exact `tokens.spacing.scaleN` (match the value; only snap to nearest if no exact scale exists).
60
+ - **Color variables** → `tokens.colors.*` by role/name. No token equivalent → **Gap** (Ask-first: approximate with an existing token or add to the token set — never a literal hex).
61
+ - **Typography variables** → `tokens.typography.*`.
62
+ - **Breakpoints / auto-layout** → `tokens.breakpoints.*` + flex/grid; feeds the mandatory `Responsive:` line.
63
+ - Rule unchanged: tokens never literals — now sourced from real variables instead of guessed.
64
+
65
+ ## Notes — Figma MCP (live; server-configured by the user)
66
+ A Figma MCP gives exact frames, auto-layout, and design variables (vs inferring from an image). Wavefront ships only the detection + mapping rules — the user configures their own server (official Dev Mode MCP or Framelink). When none is present, Figma degrades to the exported-image path (#1a) with no error. Tracked in `docs/roadmap.md` F6 notes.
67
+
68
+ ## Related
69
+ [[design-system-inventory]] [[responsive-layouts]] [[mui-design-tokens]] [[requirement-intake]] [[hook-patterns]] [[i18n-next-intl]]
@@ -0,0 +1,38 @@
1
+ ---
2
+ name: design-system-inventory
3
+ description: Catalog of the app's design system (@/ui components, @/styles/tokens, and how real screens compose them) so generated UI is visually continuous with the rest of the app. Read before building any view/component, especially when no design is provided.
4
+ source: scaffold-nextjs-app/src/ui, src/styles, src/modules/*/views + components
5
+ source_version: 8edaa0b
6
+ ---
7
+
8
+ # Design system inventory
9
+
10
+ The continuity engine. Whatever the design source (or none), generated UI must look like the rest of THIS app. That means: build from `@/ui`, space with `@/styles/tokens`, and compose the way existing screens do. **Build the inventory by reading the real code — do not assume.**
11
+
12
+ ## Step 0 — inventory the actual app (do this each run)
13
+ 1. `@/ui` exports: read `src/ui/index.ts` → the available components (Button, TextField, Selector, Dialog, Modal, Card, Box, Typography, Container, Grid, List, Checkbox, RadioButton, Switch, Pagination, Toast, etc. — read the real list, it evolves).
14
+ 2. Tokens: read `src/styles/tokens.ts` + subdirs → `tokens.spacing.scaleN`, `tokens.colors.*`, `tokens.typography.*`, `tokens.breakpoints.*`. Use these, never literals.
15
+ 3. Composition patterns: read 1–2 existing views (`src/modules/todo/views/...`, `src/modules/about/...`) → how they lay out forms, lists, cards; what wraps what; how loading/empty/error states render.
16
+
17
+ ## Component selection map (intent → @/ui)
18
+ Match design intent to the real component, never raw `@mui/material`:
19
+ - text input → `TextField` (uncontrolled `onChange:(value)=>` or RHF `control`).
20
+ - choice/dropdown → `Selector`; toggle → `Switch`/`ToggleButton`; multi → `Checkbox`/`RadioButton`.
21
+ - action → `Button` (`variant="contained|outlined"`).
22
+ - container/section → `Card`+`CardContent`, `Box`, `Container` (theme breakpoints e.g. `maxWidth="largeScreen"`).
23
+ - layout → `Grid`, `Box` (`display="flex"`, `sx={{ gap: tokens.spacing.scaleN }}`).
24
+ - list → `List` + items; feedback → `Toast`/`Dialog`/`Modal`; loading → `CircularProgress`/`LoadingIndicator`/`Skeleton`.
25
+ If the design needs something with NO `@/ui` equivalent → flag it (see [[design-intake]] gap-detection): propose adding to `@/ui` or approximating, don't hand-roll a raw element.
26
+
27
+ ## Composition conventions (from real screens)
28
+ - Functional components, explicit Props interface, named export, `memo` + `displayName` for presentational, `'use client'` when stateful.
29
+ - Spacing/gap/margins via `tokens.spacing.scaleN`. Colors/typography via tokens.
30
+ - Always cover the **four states**: loading, empty, error, populated (existing views do — match them).
31
+ - View = render only; data via business hook, UI state via controller hook ([[hook-patterns]]).
32
+ - Strings via next-intl `t()` ([[i18n-next-intl]]); never literals.
33
+
34
+ ## Continuity rule (no-design case)
35
+ When no design is supplied: pick the existing screen closest in purpose (a list feature → mirror the todo list; a form → mirror the about form), and reuse its component choices, layout, spacing rhythm, and state handling. Output should be indistinguishable in style from a screen a teammate wrote.
36
+
37
+ ## Related
38
+ [[design-intake]] [[mui-design-tokens]] [[hook-patterns]] [[i18n-next-intl]] [[module-structure]]
@@ -0,0 +1,75 @@
1
+ ---
2
+ name: forms-rhf-zod
3
+ description: React Hook Form + Zod resolver + @/ui controlled inputs + i18n error keys in scaffold-nextjs-app. Read before building a validated form.
4
+ source: scaffold-nextjs-app/src/modules/about/views/about-view + @/ui form components
5
+ source_version: 8edaa0b
6
+ ---
7
+
8
+ # Forms: React Hook Form + Zod
9
+
10
+ Views layer. Validated forms use `useForm` + `zodResolver`, render `@/ui` inputs wired via `control`, and surface error messages as **i18n keys**.
11
+
12
+ > Note: simple transient forms (e.g. todo-form) may use local `useState`. Use RHF+Zod when there is real validation. This is the validated-form reference.
13
+
14
+ ## Schema + type, colocated in `*.types.ts`
15
+
16
+ ```ts
17
+ import { z } from 'zod';
18
+
19
+ export const FormPersonalInfoInputSchema = z.object({
20
+ name: z.string().min(1, 'validation.name.required'), // message = i18n key
21
+ surname: z.string().min(1, 'validation.surname.required'),
22
+ email: z.string().email('validation.email.invalid'),
23
+ });
24
+ export type FormPersonalInfoInput = z.infer<typeof FormPersonalInfoInputSchema>;
25
+ ```
26
+ Zod messages are **translation keys**, resolved with `next-intl` `t()` at render.
27
+
28
+ ## Form component
29
+
30
+ ```tsx
31
+ 'use client';
32
+ import { useForm } from 'react-hook-form';
33
+ import { zodResolver } from '@hookform/resolvers/zod';
34
+ import { useTranslations } from 'next-intl';
35
+ import { Box, Button, Grid, TextField } from '@/ui';
36
+ import { FormPersonalInfoInput, FormPersonalInfoInputSchema } from './about-view.types';
37
+
38
+ export const AboutView = () => {
39
+ const t = useTranslations();
40
+ const { control, handleSubmit, formState: { errors, isValid } } = useForm<FormPersonalInfoInput>({
41
+ mode: 'onTouched',
42
+ defaultValues: { name: '', surname: '', email: '' },
43
+ resolver: zodResolver(FormPersonalInfoInputSchema),
44
+ });
45
+
46
+ const onSubmit = (data: FormPersonalInfoInput) => { /* call business hook / mutation */ };
47
+
48
+ return (
49
+ <form noValidate onSubmit={handleSubmit(onSubmit)}>
50
+ <TextField
51
+ required
52
+ control={control} // @/ui TextField is RHF-aware
53
+ name="name"
54
+ label="Name"
55
+ errorMessage={errors.name?.message && t(errors.name.message)} // key → translation
56
+ />
57
+ <Button disabled={!isValid} type="submit" variant="outlined">Submit</Button>
58
+ </form>
59
+ );
60
+ };
61
+ ```
62
+
63
+ ## Conventions
64
+ - `@/ui` form inputs (`TextField`, `Selector`, `RadioButton`, `TimePicker`, ...) accept RHF `control` + `name`; pass them rather than wiring `register` manually.
65
+ - `mode: 'onTouched'`; gate submit with `formState.isValid`.
66
+ - Error messages: Zod returns a key, render with `t(errors.field?.message)`. See [[i18n-next-intl]].
67
+ - Submit handler delegates to the business hook / repository mutation — no fetching in the form.
68
+
69
+ ## Rules
70
+ - Schema is single source of truth; derive the TS type with `z.infer`.
71
+ - Validation keys are real i18n keys; don't hardcode user-facing strings.
72
+ - `'use client'` on form components.
73
+
74
+ ## Related skills
75
+ [[i18n-next-intl]] · [[mui-design-tokens]] · [[data-fetching-react-query]] · [[hook-patterns]]
@@ -0,0 +1,79 @@
1
+ ---
2
+ name: gateway-pattern
3
+ description: Data-source abstraction (http | localStorage) via gateway factory in scaffold-nextjs-app. Read before writing a module's data access.
4
+ source: scaffold-nextjs-app/docs/gateway-pattern.md + src/modules/todo/repositories/todo/gateways
5
+ source_version: 8edaa0b
6
+ ---
7
+
8
+ # Gateway pattern
9
+
10
+ Gateways abstract the data source so the rest of the module is agnostic to http vs localStorage. Infrastructure layer.
11
+
12
+ ## Pieces (per entity)
13
+
14
+ ```
15
+ repositories/[entity]/gateways/
16
+ ├── http-gateway/http-gateway.ts
17
+ ├── local-storage-gateway/local-storage-gateway.ts # kebab dir
18
+ │ └── helpers/[name]/[name].helper.ts
19
+ ├── [entity].gateway.types.ts # interface extends BaseGateway
20
+ └── index.ts # factory + re-exports
21
+ ```
22
+
23
+ ## Interface (extends shared BaseGateway)
24
+
25
+ ```ts
26
+ // [entity].gateway.types.ts
27
+ import type { BaseGateway, GatewayOptions } from '@/types/gateway.types';
28
+
29
+ export interface TodoGateway extends BaseGateway {
30
+ findAll(filters?: TodoFilters, options?: GatewayOptions): Promise<Todo[]>;
31
+ findById(id: string | number, options?: GatewayOptions): Promise<Todo>;
32
+ create(todo: CreateTodoRequest, options?: GatewayOptions): Promise<Todo>;
33
+ update(id: string | number, todo: UpdateTodoRequest, options?: GatewayOptions): Promise<Todo>;
34
+ delete(id: string | number, options?: GatewayOptions): Promise<void>;
35
+ }
36
+ ```
37
+ `GatewayOptions` carries `signal?: AbortSignal`. `BaseGateway` provides `getSourceInfo()`.
38
+
39
+ ## Concrete gateway = factory returning the interface
40
+
41
+ ```ts
42
+ // http-gateway/http-gateway.ts
43
+ export const createHttpTodoGateway = (): TodoGateway => ({
44
+ async findAll(filters, options) { return todoApi.getAll(filters, options); },
45
+ async create(todo, options) {
46
+ validateTodoData(todo); // gateway-level domain validation
47
+ return todoApi.create(todo, options);
48
+ },
49
+ // ...
50
+ getSourceInfo() {
51
+ return { type: 'http', name: 'HTTP API Gateway',
52
+ capabilities: { offline: false, realtime: true, persistence: true } };
53
+ },
54
+ });
55
+ ```
56
+ - HTTP gateway delegates to the **api layer** ([[data-fetching-react-query]] uses the api via gateway). localStorage gateway implements the same interface against `localStorage`, with pure helpers in `helpers/`.
57
+ - Domain validation (e.g. `validateTodoData`) lives in the gateway, not the api.
58
+
59
+ ## Factory selector
60
+
61
+ ```ts
62
+ // gateways/index.ts
63
+ export const createTodoGateway = (source: DataSource = 'http'): TodoGateway => {
64
+ switch (source) {
65
+ case 'http': return createHttpTodoGateway();
66
+ case 'localStorage': return createLocalStorageTodoGateway();
67
+ default: return createHttpTodoGateway();
68
+ }
69
+ };
70
+ ```
71
+ `DataSource = 'http' | 'localStorage'` from `@/types/gateway.types` (TWO sources in the shipped scaffold — verify the union before assuming `'mock'`). Repository/query-options call `createXGateway(dataSource)` — never import a concrete gateway directly. Keep the long gateway-types import RELATIVE (`../notification.gateway.types`) inside `gateways/` to stay under `max-len 120`.
72
+
73
+ ## Rules
74
+ - One interface per entity extending `BaseGateway`; all sources implement it identically.
75
+ - Gateways return domain types, accept `GatewayOptions` (forward `signal`).
76
+ - No React, no React Query here — pure data access. Repository layer wraps it.
77
+
78
+ ## Related skills
79
+ [[repository-pattern]] · [[data-fetching-react-query]] · [[clean-architecture]]
@@ -0,0 +1,74 @@
1
+ ---
2
+ name: hook-patterns
3
+ description: Mandatory business-hook vs controller-hook separation in scaffold-nextjs-app views. Read before writing any view/feature hook.
4
+ source: scaffold-nextjs-app/docs/hook-patterns.md + src/modules/todo/views/todo-management/hooks
5
+ source_version: 8edaa0b
6
+ ---
7
+
8
+ # Hook patterns (business vs controller)
9
+
10
+ Views layer. REQUIRED split: business hook (data + domain logic) and controller hook (UI state + interactions). The view component consumes both and stays render-only.
11
+
12
+ ```
13
+ views/[view]/hooks/
14
+ ├── use-[view]-business/use-[view]-business.hook.ts
15
+ └── use-[view]-controller/use-[view]-controller.hook.ts
16
+ ```
17
+
18
+ ## Business hook — data, logic, transforms
19
+
20
+ ```ts
21
+ 'use client';
22
+ import { useCallback, useMemo } from 'react';
23
+ import { todoRepository } from '@/modules/todo/repositories/todo';
24
+ import { useTodoStatsSelector, useFiltersSelector } from '@/modules/todo/selectors';
25
+ import { useTodoActions } from '@/modules/todo/stores/todo.store.actions';
26
+ import type { DataSource } from '@/types/gateway.types';
27
+
28
+ export const useTodoManagementBusiness = (dataSource: DataSource = 'http') => {
29
+ const { filters } = useFiltersSelector();
30
+ const { setFilters } = useTodoActions();
31
+
32
+ const todosQuery = todoRepository.queries.useTodos(filters, dataSource);
33
+ const createMutation = todoRepository.mutations.useCreateTodo(dataSource);
34
+
35
+ const createTodo = useCallback(async (data: CreateTodoRequest) => {
36
+ if (!data.title?.trim()) throw new Error('Todo title is required'); // business rule
37
+ return createMutation.mutate({ ...data, title: data.title.trim() });
38
+ }, [createMutation]);
39
+
40
+ const todos = useMemo(() => (todosQuery.data ?? []).map((t) => ({ ...t /* presentation shape */ })), [todosQuery.data]);
41
+
42
+ return {
43
+ createTodo, applyFilters: (f) => setFilters({ ...filters, ...f }), // actions
44
+ todos, filters, // data
45
+ isLoading: todosQuery.isLoading, isCreating: createMutation.isPending, hasError: !!todosQuery.error, // states
46
+ refetch: todosQuery.refetch, // utilities
47
+ };
48
+ };
49
+ ```
50
+ Business hook = repository + selectors + store actions, wrapped in `useCallback`/`useMemo`, enforcing domain rules. Returns grouped: **actions / data / states / utilities**.
51
+
52
+ ## Controller hook — UI state, interactions
53
+
54
+ Owns local UI concerns: dialog open/close, selected tab, form-open flags, handlers that translate user events into business calls. Reads business hook output, holds `useState` for view-only state. No data fetching, no API.
55
+
56
+ ## View component — render only
57
+
58
+ ```tsx
59
+ 'use client';
60
+ export const TodoManagementView = () => {
61
+ const business = useTodoManagementBusiness();
62
+ const controller = useTodoManagementController(business);
63
+ return (/* JSX wiring business data + controller handlers */);
64
+ };
65
+ ```
66
+
67
+ ## Rules
68
+ - Never fetch or mutate in the component body (no side effects in render).
69
+ - Business hook: domain logic + data. Controller hook: UI state + event wiring. Don't mix.
70
+ - `'use client'` on hooks/views that use React state/effects.
71
+ - Shared (non-view) hooks live in `modules/[m]/hooks/[name]/[name].hook.ts`.
72
+
73
+ ## Related skills
74
+ [[repository-pattern]] · [[selector-pattern]] · [[store-zustand]] · [[clean-architecture]]