Skip to content

Commit 2b6e676

Browse files
authored
Merge pull request #3371 from AtCoder-NoviSteps/#3364
fix: implement CodeRabbit findings (#3364)
2 parents 794f5d3 + bae4e80 commit 2b6e676

34 files changed

Lines changed: 1456 additions & 849 deletions

File tree

.claude/rules/coding-style.md

Lines changed: 44 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,143 +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-
| Pure utilities | `src/**/utils/` (`_utils/` inside `src/routes/`) | No side effects; adjacent unit test required |
18-
| State | `src/**/stores/` | `.svelte.ts`; class + `$state()`; singleton export |
19-
| Route handlers | `src/routes/` | Page: `redirect()`; API: `error()` |
20-
| UI components | `src/**/*.svelte` | Svelte 5 Runes; business logic → `utils/` |
21-
22-
## Code Structure
23-
24-
### Naming
25-
26-
- **Abbreviations**: avoid non-standard abbreviations (`res``response`, `btn``button`). When in doubt, spell it out.
27-
- **Lambda parameters**: no single-character names (e.g., use `placement`, `workbook`). Iterator index `i` is the only exception.
28-
- **`upsert`**: only use when the implementation performs both insert and update. For insert-only, use `initialize`, `seed`, or another accurate verb.
29-
- **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`.
30-
- **`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`.
31-
32-
- **UI labels**: if a label does not match actual behavior, update it or add an inline comment explaining the intentional mismatch.
33-
- **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`.
34-
- **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:
35-
- Custom files in routes (utilities, helpers, etc.): `snake_case` (e.g., `user_profile.ts`)
36-
- SvelteKit special files: follow framework conventions (`+page.svelte`, `+page.server.ts`, `+server.ts`)
37-
- Helper directories inside `src/routes/`: underscore-prefixed (`_utils/`, `_types/`, `_fixtures/`, `_components/`)
38-
- Other directories: `kebab-case` (e.g., `contest-table/`)
39-
40-
### Syntax
41-
42-
- **Braces**: always use braces for single-statement `if` blocks. Never `if () return;` — write `if () { return; }`.
43-
- **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.
44-
- **Plural type aliases**: define `type Placements = Placement[]` instead of using `Placement[]` directly in signatures and variables.
45-
- **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
464

47-
```typescript
48-
try { ... } catch (error) { console.error('...'); throw error; } // good
49-
try { localStorage.setItem(key, value); } catch { /* unavailable */ } // good + comment
50-
```
51-
52-
### No Hard-Coded Values
53-
54-
Extract magic numbers and strings to named constants. Never embed literal values whose meaning is not self-evident from the type or immediate context.
55-
56-
```typescript
57-
// Bad: magic literals embedded inline
58-
if (grade >= 11) { ... }
59-
60-
const response = await fetch('/api/workbooks/submit', options);
61-
62-
// Good
63-
const MIN_GRADE = 11;
64-
const SUBMIT_URL = '/api/workbooks/submit';
65-
66-
if (grade >= MIN_GRADE) { ... }
67-
68-
const response = await fetch(SUBMIT_URL, options);
69-
```
70-
71-
Place constants at the top of the file, or in a dedicated `constants/` module when shared across files.
72-
73-
### Function Ordering
74-
75-
Within a file, order declarations as follows:
76-
77-
1. Exported functions and classes (public API first)
78-
2. Internal helper functions (supporting the exports above)
5+
Before writing logic, decide which layer it belongs to:
796

80-
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.
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/` |
8120

82-
### URL Parameter Patterns
21+
## Naming
8322

84-
#### null-as-ALL: Omitting Params for "All" State
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/`)
8529

86-
When a filter has an "all" or "unfiltered" state, omit the parameter entirely rather than using a magic value (e.g., "ALL", "\*").
30+
## Syntax
8731

88-
**Pattern:**
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
8936

90-
- Parse function defaults to `null` when param is absent
91-
- `null` → "show all" (no filter applied)
92-
- URL: `/workbooks?tab=solution` (no `categories=`)
93-
- Browser back button naturally restores default "all" view
37+
## Type Guards at API Boundaries
9438

95-
**Benefit:** Cleaner URLs, intuitive history behavior, smaller sessionStorage footprint.
96-
97-
**Example:** `parseWorkBookCategory()` defaults to `null` = all categories
98-
99-
### Type Guards: Precise Narrowing for Excluded Values
100-
101-
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.
102-
103-
### Layer-Specific Responsibility: Normalization vs Filtering
104-
105-
When filtering data by multiple criteria (format + role):
106-
107-
- **Service layer**: Normalize raw data format (e.g., `null → PENDING`). Return all data; stay framework-agnostic and testable.
108-
- **UI layer**: Filter by role/context (e.g., "admin sees PENDING, user doesn't"). Apply display rules in component.
109-
110-
**Benefit**: Services remain pure and unit-testable without mocking roles; UI logic explicit in one place.
111-
112-
### Function Composition: Single Responsibility
113-
114-
Separate independent transformations into distinct functions. Compose at call site.
39+
When receiving enum values from external APIs, validate with a type guard:
11540

11641
```typescript
117-
// groupBySolutionCategory(): pure grouping (testable)
118-
// filterGroupsByRole(): pure filtering by role (testable)
119-
let filtered = $derived(filterGroupsByRole(groupBySolutionCategory(workbooks, map), role));
42+
// Good: `isValidTaskGrade(value): value is TaskGrade`
43+
const grade = isValidTaskGrade(data.grade) ? data.grade : null;
12044
```
12145

122-
**Benefit**: Each function testable in isolation; reusable in different contexts.
123-
124-
## Documentation
125-
126-
### Language Policy
46+
Extract to `src/lib/utils/` with adjacent tests.
12747

128-
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.
48+
## Dead Code: Three-Condition Rule
12949

130-
**Exception**: The `## CodeRabbit Findings` section in `plan.md` must quote findings verbatim in their original language (English). Do not translate CodeRabbit output.
50+
Delete function only if: (1) zero callers, (2) replacement exists, (3) dependent fields also deleted.
13151

132-
### TSDoc
133-
134-
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.
135-
136-
### Documentation
52+
## Documentation
13753

138-
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.
54+
- **Plans/dev-notes**: Japanese
55+
- **Code/commits/tests**: English
56+
- **TSDoc**: required on all exports
57+
- **Code blocks**: specify language (`typescript`, `sql`, `bash`)
13958

140-
## Security & Code Review
59+
## Security
14160

142-
- **Logging**: No user-identifiable data in `console.log`. Single authoritative log in service layer.
143-
- **CodeRabbit**: Run after all phases complete. Write `critical`/`high`/`medium` findings to `plan.md` verbatim; defer `nitpick` to PR CI.
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: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,32 @@ automatically excluded. This is not an IS NOT NULL check; the mechanism is the J
8383
When documenting this behavior, write "excluded by INNER JOIN" rather than
8484
"implicitly includes IS NOT NULL".
8585

86+
## FK Relations: Always Define @relation
87+
88+
Any field that references another model's ID must have an explicit `@relation` defined. Without `@relation`, Prisma does not generate FK constraints automatically, leading to referential integrity gaps.
89+
90+
```prisma
91+
// Bad: FK without @relation
92+
userId String
93+
94+
// Good: explicit @relation generates FK constraint
95+
userId String
96+
user User @relation(fields: [userId], references: [id])
97+
```
98+
99+
## DB-Level Value Constraints
100+
101+
Add `CHECK` constraints via manual migration SQL. Document with inline `schema.prisma` comments (e.g., `/// CHECK: count >= 0`). Note: `prisma-erd-generator` may overwrite `ERD.md` on each migration—always keep the constraint definition in `schema.prisma` as the source of truth.
102+
103+
## Service Layer Error Handling
104+
105+
Catch Prisma errors in service functions, return domain values:
106+
107+
- `P2025` (record not found) → `null` (no exception)
108+
- Other errors → re-throw (caller handles as 500)
109+
110+
This removes Prisma imports from route handlers and enables easy testing with mocked returns.
111+
86112
## Dual-Enforcement Constraints
87113

88114
When the same constraint is enforced in both Zod (early validation) and SQL `CHECK` (last line of defense), add an inline comment stating each layer's role and the obligation to keep them in sync:
@@ -99,9 +125,10 @@ Prisma does not support `@@check`. To add one:
99125

100126
1. `pnpm exec prisma migrate dev --create-only --name <description>` — generate migration without applying
101127
2. Edit the generated `migration.sql` to add the CHECK constraint manually
102-
3. `pnpm exec prisma migrate dev` — apply
128+
3. Add an inline comment in `schema.prisma` (e.g., `/// CHECK: constraint description`)
129+
4. `pnpm exec prisma migrate dev` — apply
103130

104-
Document the constraint in `prisma/ERD.md` (the only place it's visible):
131+
For visibility, also document complex constraints in `prisma/ERD.md`:
105132

106133
```mermaid
107134
%% XOR constraint: workbookplacement_xor_grade_category — exactly one of taskGrade or solutionCategory must be non-null

0 commit comments

Comments
 (0)