@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.
- package/LICENSE +21 -0
- package/README.md +226 -0
- package/agents/i18n-tokens.md +58 -0
- package/agents/mapper.md +64 -0
- package/agents/module-builder.md +90 -0
- package/agents/reviewer.md +65 -0
- package/agents/tester.md +84 -0
- package/bin/wavefront.js +91 -0
- package/commands/wavefront-change.md +26 -0
- package/commands/wavefront-execute.md +28 -0
- package/commands/wavefront-feature.md +25 -0
- package/commands/wavefront-fix.md +28 -0
- package/commands/wavefront-init.md +27 -0
- package/commands/wavefront-plan.md +30 -0
- package/commands/wavefront-ship.md +26 -0
- package/commands/wavefront-state.md +23 -0
- package/commands/wavefront-verify.md +24 -0
- package/hooks/lint-after-edit.sh +43 -0
- package/hooks/typecheck-on-stop.sh +27 -0
- package/install.sh +83 -0
- package/package.json +67 -0
- package/planning-templates/PHASE_PLAN.md +37 -0
- package/planning-templates/PROJECT.md +18 -0
- package/planning-templates/REQUIREMENTS.md +27 -0
- package/planning-templates/ROADMAP.md +12 -0
- package/planning-templates/STATE.md +21 -0
- package/settings.template.json +29 -0
- package/skills/clean-architecture/SKILL.md +46 -0
- package/skills/component-composition/SKILL.md +67 -0
- package/skills/component-composition/examples/compound-component.md +62 -0
- package/skills/data-fetching-react-query/SKILL.md +68 -0
- package/skills/design-intake/SKILL.md +69 -0
- package/skills/design-system-inventory/SKILL.md +38 -0
- package/skills/forms-rhf-zod/SKILL.md +75 -0
- package/skills/gateway-pattern/SKILL.md +79 -0
- package/skills/hook-patterns/SKILL.md +74 -0
- package/skills/i18n-next-intl/SKILL.md +59 -0
- package/skills/module-structure/SKILL.md +98 -0
- package/skills/mui-design-tokens/SKILL.md +60 -0
- package/skills/naming-conventions/SKILL.md +65 -0
- package/skills/repository-pattern/SKILL.md +128 -0
- package/skills/requirement-intake/SKILL.md +65 -0
- package/skills/responsive-layouts/SKILL.md +64 -0
- package/skills/selector-pattern/SKILL.md +59 -0
- package/skills/store-zustand/SKILL.md +63 -0
- package/skills/sync-audit/SKILL.md +52 -0
- package/skills/testing-jest-rtl/SKILL.md +83 -0
- package/uninstall.sh +36 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: i18n-next-intl
|
|
3
|
+
description: next-intl usage in scaffold-nextjs-app — useTranslations, locale routing via @/i18n/routing, remote-fetched messages, validation keys. Read before adding user-facing strings.
|
|
4
|
+
source: scaffold-nextjs-app/docs/intl.md + src/i18n/request.ts, src/i18n/routing.ts
|
|
5
|
+
source_version: 8edaa0b
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# i18n: next-intl
|
|
9
|
+
|
|
10
|
+
All user-facing strings go through next-intl. No hardcoded copy in components.
|
|
11
|
+
|
|
12
|
+
## Translating strings
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
'use client';
|
|
16
|
+
import { useTranslations } from 'next-intl';
|
|
17
|
+
|
|
18
|
+
export const Greeting = () => {
|
|
19
|
+
const t = useTranslations(); // or useTranslations('namespace')
|
|
20
|
+
return <Typography>{t('home.title')}</Typography>;
|
|
21
|
+
};
|
|
22
|
+
```
|
|
23
|
+
- Client components: `useTranslations()` hook.
|
|
24
|
+
- Server components: `getTranslations()` from `next-intl/server`.
|
|
25
|
+
- Keys are dot-paths (`home.title`, `validation.email.invalid`).
|
|
26
|
+
|
|
27
|
+
## Locale-aware navigation (use @/i18n/routing, NOT next/link)
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { Link, redirect, usePathname, useRouter } from '@/i18n/routing';
|
|
31
|
+
```
|
|
32
|
+
`routing` is built with `defineRouting({ locales, defaultLocale })` + `createNavigation`. Locales come from `config.translation`. Routes use the `[locale]` App Router segment.
|
|
33
|
+
|
|
34
|
+
## Messages are remote-fetched
|
|
35
|
+
|
|
36
|
+
`src/i18n/request.ts` (`getRequestConfig`) fetches messages from the settings API per locale into the `common` namespace (with `revalidate`). There is **no local `messages/en.json`** to edit by default — translation strings live server-side. When adding a key:
|
|
37
|
+
- Reference it in code (`t('feature.key')`).
|
|
38
|
+
- Coordinate the actual translation value with the settings/translation backend (per `docs/intl.md`), not a local file — unless the project has been changed to local messages.
|
|
39
|
+
|
|
40
|
+
## Validation messages = i18n keys
|
|
41
|
+
|
|
42
|
+
Zod schemas return translation keys; render with `t()`:
|
|
43
|
+
```ts
|
|
44
|
+
z.string().email('validation.email.invalid')
|
|
45
|
+
// in form: errorMessage={errors.email?.message && t(errors.email.message)}
|
|
46
|
+
```
|
|
47
|
+
See [[forms-rhf-zod]].
|
|
48
|
+
|
|
49
|
+
## Conventions
|
|
50
|
+
- Namespace keys by feature/module (`todo.*`, `auth.*`, `validation.*`).
|
|
51
|
+
- Never hardcode visible text or error strings.
|
|
52
|
+
- In tests, next-intl is mocked (`test/__mocks__/next-intl`) — `t('key')` typically returns the key; assert accordingly.
|
|
53
|
+
|
|
54
|
+
## Rules
|
|
55
|
+
- Components stay copy-free: text via `t()`, links via `@/i18n/routing`.
|
|
56
|
+
- New keys: add usage in code + register the value with the translation source.
|
|
57
|
+
|
|
58
|
+
## Related skills
|
|
59
|
+
[[forms-rhf-zod]] · [[mui-design-tokens]] · [[testing-jest-rtl]]
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: module-structure
|
|
3
|
+
description: Exact folder/file shape of a feature module and the src/ tree in scaffold-nextjs-app. Read before scaffolding or placing any file.
|
|
4
|
+
source: scaffold-nextjs-app/docs/project-structure.md
|
|
5
|
+
source_version: 8edaa0b
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Module structure
|
|
9
|
+
|
|
10
|
+
## Module shape (every feature module)
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
src/modules/[module-name]/
|
|
14
|
+
├── api/ # HTTP integration + Zod validation
|
|
15
|
+
├── components/ # module-specific UI
|
|
16
|
+
├── repositories/
|
|
17
|
+
│ └── [entity]/
|
|
18
|
+
│ ├── gateways/
|
|
19
|
+
│ │ ├── http-gateway/
|
|
20
|
+
│ │ ├── local-storage-gateway/ # kebab (NOT localStorage-gateway)
|
|
21
|
+
│ │ │ └── helpers/[name]/[name].helper.ts
|
|
22
|
+
│ │ ├── index.ts
|
|
23
|
+
│ │ └── [entity].gateway.types.ts
|
|
24
|
+
│ ├── helpers/[name]/[name].helper.ts # pure business utils, one dir each
|
|
25
|
+
│ ├── index.ts
|
|
26
|
+
│ ├── [entity].query-options.ts
|
|
27
|
+
│ ├── [entity].repository.keys.ts # React Query key factory
|
|
28
|
+
│ ├── [entity].repository.types.ts
|
|
29
|
+
│ ├── [entity].repository.queries.ts
|
|
30
|
+
│ └── [entity].repository.mutations.ts
|
|
31
|
+
├── selectors/[name]-selector/[name]-selector.hook.ts # one dir per selector
|
|
32
|
+
├── stores/
|
|
33
|
+
│ ├── [module].store.ts
|
|
34
|
+
│ ├── [module].store.actions.ts
|
|
35
|
+
│ └── [module].store.types.ts
|
|
36
|
+
├── views/
|
|
37
|
+
│ └── [view]/
|
|
38
|
+
│ ├── [view].view.tsx
|
|
39
|
+
│ ├── hooks/use-[view]-business/use-[view]-business.hook.ts
|
|
40
|
+
│ ├── hooks/use-[view]-controller/use-[view]-controller.hook.ts
|
|
41
|
+
│ └── helpers/[name]/[name].helper.ts # view-scoped helpers
|
|
42
|
+
├── hooks/[name]/[name].hook.ts # shared module hooks
|
|
43
|
+
├── index.ts # public API (barrel)
|
|
44
|
+
└── [module].types.ts # module types
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Reference modules in scaffold: `src/modules/todo` (full: stores, repositories, components, hooks, api, selectors, views), `src/modules/auth` (stores, repositories, api, selectors, views). Mirror their shape.
|
|
48
|
+
|
|
49
|
+
Nesting note: components, views, selectors, hooks each use a **one-dir-per-unit** pattern (`use-x-selector/use-x-selector.hook.ts`). Views and components may nest their own `hooks/` (business+controller) and `helpers/`. Tests colocate next to source as `*.test.ts(x)`.
|
|
50
|
+
|
|
51
|
+
## Surrounding src/ tree (do not put module code here)
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
src/
|
|
55
|
+
├── api/ # SHARED api config: endpoints.ts, http-client.ts, types.ts
|
|
56
|
+
├── app/ # Next.js App Router; [locale]/ i18n routes; layout.tsx
|
|
57
|
+
├── components/ # project-wide reusable components (not design-system-generic)
|
|
58
|
+
├── core/ # cross-project shared: components, hooks, helpers, layouts,
|
|
59
|
+
│ # lib/ (react-query/, zustand/)
|
|
60
|
+
├── hooks/ # app-wide custom hooks
|
|
61
|
+
├── i18n/ # internationalization config
|
|
62
|
+
├── modules/ # ← feature modules live here
|
|
63
|
+
├── navigation/ # routing helpers
|
|
64
|
+
├── shared/ # api/, gateways/ (base gateway types), types/
|
|
65
|
+
├── styles/ # design tokens + styling
|
|
66
|
+
├── types/ # global TS types
|
|
67
|
+
└── ui/ # design system components (Button, Modal, Form, ...)
|
|
68
|
+
test/ # utils/ (provider wrappers), entities/ (faker factories), __mocks__/
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Import aliases (tsconfig paths)
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
@/* → ./src/*
|
|
75
|
+
@/components/* → ./src/components/*
|
|
76
|
+
@/modules/* → ./src/modules/*
|
|
77
|
+
@/core/* → ./src/core/*
|
|
78
|
+
@/shared/* → ./src/shared/*
|
|
79
|
+
@/ui/* → ./src/ui/*
|
|
80
|
+
@test/* → ./test/*
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Always use absolute imports. Examples:
|
|
84
|
+
```ts
|
|
85
|
+
import { Button } from '@/ui';
|
|
86
|
+
import { todoRepository } from '@/modules/todo';
|
|
87
|
+
import type { Todo } from '@/modules/todo';
|
|
88
|
+
import { renderWithProviders, createMockTodo } from '@test/utils';
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Placement rules
|
|
92
|
+
|
|
93
|
+
- Module-only code → inside the module. Cross-module reusable → `core/` (no business logic) or `components/`.
|
|
94
|
+
- Generic design-system UI → `ui/`. Base gateway types → `shared/gateways/`.
|
|
95
|
+
- Each dir exports via `index.ts` barrel; module's `index.ts` is its only public API.
|
|
96
|
+
|
|
97
|
+
## Related skills
|
|
98
|
+
[[clean-architecture]] · [[naming-conventions]] · [[repository-pattern]] · [[gateway-pattern]]
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mui-design-tokens
|
|
3
|
+
description: Build UI from the @/ui design system + @/styles/tokens (Style Dictionary), never raw @mui/material or hardcoded values, in scaffold-nextjs-app.
|
|
4
|
+
source: scaffold-nextjs-app/docs/design-tokens.md, project-structure.md + src/modules/todo/components
|
|
5
|
+
source_version: 8edaa0b
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# MUI + design tokens
|
|
9
|
+
|
|
10
|
+
Views layer. Components are built from the project design system, not MUI directly, and spacing/scale come from tokens, not magic numbers.
|
|
11
|
+
|
|
12
|
+
## Import from `@/ui`, not `@mui/material`
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import { Box, Button, Card, CardContent, Selector, TextField, Typography } from '@/ui'; // ✅
|
|
16
|
+
// import { Button } from '@mui/material'; // ❌
|
|
17
|
+
```
|
|
18
|
+
`@/ui` exposes wrapped/themed design-system components (Button, Modal, TextField, Selector, Dialog, etc.). MUI icons (`@mui/icons-material`) are imported directly. Layout primitives (`Box`, `Typography`, `Card`) also come from `@/ui`.
|
|
19
|
+
|
|
20
|
+
## Tokens for spacing/scale, not literals
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
import tokens from '@/styles/tokens';
|
|
24
|
+
|
|
25
|
+
<Card sx={{ mb: tokens.spacing.scale6 }}>
|
|
26
|
+
<Box sx={{ gap: tokens.spacing.scale4 }} display="flex" />
|
|
27
|
+
<Box sx={{ mt: tokens.spacing.scale4 }} />
|
|
28
|
+
</Card>
|
|
29
|
+
```
|
|
30
|
+
- Use `tokens.spacing.scaleN` (and other token groups) instead of hardcoded `8`, `'16px'`, etc.
|
|
31
|
+
- Tokens are generated by Style Dictionary (`yarn build:dictionary`) into `@/styles/tokens`.
|
|
32
|
+
- One-off layout values that are genuinely not design-system concerns (e.g. a fixed control height) may stay inline, but prefer tokens for spacing/color/typography.
|
|
33
|
+
|
|
34
|
+
## Component conventions
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
'use client';
|
|
38
|
+
import { memo } from 'react';
|
|
39
|
+
|
|
40
|
+
interface TodoFormProps { isCreating: boolean; onSubmit: (todo: CreateTodoRequest) => void; }
|
|
41
|
+
|
|
42
|
+
export const TodoForm = memo(({ isCreating, onSubmit }: TodoFormProps) => { /* ... */ });
|
|
43
|
+
TodoForm.displayName = 'TodoForm';
|
|
44
|
+
```
|
|
45
|
+
- Functional components only. Explicit `Props` interface. Named export.
|
|
46
|
+
- `memo` for pure presentational components; set `displayName` when memo-wrapped.
|
|
47
|
+
- `'use client'` when using state/handlers.
|
|
48
|
+
- No `any` in props. No render-time side effects.
|
|
49
|
+
|
|
50
|
+
## Theme breakpoints (Container maxWidth)
|
|
51
|
+
|
|
52
|
+
The theme augments MUI breakpoints with custom names. `Container maxWidth` accepts the THEME names (e.g. `"largeScreen"`), NOT MUI defaults (`"sm"`/`"md"` → type error). Match existing usage (`about-view` uses `maxWidth="largeScreen"`) or omit `maxWidth`.
|
|
53
|
+
|
|
54
|
+
## Rules
|
|
55
|
+
- UI from `@/ui`; icons from `@mui/icons-material`; never raw `@mui/material` in feature code.
|
|
56
|
+
- Spacing/scale/color via `@/styles/tokens`; avoid hardcoded values.
|
|
57
|
+
- i18n strings via next-intl, not literals — see [[i18n-next-intl]].
|
|
58
|
+
|
|
59
|
+
## Related skills
|
|
60
|
+
[[responsive-layouts]] · [[design-system-inventory]] · [[i18n-next-intl]] · [[naming-conventions]] · [[hook-patterns]]
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: naming-conventions
|
|
3
|
+
description: File/folder naming + suffixes + code casing enforced by eslint-plugin-check-file in scaffold-nextjs-app. Read before creating or renaming any file.
|
|
4
|
+
source: scaffold-nextjs-app/docs/file-naming-conventions.md, docs/rules-conventions.md, eslint.config.mjs
|
|
5
|
+
source_version: 8edaa0b
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Naming conventions
|
|
9
|
+
|
|
10
|
+
ESLint-enforced (`eslint-plugin-check-file`). Violations = lint error, not style preference.
|
|
11
|
+
|
|
12
|
+
## Enforced by lint (hard)
|
|
13
|
+
|
|
14
|
+
- **File names:** `KEBAB_CASE` for all `*.{js,jsx,ts,tsx}` (`ignoreMiddleExtensions: true` → middle ext like `.component` allowed).
|
|
15
|
+
- **Folders in `src/`:** `NEXT_JS_APP_ROUTER_CASE` (kebab, plus App Router segments like `[locale]`, `(group)`).
|
|
16
|
+
- **Folders in `test/`:** `KEBAB_CASE`.
|
|
17
|
+
- **`__mocks__/`:** exempt from filename rule.
|
|
18
|
+
|
|
19
|
+
## File suffixes (descriptive type tags)
|
|
20
|
+
|
|
21
|
+
| Suffix | Meaning | Example |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| `*.component.tsx` | React component | `todo-form.component.tsx` |
|
|
24
|
+
| `*.view.tsx` | page/screen view | `todo-management.view.tsx` |
|
|
25
|
+
| `*.hook.ts` | React hook (incl. business/controller/selector) | `use-todo-business.hook.ts` |
|
|
26
|
+
| `*.helper.ts` | pure util (no React) | `validation.helper.ts` |
|
|
27
|
+
| `*.store.ts` | Zustand store/actions | `todo.store.ts` |
|
|
28
|
+
| `*.types.ts` | type definitions | `todo.types.ts` |
|
|
29
|
+
|
|
30
|
+
Repository-layer files use dotted descriptors: `[entity].repository.queries.ts`, `[entity].repository.mutations.ts`, `[entity].repository.keys.ts`, `[entity].repository.types.ts`, `[entity].query-options.ts`, `[entity].gateway.types.ts`. Store: `[m].store.ts`, `[m].store.actions.ts`, `[m].store.types.ts`.
|
|
31
|
+
|
|
32
|
+
Tests colocate next to source: `*.test.ts` / `*.test.tsx`. Test filenames may carry the source suffix (`todo-form.component.test.tsx`) or just `.test` on plain TS files (`local-storage-gateway.test.ts`) — both pass lint (`ignoreMiddleExtensions`).
|
|
33
|
+
|
|
34
|
+
Do NOT skip the suffix (`button.tsx` ❌ → `button.component.tsx` ✅). No generic `utils.ts`.
|
|
35
|
+
|
|
36
|
+
## Code element casing
|
|
37
|
+
|
|
38
|
+
| Element | Case | Example |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| Variables / functions | `camelCase` | `userName`, `handleSubmit` |
|
|
41
|
+
| Components | `PascalCase` | `TodoManagementView` |
|
|
42
|
+
| Constants | `SCREAMING_SNAKE_CASE` | `MAX_RETRY_ATTEMPTS` |
|
|
43
|
+
| Types / interfaces | `PascalCase` | `TodoRepository` |
|
|
44
|
+
|
|
45
|
+
## Barrels
|
|
46
|
+
|
|
47
|
+
Every dir has `index.ts` re-exporting its public items → enables `import { X } from '@/...'`.
|
|
48
|
+
```ts
|
|
49
|
+
// components/index.ts
|
|
50
|
+
export { TodoForm } from './todo-form/todo-form.component';
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Exceptions to suffix rule
|
|
54
|
+
|
|
55
|
+
`index.ts`, `package.json`, framework files (`layout.tsx`, `page.tsx`), config files.
|
|
56
|
+
|
|
57
|
+
## Other enforced lint (relevant)
|
|
58
|
+
|
|
59
|
+
- TS strict; `array-simple` array style; `record` indexed-object style; prefer optional chaining.
|
|
60
|
+
- Import sort (`simple-import-sort`): react/externals → `@/` aliases → side-effect → parent → sibling → css.
|
|
61
|
+
- Restricted imports: pull RTL/`react-dom` test bits from `@test/utils/test-utils`, not directly (`ui/` and `test/` exempt).
|
|
62
|
+
- Unused vars: prefix `_` to ignore.
|
|
63
|
+
|
|
64
|
+
## Related skills
|
|
65
|
+
[[module-structure]] · [[clean-architecture]]
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: repository-pattern
|
|
3
|
+
description: Data-access layer combining React Query hooks + key factory + query/mutation options in scaffold-nextjs-app. Read before wiring a module's data fetching.
|
|
4
|
+
source: scaffold-nextjs-app/docs/repository-pattern.md + src/modules/todo/repositories/todo
|
|
5
|
+
source_version: 8edaa0b
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Repository pattern
|
|
9
|
+
|
|
10
|
+
Application layer. Wraps gateways with React Query. The View/business hook talks ONLY to the repository, never to gateway/api directly.
|
|
11
|
+
|
|
12
|
+
## Files (per entity)
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
repositories/[entity]/
|
|
16
|
+
├── [entity].repository.types.ts # interfaces for queries/mutations/repository
|
|
17
|
+
├── [entity].repository.keys.ts # query key factory
|
|
18
|
+
├── [entity].query-options.ts # queryOptions/mutationOptions factories
|
|
19
|
+
├── [entity].repository.queries.ts # query hooks object
|
|
20
|
+
├── [entity].repository.mutations.ts # mutation hooks object
|
|
21
|
+
├── helpers/[name]/[name].helper.ts
|
|
22
|
+
├── gateways/ # see [[gateway-pattern]]
|
|
23
|
+
└── index.ts # combined repository object
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## 1. Key factory
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
export const todoQueryKeys = {
|
|
30
|
+
all: ['todos'] as const,
|
|
31
|
+
lists: (ds: DataSource = 'http') => [...todoQueryKeys.all, 'list', ds] as const,
|
|
32
|
+
list: (filters: TodoFilters = {}, ds: DataSource = 'http') =>
|
|
33
|
+
[...todoQueryKeys.lists(ds), filters] as const,
|
|
34
|
+
details: (ds: DataSource = 'http') => [...todoQueryKeys.all, 'detail', ds] as const,
|
|
35
|
+
detail: (id: string | number, ds: DataSource = 'http') =>
|
|
36
|
+
[...todoQueryKeys.details(ds), id] as const,
|
|
37
|
+
} as const;
|
|
38
|
+
```
|
|
39
|
+
Keys are namespaced by `dataSource` so http/localStorage caches don't collide.
|
|
40
|
+
|
|
41
|
+
## 2. query/mutation options (reusable, server-component-friendly)
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
const getTodosQueryOptions = (filters = {}, ds: DataSource = 'http') =>
|
|
45
|
+
queryOptions({
|
|
46
|
+
queryKey: todoQueryKeys.list(filters, ds),
|
|
47
|
+
queryFn: ({ signal }) => createTodoGateway(ds).findAll(filters, { signal }),
|
|
48
|
+
staleTime: 5 * 60 * 1000, gcTime: 10 * 60 * 1000,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const getCreateTodoMutationOptions = (ds: DataSource = 'http') =>
|
|
52
|
+
mutationOptions({ mutationFn: (todo) => createTodoGateway(ds).create(todo), retry: 1 });
|
|
53
|
+
|
|
54
|
+
export const todoQueryOptions = { todos: getTodosQueryOptions, /* ... */ } as const;
|
|
55
|
+
export const todoMutationOptions = { createTodo: getCreateTodoMutationOptions, /* ... */ } as const;
|
|
56
|
+
```
|
|
57
|
+
queryFn forwards `signal`, calls the gateway factory. Reused by both hooks and prefetch.
|
|
58
|
+
|
|
59
|
+
## 3. Query hooks (compose options)
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
export const todoQueriesRepository: TodoQueriesRepository = {
|
|
63
|
+
useTodos: (filters = {}, ds = 'http', options) =>
|
|
64
|
+
useQuery({ ...todoQueryOptions.todos(filters, ds), ...options }),
|
|
65
|
+
// prefetch: { prefetchTodos: createPrefetchFunction(...) } // server components
|
|
66
|
+
// cancel: { cancelTodos: async (...) => queryClient.cancelQueries(...) }
|
|
67
|
+
};
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## 4. Mutation hooks (compose options + cache writes)
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
export const todoMutationsRepository: TodoMutationsRepository = {
|
|
74
|
+
useCreateTodo: (ds = 'http', options) => {
|
|
75
|
+
const queryClient = useQueryClient();
|
|
76
|
+
return useMutation({
|
|
77
|
+
...todoMutationOptions.createTodo(ds),
|
|
78
|
+
// eslint-disable-next-line max-params ← REQUIRED: onSuccess has 4 params, lint cap is 3
|
|
79
|
+
onSuccess: (newTodo, vars, onMutateResult, ctx) => {
|
|
80
|
+
queryClient.invalidateQueries({ queryKey: todoQueryKeys.lists(ds) });
|
|
81
|
+
queryClient.setQueryData<Todo[]>(todoQueryKeys.lists(ds), (old) => old ? [...old, newTodo] : [newTodo]);
|
|
82
|
+
options?.onSuccess?.(newTodo, vars, onMutateResult, ctx);
|
|
83
|
+
},
|
|
84
|
+
...options,
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
```
|
|
89
|
+
Mutations own cache invalidation/optimistic writes via `queryKeys`. Always call through user `options?.onSuccess`.
|
|
90
|
+
|
|
91
|
+
## 5. Combined repository (the public surface)
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
export const todoRepository: TodoRepository = {
|
|
95
|
+
queries: todoQueriesRepository,
|
|
96
|
+
mutations: todoMutationsRepository,
|
|
97
|
+
queryKeys: todoQueryKeys,
|
|
98
|
+
queryOptions: todoQueryOptions,
|
|
99
|
+
mutationOptions: todoMutationOptions,
|
|
100
|
+
};
|
|
101
|
+
```
|
|
102
|
+
Consumers: `todoRepository.queries.useTodos(filters, ds)` / `todoRepository.mutations.useCreateTodo(ds)`.
|
|
103
|
+
|
|
104
|
+
## Repository types (`*.repository.types.ts`)
|
|
105
|
+
|
|
106
|
+
Interfaces import from `@/core/lib/react-query`: `QueryOptions`, `MutationOptions` (custom option subsets), and queries-repo `extends BaseRepository`. Hook return types use `UseQueryResult<T, Error>` / `UseMutationResult<T, Error, TVars>`.
|
|
107
|
+
```ts
|
|
108
|
+
import { type UseMutationResult, type UseQueryResult } from '@tanstack/react-query';
|
|
109
|
+
import type { BaseRepository } from '@/core/lib/react-query';
|
|
110
|
+
import { MutationOptions, QueryOptions } from '@/core/lib/react-query';
|
|
111
|
+
|
|
112
|
+
export interface XQueriesRepository extends BaseRepository {
|
|
113
|
+
useXs: (filters?: XFilters, dataSource?: DataSource, options?: QueryOptions) => UseQueryResult<X[], Error>;
|
|
114
|
+
}
|
|
115
|
+
export interface XMutationsRepository {
|
|
116
|
+
useCreateX: (dataSource?: DataSource, options?: MutationOptions) => UseMutationResult<X, Error, CreateXRequest>;
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
Combined-repo `queryKeys`/`queryOptions`/`mutationOptions` typed via `typeof import('./...')`.
|
|
120
|
+
|
|
121
|
+
## Rules
|
|
122
|
+
- Every hook accepts `dataSource` (default `'http'`) and passthrough `options`.
|
|
123
|
+
- Keys derive from the factory only; never inline key arrays in components.
|
|
124
|
+
- Repository is the boundary: business hooks/selectors import the repository, not gateways/api.
|
|
125
|
+
- `DataSource = 'http' | 'localStorage'` (the scaffold ships TWO sources, not `'mock'` despite older docs). Confirm in `@/types/gateway.types`.
|
|
126
|
+
|
|
127
|
+
## Related skills
|
|
128
|
+
[[gateway-pattern]] · [[selector-pattern]] · [[hook-patterns]] · [[data-fetching-react-query]] · [[clean-architecture]]
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: requirement-intake
|
|
3
|
+
description: Normalize a raw work item (prompt, user story, or spec) into a structured REQUIREMENTS doc with acceptance criteria, asking clarifying questions and proactively surfacing gaps. Read at the start of any wavefront feature/change/fix.
|
|
4
|
+
source: wavefront framework meta — used by /wavefront-feature, /wavefront-change, /wavefront-fix
|
|
5
|
+
source_version: 8edaa0b
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Requirement intake
|
|
9
|
+
|
|
10
|
+
Turn any input shape into a planning-ready spec. The intake is **interactive and proactive** — not a parser. Read like a senior engineer: extract intent, then challenge gaps before any code is planned.
|
|
11
|
+
|
|
12
|
+
## Accepted inputs
|
|
13
|
+
- **Prompt:** one-liner ("add a notifications module").
|
|
14
|
+
- **User story:** "Como [rol] quiero [acción] para [beneficio]" + optional acceptance criteria.
|
|
15
|
+
- **Spec:** long doc / pasted ticket.
|
|
16
|
+
|
|
17
|
+
## Output: REQUIREMENTS structure
|
|
18
|
+
```
|
|
19
|
+
## Work item: <short title>
|
|
20
|
+
Type: feature | change | fix
|
|
21
|
+
Source: <prompt | user story | spec>
|
|
22
|
+
|
|
23
|
+
### Intent
|
|
24
|
+
<1–3 sentences: who, what, why>
|
|
25
|
+
|
|
26
|
+
### Scope
|
|
27
|
+
- In: <what's included>
|
|
28
|
+
- Out: <explicitly excluded>
|
|
29
|
+
|
|
30
|
+
### Entities & layers touched
|
|
31
|
+
<entities; which Clean Arch layers (api/gateway/repo/store/selector/hook/view/component) are affected>
|
|
32
|
+
|
|
33
|
+
### Acceptance criteria (→ become tests)
|
|
34
|
+
- [ ] <criterion 1, observable/testable>
|
|
35
|
+
- [ ] ...
|
|
36
|
+
|
|
37
|
+
### Open questions (must resolve before planning)
|
|
38
|
+
- <question> — <why it matters>
|
|
39
|
+
|
|
40
|
+
### Suggested additions (gaps intake spotted, propose to user)
|
|
41
|
+
- <edge case / error state / missing criterion the story didn't mention>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Procedure
|
|
45
|
+
1. **Classify** the input (prompt/story/spec) and the worktype (feature/change/fix).
|
|
46
|
+
2. **Extract** intent, scope, entities, and any stated acceptance criteria.
|
|
47
|
+
3. **Map to layers:** which Clean Arch layers this likely touches (informs the plan). See [[module-structure]] [[clean-architecture]].
|
|
48
|
+
4. **Challenge — the important part.** Proactively surface what the input left implicit:
|
|
49
|
+
- Acceptance criteria implied but not written.
|
|
50
|
+
- **Edge cases:** empty/loading/error states, pagination, permissions, concurrent edits.
|
|
51
|
+
- **Error handling:** what happens on API failure, validation failure, not-found.
|
|
52
|
+
- **Ambiguity:** vague terms ("fast", "some", "etc"), undefined entities, unclear ownership.
|
|
53
|
+
- **i18n/a11y:** user-facing strings, keyboard/screen-reader needs (this stack is i18n-first — see [[i18n-next-intl]]).
|
|
54
|
+
Put these in **Open questions** (blocking) and **Suggested additions** (proposals).
|
|
55
|
+
5. **Ask** the blocking questions. Use a concise question list; offer recommended answers where you have a strong default. Don't proceed to planning with unresolved blockers.
|
|
56
|
+
6. **Finalize** REQUIREMENTS once questions are answered; write/update `.claude/.planning/REQUIREMENTS.md`.
|
|
57
|
+
|
|
58
|
+
## Rules
|
|
59
|
+
- Never invent acceptance criteria silently — propose them under Suggested additions and confirm.
|
|
60
|
+
- Acceptance criteria must be observable (a test can assert them).
|
|
61
|
+
- A vague story is a signal to ask, not to guess.
|
|
62
|
+
- Keep it tight: intake produces a spec, not an essay.
|
|
63
|
+
|
|
64
|
+
## Related
|
|
65
|
+
[[clean-architecture]] [[module-structure]] [[hook-patterns]] [[testing-jest-rtl]] [[i18n-next-intl]]
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: responsive-layouts
|
|
3
|
+
description: Mobile-first responsive layout in scaffold-nextjs-app — the theme's custom breakpoints, responsive MUI sx, and the useResponsiveScreen hook. Read before building any view/component. Responsive is mandatory, not optional.
|
|
4
|
+
source: scaffold-nextjs-app/src/theme.ts, src/styles/breakpoints, src/core/hooks/use-responsive-screen, src/modules/*/views
|
|
5
|
+
source_version: 8edaa0b
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Responsive layouts
|
|
9
|
+
|
|
10
|
+
Every view/component is responsive — **mandatory, mobile-first**. A design usually arrives in one viewport (a desktop Figma, one screenshot); the implementation must still work on mobile, tablet, desktop. Never ship a fixed-width layout that breaks on small screens.
|
|
11
|
+
|
|
12
|
+
## The theme uses CUSTOM breakpoint names (not xs/sm/md)
|
|
13
|
+
From `src/theme.ts` (values from `src/styles/breakpoints`):
|
|
14
|
+
| Key | Width |
|
|
15
|
+
|---|---|
|
|
16
|
+
| `minMobile` | 0px |
|
|
17
|
+
| `maxMobile` | 767px |
|
|
18
|
+
| `minTablet` | 768px |
|
|
19
|
+
| `maxTablet` | 1023px |
|
|
20
|
+
| `minDesktop` | 1024px |
|
|
21
|
+
| `largeScreen` | 1440px |
|
|
22
|
+
| `xlargeScreen` | 2560px |
|
|
23
|
+
|
|
24
|
+
Use THESE keys in MUI `sx`/breakpoint props — NOT default `xs/sm/md/lg` (the theme replaced them). `Container maxWidth` also takes these (`maxWidth="largeScreen"`).
|
|
25
|
+
|
|
26
|
+
## Two responsive tools
|
|
27
|
+
|
|
28
|
+
### 1. CSS-driven via `sx` (preferred — no JS, no re-render)
|
|
29
|
+
```tsx
|
|
30
|
+
<Box
|
|
31
|
+
display="flex"
|
|
32
|
+
sx={{
|
|
33
|
+
flexDirection: { minMobile: 'column', minTablet: 'row' }, // stack on phone, row from tablet
|
|
34
|
+
gap: { minMobile: tokens.spacing.scale2, minTablet: tokens.spacing.scale4 },
|
|
35
|
+
}}
|
|
36
|
+
/>
|
|
37
|
+
<Typography sx={{ fontSize: { minMobile: '1rem', minDesktop: '1.25rem' } }} />
|
|
38
|
+
<Grid container columns={{ minMobile: 1, minTablet: 2, minDesktop: 3 }} />
|
|
39
|
+
```
|
|
40
|
+
Object syntax `{ minMobile: A, minTablet: B }` = mobile-first (value applies from that breakpoint up). Default to `sx` responsive for layout/spacing/sizing.
|
|
41
|
+
|
|
42
|
+
### 2. JS-driven via `useResponsiveScreen` (when you need branching logic)
|
|
43
|
+
```tsx
|
|
44
|
+
import { useResponsiveScreen } from '@/core';
|
|
45
|
+
const { isMobileDevice, isTabletDevice, isDesktopDevice, deviceType, isPortrait, isLandscape } = useResponsiveScreen();
|
|
46
|
+
// e.g. render a Drawer on mobile vs a sidebar on desktop; change column count; swap components
|
|
47
|
+
```
|
|
48
|
+
Use only when CSS `sx` can't express it (different component per device, conditional rendering). Prefer `sx` first.
|
|
49
|
+
|
|
50
|
+
## Rules (mandatory)
|
|
51
|
+
- Every view/component works at minMobile, minTablet, minDesktop. No fixed pixel widths that overflow mobile — use `%`, `fullWidth`, flex, or breakpoint objects.
|
|
52
|
+
- Mobile-first: base value = smallest screen; scale up with breakpoint keys.
|
|
53
|
+
- Spacing/sizing still via `tokens.spacing.scaleN` (responsive values pick among tokens).
|
|
54
|
+
- Touch targets adequate on mobile; no horizontal scroll on small screens.
|
|
55
|
+
- The four states (loading/empty/error/populated) must each be responsive too.
|
|
56
|
+
|
|
57
|
+
## When the design source has only ONE viewport
|
|
58
|
+
This is the norm. Do NOT silently guess:
|
|
59
|
+
1. Note that responsive behavior is unspecified.
|
|
60
|
+
2. **Ask** the user how key elements should adapt (stack vs wrap vs hide; column counts; nav pattern) — see [[design-intake]]. Offer sensible defaults (mobile stacks columns, multi-col grids collapse, side-nav → top/drawer).
|
|
61
|
+
3. Only after confirmation, implement. If the user defers, apply mobile-first defaults and flag them.
|
|
62
|
+
|
|
63
|
+
## Related
|
|
64
|
+
[[design-intake]] [[design-system-inventory]] [[mui-design-tokens]] [[hook-patterns]]
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: selector-pattern
|
|
3
|
+
description: Derived-state selector hooks that combine repository data + store state with useMemo in scaffold-nextjs-app. Read before computing derived data.
|
|
4
|
+
source: scaffold-nextjs-app/docs/selector-pattern.md + src/modules/todo/selectors
|
|
5
|
+
source_version: 8edaa0b
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Selector pattern
|
|
9
|
+
|
|
10
|
+
Application layer. A selector hook computes derived/aggregated data from repository queries and/or store state, memoized. Keeps business hooks and components thin.
|
|
11
|
+
|
|
12
|
+
## Shape (one dir per selector)
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
selectors/
|
|
16
|
+
├── use-[name]-selector/use-[name]-selector.hook.ts
|
|
17
|
+
└── index.ts # barrel
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Example
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
// use-todo-stats-selector/use-todo-stats-selector.hook.ts
|
|
24
|
+
import { useMemo } from 'react';
|
|
25
|
+
import { todoRepository } from '@/modules/todo/repositories/todo';
|
|
26
|
+
import type { DataSource } from '@/types/gateway.types';
|
|
27
|
+
|
|
28
|
+
export interface TodoStats { total: number; completed: number; completionRate: number; /* ... */ }
|
|
29
|
+
|
|
30
|
+
export const useTodoStatsSelector = (dataSource: DataSource = 'http') => {
|
|
31
|
+
const todosQuery = todoRepository.queries.useTodos({}, dataSource);
|
|
32
|
+
|
|
33
|
+
const stats = useMemo((): TodoStats => {
|
|
34
|
+
const todos = todosQuery.data ?? [];
|
|
35
|
+
const completed = todos.filter((t) => t.completed).length;
|
|
36
|
+
return {
|
|
37
|
+
total: todos.length,
|
|
38
|
+
completed,
|
|
39
|
+
completionRate: todos.length ? Math.round((completed / todos.length) * 100) : 0,
|
|
40
|
+
};
|
|
41
|
+
}, [todosQuery.data]);
|
|
42
|
+
|
|
43
|
+
return { data: stats, isLoading: todosQuery.isLoading, error: todosQuery.error, refetch: todosQuery.refetch };
|
|
44
|
+
};
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Conventions
|
|
48
|
+
- Name `useXSelector`, accept `dataSource` (default `'http'`).
|
|
49
|
+
- Read source data from the **repository** (queries) and/or **store** (`useXStore`). Never fetch directly.
|
|
50
|
+
- Compute in **`useMemo`** keyed on the source data.
|
|
51
|
+
- Return a query-like shape: `{ data, isLoading, error, refetch }` so consumers treat selectors and repo queries uniformly.
|
|
52
|
+
- Pure derivation only — no mutations, no side effects.
|
|
53
|
+
|
|
54
|
+
## Rules
|
|
55
|
+
- One selector = one derived concern (stats, filtered list, selected item). Compose multiple in a business hook.
|
|
56
|
+
- Selectors are the place for cross-source combination (repo data + store filters), not components.
|
|
57
|
+
|
|
58
|
+
## Related skills
|
|
59
|
+
[[repository-pattern]] · [[store-zustand]] · [[hook-patterns]] · [[clean-architecture]]
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: store-zustand
|
|
3
|
+
description: Module-local Zustand stores with createStoreWithMiddleware (immer + persist) and nested actions in scaffold-nextjs-app. Read before adding local state.
|
|
4
|
+
source: scaffold-nextjs-app/src/modules/todo/stores, src/core/lib/zustand
|
|
5
|
+
source_version: 8edaa0b
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Zustand stores
|
|
9
|
+
|
|
10
|
+
Application layer. Local UI/domain state per module (selection, filters, transient flags). NOT server data — that's React Query's job ([[repository-pattern]]).
|
|
11
|
+
|
|
12
|
+
## Files (per module)
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
stores/
|
|
16
|
+
├── [module].store.ts # store definition
|
|
17
|
+
├── [module].store.actions.ts # actions selector hook (useXActions)
|
|
18
|
+
└── [module].store.types.ts # State + StoreState types
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Store definition
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
// [module].store.ts
|
|
25
|
+
import type { Draft } from 'immer';
|
|
26
|
+
import { createStoreWithMiddleware } from '@/core/lib/zustand';
|
|
27
|
+
import type { TodoState, TodoStoreState } from './todo.store.types';
|
|
28
|
+
|
|
29
|
+
const initialState: TodoState = { selectedTodo: null, filters: {}, isCreating: false, isEditing: false };
|
|
30
|
+
|
|
31
|
+
export const useTodoStore = createStoreWithMiddleware<TodoStoreState>(
|
|
32
|
+
(set, _get) => ({
|
|
33
|
+
...initialState,
|
|
34
|
+
actions: {
|
|
35
|
+
setSelectedTodo: (todo) => set({ selectedTodo: todo }),
|
|
36
|
+
setFilters: (newFilters) =>
|
|
37
|
+
set((draft: Draft<TodoStoreState>) => { Object.assign(draft.filters, newFilters); }), // immer
|
|
38
|
+
clearFilters: () => set({ filters: {} }),
|
|
39
|
+
},
|
|
40
|
+
}),
|
|
41
|
+
'todo-store', // store name (persist key / devtools)
|
|
42
|
+
{ persist: true, exclude: ['isCreating', 'isEditing'] }, // persist config
|
|
43
|
+
);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Conventions
|
|
47
|
+
- **`createStoreWithMiddleware`** from `@/core/lib/zustand` wraps immer + persist + devtools. Never call raw `create` from `zustand`.
|
|
48
|
+
- **Actions nested under `actions`** key (kept out of persisted/selected state).
|
|
49
|
+
- **Immer**: object/array mutations use `set((draft) => {...})` with `Draft<T>`; simple replacements use `set({ ... })`.
|
|
50
|
+
- **Third arg = store name**; **fourth = options** (`persist`, `exclude` keys from persistence).
|
|
51
|
+
- Types split: `XState` (data shape) + `XStoreState` (state + `actions`) in `*.store.types.ts`.
|
|
52
|
+
|
|
53
|
+
## Consuming
|
|
54
|
+
- State via selector: `const filters = useTodoStore((s) => s.filters)`.
|
|
55
|
+
- Actions via dedicated hook: `useTodoActions()` (in `*.store.actions.ts`) → `const { setFilters } = useTodoActions()`.
|
|
56
|
+
- Derived/cross-source data → use a [[selector-pattern]] hook, not the store.
|
|
57
|
+
|
|
58
|
+
## Rules
|
|
59
|
+
- Store holds local state only. No fetching, no API.
|
|
60
|
+
- Keep transient flags out of persistence via `exclude`.
|
|
61
|
+
|
|
62
|
+
## Related skills
|
|
63
|
+
[[selector-pattern]] · [[hook-patterns]] · [[clean-architecture]]
|