You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: .claude/rules/coding-style.md
+23Lines changed: 23 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,6 +8,15 @@
8
8
-**`any`**: before using `any`, check the value's origin — adding a missing `@types/*` or `devDependency` often provides the correct type.
9
9
-**UI labels**: if a label does not match actual behavior, update it or add an inline comment explaining the intentional mismatch.
10
10
11
+
## TSDoc
12
+
13
+
Add TSDoc comments to every exported function, type, and class. The minimum required fields are `@param` (for non-obvious parameters) and `@returns` (when the return value is not evident from the type). One-liner `/** ... */` is sufficient for simple cases; use multi-line only when behaviour needs explanation.
14
+
15
+
```typescript
16
+
/** Returns the URL slug for a workbook, falling back to the workbook ID. */
let count = $state(untrack(() => initialCount)); // intentional: prop is initial seed only
40
40
```
41
41
42
+
## `$effect` — Store Reading
43
+
44
+
Inside `$effect`, use `$store` syntax, not `get(store)`. `get()` bypasses the signal graph — the effect will not re-run when the store updates:
45
+
46
+
```svelte
47
+
// Bad: get() takes a snapshot; effect won't react to store changes
48
+
$effect(() => {
49
+
const grade = get(myStore).get(key) ?? fallback;
50
+
});
51
+
52
+
// Good: $store subscribes and re-runs the effect on updates
53
+
$effect(() => {
54
+
const grade = $myStore.get(key) ?? fallback;
55
+
});
56
+
```
57
+
58
+
## `$derived` — No Arrow Wrapper
59
+
60
+
Use `$derived(expr)`, not `$derived(() => expr)`. The arrow form makes the derived value a _function_, not a reactive value — dependencies may not be tracked and the template call site is confusing.
61
+
62
+
## `{@const}` Placement
63
+
64
+
`{@const}` must be an **immediate child** of a block statement (`{#if}`, `{#each}`, `{:else}`, `{#snippet}`, etc.). Placing it inside an HTML element is a compile error:
65
+
42
66
## `{#snippet}` Placement
43
67
44
68
Define snippets at the **top level**, outside component tags. Inside a tag = named slot = type error:
@@ -57,6 +81,8 @@ Define snippets at the **top level**, outside component tags. Inside a tag = nam
57
81
Prefer `{#snippet}` when: (1) needs direct `$state` access, (2) pure display only, (3) same-file DRY.
58
82
Promote to component when: independent state/lifecycle needed, exceeds ~30 lines, or reused across files.
59
83
84
+
**Sibling consistency** — when one sibling block warrants snippet extraction, extract its parallel siblings too, even if they are short. A parent template that mixes inline markup with `{@render}` calls is harder to scan than one where every top-level section is a named snippet.
85
+
60
86
## Component Boundaries
61
87
62
88
- One component, one responsibility: don't mix display, state management, and data fetching
Always provide a key expression when the list or its items may change dynamically. This is especially critical when the block contains an inner `{#if}` — without a key, Svelte reuses DOM nodes by position, so filtering can silently bind data to the wrong element:
111
+
112
+
```svelte
113
+
{#each workbooks as workbook (workbook.id)}
114
+
{#if canRead(workbook)}
115
+
<Row {workbook} />
116
+
{/if}
117
+
{/each}
118
+
```
83
119
84
120
Use `{:else}` to render a placeholder when the list is empty — no wrapper conditional needed:
85
121
@@ -91,6 +127,10 @@ Use `{:else}` to render a placeholder when the list is empty — no wrapper cond
91
127
{/each}
92
128
```
93
129
130
+
## Directory Structure: `list/` Subdirectories
131
+
132
+
Consider introducing a `list/` subdirectory (or other domain-scoped subdirectory) when the component count in a directory starts to feel unwieldy — roughly 20 files is a reasonable prompt to reconsider. Below that threshold, flat organization is preferred — subdirectories add navigation cost without proportional benefit.
133
+
94
134
## Eliminate Branching with Records
95
135
96
136
Replace `if`/ternary chains with `Record<EnumType, T>`:
When not all enum keys need an entry, use `Partial<Record<K, V>>` as a **type annotation** — not `satisfies`. `as const satisfies Partial<Record<K, V>>` preserves the narrowed literal type, so indexing with other enum values causes a type error:
148
+
149
+
```typescript
150
+
// NG: satisfies narrows the type — obj[key] errors for keys not in the literal
Copy file name to clipboardExpand all lines: .claude/rules/testing.md
+46-4Lines changed: 46 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -9,6 +9,10 @@ paths:
9
9
10
10
# Testing
11
11
12
+
## Test Titles
13
+
14
+
Write all test titles in English. Use descriptive sentences that state the expected behavior (e.g., `'returns empty array when workbooks is empty'`). Japanese is only acceptable in inline comments or fixture strings that represent real user-facing content.
15
+
12
16
## Test Integrity
13
17
14
18
- Never delete, comment out, or weaken assertions (e.g. `toEqual` → `toBeDefined`) to make tests pass
@@ -26,6 +30,7 @@ paths:
26
30
- Use `toBe(true)` / `toBe(false)` over `toBeTruthy()` / `toBeFalsy()`
27
31
- For DB query tests, assert `orderBy`, `include`, and other significant parameters with `expect.objectContaining` — not just `where`
28
32
- Enum membership: `in` traverses the prototype chain; use `Object.hasOwn(Enum, value)` instead
33
+
-**E2E state transitions**: after an interaction that changes element state (active tab, toggle, selection), assert the _new_ state — not just that the element is visible, which may have been true before the interaction. Assert an active CSS class, `aria-selected`, or similar attribute instead of `toBeVisible()`
29
34
30
35
## Cleanup in Tests
31
36
@@ -45,19 +50,42 @@ try {
45
50
- Use realistic fixture values (real task IDs, grade names) instead of placeholders like `'t1'`
46
51
- Extract shared data into fixture files; inline is fine for single-use cases
47
52
- After `.filter()` on fixtures, verify actual contents — same ID may refer to a different entity after fixture updates
53
+
-**Description ↔ code path alignment**: when a test name describes a specific scenario (e.g. "tie-break"), verify the fixture actually exercises that code path. A test that passes without reaching the branch it claims to cover gives false confidence
48
54
49
55
## Mock Helpers
50
56
51
-
Extract repeated mock patterns into a helper in the test file:
57
+
Extract repeated mock patterns into helpers in the test file. For Prisma service tests, define the return type alias once and use it across all helpers:
Extract `mockFindUnique`, `mockFindMany`, and `mockCount` as the standard trio for service tests that touch a single Prisma model. Add `mockCreate`, `mockTransaction`, and `mockDelete` when those operations are also tested.
79
+
80
+
## Component Vitest Unit Tests
81
+
82
+
Omit Vitest unit tests for a Svelte component when **both** conditions hold:
83
+
84
+
1. The component is template-only (no logic beyond prop bindings and basic conditionals)
85
+
2. The component is covered by E2E tests
86
+
87
+
When a component contains extracted logic (e.g. derived values, event handlers, utility calls), add unit tests for that logic in the nearest `utils/` file instead of testing the component directly.
88
+
61
89
## Testing Extracted Utilities
62
90
63
91
- Add tests at extraction time, not later
@@ -81,3 +109,17 @@ Stop the split if internal helpers (e.g. `fetchUnplacedWorkbooks`) would be frag
81
109
## HTTP Mocking
82
110
83
111
Use Nock for external HTTP calls. See `src/test/lib/clients/` for examples.
112
+
113
+
## Flowbite Toggle in E2E Tests
114
+
115
+
Flowbite's `Toggle` renders an `sr-only``<input type="checkbox">` inside a `<label>`. Clicking the input directly fails because the visual `<span>` sibling intercepts pointer events. Click the label wrapper instead:
description: Standard closing routine for an implementation session. Verifies tests, updates the plan checklist, proposes rule/skill additions, checks for bloat, and detects repeated instructions.
4
+
disable-model-invocation: true
5
+
argument-hint: '[plan-file-path]'
6
+
---
7
+
8
+
Run the session-close routine described in [instructions.md](instructions.md).
9
+
10
+
Arguments: path to the active `plan.md` (optional — defaults to the most recently modified `docs/dev-notes/**/plan.md`).
Run both checks and fix any failures before proceeding:
6
+
7
+
```bash
8
+
pnpm test:unit
9
+
pnpm test:integration
10
+
pnpm check
11
+
```
12
+
13
+
Only errors introduced by this session need fixing. Pre-existing errors (visible in git diff baseline) may be left as-is with a note.
14
+
15
+
## Step 2: Update plan.md
16
+
17
+
Target file: the path passed as `$ARGUMENTS`, or the most recently modified `docs/dev-notes/**/plan.md`.
18
+
19
+
- Mark completed tasks: `- [ ]` → `- [x]`
20
+
- If all tasks in the plan are done, append a one-line completion note, then delete the file or replace its body with a single-line summary. Stale plan files must not be left behind.
21
+
22
+
## Step 3: Propose Rule / Skill Additions
23
+
24
+
Read all files in `.claude/rules/` and `.claude/skills/`. Then review the session's changes and identify lessons that meet **all** of the following criteria:
25
+
26
+
1. Generic enough to apply in future sessions (not specific to this PR's domain)
27
+
2. Not already covered by an existing rule or skill
28
+
3. Grounded in something that actually happened in this session (a bug caught, a type error, a pattern extracted)
29
+
30
+
Present each candidate as:
31
+
32
+
```
33
+
→ Add to `<filename>`: under section `<section>`
34
+
<code example>
35
+
<one-sentence rationale>
36
+
```
37
+
38
+
Do not apply changes until the user confirms.
39
+
40
+
## Step 4: Validate Rules / Skills for Bloat
41
+
42
+
For each file in `.claude/rules/`:
43
+
44
+
- Flag sections that duplicate content in another rule file
45
+
- Flag files exceeding 150 lines where consolidation is possible
46
+
- Flag outdated or project-specific content that no longer applies
47
+
48
+
Present a concrete diff proposal for each issue. Do not apply without confirmation.
49
+
50
+
## Step 5: Detect Repeated User Instructions
51
+
52
+
Scan the conversation history for instructions the user gave more than once across this or prior sessions (visible in memory or chat context). Categorize each as:
Copy file name to clipboardExpand all lines: AGENTS.md
+3-3Lines changed: 3 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -12,8 +12,7 @@ Always prefer simplicity over pathological correctness. YAGNI, KISS, DRY. No bac
12
12
2. Before writing a new function, search `src/lib/utils/`, `src/lib/services/`, `src/features/*/utils/` and `src/features/*/services/` for existing implementations; extract shared logic there when it appears in 2+ places
13
13
3. Write tests first, then implement production code, then verify with `pnpm test:unit`
14
14
4. Review critically after implementing: flag YAGNI violations, over-abstraction, missing tests
15
-
5. Record reusable insights in `.claude/rules/` or `docs/guides/` after the session
16
-
6. Discard or summarize completed plans; don't leave stale TODOs
15
+
5. Run `/session-close` at the end of each session: updates plan checklist, proposes rule/skill additions, checks for bloat, and detects repeated instructions
-**Svelte 5 Runes**: Use `$props()`, `$state()`, `$derived()` in all new components
71
+
-**Service layer**: Services return data or `null`; never call `error()` or `redirect()`. HTTP error translation belongs in the route handler — the service must stay framework-agnostic and unit-testable.
72
72
-**Server data**: `+page.server.ts` → `+page.svelte` via `data` prop
73
73
-**Forms**: Superforms + Zod validation
74
-
-**Tests**: Write tests before implementation (TDD). Use `@quramy/prisma-fabbrica` for factories, Nock for HTTP mocking
74
+
-**Tests**: Write tests before implementation (TDD). Use `@quramy/prisma-fabbrica` for factories only in `prisma/seed.ts` and Playwright global setup (`tests/global-setup.ts`). For service-layer unit tests, mock the DB with `vi.mock('$lib/server/database', ...)` — do not use fabbrica there. Use Nock for HTTP mocking
0 commit comments