@baldurpan/create-ai-workflow 0.2.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 +165 -0
- package/dist/agents-block.js +70 -0
- package/dist/bin.js +14 -0
- package/dist/check/markdown.js +47 -0
- package/dist/check/parse.js +113 -0
- package/dist/check/rules.js +245 -0
- package/dist/cli.js +109 -0
- package/dist/commands/check.js +41 -0
- package/dist/commands/install.js +76 -0
- package/dist/commands/standards-add.js +145 -0
- package/dist/commands/update.js +214 -0
- package/dist/layout.js +115 -0
- package/dist/log.js +20 -0
- package/dist/manifest.js +53 -0
- package/dist/paths.js +41 -0
- package/package.json +37 -0
- package/templates/blocks/agents-block.md +30 -0
- package/templates/claude/agents/planner.agent.md +60 -0
- package/templates/claude/agents/reviewer.agent.md +77 -0
- package/templates/context/README.md +81 -0
- package/templates/context/plan-template.md +69 -0
- package/templates/context/plan-template.notes.md +55 -0
- package/templates/context/roles/coder.md +76 -0
- package/templates/context/workflow.md +143 -0
- package/templates/skills/feature-close/SKILL.md +102 -0
- package/templates/skills/feature-implement/SKILL.md +142 -0
- package/templates/skills/feature-plan/SKILL.md +138 -0
- package/templates/skills/feature-status/SKILL.md +86 -0
- package/templates/skills/onboard/SKILL.md +122 -0
- package/templates/skills/orchestrate/SKILL.md +92 -0
- package/templates/skills/roadmap/SKILL.md +97 -0
- package/templates/standards/.source +8 -0
- package/templates/standards/README.md +104 -0
- package/templates/standards/architecture/api-design.md +212 -0
- package/templates/standards/architecture/dependency-boundaries.md +104 -0
- package/templates/standards/architecture/feature-driven.md +102 -0
- package/templates/standards/architecture/folder-structure.md +108 -0
- package/templates/standards/architecture/monorepos.md +107 -0
- package/templates/standards/architecture/refactoring.md +97 -0
- package/templates/standards/architecture/shared-code.md +81 -0
- package/templates/standards/docs/PHP-SPEC.md +1195 -0
- package/templates/standards/docs/SPEC.md +540 -0
- package/templates/standards/examples/bad/data-fetching.tsx +52 -0
- package/templates/standards/examples/bad/derived-state.tsx +60 -0
- package/templates/standards/examples/bad/feature-component.tsx +80 -0
- package/templates/standards/examples/bad/prop-drilling.tsx +70 -0
- package/templates/standards/examples/good/data-fetching.tsx +53 -0
- package/templates/standards/examples/good/derived-state.tsx +52 -0
- package/templates/standards/examples/good/feature-component.tsx +141 -0
- package/templates/standards/examples/good/invoice.schema.ts +20 -0
- package/templates/standards/examples/good/prop-drilling.tsx +83 -0
- package/templates/standards/philosophy/ai-agent-behavior.md +93 -0
- package/templates/standards/philosophy/core-principles.md +49 -0
- package/templates/standards/philosophy/incremental-abstraction.md +56 -0
- package/templates/standards/philosophy/maintainability.md +66 -0
- package/templates/standards/philosophy/readability.md +87 -0
- package/templates/standards/php/README.md +62 -0
- package/templates/standards/php/anti-patterns.md +259 -0
- package/templates/standards/php/architecture.md +241 -0
- package/templates/standards/php/frameworks.md +197 -0
- package/templates/standards/php/rules.md +205 -0
- package/templates/standards/php/security.md +190 -0
- package/templates/standards/php/testing.md +139 -0
- package/templates/standards/php/tooling.md +245 -0
- package/templates/standards/react/accessibility.md +130 -0
- package/templates/standards/react/anti-patterns.md +187 -0
- package/templates/standards/react/component-design.md +139 -0
- package/templates/standards/react/error-boundaries.md +195 -0
- package/templates/standards/react/forms.md +147 -0
- package/templates/standards/react/hooks.md +88 -0
- package/templates/standards/react/memoization.md +87 -0
- package/templates/standards/react/state-management.md +123 -0
- package/templates/standards/react/testing.md +118 -0
- package/templates/standards/react/use-effect.md +110 -0
- package/templates/standards/security/api-security.md +136 -0
- package/templates/standards/security/auth.md +112 -0
- package/templates/standards/security/secrets.md +105 -0
- package/templates/standards/security/validation.md +89 -0
- package/templates/standards/templates/.editorconfig +16 -0
- package/templates/standards/templates/.nvmrc +1 -0
- package/templates/standards/templates/AGENTS.md +46 -0
- package/templates/standards/templates/CLAUDE.md +3 -0
- package/templates/standards/templates/_dot_gitignore +50 -0
- package/templates/standards/templates/biome.json +53 -0
- package/templates/standards/templates/eslint.config.js +61 -0
- package/templates/standards/templates/project-readme.md +76 -0
- package/templates/standards/templates/pull-request-template.md +46 -0
- package/templates/standards/templates/tsconfig.json +35 -0
- package/templates/standards/tooling/biome.md +92 -0
- package/templates/standards/tooling/ci.md +128 -0
- package/templates/standards/tooling/dates.md +194 -0
- package/templates/standards/tooling/dependencies.md +90 -0
- package/templates/standards/tooling/nx.md +113 -0
- package/templates/standards/tooling/observability.md +183 -0
- package/templates/standards/tooling/prisma.md +152 -0
- package/templates/standards/tooling/shadcn.md +115 -0
- package/templates/standards/tooling/tailwind.md +135 -0
- package/templates/standards/tooling/tanstack-query.md +214 -0
- package/templates/standards/tooling/tanstack-router.md +365 -0
- package/templates/standards/tooling/tanstack.md +31 -0
- package/templates/standards/tooling/vite.md +135 -0
- package/templates/standards/typescript/anti-patterns.md +107 -0
- package/templates/standards/typescript/error-handling.md +216 -0
- package/templates/standards/typescript/naming.md +78 -0
- package/templates/standards/typescript/rules.md +159 -0
- package/templates/standards/typescript/tsconfig/base.json +32 -0
- package/templates/standards/typescript/tsconfig/nextjs.json +12 -0
- package/templates/standards/typescript/tsconfig/react.json +9 -0
- package/templates/standards/typescript/validation.md +104 -0
- package/templates/stubs/executors.md +49 -0
- package/templates/stubs/findings.md +40 -0
- package/templates/stubs/history.md +13 -0
- package/templates/stubs/roadmap.md +29 -0
- package/templates/stubs/stack.md +35 -0
- package/templates/stubs/verify.md +48 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# API Design and Client Patterns
|
|
2
|
+
|
|
3
|
+
## Core Rules
|
|
4
|
+
|
|
5
|
+
- API types are generated or inferred — never duplicated by hand
|
|
6
|
+
- Validate at the boundary on both sides (server validates incoming, client validates responses)
|
|
7
|
+
- Use a consistent error response shape across all endpoints
|
|
8
|
+
- Versioning is explicit when contracts change in breaking ways
|
|
9
|
+
- Mock servers in development — do not block frontend work on backend availability
|
|
10
|
+
|
|
11
|
+
## Choosing an API Layer
|
|
12
|
+
|
|
13
|
+
| Approach | Use when |
|
|
14
|
+
|---|---|
|
|
15
|
+
| **tRPC** | Same team owns FE and BE, both in TypeScript — end-to-end type safety, no codegen step |
|
|
16
|
+
| **OpenAPI + generated client** | Polyglot backend, public API, or external consumers |
|
|
17
|
+
| **GraphQL** | Many client types with different field needs, or complex relational queries |
|
|
18
|
+
| **Hand-written REST + Zod** | Small project, no FE/BE coordination needed, simple endpoints |
|
|
19
|
+
|
|
20
|
+
Pick one per service. Mixing approaches creates two type pipelines to maintain.
|
|
21
|
+
|
|
22
|
+
## tRPC Pattern
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
// server/router.ts
|
|
26
|
+
import { initTRPC } from "@trpc/server";
|
|
27
|
+
import { z } from "zod";
|
|
28
|
+
|
|
29
|
+
const t = initTRPC.create();
|
|
30
|
+
|
|
31
|
+
export const appRouter = t.router({
|
|
32
|
+
invoice: t.router({
|
|
33
|
+
list: t.procedure
|
|
34
|
+
.input(z.object({ status: z.enum(["all", "open", "paid"]).default("all") }))
|
|
35
|
+
.query(({ input }) => db.invoice.findMany({ where: { status: input.status } })),
|
|
36
|
+
|
|
37
|
+
create: t.procedure
|
|
38
|
+
.input(createInvoiceSchema)
|
|
39
|
+
.mutation(({ input, ctx }) =>
|
|
40
|
+
db.invoice.create({ data: { ...input, userId: ctx.userId } }),
|
|
41
|
+
),
|
|
42
|
+
}),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export type AppRouter = typeof appRouter;
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
// client — types flow from the server router definition
|
|
50
|
+
const invoices = await trpc.invoice.list.query({ status: "open" });
|
|
51
|
+
// invoices is fully typed; no manual DTO
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## OpenAPI Pattern
|
|
55
|
+
|
|
56
|
+
For polyglot or public APIs:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Generate a typed client from an OpenAPI spec
|
|
60
|
+
pnpm dlx openapi-typescript ./openapi.yaml -o ./src/lib/api-types.ts
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Use a small fetch wrapper that validates with the generated types:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import type { paths } from "~/lib/api-types";
|
|
67
|
+
import createClient from "openapi-fetch";
|
|
68
|
+
|
|
69
|
+
export const api = createClient<paths>({ baseUrl: env.VITE_API_BASE_URL });
|
|
70
|
+
|
|
71
|
+
const { data, error } = await api.GET("/invoices", { params: { query: { status: "open" } } });
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Hand-Written Fetch + Zod
|
|
75
|
+
|
|
76
|
+
For small projects without a generated client:
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import { z } from "zod";
|
|
80
|
+
|
|
81
|
+
const invoiceSchema = z.object({ id: z.string(), title: z.string(), amount: z.number() });
|
|
82
|
+
const invoicesResponseSchema = z.object({ items: invoiceSchema.array(), nextCursor: z.string().nullable() });
|
|
83
|
+
|
|
84
|
+
export async function fetchInvoices(cursor?: string) {
|
|
85
|
+
const url = new URL("/api/invoices", env.VITE_API_BASE_URL);
|
|
86
|
+
if (cursor) url.searchParams.set("cursor", cursor);
|
|
87
|
+
|
|
88
|
+
const response = await fetch(url);
|
|
89
|
+
if (!response.ok) {
|
|
90
|
+
throw new ApiError(response.status, await response.text());
|
|
91
|
+
}
|
|
92
|
+
return invoicesResponseSchema.parse(await response.json());
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Standard Error Response Shape
|
|
97
|
+
|
|
98
|
+
Use one error shape across all endpoints:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
// shared/schemas/api-error.schema.ts
|
|
102
|
+
export const apiErrorSchema = z.object({
|
|
103
|
+
error: z.object({
|
|
104
|
+
code: z.string(), // machine-readable: "NOT_FOUND", "VALIDATION_FAILED"
|
|
105
|
+
message: z.string(), // human-readable, safe to show
|
|
106
|
+
details: z.record(z.unknown()).optional(), // field errors, etc.
|
|
107
|
+
requestId: z.string().optional(),
|
|
108
|
+
}),
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Server example:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
return res.status(422).json({
|
|
116
|
+
error: {
|
|
117
|
+
code: "VALIDATION_FAILED",
|
|
118
|
+
message: "Some fields are invalid",
|
|
119
|
+
details: result.error.flatten().fieldErrors,
|
|
120
|
+
requestId: req.id,
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Pagination
|
|
126
|
+
|
|
127
|
+
Prefer **cursor-based pagination** for performance and stability with mutable datasets:
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
// Request: GET /invoices?cursor=eyJpZCI6...&limit=20
|
|
131
|
+
// Response:
|
|
132
|
+
{
|
|
133
|
+
items: Invoice[],
|
|
134
|
+
nextCursor: string | null, // null when at end
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Use **offset/page pagination** only for small, stable datasets where the user expects page numbers (admin tables, search results).
|
|
139
|
+
|
|
140
|
+
## Versioning
|
|
141
|
+
|
|
142
|
+
When making a breaking change:
|
|
143
|
+
|
|
144
|
+
- **Additive change** (new optional field, new endpoint) — no version bump needed
|
|
145
|
+
- **Breaking change** (removed field, changed type, removed endpoint) — bump the API version
|
|
146
|
+
|
|
147
|
+
Common strategies:
|
|
148
|
+
- URL prefix: `/v1/invoices`, `/v2/invoices`
|
|
149
|
+
- Header: `Accept: application/vnd.acme.v2+json`
|
|
150
|
+
|
|
151
|
+
Keep the old version working for one deprecation cycle (typically 1–2 releases). Document the migration path.
|
|
152
|
+
|
|
153
|
+
## Mock Servers
|
|
154
|
+
|
|
155
|
+
Frontend should never be blocked on backend availability.
|
|
156
|
+
|
|
157
|
+
Options:
|
|
158
|
+
- **MSW** (Mock Service Worker) — intercepts fetch at the browser/Node level; same handlers in tests and dev
|
|
159
|
+
- **Prism** — runs an OpenAPI spec as a mock server
|
|
160
|
+
- **tRPC** — type-safe mocks via `@trpc/react-query`'s `createTRPCMsw`
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
// MSW handler
|
|
164
|
+
import { http, HttpResponse } from "msw";
|
|
165
|
+
|
|
166
|
+
export const handlers = [
|
|
167
|
+
http.get("/api/invoices", () =>
|
|
168
|
+
HttpResponse.json({
|
|
169
|
+
items: [{ id: "1", title: "Q4 Report", amount: 500 }],
|
|
170
|
+
nextCursor: null,
|
|
171
|
+
}),
|
|
172
|
+
),
|
|
173
|
+
];
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Idempotency
|
|
177
|
+
|
|
178
|
+
For mutations that might be retried (network errors, optimistic UI), accept an `Idempotency-Key` header so the same request can be safely sent twice:
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
// Client
|
|
182
|
+
await api.POST("/invoices", {
|
|
183
|
+
body: invoice,
|
|
184
|
+
headers: { "Idempotency-Key": crypto.randomUUID() },
|
|
185
|
+
});
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
The server deduplicates on this key for some window (e.g., 24 hours).
|
|
189
|
+
|
|
190
|
+
## DO NOT
|
|
191
|
+
|
|
192
|
+
- Hand-write FE types that mirror BE types — generate or infer instead
|
|
193
|
+
- Return different error shapes from different endpoints
|
|
194
|
+
- Mix snake_case and camelCase in the same API surface
|
|
195
|
+
- Expose internal IDs / database errors / stack traces in error responses
|
|
196
|
+
- Skip mocking in development — it slows everyone down
|
|
197
|
+
|
|
198
|
+
## PRIORITY
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
Generated types > Hand-written DTOs
|
|
202
|
+
Cursor pagination > Offset pagination
|
|
203
|
+
Mock-first frontend > Blocked-on-backend frontend
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## See Also
|
|
207
|
+
|
|
208
|
+
- [`typescript/validation.md`](../typescript/validation.md) — schema-driven validation
|
|
209
|
+
- [`security/api-security.md`](../security/api-security.md) — status codes, rate limiting, CORS
|
|
210
|
+
- [`security/validation.md`](../security/validation.md) — input validation at boundaries
|
|
211
|
+
- [`tooling/tanstack-query.md`](../tooling/tanstack-query.md) — TanStack Query patterns
|
|
212
|
+
- [`tooling/tanstack-router.md`](../tooling/tanstack-router.md) — route loaders, typed search params
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Dependency Boundaries
|
|
2
|
+
|
|
3
|
+
## Core Rule
|
|
4
|
+
|
|
5
|
+
Features must not depend on each other. Features depend on `shared/`. `shared/` depends on nothing internal.
|
|
6
|
+
|
|
7
|
+
## Allowed Dependencies
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
apps/web → packages/ui, packages/schemas
|
|
11
|
+
apps/api → packages/schemas
|
|
12
|
+
features/X → shared/
|
|
13
|
+
features/X → lib/
|
|
14
|
+
shared/ → lib/
|
|
15
|
+
lib/ → external packages only
|
|
16
|
+
packages/* → external packages only (no internal app code)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Forbidden Dependencies
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
features/invoices → features/users ✗ cross-feature import
|
|
23
|
+
packages/ui → apps/web ✗ package depends on app
|
|
24
|
+
shared/ → features/ ✗ shared depends on feature
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## In Practice
|
|
28
|
+
|
|
29
|
+
If feature A needs something from feature B:
|
|
30
|
+
|
|
31
|
+
**Option 1: Move it to `shared/`**
|
|
32
|
+
- Move the shared concept to `shared/`
|
|
33
|
+
- Both features import from `shared/`
|
|
34
|
+
|
|
35
|
+
**Option 2: Pass it as a prop or callback**
|
|
36
|
+
- Feature A receives the data it needs via props
|
|
37
|
+
- Feature B provides the data at the composition layer (page, layout)
|
|
38
|
+
|
|
39
|
+
**Option 3: Use a shared store or context**
|
|
40
|
+
- If it is runtime state (current user, theme), put it in context or Zustand
|
|
41
|
+
- Both features read from the same source
|
|
42
|
+
|
|
43
|
+
## Detecting Violations
|
|
44
|
+
|
|
45
|
+
In a monorepo, use Nx's enforcement:
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
// nx.json
|
|
49
|
+
{
|
|
50
|
+
"targetDefaults": {
|
|
51
|
+
"lint": {
|
|
52
|
+
"configurations": {
|
|
53
|
+
"enforce-boundaries": true
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
In a single app, use ESLint's `import/no-restricted-paths`:
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
// eslint.config.js
|
|
64
|
+
{
|
|
65
|
+
rules: {
|
|
66
|
+
"import/no-restricted-paths": ["error", {
|
|
67
|
+
zones: [
|
|
68
|
+
{
|
|
69
|
+
target: "./src/features",
|
|
70
|
+
from: "./src/features",
|
|
71
|
+
except: ["./src/features/invoices"], // each feature only allows itself
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
}],
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Circular Dependencies
|
|
80
|
+
|
|
81
|
+
Circular dependencies are always forbidden. They indicate a design problem.
|
|
82
|
+
|
|
83
|
+
Common causes:
|
|
84
|
+
- `shared/` importing from a feature
|
|
85
|
+
- Two features that should be combined into one
|
|
86
|
+
- Missing abstraction that should be in `shared/`
|
|
87
|
+
|
|
88
|
+
Use `madge` to detect cycles:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pnpm dlx madge --circular src/
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## PRIORITY
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
Isolation > Convenience > DRY
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## See Also
|
|
101
|
+
|
|
102
|
+
- [`feature-driven.md`](feature-driven.md) — feature ownership
|
|
103
|
+
- [`shared-code.md`](shared-code.md) — what belongs in `shared/`
|
|
104
|
+
- [`monorepos.md`](monorepos.md) — package-level boundaries
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Feature-Driven Architecture
|
|
2
|
+
|
|
3
|
+
## Core Rule
|
|
4
|
+
|
|
5
|
+
Organize code by feature, not by type. A feature owns everything it needs.
|
|
6
|
+
|
|
7
|
+
## Structure
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
src/
|
|
11
|
+
features/
|
|
12
|
+
invoices/
|
|
13
|
+
InvoiceTable.tsx ← component
|
|
14
|
+
InvoiceTable.test.tsx ← test colocated
|
|
15
|
+
InvoiceFilters.tsx
|
|
16
|
+
InvoiceRow.tsx
|
|
17
|
+
useInvoiceSort.ts ← hook used only in this feature
|
|
18
|
+
invoice.schema.ts ← Zod schemas for this feature
|
|
19
|
+
invoice.api.ts ← data fetching for this feature
|
|
20
|
+
invoice.types.ts ← types not derivable from schema
|
|
21
|
+
users/
|
|
22
|
+
UserProfile.tsx
|
|
23
|
+
useCurrentUser.ts
|
|
24
|
+
user.schema.ts
|
|
25
|
+
shared/
|
|
26
|
+
components/ ← reused across 3+ features
|
|
27
|
+
hooks/
|
|
28
|
+
schemas/
|
|
29
|
+
utils/
|
|
30
|
+
app/ ← routing, layout, providers
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## What Goes in a Feature
|
|
34
|
+
|
|
35
|
+
A feature folder contains everything the feature needs that is not shared with other features:
|
|
36
|
+
|
|
37
|
+
- Components (primary + sub-components)
|
|
38
|
+
- Hooks specific to this feature
|
|
39
|
+
- API functions
|
|
40
|
+
- Zod schemas and inferred types
|
|
41
|
+
- Tests
|
|
42
|
+
- Constants used only here
|
|
43
|
+
|
|
44
|
+
## What Belongs in `shared/`
|
|
45
|
+
|
|
46
|
+
Move to `shared/` only when:
|
|
47
|
+
- Used by 3 or more distinct features
|
|
48
|
+
- The shared nature is expected to be permanent, not incidental
|
|
49
|
+
|
|
50
|
+
Never move something to `shared/` preemptively.
|
|
51
|
+
|
|
52
|
+
## DO NOT
|
|
53
|
+
|
|
54
|
+
- Organize by type at the root level (`components/`, `hooks/`, `types/`) for a large app
|
|
55
|
+
- Import from another feature's internals directly
|
|
56
|
+
- Create a `utils/` catch-all that accumulates unrelated helpers over time
|
|
57
|
+
|
|
58
|
+
## Feature Communication
|
|
59
|
+
|
|
60
|
+
Features must not import from each other's internals. If two features share a concept:
|
|
61
|
+
|
|
62
|
+
1. Move the shared concept to `shared/`
|
|
63
|
+
2. Both features import from `shared/`
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
features/
|
|
67
|
+
invoices/
|
|
68
|
+
invoice.schema.ts ← imports User from shared/schemas/user.schema.ts
|
|
69
|
+
users/
|
|
70
|
+
user.schema.ts ← defined here, re-exported from shared/
|
|
71
|
+
shared/
|
|
72
|
+
schemas/
|
|
73
|
+
user.schema.ts ← canonical location for shared User schema
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Scaling
|
|
77
|
+
|
|
78
|
+
As the app grows, features can be grouped by domain:
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
features/
|
|
82
|
+
billing/
|
|
83
|
+
invoices/
|
|
84
|
+
payments/
|
|
85
|
+
subscriptions/
|
|
86
|
+
crm/
|
|
87
|
+
contacts/
|
|
88
|
+
companies/
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## PRIORITY
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
Feature isolation > Shared reuse > Organizational elegance
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## See Also
|
|
98
|
+
|
|
99
|
+
- [`shared-code.md`](shared-code.md) — what belongs in `shared/`
|
|
100
|
+
- [`folder-structure.md`](folder-structure.md) — canonical folder maps
|
|
101
|
+
- [`dependency-boundaries.md`](dependency-boundaries.md) — feature isolation rules
|
|
102
|
+
- [`../philosophy/maintainability.md`](../philosophy/maintainability.md) — colocation principle
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Folder Structure
|
|
2
|
+
|
|
3
|
+
## React App (Single Repo)
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
src/
|
|
7
|
+
app/ ← routing, layout, root providers
|
|
8
|
+
layout.tsx
|
|
9
|
+
providers.tsx
|
|
10
|
+
router.tsx
|
|
11
|
+
features/ ← feature-driven modules
|
|
12
|
+
invoices/
|
|
13
|
+
InvoiceTable.tsx
|
|
14
|
+
InvoiceTable.test.tsx
|
|
15
|
+
useInvoiceSort.ts
|
|
16
|
+
invoice.schema.ts
|
|
17
|
+
invoice.api.ts
|
|
18
|
+
users/
|
|
19
|
+
UserProfile.tsx
|
|
20
|
+
user.schema.ts
|
|
21
|
+
shared/ ← reused across 3+ features
|
|
22
|
+
components/
|
|
23
|
+
Button.tsx
|
|
24
|
+
Modal.tsx
|
|
25
|
+
hooks/
|
|
26
|
+
useDebounce.ts
|
|
27
|
+
schemas/
|
|
28
|
+
pagination.schema.ts
|
|
29
|
+
utils/
|
|
30
|
+
format-currency.ts
|
|
31
|
+
lib/ ← third-party adapter layer
|
|
32
|
+
db.ts
|
|
33
|
+
auth.ts
|
|
34
|
+
stripe.ts
|
|
35
|
+
styles/
|
|
36
|
+
globals.css
|
|
37
|
+
main.tsx
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Next.js App (App Router)
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
app/ ← Next.js App Router
|
|
44
|
+
(auth)/
|
|
45
|
+
login/
|
|
46
|
+
page.tsx
|
|
47
|
+
(dashboard)/
|
|
48
|
+
invoices/
|
|
49
|
+
page.tsx
|
|
50
|
+
[id]/
|
|
51
|
+
page.tsx
|
|
52
|
+
layout.tsx
|
|
53
|
+
providers.tsx
|
|
54
|
+
src/ ← application code (same as above)
|
|
55
|
+
features/
|
|
56
|
+
shared/
|
|
57
|
+
lib/
|
|
58
|
+
public/
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Monorepo
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
apps/
|
|
65
|
+
web/ ← frontend app
|
|
66
|
+
src/
|
|
67
|
+
features/
|
|
68
|
+
shared/
|
|
69
|
+
lib/
|
|
70
|
+
api/ ← backend app
|
|
71
|
+
src/
|
|
72
|
+
routes/
|
|
73
|
+
services/
|
|
74
|
+
lib/
|
|
75
|
+
packages/
|
|
76
|
+
ui/ ← shared component library
|
|
77
|
+
schemas/ ← shared Zod schemas
|
|
78
|
+
tsconfig/
|
|
79
|
+
eslint-config/
|
|
80
|
+
biome-config/
|
|
81
|
+
nx.json
|
|
82
|
+
pnpm-workspace.yaml
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Rules
|
|
86
|
+
|
|
87
|
+
- Feature folders are flat by default — add sub-folders only when a feature grows large
|
|
88
|
+
- `shared/` files are named by purpose, not by type: `format-currency.ts` not `string-utils.ts`
|
|
89
|
+
- `lib/` contains only external service adapters — no business logic
|
|
90
|
+
- Tests live next to the code they test, not in a separate `__tests__/` directory
|
|
91
|
+
- Use `index.ts` barrel exports sparingly — only at feature boundaries, not inside features
|
|
92
|
+
|
|
93
|
+
## File Naming
|
|
94
|
+
|
|
95
|
+
| Type | Convention | Example |
|
|
96
|
+
|---|---|---|
|
|
97
|
+
| React components | `PascalCase.tsx` | `InvoiceTable.tsx` |
|
|
98
|
+
| Hooks | `camelCase.ts` | `useInvoiceSort.ts` |
|
|
99
|
+
| Schemas | `kebab-case.schema.ts` | `invoice.schema.ts` |
|
|
100
|
+
| API functions | `kebab-case.api.ts` | `invoice.api.ts` |
|
|
101
|
+
| Utilities | `kebab-case.ts` | `format-currency.ts` |
|
|
102
|
+
| Tests | same name + `.test.ts(x)` | `InvoiceTable.test.tsx` |
|
|
103
|
+
|
|
104
|
+
## See Also
|
|
105
|
+
|
|
106
|
+
- [`feature-driven.md`](feature-driven.md) — feature folder anatomy
|
|
107
|
+
- [`shared-code.md`](shared-code.md) — `shared/` vs `lib/`
|
|
108
|
+
- [`dependency-boundaries.md`](dependency-boundaries.md) — import rules between layers
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Monorepos
|
|
2
|
+
|
|
3
|
+
## Preferred Tooling
|
|
4
|
+
|
|
5
|
+
- **Nx** — build system, task orchestration, generators, affected commands
|
|
6
|
+
- **pnpm** — package manager (workspaces, efficient storage, strict linking)
|
|
7
|
+
|
|
8
|
+
## When to Use a Monorepo
|
|
9
|
+
|
|
10
|
+
Use a monorepo when:
|
|
11
|
+
- A frontend and backend share types or schemas
|
|
12
|
+
- Multiple apps share a component library
|
|
13
|
+
- A team maintains more than one deployable unit with significant shared code
|
|
14
|
+
|
|
15
|
+
Do not add monorepo tooling to a single-app project speculatively.
|
|
16
|
+
|
|
17
|
+
## Workspace Structure
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
apps/
|
|
21
|
+
web/ ← Next.js / Remix frontend
|
|
22
|
+
api/ ← Express / Hono / tRPC backend
|
|
23
|
+
packages/
|
|
24
|
+
ui/ ← shared component library
|
|
25
|
+
schemas/ ← shared Zod schemas (FE + BE)
|
|
26
|
+
tsconfig/ ← shared tsconfig presets
|
|
27
|
+
eslint-config/ ← shared ESLint config
|
|
28
|
+
biome-config/ ← shared Biome config
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Group packages by domain when scaling:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
packages/
|
|
35
|
+
frontend/
|
|
36
|
+
ui/
|
|
37
|
+
design-tokens/
|
|
38
|
+
backend/
|
|
39
|
+
database/
|
|
40
|
+
email/
|
|
41
|
+
shared/
|
|
42
|
+
schemas/
|
|
43
|
+
utils/
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Nx Setup
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pnpm dlx create-nx-workspace@latest acme --preset=ts
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Key Nx commands:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
nx build web # build a specific project
|
|
56
|
+
nx test api # test a specific project
|
|
57
|
+
nx affected --target=build # build only changed projects
|
|
58
|
+
nx graph # visualize dependency graph
|
|
59
|
+
nx generate @nx/react:component # generate a component
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## pnpm Workspaces
|
|
63
|
+
|
|
64
|
+
```yaml
|
|
65
|
+
# pnpm-workspace.yaml
|
|
66
|
+
packages:
|
|
67
|
+
- "apps/*"
|
|
68
|
+
- "packages/*"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Use workspace protocol for internal dependencies:
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{
|
|
75
|
+
"dependencies": {
|
|
76
|
+
"@acme/schemas": "workspace:*",
|
|
77
|
+
"@acme/ui": "workspace:*"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Dependency Rules
|
|
83
|
+
|
|
84
|
+
- Apps may depend on packages
|
|
85
|
+
- Packages must not depend on apps
|
|
86
|
+
- Packages should not depend on other packages unless the dependency is stable and intentional
|
|
87
|
+
- Use `nx graph` to audit the dependency graph
|
|
88
|
+
- Circular dependencies are forbidden
|
|
89
|
+
|
|
90
|
+
## DO NOT
|
|
91
|
+
|
|
92
|
+
- Create publishable packages unless explicitly required for external consumption
|
|
93
|
+
- Add unnecessary inter-package dependencies
|
|
94
|
+
- Bypass the monorepo's package boundaries with relative imports across app/package boundaries
|
|
95
|
+
- Use `npm` or `yarn` in a pnpm workspace
|
|
96
|
+
|
|
97
|
+
## PRIORITY
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
Feature isolation > Build efficiency > Package granularity
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## See Also
|
|
104
|
+
|
|
105
|
+
- [`../tooling/nx.md`](../tooling/nx.md) — Nx commands and configuration
|
|
106
|
+
- [`dependency-boundaries.md`](dependency-boundaries.md) — cross-package rules
|
|
107
|
+
- [`../tooling/ci.md`](../tooling/ci.md) — `nx affected` in CI
|