@stonepandastudio/cairn 0.4.2 → 0.6.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 (72) hide show
  1. package/README.md +60 -23
  2. package/bin/cairn.js +13 -5
  3. package/lib/doctor/index.js +6 -1
  4. package/lib/init.js +15 -1
  5. package/lib/manifest.js +33 -0
  6. package/lib/render/cli.js +116 -0
  7. package/lib/render/engine.js +148 -0
  8. package/lib/render/index.js +220 -0
  9. package/lib/sync/cli.js +197 -0
  10. package/lib/sync/index.js +249 -0
  11. package/package.json +2 -1
  12. package/presets/EXTRACTION.md +210 -0
  13. package/presets/README.md +86 -0
  14. package/presets/angular/code-guidelines.md +197 -0
  15. package/presets/angular/slots/architect-discussion-topics.md +10 -0
  16. package/presets/angular/slots/architect-mandatory-docs.md +5 -0
  17. package/presets/angular/slots/architect-references.md +4 -0
  18. package/presets/angular/slots/implementation-reference.md +34 -0
  19. package/presets/angular/slots/key-patterns.md +11 -0
  20. package/presets/angular/slots/plan-step-ordering.md +11 -0
  21. package/presets/angular/slots/review-checklist.md +16 -0
  22. package/presets/angular/variants/i18n-external-service.md +11 -0
  23. package/presets/angular/variants/i18n-glossr.md +63 -0
  24. package/presets/core/AGENTS.md +49 -0
  25. package/presets/core/README.md +35 -0
  26. package/presets/core/WORKFLOW.md +56 -0
  27. package/presets/core/agents/architect.md +269 -0
  28. package/presets/core/agents/developer.md +145 -0
  29. package/presets/core/agents/reviewer.md +167 -0
  30. package/presets/core/commands/_stub.md +7 -0
  31. package/presets/core/workflow.json +45 -0
  32. package/presets/drizzle/code-guidelines.md +33 -0
  33. package/presets/drizzle/slots/architect-discussion-topics.md +4 -0
  34. package/presets/drizzle/slots/architect-mandatory-docs.md +4 -0
  35. package/presets/drizzle/slots/implementation-reference.md +17 -0
  36. package/presets/drizzle/slots/key-patterns.md +7 -0
  37. package/presets/drizzle/slots/review-checklist.md +10 -0
  38. package/presets/nestjs/code-guidelines.md +273 -0
  39. package/presets/nestjs/slots/architect-discussion-topics.md +4 -0
  40. package/presets/nestjs/slots/architect-mandatory-docs.md +5 -0
  41. package/presets/nestjs/slots/architect-references.md +5 -0
  42. package/presets/nestjs/slots/implementation-reference.md +45 -0
  43. package/presets/nestjs/slots/key-patterns.md +11 -0
  44. package/presets/nestjs/slots/plan-step-ordering.md +12 -0
  45. package/presets/nestjs/slots/review-checklist.md +12 -0
  46. package/presets/nestjs/variants/validation-class-validator.md +120 -0
  47. package/presets/nestjs/variants/validation-zod.md +194 -0
  48. package/presets/nextjs/code-guidelines.md +45 -0
  49. package/presets/nextjs/slots/architect-discussion-topics.md +5 -0
  50. package/presets/nextjs/slots/architect-mandatory-docs.md +3 -0
  51. package/presets/nextjs/slots/architect-references.md +6 -0
  52. package/presets/nextjs/slots/implementation-reference.md +24 -0
  53. package/presets/nextjs/slots/key-patterns.md +8 -0
  54. package/presets/nextjs/slots/plan-step-ordering.md +11 -0
  55. package/presets/nextjs/slots/review-checklist.md +11 -0
  56. package/presets/react/code-guidelines.md +46 -0
  57. package/presets/react/slots/architect-discussion-topics.md +5 -0
  58. package/presets/react/slots/architect-references.md +5 -0
  59. package/presets/react/slots/implementation-reference.md +26 -0
  60. package/presets/react/slots/key-patterns.md +8 -0
  61. package/presets/react/slots/plan-step-ordering.md +9 -0
  62. package/presets/react/slots/review-checklist.md +10 -0
  63. package/presets/tailwind/code-guidelines.md +28 -0
  64. package/presets/tailwind/slots/implementation-reference.md +8 -0
  65. package/presets/tailwind/slots/key-patterns.md +5 -0
  66. package/presets/tailwind/slots/review-checklist.md +8 -0
  67. package/presets/typeorm/code-guidelines.md +329 -0
  68. package/presets/typeorm/slots/architect-discussion-topics.md +4 -0
  69. package/presets/typeorm/slots/architect-mandatory-docs.md +3 -0
  70. package/presets/typeorm/slots/implementation-reference.md +19 -0
  71. package/presets/typeorm/slots/key-patterns.md +8 -0
  72. package/presets/typeorm/slots/review-checklist.md +8 -0
@@ -0,0 +1,12 @@
1
+ <!-- cairn preset: nestjs — fills {{> stack/review-checklist }} in the core reviewer.
2
+ Framework rules only. A repo on `--stack nestjs,typeorm` also gets the ORM
3
+ rows from presets/typeorm/slots/review-checklist.md (the renderer concatenates
4
+ this slot from every stack entry). Response-mapping and validation-decorator
5
+ rows come from the chosen validation-stack variant, not here. -->
6
+
7
+ | No `any` type — use proper TypeScript types | All changed files |
8
+ | Enums in `enums/` folder, constants in `constants/` folder, kebab-case filenames | Feature module |
9
+ | No magic strings — use enum values or named constants | Anywhere |
10
+ | API modules contain no business logic (thin controllers, delegate to feature services) | Controller / API service files |
11
+ | Feature modules export only the services that API modules need | Module files |
12
+ | NestJS built-in exceptions used for errors (`NotFoundException`, `BadRequestException`, …) | Service and controller files |
@@ -0,0 +1,120 @@
1
+ <!-- cairn variant: validation-stack = class-validator. Fills the dto-naming,
2
+ dto-definition, validation-stack, controller-response,
3
+ response-transformation and pagination-dtos slots in
4
+ presets/node/code-guidelines.md.
5
+ In use by: snap-backend. Requires `class-validator` + `class-transformer`
6
+ and the project's own `@ResponseMapper` decorator. -->
7
+
8
+ ## slot: dto-naming
9
+
10
+ * Use operation-prefixed names for incoming request data:
11
+ - `CreateProjectDto`, `UpdateUserDto`
12
+ - File: `create-feature.dto.ts`, `update-feature.dto.ts`
13
+ * Use the `Response` suffix for server response shapes:
14
+ - `UserResponse`, `ProjectListResponse`
15
+ - File: `feature-response.dto.ts`
16
+
17
+ ## slot: dto-definition
18
+
19
+ * Use class-validator decorators: `@IsString()`, `@IsNumber()`, `@IsEmail()`, `@IsOptional()`, `@IsNotEmpty()`
20
+ * Use class-transformer decorators: `@Type()`, `@Exclude()`, `@Expose()`
21
+ * Add Swagger documentation: `@ApiProperty()`, `@ApiPropertyOptional()`
22
+ * Provide validation error messages where appropriate
23
+
24
+ ## slot: validation-stack
25
+
26
+ * Use class-validator decorators on all DTO properties
27
+ * Use `ValidationPipe` globally or per-controller
28
+ * Create custom validators for complex validation logic
29
+
30
+ ## slot: controller-response
31
+
32
+ * Apply `@UsePipes(ValidationPipe)` for automatic DTO validation
33
+ * Annotate the response shape with `@ResponseMapper(ResponseDtoClass)` on every endpoint that returns data
34
+
35
+ ## slot: response-transformation
36
+
37
+ **IMPORTANT**: The `@ResponseMapper(ResponseDtoClass)` decorator automatically handles entity-to-DTO transformation using class-transformer.
38
+
39
+ **How it works**:
40
+ * The decorator uses `@Expose()` decorators in the response DTO to pick which fields to include
41
+ * The decorator uses `@Type()` decorators to handle nested object transformations
42
+ * You **DO NOT** need to manually create mapper functions to pick fields
43
+
44
+ **Best Practices**:
45
+ * Return entities or plain objects from service methods
46
+ * Only perform minimal transformations needed for data structure (e.g., flattening nested properties)
47
+ * Let `@ResponseMapper` handle field selection and type transformation
48
+ * Do **NOT** create separate mapper functions just to pick fields - the decorator does this automatically
49
+
50
+ **Example - Correct approach**:
51
+ ```typescript
52
+ // Service method - minimal transformation
53
+ async getDevicesByProjects() {
54
+ const devices = await this.siteDeviceService.getDevicesByProjects();
55
+
56
+ // Only flatten nested structures that don't match DTO
57
+ return devices.map(device => ({
58
+ ...device,
59
+ site: device.site ? {
60
+ ...device.site,
61
+ city: device.site.city?.name || null // Flatten CityEntity to string
62
+ } : null,
63
+ }));
64
+ }
65
+
66
+ // Controller - @ResponseMapper handles field picking
67
+ @Get('by-projects')
68
+ @ResponseMapper(DeviceWithProjectsResponse)
69
+ async getDevicesByProjects(@Query() query: QueryDto) {
70
+ return this.service.getDevicesByProjects(query);
71
+ }
72
+
73
+ // Response DTO - @Expose() picks fields automatically
74
+ export class DeviceWithProjectsResponse {
75
+ @Expose() id: string;
76
+ @Expose() description: string;
77
+ // Only exposed fields will be returned
78
+ }
79
+ ```
80
+
81
+ **Example - Incorrect approach** (avoid this):
82
+ ```typescript
83
+ // ❌ DON'T create mapper functions just to pick fields
84
+ function mapToResponse(device) {
85
+ return {
86
+ id: device.id, // @ResponseMapper does this
87
+ description: device.description, // automatically
88
+ };
89
+ }
90
+ ```
91
+
92
+ ## slot: pagination-dtos
93
+
94
+ **Query params** — declare `page` and `pageSize` with `@Type(() => Number)` so query strings coerce:
95
+
96
+ ```typescript
97
+ export class FooListQuery {
98
+ @Type(() => Number)
99
+ @IsInt()
100
+ @Min(1)
101
+ page = 1;
102
+
103
+ @Type(() => Number)
104
+ @IsInt()
105
+ @Min(1)
106
+ @Max(100)
107
+ pageSize = 50;
108
+ }
109
+ ```
110
+
111
+ **Response DTO** — expose the envelope fields and type the items:
112
+
113
+ ```typescript
114
+ export class FooListResponse {
115
+ @Expose() @Type(() => FooResponse) items: FooResponse[];
116
+ @Expose() total: number;
117
+ @Expose() skip: number;
118
+ @Expose() take: number;
119
+ }
120
+ ```
@@ -0,0 +1,194 @@
1
+ <!-- cairn variant: validation-stack = zod. Fills the dto-naming, dto-definition,
2
+ validation-stack, controller-response, response-transformation and
3
+ pagination-dtos slots in presets/node/code-guidelines.md.
4
+ In use by: glossr-api. Requires `zod` + `nestjs-zod`. -->
5
+
6
+ ## slot: dto-naming
7
+
8
+ * Use **`Payload` prefix** for incoming request data (body, query params):
9
+ - `RegisterOwnerPayload`, `CreateProjectPayload`, `UpdateUserPayload`
10
+ - File: `register-owner-payload.dto.ts`
11
+ * Use **`Response` prefix** for server response shapes:
12
+ - `RegisterOwnerResponse`, `UserResponse`, `ProjectListResponse`
13
+ - File: `register-owner-response.dto.ts`
14
+
15
+ **Examples**:
16
+ ```typescript
17
+ // Request DTO
18
+ export class RegisterOwnerPayload extends createZodDto(RegisterOwnerSchema) {}
19
+
20
+ // Response DTO
21
+ export class RegisterOwnerResponse extends createZodDto(RegisterOwnerResponseSchema) {}
22
+ ```
23
+
24
+ ## slot: dto-definition
25
+
26
+ * Use **Zod schemas** with `nestjs-zod` integration: `z.string()`, `z.number()`, `z.email()`, `z.optional()`, etc.
27
+ * Create DTOs by extending `createZodDto()` from Zod schemas
28
+ * Add Swagger documentation via Zod schema metadata or `@ApiProperty()` decorators
29
+ * Provide validation error messages in Zod schema definitions
30
+
31
+ **Example**:
32
+ ```typescript
33
+ import { z } from 'zod';
34
+ import { createZodDto } from 'nestjs-zod';
35
+
36
+ const CreateUserSchema = z.object({
37
+ email: z.string().email('Invalid email format'),
38
+ password: z.string().min(8, 'Password must be at least 8 characters'),
39
+ name: z.string().min(1, 'Name is required'),
40
+ });
41
+
42
+ export class CreateUserDto extends createZodDto(CreateUserSchema) {}
43
+ ```
44
+
45
+ ## slot: validation-stack
46
+
47
+ * Use **Zod schemas** for all DTO validation
48
+ * Use `ZodValidationPipe` (from `nestjs-zod`) globally or per-controller
49
+ * Create custom Zod schemas for complex validation logic
50
+ * Provide clear, user-friendly validation error messages in schema definitions
51
+
52
+ ## slot: controller-response
53
+
54
+ * **ALWAYS** use `@ZodResponse({ type: MyResponse })` on every endpoint that returns data — do NOT add explicit TypeScript return types when using `@ZodResponse` as it infers them
55
+
56
+ **`@ZodResponse` rules:**
57
+ * Replaces `@ZodSerializerDto`, `@ApiResponse`, and the return type annotation in one decorator
58
+ * Keeps run-time serialization, compile-time types, and OpenAPI docs in sync
59
+ * For arrays use `[MyDto]` syntax: `@ZodResponse({ type: [UserResponse] })`
60
+ * For non-200 status codes: `@ZodResponse({ status: 201, type: MyResponse })`
61
+ * For endpoints returning `void` (e.g. DELETE), omit `@ZodResponse`
62
+ * For endpoints returning raw streams/files (using `@Res()`), omit `@ZodResponse`
63
+
64
+ **Example**:
65
+ ```typescript
66
+ import { ZodResponse } from 'nestjs-zod';
67
+
68
+ @Controller('users')
69
+ export class UserController {
70
+ @Get('me')
71
+ @UseGuards(JwtAuthGuard)
72
+ @ZodResponse({ type: UserResponse })
73
+ async getMe(@CurrentUser('sub') userId: string) {
74
+ return this.userService.findById(userId);
75
+ }
76
+
77
+ @Get()
78
+ @ZodResponse({ type: [UserResponse] })
79
+ async listUsers() {
80
+ return this.userService.findAll();
81
+ }
82
+
83
+ @Post()
84
+ @ZodResponse({ status: 201, type: UserResponse })
85
+ async createUser(@Body() dto: CreateUserPayload) {
86
+ return this.userService.create(dto);
87
+ }
88
+
89
+ @Delete(':id')
90
+ // No @ZodResponse — returns void
91
+ async deleteUser(@Param('id') id: string): Promise<void> {
92
+ await this.userService.delete(id);
93
+ }
94
+ }
95
+ ```
96
+
97
+ ## slot: response-transformation
98
+
99
+ **IMPORTANT**: Use Zod schemas for response DTOs to automatically handle entity-to-DTO transformation with type safety.
100
+
101
+ **How it works**:
102
+ * Response DTOs extend `createZodDto()` from Zod schemas
103
+ * Zod schemas pick which fields to include in the response
104
+ * Automatic type inference from Zod schemas
105
+ * Built-in serialization support via `nestjs-zod`
106
+
107
+ **Best Practices**:
108
+ * Return entities or plain objects from service methods
109
+ * Define response DTOs as separate Zod schemas
110
+ * Only perform minimal transformations needed for data structure (e.g., flattening nested properties)
111
+ * Let Zod handle field selection and type transformation
112
+ * Do **NOT** create separate mapper functions just to pick fields
113
+
114
+ **Example - Correct approach**:
115
+ ```typescript
116
+ import { z } from 'zod';
117
+ import { createZodDto } from 'nestjs-zod';
118
+
119
+ // Response schema - pick fields that should be returned
120
+ const DeviceWithProjectsSchema = z.object({
121
+ id: z.string(),
122
+ description: z.string(),
123
+ site: z.object({
124
+ id: z.string(),
125
+ name: z.string(),
126
+ city: z.string().nullable(),
127
+ }).nullable(),
128
+ });
129
+
130
+ export class DeviceWithProjectsResponse extends createZodDto(DeviceWithProjectsSchema) {}
131
+
132
+ // Service method - minimal transformation
133
+ async getDevicesByProjects() {
134
+ const devices = await this.siteDeviceService.getDevicesByProjects();
135
+
136
+ // Only flatten nested structures that don't match schema
137
+ return devices.map(device => ({
138
+ ...device,
139
+ site: device.site ? {
140
+ ...device.site,
141
+ city: device.site.city?.name || null // Flatten CityEntity to string
142
+ } : null,
143
+ }));
144
+ }
145
+ ```
146
+
147
+ **Example - Incorrect approach** (avoid this):
148
+ ```typescript
149
+ // ❌ DON'T create mapper functions just to pick fields
150
+ function mapToResponse(device) {
151
+ return {
152
+ id: device.id, // The Zod schema does this automatically
153
+ description: device.description,
154
+ };
155
+ }
156
+ ```
157
+
158
+ ## slot: pagination-dtos
159
+
160
+ **Query params** — always `page` + `pageSize` with `z.coerce.number()` (coercion required because query params arrive as strings):
161
+
162
+ ```typescript
163
+ // src/api/modules/fooApi/dtos/foo-list-query.dto.ts
164
+ const FooListQuerySchema = z.object({
165
+ page: z.coerce.number().int().min(1).default(1),
166
+ pageSize: z.coerce.number().int().min(1).max(100).default(50),
167
+ // add sorting / filter fields as needed
168
+ });
169
+
170
+ export class FooListQuery extends createZodDto(FooListQuerySchema) {}
171
+ ```
172
+
173
+ **Response DTO** — wrap the item schema with `paginatedResponseSchema`:
174
+
175
+ ```typescript
176
+ // src/api/modules/fooApi/dtos/foo-list-response.dto.ts
177
+ import { createZodDto } from 'nestjs-zod';
178
+ import { paginatedResponseSchema } from '@common/dtos/paginated-response.schema';
179
+ import { FooResponseSchema } from './foo-response.dto';
180
+
181
+ const FooListResponseSchema = paginatedResponseSchema(FooResponseSchema);
182
+
183
+ export class FooListResponse extends createZodDto(FooListResponseSchema) {}
184
+ ```
185
+
186
+ **Controller**:
187
+
188
+ ```typescript
189
+ @Get()
190
+ @ZodResponse({ type: FooListResponse })
191
+ async list(@Query() query: FooListQuery) {
192
+ return this.fooService.list(query);
193
+ }
194
+ ```
@@ -0,0 +1,45 @@
1
+ # Next.js Guidelines
2
+
3
+ <!-- cairn preset: nextjs. The App-Router framework layer, added on top of the
4
+ `react` component base: `--stack nextjs,react,tailwind,drizzle`. It owns the
5
+ server/client boundary, Server Actions, Route Handlers, and the App-Router
6
+ file conventions — not component/hook/state basics, which stay in
7
+ `presets/react`. Seeded 2026-09-09 from charityiq-app (Next.js 16, Sprint 0).
8
+ Single consumer; refine when a second Next.js repo appears. -->
9
+
10
+ ## The framework moves fast
11
+
12
+ Next.js's App Router, `"use cache"`, and the RSC model change between minor
13
+ versions and are often ahead of training data. Before writing anything non-obvious,
14
+ check `node_modules/next/dist/**` or the version's docs. Heed deprecation notices.
15
+
16
+ ## Server / Client boundary
17
+
18
+ * **Server Components by default.** Add `"use client"` only for interactivity, browser APIs, or hooks (`useState`, `useEffect`, event handlers).
19
+ * A `"use client"` module must not import server-only code — DB clients, secrets, anything marked `server-only`. Push the client boundary as far down the tree as possible; a page can be a Server Component that renders a small client island.
20
+ * Never read a secret in a Client Component. Client-exposed env vars carry the `NEXT_PUBLIC_` prefix; everything else is server-only.
21
+
22
+ ## Server Actions
23
+
24
+ * `'use server'` at the top of the module (or the function). Actions run on the server; treat every argument as untrusted input and validate it.
25
+ * An action is a mutation entry point — it does the whole unit of work (validate → authorize → mutate → revalidate) and returns a serializable result.
26
+ * Call `revalidatePath` / `revalidateTag` after a write so the RSC cache reflects it.
27
+ * Do not use an action as a generic RPC for reads — read in the Server Component.
28
+
29
+ ## Route Handlers
30
+
31
+ * `app/**/route.ts` for webhooks, third-party callbacks, and non-action HTTP (file downloads, health checks).
32
+ * Verify the caller (signature, secret, or auth) before doing any work.
33
+ * Keep them thin — parse, authorize, delegate to a service, respond.
34
+
35
+ ## App Router structure
36
+
37
+ * `page.tsx` / `layout.tsx` / `loading.tsx` / `error.tsx` / `not-found.tsx` per segment; route groups `(name)/` for layout scoping without a URL segment; `[param]` / `[...slug]` for dynamic routes.
38
+ * Colocate a segment's server code (`actions.ts`, `queries.ts`) inside its folder.
39
+ * `Metadata` export (static or `generateMetadata`) on every routable page for SEO.
40
+ * `next/font` for fonts, `next/image` for images — never a raw `<img>` for content images or a `<link>` font.
41
+
42
+ ## Data fetching
43
+
44
+ * Fetch in Server Components and Server Actions, not in client `useEffect`.
45
+ * Reach for the framework's caching primitives (`fetch` cache options, `revalidate`, `unstable_cache` / `"use cache"` where stable) rather than a bespoke client cache.
@@ -0,0 +1,5 @@
1
+ <!-- cairn preset: nextjs — appended to the architect-discussion-topics slot on a
2
+ `--stack nextjs,react,...` repo. -->
3
+ - **Server vs client** — does this segment need interactivity, or can it stay a Server Component
4
+ - **Action vs Route Handler** — a user-triggered mutation (action) or an external/webhook entry point (handler)
5
+ - **Cache / revalidation** — what has to be re-fetched after this write, and how it's tagged
@@ -0,0 +1,3 @@
1
+ <!-- cairn preset: nextjs — appended to the architect-mandatory-docs slot on a
2
+ `--stack nextjs,react,...` repo. -->
3
+ - `ai/infrastructure/project-structure.md` — the App Router layout and where server code lives
@@ -0,0 +1,6 @@
1
+ <!-- cairn preset: nextjs — fills {{> stack/architect-references }} (A.1).
2
+ Primary-stack-only. This is a fullstack repo — one place holds both the UI
3
+ and the server code. -->
4
+ - The App Router tree (`app/`) — pages, layouts, colocated `actions.ts` / `queries.ts`
5
+ - Shared server modules (`lib/`) — DB, auth, integrations, the AI client
6
+ - Context docs in `ai/contexts/**` — entities, features, page/flow business logic
@@ -0,0 +1,24 @@
1
+ <!-- cairn preset: nextjs — appended to the implementation-reference slot on a
2
+ `--stack nextjs,react,...` repo. -->
3
+
4
+ ### Server / Client
5
+ - Default Server Component; `"use client"` at the smallest island that needs it
6
+ - Client boundary never pulls in DB clients, secrets, or `server-only` modules
7
+ - Pass data down as props from the Server Component; don't refetch on the client
8
+
9
+ ### Server Actions
10
+ - `'use server'`; validate args; do the whole unit of work (authorize → mutate → revalidate)
11
+ - `revalidatePath` / `revalidateTag` after a write; return a serializable result
12
+ - Colocate as `app/<segment>/actions.ts`
13
+
14
+ ### Route Handlers
15
+ - `app/**/route.ts` for webhooks and non-action HTTP; verify the caller first; keep thin
16
+
17
+ ### App Router
18
+ - `page` / `layout` / `loading` / `error` / `not-found` per segment
19
+ - Route groups `(name)/`, dynamic `[param]` / `[...slug]`
20
+ - `Metadata` / `generateMetadata` on every routable page
21
+ - `next/font`, `next/image` — no raw `<img>` / `<link>` font
22
+
23
+ ### Data
24
+ - Fetch in RSC / Actions; use framework cache primitives, not a client cache
@@ -0,0 +1,8 @@
1
+ <!-- cairn preset: nextjs — appended to the key-patterns slot on a
2
+ `--stack nextjs,react,...` repo. -->
3
+
4
+ App Router structure, the server/client boundary, Server Action and Route Handler
5
+ conventions: **`ai/infrastructure/code-guidelines.md`** § Next.js.
6
+
7
+ Existing routes and their layouts: **`app/`** — mirror the nearest segment's shape
8
+ (colocated `actions.ts` / `queries.ts`, `loading` / `error` files) before adding one.
@@ -0,0 +1,11 @@
1
+ <!-- cairn preset: nextjs — fills {{> stack/plan-step-ordering }} in the core
2
+ architect's Plan format. Primary-stack-only. Put `nextjs` first in `--stack`
3
+ so this fullstack ordering wins over the plain-react one. -->
4
+ 1. Types / schema / DB migration
5
+ 2. Server modules — queries, integrations, `lib/` helpers
6
+ 3. Server Actions / Route Handlers
7
+ 4. Server Components — pages, layouts, data loading
8
+ 5. Client islands — interactive components
9
+ 6. Styling and responsive / dark-mode passes
10
+ 7. Tests — Vitest units, then a Playwright flow
11
+ 8. Context doc updates
@@ -0,0 +1,11 @@
1
+ <!-- cairn preset: nextjs — appended to the review-checklist slot on a
2
+ `--stack nextjs,react,...` repo. Framework-boundary rules only. -->
3
+
4
+ | Server Component by default — `"use client"` only for interactivity / browser APIs / hooks | component `.tsx` |
5
+ | `"use client"` module imports no server-only code (DB client, secrets, `server-only`) | client components |
6
+ | No secret read in a Client Component; client env vars are `NEXT_PUBLIC_`-prefixed | `.tsx`, env usage |
7
+ | Server Action starts with `'use server'` and validates every argument as untrusted | `actions.ts` |
8
+ | Server Action calls `revalidatePath` / `revalidateTag` after a write | `actions.ts` |
9
+ | Route Handler verifies the caller before any work | `app/**/route.ts` |
10
+ | Data fetched in a Server Component / Action, not a client `useEffect` | component / hook files |
11
+ | Routable page exports `Metadata`; content images use `next/image`, fonts use `next/font` | `page.tsx`, `layout.tsx` |
@@ -0,0 +1,46 @@
1
+ # React Guidelines
2
+
3
+ <!-- cairn preset: react. The frontend framework spine for a React app — Vite SPA
4
+ or the client layer of a Next.js app. Seeded 2026-09-09 from charityiq-app
5
+ (Sprint 0: CLAUDE.md conventions + ADR 0001, no component code yet). Single
6
+ consumer; refine when a second React repo appears.
7
+
8
+ What is NOT here: RSC, Server Actions, route handlers, the App Router. Those
9
+ are Next.js-specific — a `presets/nextjs` composes on top (`--stack
10
+ nextjs,drizzle`) if the repo stays Next.js. If it splits into a NestJS API +
11
+ a React SPA, this preset pairs with `presets/nestjs` (`--stack
12
+ nestjs,react,typeorm` on the API side is nonsense — each repo has its own
13
+ stack). The styling system is a separate add-on (`presets/tailwind`); this
14
+ preset is styling-agnostic. -->
15
+
16
+ ## Components
17
+
18
+ * Function components only. One component per file, named after the file.
19
+ * Group by role: `components/ui/` (primitives), `components/feature/` (domain), `components/brand/` (marketing/identity).
20
+ * Props typed with an explicit `interface` or `type`; no `any` without a comment saying why.
21
+ * Derive, don't duplicate — compute from props/state in render, not in an effect.
22
+
23
+ ## State
24
+
25
+ * **No `localStorage` or `sessionStorage`.** Persistent state lives server-side; per-session state lives in signed cookies. Client caches that survive a reload leak data across users on shared devices.
26
+ * Local UI state via `useState` / `useReducer`. Cross-tree state via context, kept small.
27
+ * Server state via a query library (or RSC) — not hand-rolled fetch-in-effect.
28
+
29
+ ## Styling
30
+
31
+ * Framework-agnostic — the styling system is a composable add-on. For Tailwind, see
32
+ `presets/tailwind` (`--stack react,tailwind`). The one rule that holds regardless:
33
+ no hard-coded colours in components; go through whatever token layer the styling
34
+ preset defines.
35
+
36
+ ## Tests
37
+
38
+ * Unit: **Vitest** + jsdom + Testing Library, in `tests/unit/`. Test behaviour, not implementation.
39
+ * HTTP mocks: **MSW**.
40
+ * End-to-end: **Playwright**, in `tests/e2e/`.
41
+
42
+ ## Tooling
43
+
44
+ * TypeScript strict.
45
+ * **Biome** for formatting (tabs, double quotes) and import organisation; ESLint for lint rules Biome does not cover.
46
+ * Path alias `@/*` → repo root.
@@ -0,0 +1,5 @@
1
+ <!-- cairn preset: react — fills {{> stack/architect-discussion-topics }} (A.3). -->
2
+ - **Ambiguities** — anything in the description that admits multiple reasonable interpretations
3
+ - **UX / layout tradeoffs** — modal vs. page, tabs vs. stacked sections, inline vs. separate view
4
+ - **Component boundary** — extend an existing `ui/`/`feature/` component vs. add a new one
5
+ - **State placement** — local, context, or server
@@ -0,0 +1,5 @@
1
+ <!-- cairn preset: react — fills {{> stack/architect-references }} (A.1).
2
+ Primary-stack-only, not concatenated. -->
3
+ - Frontend source — components, hooks, the app shell
4
+ - The API's context docs, if the task touches a request/response shape the frontend consumes
5
+ - The API source, for shape confirmation
@@ -0,0 +1,26 @@
1
+ <!-- cairn preset: react — fills {{> stack/implementation-reference }} in the core
2
+ developer. -->
3
+
4
+ ### Components
5
+ - Function component per file; props via an explicit `interface`
6
+ - Folder by role: `components/{ui,feature,brand}/`
7
+ - Compose small primitives; lift state only as far as it is shared
8
+
9
+ ### Hooks
10
+ - Custom hooks in `hooks/` (or colocated), prefixed `use`
11
+ - One concern per hook; return a stable shape
12
+ - Effects only for genuine side effects (subscriptions, imperative DOM) — not to mirror props into state
13
+
14
+ ### Styling
15
+ - Whatever the styling add-on defines (`presets/tailwind` for Tailwind repos)
16
+ - The constant: no inline hex in components, responsive and dark mode handled at the token layer
17
+
18
+ ### Data
19
+ - Server state through a query library (or RSC where the framework provides it)
20
+ - No `localStorage` / `sessionStorage`
21
+ - Loading and error states are explicit, not implicit
22
+
23
+ ### Tests
24
+ - `tests/unit/*.test.tsx` — Vitest + Testing Library, behaviour-first
25
+ - MSW for any HTTP the component makes
26
+ - `tests/e2e/*.spec.ts` — Playwright for the whole flow
@@ -0,0 +1,8 @@
1
+ <!-- cairn preset: react — fills {{> stack/key-patterns }} in the core architect
2
+ and developer. -->
3
+
4
+ Component and folder conventions, state rules, test wiring:
5
+ **`ai/infrastructure/code-guidelines.md`** § React.
6
+
7
+ Reusable primitives and domain components already built: **`components/`** — check
8
+ `ui/` and `feature/` for an analog before adding a new one.
@@ -0,0 +1,9 @@
1
+ <!-- cairn preset: react — fills {{> stack/plan-step-ordering }} in the core
2
+ architect's Plan format. Primary-stack-only, not concatenated. -->
3
+ 1. Types / models / API client methods
4
+ 2. Hooks and state
5
+ 3. Components (`ui/` primitives first, then `feature/`)
6
+ 4. Wiring — routes, layout, data loading
7
+ 5. Styling (per the styling add-on) — responsive and dark-mode passes
8
+ 6. Tests — Vitest units, then a Playwright flow
9
+ 7. Context doc updates
@@ -0,0 +1,10 @@
1
+ <!-- cairn preset: react — fills {{> stack/review-checklist }} in the core reviewer.
2
+ Concatenated with any other stack entry's slot. -->
3
+
4
+ | Function components, one per file, typed props (no bare `any`) | component `.tsx` files |
5
+ | Component in the right folder — `ui/` primitive, `feature/` domain, `brand/` identity | `components/` |
6
+ | No `localStorage` / `sessionStorage` | any client code |
7
+ | No fetch-in-`useEffect` for server state — a query library or RSC | component / hook files |
8
+ | No hard-coded colours in components — go through the styling preset's token layer | `.tsx` |
9
+ | Derived values computed in render, not synced via an effect | component `.tsx` |
10
+ | New behaviour has a Vitest test; user-visible flow has a Playwright spec | `tests/unit/`, `tests/e2e/` |
@@ -0,0 +1,28 @@
1
+ # Tailwind Guidelines
2
+
3
+ <!-- cairn preset: tailwind. The styling add-on — orthogonal to the framework, the
4
+ way an ORM add-on is. Compose it: `--stack nextjs,tailwind,drizzle`,
5
+ `--stack react,tailwind`. Seeded 2026-09-09 from charityiq-app (Tailwind v4).
6
+ Single consumer; refine when a second Tailwind repo appears.
7
+
8
+ Not in it: the repo's actual brand token values and font choice — those are
9
+ project-owned (charityiq's `app/globals.css` holds `coral`/`ink`/`paper` and
10
+ Geist). This preset says *where* tokens go and *how* to consume them. -->
11
+
12
+ ## Tokens
13
+
14
+ * Design tokens live in a `@theme` block in the app's global stylesheet (Tailwind v4). **Never create a `tailwind.config.ts`** — Tailwind v4 ignores it.
15
+ * Semantic tokens (`--color-background`, `--color-foreground`) map to raw brand tokens; components use the semantic ones.
16
+ * Dark mode via `@media (prefers-color-scheme: dark)` re-mapping the semantic tokens — not a `dark:` variant on every element.
17
+
18
+ ## Usage
19
+
20
+ * Consume tokens through the standard utility names Tailwind generates (`bg-coral`, `text-ink`, `font-sans`). No raw hex, `rgb()`, or CSS custom properties in component markup.
21
+ * Utility classes in the markup; reach for `@apply` only for a genuinely repeated cluster, in a component-scoped stylesheet.
22
+ * Responsive with Tailwind's breakpoint prefixes; do not hand-write media queries for layout.
23
+ * Class order left to the formatter (Biome / Prettier plugin) — do not hand-sort.
24
+
25
+ ## Structure
26
+
27
+ * One global stylesheet for tokens and base layer; everything else is utilities in components.
28
+ * No global element selectors beyond a minimal base (`body`, `:root`).
@@ -0,0 +1,8 @@
1
+ <!-- cairn preset: tailwind — appended to the framework preset's
2
+ implementation-reference slot on `--stack <framework>,tailwind`. -->
3
+
4
+ ### Styling
5
+ - Tailwind v4 utilities in the markup; tokens from the `@theme` block
6
+ - Semantic tokens (`background`/`foreground`) in components; raw brand tokens only in `@theme`
7
+ - Responsive via breakpoint prefixes (`md:`, `lg:`); dark mode via `prefers-color-scheme` token re-mapping
8
+ - No `tailwind.config.ts`, no inline hex, no per-element `dark:` sprawl
@@ -0,0 +1,5 @@
1
+ <!-- cairn preset: tailwind — appended to the framework preset's key-patterns slot
2
+ on `--stack <framework>,tailwind`. -->
3
+
4
+ Design tokens and the base layer: the app's global stylesheet `@theme` block.
5
+ Token names and dark-mode mapping conventions: **`ai/infrastructure/code-guidelines.md`** § Tailwind.
@@ -0,0 +1,8 @@
1
+ <!-- cairn preset: tailwind — appended to the framework preset's review-checklist
2
+ slot on `--stack <framework>,tailwind`. -->
3
+
4
+ | No raw hex / `rgb()` / CSS custom properties in markup — only token utilities (`bg-coral`, `text-ink`) | `.tsx` / `.html` |
5
+ | No `tailwind.config.ts` — tokens live in a `@theme` block in the global stylesheet | project root |
6
+ | Dark mode via re-mapped semantic tokens, not `dark:` on every element | global stylesheet |
7
+ | `@apply` only for a genuinely repeated cluster, component-scoped — not scattered | component stylesheets |
8
+ | No hand-written layout media queries — Tailwind breakpoint prefixes | `.tsx` / `.html` |