guardian-framework 0.1.25 → 0.1.26

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.
@@ -1,383 +1,393 @@
1
1
  ---
2
2
  name: nextjs-enterprise-codegen
3
- description: Full reference for Next.js enterprise code generation with DDD + Clean Architecture. Covers module structure, server/client boundary, data fetching, state management, design system integration, error handling, testing, security, performance, and accessibility. Loaded on-demand via agents/nextjs-codegen.md skill.
3
+ description: Full reference for Next.js enterprise code generation with DDD + Clean Architecture. Every module uses 4 DDD layers: domain/, application/, infrastructure/, interfaces/. Covers User Intents, App Shell, design system, navigation philosophy, offline strategy, server/client boundary, and testing. Loaded on-demand via agents/nextjs-codegen.md skill.
4
4
  ---
5
5
 
6
6
  # Next.js Enterprise Code Generation — DDD + Clean Architecture
7
7
 
8
8
  > Canonical skill for generating production-grade Next.js code with DDD patterns.
9
- > All code MUST follow these patterns. Validators enforce compliance.
9
+ > **Every module MUST follow the 4 DDD layers below.**
10
+ > Validators enforce compliance.
10
11
  >
11
- > For design system and styling patterns, see: `.pi/skills/design-system-enterprise-codegen.md` — tokens, CSS adapters, theming, component API contracts
12
+ > For design system and styling patterns, see: `.pi/skills/design-system-enterprise-codegen.md`
12
13
 
13
14
  ---
14
15
 
15
- ## 1. Project Structure — DDD with Next.js App Router
16
+ ## 1. DDD Module Structure — The 4 Layers
17
+
18
+ Every feature module uses exactly this structure:
16
19
 
17
20
  ```
18
21
  src/
19
- module/ # One bounded context
20
- domain/ # Pure business logic, zero framework imports
21
- entity.ts # Domain entities with encapsulated state
22
- value.ts # Immutable value objects
23
- events.ts # Domain events
24
- errors.ts # Typed error classes
25
- application/ # Application logic
26
- store.ts # Zustand / Jotai state (client-only)
27
- actions.ts # Server Actions (server-only)
28
- queries.ts # React Query hooks for data fetching
29
- dto.ts # Command/Query DTOs
30
- infrastructure/ # External adapters
31
- api-client.ts # fetch wrapper, error handling, retry
32
- auth.ts # Auth provider SDK wrapper
33
- analytics.ts # Analytics SDK wrapper
34
- interfaces/ # UI layer
35
- page.tsx # App Router page (default: Server Component)
36
- component.client.tsx # Client Component with 'use client'
37
- layout.tsx # Layout wrapper
38
- shared/ # Cross-module shared code
39
- ui/ # Design system components
40
- button/
41
- card/
42
- modal/
43
- lib/ # Shared utilities
44
- hooks/ # Shared React hooks
22
+ collaboration/ # One bounded context / feature
23
+ domain/ # Layer 1: Pure business logic
24
+ types.ts # Types, interfaces, enums (zero React)
25
+ errors.ts # Typed error classes
26
+ notification.ts # Domain value objects
27
+ application/ # Layer 2: State + use cases
28
+ store.ts # Zustand store
29
+ actions.ts # Server Actions
30
+ infrastructure/ # Layer 3: External adapters
31
+ api.ts # API client (fetch)
32
+ interfaces/ # Layer 4: UI components
33
+ thread-panel.tsx # React components
34
+ comment-list.tsx
45
35
  ```
46
36
 
47
- ### Dependency Rule
37
+ ### Dependency Rule (Inward)
38
+
48
39
  ```
49
40
  domain → application → infrastructure → interfaces
50
-
51
- shared/ (no framework deps)
52
41
  ```
53
42
 
54
- - **domain/**zero React/Next.js imports. Pure TypeScript.
55
- - **application/** — imports from domain. May import from `shared/lib/`.
56
- - **infrastructure/** — imports from domain + application. Wraps external SDKs.
57
- - **interfaces/** — imports from application. Contains all React/Next.js code.
43
+ - `domain/`**ZERO React/Next.js imports.** Pure TypeScript. No `'use client'`.
44
+ - `application/` — imports from `domain/`. Stores, actions, server actions.
45
+ - `infrastructure/` — imports from `domain/` and `application/`. API clients, SDK wrappers.
46
+ - `interfaces/` — imports from `application/`. **Only this layer has React components.**
47
+
48
+ ### What goes where — specific rules
49
+
50
+ | DO put in `domain/` | DO NOT put in `domain/` |
51
+ |--------------------|------------------------|
52
+ | TypeScript interfaces and types | React components |
53
+ | Error classes (`class NotFoundError`) | Zustand stores |
54
+ | Value objects (`class Money`) | `useState`, `useEffect` |
55
+ | Enum definitions (`enum Status`) | Server Actions (`'use server'`) |
56
+ | Pure functions (no side effects) | API fetch calls |
57
+ | Constants and configuration | CSS, Tailwind classes |
58
+
59
+ | DO put in `application/` | DO NOT put in `application/` |
60
+ |-------------------------|------------------------------|
61
+ | Zustand stores | React components |
62
+ | Server Actions (`'use server'`) | API fetch implementation |
63
+ | React Query hooks | UI state like `isOpen`, `isVisible` |
64
+ | Command/Query DTOs | JSX markup |
65
+
66
+ | DO put in `infrastructure/` | DO NOT put in `infrastructure/` |
67
+ |----------------------------|--------------------------------|
68
+ | API client functions (`fetch`, `axios`) | React components |
69
+ | localStorage/cookie wrappers | Store logic |
70
+ | Auth provider SDK wrappers | Domain types (import them) |
71
+ | Analytics SDK wrappers | UI state |
72
+
73
+ | DO put in `interfaces/` | DO NOT put in `interfaces/` |
74
+ |------------------------|-----------------------------|
75
+ | Page components (`page.tsx`) | API fetch calls |
76
+ | Client components (`*.client.tsx`) | Domain types (import them) |
77
+ | Layout components | Business logic |
78
+ | UI state (`useState` for modals) | Server Actions (call them, don't define) |
58
79
 
59
80
  ---
60
81
 
61
- ## 2. Server/Client Boundary
62
-
63
- This is the most critical architectural decision in Next.js. Get it wrong and you lose the benefits of both paradigms.
82
+ ## 2. Domain Layer — Pure Business Logic
64
83
 
65
- ### What goes where
84
+ The domain layer has zero framework dependencies. It defines the types, errors, and value objects that the rest of the module uses.
66
85
 
67
- | Layer | Renders on | 'use client'? | Can use |
68
- |-------|-----------|---------------|---------|
69
- | `interfaces/page.tsx` | Server | No | `async`, `cookies()`, `headers()`, direct DB |
70
- | `interfaces/*.client.tsx` | Client | Yes | `useState`, `useEffect`, `onClick`, browser APIs |
71
- | `application/store.ts` | Client | Depends | Zustand, Jotai |
72
- | `application/actions.ts` | Server | No | `'use server'`, `revalidatePath()` |
73
- | `application/queries.ts` | Client | Yes | React Query, SWR |
74
- | `domain/`, `infrastructure/` | Both | No | Pure TS, fetch |
75
-
76
- ### Pattern: Island Architecture
86
+ ```typescript
87
+ // collaboration/domain/types.ts
88
+ export interface Thread {
89
+ id: string;
90
+ knowledgeNodeId: string;
91
+ title: string;
92
+ createdAt: string;
93
+ createdBy: User;
94
+ replyCount: number;
95
+ lastActivityAt: string;
96
+ }
77
97
 
98
+ export interface Comment {
99
+ id: string;
100
+ threadId: string;
101
+ body: string;
102
+ mentions: string[];
103
+ createdAt: string;
104
+ createdBy: User;
105
+ }
78
106
  ```
79
- ┌─────────────────────────────────────────┐
80
- │ page.tsx (Server Component) │
81
- │ <header>Static header</header> │
82
- │ <ProductList> │
83
- │ {products.map(p => <ProductCard/>)} │ ← Server-rendered
84
- │ </ProductList> │
85
- │ <CheckoutButton.client.tsx> │ ← Client island
86
- │ <AddToCartButton/> │
87
- │ <PriceSummary/> │
88
- │ </CheckoutButton.client.tsx> │
89
- └─────────────────────────────────────────┘
90
- ```
91
-
92
- ### Rules
93
- - ✅ Default to Server Components. Add `'use client'` only when you need interactivity.
94
- - ✅ Push client state DOWN to the leaf components that need it.
95
- - ✅ Use Server Actions for mutations, client handlers for optimistic updates.
96
- - ❌ Never put `'use client'` on a page wrapper — only on interactive islands.
97
- - ❌ Never import server-only code (actions, DB queries) in client components.
98
-
99
- ---
100
-
101
- ## 3. Data Fetching Patterns
102
-
103
- ### Server Component Fetch (Default)
104
107
 
105
108
  ```typescript
106
- // interfaces/products/page.tsx — Server Component, async
107
- import { ProductService } from '../../application/actions';
109
+ // collaboration/domain/errors.ts
110
+ export class CommentError extends Error {
111
+ constructor(
112
+ public readonly code: string,
113
+ message: string,
114
+ public readonly retriable: boolean = false,
115
+ ) {
116
+ super(message);
117
+ this.name = 'CommentError';
118
+ }
108
119
 
109
- export default async function ProductsPage() {
110
- const products = await ProductService.list(); // Direct call, no API route
120
+ static saveFailed(): CommentError {
121
+ return new CommentError('SAVE_FAILED', 'Failed to save comment', true);
122
+ }
111
123
 
112
- return (
113
- <div>
114
- {products.map(p => (
115
- <ProductCard key={p.id} product={p} /> // Server-rendered
116
- ))}
117
- </div>
118
- );
124
+ static notFound(id: string): CommentError {
125
+ return new CommentError('NOT_FOUND', `Comment ${id} not found`);
126
+ }
119
127
  }
120
128
  ```
121
129
 
122
- ### Client Data Fetching (React Query)
130
+ ---
131
+
132
+ ## 3. Application Layer — Stores and Actions
123
133
 
124
134
  ```typescript
125
- // application/queries.ts
126
- import { useQuery } from '@tanstack/react-query';
127
- import { apiClient } from '../infrastructure/api-client';
128
-
129
- export function useProducts() {
130
- return useQuery({
131
- queryKey: ['products'],
132
- queryFn: () => apiClient.get('/api/products'),
133
- staleTime: 30_000, // 30s before refetch
134
- gcTime: 5 * 60 * 1000, // 5min cache
135
- });
135
+ // collaboration/application/store.ts
136
+ import { create } from 'zustand';
137
+ import type { Thread, Comment } from '../domain/types';
138
+
139
+ interface CollaborationState {
140
+ threads: Thread[];
141
+ activeThreadId: string | null;
142
+ isLoading: boolean;
143
+ error: string | null;
144
+
145
+ setThreads: (threads: Thread[]) => void;
146
+ setActiveThread: (id: string) => void;
147
+ optimisticallyAddComment: (comment: Comment) => void;
148
+ rollbackComment: (commentId: string) => void;
136
149
  }
137
150
 
138
- // interfaces/product-list.client.tsx
139
- 'use client';
140
- export function ProductList() {
141
- const { data, isLoading } = useProducts();
142
- if (isLoading) return <Skeleton />;
143
- return data.map(p => <ProductCard key={p.id} product={p} />);
144
- }
151
+ export const useCollaborationStore = create<CollaborationState>((set) => ({
152
+ threads: [],
153
+ activeThreadId: null,
154
+ isLoading: false,
155
+ error: null,
156
+
157
+ setThreads: (threads) => set({ threads }),
158
+ setActiveThread: (id) => set({ activeThreadId: id }),
159
+ optimisticallyAddComment: (comment) => set((state) => ({
160
+ threads: state.threads.map(t =>
161
+ t.id === comment.threadId
162
+ ? { ...t, replyCount: t.replyCount + 1 }
163
+ : t
164
+ ),
165
+ })),
166
+ rollbackComment: (commentId) => set((state) => ({
167
+ threads: state.threads.map(t =>
168
+ t.id === commentId
169
+ ? { ...t, replyCount: t.replyCount - 1 }
170
+ : t
171
+ ),
172
+ })),
173
+ }));
145
174
  ```
146
175
 
147
- ### Server Actions (Mutations)
148
-
149
176
  ```typescript
150
- // application/actions.ts
177
+ // collaboration/application/actions.ts
151
178
  'use server';
152
179
  import { revalidatePath } from 'next/cache';
153
- import { Cart } from '../domain/entity';
154
-
155
- export async function addToCart(productId: string, quantity: number) {
156
- const cart = await loadCart();
157
- cart.addItem(productId, quantity);
158
- await saveCart(cart);
159
- revalidatePath('/cart'); // Revalidate without full page reload
160
- return { success: true };
180
+ import type { Comment } from '../domain/types';
181
+
182
+ export async function createComment(
183
+ nodeId: string,
184
+ body: string,
185
+ mentions: string[],
186
+ ): Promise<Comment> {
187
+ // Server Action calls backend API
188
+ const res = await fetch(`/api/nodes/${nodeId}/comments`, {
189
+ method: 'POST',
190
+ body: JSON.stringify({ body, mentions }),
191
+ });
192
+ if (!res.ok) throw new Error('Failed to create comment');
193
+ const comment = await res.json();
194
+ revalidatePath(`/nodes/${nodeId}`);
195
+ return comment;
161
196
  }
162
197
  ```
163
198
 
164
- ### Rules
165
- - ✅ Server Components for initial data — zero client JS for the first render
166
- - ✅ React Query for client-side data that needs realtime/stale-while-revalidate
167
- - ✅ Server Actions for mutations — no API route boilerplate
168
- - ❌ Never fetch in Server Components for data that needs realtime updates
169
- - ❌ Never use `useEffect` for data fetching — use React Query or Server Actions
170
-
171
199
  ---
172
200
 
173
- ## 4. State Management
174
-
175
- | State type | Tool | Location |
176
- |-----------|------|----------|
177
- | Server state (data) | React Query / SWR | `application/queries.ts` |
178
- | Client state (UI) | Zustand / Jotai | `application/store.ts` |
179
- | URL state (filters, page) | `useSearchParams` | `interfaces/*.client.tsx` |
180
- | Form state | React Hook Form + Zod | `interfaces/*.client.tsx` |
181
- | Server mutations | Server Actions | `application/actions.ts` |
201
+ ## 4. Infrastructure Layer — API Clients
182
202
 
183
203
  ```typescript
184
- // application/store.ts — Zustand store
185
- import { create } from 'zustand';
186
- import { devtools } from 'zustand/middleware';
187
- import { Cart } from '../domain/entity';
188
-
189
- interface CartState {
190
- cart: Cart | null;
191
- isOpen: boolean; // UI state — cart drawer toggle
192
- addItem: (productId: string, qty: number) => Promise<void>;
193
- toggleCart: () => void;
204
+ // collaboration/infrastructure/api.ts
205
+ import type { Thread, Comment } from '../domain/types';
206
+ import { CommentError } from '../domain/errors';
207
+
208
+ export async function fetchThreads(nodeId: string): Promise<Thread[]> {
209
+ const res = await fetch(`/api/nodes/${nodeId}/threads`);
210
+ if (!res.ok) throw new CommentError('FETCH_FAILED', 'Failed to load threads', true);
211
+ return res.json();
194
212
  }
195
213
 
196
- export const useCartStore = create<CartState>()(
197
- devtools(
198
- (set) => ({
199
- cart: null,
200
- isOpen: false,
201
- addItem: async (productId, qty) => {
202
- // Call Server Action, update local state optimistically
203
- },
204
- toggleCart: () => set(s => ({ isOpen: !s.isOpen })),
205
- }),
206
- { name: 'cart-store' },
207
- ),
208
- );
214
+ export async function saveComment(
215
+ nodeId: string,
216
+ threadId: string,
217
+ body: string,
218
+ ): Promise<Comment> {
219
+ const res = await fetch(`/api/nodes/${nodeId}/threads/${threadId}/comments`, {
220
+ method: 'POST',
221
+ body: JSON.stringify({ body }),
222
+ });
223
+ if (!res.ok) throw CommentError.saveFailed();
224
+ return res.json();
225
+ }
209
226
  ```
210
227
 
211
- ### Rules
212
- - ✅ Separate server state (React Query) from client state (Zustand)
213
- - ✅ Keep UI-only state (modals, toggles) local with `useState` when possible
214
- - ✅ Use Zustand slices for complex domains
215
- - ❌ Don't put server data in Zustand — that's what React Query is for
216
-
217
228
  ---
218
229
 
219
- ## 5. Design System & CSS Architecture
220
-
221
- ### Structure
230
+ ## 5. Interfaces Layer React Components
222
231
 
223
- ```
224
- shared/
225
- ui/ # Design system components
226
- tokens/ # Design tokens
227
- colors.ts
228
- typography.ts
229
- spacing.ts
230
- button/
231
- button.tsx # Component
232
- button.module.css # Styles
233
- button.test.tsx # Tests
234
- card/
235
- modal/
236
- form/
237
- input.tsx
238
- select.tsx
239
- ```
232
+ ```typescript
233
+ // collaboration/interfaces/thread-panel.tsx
234
+ 'use client';
235
+ import { useCollaborationStore } from '../application/store';
236
+ import { fetchThreads } from '../infrastructure/api';
237
+ import { CommentList } from './comment-list';
240
238
 
241
- ### Styling Strategy
239
+ export function ThreadPanel({ nodeId }: { nodeId: string }) {
240
+ const { threads, activeThreadId, setActiveThread, setThreads } = useCollaborationStore();
242
241
 
243
- | Approach | When to use |
244
- |----------|-------------|
245
- | CSS Modules (`*.module.css`) | Component-scoped styles in design system |
246
- | Tailwind utility classes | Layout, spacing, one-off adjustments |
247
- | CSS Variables (`var(--color-primary)`) | Design tokens, theming |
248
- | `clsx` / `tailwind-merge` | Conditional class composition |
242
+ useEffect(() => {
243
+ fetchThreads(nodeId).then(setThreads);
244
+ }, [nodeId]);
249
245
 
250
- ```typescript
251
- // shared/ui/button/button.tsx
252
- import { type VariantProps, cva } from 'class-variance-authority';
253
- import { cn } from '../../lib/utils';
254
-
255
- const buttonVariants = cva(
256
- 'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors',
257
- {
258
- variants: {
259
- variant: {
260
- primary: 'bg-primary text-primary-foreground hover:bg-primary/90',
261
- secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
262
- ghost: 'hover:bg-accent hover:text-accent-foreground',
263
- destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
264
- },
265
- size: {
266
- sm: 'h-9 px-3',
267
- md: 'h-10 px-4 py-2',
268
- lg: 'h-11 px-8',
269
- },
270
- },
271
- defaultVariants: { variant: 'primary', size: 'md' },
272
- },
273
- );
274
-
275
- interface ButtonProps
276
- extends React.ButtonHTMLAttributes<HTMLButtonElement>,
277
- VariantProps<typeof buttonVariants> {}
278
-
279
- export function Button({ className, variant, size, ...props }: ButtonProps) {
280
- return <button className={cn(buttonVariants({ variant, size }), className)} {...props} />;
246
+ return (
247
+ <div>
248
+ {threads.map(thread => (
249
+ <button key={thread.id} onClick={() => setActiveThread(thread.id)}>
250
+ {thread.title} ({thread.replyCount})
251
+ </button>
252
+ ))}
253
+ {activeThreadId && <CommentList threadId={activeThreadId} />}
254
+ </div>
255
+ );
281
256
  }
282
257
  ```
283
258
 
284
- ### Design System Rules
285
- - ✅ Design tokens in `shared/ui/tokens/` — single source of truth for colors, spacing, typography
286
- - ✅ Components use `cva` (class-variance-authority) for variant props
287
- - ✅ CSS Modules for component styles, Tailwind for layout
288
- - ✅ Dark mode via `class` strategy on `<html>` element
289
- - ❌ No inline styles (`style={{}}`) except for dynamic values (animations, transforms)
290
- - ❌ No CSS-in-JS libraries (styled-components, emotion) — they break Server Components
291
-
292
259
  ---
293
260
 
294
- ## 6. Error Handling
261
+ ## 6. Server/Client Boundary
295
262
 
296
- ```typescript
297
- // domain/errors.ts — Typed domain errors
298
- export class DomainError extends Error {
299
- constructor(
300
- public readonly code: string,
301
- message: string,
302
- public readonly status: number = 400,
303
- ) {
304
- super(message);
305
- this.name = 'DomainError';
306
- }
263
+ | Layer | 'use client'? | 'use server'? | Can use hooks? |
264
+ |-------|--------------|---------------|----------------|
265
+ | `domain/` | No | No | No |
266
+ | `application/` | No (except stores) | Yes (actions) | No |
267
+ | `infrastructure/` | No | No | No |
268
+ | `interfaces/` | Yes | No | Yes |
307
269
 
308
- static notFound(id: string) {
309
- return new DomainError('NOT_FOUND', `Resource ${id} not found`, 404);
310
- }
311
- }
270
+ ---
312
271
 
313
- // interfaces/error.tsxNext.js error boundary
314
- 'use client';
315
- export default function Error({ error, reset }: { error: Error; reset: () => void }) {
316
- return (
317
- <div role="alert">
318
- <h2>Something went wrong</h2>
319
- <button onClick={() => reset()}>Try again</button>
320
- </div>
321
- );
322
- }
272
+ ## 7. Shared Code When Something Spans Multiple Modules
323
273
 
324
- // interfaces/not-found.tsx Next.js 404 boundary
325
- export default function NotFound() {
326
- return <div>Page not found</div>;
327
- }
274
+ Not everything belongs inside a module. The `shared/` directory holds cross-cutting code that multiple modules need. But it has strict rules.
275
+
276
+ ### What goes in `shared/` vs `module/`
277
+
278
+ ```
279
+ src/
280
+ shared/ # Cross-cutting, NOT a bounded context
281
+ domain/ # Shared domain concepts (Anti-Corruption Layer)
282
+ user.ts # User type used by multiple contexts
283
+ team.ts # Team type used by multiple contexts
284
+ pagination.ts # Generic pagination types
285
+ hooks/ # Generic React hooks (zero business logic)
286
+ use-debounce.ts
287
+ use-media-query.ts
288
+ use-intersection-observer.ts
289
+ lib/ # Pure utilities
290
+ date-utils.ts # Date formatting
291
+ string-utils.ts # Slug generation
292
+ api-client.ts # Base fetch wrapper (not domain-specific)
293
+ ui/ # Design system (framework-agnostic)
294
+ button/
295
+ card/
296
+ modal/
297
+ collaboration/ # Bounded context
298
+ domain/
299
+ thread.ts # Business-specific concept
300
+ comment.ts
301
+ application/
302
+ store.ts
303
+ infrastructure/
304
+ api.ts
305
+ interfaces/
306
+ thread-panel.tsx
328
307
  ```
329
308
 
330
- ---
309
+ ### The rules
331
310
 
332
- ## 7. Testing Patterns
311
+ | Goes in `shared/` | Goes in `module/` |
312
+ |------------------|-------------------|
313
+ | `useDebounce` (generic hook) | `useCollaborationStore` (domain-specific) |
314
+ | `apiClient.fetch()` (generic HTTP) | `collaborationApi.fetchThreads()` (domain-specific) |
315
+ | `formatDate()` (utility) | `calculateThreadActivity()` (domain logic) |
316
+ | `Button`, `Card` (design system) | `ThreadPanel`, `CommentList` (domain components) |
317
+ | `User`, `Team` (shared kernel) | `Thread`, `Comment` (domain entities) |
333
318
 
334
- | Test type | Tool | What to test |
335
- |-----------|------|-------------|
336
- | Unit | Vitest | `domain/` entities, value objects |
337
- | Component | Vitest + Testing Library | `interfaces/` components |
338
- | Integration | Vitest + MSW | API client with mocked server |
339
- | E2E | Playwright | Full user flows |
319
+ ### The critical rule: `shared/` must NOT contain business logic
340
320
 
341
321
  ```typescript
342
- // domain/entity.test.tsPure unit test, no React
343
- import { Cart } from './entity';
344
-
345
- describe('Cart', () => {
346
- it('adds item', () => {
347
- const cart = new Cart();
348
- cart.addItem({ id: '1', name: 'Test', price: 100 }, 2);
349
- expect(cart.total).toBe(200);
350
- });
351
- });
322
+ // BAD business logic in shared/
323
+ shared/lib/thread-utils.ts
324
+ export function calculateThreadPriority(thread: Thread): number { }
325
+
326
+ // GOOD business logic in the module
327
+ collaboration/domain/thread.ts
328
+ export function calculateThreadPriority(thread: Thread): number { }
352
329
  ```
353
330
 
354
- ---
331
+ ```typescript
332
+ // ✅ GOOD — generic infrastructure in shared/
333
+ shared/lib/api-client.ts
334
+ export class ApiClient {
335
+ async get<T>(path: string): Promise<T> { }
336
+ }
337
+
338
+ // ✅ GOOD — domain-specific API in module infrastructure
339
+ collaboration/infrastructure/api.ts
340
+ import { ApiClient } from '@/shared/lib/api-client';
341
+ const api = new ApiClient();
342
+ export async function fetchThreads(nodeId: string): Promise<Thread[]> {
343
+ return api.get(`/nodes/${nodeId}/threads`);
344
+ }
345
+ ```
355
346
 
356
- ## 8. Anti-Patterns NEVER DO
347
+ ### When the same concept appears in multiple domains (Shared Kernel)
357
348
 
358
349
  ```typescript
359
- // Business logic in components
360
- export default function Page() {
361
- const [cart, setCart] = useState([]); // BAD — belongs in domain/
350
+ // shared/domain/user.ts User is a shared concept
351
+ // Shared kernel: multiple domains reference the same User
352
+ export interface User {
353
+ id: string;
354
+ name: string;
355
+ email: string;
362
356
  }
363
357
 
364
- // Data fetching in client components without React Query
365
- useEffect(() => { fetch('/api/data').then(setData); }, []); // BAD
358
+ // collaboration/domain/thread.ts imports shared User, doesn't redefine it
359
+ import { User } from '@/shared/domain/user';
360
+ export interface Thread {
361
+ id: string;
362
+ title: string;
363
+ createdBy: User; // References shared type, not duplicated
364
+ }
365
+ ```
366
366
 
367
- // 'use client' on the entire page
368
- 'use client'; // BAD — only add to interactive islands
367
+ ### File naming convention
369
368
 
370
- // Inline styles
371
- <div style={{ marginLeft: '16px' }}> // BAD — use Tailwind or CSS Modules
369
+ Files are named by their **concept**, not prefixed with the module name:
372
370
 
373
- // Mixing server/client concerns
374
- import { cookies } from 'next/headers'; // BAD in client component
371
+ | Wrong | Right |
372
+ |-------|-------|
373
+ | `collaboration-types.ts` | `types.ts` (or `thread.ts`, `comment.ts`) |
374
+ | `collaboration-errors.ts` | `errors.ts` |
375
+ | `collaboration-store.ts` | `store.ts` |
375
376
 
376
- // Global CSS for component styles
377
- h1 { color: blue; } // BAD — use CSS Modules or Tailwind
378
- ```
377
+ The directory path `collaboration/domain/thread.ts` already tells you which module it belongs to.
378
+
379
+ ---
380
+
381
+ ## 8. Testing by Layer
382
+
383
+ | Layer | Test type | Framework | What to test |
384
+ |-------|-----------|-----------|-------------|
385
+ | `domain/` | Unit | Vitest | Types, errors, pure functions |
386
+ | `application/` | Unit | Vitest | Store logic, action validation |
387
+ | `infrastructure/` | Integration | Vitest + MSW | API calls with mocked server |
388
+ | `interfaces/` | Component | Vitest + Testing Library | Render, user events, accessibility |
379
389
 
380
390
  ---
381
391
 
382
- *Version: 1.1.0*
392
+ *Version: 1.2.0*
383
393
  *Last updated: 2026-07-03*