@quanticjs/create-app 0.1.1

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 (136) hide show
  1. package/dist/deps.d.ts +4 -0
  2. package/dist/deps.js +97 -0
  3. package/dist/deps.js.map +1 -0
  4. package/dist/generators/backend.d.ts +2 -0
  5. package/dist/generators/backend.js +20 -0
  6. package/dist/generators/backend.js.map +1 -0
  7. package/dist/generators/bff.d.ts +2 -0
  8. package/dist/generators/bff.js +9 -0
  9. package/dist/generators/bff.js.map +1 -0
  10. package/dist/generators/claude.d.ts +2 -0
  11. package/dist/generators/claude.js +45 -0
  12. package/dist/generators/claude.js.map +1 -0
  13. package/dist/generators/docker.d.ts +2 -0
  14. package/dist/generators/docker.js +10 -0
  15. package/dist/generators/docker.js.map +1 -0
  16. package/dist/generators/e2e.d.ts +2 -0
  17. package/dist/generators/e2e.js +8 -0
  18. package/dist/generators/e2e.js.map +1 -0
  19. package/dist/generators/frontend.d.ts +2 -0
  20. package/dist/generators/frontend.js +28 -0
  21. package/dist/generators/frontend.js.map +1 -0
  22. package/dist/generators/module.d.ts +2 -0
  23. package/dist/generators/module.js +35 -0
  24. package/dist/generators/module.js.map +1 -0
  25. package/dist/generators/root.d.ts +2 -0
  26. package/dist/generators/root.js +7 -0
  27. package/dist/generators/root.js.map +1 -0
  28. package/dist/generators/scripts.d.ts +2 -0
  29. package/dist/generators/scripts.js +10 -0
  30. package/dist/generators/scripts.js.map +1 -0
  31. package/dist/index.d.ts +2 -0
  32. package/dist/index.js +40 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/prompts.d.ts +8 -0
  35. package/dist/prompts.js +53 -0
  36. package/dist/prompts.js.map +1 -0
  37. package/dist/scaffold.d.ts +2 -0
  38. package/dist/scaffold.js +79 -0
  39. package/dist/scaffold.js.map +1 -0
  40. package/dist/utils/exec.d.ts +2 -0
  41. package/dist/utils/exec.js +14 -0
  42. package/dist/utils/exec.js.map +1 -0
  43. package/dist/utils/template.d.ts +10 -0
  44. package/dist/utils/template.js +20 -0
  45. package/dist/utils/template.js.map +1 -0
  46. package/dist/utils/validate.d.ts +4 -0
  47. package/dist/utils/validate.js +27 -0
  48. package/dist/utils/validate.js.map +1 -0
  49. package/package.json +50 -0
  50. package/templates/backend/app.module.ts.ejs +61 -0
  51. package/templates/backend/bff.controller.ts.ejs +60 -0
  52. package/templates/backend/bff.module.ts.ejs +10 -0
  53. package/templates/backend/bff.service.ts.ejs +6 -0
  54. package/templates/backend/create-schema.migration.ts.ejs +15 -0
  55. package/templates/backend/data-source.ts.ejs +13 -0
  56. package/templates/backend/env.example.ejs +30 -0
  57. package/templates/backend/main.ts.ejs +43 -0
  58. package/templates/backend/module.ts.ejs +14 -0
  59. package/templates/backend/nest-cli.json.ejs +8 -0
  60. package/templates/backend/package.json.ejs +23 -0
  61. package/templates/backend/tsconfig.build.json.ejs +4 -0
  62. package/templates/backend/tsconfig.json.ejs +24 -0
  63. package/templates/claude/CLAUDE.md.ejs +86 -0
  64. package/templates/claude/hooks/auto-format.sh +22 -0
  65. package/templates/claude/hooks/check-secrets.sh +49 -0
  66. package/templates/claude/hooks/guard-destructive.sh +42 -0
  67. package/templates/claude/hooks/on-compaction.sh +29 -0
  68. package/templates/claude/mcp.json +10 -0
  69. package/templates/claude/rules/api-patterns.md +86 -0
  70. package/templates/claude/rules/auth-patterns.md +109 -0
  71. package/templates/claude/rules/backend-patterns.md +421 -0
  72. package/templates/claude/rules/database-patterns.md +96 -0
  73. package/templates/claude/rules/docker-patterns.md +86 -0
  74. package/templates/claude/rules/frontend-patterns.md +262 -0
  75. package/templates/claude/rules/observability-backend.md +132 -0
  76. package/templates/claude/rules/observability-frontend.md +49 -0
  77. package/templates/claude/rules/playwright-mcp.md +80 -0
  78. package/templates/claude/rules/resilience-ops.md +103 -0
  79. package/templates/claude/rules/testing-e2e-ui.md +190 -0
  80. package/templates/claude/rules/testing-patterns.md +94 -0
  81. package/templates/claude/rules/workflow-backend.md +64 -0
  82. package/templates/claude/rules/workflow-frontend.md +60 -0
  83. package/templates/claude/settings.json +68 -0
  84. package/templates/claude/skills/add-api-endpoint/SKILL.md +59 -0
  85. package/templates/claude/skills/add-auth-endpoint/SKILL.md +68 -0
  86. package/templates/claude/skills/add-entity/SKILL.md +56 -0
  87. package/templates/claude/skills/add-event/SKILL.md +127 -0
  88. package/templates/claude/skills/add-feature/SKILL.md +20 -0
  89. package/templates/claude/skills/add-frontend-page/SKILL.md +75 -0
  90. package/templates/claude/skills/add-handler/SKILL.md +105 -0
  91. package/templates/claude/skills/add-integration/SKILL.md +176 -0
  92. package/templates/claude/skills/add-migration/SKILL.md +20 -0
  93. package/templates/claude/skills/add-module/SKILL.md +89 -0
  94. package/templates/claude/skills/add-realtime/SKILL.md +119 -0
  95. package/templates/claude/skills/audit-rules/SKILL.md +120 -0
  96. package/templates/claude/skills/debugging/SKILL.md +105 -0
  97. package/templates/claude/skills/docker-dev/SKILL.md +86 -0
  98. package/templates/claude/skills/e2e-audit/SKILL.md +85 -0
  99. package/templates/claude/skills/e2e-full/SKILL.md +132 -0
  100. package/templates/claude/skills/e2e-scan/SKILL.md +171 -0
  101. package/templates/claude/skills/e2e-verify/SKILL.md +145 -0
  102. package/templates/claude/skills/fix-bug/SKILL.md +33 -0
  103. package/templates/claude/skills/implement-spec/SKILL.md +98 -0
  104. package/templates/claude/skills/review-code/SKILL.md +109 -0
  105. package/templates/claude/skills/review-spec/SKILL.md +216 -0
  106. package/templates/claude/skills/run-tests/SKILL.md +37 -0
  107. package/templates/claude/skills/specify/SKILL.md +87 -0
  108. package/templates/claude/skills/write-backend-tests/SKILL.md +182 -0
  109. package/templates/claude/skills/write-ui-tests/SKILL.md +118 -0
  110. package/templates/docker/Dockerfile.client.ejs +14 -0
  111. package/templates/docker/Dockerfile.ejs +28 -0
  112. package/templates/docker/docker-compose.test.yml.ejs +54 -0
  113. package/templates/docker/docker-compose.yml.ejs +76 -0
  114. package/templates/docker/nginx.conf.ejs +21 -0
  115. package/templates/frontend/App.tsx.ejs +64 -0
  116. package/templates/frontend/DashboardPage.tsx.ejs +37 -0
  117. package/templates/frontend/LoginPage.tsx.ejs +20 -0
  118. package/templates/frontend/NotFoundPage.tsx.ejs +15 -0
  119. package/templates/frontend/api-client.ts.ejs +15 -0
  120. package/templates/frontend/index.css.ejs +57 -0
  121. package/templates/frontend/index.html.ejs +13 -0
  122. package/templates/frontend/main.tsx.ejs +10 -0
  123. package/templates/frontend/package.json.ejs +16 -0
  124. package/templates/frontend/playwright.config.ts.ejs +20 -0
  125. package/templates/frontend/postcss.config.js.ejs +3 -0
  126. package/templates/frontend/smoke.spec.ts.ejs +37 -0
  127. package/templates/frontend/tailwind.config.ts.ejs +56 -0
  128. package/templates/frontend/tsconfig.json.ejs +25 -0
  129. package/templates/frontend/tsconfig.node.json.ejs +15 -0
  130. package/templates/frontend/utils.ts.ejs +6 -0
  131. package/templates/frontend/vite-env.d.ts.ejs +1 -0
  132. package/templates/frontend/vite.config.ts.ejs +20 -0
  133. package/templates/root/gitignore.ejs +9 -0
  134. package/templates/root/prettierrc.ejs +7 -0
  135. package/templates/scripts/init-db.sh.ejs +8 -0
  136. package/templates/scripts/save-auth-state.ts.ejs +24 -0
@@ -0,0 +1,96 @@
1
+ ---
2
+ globs: "src/**/*.ts"
3
+ ---
4
+
5
+ # Database Patterns
6
+
7
+ ## TypeORM Code-First Migrations
8
+
9
+ Generate migrations from entity changes — never write SQL by hand:
10
+
11
+ ```bash
12
+ npx typeorm migration:generate src/migrations/AddItemTable
13
+ npx typeorm migration:run
14
+ ```
15
+
16
+ ## Schema Per Service
17
+
18
+ Each service gets its own PostgreSQL schema (e.g., `identity`, `billing`).
19
+ Migrations reference the schema explicitly.
20
+
21
+ ## CRITICAL: TypeORM Uses camelCase Column Names
22
+
23
+ TypeORM's default naming strategy maps entity properties directly to column names.
24
+ **Column names in the database are camelCase, NOT snake_case.**
25
+
26
+ ```typescript
27
+ // Entity property: displayLatitude
28
+ // Database column: "displayLatitude" (NOT display_latitude)
29
+
30
+ // ❌ WRONG
31
+ CREATE INDEX idx_post_lat ON activity.posts ("display_latitude");
32
+
33
+ // ✅ CORRECT
34
+ CREATE INDEX idx_post_lat ON activity.posts ("displayLatitude");
35
+ ```
36
+
37
+ When referencing tables in raw SQL, use `schema.tableName` (NOT `"schema"."tableName"` with the schema quoted):
38
+ ```sql
39
+ -- ❌ WRONG
40
+ SELECT * FROM "activity"."post";
41
+
42
+ -- ✅ CORRECT
43
+ SELECT * FROM activity.posts;
44
+ ```
45
+
46
+ ## Entity Index Patterns — No Duplicates
47
+
48
+ Use EITHER class-level `@Index` OR property-level `@Index`, never both for the same column.
49
+
50
+ ## Migration SQL Rules
51
+
52
+ ### CREATE INDEX CONCURRENTLY — Non-Transactional Migrations Only
53
+
54
+ TypeORM migrations run inside transactions by default. `CONCURRENTLY` cannot run in a transaction. For normal migrations, use regular `CREATE INDEX`:
55
+
56
+ ```typescript
57
+ // ❌ WRONG — CONCURRENTLY inside a default (transactional) migration
58
+ await queryRunner.query(`CREATE INDEX CONCURRENTLY idx_name ON schema.table ("column")`);
59
+
60
+ // ✅ CORRECT — regular index in a transactional migration
61
+ await queryRunner.query(`CREATE INDEX idx_name ON schema.table ("column")`);
62
+ ```
63
+
64
+ For large tables (millions of rows) where locking must be avoided, create a **separate migration file** with `transaction = false`:
65
+
66
+ ```typescript
67
+ export class AddIndexOnOrderLocationConcurrently1234567890 implements MigrationInterface {
68
+ transaction = false as const;
69
+
70
+ async up(queryRunner: QueryRunner): Promise<void> {
71
+ await queryRunner.query(
72
+ `CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_order_location ON catalog.orders ("latitude", "longitude")`,
73
+ );
74
+ }
75
+
76
+ async down(queryRunner: QueryRunner): Promise<void> {
77
+ await queryRunner.query(`DROP INDEX IF EXISTS catalog.idx_order_location`);
78
+ }
79
+ }
80
+ ```
81
+
82
+ ### NEVER Write Hand-Crafted Migrations That Duplicate Entities
83
+
84
+ If a TypeORM entity defines a table, run `migration:generate`. Do NOT also write a manual `CREATE TABLE`.
85
+
86
+ ## Migration Naming Convention
87
+
88
+ Use descriptive names: `AddItemTable`, `AddStatusColumnToOrder`, `CreateIndexOnEmail`.
89
+
90
+ ## NEVER
91
+
92
+ - **NEVER** use `synchronize: true` in staging or production
93
+ - **NEVER** write snake_case column names
94
+ - **NEVER** use `CREATE INDEX CONCURRENTLY` inside transactional migrations
95
+ - **NEVER** write manual `CREATE TABLE` for tables with TypeORM entities
96
+ - **NEVER** access another module's tables directly
@@ -0,0 +1,86 @@
1
+ ---
2
+ globs: "Dockerfile, client/Dockerfile, docker-compose*.yml, scripts/**"
3
+ ---
4
+
5
+ # Docker Patterns
6
+
7
+ ## Local Dev Uses Docker Compose (Internal Network)
8
+
9
+ ```bash
10
+ # Daily development
11
+ docker compose up # Start infra + backend (watch mode)
12
+ cd client && npm run dev # Start Vite natively (separate terminal)
13
+ ```
14
+
15
+ ## Architecture: Modular Monolith — Two Dockerfiles
16
+
17
+ | Service | Dockerfile | What it builds |
18
+ |---------|-----------|----------------|
19
+ | Backend (NestJS) | `Dockerfile` (project root) | All backend modules in one image |
20
+ | Frontend (React) | `client/Dockerfile` | Vite build → nginx (K8s only) |
21
+
22
+ ## docker-compose.yml Design
23
+
24
+ - **Only `backend` and `keycloak` have `ports:` sections** (3000, 8080)
25
+ - **Backend uses Docker hostnames** (`postgres`, `redis`, `keycloak`) — NOT `localhost`
26
+ - **Source code is volume-mounted** (`./src:/app/src:cached`)
27
+ - **Keycloak uses dev-mem mode** for fast startup
28
+ - **Database init** handled by `scripts/init-db.sh`
29
+
30
+ ## Backend Dockerfile (multi-stage)
31
+
32
+ ```dockerfile
33
+ # Development target — used by docker-compose.yml
34
+ FROM node:20-alpine AS development
35
+ WORKDIR /app
36
+ COPY package*.json ./
37
+ RUN npm ci
38
+ COPY tsconfig*.json nest-cli.json ./
39
+ # src/ volume-mounted at runtime
40
+ CMD ["npm", "run", "start:dev"]
41
+
42
+ # Production target
43
+ FROM node:20-alpine AS production
44
+ WORKDIR /app
45
+ ENV NODE_ENV=production
46
+ COPY package*.json ./
47
+ RUN npm ci --only=production && npm cache clean --force
48
+ COPY --from=builder /app/dist ./dist
49
+ CMD ["node", "dist/main.js"]
50
+ ```
51
+
52
+ ## Vite Proxy Configuration (critical for BFF auth)
53
+
54
+ ```typescript
55
+ // client/vite.config.ts
56
+ export default defineConfig({
57
+ server: {
58
+ proxy: {
59
+ '/api': { target: 'http://localhost:3000', changeOrigin: true },
60
+ '/auth': { target: 'http://localhost:3000', changeOrigin: true },
61
+ },
62
+ },
63
+ });
64
+ ```
65
+
66
+ ## Kubernetes (Integration Testing Only)
67
+
68
+ ```bash
69
+ scripts/local-dev-up.sh # Create Kind cluster + deploy
70
+ scripts/helm-deploy-local.sh # Re-deploy after Helm changes
71
+ ```
72
+
73
+ Use Helm release-prefixed service names, not Docker Compose short names.
74
+
75
+ ## NEVER
76
+
77
+ - **NEVER** expose infrastructure ports to the host (except Keycloak 8080 for OIDC redirects)
78
+ - **NEVER** run Vite inside Docker — HMR is unreliable with volume mounts
79
+ - **NEVER** create per-service Dockerfiles — this is a monolith
80
+ - **NEVER** run services as root in production images
81
+ - **NEVER** copy `node_modules/` into the image — always `npm ci`
82
+ - **NEVER** use `docker compose up` for E2E or integration tests — use `docker compose -f docker-compose.test.yml up` (isolated ports: API 3099, Keycloak 8099, Frontend 5199)
83
+ - **NEVER** hardcode API URLs in frontend — use relative paths (`/api/items`)
84
+ - **NEVER** use Docker Compose short hostnames in Helm values
85
+ - **NEVER** mount host `node_modules` into Kubernetes pods
86
+ - **NEVER** use local K8s for daily feature development — use Docker Compose
@@ -0,0 +1,262 @@
1
+ ---
2
+ globs: "client/**/*.{ts,tsx}"
3
+ ---
4
+
5
+ # Frontend Patterns
6
+
7
+ ## State Management — Decision Tree
8
+
9
+ | State type | Tool | When |
10
+ |---|---|---|
11
+ | **Server/remote data** | TanStack Query (`useQuery` / `useMutation`) | Data from API |
12
+ | **URL-derived state** | `useSearchParams` (React Router) | Filters, tabs, pagination — anything bookmarkable |
13
+ | **Local UI state** | `useState` | Open/closed, hover, animation — never leaves the component |
14
+ | **Shared client state** | Zustand store | Client-only state needed by 2+ unrelated components |
15
+ | **Form state** | `useForm` (`@quanticjs/react-forms`) + Zod | Multi-field forms with validation + automatic server error mapping |
16
+
17
+ ### TanStack Query — All API Calls
18
+
19
+ ```typescript
20
+ const { data, isLoading } = useQuery({
21
+ queryKey: ['items', id],
22
+ queryFn: () => api.getItem(id),
23
+ });
24
+ ```
25
+
26
+ Same `queryKey` in multiple components = one network request (auto-dedup).
27
+
28
+ ### Zustand — Selectors Only
29
+
30
+ ```typescript
31
+ // ✅ CORRECT — only re-renders when radius changes
32
+ const radius = useFilterStore((s) => s.radius);
33
+
34
+ // ❌ WRONG — re-renders on ANY store change
35
+ const { radius, category } = useFilterStore();
36
+ ```
37
+
38
+ ### URL State
39
+
40
+ ```typescript
41
+ const [searchParams, setSearchParams] = useSearchParams();
42
+ const tab = searchParams.get('tab') ?? 'discover';
43
+ ```
44
+
45
+ ## API Error Handling
46
+
47
+ The backend returns RFC 9457 problem-details JSON on all errors. The API client parses these into `ApiError` instances. Three tiers handle them: forms → toast → root ErrorBoundary (see provider stack below).
48
+
49
+ ### Error classification
50
+
51
+ | ApiError property | HTTP status | UI behavior |
52
+ |---|---|---|
53
+ | `isValidation` | 400, 422 | Field errors on form, or detailed toast |
54
+ | `isNotFound` | 404 | Navigate to "not found" page or show empty state |
55
+ | `isForbidden` | 403 | Show "access denied" — do NOT retry |
56
+ | `isUnauthorized` | 401 | Automatic — refresh token → retry → redirect to login |
57
+ | `isConflict` | 409 | Show "already exists" or "was modified" — suggest refresh |
58
+ | 5xx | 500, 502, 503 | Generic "Something went wrong" toast — **never** show `error.detail` (may contain stack traces) |
59
+
60
+ Always include `error.correlationId` in error UI so users can report it to support.
61
+
62
+ ### Forms — automatic server-to-field mapping
63
+
64
+ ```typescript
65
+ const { register, handleSubmit, errors } = useForm({
66
+ schema: z.object({ name: z.string().min(1), email: z.string().email() }),
67
+ onSubmit: async (data) => await api.post('/items', data),
68
+ });
69
+ // Server { errors: { email: "already taken" } } → errors.email auto-set
70
+ // Non-field errors → errors._root
71
+ ```
72
+
73
+ ### Non-form mutations — MANDATORY `onError`
74
+
75
+ ```typescript
76
+ const mutation = useMutation({
77
+ mutationFn: (id: string) => api.delete(`/items/${id}`),
78
+ onError: (error) => {
79
+ if (error instanceof ApiError) {
80
+ toast.error(error); // ApiError-aware — extracts title + detail
81
+ }
82
+ },
83
+ });
84
+ ```
85
+
86
+ ## App Root Provider Stack (MANDATORY)
87
+
88
+ The app root must wrap providers in this exact order. Outer providers are available to inner ones.
89
+
90
+ ```tsx
91
+ <Sentry.ErrorBoundary fallback={<ErrorFallback />}>
92
+ <QuanticProvider client={apiClient}>
93
+ <QuanticQueryProvider>
94
+ <ToastProvider>
95
+ <ErrorBoundary
96
+ fallback={(error, reset) => <AppErrorPage error={error} onRetry={reset} />}
97
+ onError={(error) => Sentry.captureException(error)}
98
+ >
99
+ <RouterProvider router={router} />
100
+ </ErrorBoundary>
101
+ </ToastProvider>
102
+ </QuanticQueryProvider>
103
+ </QuanticProvider>
104
+ </Sentry.ErrorBoundary>
105
+ ```
106
+
107
+ **Why this order:**
108
+ - `Sentry.ErrorBoundary` outermost — catches errors even if inner providers fail to mount
109
+ - `QuanticProvider` — API client context, needed by everything below
110
+ - `QuanticQueryProvider` — TanStack Query with smart defaults (no 4xx retry, 30s stale time)
111
+ - `ToastProvider` — toast context must wrap ErrorBoundary so error fallbacks can show toasts
112
+ - `ErrorBoundary` — catches render errors, shows recoverable UI, reports to Sentry
113
+ - `RouterProvider` innermost — pages and components
114
+
115
+ ## shadcn/ui Components
116
+
117
+ Use shadcn/ui primitives from `@/components/ui`. Compose into feature components — never modify primitives directly. All components must accept `className` for extension and forward refs.
118
+
119
+ ## Component Library Requirements
120
+
121
+ - Every shared component must have a Storybook entry showing variants, states (loading, disabled, error), and usage
122
+ - Accessibility enforced in CI via axe-core at WCAG 2.1 Level AA — violations block PR merges
123
+
124
+ ## Dark Mode
125
+
126
+ Use CSS variables: `hsl(var(--background))`, `hsl(var(--primary))`, etc.
127
+
128
+ ## TypeScript Strict Mode
129
+
130
+ All code uses `strict: true`, `noUncheckedIndexedAccess: true`. No `any`, no `@ts-ignore`.
131
+
132
+ ```json
133
+ {
134
+ "compilerOptions": {
135
+ "strict": true,
136
+ "noUncheckedIndexedAccess": true,
137
+ "noImplicitOverride": true,
138
+ "noFallthroughCasesInSwitch": true,
139
+ "forceConsistentCasingInFileNames": true
140
+ }
141
+ }
142
+ ```
143
+
144
+ ## Browser Type Safety
145
+
146
+ ```typescript
147
+ // ❌ WRONG — requires @types/node
148
+ const timer: NodeJS.Timeout = setTimeout(() => {}, 1000);
149
+
150
+ // ✅ CORRECT
151
+ const timer: ReturnType<typeof setTimeout> = setTimeout(() => {}, 1000);
152
+ ```
153
+
154
+ ## Page Components
155
+
156
+ All page components are lazy-loaded with `React.lazy()` and wrapped in `<Suspense>`.
157
+
158
+ ## Design Tokens
159
+
160
+ No hardcoded hex values in application code. All visual values come from design tokens/CSS variables.
161
+
162
+ ## QuanticJS Framework Usage
163
+
164
+ All frontend code uses framework packages from `@quanticjs/*`. These are the canonical tools — do not introduce alternatives.
165
+
166
+ ### Packages
167
+
168
+ | Package | Purpose | Used by |
169
+ |---|---|---|
170
+ | `@quanticjs/react-core` | API client, `ApiError`, `useClient`, `usePermissions`, `QuanticProvider`, `QuanticQueryProvider` | App roots, data hooks |
171
+ | `@quanticjs/react-forms` | `useForm` with Zod + automatic server error mapping | All forms |
172
+ | `@quanticjs/workflow-ui` | `WorkflowProvider`, task hooks (`useTaskList`, `useTask`, `useTaskClaim`, `useTaskAction`, `useProcessTimeline`), `TaskInbox`, `WorkflowForm`, `TaskDetail`, `TaskActions`, `ProcessTimeline` | Workflow apps |
173
+
174
+ ### API Client and Errors
175
+
176
+ Use `useClient()` from `@quanticjs/react-core` for API calls. The client parses RFC 9457 responses into `ApiError` instances.
177
+
178
+ ```typescript
179
+ import { useClient, ApiError, isApiError } from '@quanticjs/react-core';
180
+ ```
181
+
182
+ ### Mutation Error Handling
183
+
184
+ Every `useMutation` must have an `onError` callback that uses `ApiError`-aware toasting:
185
+
186
+ ```typescript
187
+ onError: (error) => {
188
+ if (error instanceof ApiError) {
189
+ toast({
190
+ title: error.isServerError ? 'Something went wrong' : error.title,
191
+ description: error.isServerError
192
+ ? `Please try again. (ref: ${error.correlationId ?? 'unknown'})`
193
+ : `${error.detail ?? ''} (ref: ${error.correlationId ?? 'unknown'})`,
194
+ variant: 'destructive',
195
+ });
196
+ }
197
+ },
198
+ ```
199
+
200
+ ### Toast Provider
201
+
202
+ Use the app's `useToast()` hook (from `@/components/toast-provider` or `WorkflowProvider`). Toasts always include `correlationId` for error cases.
203
+
204
+ ### Library Components (`@quanticjs/workflow-ui`)
205
+
206
+ All exported components MUST:
207
+ - Accept `className` prop for style extension
208
+ - Forward refs where applicable
209
+ - Use CSS variables / Tailwind semantic classes — no hardcoded hex or spacing values
210
+ - No `console.log` / `console.warn` — use Sentry or structured error reporting
211
+
212
+ ### Third-Party Library Integration (bpmn-js)
213
+
214
+ bpmn-js lacks TypeScript types. All `any` casts must be isolated behind a typed adapter module (`bpmn-types.ts`) that provides typed wrappers for `modeler.get()`:
215
+
216
+ ```typescript
217
+ // bpmn-types.ts — centralized typed access
218
+ import type Modeler from 'bpmn-js/lib/Modeler';
219
+ import type Viewer from 'bpmn-js/lib/Viewer';
220
+
221
+ interface BpmnCanvas { zoom(level: number | 'fit-viewport'): number; addMarker(id: string, cls: string): void; removeMarker(id: string, cls: string): void; scrollToElement(el: BpmnElement): void; }
222
+ interface BpmnCommandStack { canUndo(): boolean; canRedo(): boolean; undo(): void; redo(): void; }
223
+ interface BpmnElementRegistry { get(id: string): BpmnElement | undefined; }
224
+ interface BpmnModeling { updateProperties(element: BpmnElement, props: Record<string, unknown>): void; }
225
+ interface BpmnSelection { select(element: BpmnElement): void; }
226
+ interface BpmnOverlays { add(id: string, type: string, overlay: unknown): void; remove(filter: Record<string, unknown>): void; }
227
+ interface BpmnModdle { create(type: string, props?: Record<string, unknown>): unknown; }
228
+ export interface BpmnElement { id: string; type: string; businessObject?: Record<string, unknown>; width?: number; height?: number; source?: BpmnElement; [key: string]: unknown; }
229
+
230
+ type ServiceMap = { canvas: BpmnCanvas; commandStack: BpmnCommandStack; elementRegistry: BpmnElementRegistry; modeling: BpmnModeling; selection: BpmnSelection; overlays: BpmnOverlays; moddle: BpmnModdle; };
231
+
232
+ export function getService<K extends keyof ServiceMap>(modeler: Modeler | Viewer, name: K): ServiceMap[K] {
233
+ return (modeler as Record<string, unknown>).get(name) as ServiceMap[K];
234
+ }
235
+ ```
236
+
237
+ ## NEVER
238
+
239
+ - **NEVER** fetch data with `useEffect` + `useState` — use TanStack Query
240
+ - **NEVER** copy query data into `useState` — it creates a stale snapshot
241
+ - **NEVER** mirror URL params into `useState` — read from `useSearchParams` directly
242
+ - **NEVER** put server data in Zustand — use TanStack Query
243
+ - **NEVER** destructure entire Zustand store without selectors
244
+ - **NEVER** add Redux, MobX, Jotai, Recoil, or Valtio
245
+ - **NEVER** use `console.log` in production code — use Sentry
246
+ - **NEVER** use `any` — use `unknown` and narrow
247
+ - **NEVER** use `@ts-ignore` — use `@ts-expect-error` with comment
248
+ - **NEVER** use `NodeJS.Timeout` or other Node.js types in frontend code
249
+ - **NEVER** hardcode hex colors or spacing values — use design tokens
250
+ - **NEVER** prop-drill through components that don't use the prop
251
+ - **NEVER** parse API error responses manually — use `ApiError` properties (`detail`, `fieldErrors`, `correlationId`)
252
+ - **NEVER** show raw error messages to users in production — map `ApiError.status` to user-friendly messages
253
+ - **NEVER** show `error.detail` from 5xx responses — may contain stack traces; use generic message instead
254
+ - **NEVER** write `catch (e) { console.log(e) }` on mutations — swallowing errors silently is a bug
255
+ - **NEVER** omit `onError` on non-form mutations — every mutation failure must be visible to the user
256
+ - **PREFER** `<ErrorBoundary>` on page components — allows recovering a single page without resetting the entire app (the root boundary is the fallback)
257
+ - **NEVER** use `react-hook-form` directly — use `useForm` from `@quanticjs/react-forms` which adds automatic server error mapping
258
+ - **NEVER** manually map server validation errors to form fields — `useForm` does this automatically via `ApiError.fieldErrors`
259
+ - **NEVER** scatter `(modeler as any).get(...)` across components — isolate all bpmn-js `any` casts in a single `bpmn-types.ts` adapter module
260
+ - **NEVER** use `console.warn` or `console.error` in library packages — use Sentry or structured error reporting
261
+ - **NEVER** export components from `@quanticjs/*` packages without `className` prop support
262
+ - **NEVER** use inline `style={{ gap: '1rem' }}` — use Tailwind gap utilities (`gap-4`) or CSS variables
@@ -0,0 +1,132 @@
1
+ ---
2
+ globs: "src/**/*.ts"
3
+ ---
4
+
5
+ # Backend Observability
6
+
7
+ ## Three Pillars
8
+
9
+ | Pillar | Tool | Purpose |
10
+ |--------|------|---------|
11
+ | **Logging** | ELK via `nestjs-pino` | Structured JSON logs with correlation IDs |
12
+ | **Metrics** | Prometheus + Grafana | Latency, error rates, Redis stream lag |
13
+ | **Tracing** | OpenTelemetry → Elasticsearch APM | Distributed traces across HTTP, CQRS, Redis |
14
+
15
+ ## Structured Logging
16
+
17
+ All logging uses Pino (`nestjs-pino`). Every request gets a `requestId` propagated through the CQRS pipeline. All logs must be structured JSON (key-value pairs), not string interpolation.
18
+
19
+ ```typescript
20
+ // ✅ CORRECT
21
+ this.logger.info({ userId }, 'User created');
22
+
23
+ // ❌ WRONG
24
+ this.logger.info('User created: ' + userId);
25
+ ```
26
+
27
+ **Log levels:**
28
+
29
+ | Level | When |
30
+ |-------|------|
31
+ | `error` | Unrecoverable failures — DB down, DLQ events, uncaught exceptions |
32
+ | `warn` | Degraded operation — retry triggered, cache miss on hot path, slow query (>1s) |
33
+ | `info` | Normal operation — request start/end, command executed, event published |
34
+ | `debug` | Dev-only — query params, cache key computed, lock acquired |
35
+
36
+ ## CQRS Pipeline Logging
37
+
38
+ Automatic via `LogBehavior`: one structured entry per command/query with name, duration, result, correlationId, userId.
39
+
40
+ ## Sensitive Field Handling (LogBehavior)
41
+
42
+ The `LogBehavior` pipeline step handles sensitive data automatically at three levels:
43
+
44
+ **1. Built-in PII masking (automatic — no configuration needed):**
45
+
46
+ These fields are auto-detected and masked in every command/query payload:
47
+
48
+ | Field name | Masking |
49
+ |------------|---------|
50
+ | `email` | `j***@example.com` (first char + domain) |
51
+ | `password` | `[REDACTED]` |
52
+ | `token` | `[REDACTED]` |
53
+ | `accessToken` | `[REDACTED]` |
54
+ | `githubAccessToken` | `[REDACTED]` |
55
+ | `secretKey` | `[REDACTED]` |
56
+
57
+ **2. Per-command field exclusion (`logExclude`):**
58
+
59
+ Commands can exclude additional fields via a static property. Excluded fields show `[excluded]` in logs:
60
+
61
+ ```typescript
62
+ export class CreateIntegrationCommand {
63
+ static logExclude = ['webhookSecret', 'apiKey'];
64
+
65
+ constructor(
66
+ readonly name: string,
67
+ readonly webhookSecret: string,
68
+ readonly apiKey: string,
69
+ ) {}
70
+ }
71
+ // Logs: { name: "Stripe", webhookSecret: "[excluded]", apiKey: "[excluded]" }
72
+ ```
73
+
74
+ **3. Suppressing entire payload (`@Log({ logPayload: false })`):**
75
+
76
+ For commands where the entire payload is sensitive, disable payload logging — only metadata (name, duration, result status) is logged:
77
+
78
+ ```typescript
79
+ @Log({ logPayload: false })
80
+ @Validate(BulkImportValidator)
81
+ export class BulkImportCommand {
82
+ constructor(readonly records: SensitiveRecord[]) {}
83
+ }
84
+ ```
85
+
86
+ ## Payload Sanitization (automatic)
87
+
88
+ LogBehavior sanitizes all payloads before logging:
89
+
90
+ | Rule | Behavior |
91
+ |------|----------|
92
+ | Strings > 200 chars | Truncated: `"value..."` + `(N chars)` |
93
+ | Arrays > 5 items | First 5 items + `"... +N more"` |
94
+ | Object depth > 2 | Nested objects show `[nested]` |
95
+
96
+ ## Pino HTTP Serializers
97
+
98
+ HTTP request/response logs are stripped to safe fields only:
99
+ - **Request:** `id`, `method`, `url`, `correlationId`
100
+ - **Response:** `statusCode`
101
+
102
+ No headers, bodies, or query parameters are logged at the HTTP layer.
103
+
104
+ ## Handler Skip — Not Supported
105
+
106
+ There is no mechanism to skip logging for an entire handler. All commands/queries pass through `LogBehavior`. Use `@Log({ logPayload: false })` to suppress payload if needed.
107
+
108
+ ## Key Metrics
109
+
110
+ - `http_request_duration_seconds`, `http_requests_total`
111
+ - `cqrs_command_duration_seconds`, `cqrs_command_errors_total`
112
+ - `redis_stream_lag`, `redis_stream_dlq_length`
113
+ - `typeorm_query_duration_seconds`
114
+
115
+ ## Alerting Thresholds
116
+
117
+ | Alert | Condition | Severity |
118
+ |-------|-----------|----------|
119
+ | High API latency | p95 > 2s for 5 min | Warning |
120
+ | API error rate | 5xx > 5% for 5 min | Critical |
121
+ | Redis stream lag | Pending > 1000 for 10 min | Warning |
122
+ | DLQ growing | DLQ > 100 in 1 hour | Critical |
123
+ | Connection pool exhausted | Active > 90% pool | Critical |
124
+ | Pod OOM kill | Container OOMKilled restart | Critical |
125
+
126
+ ## NEVER
127
+
128
+ - **NEVER** use `console.log` in application code — use Pino via the injected logger
129
+ - **NEVER** use unstructured log messages — all logs must be structured JSON key-value pairs
130
+ - **NEVER** log sensitive data (passwords, tokens, PII) — use `logExclude` or `@Log({ logPayload: false })`
131
+ - **NEVER** rely solely on built-in PII masking for domain-specific secrets — add them to `logExclude`
132
+ - **NEVER** leave critical paths without alerting
@@ -0,0 +1,49 @@
1
+ ---
2
+ globs: "client/src/**/*.{ts,tsx}"
3
+ ---
4
+
5
+ # Frontend Observability
6
+
7
+ ## Sentry — Error Tracking + Performance
8
+
9
+ ```typescript
10
+ Sentry.init({
11
+ dsn: import.meta.env.VITE_SENTRY_DSN,
12
+ integrations: [Sentry.browserTracingIntegration(), Sentry.replayIntegration({ maskAllText: true })],
13
+ tracesSampleRate: import.meta.env.VITE_ENV === 'production' ? 0.1 : 1.0,
14
+ replaysOnErrorSampleRate: 1.0,
15
+ });
16
+ ```
17
+
18
+ ## Environment Configuration
19
+
20
+ | Environment | Error tracking | Performance sampling | Session replay |
21
+ |---|---|---|---|
22
+ | Local dev | Disabled | Disabled | Disabled |
23
+ | Dev / Staging | All errors | 100% | On error only |
24
+ | Production | All errors | 10% | On error only |
25
+
26
+ ## Alert Triage
27
+
28
+ Unresolved Sentry issues must be triaged within 48 hours — assign an owner or mark as expected behavior.
29
+
30
+ ## Web Vitals
31
+
32
+ LCP < 2.5s, INP < 200ms, CLS < 0.1. JS bundle < 200KB gzipped.
33
+
34
+ ## Backend Correlation
35
+
36
+ Every API request includes `x-request-id` header for end-to-end tracing.
37
+
38
+ ```typescript
39
+ api.interceptors.request.use((config) => {
40
+ config.headers['x-request-id'] = crypto.randomUUID();
41
+ return config;
42
+ });
43
+ ```
44
+
45
+ ## NEVER
46
+
47
+ - **NEVER** use `console.log` in application code — use Sentry
48
+ - **NEVER** include monitoring tools (pino-pretty, debug transports) in production frontend bundles
49
+ - **NEVER** log PII to Sentry