Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 36 additions & 6 deletions .claude/rules/coding-style.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Before writing new logic, decide which layer it belongs to. Run this check at pl
| DB schema | `prisma/` | Migrations are immutable after apply |
| DB access | `src/lib/server/` | Server-only; never import in client code |
| Validation | `src/**/zod/` | `z.number().int()` for Int fields; comment dual-enforcement with SQL CHECK |
| Domain types | `src/**/types/` (`_types/` inside `src/routes/`) | Plural aliases; TSDoc on every export; no `any` |
| Domain types | `src/**/types/` (`_types/` inside `src/routes/`) | Plural aliases; TSDoc on every export; avoid `any`; see alternatives |
| Test data | `src/**/fixtures/` (`_fixtures/` inside `src/routes/`) | Write before implementation (TDD); use realistic values |
| Business logic | `src/**/services/` | Return pure values or `null`; no `Response`/`json()` |
| Pure utilities | `src/**/utils/` (`_utils/` inside `src/routes/`) | No side effects; adjacent unit test required |
Expand All @@ -26,7 +26,16 @@ Before writing new logic, decide which layer it belongs to. Run this check at pl
- **Abbreviations**: avoid non-standard abbreviations (`res` → `response`, `btn` → `button`). When in doubt, spell it out.
- **Lambda parameters**: no single-character names (e.g., use `placement`, `workbook`). Iterator index `i` is the only exception.
- **`upsert`**: only use when the implementation performs both insert and update. For insert-only, use `initialize`, `seed`, or another accurate verb.
- **`any`**: before using `any`, check the value's origin — adding a missing `@types/*` or `devDependency` often provides the correct type.
- **`any`**: before using `any`, check the value's origin — adding a missing `@types/*` or `devDependency` often provides the correct type. When `any` seems unavoidable, use the narrowest alternative:

| Situation | Alternative |
| ---------------------------------------------------------- | ------------------------------------------------------------------------ |
| Assign to a property not on the type | `obj as T & { prop: U }` (intersection cast) |
| Return type too complex to write manually | `ReturnType<typeof fn>` |
| Partial mock: only specific properties matter | `Partial<T>`, `Pick<T, 'a' \| 'b'>`, or `satisfies` — prefer these first |
| Partial mock: none of the above narrow the type far enough | `as unknown as T` — last resort; bypasses type checking entirely |
| Inline `: any` annotation where inference reaches | Delete the annotation |

- **UI labels**: if a label does not match actual behavior, update it or add an inline comment explaining the intentional mismatch.
- **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`.
- **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:
Expand All @@ -39,23 +48,44 @@ Before writing new logic, decide which layer it belongs to. Run this check at pl

- **Braces**: always use braces for single-statement `if` blocks. Never `if () return;` — write `if () { return; }`.
- **Plural type aliases**: define `type Placements = Placement[]` instead of using `Placement[]` directly in signatures and variables.
- **Empty `catch` blocks**: never use `catch { }` or `catch (_e)` to silence errors. Every `catch` must re-throw, log, or contain an explanatory comment justifying the suppression. Silent swallowing hides bugs and makes failures untraceable.

```typescript
// Bad: silently discards the error
try { ... } catch { }
try { ... } catch (_e) { }

// Good: log and re-throw (adds context before propagating)
try { ... } catch (error) {
console.error('Operation failed:', error);
throw error;
}

// Good: intentional suppression with explanation
try {
localStorage.setItem(key, value);
} catch {
// localStorage may be unavailable (private browsing) — fall back to in-memory store
}
```

### No Hard-Coded Values

Extract magic numbers and strings to named constants. Never embed literal values whose meaning is not self-evident from the type or immediate context.

```typescript
// Bad
// Bad: magic literals embedded inline
if (grade >= 11) { ... }

const url = '/api/workbooks/submit';
const response = await fetch('/api/workbooks/submit', options);

// Good
const MIN_GRADE = 11;
const SUBMIT_URL = '/api/workbooks/submit';

if (grade >= MIN_GRADE) { ... }

const SUBMIT_URL = '/api/workbooks/submit';
const response = await fetch(SUBMIT_URL, options);
```

Place constants at the top of the file, or in a dedicated `constants/` module when shared across files.
Expand All @@ -67,7 +97,7 @@ Within a file, order declarations as follows:
1. Exported functions and classes (public API first)
2. Internal helper functions (supporting the exports above)

Shared helper functions (used by two or more exports) should be grouped at the end of the file.
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.

## Documentation

Expand Down
19 changes: 19 additions & 0 deletions .claude/rules/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,25 @@ If a task description does not mention tests, add them anyway for any non-trivia
- Never delete, comment out, or weaken assertions (e.g. `toEqual` → `toBeDefined`) to make tests pass
- Fix the implementation, not the test; if the test itself is wrong, explain why in a comment or commit message

## Unused Imports in Test Files

An unused import in a test file is a signal that a test was planned but not yet written — not dead code to remove.

Before deleting such an import, check whether the corresponding test case is missing and add it:

```typescript
// Bad: remove the import because it's unused
import { ABCLikeProvider } from './contest_table_provider';

// Good: the import is unused because the test is missing — add the test
test('expects to create ABCLike preset correctly', () => {
const group = prepareContestProviderPresets().ABCLike();
expect(group.getProvider(ContestType.ABC_LIKE)).toBeInstanceOf(ABCLikeProvider);
});
```

Removing the import silences the linter but leaves a coverage gap. Adding the test both satisfies the linter and improves coverage.

## Test Types

| Type | Tool | Location | Run Command |
Expand Down
29 changes: 29 additions & 0 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"ignorePatterns": ["**/*.svelte"],
"env": {
"browser": true,
"node": true
},
"globals": {
"$state": "readonly",
"$derived": "readonly",
"$effect": "readonly",
"$props": "readonly",
"$bindable": "readonly",
"$inspect": "readonly"
},
Comment thread
coderabbitai[bot] marked this conversation as resolved.
"rules": {
"@typescript-eslint/ban-ts-comment": [
"error",
{
"ts-expect-error": "allow-with-description",
"ts-ignore": false
}
],
"@typescript-eslint/no-unused-vars": [
"error",
{ "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }
],
"@typescript-eslint/no-explicit-any": "warn"
}
}
Comment thread
KATO-Hiro marked this conversation as resolved.
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Always prefer simplicity over pathological correctness. YAGNI, KISS, DRY. No bac

## Tech Stack

SvelteKit 2 + Svelte 5 (Runes) + TypeScript | PostgreSQL + Prisma | Flowbite Svelte + Tailwind 4 | Vitest + Playwright
SvelteKit 2 + Svelte 5 (Runes) + TypeScript | PostgreSQL + Prisma | Flowbite Svelte + Tailwind 4 | Vitest + Playwright | oxlint (JS/TS) + ESLint (Svelte)

## Commands

Expand All @@ -32,7 +32,7 @@ pnpm test # Run all tests
pnpm test:unit # Vitest unit tests
pnpm test:e2e # Playwright E2E tests
pnpm coverage # Report test coverage
pnpm lint # ESLint check
pnpm lint # Prettier + oxlint (JS/TS) + ESLint (.svelte) check
pnpm format # Prettier format
pnpm check # Svelte type check
pnpm exec prisma generate # Generate Prisma client
Expand Down Expand Up @@ -78,7 +78,7 @@ prisma/schema.prisma # Database schema
- **Forms**: Superforms + Zod validation
- **Tests**: Write tests before implementation (TDD). Use `@quramy/prisma-fabbrica` for factories only in `prisma/seed.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
- **Naming**: `camelCase` variables, `PascalCase` types/components, `snake_case` files/routes, `kebab-case` directories
- **Pre-commit**: Lefthook runs Prettier + ESLint (bypass: `LEFTHOOK=0 git commit`)
- **Pre-commit**: Lefthook runs Prettier + oxlint (JS/TS) + ESLint (.svelte only) (bypass: `LEFTHOOK=0 git commit`)

## References

Expand Down
6 changes: 4 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
- パッケージマネージャ
- [pnpm](https://pnpm.io/ja/)
- 文法およびフォーマットチェッカー
- [ESLint](https://eslint.org/)
- [oxlint](https://oxc.rs/docs/guide/usage/linter.html): JS/TS ファイルの高速リンター(50–100x 高速)
- [ESLint](https://eslint.org/): Svelte ファイル専用リンター(eslint-plugin-svelte)のみ使用
- [Prettier](https://prettier.io/)
- [lefthook](https://github.com/evilmartians/lefthook): Git hooks 管理ツール(コミット前の自動フォーマット・リント)
- Search Engine Optimization (SEO) 対策
Expand Down Expand Up @@ -283,7 +284,8 @@

- **Pre-commit Hook**: ステージ済みファイルのみに対して以下を実行
- `prettier --write`: コード書式の自動修正(JavaScript、TypeScript、Markdown、Svelte)
- `eslint`: リント(JavaScript、TypeScript、Svelte)
- `oxlint`: JS/TS ファイルのリント(JavaScript、TypeScript)
- `eslint`: Svelte ファイルのリント(.svelte のみ)

Hook は自動的にセットアップされるため、特別な操作は不要です。

Expand Down
2 changes: 1 addition & 1 deletion e2e/custom_colors.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ test.describe('Custom colors for TailwindCSS v4 configuration', () => {
if (cssFiles.length === 0) {
cssFiles = allCssFiles;
}
} catch (e) {
} catch {
// True error: directory not found or inaccessible
throw new Error(`Not found CSS directory: ${cssDir}`);
}
Expand Down
2 changes: 1 addition & 1 deletion e2e/signin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ async function login(page: Page, username: string, password: string): Promise<vo
await expect(page).toHaveURL('/', { timeout: UP_TO_ONE_MINUTE });
}

async function logout(page: Page, username: string): Promise<void> {
async function logout(page: Page, _username: string): Promise<void> {
Comment thread
KATO-Hiro marked this conversation as resolved.
// Step 1: Click user button to display dropdown
// Use ID selector because role="presentation" due to Flowbite-Svelte NavLi + Dropdown
const userButton = page.locator('button[id="nav-user-page"]');
Expand Down
5 changes: 2 additions & 3 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import globals from 'globals';
import tsParser from '@typescript-eslint/parser';
import svelteParser from 'svelte-eslint-parser';
import sveltePlugin from 'eslint-plugin-svelte';
import js from '@eslint/js';

export default [
{
Expand All @@ -28,10 +27,10 @@ export default [
'**/vite.config.js.timestamp-*',
'**/vite.config.ts.timestamp-*',
'prisma/.fabbrica/index.ts',
// oxlint handles JS/TS files
'**/*.{js,ts,tsx,mjs,cjs}',
],
},
// Base JS rules first
js.configs.recommended,
// Svelte rules override JS rules where appropriate (intentional)
// This allows Svelte-specific handling of rules like no-undef, no-unused-vars
...sveltePlugin.configs['flat/recommended'],
Expand Down
6 changes: 5 additions & 1 deletion lefthook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ pre-commit:
run: pnpm exec prettier --write {staged_files}
glob: '**/*.{js,jsx,ts,tsx,md,svelte}'

- name: oxlint
run: pnpm exec oxlint {staged_files}
glob: '**/*.{js,jsx,ts,tsx}'

- name: eslint
run: pnpm exec eslint {staged_files}
glob: '**/*.{js,jsx,ts,tsx,svelte}'
glob: '**/*.svelte'
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"test": "npm run test:e2e && npm run test:unit",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --check . && eslint .",
"lint": "prettier --check . && oxlint . && eslint .",
"format": "prettier --write .",
"test:e2e": "playwright test",
"test:unit": "vitest",
Expand All @@ -26,7 +26,6 @@
"@dnd-kit/abstract": "0.3.2",
"@dnd-kit/dom": "0.3.2",
"@eslint/eslintrc": "3.3.5",
"@eslint/js": "10.0.1",
"@playwright/test": "1.58.2",
"@quramy/prisma-fabbrica": "2.3.3",
"@sveltejs/adapter-vercel": "6.3.3",
Expand All @@ -47,9 +46,10 @@
"flowbite": "3.1.2",
"flowbite-svelte": "1.31.0",
"globals": "17.4.0",
"lefthook": "2.1.4",
"jsdom": "29.0.1",
"lefthook": "2.1.4",
"nock": "14.0.11",
"oxlint": "^1.56.0",
"pnpm": "10.32.1",
"prettier": "3.8.1",
"prettier-plugin-svelte": "3.5.1",
Expand Down
Loading
Loading