create-claude-workspace 1.1.85 → 1.1.87
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/template/.claude/agents/backend-ts-architect.md +9 -2
- package/dist/template/.claude/agents/orchestrator.md +154 -26
- package/dist/template/.claude/agents/senior-code-reviewer.md +2 -1
- package/dist/template/.claude/agents/test-engineer.md +97 -0
- package/dist/template/.claude/agents/ui-engineer.md +26 -4
- package/dist/template/.claude/profiles/angular.md +83 -47
- package/dist/template/.claude/profiles/react.md +71 -23
- package/dist/template/.claude/profiles/svelte.md +67 -21
- package/dist/template/.claude/profiles/vue.md +68 -21
- package/dist/template/.claude/templates/claude-md.md +10 -0
- package/package.json +1 -1
|
@@ -144,10 +144,17 @@ Which project patterns apply (onion layers, DI, UnitOfWork, etc.)
|
|
|
144
144
|
Common mistakes to avoid for this specific task.
|
|
145
145
|
|
|
146
146
|
### TESTING
|
|
147
|
-
Which files need tests and what to test.
|
|
147
|
+
Which files need tests and what to test. **You MUST specify test layers explicitly** — don't leave it to the orchestrator to guess:
|
|
148
|
+
|
|
149
|
+
**Unit tests** (always, for files with logic):
|
|
148
150
|
- `path/to/file.ts` — test cases: [list key scenarios, edge cases, error conditions]
|
|
149
151
|
- `path/to/other.ts` — no tests needed (config / wiring only)
|
|
150
|
-
|
|
152
|
+
|
|
153
|
+
**Integration tests** (REQUIRED for any task that adds/changes API endpoints, database operations, service interactions, or publishable library public API):
|
|
154
|
+
- Endpoints/services to cover: [list with expected request/response patterns]
|
|
155
|
+
- If no integration tests needed, write: "Integration: not applicable (no API/DB/service changes)"
|
|
156
|
+
|
|
157
|
+
Be explicit about every layer — omitting a layer means no tests for it.
|
|
151
158
|
|
|
152
159
|
### API CONTRACT REVISION (only when re-invoked with frontend feedback)
|
|
153
160
|
If the orchestrator re-delegates with frontend architect feedback about impractical API contract, re-output only the revised INTERFACES section. Include a brief changelog:
|
|
@@ -202,14 +202,90 @@ If `PLAN.md` exists in the project root, check whether it changed since the last
|
|
|
202
202
|
- Log in MEMORY.md Notes: "Retroactive git sync completed — [N] issues created, [M] marked as done"
|
|
203
203
|
- Skip if any trigger condition is not met (no remote, no TODO.md, markers already present, CLI missing, auth invalid)
|
|
204
204
|
|
|
205
|
-
### 11. Kit
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
205
|
+
### 11. Kit auto-upgrade (check npm + migrate project)
|
|
206
|
+
Automatically check for newer `create-claude-workspace` versions, update kit files, analyze changes, and create migration tasks for the target project.
|
|
207
|
+
|
|
208
|
+
#### 11a. Check npm registry for latest version
|
|
209
|
+
```bash
|
|
210
|
+
CURRENT=$(cat .claude/.kit-version 2>/dev/null || echo "0.0.0")
|
|
211
|
+
LATEST=$(npm view create-claude-workspace version 2>/dev/null || echo "")
|
|
212
|
+
```
|
|
213
|
+
- If `LATEST` is empty (network failure, npm unavailable) → skip entire step 11
|
|
214
|
+
- If `LATEST` equals `CURRENT` → skip (already up to date)
|
|
215
|
+
- If `LATEST` is newer than `CURRENT` → proceed to 11b
|
|
216
|
+
|
|
217
|
+
#### 11b. Update kit files
|
|
218
|
+
```bash
|
|
219
|
+
# Run update using the project's package runner
|
|
220
|
+
{PKG_RUNNER} create-claude-workspace@latest --update
|
|
221
|
+
```
|
|
222
|
+
- This overwrites `.claude/` files (agents, profiles, templates, docker, router, .kit-version) with the latest version
|
|
223
|
+
- Project-specific files (CLAUDE.md, PRODUCT.md, TODO.md, MEMORY.md) are NOT touched by the update command
|
|
224
|
+
|
|
225
|
+
#### 11c. Capture and analyze diff
|
|
226
|
+
```bash
|
|
227
|
+
# Capture what changed in .claude/
|
|
228
|
+
git diff .claude/
|
|
229
|
+
```
|
|
230
|
+
- If diff is empty (update ran but nothing changed) → skip to 11e
|
|
231
|
+
- Categorize changes:
|
|
232
|
+
- **Template changes** (`.claude/templates/claude-md.md`) → new conventions, patterns, rules for project CLAUDE.md
|
|
233
|
+
- **Profile changes** (`.claude/profiles/*.md`) → new frontend patterns, review checklists
|
|
234
|
+
- **Agent changes** (`.claude/agents/*.md`) → updated agent behavior (self-updating, no project migration needed)
|
|
235
|
+
- **Docker changes** (`.claude/docker/*`) → updated container config
|
|
236
|
+
- **Router changes** (`.claude/CLAUDE.md`) → updated routing instructions
|
|
237
|
+
|
|
238
|
+
#### 11d. Create migration tasks (if project changes needed)
|
|
239
|
+
Only if template or profile changes affect the target project — delegate to `technical-planner`:
|
|
240
|
+
|
|
241
|
+
"The `create-claude-workspace` kit was upgraded from v{CURRENT} to v{LATEST}. Below is the diff of changed files. Analyze what changed and determine if the TARGET PROJECT needs migration or refactoring tasks.
|
|
242
|
+
|
|
243
|
+
**The diff:**
|
|
244
|
+
```
|
|
245
|
+
{git diff output}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**Focus on these change types (create a task for each that applies):**
|
|
249
|
+
- New or changed preferred libraries → refactoring task (e.g., 'Migrate from X to Y per kit v{LATEST}')
|
|
250
|
+
- New or changed code patterns/conventions → refactoring task (e.g., 'Apply new naming convention from kit v{LATEST}')
|
|
251
|
+
- New or removed rules (e.g., new lint rule, banned pattern) → cleanup task (e.g., 'Fix all eslint-disable comments per kit v{LATEST}')
|
|
252
|
+
- Changed profile conventions (frontend patterns) → style refactoring task
|
|
253
|
+
- Changes ONLY in agent instructions (`.claude/agents/*.md`) → NO task needed (agents self-update)
|
|
254
|
+
- Changes ONLY in Docker/router files → NO task needed
|
|
255
|
+
|
|
256
|
+
**If migration tasks are needed:**
|
|
257
|
+
- Insert them at the TOP of the current phase in TODO.md with tag `Kit-Upgrade: v{LATEST}`
|
|
258
|
+
- Use Complexity: S or M (never L for migration tasks — split if large)
|
|
259
|
+
- Each task must have clear scope: which files to change, what pattern to apply
|
|
260
|
+
- Add dependency on each other if they must be sequential
|
|
261
|
+
|
|
262
|
+
**If no project changes needed**, respond with: `MIGRATION: NONE — kit v{LATEST} changes are agent-only, no project impact.`
|
|
263
|
+
|
|
264
|
+
Read the current TODO.md and MEMORY.md for context on the project's current phase and tech stack."
|
|
265
|
+
|
|
266
|
+
- If planner returns `MIGRATION: NONE` → no tasks created, proceed to 11e
|
|
267
|
+
- If planner creates tasks → if git integration active, delegate to `devops-integrator` to create issues for the new migration tasks
|
|
268
|
+
- Log in MEMORY.md Notes: "Kit upgraded v{CURRENT} → v{LATEST}: [N migration tasks created / no project migration needed]"
|
|
269
|
+
|
|
270
|
+
#### 11e. Commit update + refresh CLAUDE.md
|
|
271
|
+
1. Run `/revise-claude-md` to reconcile the project's CLAUDE.md with new conventions from the updated template (`.claude/templates/claude-md.md`). If `/revise-claude-md` is unavailable, manually compare CLAUDE.md against the template and update sections with new content. Do NOT overwrite project-specific content (project name, tech stack choices, deployment config).
|
|
272
|
+
2. Update MEMORY.md: set `Kit_Version: [value from .claude/.kit-version]`
|
|
273
|
+
3. Commit all changes:
|
|
274
|
+
```bash
|
|
275
|
+
git add .claude/ CLAUDE.md MEMORY.md TODO.md
|
|
276
|
+
git commit -m "chore: upgrade kit to v{LATEST}"
|
|
277
|
+
```
|
|
278
|
+
4. Push if remote exists: `git push origin HEAD`
|
|
279
|
+
|
|
280
|
+
#### 11f. Migration task priority
|
|
281
|
+
- After step 11 completes, if migration tasks were created in TODO.md, they become the **next tasks to pick** in STEP 1 (before any regular development tasks in the current phase)
|
|
282
|
+
- This ensures the project stays aligned with kit conventions before new features are built on outdated patterns
|
|
283
|
+
- Migration tasks follow the normal development cycle (STEP 1-12) — they get architect plans, tests, review, just like any other task
|
|
284
|
+
|
|
285
|
+
#### Skip conditions
|
|
286
|
+
- `.claude/.kit-version` does not exist → skip entire step 11
|
|
287
|
+
- `npm view` fails (network, npm not installed) → skip, log in MEMORY.md Notes: "Kit version check skipped — npm registry unreachable"
|
|
288
|
+
- Current version equals latest → skip (up to date)
|
|
213
289
|
|
|
214
290
|
### 12. Ingest external issues (if git integration active)
|
|
215
291
|
- **Only run at phase transitions** — not every invocation
|
|
@@ -245,6 +321,7 @@ To determine if a task is frontend, backend, or fullstack, use this heuristic:
|
|
|
245
321
|
- Read MEMORY.md, find next incomplete item and current phase
|
|
246
322
|
- Update MEMORY.md: set `Current Step: 1 — PICK`
|
|
247
323
|
- Cross-reference with TODO.md — find the first unchecked `- [ ]` task in the current phase
|
|
324
|
+
- **Kit-upgrade tasks first**: if any `- [ ]` tasks in the current phase have `Kit-Upgrade:` tag, pick those before any regular tasks (regardless of position in the phase)
|
|
248
325
|
- Skip tasks marked as `- [~]` (skipped/blocked)
|
|
249
326
|
- **If no `[ ]` tasks remain in the current phase**: advance to the next phase. Update MEMORY.md `Current Phase` to the next phase heading from TODO.md. Then look for the first `- [ ]` task in that new phase. This IS a phase transition — the phase transition check below will trigger for the first task. If no `[ ]` tasks remain in ANY phase, go to the final completion sequence in STEP 12.
|
|
250
327
|
- **Dependency check**: verify ALL tasks listed in "Depends on" are checked `[x]` in TODO.md. If any dependency is incomplete, skip this task and pick the next one without unmet dependencies.
|
|
@@ -361,20 +438,28 @@ To determine if a task is frontend, backend, or fullstack, use this heuristic:
|
|
|
361
438
|
- Used within ONE domain group -> `libs/[domain]/shared/`
|
|
362
439
|
- Used within ONE library -> keep it local
|
|
363
440
|
|
|
364
|
-
**STEP 4: WRITE TESTS — DELEGATE to `test-engineer` (
|
|
441
|
+
**STEP 4: WRITE TESTS — DELEGATE to `test-engineer` (MANDATORY)**
|
|
365
442
|
- Update MEMORY.md (on main): set `Current Step: 4 — TESTS`
|
|
366
|
-
-
|
|
367
|
-
-
|
|
368
|
-
- **
|
|
369
|
-
- **
|
|
370
|
-
-
|
|
443
|
+
- **This step is NOT optional.** Every task that changes code MUST go through test-engineer. The only exception is tasks that change ONLY config/CI/docs with zero logic.
|
|
444
|
+
- Read the architect's **TESTING** section from STEP 2. It specifies test layers explicitly:
|
|
445
|
+
- **Unit tests** — files + test cases
|
|
446
|
+
- **Integration tests** — endpoints/services/DB operations (backend tasks)
|
|
447
|
+
- **E2E tests** — user flows/routes (frontend tasks)
|
|
448
|
+
- **VRT tests** — pages/views for visual regression (frontend tasks)
|
|
449
|
+
- **Fallback if TESTING section is missing or vague**: Assume ALL layers are needed based on task type:
|
|
450
|
+
- Backend task → unit + integration tests
|
|
451
|
+
- Frontend task → unit + E2E tests (+ VRT if new pages)
|
|
452
|
+
- Fullstack task → unit + integration + E2E tests
|
|
453
|
+
- **Build the test-engineer prompt with ALL applicable layers in a single delegation.** Include:
|
|
371
454
|
- **Working directory: {worktree-abs-path}** — all file reads/edits/test runs must use this path
|
|
372
|
-
- The TESTING section from the architect's plan
|
|
455
|
+
- The full TESTING section from the architect's plan
|
|
373
456
|
- List of new/changed files
|
|
374
|
-
-
|
|
375
|
-
-
|
|
376
|
-
- **E2E tests
|
|
377
|
-
- **Integration tests
|
|
457
|
+
- Explicit request for each test layer the architect specified (or the fallback layers)
|
|
458
|
+
- Ask for: TEST PLAN per layer, then full implementation, then RUN all tests and report results
|
|
459
|
+
- **E2E tests**: Include in the prompt: "Write E2E tests for the following user flows: [list flows from TESTING section]. Use Playwright. Ensure E2E project is set up (scaffold via `nx g @nx/playwright:configuration` if missing)."
|
|
460
|
+
- **Integration tests**: Include in the prompt: "Write integration tests for: [list endpoints/services from TESTING section]. Test real request/response cycles (use test server or supertest), database operations against test DB, and public API contracts. Keep integration tests in `*.integration.spec.ts` files (co-located next to source, same as unit tests)."
|
|
461
|
+
- **Visual regression tests**: Include in the prompt: "Write visual regression tests (`*.vrt.spec.ts`) for these pages/views: [list from TESTING section]. Use the VRT template with 3-viewport matrix (mobile 375px, tablet 768px, desktop 1280px). Use `maxDiffPixelRatio: 0.01` and `fullPage: true` for page screenshots. Mask dynamic content (timestamps, user avatars) with `mask` option."
|
|
462
|
+
- **Baseline updates for intentional visual changes**: If the architect's plan describes intentional visual changes to existing pages (redesign, new theme, layout restructure), add to the test-engineer prompt: "Existing VRT baselines will need updating. After writing/updating VRT tests, run `nx e2e [APP]-e2e -- --update-snapshots` to regenerate baselines. Include the updated baseline images in the commit."
|
|
378
463
|
- **Error recovery**: If test-engineer agent fails, write basic smoke tests yourself and note in MEMORY.md
|
|
379
464
|
|
|
380
465
|
**STEP 5: BUILD, LINT & TEST (with coverage)**
|
|
@@ -402,6 +487,7 @@ To determine if a task is frontend, backend, or fullstack, use this heuristic:
|
|
|
402
487
|
bunx kill-port 4200 2>/dev/null || npx kill-port 4200 2>/dev/null || true
|
|
403
488
|
```
|
|
404
489
|
- E2E and integration test failures are treated the same as unit test failures — fix before proceeding.
|
|
490
|
+
- **VRT first-run note**: If VRT tests (`*.vrt.spec.ts`) were newly created in STEP 4, the first E2E run will fail because no baseline screenshots exist yet. This is expected — not a real failure. Re-run E2E once: first run creates baselines, second run compares against them. If the second run passes, the baselines are correct. Ensure `*-snapshots/` directories are staged for commit in STEP 11.
|
|
405
491
|
- **Stylelint** (for tasks with SCSS changes): run `stylelint "libs/**/*.scss" "apps/**/*.scss" --max-warnings=0` via the PKG runner (e.g., `bunx stylelint ...` / `npx stylelint ...`) or `nx stylelint [PROJECT]` if configured as Nx target
|
|
406
492
|
- Pipe output through `| tail -30` for readability
|
|
407
493
|
- Only use `--skip-nx-cache` if you suspect stale cache
|
|
@@ -415,6 +501,7 @@ To determine if a task is frontend, backend, or fullstack, use this heuristic:
|
|
|
415
501
|
**STEP 6: VISUAL VERIFICATION (UI tasks only)**
|
|
416
502
|
- Update MEMORY.md (on main): set `Current Step: 6 — VISUAL VERIFY`
|
|
417
503
|
- Skip this step for backend-only tasks
|
|
504
|
+
- **Note**: Automated VRT (pixel comparison via `toHaveScreenshot()`) runs in STEP 5 and catches regressions against baselines. This manual verification step complements VRT by checking subjective quality: does the page look good? Does it match the design intent? Are proportions and spacing aesthetically correct? VRT catches what changed; this step judges whether the change looks right.
|
|
418
505
|
- For frontend tasks, use Playwright MCP to verify visual output:
|
|
419
506
|
1. Start dev server **from the worktree**. **Important**: each Bash tool call runs in a separate shell, so PID variables don't persist. Start the server in one Bash call and leave it running:
|
|
420
507
|
```bash
|
|
@@ -455,13 +542,25 @@ To determine if a task is frontend, backend, or fullstack, use this heuristic:
|
|
|
455
542
|
- **Error recovery**: If reviewer agent fails, run basic checks yourself (build, lint, test pass + quick read of changed files for obvious issues), note in MEMORY.md that automated review was skipped
|
|
456
543
|
- DO NOT skip this step even if you think the code is perfect
|
|
457
544
|
|
|
458
|
-
**STEP 8: REWORK (based on reviewer's findings)**
|
|
545
|
+
**STEP 8: REWORK — DELEGATE to the original implementer (based on reviewer's findings)**
|
|
459
546
|
- Update MEMORY.md (on main): set `Current Step: 8 — REWORK`
|
|
460
|
-
-
|
|
461
|
-
-
|
|
462
|
-
-
|
|
463
|
-
|
|
464
|
-
-
|
|
547
|
+
- **You are an orchestrator — you MUST NOT fix code yourself.** Delegate rework to the same agent that did the implementation:
|
|
548
|
+
- Frontend issues → Agent tool with `ui-engineer`, model `"opus"`
|
|
549
|
+
- Backend issues → Agent tool with `backend-ts-architect`, model `"opus"`
|
|
550
|
+
- Fullstack issues → call both sequentially (backend first for API changes, then frontend)
|
|
551
|
+
- In your Agent tool prompt, include:
|
|
552
|
+
- **Working directory: {worktree-abs-path}**
|
|
553
|
+
- The reviewer's findings (copy the CRITICAL/WARN/NICE-TO-HAVE sections verbatim)
|
|
554
|
+
- Which items to fix vs skip, with your reasoning
|
|
555
|
+
- "After fixing, run build + lint + test to verify. Do NOT introduce new issues."
|
|
556
|
+
- Fix priority:
|
|
557
|
+
- CRITICAL: Fix ALL. No exceptions.
|
|
558
|
+
- WARN: Fix ALL. No exceptions.
|
|
559
|
+
- NICE-TO-HAVE: Implement ALL that make sense.
|
|
560
|
+
Skip ONLY if: (a) adds unnecessary complexity, (b) out of scope, (c) requires large unrelated refactor.
|
|
561
|
+
- GREEN: No action, confirms correct direction.
|
|
562
|
+
- DO NOT edit files yourself — delegate via Agent tool
|
|
563
|
+
- DO NOT skip delegation "because the fixes are small" — even one-line fixes go through the specialist agent
|
|
465
564
|
|
|
466
565
|
**STEP 9: RE-REVIEW — DELEGATE again (max 4 re-reviews here, 5 total with STEP 7)**
|
|
467
566
|
- Update MEMORY.md (on main): set `Current Step: 9 — RE-REVIEW`
|
|
@@ -501,6 +600,7 @@ To determine if a task is frontend, backend, or fullstack, use this heuristic:
|
|
|
501
600
|
cd {worktree-path} && git add [source files] [test files] [migration files] TODO.md MEMORY.md
|
|
502
601
|
```
|
|
503
602
|
- Include ALL files that are part of this task: source, tests, migrations, config changes, PLUS tracking files (TODO.md, MEMORY.md)
|
|
603
|
+
- Include VRT baseline snapshots (`*-snapshots/` directories) if VRT tests were created or baselines updated in this task
|
|
504
604
|
- If unsure about a file, check `git -C {worktree-path} diff [file]` — if it's related to this task, include it
|
|
505
605
|
- Descriptive message, English, focus on "why" not "what"
|
|
506
606
|
- Conventional commits: feat/fix/refactor/chore/docs
|
|
@@ -762,10 +862,38 @@ If a bug is caused by a third-party dependency (confirmed by isolating the issue
|
|
|
762
862
|
4. At the next dependency freshness check (phase transition or manual), check if a newer version of the package is available. If so, upgrade and re-attempt the blocked task.
|
|
763
863
|
5. If no other tasks are available, log the blocker and end the session with `status: blocked, action: needs_package_update`. The external loop will retry in the next invocation — the dependency freshness check at each session start will detect new versions.
|
|
764
864
|
|
|
865
|
+
## User Interaction (Interactive Mode)
|
|
866
|
+
|
|
867
|
+
When the user sends a message during an interactive session (not UNATTENDED mode), **delegate rather than answer directly**:
|
|
868
|
+
|
|
869
|
+
| User intent | Delegate to | Example |
|
|
870
|
+
|---|---|---|
|
|
871
|
+
| Frontend question/request (components, styling, UI patterns, a11y) | `ui-engineer` (opus) | "How should I structure this form?" |
|
|
872
|
+
| Backend question/request (API, DB, architecture, services) | `backend-ts-architect` (opus) | "What's the best way to handle auth tokens?" |
|
|
873
|
+
| Code review / quality question | `senior-code-reviewer` (opus) | "Is this implementation okay?" |
|
|
874
|
+
| Testing question/request | `test-engineer` (sonnet) | "What tests are missing for this feature?" |
|
|
875
|
+
| Deployment / CI/CD question | `deployment-engineer` (sonnet) | "How do I set up preview deploys?" |
|
|
876
|
+
| Git / issues / MR question | `devops-integrator` (sonnet) | "Create an issue for this bug" |
|
|
877
|
+
| Product / prioritization question | `product-owner` (opus) | "Should we add this feature to the roadmap?" |
|
|
878
|
+
| Planning / task breakdown | `technical-planner` (opus) | "Break this feature into tasks" |
|
|
879
|
+
|
|
880
|
+
**How to delegate user questions:**
|
|
881
|
+
1. Identify which specialist owns the domain
|
|
882
|
+
2. Delegate via Agent tool with the user's question + relevant context (current task, worktree path, affected files)
|
|
883
|
+
3. Relay the specialist's answer back to the user
|
|
884
|
+
4. If the question spans multiple domains (e.g., fullstack), delegate to each relevant specialist and synthesize
|
|
885
|
+
|
|
886
|
+
**When NOT to delegate** (answer directly):
|
|
887
|
+
- Questions about the orchestrator workflow itself ("What step are we on?", "What's the next task?")
|
|
888
|
+
- MEMORY.md / TODO.md status queries
|
|
889
|
+
- Simple confirmations ("Should I continue?", "Ready to proceed?")
|
|
890
|
+
|
|
891
|
+
After handling the user's message, resume the development cycle from where you left off.
|
|
892
|
+
|
|
765
893
|
## Rules
|
|
766
894
|
|
|
767
895
|
- You are an ORCHESTRATOR — delegate to specialist agents, do not bypass them
|
|
768
|
-
- Work FULLY autonomously,
|
|
896
|
+
- Work FULLY autonomously in UNATTENDED mode; in interactive mode, handle user messages per the User Interaction section above
|
|
769
897
|
- NEVER commit code that: (a) failed review, (b) failed build/lint, (c) breaks tests
|
|
770
898
|
- NEVER implement without architect plan (STEP 2) or skip code review (STEP 7)
|
|
771
899
|
- Each commit = 1 logical unit
|
|
@@ -35,7 +35,7 @@ Before reviewing, you MUST:
|
|
|
35
35
|
|
|
36
36
|
**Frontend Patterns (from profile)**
|
|
37
37
|
Read `.claude/profiles/frontend.md` for the framework-specific review checklist. Apply ALL items listed there. Common cross-framework checks:
|
|
38
|
-
- Smart/dumb component separation — no service injection in presentational components
|
|
38
|
+
- Smart/dumb component separation — no business service injection in presentational components (UI infrastructure like Overlay, CDK, useRef is OK), no visual styling in feature components (layout orchestration via grid/flex is OK). Flag: colors, backgrounds, borders, shadows, typography, padding in feature components.
|
|
39
39
|
- No method calls or complex expressions in templates/JSX — use computed/derived values
|
|
40
40
|
- Correct state management patterns for the framework
|
|
41
41
|
- Lazy loading and code splitting applied correctly
|
|
@@ -113,6 +113,7 @@ Read `.claude/profiles/frontend.md` for the framework-specific review checklist.
|
|
|
113
113
|
- Do NOT require tests for: pure UI layout components, config files, SCSS, route wiring
|
|
114
114
|
- **Coverage thresholds**: project-wide 80% for statements, branches, functions, and lines. Flag WARN if a new file with business logic has significantly lower coverage than this target.
|
|
115
115
|
- **IMPORTANT**: The orchestrator may include an architect's TESTING section in the review prompt. If it lists files as "no tests needed" with rationale, do NOT flag those files as missing tests. Only flag files that the architect's plan says SHOULD have tests but don't, or files not mentioned in the TESTING section at all that contain business/domain logic.
|
|
116
|
+
- **Visual regression**: For frontend tasks that add or change pages/routes, flag WARN if no VRT test (`*.vrt.spec.ts`) exists for the affected pages. Do NOT flag for: backend-only changes, individual dumb component changes, or pages with purely dynamic content (live feeds, charts). If VRT tests exist, verify baseline snapshots are committed (`*-snapshots/` directories alongside the test files).
|
|
116
117
|
|
|
117
118
|
**Code Duplication (HIGH PRIORITY)**
|
|
118
119
|
- Search for similar/identical logic elsewhere in the codebase before approving new code
|
|
@@ -302,6 +302,103 @@ describe('my-lib public API (integration)', () => {
|
|
|
302
302
|
- Keep integration tests focused — test the contract/behavior, not internals
|
|
303
303
|
- Integration tests are slower than unit tests — that's expected. Don't optimize for speed at the cost of coverage.
|
|
304
304
|
|
|
305
|
+
## Visual Regression Testing (Playwright `toHaveScreenshot()`)
|
|
306
|
+
|
|
307
|
+
Automated pixel-by-pixel comparison against baseline screenshots. Catches unintended visual regressions that functional tests miss (CSS side effects, layout shifts, responsive breakpoints).
|
|
308
|
+
|
|
309
|
+
### When to write VRT tests
|
|
310
|
+
- **Page-level views** — each route/page gets a VRT test covering default state
|
|
311
|
+
- **Key interactive states** — modal open, form validation errors, empty state, loaded state, skeleton/loading
|
|
312
|
+
- **Multi-component compositions** — dashboards, listings, onboarding flows
|
|
313
|
+
- **Do NOT write VRT for**: individual dumb components in isolation, backend-only tasks, pages with highly dynamic content (live charts, video feeds, real-time data)
|
|
314
|
+
|
|
315
|
+
### VRT file naming
|
|
316
|
+
- `*.vrt.spec.ts` suffix — co-located with functional E2E tests in `apps/[APP]-e2e/src/` or `e2e/`
|
|
317
|
+
- Example: `checkout.vrt.spec.ts` beside `checkout.spec.ts`
|
|
318
|
+
- Baselines auto-stored in `*.vrt.spec.ts-snapshots/` directories — **commit these to git**
|
|
319
|
+
|
|
320
|
+
### Viewport matrix
|
|
321
|
+
```typescript
|
|
322
|
+
const VIEWPORTS = [
|
|
323
|
+
{ name: 'mobile', width: 375, height: 812 },
|
|
324
|
+
{ name: 'tablet', width: 768, height: 1024 },
|
|
325
|
+
{ name: 'desktop', width: 1280, height: 720 },
|
|
326
|
+
] as const;
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### VRT test template
|
|
330
|
+
```typescript
|
|
331
|
+
import { test, expect } from '@playwright/test';
|
|
332
|
+
|
|
333
|
+
const VIEWPORTS = [
|
|
334
|
+
{ name: 'mobile', width: 375, height: 812 },
|
|
335
|
+
{ name: 'tablet', width: 768, height: 1024 },
|
|
336
|
+
{ name: 'desktop', width: 1280, height: 720 },
|
|
337
|
+
] as const;
|
|
338
|
+
|
|
339
|
+
test.describe('Checkout Page — Visual Regression', () => {
|
|
340
|
+
for (const vp of VIEWPORTS) {
|
|
341
|
+
test.describe(vp.name, () => {
|
|
342
|
+
test.use({ viewport: { width: vp.width, height: vp.height } });
|
|
343
|
+
|
|
344
|
+
test('default state', async ({ page }) => {
|
|
345
|
+
await page.goto('/checkout');
|
|
346
|
+
await page.waitForLoadState('networkidle');
|
|
347
|
+
await expect(page).toHaveScreenshot(`checkout-default-${vp.name}.png`, {
|
|
348
|
+
maxDiffPixelRatio: 0.01,
|
|
349
|
+
fullPage: true,
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test('validation errors', async ({ page }) => {
|
|
354
|
+
await page.goto('/checkout');
|
|
355
|
+
await page.getByRole('button', { name: 'Pay' }).click();
|
|
356
|
+
await expect(page).toHaveScreenshot(`checkout-errors-${vp.name}.png`, {
|
|
357
|
+
maxDiffPixelRatio: 0.01,
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### VRT configuration
|
|
366
|
+
- `maxDiffPixelRatio: 0.01` (1%) — tolerates minor anti-aliasing differences across environments
|
|
367
|
+
- `fullPage: true` for page-level screenshots (captures below-fold content)
|
|
368
|
+
- Omit `fullPage` for component-state screenshots (captures only the viewport)
|
|
369
|
+
- Screenshot name includes viewport name — ensures unique baselines per breakpoint
|
|
370
|
+
- `animations: 'disabled'` in Playwright config `expect.toHaveScreenshot` — prevents flaky diffs from CSS transitions
|
|
371
|
+
|
|
372
|
+
### Baseline management
|
|
373
|
+
- Baselines live in `*-snapshots/` directories next to the test file — **always commit them to git**
|
|
374
|
+
- When visual changes are **intentional**: run with `--update-snapshots` flag, then commit new baselines
|
|
375
|
+
- **First run** always fails (no baselines exist) — run twice: first creates baselines, second verifies
|
|
376
|
+
- In CI: use the Playwright Docker image (`mcr.microsoft.com/playwright`) for consistent rendering
|
|
377
|
+
|
|
378
|
+
### Running VRT tests
|
|
379
|
+
```bash
|
|
380
|
+
# Run all E2E (includes VRT)
|
|
381
|
+
nx e2e [APP]-e2e
|
|
382
|
+
|
|
383
|
+
# Update baselines after intentional visual changes
|
|
384
|
+
nx e2e [APP]-e2e -- --update-snapshots
|
|
385
|
+
|
|
386
|
+
# Run only VRT tests
|
|
387
|
+
nx e2e [APP]-e2e -- --grep "Visual Regression"
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Anti-flakiness rules (STRICT)
|
|
391
|
+
- Always `await` content visibility before screenshot: `await expect(page.getByText('...')).toBeVisible()`
|
|
392
|
+
- Use `page.waitForLoadState('networkidle')` before page screenshots if the page fetches data
|
|
393
|
+
- Mask dynamic content (timestamps, avatars, user-specific data) using `mask` option:
|
|
394
|
+
```typescript
|
|
395
|
+
await expect(page).toHaveScreenshot('name.png', {
|
|
396
|
+
mask: [page.locator('.timestamp'), page.locator('.avatar')],
|
|
397
|
+
});
|
|
398
|
+
```
|
|
399
|
+
- If a VRT test is flaky after 3 runs, **delete it** rather than increase the threshold — flaky VRT is worse than no VRT
|
|
400
|
+
- NEVER set `maxDiffPixelRatio` above `0.02` — if more tolerance is needed, the test is measuring the wrong thing
|
|
401
|
+
|
|
305
402
|
## Running Tests (MANDATORY)
|
|
306
403
|
|
|
307
404
|
After writing ALL test files, you MUST run them and report results:
|
|
@@ -55,13 +55,23 @@ Write clean, elegant code that is easy to test and reason about:
|
|
|
55
55
|
|
|
56
56
|
**Dumb components (ui/):**
|
|
57
57
|
- Pure presentational — inputs + event emitters/callbacks + slots/content projection
|
|
58
|
-
-
|
|
58
|
+
- UI infrastructure injection is allowed (Overlay, ElementRef, Renderer2, CDK, useRef, etc.) — these are UI plumbing, not business logic
|
|
59
|
+
- No business services, no router, no HTTP, no data fetching
|
|
59
60
|
- Fully reusable and testable in isolation
|
|
60
61
|
|
|
61
62
|
**Feature components (feature/ or pages/):**
|
|
62
63
|
- Smart orchestrators — inject services, manage state
|
|
63
|
-
- Coordinate dumb components
|
|
64
|
+
- Coordinate dumb components via composition and content projection/slots
|
|
64
65
|
- Tied to specific use cases
|
|
66
|
+
- **NO visual styling** — no colors, backgrounds, borders, shadows, typography, padding on content areas, animations. All visual styling belongs in dumb components.
|
|
67
|
+
- **Layout orchestration is OK** — arranging child components via grid/flex (`display: grid/flex`, `grid-template-areas`, `gap`) is acceptable, especially for page-level route components. This is layout plumbing, not visual design.
|
|
68
|
+
- When a page layout pattern is **reused across multiple routes**, extract it to a dumb layout shell component. Single-use layout stays in the feature component — don't create single-use wrapper components just for purity.
|
|
69
|
+
|
|
70
|
+
**Anti-patterns to watch for:**
|
|
71
|
+
- Dumb component with 8+ inputs → restructure via compound component pattern, slots/projection, or context/provide
|
|
72
|
+
- Feature component coordinating 10+ child components → extract sub-features
|
|
73
|
+
- Single-use layout shell component → unnecessary indirection, keep layout in the feature component
|
|
74
|
+
- Prop drilling through 3+ levels → use slots/projection instead of passing inputs down
|
|
65
75
|
|
|
66
76
|
## Approach
|
|
67
77
|
|
|
@@ -121,10 +131,21 @@ Which project patterns apply (smart/dumb, directives/hooks/composables, content
|
|
|
121
131
|
Common mistakes to avoid for this specific task.
|
|
122
132
|
|
|
123
133
|
### TESTING
|
|
124
|
-
Which files need tests and what to test.
|
|
134
|
+
Which files need tests and what to test. **You MUST specify test layers explicitly** — don't leave it to the orchestrator to guess:
|
|
135
|
+
|
|
136
|
+
**Unit tests** (always, for files with logic):
|
|
125
137
|
- `path/to/file.ts` — test cases: [list key scenarios, edge cases, error conditions]
|
|
126
138
|
- `path/to/other.ts` — no tests needed (pure UI layout / config / styles)
|
|
127
|
-
|
|
139
|
+
|
|
140
|
+
**E2E tests** (REQUIRED for any task that adds/changes user-facing flows — navigation, forms, multi-step interactions, authentication, CRUD operations):
|
|
141
|
+
- Routes/flows to cover: [list routes and user scenarios]
|
|
142
|
+
- If no E2E needed, write: "E2E: not applicable (no user-facing flow changes)"
|
|
143
|
+
|
|
144
|
+
**VRT tests** (REQUIRED for any task that adds new pages or significantly changes page layout/visual appearance):
|
|
145
|
+
- Pages/views to screenshot: [list routes with viewport matrix]
|
|
146
|
+
- If no VRT needed, write: "VRT: not applicable (no page-level visual changes)"
|
|
147
|
+
|
|
148
|
+
Be explicit about every layer — omitting a layer means no tests for it.
|
|
128
149
|
|
|
129
150
|
### API CONTRACT FEEDBACK (optional, fullstack tasks only)
|
|
130
151
|
If you received an API contract from the backend architect and it is impractical for the UI (too many round trips, missing pagination, wrong data shape for components, missing fields needed for display), list the specific issues and suggest changes. The orchestrator will send this feedback to the backend architect for revision.
|
|
@@ -136,6 +157,7 @@ Only include this section if splitting is genuinely needed — do NOT split task
|
|
|
136
157
|
|
|
137
158
|
### VERIFICATION
|
|
138
159
|
How to verify the implementation is correct (build, visual check, test scenarios).
|
|
160
|
+
If the task adds or changes page-level UI, include: "Update VRT baselines if visual output changed intentionally." This signals to the orchestrator that STEP 4 should include VRT baseline updates.
|
|
139
161
|
|
|
140
162
|
---
|
|
141
163
|
|
|
@@ -69,9 +69,20 @@ After generation, the architect's plan specifies what code to write IN the gener
|
|
|
69
69
|
- Each component = separate `.ts`, `.html`, `.scss` files
|
|
70
70
|
|
|
71
71
|
**Feature vs Dumb components (STRICT separation):**
|
|
72
|
-
- **Dumb (ui/)**: Pure presentational. Input signals + output emitters + content projection. No
|
|
73
|
-
- **
|
|
74
|
-
-
|
|
72
|
+
- **Dumb (ui/)**: Pure presentational. Input signals + output emitters + content projection. No business services, no router, no HTTP, no data fetching. Fully reusable.
|
|
73
|
+
- **UI infrastructure `inject()` is allowed**: `Overlay`, `ElementRef`, `Renderer2`, `ViewContainerRef`, `AnimationBuilder`, `DestroyRef`, CDK primitives — these are UI plumbing, not business logic. The component stays presentational.
|
|
74
|
+
- **NOT allowed**: data services, `HttpClient`, `Router`, `ActivatedRoute`, stores, any service that fetches/mutates application state.
|
|
75
|
+
- **Feature (feature/ or pages/)**: Smart orchestrators. Inject services, manage state, coordinate dumb components via composition and `<ng-content>`. NOT reusable — tied to a specific use case.
|
|
76
|
+
- **NO visual styling**: no colors, backgrounds, borders, shadows, typography, padding on content areas, animations. All visual styling belongs in dumb components.
|
|
77
|
+
- **Layout orchestration is OK**: `:host { display: block/grid/flex }`, `grid-template-areas`, `grid-template-columns`, `gap` are acceptable for arranging child dumb components — this is layout plumbing, not visual design.
|
|
78
|
+
- When a layout pattern is **reused across multiple routes**, extract it to a dumb layout shell (e.g., `lib-dashboard-layout`). Single-use page layout stays in the feature component.
|
|
79
|
+
- Rule: if a component injects **business services** (data, state, API, router), it belongs in `feature/` or `pages/`, not `ui/`. UI infrastructure injection (`Overlay`, `ElementRef`, CDK) is fine in `ui/`.
|
|
80
|
+
|
|
81
|
+
**Anti-patterns:**
|
|
82
|
+
- Dumb component with 8+ inputs → restructure via compound component pattern, `<ng-content>`, or hierarchical DI (`provide`/`inject`)
|
|
83
|
+
- Feature component coordinating 10+ child components → extract sub-features
|
|
84
|
+
- Single-use layout shell component → unnecessary indirection, keep layout in the feature component
|
|
85
|
+
- Prop drilling through 3+ levels → use content projection instead of passing inputs down
|
|
75
86
|
|
|
76
87
|
**Content projection (composite pattern):**
|
|
77
88
|
- Prefer `<ng-content>` and `<ng-content select="...">` over deep @Input trees
|
|
@@ -299,9 +310,9 @@ npx themecraft generate # generates type-safe SCSS from tokens.json
|
|
|
299
310
|
4. `@themecraft/core/themes` → `variables()` mixin emits CSS custom properties at `:root`
|
|
300
311
|
5. `@themecraft/angular` → `provideTheme()` for runtime switching, `provideThemeServer()` for SSR (no FOUC)
|
|
301
312
|
|
|
302
|
-
**Component SCSS** — import via package name, NEVER use relative paths, NEVER call `color-var()`/`size-var()` directly:
|
|
313
|
+
**Component SCSS** — import via workspace package name, NEVER use relative paths or `generated/` paths, NEVER call `color-var()`/`size-var()` directly:
|
|
303
314
|
```scss
|
|
304
|
-
// Import via package name (
|
|
315
|
+
// Import via workspace package name (linked in package.json)
|
|
305
316
|
// SCSS @use automatically uses the last path segment as namespace — no `as` needed
|
|
306
317
|
@use 'theme/colors';
|
|
307
318
|
@use 'theme/sizes';
|
|
@@ -318,56 +329,48 @@ npx themecraft generate # generates type-safe SCSS from tokens.json
|
|
|
318
329
|
```
|
|
319
330
|
|
|
320
331
|
**SCSS import rules:**
|
|
321
|
-
- ALWAYS import via package
|
|
322
|
-
- NEVER use relative paths (`../../theme/src
|
|
332
|
+
- ALWAYS import via workspace package name (e.g. `@use 'theme/colors'` or `@use '@[scope]/theme/colors'`)
|
|
333
|
+
- NEVER use relative paths (`../../theme/src/...`) — they break on refactoring and are unreadable
|
|
334
|
+
- NEVER use `generated/` paths (`@use 'generated/theme/colors'`) — the package link abstracts this away
|
|
323
335
|
- NEVER use redundant `as` aliases that match the last path segment (`@use 'theme/colors' as colors` → just `@use 'theme/colors'`). Only use `as` when you need a different name.
|
|
324
336
|
|
|
325
|
-
**Making theme resolvable by package name (
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
"stylePreprocessorOptions": {
|
|
344
|
-
"includePaths": ["libs/shared/ui/theme/src"]
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
```
|
|
350
|
-
|
|
351
|
-
**How it works:** `npx themecraft generate` reads `tokens.json` and produces typed SCSS files (`generated/theme/colors.scss`, `sizes.scss`, `typography.scss`) that wrap `color-var()`/`size-var()` internally. Components consume these generated variables — never the raw accessor functions.
|
|
337
|
+
**Making theme resolvable by package name (MANDATORY):**
|
|
338
|
+
|
|
339
|
+
Add the theme library as a workspace dependency in `package.json` so SCSS resolves `@use 'theme/...'` from `node_modules`:
|
|
340
|
+
```jsonc
|
|
341
|
+
// package.json (root or consuming app)
|
|
342
|
+
{
|
|
343
|
+
"dependencies": {
|
|
344
|
+
"theme": "workspace:./libs/shared/ui/theme" // pnpm/bun
|
|
345
|
+
// or "file:./libs/shared/ui/theme" for npm
|
|
346
|
+
// If repo has a scope: "@[scope]/theme": "workspace:./libs/shared/ui/theme"
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
Then run `<PM> install` to create the link. SCSS `@use 'theme/colors'` (or `@use '@[scope]/theme/colors'` with scope) resolves automatically.
|
|
351
|
+
|
|
352
|
+
**NEVER use `stylePreprocessorOptions.includePaths`** — workspace dependency is the only supported approach. `includePaths` breaks IDE autocomplete, makes imports ambiguous, and doesn't survive refactoring.
|
|
353
|
+
|
|
354
|
+
**How it works:** `npx themecraft generate` runs inside the theme library (`libs/shared/ui/theme/`) and produces typed SCSS files (`src/colors.scss`, `sizes.scss`, `typography.scss`) that wrap `color-var()`/`size-var()` internally. Because the library is linked as a package, `@use 'theme/colors'` resolves to these generated files. Components consume the generated variables — never the raw accessor functions, never `generated/` paths.
|
|
352
355
|
|
|
353
356
|
**Token source (pick one):**
|
|
354
357
|
- **With Figma:** `npx themecraft figma-sync` pulls design variables into `tokens.json`, then `npx themecraft generate`
|
|
355
358
|
- **Without Figma:** `npx themecraft init` creates `tokens.json` scaffold → manually define token names (color groups, size groups, typography levels) → `npx themecraft generate`
|
|
356
359
|
|
|
357
|
-
Either way, the result is the same: typed SCSS files
|
|
360
|
+
Either way, the result is the same: typed SCSS files inside the theme library, importable via `@use 'theme/colors'`.
|
|
358
361
|
|
|
359
362
|
**Theme definition** (`themes/light.scss`):
|
|
360
363
|
```scss
|
|
361
364
|
@use '@themecraft/core/theming' as theming;
|
|
362
|
-
@use '@themecraft/core/themes' as themes
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
)
|
|
365
|
+
@use '@themecraft/core/themes' as themes with (
|
|
366
|
+
$themes: (
|
|
367
|
+
light: theming.define-light-theme((
|
|
368
|
+
color: theming.define-color-scheme(( surface: #ffffff, on-surface: #1a1a1a, primary: #0066cc )),
|
|
369
|
+
size: theming.define-sizes(( card-padding: theming.size(12px, 16px, 24px) )),
|
|
370
|
+
typography: ( body-primary: theming.define-typography-level(16px, 1.5, 400, 'Inter, sans-serif') ),
|
|
371
|
+
breakpoint: ( medium: 768px, large: 1200px ),
|
|
372
|
+
))
|
|
373
|
+
)
|
|
371
374
|
);
|
|
372
375
|
|
|
373
376
|
@include themes.variables();
|
|
@@ -460,6 +463,38 @@ describe('CardBadge', () => {
|
|
|
460
463
|
});
|
|
461
464
|
```
|
|
462
465
|
|
|
466
|
+
## Playwright VRT Configuration
|
|
467
|
+
|
|
468
|
+
Add to `playwright.config.ts` in the E2E project (`apps/[APP]-e2e/` or `e2e/`):
|
|
469
|
+
```typescript
|
|
470
|
+
import { defineConfig } from '@playwright/test';
|
|
471
|
+
|
|
472
|
+
export default defineConfig({
|
|
473
|
+
testDir: './src',
|
|
474
|
+
testMatch: ['**/*.spec.ts', '**/*.vrt.spec.ts'],
|
|
475
|
+
fullyParallel: true,
|
|
476
|
+
retries: process.env.CI ? 2 : 0,
|
|
477
|
+
use: {
|
|
478
|
+
baseURL: 'http://localhost:4200',
|
|
479
|
+
trace: 'on-first-retry',
|
|
480
|
+
},
|
|
481
|
+
expect: {
|
|
482
|
+
toHaveScreenshot: {
|
|
483
|
+
maxDiffPixelRatio: 0.01,
|
|
484
|
+
animations: 'disabled',
|
|
485
|
+
},
|
|
486
|
+
},
|
|
487
|
+
webServer: {
|
|
488
|
+
command: 'nx serve [APP]',
|
|
489
|
+
url: 'http://localhost:4200',
|
|
490
|
+
reuseExistingServer: !process.env.CI,
|
|
491
|
+
timeout: 120_000,
|
|
492
|
+
},
|
|
493
|
+
});
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
In CI, use the Playwright Docker image for consistent rendering: `mcr.microsoft.com/playwright:v[version]-jammy`.
|
|
497
|
+
|
|
463
498
|
## Angular Review Checklist
|
|
464
499
|
|
|
465
500
|
When reviewing Angular code, check:
|
|
@@ -467,7 +502,7 @@ When reviewing Angular code, check:
|
|
|
467
502
|
- Forms: signal forms API used (no legacy FormControl/FormGroup/FormBuilder)
|
|
468
503
|
- Component file organization: each component in its own subdirectory (not flat in lib/)
|
|
469
504
|
- ZERO method calls in templates — all derived state in computed()
|
|
470
|
-
- Feature vs dumb separation — no
|
|
505
|
+
- Feature vs dumb separation — no business service injection in ui/ components (UI infrastructure like Overlay, ElementRef, CDK is OK), no visual styling in feature/ components (layout orchestration via `:host { display: grid/flex }` + `gap` + `grid-template` is OK)
|
|
471
506
|
- Content projection (composite pattern) over deep @Input trees
|
|
472
507
|
- Behavior extracted to directives (not duplicated across components)
|
|
473
508
|
- @for has track expression (by unique id, not index)
|
|
@@ -478,8 +513,9 @@ When reviewing Angular code, check:
|
|
|
478
513
|
- Resource disposal (ImageBitmap.close(), stream.stop(), BroadcastChannel.close())
|
|
479
514
|
- No memory leaks in effects or event listeners
|
|
480
515
|
- Theme compliance: generated @themecraft SCSS variables — no hardcoded colors/sizes, no direct `color-var()`/`size-var()` calls
|
|
481
|
-
- SCSS imports: via package name (no relative paths), no redundant `as` aliases
|
|
516
|
+
- SCSS imports: via workspace package name (no relative paths, no `generated/` paths), no redundant `as` aliases
|
|
482
517
|
- Lazy loading via loadComponent/loadChildren, no eager page imports
|
|
483
518
|
- @defer for heavy below-fold components
|
|
484
519
|
- Naming: PascalCase components, lib- selectors, kebab-case files
|
|
485
|
-
- i18n: ALL user-facing text has `i18n` attribute (templates) or `$localize` (TS) — no bare text
|
|
520
|
+
- i18n: ALL user-facing text has `i18n` attribute (templates) or `$localize` (TS) — no bare text
|
|
521
|
+
- VRT: new pages/routes have corresponding `*.vrt.spec.ts` with 3-viewport coverage (375px, 768px, 1280px), baseline snapshots committed
|
|
@@ -54,9 +54,20 @@ src/
|
|
|
54
54
|
- One component per file, named after its default/named export
|
|
55
55
|
|
|
56
56
|
**Container vs Presentational components (STRICT separation):**
|
|
57
|
-
- **Presentational (ui/)**: Pure rendering. Props in, JSX out. No
|
|
57
|
+
- **Presentational (ui/)**: Pure rendering. Props in, JSX out. No data fetching, no application state hooks, no direct service calls. Fully reusable and trivially testable.
|
|
58
|
+
- **UI-infrastructure hooks are allowed**: `useRef` for DOM measurement, `useState` for local UI state (open/closed, animation phase), `useCallback` for stable callbacks. The component stays presentational.
|
|
59
|
+
- **NOT allowed**: data-fetching hooks, application state hooks, `useEffect` for side effects, direct API calls.
|
|
58
60
|
- **Container (components/ or pages/)**: Smart orchestrators. Use hooks, fetch data, manage state, coordinate presentational components. Tied to a specific use case.
|
|
59
|
-
-
|
|
61
|
+
- **NO visual styling**: no colors, backgrounds, borders, shadows, typography, padding on content areas, animations. All visual styling belongs in presentational components.
|
|
62
|
+
- **Layout orchestration is OK**: arranging child components via grid/flex (`display: grid/flex`, `grid-template-areas`, `gap`) is acceptable, especially for page-level route components. This is layout plumbing, not visual design.
|
|
63
|
+
- When a layout pattern is **reused across multiple routes**, extract it to a presentational layout component. Single-use page layout stays in the container.
|
|
64
|
+
- Rule: if a component calls `useEffect` for data fetching, accesses application state context, or calls APIs, it belongs in `components/` or `pages/`, not `ui/`
|
|
65
|
+
|
|
66
|
+
**Anti-patterns:**
|
|
67
|
+
- Presentational component with 8+ props → restructure via compound component pattern, `children`, or context
|
|
68
|
+
- Container coordinating 10+ child components → extract sub-containers
|
|
69
|
+
- Single-use layout wrapper → unnecessary indirection, keep layout in the container
|
|
70
|
+
- Prop drilling through 3+ levels → use composition (`children`/render props) or context
|
|
60
71
|
|
|
61
72
|
**Compound components (composition over prop drilling):**
|
|
62
73
|
- Prefer `children` and render slots over deep prop trees
|
|
@@ -190,7 +201,7 @@ npx themecraft generate # generates type-safe SCSS from tokens.json
|
|
|
190
201
|
4. `@themecraft/core/themes` → `variables()` mixin emits CSS custom properties at `:root`
|
|
191
202
|
5. `ThemeManager` (from `@themecraft/core/runtime`) — runtime theme switching, storage persistence, FOUC prevention
|
|
192
203
|
|
|
193
|
-
**Component SCSS** (`.module.scss`) — import via package name, NEVER use relative paths, NEVER call `color-var()`/`size-var()` directly:
|
|
204
|
+
**Component SCSS** (`.module.scss`) — import via workspace package name, NEVER use relative paths or `generated/` paths, NEVER call `color-var()`/`size-var()` directly:
|
|
194
205
|
```scss
|
|
195
206
|
// Import via package name — SCSS @use uses last path segment as namespace automatically
|
|
196
207
|
@use 'theme/colors';
|
|
@@ -208,31 +219,35 @@ npx themecraft generate # generates type-safe SCSS from tokens.json
|
|
|
208
219
|
```
|
|
209
220
|
|
|
210
221
|
**SCSS import rules:**
|
|
211
|
-
- ALWAYS import via package
|
|
212
|
-
- NEVER use relative paths (`../../theme/src
|
|
222
|
+
- ALWAYS import via workspace package name (e.g. `@use 'theme/colors'` or `@use '@[scope]/theme/colors'`)
|
|
223
|
+
- NEVER use relative paths (`../../theme/src/...`) — they break on refactoring and are unreadable
|
|
224
|
+
- NEVER use `generated/` paths (`@use 'generated/theme/colors'`) — the package link abstracts this away
|
|
213
225
|
- NEVER use redundant `as` aliases that match the last path segment (`@use 'theme/colors' as colors` → just `@use 'theme/colors'`). Only use `as` when you need a different name.
|
|
214
226
|
|
|
215
|
-
**Making theme resolvable by package name (
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
227
|
+
**Making theme resolvable by package name (MANDATORY):**
|
|
228
|
+
|
|
229
|
+
Add the theme library as a workspace dependency in `package.json` so SCSS resolves `@use 'theme/...'` from `node_modules`:
|
|
230
|
+
```jsonc
|
|
231
|
+
// package.json (root or consuming app)
|
|
232
|
+
{
|
|
233
|
+
"dependencies": {
|
|
234
|
+
"theme": "workspace:./libs/shared/ui/theme" // pnpm/bun
|
|
235
|
+
// or "file:./libs/shared/ui/theme" for npm
|
|
236
|
+
// If repo has a scope: "@[scope]/theme": "workspace:./libs/shared/ui/theme"
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
Then run `<PM> install` to create the link. SCSS `@use 'theme/colors'` (or `@use '@[scope]/theme/colors'` with scope) resolves automatically.
|
|
241
|
+
|
|
242
|
+
**NEVER use `sassOptions.includeDirs` or `css.preprocessorOptions.scss.includePaths`** — workspace dependency is the only supported approach.
|
|
243
|
+
|
|
244
|
+
**How it works:** `npx themecraft generate` runs inside the theme library and produces typed SCSS files (`src/colors.scss`, `sizes.scss`, `typography.scss`) that wrap `color-var()`/`size-var()` internally. Because the library is linked as a package, `@use 'theme/colors'` resolves to these generated files. Components consume the generated variables — never the raw accessor functions, never `generated/` paths.
|
|
230
245
|
|
|
231
246
|
**Token source (pick one):**
|
|
232
247
|
- **With Figma:** `npx themecraft figma-sync` pulls design variables into `tokens.json`, then `npx themecraft generate`
|
|
233
248
|
- **Without Figma:** `npx themecraft init` creates `tokens.json` scaffold → manually define token names (color groups, size groups, typography levels) → `npx themecraft generate`
|
|
234
249
|
|
|
235
|
-
Either way, the result is the same: typed SCSS files
|
|
250
|
+
Either way, the result is the same: typed SCSS files inside the theme library, importable via `@use 'theme/colors'`.
|
|
236
251
|
|
|
237
252
|
**Runtime setup** (in layout or `_app.tsx`):
|
|
238
253
|
```typescript
|
|
@@ -321,12 +336,44 @@ describe('CardBadge', () => {
|
|
|
321
336
|
- Test behavior, not implementation — don't assert on state variables or hook internals
|
|
322
337
|
- Mock server state with MSW (Mock Service Worker) or TanStack Query's test utilities
|
|
323
338
|
|
|
339
|
+
## Playwright VRT Configuration
|
|
340
|
+
|
|
341
|
+
Add to `playwright.config.ts` in the E2E project:
|
|
342
|
+
```typescript
|
|
343
|
+
import { defineConfig } from '@playwright/test';
|
|
344
|
+
|
|
345
|
+
export default defineConfig({
|
|
346
|
+
testDir: './src',
|
|
347
|
+
testMatch: ['**/*.spec.ts', '**/*.vrt.spec.ts'],
|
|
348
|
+
fullyParallel: true,
|
|
349
|
+
retries: process.env.CI ? 2 : 0,
|
|
350
|
+
use: {
|
|
351
|
+
baseURL: 'http://localhost:3000',
|
|
352
|
+
trace: 'on-first-retry',
|
|
353
|
+
},
|
|
354
|
+
expect: {
|
|
355
|
+
toHaveScreenshot: {
|
|
356
|
+
maxDiffPixelRatio: 0.01,
|
|
357
|
+
animations: 'disabled',
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
webServer: {
|
|
361
|
+
command: 'nx serve [APP]',
|
|
362
|
+
url: 'http://localhost:3000',
|
|
363
|
+
reuseExistingServer: !process.env.CI,
|
|
364
|
+
timeout: 120_000,
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
Adjust `baseURL`/`url` to match the dev server port (Next.js: 3000, Vite: 5173). In CI, use the Playwright Docker image for consistent rendering.
|
|
370
|
+
|
|
324
371
|
## React Review Checklist
|
|
325
372
|
|
|
326
373
|
When reviewing React code, check:
|
|
327
374
|
- Hooks rules: no conditional hooks, no hooks inside loops or nested functions
|
|
328
375
|
- `useMemo`/`useCallback` usage: present where needed (expensive computations, stable references for children), absent where unnecessary (premature optimization)
|
|
329
|
-
- Container vs presentational separation — no data fetching or
|
|
376
|
+
- Container vs presentational separation — no data fetching or app state in ui/ components (UI hooks like useRef/useState for UI state are OK), no visual styling in container components (layout orchestration via grid/flex is OK)
|
|
330
377
|
- Composition via children/compound components over deep prop drilling
|
|
331
378
|
- Custom hooks extracted for shared behavior (not duplicated across components)
|
|
332
379
|
- `key` prop on list items uses unique stable id, never array index
|
|
@@ -337,10 +384,11 @@ When reviewing React code, check:
|
|
|
337
384
|
- Server/client separation: Flag WARN if a module mixes server and browser logic without clear boundaries
|
|
338
385
|
- No memory leaks in effects, event listeners, or subscriptions
|
|
339
386
|
- Theme compliance: generated @themecraft SCSS variables — no hardcoded colors/sizes, no direct `color-var()`/`size-var()` calls
|
|
340
|
-
- SCSS imports: via package name (no relative paths), no redundant `as` aliases
|
|
387
|
+
- SCSS imports: via workspace package name (no relative paths, no `generated/` paths), no redundant `as` aliases
|
|
341
388
|
- Code splitting: `React.lazy` + `Suspense` for heavy routes/components, no eager imports of page-level components
|
|
342
389
|
- Dynamic imports for heavy third-party libraries
|
|
343
390
|
- Naming: PascalCase components, camelCase hooks, kebab-case files
|
|
344
391
|
- i18n: ALL user-facing text goes through `t()` — no bare strings in JSX or UI logic
|
|
345
392
|
- No unstable nested component definitions (components defined inside render)
|
|
346
393
|
- `React.memo` only on components with measured re-render cost — not as a default
|
|
394
|
+
- VRT: new pages/routes have corresponding `*.vrt.spec.ts` with 3-viewport coverage (375px, 768px, 1280px), baseline snapshots committed
|
|
@@ -48,8 +48,17 @@ src/
|
|
|
48
48
|
**Feature vs Dumb components (STRICT separation):**
|
|
49
49
|
- **Dumb (ui/)**: Pure presentational. `$props()` for inputs, callback props for events, slots/snippets for composition. No data fetching, no navigation, no global state access. Fully reusable.
|
|
50
50
|
- **Feature (features/ or routes/)**: Smart orchestrators. Access load data, manage state, call APIs, coordinate dumb components. NOT reusable — tied to a specific use case.
|
|
51
|
+
- **NO visual styling**: no colors, backgrounds, borders, shadows, typography, padding on content areas, animations. All visual styling belongs in dumb components.
|
|
52
|
+
- **Layout orchestration is OK**: arranging child components via grid/flex (`display: grid/flex`, `grid-template-areas`, `gap`) is acceptable, especially for page-level route components.
|
|
53
|
+
- When a layout pattern is **reused across multiple routes**, extract it to a dumb layout component (e.g., `DashboardLayout.svelte`). Single-use page layout stays in the feature component.
|
|
51
54
|
- Rule: if a component fetches data or accesses global state, it belongs in `features/` or `routes/`, not `ui/`
|
|
52
55
|
|
|
56
|
+
**Anti-patterns:**
|
|
57
|
+
- Dumb component with 8+ props → restructure via compound component pattern, `<slot>`s, or context
|
|
58
|
+
- Feature component coordinating 10+ child components → extract sub-features
|
|
59
|
+
- Single-use layout shell component → unnecessary indirection, keep layout in the feature component
|
|
60
|
+
- Prop drilling through 3+ levels → use slots or Svelte context
|
|
61
|
+
|
|
53
62
|
**Composition patterns:**
|
|
54
63
|
- Prefer slots (`<slot>`) and named slots (`<slot name="header">`) over deep prop drilling
|
|
55
64
|
- Use snippet blocks (`{#snippet}`) for typed, reusable template fragments within a component
|
|
@@ -190,7 +199,7 @@ npx themecraft generate # generates type-safe SCSS from tokens.json
|
|
|
190
199
|
4. `@themecraft/core/themes` → `variables()` mixin emits CSS custom properties at `:root`
|
|
191
200
|
5. `ThemeManager` (from `@themecraft/core/runtime`) — runtime switching, storage, FOUC prevention
|
|
192
201
|
|
|
193
|
-
**Component SCSS** — import via package name, NEVER use relative paths, NEVER call `color-var()`/`size-var()` directly:
|
|
202
|
+
**Component SCSS** — import via workspace package name, NEVER use relative paths or `generated/` paths, NEVER call `color-var()`/`size-var()` directly:
|
|
194
203
|
```svelte
|
|
195
204
|
<style lang="scss">
|
|
196
205
|
// Import via package name — SCSS @use uses last path segment as namespace automatically
|
|
@@ -210,31 +219,35 @@ npx themecraft generate # generates type-safe SCSS from tokens.json
|
|
|
210
219
|
```
|
|
211
220
|
|
|
212
221
|
**SCSS import rules:**
|
|
213
|
-
- ALWAYS import via package
|
|
214
|
-
- NEVER use relative paths (`../../theme/src
|
|
222
|
+
- ALWAYS import via workspace package name (e.g. `@use 'theme/colors'` or `@use '@[scope]/theme/colors'`)
|
|
223
|
+
- NEVER use relative paths (`../../theme/src/...`) — they break on refactoring and are unreadable
|
|
224
|
+
- NEVER use `generated/` paths (`@use 'generated/theme/colors'`) — the package link abstracts this away
|
|
215
225
|
- NEVER use redundant `as` aliases that match the last path segment (`@use 'theme/colors' as colors` → just `@use 'theme/colors'`). Only use `as` when you need a different name.
|
|
216
226
|
|
|
217
|
-
**Making theme resolvable by package name (
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
227
|
+
**Making theme resolvable by package name (MANDATORY):**
|
|
228
|
+
|
|
229
|
+
Add the theme library as a workspace dependency in `package.json` so SCSS resolves `@use 'theme/...'` from `node_modules`:
|
|
230
|
+
```jsonc
|
|
231
|
+
// package.json (root or consuming app)
|
|
232
|
+
{
|
|
233
|
+
"dependencies": {
|
|
234
|
+
"theme": "workspace:./libs/shared/ui/theme" // pnpm/bun
|
|
235
|
+
// or "file:./libs/shared/ui/theme" for npm
|
|
236
|
+
// If repo has a scope: "@[scope]/theme": "workspace:./libs/shared/ui/theme"
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
Then run `<PM> install` to create the link. SCSS `@use 'theme/colors'` (or `@use '@[scope]/theme/colors'` with scope) resolves automatically.
|
|
241
|
+
|
|
242
|
+
**NEVER use `css.preprocessorOptions.scss.includePaths`** — workspace dependency is the only supported approach.
|
|
243
|
+
|
|
244
|
+
**How it works:** `npx themecraft generate` runs inside the theme library and produces typed SCSS files (`src/colors.scss`, `sizes.scss`, `typography.scss`) that wrap `color-var()`/`size-var()` internally. Because the library is linked as a package, `@use 'theme/colors'` resolves to these generated files. Components consume the generated variables — never the raw accessor functions, never `generated/` paths.
|
|
232
245
|
|
|
233
246
|
**Token source (pick one):**
|
|
234
247
|
- **With Figma:** `npx themecraft figma-sync` pulls design variables into `tokens.json`, then `npx themecraft generate`
|
|
235
248
|
- **Without Figma:** `npx themecraft init` creates `tokens.json` scaffold → manually define token names (color groups, size groups, typography levels) → `npx themecraft generate`
|
|
236
249
|
|
|
237
|
-
Either way, the result is the same: typed SCSS files
|
|
250
|
+
Either way, the result is the same: typed SCSS files inside the theme library, importable via `@use 'theme/colors'`.
|
|
238
251
|
|
|
239
252
|
**Runtime setup** (in `+layout.svelte` or `hooks.server.ts`):
|
|
240
253
|
```typescript
|
|
@@ -324,6 +337,38 @@ describe('CardBadge', () => {
|
|
|
324
337
|
});
|
|
325
338
|
```
|
|
326
339
|
|
|
340
|
+
## Playwright VRT Configuration
|
|
341
|
+
|
|
342
|
+
Add to `playwright.config.ts` in the E2E project:
|
|
343
|
+
```typescript
|
|
344
|
+
import { defineConfig } from '@playwright/test';
|
|
345
|
+
|
|
346
|
+
export default defineConfig({
|
|
347
|
+
testDir: './src',
|
|
348
|
+
testMatch: ['**/*.spec.ts', '**/*.vrt.spec.ts'],
|
|
349
|
+
fullyParallel: true,
|
|
350
|
+
retries: process.env.CI ? 2 : 0,
|
|
351
|
+
use: {
|
|
352
|
+
baseURL: 'http://localhost:5173',
|
|
353
|
+
trace: 'on-first-retry',
|
|
354
|
+
},
|
|
355
|
+
expect: {
|
|
356
|
+
toHaveScreenshot: {
|
|
357
|
+
maxDiffPixelRatio: 0.01,
|
|
358
|
+
animations: 'disabled',
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
webServer: {
|
|
362
|
+
command: 'nx serve [APP]',
|
|
363
|
+
url: 'http://localhost:5173',
|
|
364
|
+
reuseExistingServer: !process.env.CI,
|
|
365
|
+
timeout: 120_000,
|
|
366
|
+
},
|
|
367
|
+
});
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
In CI, use the Playwright Docker image for consistent rendering: `mcr.microsoft.com/playwright:v[version]-jammy`.
|
|
371
|
+
|
|
327
372
|
## Svelte Review Checklist
|
|
328
373
|
|
|
329
374
|
When reviewing Svelte code, check:
|
|
@@ -332,7 +377,7 @@ When reviewing Svelte code, check:
|
|
|
332
377
|
- `$derived()` for all computed values — no recalculation in templates or reactive statements
|
|
333
378
|
- `$state.raw()` for large data that doesn't need deep reactivity
|
|
334
379
|
- `{#each}` has key expression `(item.id)` — NEVER keyed by index
|
|
335
|
-
- Feature vs dumb separation — no data fetching or global state in `ui/` components
|
|
380
|
+
- Feature vs dumb separation — no data fetching or global state in `ui/` components, no visual styling in feature/ components (layout orchestration via grid/flex is OK)
|
|
336
381
|
- Composition via slots/snippets over deep prop drilling
|
|
337
382
|
- Reusable DOM behavior extracted to actions (`use:`) — not duplicated across components
|
|
338
383
|
- SSR safety: no `window`/`document`/`navigator` in server-reachable code, use `browser` guard or `onMount`
|
|
@@ -340,10 +385,11 @@ When reviewing Svelte code, check:
|
|
|
340
385
|
- `$lib/` imports — no deep relative paths (`../../../`)
|
|
341
386
|
- `$lib/server/` for server-only modules — enforced by SvelteKit
|
|
342
387
|
- Theme compliance: generated @themecraft SCSS variables — no hardcoded colors/sizes, no direct `color-var()`/`size-var()` calls
|
|
343
|
-
- SCSS imports: via package name (no relative paths), no redundant `as` aliases
|
|
388
|
+
- SCSS imports: via workspace package name (no relative paths, no `generated/` paths), no redundant `as` aliases
|
|
344
389
|
- Lazy loading: routes auto-split, dynamic `import()` for heavy non-route components
|
|
345
390
|
- i18n: ALL user-facing text goes through translation functions — no bare strings in templates
|
|
346
391
|
- Accessibility: all Svelte compiler a11y warnings resolved (not suppressed)
|
|
347
392
|
- No `{@html}` without sanitization
|
|
348
393
|
- Callback props for outputs (not `createEventDispatcher` — that's Svelte 4)
|
|
349
394
|
- Resource cleanup in `onDestroy` or `$effect` return for manual subscriptions
|
|
395
|
+
- VRT: new pages/routes have corresponding `*.vrt.spec.ts` with 3-viewport coverage (375px, 768px, 1280px), baseline snapshots committed
|
|
@@ -58,9 +58,19 @@ src/
|
|
|
58
58
|
|
|
59
59
|
**Feature vs Dumb components (STRICT separation):**
|
|
60
60
|
- **Dumb (ui/components/)**: Pure presentational. `defineProps` + `defineEmits` + slots. No store access, no router, no HTTP, no side effects. Fully reusable.
|
|
61
|
+
- **Dumb UI-infrastructure usage is allowed**: `useTemplateRef`, `nextTick`, `useSlots` — these are UI plumbing, not business logic.
|
|
61
62
|
- **Feature (features/[domain]/components/ or pages/)**: Smart orchestrators. Use composables, access stores, call APIs, coordinate dumb components. NOT reusable — tied to a specific use case.
|
|
63
|
+
- **NO visual styling**: no colors, backgrounds, borders, shadows, typography, padding on content areas, animations. All visual styling belongs in dumb components.
|
|
64
|
+
- **Layout orchestration is OK**: arranging child components via grid/flex (`display: grid/flex`, `grid-template-areas`, `gap`) is acceptable, especially for page-level route components.
|
|
65
|
+
- When a layout pattern is **reused across multiple routes**, extract it to a dumb layout component (e.g., `DashboardLayout.vue`). Single-use page layout stays in the feature component.
|
|
62
66
|
- Rule: if a component imports a store or calls an API, it belongs in `features/` or `pages/`, not `ui/`
|
|
63
67
|
|
|
68
|
+
**Anti-patterns:**
|
|
69
|
+
- Dumb component with 8+ props → restructure via compound component pattern, `<slot>`s, or `provide`/`inject`
|
|
70
|
+
- Feature component coordinating 10+ child components → extract sub-features
|
|
71
|
+
- Single-use layout shell component → unnecessary indirection, keep layout in the feature component
|
|
72
|
+
- Prop drilling through 3+ levels → use slots or `provide`/`inject`
|
|
73
|
+
|
|
64
74
|
**Slots (composite pattern):**
|
|
65
75
|
- Prefer slots over deep prop drilling for component composition
|
|
66
76
|
- Default slot for primary content: `<slot />`
|
|
@@ -217,7 +227,7 @@ npx themecraft generate # generates type-safe SCSS from tokens.json
|
|
|
217
227
|
4. `@themecraft/core/themes` → `variables()` mixin emits CSS custom properties at `:root`
|
|
218
228
|
5. `ThemeManager` (from `@themecraft/core/runtime`) — runtime switching, storage, FOUC prevention
|
|
219
229
|
|
|
220
|
-
**Component SCSS** — import via package name, NEVER use relative paths, NEVER call `color-var()`/`size-var()` directly:
|
|
230
|
+
**Component SCSS** — import via workspace package name, NEVER use relative paths or `generated/` paths, NEVER call `color-var()`/`size-var()` directly:
|
|
221
231
|
```vue
|
|
222
232
|
<style scoped lang="scss">
|
|
223
233
|
// Import via package name — SCSS @use uses last path segment as namespace automatically
|
|
@@ -237,31 +247,35 @@ npx themecraft generate # generates type-safe SCSS from tokens.json
|
|
|
237
247
|
```
|
|
238
248
|
|
|
239
249
|
**SCSS import rules:**
|
|
240
|
-
- ALWAYS import via package
|
|
241
|
-
- NEVER use relative paths (`../../theme/src
|
|
250
|
+
- ALWAYS import via workspace package name (e.g. `@use 'theme/colors'` or `@use '@[scope]/theme/colors'`)
|
|
251
|
+
- NEVER use relative paths (`../../theme/src/...`) — they break on refactoring and are unreadable
|
|
252
|
+
- NEVER use `generated/` paths (`@use 'generated/theme/colors'`) — the package link abstracts this away
|
|
242
253
|
- NEVER use redundant `as` aliases that match the last path segment (`@use 'theme/colors' as colors` → just `@use 'theme/colors'`). Only use `as` when you need a different name.
|
|
243
254
|
|
|
244
|
-
**Making theme resolvable by package name (
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
255
|
+
**Making theme resolvable by package name (MANDATORY):**
|
|
256
|
+
|
|
257
|
+
Add the theme library as a workspace dependency in `package.json` so SCSS resolves `@use 'theme/...'` from `node_modules`:
|
|
258
|
+
```jsonc
|
|
259
|
+
// package.json (root or consuming app)
|
|
260
|
+
{
|
|
261
|
+
"dependencies": {
|
|
262
|
+
"theme": "workspace:./libs/shared/ui/theme" // pnpm/bun
|
|
263
|
+
// or "file:./libs/shared/ui/theme" for npm
|
|
264
|
+
// If repo has a scope: "@[scope]/theme": "workspace:./libs/shared/ui/theme"
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
Then run `<PM> install` to create the link. SCSS `@use 'theme/colors'` (or `@use '@[scope]/theme/colors'` with scope) resolves automatically.
|
|
269
|
+
|
|
270
|
+
**NEVER use `css.preprocessorOptions.scss.includePaths`** — workspace dependency is the only supported approach.
|
|
271
|
+
|
|
272
|
+
**How it works:** `npx themecraft generate` runs inside the theme library and produces typed SCSS files (`src/colors.scss`, `sizes.scss`, `typography.scss`) that wrap `color-var()`/`size-var()` internally. Because the library is linked as a package, `@use 'theme/colors'` resolves to these generated files. Components consume the generated variables — never the raw accessor functions, never `generated/` paths.
|
|
259
273
|
|
|
260
274
|
**Token source (pick one):**
|
|
261
275
|
- **With Figma:** `npx themecraft figma-sync` pulls design variables into `tokens.json`, then `npx themecraft generate`
|
|
262
276
|
- **Without Figma:** `npx themecraft init` creates `tokens.json` scaffold → manually define token names (color groups, size groups, typography levels) → `npx themecraft generate`
|
|
263
277
|
|
|
264
|
-
Either way, the result is the same: typed SCSS files
|
|
278
|
+
Either way, the result is the same: typed SCSS files inside the theme library, importable via `@use 'theme/colors'`.
|
|
265
279
|
|
|
266
280
|
**Runtime setup** (in `App.vue` or plugin):
|
|
267
281
|
```typescript
|
|
@@ -348,13 +362,45 @@ describe('UserCard', () => {
|
|
|
348
362
|
|
|
349
363
|
**Pinia stores:** Use `setActivePinia(createPinia())` in `beforeEach`, then test store actions/getters directly.
|
|
350
364
|
|
|
365
|
+
## Playwright VRT Configuration
|
|
366
|
+
|
|
367
|
+
Add to `playwright.config.ts` in the E2E project:
|
|
368
|
+
```typescript
|
|
369
|
+
import { defineConfig } from '@playwright/test';
|
|
370
|
+
|
|
371
|
+
export default defineConfig({
|
|
372
|
+
testDir: './src',
|
|
373
|
+
testMatch: ['**/*.spec.ts', '**/*.vrt.spec.ts'],
|
|
374
|
+
fullyParallel: true,
|
|
375
|
+
retries: process.env.CI ? 2 : 0,
|
|
376
|
+
use: {
|
|
377
|
+
baseURL: 'http://localhost:5173',
|
|
378
|
+
trace: 'on-first-retry',
|
|
379
|
+
},
|
|
380
|
+
expect: {
|
|
381
|
+
toHaveScreenshot: {
|
|
382
|
+
maxDiffPixelRatio: 0.01,
|
|
383
|
+
animations: 'disabled',
|
|
384
|
+
},
|
|
385
|
+
},
|
|
386
|
+
webServer: {
|
|
387
|
+
command: 'nx serve [APP]',
|
|
388
|
+
url: 'http://localhost:5173',
|
|
389
|
+
reuseExistingServer: !process.env.CI,
|
|
390
|
+
timeout: 120_000,
|
|
391
|
+
},
|
|
392
|
+
});
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
Adjust `baseURL`/`url` to match the dev server port (Vite: 5173, Nuxt: 3000). In CI, use the Playwright Docker image for consistent rendering.
|
|
396
|
+
|
|
351
397
|
## Vue Review Checklist
|
|
352
398
|
|
|
353
399
|
When reviewing Vue code, check:
|
|
354
400
|
- Composition API only — no Options API (`data()`, `methods`, `computed` object syntax)
|
|
355
401
|
- `<script setup>` in all SFCs — no `defineComponent()` with `setup()` function
|
|
356
402
|
- ZERO method calls in templates — all derived state in `computed()`
|
|
357
|
-
- Feature vs dumb separation — no store access or API calls in `ui/` components
|
|
403
|
+
- Feature vs dumb separation — no store access or API calls in `ui/` components (UI infrastructure like `useTemplateRef`/`nextTick` is OK), no visual styling in feature/ components (layout orchestration via grid/flex is OK)
|
|
358
404
|
- Slots used for composition over deep prop drilling
|
|
359
405
|
- Composables (`use*`) for shared stateful logic — not mixins, not utility classes
|
|
360
406
|
- Custom directives for shared DOM behavior — not duplicated across components
|
|
@@ -368,10 +414,11 @@ When reviewing Vue code, check:
|
|
|
368
414
|
- SSR/Client separation: Flag WARN if a composable mixes server and browser logic
|
|
369
415
|
- `<style scoped>` on all components — no unscoped styles leaking globally
|
|
370
416
|
- Theme compliance: generated @themecraft SCSS variables — no hardcoded colors/sizes, no direct `color-var()`/`size-var()` calls
|
|
371
|
-
- SCSS imports: via package name (no relative paths), no redundant `as` aliases
|
|
417
|
+
- SCSS imports: via workspace package name (no relative paths, no `generated/` paths), no redundant `as` aliases
|
|
372
418
|
- Lazy loading: `defineAsyncComponent` for heavy components, dynamic imports for routes
|
|
373
419
|
- No `v-html` without sanitization (XSS risk)
|
|
374
420
|
- Props defined with TypeScript types via `defineProps<{ ... }>()` — not runtime-only validation
|
|
375
421
|
- Emits declared via `defineEmits<{ ... }>()` — not untyped string arrays
|
|
376
422
|
- Naming: PascalCase components, `Base` prefix for atoms, `use` prefix for composables
|
|
377
423
|
- i18n: ALL user-facing text goes through `$t()` / `t()` — no bare strings in templates or script
|
|
424
|
+
- VRT: new pages/routes have corresponding `*.vrt.spec.ts` with 3-viewport coverage (375px, 768px, 1280px), baseline snapshots committed
|
|
@@ -281,6 +281,16 @@ Framework-specific best practices, component architecture, state management, SSR
|
|
|
281
281
|
- Coverage aggregated at workspace root under `coverage/` (add to `.gitignore`)
|
|
282
282
|
- Run: `nx test [PROJECT] --coverage` or `nx affected --target=test -- --coverage`
|
|
283
283
|
|
|
284
|
+
### Visual Regression Testing
|
|
285
|
+
|
|
286
|
+
Page-level VRT using Playwright `toHaveScreenshot()` at 3 viewports (375px, 768px, 1280px). Catches unintended visual regressions across pages.
|
|
287
|
+
|
|
288
|
+
- **Files**: `*.vrt.spec.ts` in E2E project (co-located with functional E2E tests)
|
|
289
|
+
- **Baselines**: `*-snapshots/` directories — committed to git
|
|
290
|
+
- **Threshold**: `maxDiffPixelRatio: 0.01` (1%), animations disabled
|
|
291
|
+
- **Update baselines**: `nx e2e [APP]-e2e -- --update-snapshots` after intentional visual changes
|
|
292
|
+
- **CI**: use Playwright Docker image (`mcr.microsoft.com/playwright`) for consistent rendering
|
|
293
|
+
|
|
284
294
|
### Code Quality & Linting
|
|
285
295
|
|
|
286
296
|
Config templates are in `.claude/templates/` — copy to project root during Phase 0 scaffolding:
|