@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,80 @@
1
+ # Playwright MCP — Browser Control for Claude Code
2
+
3
+ You have a Playwright MCP server configured (`.claude/mcp.json`). This gives you **real-time browser control** — navigate pages, click elements, fill forms, and take screenshots without writing test files.
4
+
5
+ **State "using Playwright MCP" in your first message of an MCP session** — otherwise Claude may default to Bash-driven Playwright commands instead of the MCP tools.
6
+
7
+ ## Setup & Version Pinning
8
+
9
+ - **Package:** Microsoft's `@playwright/mcp` — **NOT** the community `@executeautomation/playwright-mcp-server`. Do not confuse them.
10
+ - **Pin the version** in `.claude/mcp.json`. Never use `@latest` in shared configs — beta releases silently break tool schemas mid-session.
11
+ - **Node 18+ required.** Older versions throw `performance is not defined`. Verify with `node --version` before any MCP run.
12
+ - **Install browser binaries** after any version bump: `npx playwright install` (and `npx playwright install-deps` on Linux/CI).
13
+ - **Smoke test** after setup: `browser_navigate` to `http://localhost:5173/` and confirm the a11y tree returns.
14
+
15
+ ## CI / Headless
16
+
17
+ - Pass `--headless` to the MCP server args when running in CI or Docker — default is headed.
18
+ - For repeatable CI runs, prefer coded specs (`client/e2e/`) over MCP. Use MCP for exploration and debugging, not pipeline execution.
19
+
20
+ ## Two Uses
21
+
22
+ ### 1. Visual Self-QA (during implementation)
23
+ After implementing UI changes, open the page and verify it renders correctly:
24
+ - `browser_navigate` to the page → `browser_snapshot` (content) or `browser_screenshot` (visual layout) → check it looks right
25
+ - Use during any issue that touches `.tsx` files
26
+
27
+ ### 2. E2E Journey Verification (via `/e2e-verify`)
28
+ Walk through full user journeys by browsing the app interactively. This replaces hardcoded Playwright spec files for UI verification. See `.claude/skills/e2e-verify/SKILL.md` for the journey definitions.
29
+
30
+ ## MCP Tools
31
+
32
+ - `browser_navigate` — open a URL
33
+ - `browser_click` — click an element
34
+ - `browser_fill` — type into an input
35
+ - `browser_snapshot` — return the accessibility tree (~120 tokens, stable selectors)
36
+ - `browser_screenshot` — capture a visual screenshot (~1,500 tokens)
37
+ - `browser_select_option`, `browser_hover`, `browser_drag`, etc.
38
+
39
+ ## Snapshot vs Screenshot
40
+
41
+ Prefer `browser_snapshot` for element discovery, interaction, and content verification — it returns the accessibility tree, costs ~12x fewer tokens than a screenshot, and provides more stable element references.
42
+
43
+ Use `browser_take_screenshot` only when verifying **visual appearance** (colors, layout, spacing, images).
44
+
45
+ ## Authenticated Pages
46
+
47
+ Most pages require login. The app uses BFF httpOnly cookies — not sessionStorage tokens.
48
+
49
+ Run `cd client && npx tsx ../scripts/save-auth-state.ts` first (requires Docker stack up). This completes the BFF OIDC login flow through Keycloak and saves the resulting httpOnly session cookie to `client/e2e/auth/storage-state.json`.
50
+
51
+ If pages redirect to the login page, the storage state is expired — re-run the save script.
52
+
53
+ ## Ports
54
+
55
+ ### Dev Stack (visual self-QA during development)
56
+
57
+ | Service | URL | When |
58
+ |---------|-----|------|
59
+ | Vite dev server | `http://localhost:5173` | `cd client && npm run dev` |
60
+ | Backend API | `http://localhost:3000` | `docker compose up` |
61
+ | Keycloak | `http://localhost:8080` | `docker compose up` |
62
+
63
+ ### Test Stack (E2E skills: `/e2e-full`, `/e2e-verify`, `/e2e-audit`)
64
+
65
+ | Service | URL | When |
66
+ |---------|-----|------|
67
+ | Vite (test) | `http://localhost:5199` | `cd client && VITE_API_URL=http://localhost:3099 npm run dev -- --port 5199` |
68
+ | Backend API (test) | `http://localhost:3099` | `docker compose -f docker-compose.test.yml up` |
69
+ | Keycloak (test) | `http://localhost:8099` | `docker compose -f docker-compose.test.yml up` |
70
+
71
+ **E2E tests MUST use the test stack** — never the dev stack.
72
+
73
+ ## Do NOT
74
+
75
+ - Do not commit `client/e2e/auth/storage-state.json` — it contains httpOnly session cookies
76
+ - Do not use MCP for API contract assertions (status codes, JSON payloads) — use coded tests for those
77
+ - Do not use MCP as a replacement for the mocked UI E2E suite (`client/e2e/`) — MCP is for exploratory/visual verification only
78
+ - Do not point MCP at environments with real user data. Every page's DOM, console output, and form values get transmitted to Anthropic's API — keep MCP restricted to the local dev stack
79
+ - Do not use `@playwright/mcp@latest` in `.claude/mcp.json` — pin a specific version
80
+ - Do not install `@executeautomation/playwright-mcp-server` thinking it's the same package — it isn't
@@ -0,0 +1,103 @@
1
+ ---
2
+ globs: "src/**/*.ts"
3
+ ---
4
+
5
+ # Resilience & Operations Patterns
6
+
7
+ ## Health Probes
8
+
9
+ Every service imports `QuanticHealthModule.forRoot()` in `app.module.ts`. It provides three Kubernetes probes:
10
+
11
+ | Probe | Path | Checks | Purpose |
12
+ |---|---|---|---|
13
+ | Liveness | `/health/live` | Event loop only | Is the process alive? Restart if not. |
14
+ | Readiness | `/health/ready` | DB + Redis (auto-detected) + custom | Can it serve traffic? Remove from LB if not. |
15
+ | Startup | `/health/startup` | User-configured | Has initialization completed? |
16
+
17
+ **Auto-detection:** If `DataSource` or `REDIS_CLIENT` is in the DI container, readiness checks are registered automatically. Disable with `autoDetect: false`.
18
+
19
+ **Transport modes:**
20
+ - `controller` (default) — mounts on existing NestJS server, routes are `@Public()`
21
+ - `standalone` — separate `http.createServer` on dedicated port (for workers/queue consumers)
22
+ - `file` — writes to `/tmp/.healthy` on interval (for cron jobs)
23
+ - `none` — programmatic access only via `HealthRegistry`
24
+
25
+ **Custom checks:**
26
+ ```typescript
27
+ QuanticHealthModule.forRoot({
28
+ readiness: [
29
+ { name: 'minio', check: () => minioClient.bucketExists('uploads'), timeoutMs: 5000 },
30
+ { name: 'payments', url: 'http://payments:3000/health/live', timeoutMs: 3000 },
31
+ ],
32
+ })
33
+ ```
34
+
35
+ **Shutdown-aware:** On SIGTERM, readiness flips to 503 immediately, waits `shutdownDelayMs` (default 5s) for LB to stop routing, then `GracefulShutdownService` drains resources.
36
+
37
+ ## Graceful Shutdown
38
+
39
+ On SIGTERM, shutdown runs in two phases:
40
+
41
+ ```
42
+ SIGTERM → Phase 1: readiness → 503, wait 5s (LB stops routing)
43
+ → Phase 2: drainWork() → close DB → close Redis → exit
44
+ ```
45
+
46
+ Services with custom resources (queues, websockets, outbox publisher) override `drainWork()`:
47
+
48
+ ```typescript
49
+ @Injectable()
50
+ export class AppShutdownService extends GracefulShutdownService {
51
+ constructor(
52
+ @Optional() dataSource: DataSource,
53
+ @Optional() @Inject('REDIS_CLIENT') redis: Redis,
54
+ private readonly queueWorker: Worker,
55
+ ) {
56
+ super(dataSource, redis);
57
+ }
58
+
59
+ protected async drainWork(): Promise<void> {
60
+ await this.queueWorker.close(); // stop accepting jobs, wait for in-progress
61
+ }
62
+ }
63
+ ```
64
+
65
+ **Kubernetes alignment:** `terminationGracePeriodSeconds` must exceed `shutdownDelayMs + drainTimeout + buffer` (default: 45s in Helm chart).
66
+
67
+ ## Circuit Breaker
68
+
69
+ All outbound HTTP calls to external services must use `createCircuitBreaker()`:
70
+
71
+ ```typescript
72
+ import { createCircuitBreaker } from '@quanticjs/core';
73
+
74
+ const policy = createCircuitBreaker({
75
+ maxRetries: 2, // 3 total attempts, exponential backoff
76
+ consecutiveFailures: 5, // open circuit after 5 consecutive failures
77
+ halfOpenAfterMs: 30_000, // test one request after 30s
78
+ });
79
+
80
+ const result = await policy.execute(() => httpClient.get('/external-api'));
81
+ ```
82
+
83
+ **States:** Closed (normal) → Open (fast-fail, no outbound calls) → Half-open (test one request) → Closed on success.
84
+
85
+ **Where to apply:**
86
+
87
+ | Integration | Circuit breaker? |
88
+ |---|---|
89
+ | Keycloak JWKS | Yes |
90
+ | Kogito workflow | Yes |
91
+ | Third-party APIs | Yes |
92
+ | Redis | No — ioredis has built-in retry |
93
+ | TypeORM / DB | No — connection pool retries internally |
94
+
95
+ ## NEVER
96
+
97
+ - **NEVER** make liveness depend on external services (DB, Redis) — liveness checks the process only; dependency failures go in readiness
98
+ - **NEVER** skip `QuanticHealthModule.forRoot()` in `app.module.ts` — every service needs health probes
99
+ - **NEVER** exit on SIGTERM without draining — extend `GracefulShutdownService` and close custom resources in `drainWork()`
100
+ - **NEVER** make outbound HTTP calls to external services without a circuit breaker
101
+ - **NEVER** retry 4xx responses — they are deterministic client errors
102
+ - **NEVER** share a circuit breaker across integrations — one failing service must not trip the circuit for healthy ones
103
+ - **NEVER** wrap Redis or TypeORM calls in a circuit breaker — they have built-in retry/reconnect
@@ -0,0 +1,190 @@
1
+ ---
2
+ globs: "client/e2e/**/*.ts, client/playwright.config.ts"
3
+ ---
4
+
5
+ # Testing — E2E UI (Playwright, Mocked APIs)
6
+
7
+ All API calls are mocked with `page.route()`. Tests verify the UI renders correctly and user interactions work.
8
+
9
+ ## Coverage Requirements (MANDATORY)
10
+
11
+ **Every spec file MUST test all four states:**
12
+
13
+ | State | What to test | How to mock |
14
+ |-------|-------------|-------------|
15
+ | **Happy path** | Feature works as expected | Mock API returns 200 with valid data |
16
+ | **Error state** | API failure shows error UI | Mock API returns 500, verify error message shown |
17
+ | **Empty state** | No data shows empty UI | Mock API returns 200 with empty array |
18
+ | **Loading state** | Skeleton/spinner shown while loading | Delay API response, verify skeleton visible |
19
+
20
+ ```typescript
21
+ test.describe('Projects List', () => {
22
+ test('shows project cards on success', async ({ page }) => {
23
+ await page.route('**/api/projects*', route => route.fulfill({
24
+ status: 200, json: { value: mockProjects },
25
+ }));
26
+ await page.goto('/projects');
27
+ await expect(page.getByText('My Project')).toBeVisible();
28
+ });
29
+
30
+ test('shows error message on API failure', async ({ page }) => {
31
+ await page.route('**/api/projects*', route => route.fulfill({ status: 500 }));
32
+ await page.goto('/projects');
33
+ await expect(page.getByText(/something went wrong|error|try again/i)).toBeVisible();
34
+ });
35
+
36
+ test('shows empty state when no projects', async ({ page }) => {
37
+ await page.route('**/api/projects*', route => route.fulfill({
38
+ status: 200, json: { value: [] },
39
+ }));
40
+ await page.goto('/projects');
41
+ await expect(page.getByText(/no projects/i)).toBeVisible();
42
+ });
43
+
44
+ test('shows loading while fetching', async ({ page }) => {
45
+ await page.route('**/api/projects*', async route => {
46
+ await new Promise(r => setTimeout(r, 2000));
47
+ await route.fulfill({ status: 200, json: { value: [] } });
48
+ });
49
+ await page.goto('/projects');
50
+ await expect(page.locator('[role="status"]')).toBeVisible();
51
+ });
52
+ });
53
+ ```
54
+
55
+ ## Auth Mocking (MANDATORY)
56
+
57
+ Auth uses BFF httpOnly cookies — **not** localStorage/sessionStorage tokens.
58
+
59
+ Mock the BFF `/auth/me` endpoint in `beforeEach`:
60
+
61
+ ```typescript
62
+ test.beforeEach(async ({ page }) => {
63
+ await page.route('**/auth/me', route => route.fulfill({
64
+ status: 200,
65
+ contentType: 'application/json',
66
+ body: JSON.stringify({
67
+ id: 'user-1',
68
+ keycloakId: 'kc-1',
69
+ email: 'test@test.com',
70
+ displayName: 'Test User',
71
+ role: 'user',
72
+ roles: ['user'],
73
+ permissions: ['project.read', 'project.create'],
74
+ }),
75
+ }));
76
+ });
77
+
78
+ test('unauthenticated user redirected to login', async ({ page }) => {
79
+ await page.route('**/auth/me', route => route.fulfill({ status: 401 }));
80
+ await page.goto('/projects');
81
+ await expect(page).toHaveURL(/\/login/);
82
+ });
83
+
84
+ test('non-admin cannot access admin pages', async ({ page }) => {
85
+ // Default mock has role: 'user' — admin routes should redirect
86
+ await page.goto('/admin/prompt-templates');
87
+ await expect(page).not.toHaveURL('/admin/prompt-templates');
88
+ });
89
+ ```
90
+
91
+ **NEVER** mock auth by injecting tokens into `localStorage` or `sessionStorage` — the app uses BFF httpOnly cookies and checks auth via `/auth/me`.
92
+
93
+ ## Locators — Semantic ONLY
94
+
95
+ ```typescript
96
+ // ❌ WRONG
97
+ page.locator('.btn-primary');
98
+ page.locator('[data-testid="submit"]');
99
+ page.locator('.fixed.inset-0');
100
+ page.locator('[class*="animate-pulse"]');
101
+
102
+ // ✅ CORRECT
103
+ page.getByRole('button', { name: 'Submit' });
104
+ page.getByRole('heading', { name: 'Projects' });
105
+ page.getByLabel('Email address');
106
+ page.getByText('Welcome back');
107
+ page.getByPlaceholder('Search...');
108
+ ```
109
+
110
+ **Priority:** `getByRole` > `getByLabel` > `getByText` > `getByPlaceholder` > `getByTestId` (last resort)
111
+
112
+ Chain and filter to narrow scope:
113
+ ```typescript
114
+ const card = page.getByRole('listitem').filter({ hasText: 'My Project' });
115
+ await card.getByRole('link', { name: 'View' }).click();
116
+ ```
117
+
118
+ ## Assertions — Web-First Only
119
+
120
+ ```typescript
121
+ // ❌ WRONG — checks once, no retry, FLAKY
122
+ expect(await page.getByText('welcome').isVisible()).toBe(true);
123
+
124
+ // ✅ CORRECT — retries until visible or timeout
125
+ await expect(page.getByText('welcome')).toBeVisible();
126
+ await expect(page.getByRole('heading')).toHaveText('Dashboard');
127
+ await expect(page).toHaveURL('/projects');
128
+ ```
129
+
130
+ ## Waiting — Correct Alternatives
131
+
132
+ ```typescript
133
+ // ❌ WRONG
134
+ await page.waitForTimeout(500);
135
+
136
+ // ✅ CORRECT
137
+ await expect(page.getByRole('button', { name: 'Submit' })).toBeVisible();
138
+ await page.waitForURL('/projects');
139
+ await page.waitForResponse('**/api/projects');
140
+ await expect(page.getByRole('heading')).toHaveText('Success');
141
+ ```
142
+
143
+ ## Responsive Viewport Testing
144
+
145
+ ```typescript
146
+ test.describe('tablet', () => {
147
+ test.use({ viewport: { width: 768, height: 1024 } });
148
+ test('sidebar collapses on tablet', async ({ page }) => {
149
+ await page.goto('/');
150
+ // verify collapsed sidebar behavior
151
+ });
152
+ });
153
+
154
+ test.describe('desktop', () => {
155
+ test.use({ viewport: { width: 1440, height: 900 } });
156
+ test('shows expanded sidebar', async ({ page }) => {
157
+ await page.goto('/');
158
+ await expect(page.getByRole('navigation')).toBeVisible();
159
+ });
160
+ });
161
+ ```
162
+
163
+ ## Test File Structure
164
+
165
+ ```
166
+ client/e2e/
167
+ ├── auth/
168
+ │ └── storage-state.json # gitignored — MCP session cookies
169
+ ├── login.spec.ts
170
+ ├── dashboard.spec.ts
171
+ ├── projects-list.spec.ts
172
+ ├── create-project.spec.ts
173
+ ├── project-detail.spec.ts
174
+ ├── prompt-templates.spec.ts
175
+ ├── profile.spec.ts
176
+ ├── settings.spec.ts
177
+ └── users.spec.ts
178
+ ```
179
+
180
+ ## NEVER
181
+
182
+ - **NEVER** use CSS selectors (`.class`, `#id`, `div > span`, `[class*="..."]`)
183
+ - **NEVER** use `expect(await el.isVisible()).toBe(true)` — use `await expect(el).toBeVisible()`
184
+ - **NEVER** use `page.waitForTimeout()`
185
+ - **NEVER** use XPath selectors
186
+ - **NEVER** test third-party services directly — mock with `page.route()`
187
+ - **NEVER** use `localStorage`/`sessionStorage` to mock auth — mock `/auth/me` via `page.route()`
188
+ - **NEVER** use `fireEvent` — use Playwright's built-in actions (`click`, `fill`, `press`)
189
+ - **NEVER** write happy-path-only tests — all 4 states are mandatory
190
+ - **NEVER** use `.animate-spin` or other CSS class selectors for loading states — use `[role="status"]` or semantic text
@@ -0,0 +1,94 @@
1
+ ---
2
+ globs: "src/**/*.spec.ts, src/**/*.test.ts, client/src/**/*.test.{ts,tsx}, client/e2e/**"
3
+ ---
4
+
5
+ # Testing Patterns
6
+
7
+ ## Backend — Three Layers
8
+
9
+ ### Unit Tests (Jest)
10
+
11
+ Test handlers, validators, and utilities in isolation.
12
+
13
+ Every handler test must cover: happy path, validation failure, not found, conflict, permission check.
14
+
15
+ Validator tests are mandatory and separate.
16
+
17
+ ### Integration Tests (Jest + Supertest)
18
+
19
+ Full HTTP → Controller → Pipeline → Handler → Database round trips.
20
+
21
+ **Real database, not mocks.** Run against PostgreSQL via `docker-compose.test.yml`.
22
+
23
+ ### E2E Tests
24
+
25
+ Isolated test stack: `docker-compose.test.yml` with separate ports (API 3099, Keycloak 8099).
26
+
27
+ ## Frontend — Three Layers
28
+
29
+ ### Component Tests (Vitest + React Testing Library)
30
+
31
+ Test user-visible behavior, not implementation details.
32
+
33
+ **Query priority:** `getByRole` > `getByLabelText` > `getByText` > `getByTestId` (last resort).
34
+
35
+ **Every component test must cover:** happy path, loading state, error state, empty state, user interactions.
36
+
37
+ ```typescript
38
+ it('renders item name', () => {
39
+ render(<ItemCard item={{ id: '1', name: 'Test' }} />);
40
+ expect(screen.getByRole('heading', { name: 'Test' })).toBeInTheDocument();
41
+ });
42
+ ```
43
+
44
+ ### Hook Tests (Vitest + TanStack Query)
45
+
46
+ Test custom hooks with a real QueryClient. Use MSW for API mocking.
47
+
48
+ **Auth mocking:**
49
+ ```typescript
50
+ // ✅ CORRECT
51
+ queryClient.setQueryData(['auth', 'session'], { keycloakId: 'test-id', roles: ['user'] });
52
+
53
+ // ❌ WRONG
54
+ localStorage.setItem('access_token', 'fake-jwt');
55
+ ```
56
+
57
+ ### E2E Tests (Playwright)
58
+
59
+ Mock APIs via `page.route()`. Every spec covers 4 states:
60
+
61
+ | State | Mock |
62
+ |-------|------|
63
+ | Happy path | API returns 200 |
64
+ | Error | API returns 500 |
65
+ | Empty | API returns 200 + empty array |
66
+ | Loading | Delay API response |
67
+
68
+ **Auth in E2E:**
69
+ ```typescript
70
+ await page.route('**/auth/me', route => route.fulfill({
71
+ status: 200,
72
+ body: JSON.stringify({ keycloakId: 'kc-1', roles: ['user'] }),
73
+ }));
74
+ ```
75
+
76
+ **Responsive testing:** Test at mobile viewport (375×812) and desktop.
77
+
78
+ ## CI Pipeline
79
+
80
+ ```
81
+ PR → lint → type-check → unit tests → integration tests → UI E2E → merge
82
+ Nightly → system E2E (full stack)
83
+ ```
84
+
85
+ ## NEVER
86
+
87
+ - **NEVER** test implementation details (internal state, private methods, CSS classes)
88
+ - **NEVER** use CSS selectors in E2E tests — use semantic locators
89
+ - **NEVER** use `page.waitForTimeout()` — use web-first assertions
90
+ - **NEVER** use `fireEvent` — use `userEvent`
91
+ - **NEVER** mock the database in integration tests
92
+ - **NEVER** use `localStorage`/`sessionStorage` for auth in tests
93
+ - **NEVER** write happy-path-only tests
94
+ - **NEVER** use `setTimeout`/`waitForTimeout` for timing in tests
@@ -0,0 +1,64 @@
1
+ # Workflow Backend — Use the Workflow Engine, Don't Rebuild It
2
+
3
+ The workflow engine handles process orchestration, task routing, and state management. **Application code defines workflow configurations and domain-specific handlers — never reimplements the engine.**
4
+
5
+ ## Workflow Module (`src/workflow/`)
6
+
7
+ This module owns all workflow engine interaction. Other modules (change-request, notification, etc.) consume workflow events and provide domain-specific handlers — they never access workflow internals directly.
8
+
9
+ ## Engine Responsibilities
10
+
11
+ | Concern | Handled by |
12
+ |---|---|
13
+ | Process instance lifecycle (start, signal, abort) | Workflow module commands |
14
+ | Task assignment and routing | Workflow engine based on process definitions |
15
+ | Task claim/unclaim/complete | Workflow module commands dispatched from controllers |
16
+ | Process variable management | Workflow engine context — read via workflow queries |
17
+ | Workflow state transitions | Process definitions — not application code |
18
+
19
+ ## What Goes in Application Code vs. Workflow Engine
20
+
21
+ | Belongs in **application code** | Belongs in **workflow engine** (already built) |
22
+ |---|---|
23
+ | Workflow definition configs (which roles, which steps) | Process execution and state machine logic |
24
+ | Domain-specific task completion handlers | Task routing and assignment rules |
25
+ | Commands to start/signal/complete workflow steps | Process instance lifecycle management |
26
+ | Validators for task completion payloads | State transition persistence |
27
+ | Event consumers reacting to workflow state changes | Process evaluation and branching |
28
+ | Role and permission definitions for workflow steps | Task inbox queries and filtering |
29
+
30
+ ## Inter-Module Communication
31
+
32
+ Other modules interact with workflows through the bus — never by importing workflow services directly:
33
+
34
+ ```typescript
35
+ // ✅ CORRECT — dispatch via CommandBus
36
+ this.commandBus.execute(new StartWorkflowCommand(processId, variables));
37
+ this.commandBus.execute(new CompleteTaskCommand(taskId, payload));
38
+ this.queryBus.execute(new GetProcessTimelineQuery(processInstanceId));
39
+
40
+ // ❌ WRONG — direct service import from another module
41
+ await this.workflowService.startProcess('cr-approval', variables);
42
+ ```
43
+
44
+ ## Workflow Events via Redis Streams
45
+
46
+ Workflow state changes are published to Redis Streams. Domain modules subscribe to react:
47
+
48
+ | Event | Published when |
49
+ |---|---|
50
+ | `workflow.process.started` | New process instance created |
51
+ | `workflow.task.assigned` | Task routed to a user/role |
52
+ | `workflow.task.completed` | Task action submitted |
53
+ | `workflow.process.completed` | Process reaches end state |
54
+ | `workflow.process.error` | Process hits an error boundary |
55
+
56
+ ## NEVER
57
+
58
+ - **NEVER** access workflow internals from outside the workflow module — dispatch commands via the bus
59
+ - **NEVER** write your own process state machine — use the workflow engine
60
+ - **NEVER** write your own task assignment/routing logic — configure it in process definitions
61
+ - **NEVER** write your own task inbox query — use workflow module queries
62
+ - **NEVER** manage process variables outside the workflow module — read them via workflow queries
63
+ - **NEVER** poll for workflow state changes — subscribe to workflow Redis Stream events
64
+ - **NEVER** import workflow module services into other modules — use CommandBus/QueryBus
@@ -0,0 +1,60 @@
1
+ # Workflow UI — Use `@quanticjs/workflow-ui`, Don't Rebuild It
2
+
3
+ `@quanticjs/workflow-ui` provides the **complete workflow UI layer** for task management. Every component and hook below is already implemented. Import and use it — never write your own version.
4
+
5
+ ## Provider
6
+
7
+ | Export | Purpose |
8
+ |---|---|
9
+ | `WorkflowProvider` | Context provider for workflow hooks — wraps pages that use workflow features |
10
+
11
+ ## Hooks
12
+
13
+ | Hook | Purpose |
14
+ |---|---|
15
+ | `useTaskList(filters?)` | Fetches task inbox — filterable by status, role, assignee |
16
+ | `useTask(taskId)` | Fetches single task with full context (process variables, form schema) |
17
+ | `useTaskClaim(taskId)` | Claim/unclaim a task for the current user |
18
+ | `useTaskAction(taskId)` | Complete, delegate, or skip a task with payload |
19
+ | `useProcessTimeline(processInstanceId)` | Fetches workflow state history — completed/pending/active nodes |
20
+
21
+ ## Components
22
+
23
+ | Component | Purpose |
24
+ |---|---|
25
+ | `TaskInbox` | Full task list with filtering, sorting, pagination — renders role-based task assignments |
26
+ | `WorkflowForm` | Dynamic form rendered from task form schema — integrates with `useTaskAction` |
27
+ | `TaskDetail` | Task context view — process variables, form, history, actions |
28
+ | `TaskActions` | Approve/reject/claim/delegate action buttons — wired to `useTaskAction` |
29
+ | `ProcessTimeline` | Visual workflow state diagram — completed (green), active (yellow), blocked (red) |
30
+
31
+ ## When to Use
32
+
33
+ | Feature need | Use |
34
+ |---|---|
35
+ | Dashboard task list | `TaskInbox` |
36
+ | Approval interfaces | `TaskDetail` + `TaskActions` |
37
+ | Workflow visualization | `ProcessTimeline` |
38
+ | Dynamic task forms | `WorkflowForm` |
39
+ | Claiming tasks | `useTaskClaim` |
40
+ | Completing workflow steps | `useTaskAction` |
41
+
42
+ ## What Goes in Application Code vs. workflow-ui
43
+
44
+ | Belongs in **application code** | Belongs in **workflow-ui** (already built) |
45
+ |---|---|
46
+ | CR-specific approval pages | Task inbox with filtering/sorting/pagination (`TaskInbox`) |
47
+ | Custom approval form layouts composing `TaskDetail` | Task context view + form rendering (`TaskDetail`, `WorkflowForm`) |
48
+ | Page routing to task views | Approve/reject/claim/delegate buttons (`TaskActions`) |
49
+ | Workflow page wrappers with `WorkflowProvider` | Process state visualization (`ProcessTimeline`) |
50
+ | Domain-specific task filters | Task fetching, claiming, action hooks |
51
+
52
+ ## NEVER
53
+
54
+ - **NEVER** write your own task inbox component — use `TaskInbox` from `@quanticjs/workflow-ui`
55
+ - **NEVER** write your own task detail/actions UI — use `TaskDetail` + `TaskActions` from `@quanticjs/workflow-ui`
56
+ - **NEVER** write your own workflow visualization — use `ProcessTimeline` from `@quanticjs/workflow-ui`
57
+ - **NEVER** write your own dynamic form renderer for workflow tasks — use `WorkflowForm` from `@quanticjs/workflow-ui`
58
+ - **NEVER** write your own task list/claim/complete hooks — use `useTaskList` / `useTaskClaim` / `useTaskAction` from `@quanticjs/workflow-ui`
59
+ - **NEVER** write your own process timeline hook — use `useProcessTimeline` from `@quanticjs/workflow-ui`
60
+ - **NEVER** use workflow hooks outside a `WorkflowProvider` — wrap workflow pages with it
@@ -0,0 +1,68 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(npm install *)",
5
+ "Bash(npm ci)",
6
+ "Bash(npm run *)",
7
+ "Bash(npm test *)",
8
+ "Bash(npm init *)",
9
+ "Bash(npx *)",
10
+ "Bash(node *)",
11
+ "Bash(ls *)",
12
+ "Bash(find *)",
13
+ "Bash(grep *)",
14
+ "Bash(cat *)",
15
+ "Bash(mkdir *)",
16
+ "Bash(cp *)",
17
+ "Bash(mv *)",
18
+ "Bash(git status*)",
19
+ "Bash(git log*)",
20
+ "Bash(git diff*)",
21
+ "Bash(git branch*)",
22
+ "Bash(git add *)",
23
+ "Bash(git commit *)",
24
+ "Bash(docker compose *)",
25
+ "Bash(docker build *)",
26
+ "Bash(docker ps*)"
27
+ ]
28
+ },
29
+ "hooks": {
30
+ "PreToolUse": [
31
+ {
32
+ "matcher": "Bash",
33
+ "hooks": [
34
+ {
35
+ "type": "command",
36
+ "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/guard-destructive.sh\"",
37
+ "timeout": 5
38
+ },
39
+ {
40
+ "type": "command",
41
+ "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/check-secrets.sh\"",
42
+ "timeout": 10
43
+ },
44
+ {
45
+ "type": "command",
46
+ "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/auto-format.sh\"",
47
+ "timeout": 15
48
+ }
49
+ ]
50
+ }
51
+ ],
52
+ "Notification": [
53
+ {
54
+ "matcher": "compact",
55
+ "hooks": [
56
+ {
57
+ "type": "command",
58
+ "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/on-compaction.sh\"",
59
+ "timeout": 10
60
+ }
61
+ ]
62
+ }
63
+ ]
64
+ },
65
+ "enabledPlugins": {
66
+ "frontend-design@claude-plugins-official": true
67
+ }
68
+ }