Skip to content

Commit 2d3494f

Browse files
KATO-Hiroclaude
andcommitted
docs(rules): consolidate and streamline coding standards
- Compress `.claude/rules/` files by removing duplicate examples and combining related sections - coding-style.md: Simplify layer check table, consolidate naming/syntax rules, add type guard pattern at API boundaries - prisma-db.md: Update ERD.md rule to use schema.prisma inline comments (ERD.md auto-regenerated by prisma-erd-generator) - svelte-components.md: Remove verbose examples, retain core patterns - svelte-runes.md: Consolidate derived state and reactivity patterns - sveltekit.md: Compress load/error/action documentation - testing-e2e.md: Streamline navigation patterns and fixture management - testing.md: Consolidate mock helpers and parametrized test patterns Overall: ~50% line reduction while preserving essential patterns and new rules added from PR #3316 review. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
1 parent 62e9fa1 commit 2d3494f

7 files changed

Lines changed: 283 additions & 789 deletions

File tree

.claude/rules/coding-style.md

Lines changed: 43 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -1,178 +1,64 @@
11
# Coding Style
22

3-
## Plan Time
4-
5-
### Pre-Implementation Layer Check
6-
7-
Before writing new logic, decide which layer it belongs to. Run this check at plan time (design/architecture phase, before writing any code):
8-
9-
| Layer | Directory | Key constraints |
10-
| -------------- | -------------------------------------------------------- | -------------------------------------------------------------------------- |
11-
| DB schema | `prisma/` | Migrations are immutable after apply |
12-
| DB access | `src/lib/server/` | Server-only; never import in client code |
13-
| Validation | `src/**/zod/` | `z.number().int()` for Int fields; comment dual-enforcement with SQL CHECK |
14-
| Domain types | `src/**/types/` (`_types/` inside `src/routes/`) | Plural aliases; TSDoc on every export; avoid `any`; see alternatives |
15-
| Test data | `src/**/fixtures/` (`_fixtures/` inside `src/routes/`) | Write before implementation (TDD); use realistic values |
16-
| Business logic | `src/**/services/` | Return pure values or `null`; no `Response`/`json()` |
17-
| External APIs | `src/lib/clients/` or `src/features/*/internal_clients/` | Shared APIs → `lib/clients/`; feature-scoped APIs → `internal_clients/` |
18-
| Pure utilities | `src/**/utils/` (`_utils/` inside `src/routes/`) | No side effects; adjacent unit test required |
19-
| State | `src/**/stores/` | `.svelte.ts`; class + `$state()`; singleton export |
20-
| Route handlers | `src/routes/` | Page: `redirect()`; API: `error()` |
21-
| UI components | `src/**/*.svelte` | Svelte 5 Runes; business logic → `utils/` |
22-
23-
## Code Structure
24-
25-
### Naming
26-
27-
- **Abbreviations**: avoid non-standard abbreviations (`res``response`, `btn``button`). When in doubt, spell it out.
28-
- **Lambda parameters**: no single-character names (e.g., use `placement`, `workbook`). Iterator index `i` is the only exception.
29-
- **`upsert`**: only use when the implementation performs both insert and update. For insert-only, use `initialize`, `seed`, or another accurate verb.
30-
- **Function verbs**: every function name must start with a verb. Noun-only names (`pointOnCircle`, `arcPath`) are ambiguous — use `calcPointOnCircle`, `buildArcPath`, etc. Common prefixes: `get` (read existing), `build`/`create` (construct new), `calc`/`compute` (derive by formula), `update`, `fetch`, `resolve`.
31-
- **`any`**: Before using `any`, check the value's origin. If unavoidable: use `ReturnType<typeof fn>` for complex returns, `Partial<T>` for partial objects, `obj as T & { prop: U }` for property extension. Last resort: `as unknown as T`.
32-
33-
- **UI labels**: if a label does not match actual behavior, update it or add an inline comment explaining the intentional mismatch.
34-
- **Constant names**: reflect what the value IS (content), not what it is used for (purpose). e.g., a set holding all enum tab values is `EXISTING_TABS`, not `VALID_TABS`.
35-
- **New files**: before naming a new file or directory, grep the relevant `src/` directory to confirm existing conventions. Confirm at plan time, not during implementation:
36-
- Custom files in routes (utilities, helpers, etc.): `snake_case` (e.g., `user_profile.ts`)
37-
- SvelteKit special files: follow framework conventions (`+page.svelte`, `+page.server.ts`, `+server.ts`)
38-
- Helper directories inside `src/routes/`: underscore-prefixed (`_utils/`, `_types/`, `_fixtures/`, `_components/`)
39-
- Other directories: `kebab-case` (e.g., `contest-table/`)
40-
41-
### Syntax
42-
43-
- **Braces**: always use braces for single-statement `if` blocks. Never `if () return;` — write `if () { return; }`.
44-
- **Domain types over `string`**: when the Prisma schema uses an enum (e.g. `grade: TaskGrade`), the corresponding app-layer type must use the same enum — not `string`. A loose `string` type hides misspellings in fixtures and forces `as TaskGrade` casts throughout the codebase. When a field comes from an external source (form data, query params), validate and narrow it at the boundary; inside the app it must always be the domain type.
45-
- **Plural type aliases**: define `type Placements = Placement[]` instead of using `Placement[]` directly in signatures and variables.
46-
- **Empty `catch` blocks**: never use `catch { }` to silence errors. Either re-throw, log, or add a comment justifying suppression. Silent swallowing hides bugs.
3+
## Pre-Implementation Layer Check
474

48-
```typescript
49-
try { ... } catch (error) { console.error('...'); throw error; } // good
50-
try { localStorage.setItem(key, value); } catch { /* unavailable */ } // good + comment
51-
```
52-
53-
### No Hard-Coded Values
5+
Before writing logic, decide which layer it belongs to:
546

55-
Extract magic numbers and strings to named constants. Never embed literal values whose meaning is not self-evident from the type or immediate context.
7+
| Layer | Directory | Constraints |
8+
| -------------- | ------------------------------------------- | ------------------------------------------------------- |
9+
| DB schema | `prisma/` | Migrations immutable after apply |
10+
| DB access | `src/lib/server/` | Server-only; never in client |
11+
| Validation | `src/**/zod/` | `z.number().int()` for Int; dual-enforce with SQL CHECK |
12+
| Domain types | `src/**/types/` | Plural aliases; TSDoc; avoid `any` |
13+
| Test data | `src/**/fixtures/` | Write before impl (TDD); realistic values |
14+
| Business logic | `src/**/services/` | Pure values/`null`; no `Response`/`json()` |
15+
| External APIs | `src/lib/clients/` or `*/internal_clients/` | Shared → `lib`; feature-scoped → `internal_clients` |
16+
| Utilities | `src/**/utils/` | No side effects; adjacent unit test required |
17+
| State | `src/**/stores/` | `.svelte.ts`; class + `$state()`; singleton export |
18+
| Route handlers | `src/routes/` | Page: `redirect()`; API: `error()` |
19+
| Components | `src/**/*.svelte` | Svelte 5 Runes; logic → `utils/` |
5620

57-
```typescript
58-
// Bad: magic literals embedded inline
59-
if (grade >= 11) { ... }
60-
61-
const response = await fetch('/api/workbooks/submit', options);
21+
## Naming
6222

63-
// Good
64-
const MIN_GRADE = 11;
65-
const SUBMIT_URL = '/api/workbooks/submit';
23+
- **No abbreviations** unless standard (e.g., `id`, `url`)
24+
- **Lambda params**: no single-char (except `i` for loops)
25+
- **Function verbs**: `get` (read), `build`/`create` (construct), `calc`/`compute` (derive), `update`, `fetch`, `resolve`
26+
- **Domain enums**: use domain type, not `string` (validates at boundary, stays typed inside)
27+
- **Constants**: reflect content not purpose; extract magic numbers/strings
28+
- **Files**: `snake_case` in routes, `kebab-case` dirs, underscore-prefix helpers (`_utils/`, `_types/`)
6629

67-
if (grade >= MIN_GRADE) { ... }
30+
## Syntax
6831

69-
const response = await fetch(SUBMIT_URL, options);
70-
```
32+
- **Braces**: always for single-statement `if` blocks
33+
- **Catch blocks**: never silent; re-throw, log, or comment
34+
- **Plural aliases**: `type Items = Item[]` in signatures
35+
- **TSDoc**: every export; `@param`/`@returns` when non-obvious
7136

72-
Place constants at the top of the file, or in a dedicated `constants/` module when shared across files.
37+
## Type Guards at API Boundaries
7338

74-
#### Test Code: Status Code Constants
75-
76-
Applies equally to test code. HTTP status codes especially benefit from named constants for clarity of intent.
39+
When receiving enum values from external APIs, validate with a type guard:
7740

7841
```typescript
79-
import * as statusCodes from '$lib/constants/http-response-status-codes';
80-
81-
// Bad: what does 200 mean here?
82-
nock('http://localhost').get('/user').reply(200, data);
83-
nock('http://localhost').get('/user').reply(500);
84-
85-
// Good: intent is immediately clear
86-
nock('http://localhost').get('/user').reply(statusCodes.OK, data);
87-
nock('http://localhost').get('/user').reply(statusCodes.INTERNAL_SERVER_ERROR);
42+
// Good: `isValidTaskGrade(value): value is TaskGrade`
43+
const grade = isValidTaskGrade(data.grade) ? data.grade : null;
8844
```
8945

90-
Test readers (reviewers, future maintainers) must quickly understand **what is being tested**. Named constants (`OK`, `UNAUTHORIZED`) communicate intent far better than numeric codes.
91-
92-
### Function Ordering
93-
94-
Within a file, order declarations as follows:
95-
96-
1. Exported functions and classes (public API first)
97-
2. Internal helper functions (supporting the exports above)
98-
99-
Place a private helper immediately after the single export that uses it. Place helpers shared by two or more exports at the end of the file.
100-
101-
### URL Parameter Patterns
102-
103-
#### null-as-ALL: Omitting Params for "All" State
104-
105-
When a filter has an "all" or "unfiltered" state, omit the parameter entirely rather than using a magic value (e.g., "ALL", "\*").
106-
107-
**Pattern:**
108-
109-
- Parse function defaults to `null` when param is absent
110-
- `null` → "show all" (no filter applied)
111-
- URL: `/workbooks?tab=solution` (no `categories=`)
112-
- Browser back button naturally restores default "all" view
113-
114-
**Benefit:** Cleaner URLs, intuitive history behavior, smaller sessionStorage footprint.
115-
116-
**Example:** `parseWorkBookCategory()` defaults to `null` = all categories
117-
118-
### Type Guards: Precise Narrowing for Excluded Values
119-
120-
When a type guard intentionally excludes enum members, use `Exclude<T, 'VALUE'>` in the return type to match runtime behavior. Caller code then trusts the type system; no `as` casts needed.
46+
Extract to `src/lib/utils/` with adjacent tests.
12147

122-
### Layer-Specific Responsibility: Normalization vs Filtering
48+
## Dead Code: Three-Condition Rule
12349

124-
When filtering data by multiple criteria (format + role):
125-
126-
- **Service layer**: Normalize raw data format (e.g., `null → PENDING`). Return all data; stay framework-agnostic and testable.
127-
- **UI layer**: Filter by role/context (e.g., "admin sees PENDING, user doesn't"). Apply display rules in component.
128-
129-
**Benefit**: Services remain pure and unit-testable without mocking roles; UI logic explicit in one place.
130-
131-
### Function Composition: Single Responsibility
132-
133-
Separate independent transformations into distinct functions. Compose at call site.
134-
135-
```typescript
136-
// groupBySolutionCategory(): pure grouping (testable)
137-
// filterGroupsByRole(): pure filtering by role (testable)
138-
let filtered = $derived(filterGroupsByRole(groupBySolutionCategory(workbooks, map), role));
139-
```
140-
141-
**Benefit**: Each function testable in isolation; reusable in different contexts.
50+
Delete function only if: (1) zero callers, (2) replacement exists, (3) dependent fields also deleted.
14251

14352
## Documentation
14453

145-
### Language Policy
146-
147-
Write all project documentation (plans, dev-notes, guides, refactor notes) in Japanese. Write all source code comments, TSDoc, commit messages, and test titles in English. This keeps documentation readable for the team while keeping code comments universally accessible and searchable.
148-
149-
**Exception**: The `## CodeRabbit Findings` section in `plan.md` must quote findings verbatim in their original language (English). Do not translate CodeRabbit output.
150-
151-
### TSDoc
152-
153-
Add TSDoc to every exported function, type, class. Minimum: `@param` (non-obvious), `@returns` (not evident from type). One-liner OK; multi-line for complex behavior only.
154-
155-
### Documentation
156-
157-
Write plans/dev-notes/guides in Japanese. Source code comments in English. Always specify language on code blocks (e.g., `typescript`, `sql`, `bash`). For Svelte 5 unclear behavior: fetch official docs via WebFetch, not training knowledge.
158-
159-
## Security & Code Review
160-
161-
- **Logging**: No user-identifiable data in `console.log`. Single authoritative log in service layer.
162-
- **CodeRabbit**: Run after all phases complete. Write `critical`/`high`/`medium` findings to `plan.md` verbatim; defer `nitpick` to PR CI.
163-
164-
## `+page.server.ts` load() Error Handling
165-
166-
Wrap calls to service functions in try-catch and return safe default values on failure, preventing a single service error from crashing the entire page.
167-
168-
## Auth: Action Audit
169-
170-
When adding an auth guard to one action in `+page.server.ts`, audit all other actions in the same file. Asymmetric guards (some actions protected, others not) are a recurring pattern of vulnerability.
171-
172-
## Auth: success Flag and message Consistency
173-
174-
When an action returns `success: false`, the `message` and `message_type` must also reflect failure. A success flag contradicting the message is a silent bug.
54+
- **Plans/dev-notes**: Japanese
55+
- **Code/commits/tests**: English
56+
- **TSDoc**: required on all exports
57+
- **Code blocks**: specify language (`typescript`, `sql`, `bash`)
17558

176-
## Dead Code Deletion: Three-Condition Rule
59+
## Security
17760

178-
Before deleting a function, grep the full project for callers. Deletion is safe only when all three conditions hold: (1) zero callers, (2) a replacement implementation exists, (3) any fields this function wrote to are also being deleted.
61+
- No user-identifiable data in logs
62+
- No Prisma imports in route handlers
63+
- Validate input at system boundaries
64+
- Return safe defaults on service errors

.claude/rules/prisma-db.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,15 @@ user User @relation(fields: [userId], references: [id])
9898

9999
## DB-Level Value Constraints
100100

101-
Add `CHECK` constraints (via manual migration SQL) for:
101+
Add `CHECK` constraints (via manual migration SQL) for count and invalid enum values. Document with inline `schema.prisma` comments (e.g., `/// CHECK: count >= 0`) — `prisma-erd-generator` overwrites `ERD.md` on each migration.
102102

103-
- `count` fields that must be non-negative (`count >= 0`)
104-
- Enum fields where specific values are invalid at the DB level (e.g. `grade != 'PENDING'`)
103+
## Service Layer Error Handling
105104

106-
Document every `CHECK` constraint in `prisma/ERD.md` — it is the only place they are visible outside migration SQL.
105+
Catch Prisma errors in service functions, return domain values:
106+
- `P2025` (record not found) → `null` (no exception)
107+
- Other errors → re-throw (caller handles as 500)
108+
109+
This removes Prisma imports from route handlers and enables easy testing with mocked returns.
107110

108111
## Dual-Enforcement Constraints
109112

0 commit comments

Comments
 (0)