From bcee5cf52c948007ac03f94625086fdf3cd060e9 Mon Sep 17 00:00:00 2001 From: Kato Hiroki Date: Sun, 22 Mar 2026 21:23:47 +0000 Subject: [PATCH 01/11] docs: Add eslint-plugin-svelte update plan (phases 1-8) Co-Authored-By: Claude Sonnet 4.6 --- .../eslint-plugin-svelte-update/phase-1.md | 35 ++++++++ .../eslint-plugin-svelte-update/phase-2.md | 43 +++++++++ .../eslint-plugin-svelte-update/phase-3.md | 80 +++++++++++++++++ .../eslint-plugin-svelte-update/phase-4.md | 53 +++++++++++ .../eslint-plugin-svelte-update/phase-5.md | 67 ++++++++++++++ .../eslint-plugin-svelte-update/phase-6.md | 52 +++++++++++ .../eslint-plugin-svelte-update/phase-7.md | 31 +++++++ .../eslint-plugin-svelte-update/phase-8.md | 43 +++++++++ .../eslint-plugin-svelte-update/plan.md | 88 +++++++++++++++++++ 9 files changed, 492 insertions(+) create mode 100644 docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-1.md create mode 100644 docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-2.md create mode 100644 docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-3.md create mode 100644 docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-4.md create mode 100644 docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-5.md create mode 100644 docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-6.md create mode 100644 docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-7.md create mode 100644 docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-8.md create mode 100644 docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/plan.md diff --git a/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-1.md b/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-1.md new file mode 100644 index 000000000..c8754fbd1 --- /dev/null +++ b/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-1.md @@ -0,0 +1,35 @@ +# Phase 1: `svelte/prefer-svelte-reactivity` — SvelteMap 置き換え + +## 概要 + +`new Map()` をミュータブルに使用している箇所を `SvelteMap` に置き換える。 +`SvelteMap` は `svelte/reactivity` からインポートし、型注釈も合わせて変更する。 + +対象: 2ファイル、3箇所 + +## タスク + +### Task 1-1: TaskTable.svelte の Map を SvelteMap に置き換え + +**対象ファイル:** `src/features/tasks/components/contest-table/TaskTable.svelte` + +- [ ] ファイルを読み込み、現在の Map 使用箇所を確認 +- [ ] `import { SvelteMap } from 'svelte/reactivity'` をインポートに追加 +- [ ] 65行目: `prepareContestTablesMap` 関数内の `new Map()` を `new SvelteMap()` に変更 +- [ ] 161行目: `taskResultsMap` の `$derived` 内、`reduce` の初期値 `new Map()` を `new SvelteMap<...>()` に変更 + - 同行の型注釈 `map: Map` も `SvelteMap<...>` に変更 +- [ ] `pnpm lint` で当該ファイルのエラーが消えたことを確認 + +### Task 1-2: TaskGradeList.svelte の Map を SvelteMap に置き換え + +**対象ファイル:** `src/lib/components/TaskGradeList.svelte` + +- [ ] ファイルを読み込み、現在の Map 使用箇所を確認 +- [ ] `import { SvelteMap } from 'svelte/reactivity'` をインポートに追加 +- [ ] 22行目: `$state(new Map())` を `$state(new SvelteMap())` に変更 +- [ ] `run()` 内の `new Map()` も `new SvelteMap()` に変更(型の一貫性のため) +- [ ] `pnpm lint` で当該ファイルのエラーが消えたことを確認 + +## 完了条件 + +`pnpm lint` を実行し、`svelte/prefer-svelte-reactivity` のエラーが 0 件になること。 diff --git a/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-2.md b/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-2.md new file mode 100644 index 000000000..dcd31568b --- /dev/null +++ b/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-2.md @@ -0,0 +1,43 @@ +# Phase 2: 外部リンクへの `rel="external"` 追加 + +## 概要 + +`svelte/no-navigation-without-resolve` ルールは、`href` の値が外部URLであることをランタイムにしか確定できない場合も lint 時に flagging する。 +`rel` 属性に `"external"` を含めることで、このリンクが外部リンクであることをリンターに明示し、エラーを解消する。 + +対象: 3ファイル、3箇所 + +## タスク + +### Task 2-1: ExternalLinkWrapper.svelte + +**対象ファイル:** `src/lib/components/ExternalLinkWrapper.svelte` +**対象行:** 28行目 + +- [ ] ファイルを読み込んで現在の `rel` 属性を確認 +- [ ] `rel="noreferrer"` → `rel="noreferrer external"` に変更 + (`noreferrer` はセキュリティのため維持。`external` を追加してリンターに外部リンクであることを示す) + +### Task 2-2: TagForm.svelte(AtCoder外部リンク) + +**対象ファイル:** `src/lib/components/TagForm.svelte` +**対象行:** 90行目 + +- [ ] ファイルを読み込み、90行目の外部リンク(AtCoder URL)を確認 +- [ ] `rel` 属性に `external` を追加(既存の `rel` 属性がある場合はスペース区切りで追記、ない場合は新規追加) + +### Task 2-3: problems/[slug]/+page.svelte(AtCoder外部リンク) + +**対象ファイル:** `src/routes/problems/[slug]/+page.svelte` +**対象行:** 34行目 + +- [ ] ファイルを読み込み、34行目の外部リンク(`getTaskUrl()` で生成されるAtCoder URL)を確認 +- [ ] `rel` 属性に `external` を追加 + +### Task 2-4: 動作確認 + +- [ ] `pnpm lint` で Task 2-1〜2-3 の3件のエラーが解消したことを確認 + +## 完了条件 + +上記3ファイルの `svelte/no-navigation-without-resolve` エラーが解消していること。 diff --git a/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-3.md b/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-3.md new file mode 100644 index 000000000..6646898d0 --- /dev/null +++ b/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-3.md @@ -0,0 +1,80 @@ +# Phase 3: 内部 href への `resolve()` 適用 + +## 概要 + +内部ルート(SvelteKitのページ)へのリンクに `resolve()` をラップする。 +各ファイルに `import { resolve } from '$app/paths'` を追加し、内部パス文字列を `resolve(...)` でラップする。 + +**変換パターン:** +- `href="/foo"` → `href={resolve('/foo')}` +- `href="/foo/{var}"` → `href={resolve(`/foo/${var}`)}` +- `href={CONST_PATH}` → `href={resolve(CONST_PATH)}` + +対象: 9ファイル、12箇所 + +## タスク + +### Task 3-1: workbook 関連コンポーネント(3ファイル) + +**対象ファイル:** +- `src/features/workbooks/components/list/TitleTableBodyCell.svelte` (23行目) +- `src/features/workbooks/components/list/WorkbookAuthorActionsCell.svelte` (25行目) +- `src/features/workbooks/components/shared/WorkbookLink.svelte` (11行目) + +- [ ] 各ファイルを読み込み、href の内容を確認 +- [ ] 各ファイルに `import { resolve } from '$app/paths'` を追加 +- [ ] `/workbooks/{...}` パターンの href を `resolve()` でラップ + +### Task 3-2: AuthForm.svelte(href のみ) + +**対象ファイル:** `src/lib/components/AuthForm.svelte` +**対象行:** 205行目、220行目(`goto()` は Phase 4 で対処) + +- [ ] ファイルを読み込み、205・220行目の `href` 値(定数 `FORGOT_PASSWORD_PAGE` 等)を確認 +- [ ] `import { resolve } from '$app/paths'` を追加(Phase 4 の `goto()` 修正とまとめて追加可) +- [ ] `href={CONST}` → `href={resolve(CONST)}` に変更 + +### Task 3-3: TagForm.svelte(内部リンク3箇所) + +**対象ファイル:** `src/lib/components/TagForm.svelte` +**対象行:** 33行目、100行目、108行目(90行目は Phase 2 で対処済み) + +- [ ] ファイルを読み込み、各行の内部パスを確認 +- [ ] `import { resolve } from '$app/paths'` を追加(Phase 2 で既に修正した場合はまとめて追加) +- [ ] 各内部 `href` を `resolve()` でラップ + +### Task 3-4: TagListForEdit.svelte + +**対象ファイル:** `src/lib/components/TagListForEdit.svelte` +**対象行:** 60行目 + +- [ ] ファイルを読み込み、60行目の href を確認 +- [ ] `import { resolve } from '$app/paths'` を追加 +- [ ] `href="/tags/{...}"` → `href={resolve(`/tags/${...}`)}` に変更 + +### Task 3-5: TaskList.svelte / TaskListSorted.svelte + +**対象ファイル:** +- `src/lib/components/TaskList.svelte` (131行目) +- `src/lib/components/TaskListSorted.svelte` (46行目) + +- [ ] 各ファイルを読み込み、href の内容を確認 +- [ ] 各ファイルに `import { resolve } from '$app/paths'` を追加 +- [ ] `/tasks/{...}` / `/problems/{...}` パターンの href を `resolve()` でラップ + +### Task 3-6: users/[username]/+page.svelte + +**対象ファイル:** `src/routes/users/[username]/+page.svelte` +**対象行:** 21行目 + +- [ ] ファイルを読み込み、21行目の href を確認 +- [ ] `import { resolve } from '$app/paths'` を追加 +- [ ] `href="/problems"` → `href={resolve('/problems')}` に変更 + +### Task 3-7: 動作確認 + +- [ ] `pnpm lint` で Task 3-1〜3-6 の12件のエラーが解消したことを確認 + +## 完了条件 + +内部 href に関する `svelte/no-navigation-without-resolve` エラーが全件解消していること。 diff --git a/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-4.md b/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-4.md new file mode 100644 index 000000000..c3af7a78d --- /dev/null +++ b/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-4.md @@ -0,0 +1,53 @@ +# Phase 4: `goto()` / `replaceState()` への `resolve()` 適用 + +## 概要 + +プログラマティックなナビゲーション(`goto()`, `replaceState()`)の引数に `resolve()` を適用する。 +**注意:** 引数が既に絶対URL(`http://...` や `$page.url.href` 等)の場合は `resolve()` でラップしてはならない。 +実装前に各変数・関数の戻り値を確認すること。 + +対象: 3ファイル、5箇所 + +## タスク + +### Task 4-1: AuthForm.svelte の `goto()` + +**対象ファイル:** `src/lib/components/AuthForm.svelte` +**対象行:** 83行目 + +- [ ] 83行目の `goto(HOME_PAGE)` を確認 +- [ ] `HOME_PAGE` 定数の値を確認(`/` や `/home` 等の相対パスであることを確認) +- [ ] `goto(resolve(HOME_PAGE))` に変更 +- [ ] Phase 3 で追加した `resolve` import が既にあることを確認(なければ追加) + +### Task 4-2: workbooks/+page.svelte の `goto()` + +**対象ファイル:** `src/routes/workbooks/+page.svelte` +**対象行:** 50行目、68行目、72行目、76行目 + +- [ ] ファイルを読み込み、各行の `goto()` 引数を確認 +- [ ] 50行目: `saved` 変数の内容を確認 + - `onMount` 内で使用。`saved` が相対パス(`/workbooks?tab=...`)であれば `goto(resolve(saved), { replaceState: true })` に変更 + - `saved` が絶対URLの場合は `resolve()` を適用しない(要確認) +- [ ] 68行目: `goto(TAB_URL_BUILDERS[tab]())` → URLビルダーの戻り値が相対パスであることを確認して `goto(resolve(TAB_URL_BUILDERS[tab]()))` に変更 +- [ ] 72行目: `goto(buildWorkbooksUrl(...))` → 同様に確認して `resolve()` を適用 +- [ ] 76行目: `goto(buildWorkbooksUrl(...))` → 同様に確認して `resolve()` を適用 +- [ ] `import { resolve } from '$app/paths'` を追加 + +### Task 4-3: KanbanBoard.svelte の `replaceState()` + +**対象ファイル:** `src/routes/(admin)/workbooks/order/_components/KanbanBoard.svelte` +**対象行:** 57行目 + +- [ ] ファイルを読み込み、57行目の `replaceState(buildUpdatedUrl(...), {})` を確認 +- [ ] `buildUpdatedUrl` 関数の実装・戻り値を確認(`pathname + search` 形式の相対パスであることを確認) +- [ ] `replaceState(resolve(buildUpdatedUrl(...)), {})` に変更 +- [ ] `import { resolve } from '$app/paths'` を追加 + +### Task 4-4: 動作確認 + +- [ ] `pnpm lint` で `svelte/no-navigation-without-resolve` のエラーが **0件** になったことを確認 + +## 完了条件 + +`svelte/no-navigation-without-resolve` に関する全エラー(Phase 2〜4)が解消し、`pnpm lint` のエラーが 0 件になること。 diff --git a/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-5.md b/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-5.md new file mode 100644 index 000000000..39ab6b5a2 --- /dev/null +++ b/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-5.md @@ -0,0 +1,67 @@ +# Phase 5: `{#each}` ブロックへのキー追加 + +## 概要 + +`svelte/require-each-key` 警告に対応する。各 `{#each items as item}` ブロックに `(key)` 式を追加する。 + +**キーの選択基準(優先順):** +1. `.id` — エンティティID(ワークブック、タグ等) +2. `.task_id` / `.contest_id` — タスク系 +3. `.path` / `.value` — ナビゲーションリンク、選択肢 +4. ユニーク文字列フィールド(タイトル等) +5. インデックス `i` — 最終手段(固有IDがない静的リスト等) + +**変換パターン:** +```svelte +{#each items as item} → {#each items as item (item.id)} +{#each items as item, i} → {#each items as item, i (item.id)} +``` + +対象: 20ファイル、約25箇所 + +## タスク + +### Task 5-1: Header.svelte(3箇所) + +**対象ファイル:** `src/lib/components/Header.svelte` +**対象行:** 53行目、62行目、110行目 + +- [ ] ファイルを読み込んでアイテム構造を確認 +- [ ] 53行目 (`navbarDashboardLinks`): `(navbarDashboardLink.path)` をキーに追加 +- [ ] 62行目 (`navbarLinks`): `(navbarLink.path)` をキーに追加 +- [ ] 110行目 (`externalLinks`): `(externalLink.path)` をキーに追加 + +### Task 5-2: lib/components 各種(9ファイル) + +以下の各ファイルを読み込み、アイテムの構造に応じたキーを追加する: + +- [ ] `src/lib/components/LabelWithTooltips.svelte:28` — アイテム構造確認してキー追加 +- [ ] `src/lib/components/SubmissionStatus/UpdatingDropdown.svelte:201` — 同上 +- [ ] `src/lib/components/TagForm.svelte:86` — 同上 +- [ ] `src/lib/components/TagListForEdit.svelte:38, 42` — 同上 +- [ ] `src/lib/components/TaskGradeList.svelte:49` — `.grade` 等でキー追加 +- [ ] `src/lib/components/TaskGrades/GradeGuidelineTable.svelte:44` — 同上 +- [ ] `src/lib/components/TaskList.svelte:98` — `.task_id` でキー追加 +- [ ] `src/lib/components/TaskListForEdit.svelte:40, 45, 52, 59` — 同上 +- [ ] `src/lib/components/TaskListSorted.svelte:35` — `.task_id` でキー追加 +- [ ] `src/lib/components/TaskSearchBox.svelte:171` — 同上 +- [ ] `src/lib/components/ThermometerProgressBar.svelte:82, 99` — `.name` 等でキー追加 + +### Task 5-3: routes 各種(7ファイル) + +- [ ] `src/features/tasks/components/contest-table/TaskTable.svelte:193` — アイテム構造確認してキー追加 +- [ ] `src/features/workbooks/components/detail/WorkBookTasksTable.svelte:169` — 同上 +- [ ] `src/routes/(admin)/account_transfer/+page.svelte:127` — 同上 +- [ ] `src/routes/(admin)/workbooks/order/_components/ColumnSelector.svelte:31` — `option.value` でキー追加 +- [ ] `src/routes/(admin)/workbooks/order/_components/KanbanBoard.svelte:153, 166` — 同上 +- [ ] `src/routes/about/SectionSnippets.svelte:38, 140` — 同上 +- [ ] `src/routes/problems/[slug]/+page.svelte:64` — 同上 +- [ ] `src/routes/workbooks/[slug]/+page.svelte:162` — 同上 + +### Task 5-4: 動作確認 + +- [ ] `pnpm lint` で `svelte/require-each-key` の warning が 0 件になったことを確認 + +## 完了条件 + +`svelte/require-each-key` に関する全 warning が解消していること。 diff --git a/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-6.md b/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-6.md new file mode 100644 index 000000000..b9815d91c --- /dev/null +++ b/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-6.md @@ -0,0 +1,52 @@ +# Phase 6: `$derived` リファクタリング + +## 概要 + +`svelte/prefer-writable-derived` 警告に対応する。 +`$state` + `$effect` で値を計算しているパターンを `$derived` に書き換える。 + +**変換パターン:** +```typescript +// Before +let value = $state(initialValue); +$effect(() => { + value = computeFrom(deps); +}); + +// After +let value = $derived(computeFrom(deps)); +``` + +対象: 2ファイル、3箇所 + +## タスク + +### Task 6-1: ThermometerProgressBar.svelte + +**対象ファイル:** `src/lib/components/ThermometerProgressBar.svelte` +**対象行:** 20行目(`submissionRatios`)、21行目(`submissionCounts`) + +- [ ] ファイルを読み込み、`submissionRatios` と `submissionCounts` の `$state` + `$effect` パターンを確認 +- [ ] `submissionRatios`: `$state([])` と対応する `$effect` ブロックを削除し、`$derived(() => filteredStatuses.map(...))` に置き換え +- [ ] `submissionCounts`: 同様に `$derived` に置き換え +- [ ] テンプレート内での使用(82行目、99行目の `{#each}`)は変更不要(`$derived` も同様にアクセス可能) +- [ ] `pnpm check` で型エラーがないことを確認 + +### Task 6-2: workbooks/[slug]/+page.svelte + +**対象ファイル:** `src/routes/workbooks/[slug]/+page.svelte` +**対象行:** 34行目(`taskResults`) + +- [ ] ファイルを読み込み、`taskResults` の `$state` + `$effect` パターンを確認 +- [ ] `let taskResults: Map = $state(new Map())` と対応する `$effect(() => { taskResults = data.taskResults; })` を確認 +- [ ] `let taskResults = $derived(data.taskResults)` に置き換え(型注釈は必要に応じて維持) +- [ ] `pnpm check` で型エラーがないことを確認 + +### Task 6-3: 動作確認 + +- [ ] `pnpm lint` で `svelte/prefer-writable-derived` の warning が 0 件になったことを確認 +- [ ] `pnpm check` で Svelte 型エラーがないことを確認 + +## 完了条件 + +`svelte/prefer-writable-derived` に関する全 warning が解消し、型エラーがないこと。 diff --git a/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-7.md b/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-7.md new file mode 100644 index 000000000..0e775a66e --- /dev/null +++ b/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-7.md @@ -0,0 +1,31 @@ +# Phase 7: `any` 型の解消 + +## 概要 + +`@typescript-eslint/no-explicit-any` 警告に対応する。 +`TaskTable.svelte` の `ProviderData` インターフェースの `metadata` フィールドに適切な型を付与する。 + +対象: 1ファイル、1箇所 + +## タスク + +### Task 7-1: ContestTableProvider の metadata 型を確認 + +**対象ファイル:** `src/features/tasks/components/contest-table/TaskTable.svelte` +**対象行:** 58行目 + +- [ ] `ContestTableProvider` インターフェース/クラスの `getMetadata()` 戻り値型を確認 + - `src/features/tasks/` 以下のプロバイダー実装を Grep で探す +- [ ] `getMetadata()` が返すオブジェクトの構造を把握する + +### Task 7-2: metadata フィールドの型を変更 + +- [ ] `ProviderData` インターフェースの `metadata: any` を適切な型(`getMetadata()` の戻り値型)に変更 + - プロバイダー型が既に存在する場合はそれを使用 + - 存在しない場合はインラインで `{ abbreviationName: string; ... }` 等のオブジェクト型を定義 +- [ ] `pnpm check` で型エラーがないことを確認 +- [ ] `pnpm lint` で `@typescript-eslint/no-explicit-any` warning が解消したことを確認 + +## 完了条件 + +`metadata` フィールドの型が `any` でなくなり、lint warning と型エラーがないこと。 diff --git a/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-8.md b/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-8.md new file mode 100644 index 000000000..22e375ac3 --- /dev/null +++ b/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-8.md @@ -0,0 +1,43 @@ +# Phase 8: `valid-prop-names-in-kit-pages` 調査・修正 + +## 概要 + +`svelte/valid-prop-names-in-kit-pages` 警告に対応する。 +SvelteKit の `+page.svelte` コンポーネントでは、SvelteKit が渡すプロップは `data` と `form` のみ。 +他のプロップ(`formAction`, `status` 等)は非標準であり、警告が発生する。 + +対象: 2ファイル + +## タスク + +### Task 8-1: account_transfer/+page.svelte の `formAction` 調査 + +**対象ファイル:** `src/routes/(admin)/account_transfer/+page.svelte` +**対象行:** 30行目 + +- [ ] ファイルを読み込み、`formAction` prop の定義と使用箇所を確認 +- [ ] `formAction` が外部から実際にバインドされているか確認(parent layout や他ファイルからのバインド有無) +- [ ] 判断: + - 外部からバインドされていない → `let formAction = $state('account_transfer')` に変換して `$props()` から削除 + - 外部からバインドされている → そのままにして `// eslint-disable-next-line svelte/valid-prop-names-in-kit-pages` で抑制し、コメントで理由を記載 + +### Task 8-2: users/edit/+page.svelte の `status` 調査 + +**対象ファイル:** `src/routes/users/edit/+page.svelte` +**対象行:** 28行目 + +- [ ] ファイルを読み込み、`status = $bindable('nothing')` の定義と使用箇所を確認 +- [ ] `status` が外部からバインドされているか確認(parent layout や他ファイルからのバインド有無) +- [ ] 判断: + - 外部からバインドされていない → `let status = $state('nothing')` に変換して `$props()` から削除 + - 外部からバインドされている → `// eslint-disable-next-line svelte/valid-prop-names-in-kit-pages` で抑制し、コメントで理由を記載 + +### Task 8-3: 動作確認 + +- [ ] `pnpm lint` で `svelte/valid-prop-names-in-kit-pages` の warning が解消したことを確認 +- [ ] `pnpm check` で型エラーがないことを確認 +- [ ] `pnpm test:unit` で既存テストが全パスすることを確認 + +## 完了条件 + +`valid-prop-names-in-kit-pages` の warning が解消(または適切なコメントで抑制)されており、既存テストが全パスすること。 diff --git a/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/plan.md b/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/plan.md new file mode 100644 index 000000000..c295c70f4 --- /dev/null +++ b/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/plan.md @@ -0,0 +1,88 @@ +# eslint-plugin-svelte v3.10.1 → v3.16.0 アップデート対応 + +## 概要 + +PR #3297(定期依存アップデート)で eslint-plugin-svelte を v3.10.1 から v3.16.0 へ更新したことにより、CI の lint チェックが失敗している。本計画では新規追加・severity 変更されたルールへの対応を行い、CI を通過させる。 + +エラー(CI blocking)を優先して修正し、可能な限り warning も解消する。 + +## 変更内容と背景 + +v3.10.1 → v3.16.0 で追加・変更されたルール: + +| ルール | 旧 | 新 | 内容 | +|--------|----|----|------| +| `svelte/no-navigation-without-resolve` | なし | error | 内部ナビゲーションに `resolve()` の使用を強制 | +| `svelte/prefer-svelte-reactivity` | なし | error | `Map`/`Set` 等の代わりに `SvelteMap` 等を要求 | +| `svelte/require-each-key` | なし/warn | warn | `{#each}` にキーを要求 | +| `svelte/prefer-writable-derived` | なし | warn | `$state` + `$effect` → `$derived` を推奨 | + +**`resolve()` の実体:** `import { resolve } from '$app/paths'` +SvelteKit 2.55.0 で実装済み。`resolveRoute` は deprecated となり `resolve` に統一された。 +ベースパスをパスに付与する純粋な関数。除外条件: 絶対URL、`rel="external"` 属性、空文字列、hash fragment。 + +**`SvelteMap` の実体:** `import { SvelteMap } from 'svelte/reactivity'` +Svelte 5 でリアクティブな Map として提供。ミュータブルな操作(`.set()`, `.clear()` 等)をトリガーとする。 + +## 設計方針 + +- **機械的置き換えを基本とする**: ルールの意図に従い、最小限の変更で修正する +- **外部リンクは `rel="external"` で明示**: `resolve()` で外部URLをラップしない。`rel` 属性に `external` を追加することで rule を満足させる +- **`resolve()` は内部パスのみに適用**: 動的URLの場合は変数の内容(相対パスか絶対URLか)を確認してから適用 +- **警告は全件修正を目指す**: `valid-prop-names-in-kit-pages` のみコード調査が必要なため最後に対処 + +## 却下した代替案 + +| 案 | 却下理由 | +|----|---------| +| `no-navigation-without-resolve` をルール設定で無効化 | ルールの意図(base path 対応)は正当であり、将来のデプロイ設定変更に備えるべき | +| `// eslint-disable-next-line` で個別無効化 | 件数が多く、保守性が下がる。真に必要な箇所(外部リンク等)に限定すべき | +| `SvelteMap` を `Map` の型エイリアスで代替 | ルールは型ではなくコンストラクタ呼び出しを検出するため回避不可 | +| PR を revert して別途対応 | アップデート内容自体は問題なく、lint 修正のみで対応可能 | + +## 影響ファイル + +### エラー修正対象 + +| ファイル | ルール | 件数 | +|----------|--------|------| +| `src/features/tasks/components/contest-table/TaskTable.svelte` | prefer-svelte-reactivity | 2 | +| `src/lib/components/TaskGradeList.svelte` | prefer-svelte-reactivity | 1 | +| `src/lib/components/ExternalLinkWrapper.svelte` | no-navigation-without-resolve | 1 | +| `src/lib/components/TagForm.svelte` | no-navigation-without-resolve | 4 | +| `src/routes/problems/[slug]/+page.svelte` | no-navigation-without-resolve | 1 | +| `src/features/workbooks/components/list/TitleTableBodyCell.svelte` | no-navigation-without-resolve | 1 | +| `src/features/workbooks/components/list/WorkbookAuthorActionsCell.svelte` | no-navigation-without-resolve | 1 | +| `src/features/workbooks/components/shared/WorkbookLink.svelte` | no-navigation-without-resolve | 1 | +| `src/lib/components/AuthForm.svelte` | no-navigation-without-resolve | 3 | +| `src/lib/components/TagListForEdit.svelte` | no-navigation-without-resolve | 1 | +| `src/lib/components/TaskList.svelte` | no-navigation-without-resolve | 1 | +| `src/lib/components/TaskListSorted.svelte` | no-navigation-without-resolve | 1 | +| `src/routes/users/[username]/+page.svelte` | no-navigation-without-resolve | 1 | +| `src/routes/workbooks/+page.svelte` | no-navigation-without-resolve | 4 | +| `src/routes/(admin)/workbooks/order/_components/KanbanBoard.svelte` | no-navigation-without-resolve | 1 | + +### 警告修正対象(20ファイル超、require-each-key が大半) + +詳細は各 phase ファイルを参照。 + +## フェーズ一覧 + +| Phase | 内容 | 種別 | リスク | +|-------|------|------|--------| +| [Phase 1](./phase-1.md) | `SvelteMap` 置き換え | error | 低 | +| [Phase 2](./phase-2.md) | 外部リンクに `rel="external"` 追加 | error | 低 | +| [Phase 3](./phase-3.md) | 内部 href に `resolve()` 適用 | error | 低 | +| [Phase 4](./phase-4.md) | `goto()` / `replaceState()` に `resolve()` 適用 | error | 中(動的URL要確認)| +| [Phase 5](./phase-5.md) | `{#each}` ブロックへのキー追加 | warning | 低 | +| [Phase 6](./phase-6.md) | `$derived` リファクタリング | warning | 低 | +| [Phase 7](./phase-7.md) | `any` 型の解消 | warning | 低 | +| [Phase 8](./phase-8.md) | `valid-prop-names-in-kit-pages` 調査・修正 | warning | 中(要コード調査)| + +## 検証方法 + +```bash +pnpm lint # 0 errors、warning 最小化 +pnpm check # Svelte 型エラーなし +pnpm test:unit # 既存テスト全パス +``` From 5d1529ce7f09c859ab4af1b5383b0c7941c2761f Mon Sep 17 00:00:00 2001 From: Kato Hiroki Date: Sun, 22 Mar 2026 21:24:14 +0000 Subject: [PATCH 02/11] docs: Format markdown tables in eslint-plugin-svelte update plan Co-Authored-By: Claude Sonnet 4.6 --- .../eslint-plugin-svelte-update/phase-2.md | 2 +- .../eslint-plugin-svelte-update/phase-3.md | 3 + .../eslint-plugin-svelte-update/phase-5.md | 2 + .../eslint-plugin-svelte-update/phase-6.md | 1 + .../eslint-plugin-svelte-update/plan.md | 76 +++++++++---------- 5 files changed, 45 insertions(+), 39 deletions(-) diff --git a/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-2.md b/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-2.md index dcd31568b..737ee152d 100644 --- a/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-2.md +++ b/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-2.md @@ -16,7 +16,7 @@ - [ ] ファイルを読み込んで現在の `rel` 属性を確認 - [ ] `rel="noreferrer"` → `rel="noreferrer external"` に変更 - (`noreferrer` はセキュリティのため維持。`external` を追加してリンターに外部リンクであることを示す) + (`noreferrer` はセキュリティのため維持。`external` を追加してリンターに外部リンクであることを示す) ### Task 2-2: TagForm.svelte(AtCoder外部リンク) diff --git a/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-3.md b/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-3.md index 6646898d0..7be95c34e 100644 --- a/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-3.md +++ b/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-3.md @@ -6,6 +6,7 @@ 各ファイルに `import { resolve } from '$app/paths'` を追加し、内部パス文字列を `resolve(...)` でラップする。 **変換パターン:** + - `href="/foo"` → `href={resolve('/foo')}` - `href="/foo/{var}"` → `href={resolve(`/foo/${var}`)}` - `href={CONST_PATH}` → `href={resolve(CONST_PATH)}` @@ -17,6 +18,7 @@ ### Task 3-1: workbook 関連コンポーネント(3ファイル) **対象ファイル:** + - `src/features/workbooks/components/list/TitleTableBodyCell.svelte` (23行目) - `src/features/workbooks/components/list/WorkbookAuthorActionsCell.svelte` (25行目) - `src/features/workbooks/components/shared/WorkbookLink.svelte` (11行目) @@ -55,6 +57,7 @@ ### Task 3-5: TaskList.svelte / TaskListSorted.svelte **対象ファイル:** + - `src/lib/components/TaskList.svelte` (131行目) - `src/lib/components/TaskListSorted.svelte` (46行目) diff --git a/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-5.md b/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-5.md index 39ab6b5a2..f2bafbc10 100644 --- a/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-5.md +++ b/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-5.md @@ -5,6 +5,7 @@ `svelte/require-each-key` 警告に対応する。各 `{#each items as item}` ブロックに `(key)` 式を追加する。 **キーの選択基準(優先順):** + 1. `.id` — エンティティID(ワークブック、タグ等) 2. `.task_id` / `.contest_id` — タスク系 3. `.path` / `.value` — ナビゲーションリンク、選択肢 @@ -12,6 +13,7 @@ 5. インデックス `i` — 最終手段(固有IDがない静的リスト等) **変換パターン:** + ```svelte {#each items as item} → {#each items as item (item.id)} {#each items as item, i} → {#each items as item, i (item.id)} diff --git a/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-6.md b/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-6.md index b9815d91c..46913ba6b 100644 --- a/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-6.md +++ b/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/phase-6.md @@ -6,6 +6,7 @@ `$state` + `$effect` で値を計算しているパターンを `$derived` に書き換える。 **変換パターン:** + ```typescript // Before let value = $state(initialValue); diff --git a/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/plan.md b/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/plan.md index c295c70f4..af9103de4 100644 --- a/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/plan.md +++ b/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/plan.md @@ -10,12 +10,12 @@ PR #3297(定期依存アップデート)で eslint-plugin-svelte を v3.10.1 v3.10.1 → v3.16.0 で追加・変更されたルール: -| ルール | 旧 | 新 | 内容 | -|--------|----|----|------| -| `svelte/no-navigation-without-resolve` | なし | error | 内部ナビゲーションに `resolve()` の使用を強制 | -| `svelte/prefer-svelte-reactivity` | なし | error | `Map`/`Set` 等の代わりに `SvelteMap` 等を要求 | -| `svelte/require-each-key` | なし/warn | warn | `{#each}` にキーを要求 | -| `svelte/prefer-writable-derived` | なし | warn | `$state` + `$effect` → `$derived` を推奨 | +| ルール | 旧 | 新 | 内容 | +| -------------------------------------- | --------- | ----- | --------------------------------------------- | +| `svelte/no-navigation-without-resolve` | なし | error | 内部ナビゲーションに `resolve()` の使用を強制 | +| `svelte/prefer-svelte-reactivity` | なし | error | `Map`/`Set` 等の代わりに `SvelteMap` 等を要求 | +| `svelte/require-each-key` | なし/warn | warn | `{#each}` にキーを要求 | +| `svelte/prefer-writable-derived` | なし | warn | `$state` + `$effect` → `$derived` を推奨 | **`resolve()` の実体:** `import { resolve } from '$app/paths'` SvelteKit 2.55.0 で実装済み。`resolveRoute` は deprecated となり `resolve` に統一された。 @@ -33,34 +33,34 @@ Svelte 5 でリアクティブな Map として提供。ミュータブルな操 ## 却下した代替案 -| 案 | 却下理由 | -|----|---------| +| 案 | 却下理由 | +| ---------------------------------------------------- | ------------------------------------------------------------------------------ | | `no-navigation-without-resolve` をルール設定で無効化 | ルールの意図(base path 対応)は正当であり、将来のデプロイ設定変更に備えるべき | -| `// eslint-disable-next-line` で個別無効化 | 件数が多く、保守性が下がる。真に必要な箇所(外部リンク等)に限定すべき | -| `SvelteMap` を `Map` の型エイリアスで代替 | ルールは型ではなくコンストラクタ呼び出しを検出するため回避不可 | -| PR を revert して別途対応 | アップデート内容自体は問題なく、lint 修正のみで対応可能 | +| `// eslint-disable-next-line` で個別無効化 | 件数が多く、保守性が下がる。真に必要な箇所(外部リンク等)に限定すべき | +| `SvelteMap` を `Map` の型エイリアスで代替 | ルールは型ではなくコンストラクタ呼び出しを検出するため回避不可 | +| PR を revert して別途対応 | アップデート内容自体は問題なく、lint 修正のみで対応可能 | ## 影響ファイル ### エラー修正対象 -| ファイル | ルール | 件数 | -|----------|--------|------| -| `src/features/tasks/components/contest-table/TaskTable.svelte` | prefer-svelte-reactivity | 2 | -| `src/lib/components/TaskGradeList.svelte` | prefer-svelte-reactivity | 1 | -| `src/lib/components/ExternalLinkWrapper.svelte` | no-navigation-without-resolve | 1 | -| `src/lib/components/TagForm.svelte` | no-navigation-without-resolve | 4 | -| `src/routes/problems/[slug]/+page.svelte` | no-navigation-without-resolve | 1 | -| `src/features/workbooks/components/list/TitleTableBodyCell.svelte` | no-navigation-without-resolve | 1 | -| `src/features/workbooks/components/list/WorkbookAuthorActionsCell.svelte` | no-navigation-without-resolve | 1 | -| `src/features/workbooks/components/shared/WorkbookLink.svelte` | no-navigation-without-resolve | 1 | -| `src/lib/components/AuthForm.svelte` | no-navigation-without-resolve | 3 | -| `src/lib/components/TagListForEdit.svelte` | no-navigation-without-resolve | 1 | -| `src/lib/components/TaskList.svelte` | no-navigation-without-resolve | 1 | -| `src/lib/components/TaskListSorted.svelte` | no-navigation-without-resolve | 1 | -| `src/routes/users/[username]/+page.svelte` | no-navigation-without-resolve | 1 | -| `src/routes/workbooks/+page.svelte` | no-navigation-without-resolve | 4 | -| `src/routes/(admin)/workbooks/order/_components/KanbanBoard.svelte` | no-navigation-without-resolve | 1 | +| ファイル | ルール | 件数 | +| ------------------------------------------------------------------------- | ----------------------------- | ---- | +| `src/features/tasks/components/contest-table/TaskTable.svelte` | prefer-svelte-reactivity | 2 | +| `src/lib/components/TaskGradeList.svelte` | prefer-svelte-reactivity | 1 | +| `src/lib/components/ExternalLinkWrapper.svelte` | no-navigation-without-resolve | 1 | +| `src/lib/components/TagForm.svelte` | no-navigation-without-resolve | 4 | +| `src/routes/problems/[slug]/+page.svelte` | no-navigation-without-resolve | 1 | +| `src/features/workbooks/components/list/TitleTableBodyCell.svelte` | no-navigation-without-resolve | 1 | +| `src/features/workbooks/components/list/WorkbookAuthorActionsCell.svelte` | no-navigation-without-resolve | 1 | +| `src/features/workbooks/components/shared/WorkbookLink.svelte` | no-navigation-without-resolve | 1 | +| `src/lib/components/AuthForm.svelte` | no-navigation-without-resolve | 3 | +| `src/lib/components/TagListForEdit.svelte` | no-navigation-without-resolve | 1 | +| `src/lib/components/TaskList.svelte` | no-navigation-without-resolve | 1 | +| `src/lib/components/TaskListSorted.svelte` | no-navigation-without-resolve | 1 | +| `src/routes/users/[username]/+page.svelte` | no-navigation-without-resolve | 1 | +| `src/routes/workbooks/+page.svelte` | no-navigation-without-resolve | 4 | +| `src/routes/(admin)/workbooks/order/_components/KanbanBoard.svelte` | no-navigation-without-resolve | 1 | ### 警告修正対象(20ファイル超、require-each-key が大半) @@ -68,16 +68,16 @@ Svelte 5 でリアクティブな Map として提供。ミュータブルな操 ## フェーズ一覧 -| Phase | 内容 | 種別 | リスク | -|-------|------|------|--------| -| [Phase 1](./phase-1.md) | `SvelteMap` 置き換え | error | 低 | -| [Phase 2](./phase-2.md) | 外部リンクに `rel="external"` 追加 | error | 低 | -| [Phase 3](./phase-3.md) | 内部 href に `resolve()` 適用 | error | 低 | -| [Phase 4](./phase-4.md) | `goto()` / `replaceState()` に `resolve()` 適用 | error | 中(動的URL要確認)| -| [Phase 5](./phase-5.md) | `{#each}` ブロックへのキー追加 | warning | 低 | -| [Phase 6](./phase-6.md) | `$derived` リファクタリング | warning | 低 | -| [Phase 7](./phase-7.md) | `any` 型の解消 | warning | 低 | -| [Phase 8](./phase-8.md) | `valid-prop-names-in-kit-pages` 調査・修正 | warning | 中(要コード調査)| +| Phase | 内容 | 種別 | リスク | +| ----------------------- | ----------------------------------------------- | ------- | ------------------- | +| [Phase 1](./phase-1.md) | `SvelteMap` 置き換え | error | 低 | +| [Phase 2](./phase-2.md) | 外部リンクに `rel="external"` 追加 | error | 低 | +| [Phase 3](./phase-3.md) | 内部 href に `resolve()` 適用 | error | 低 | +| [Phase 4](./phase-4.md) | `goto()` / `replaceState()` に `resolve()` 適用 | error | 中(動的URL要確認) | +| [Phase 5](./phase-5.md) | `{#each}` ブロックへのキー追加 | warning | 低 | +| [Phase 6](./phase-6.md) | `$derived` リファクタリング | warning | 低 | +| [Phase 7](./phase-7.md) | `any` 型の解消 | warning | 低 | +| [Phase 8](./phase-8.md) | `valid-prop-names-in-kit-pages` 調査・修正 | warning | 中(要コード調査) | ## 検証方法 From 22d0c101767523b300a8d6ed66e1511e3dbe1fd6 Mon Sep 17 00:00:00 2001 From: Kato Hiroki Date: Mon, 23 Mar 2026 00:55:52 +0000 Subject: [PATCH 03/11] docs: Add background rationale for resolve(), SvelteMap, and prefer-writable-derived in eslint-plugin-svelte update plan Co-Authored-By: Claude Sonnet 4.6 --- .../eslint-plugin-svelte-update/plan.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/plan.md b/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/plan.md index af9103de4..203023260 100644 --- a/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/plan.md +++ b/docs/dev-notes/2026-03-22/eslint-plugin-svelte-update/plan.md @@ -21,9 +21,27 @@ v3.10.1 → v3.16.0 で追加・変更されたルール: SvelteKit 2.55.0 で実装済み。`resolveRoute` は deprecated となり `resolve` に統一された。 ベースパスをパスに付与する純粋な関数。除外条件: 絶対URL、`rel="external"` 属性、空文字列、hash fragment。 +**導入背景:** SvelteKit はルートパス以外(例: `example.com/my-app/`)へのデプロイをサポートするが、`` や `goto('/foo')` のようにパスをハードコードすると base path が加味されずナビゲーションが壊れる。`resolve()` は base path を実行時に付与する純粋関数としてこれを解決する。JS のモジュール解決(webpack alias 等)と概念は近いが、モジュール解決がビルド時に静的確定するのに対し、`resolve()` は base path がデプロイ設定に依存して実行時まで確定しないため、明示的な関数呼び出しとして設計されている。`eslint-plugin-svelte` v3.12.0 でルール化された。 + +**メリット:** base path が変わってもコードの修正不要 / `resolve('/blog/[slug]', { slug })` 形式でルートパラメータをコンパイル時に型検査できる / 全内部ナビゲーションが一貫した経路を通るため挙動が予測しやすい。 + +**デメリット:** 全コンポーネントに import が必要でボイラープレートが増加 / base path を使わないプロジェクトでは恩恵が見えにくく冗長に感じる / 既存コードへの適用コストが高い(本件がまさにそれ)。 + +> 出典: [$app/paths — SvelteKit 公式ドキュメント](https://svelte.dev/docs/kit/$app-paths) / [no-navigation-without-resolve — eslint-plugin-svelte](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/docs/rules/no-navigation-without-resolve.md) + **`SvelteMap` の実体:** `import { SvelteMap } from 'svelte/reactivity'` Svelte 5 でリアクティブな Map として提供。ミュータブルな操作(`.set()`, `.clear()` 等)をトリガーとする。 +**導入背景:** Svelte 5 のリアクティビティはプロキシベースで動作するが、`Map` / `Set` / `Date` などのネイティブ組み込みオブジェクトはエンジン内部実装のためプロキシが透過的にトラップできない。そのため `$state(new Map())` としても `.set()` などの変更が `$derived` / `$effect` に伝播しない。`SvelteMap` は `Map` のサブクラスとして全メソッドをオーバーライドし、Svelte のリアクティブシグナルと連携させることでこの制約を解消している。なお、値の深いリアクティビティ(ネストオブジェクトのプロパティ変更)は対象外(shallow のみ)。 + +リアクティビティの仕組みは**読み取りと書き込みの2方向**で成立する。読み取り側(`$derived` / `$effect` 内での `.get()` / `.size` / イテレーション)が依存を登録し、書き込み側(`.set()` / `.delete()` / `.clear()`)がその依存を無効化・再実行させる。「ミュータブルな操作をトリガーとする」とはこの書き込み側の通知発火を指す。 + +> 出典: [svelte/reactivity — Svelte 公式ドキュメント](https://svelte.dev/docs/svelte/svelte-reactivity) + +**`prefer-writable-derived` の背景:** Runes の役割分担は `$state`(変更可能な真実の源泉)/ `$derived`(他の state から計算される値)/ `$effect`(副作用 — DOM 操作・API 呼び出し等)と明確に分かれており、`$effect` を計算に使うのはアンチパターン。加えて `$effect` は DOM 更新サイクル後に非同期実行されるため、`$state + $effect` で派生値を更新すると一瞬だけ古い値が残る不整合が生じる。`$derived` は参照時に同期・遅延評価されるため常に一致する。 + +本プロジェクトでは Svelte v4 で実装を始め 2025年2月に v5 へ移行した経緯があり、当時は生成 AI の Svelte 5 学習データが少なく、v4 の `writable + subscribe` パターンを Runes に機械的に置き換えた `$state + $effect` が定着したと考えられる。今回のルール追加はその移行時の技術的負債を ESLint が炙り出している。 + ## 設計方針 - **機械的置き換えを基本とする**: ルールの意図に従い、最小限の変更で修正する From 5ba669307bfd1bff3144b62b3ab97f7e5cbedda4 Mon Sep 17 00:00:00 2001 From: Kato Hiroki Date: Mon, 23 Mar 2026 01:22:33 +0000 Subject: [PATCH 04/11] refactor: Apply eslint-plugin-svelte rules across components (Phase 1-3) - Add keyed {#each} blocks (svelte/require-each-key) - Replace hardcoded hrefs and goto() calls with resolve() ($app/paths) - Convert Map to SvelteMap for reactive updates - Replace $state+$effect patterns with $derived (prefer-writable-derived) - Add rel="noreferrer external" to external links - Remove unused props (status in users/edit, formAction in account_transfer) Co-Authored-By: Claude Sonnet 4.6 --- .../components/contest-table/TaskTable.svelte | 14 +++++++----- .../detail/WorkBookTasksTable.svelte | 2 +- .../components/list/TitleTableBodyCell.svelte | 4 +++- .../list/WorkbookAuthorActionsCell.svelte | 4 +++- .../components/shared/WorkbookLink.svelte | 4 +++- src/lib/components/AuthForm.svelte | 19 ++++++++++------ src/lib/components/ExternalLinkWrapper.svelte | 2 +- src/lib/components/Header.svelte | 6 ++--- src/lib/components/LabelWithTooltips.svelte | 2 +- .../SubmissionStatus/UpdatingDropdown.svelte | 2 +- src/lib/components/TagForm.svelte | 15 ++++++++----- src/lib/components/TagListForEdit.svelte | 9 +++++--- src/lib/components/TaskGradeList.svelte | 11 +++++----- .../TaskGrades/GradeGuidelineTable.svelte | 2 +- src/lib/components/TaskList.svelte | 6 +++-- src/lib/components/TaskListForEdit.svelte | 8 +++---- src/lib/components/TaskListSorted.svelte | 6 +++-- src/lib/components/TaskSearchBox.svelte | 2 +- .../components/ThermometerProgressBar.svelte | 22 +++++++++---------- .../(admin)/account_transfer/+page.svelte | 7 +++--- .../order/_components/ColumnSelector.svelte | 2 +- .../order/_components/KanbanBoard.svelte | 15 ++++++++----- src/routes/about/SectionSnippets.svelte | 4 ++-- src/routes/problems/[slug]/+page.svelte | 4 ++-- src/routes/users/[username]/+page.svelte | 8 ++++++- src/routes/users/edit/+page.svelte | 1 - src/routes/workbooks/+page.svelte | 14 ++++++++---- src/routes/workbooks/[slug]/+page.svelte | 8 ++----- 28 files changed, 121 insertions(+), 82 deletions(-) diff --git a/src/features/tasks/components/contest-table/TaskTable.svelte b/src/features/tasks/components/contest-table/TaskTable.svelte index f88932772..a9a5b4dbc 100644 --- a/src/features/tasks/components/contest-table/TaskTable.svelte +++ b/src/features/tasks/components/contest-table/TaskTable.svelte @@ -1,4 +1,6 @@ e.stopPropagation()} onpointerdown={(e) => e.stopPropagation()} diff --git a/src/lib/components/AuthForm.svelte b/src/lib/components/AuthForm.svelte index fdc44974d..c1a25d9f7 100644 --- a/src/lib/components/AuthForm.svelte +++ b/src/lib/components/AuthForm.svelte @@ -1,6 +1,7 @@ - タグ一覧へもどる(パンくずリストにしたい) + タグ一覧へもどる(パンくずリストにしたい)
Edit Tag
@@ -83,21 +88,21 @@ Edit Tag - {#each tasks as task} + {#each tasks as task (task.task_id)} {getContestNameLabel(task.contest_id)} {task.title} @@ -105,7 +110,7 @@ Edit Tag 編集 diff --git a/src/lib/components/TagListForEdit.svelte b/src/lib/components/TagListForEdit.svelte index 3cc64e9b4..58c8551c6 100644 --- a/src/lib/components/TagListForEdit.svelte +++ b/src/lib/components/TagListForEdit.svelte @@ -1,4 +1,6 @@ -{#each taskGradeValues as taskGrade} +{#each taskGradeValues as taskGrade (taskGrade)} {#if countTasks(taskGrade) && isShowTaskList(isAdmin, taskGrade)} diff --git a/src/lib/components/TaskGrades/GradeGuidelineTable.svelte b/src/lib/components/TaskGrades/GradeGuidelineTable.svelte index 694460119..864cca47e 100644 --- a/src/lib/components/TaskGrades/GradeGuidelineTable.svelte +++ b/src/lib/components/TaskGrades/GradeGuidelineTable.svelte @@ -41,7 +41,7 @@ - {#each gradeGuidelineTableData as { point, task, lowerGrade, upperGrade }} + {#each gradeGuidelineTableData as { point, task, lowerGrade, upperGrade } (point)} {point} diff --git a/src/lib/components/TaskList.svelte b/src/lib/components/TaskList.svelte index 6459925cb..bbfa66303 100644 --- a/src/lib/components/TaskList.svelte +++ b/src/lib/components/TaskList.svelte @@ -1,4 +1,6 @@ @@ -79,7 +77,7 @@
- {#each submissionRatios as submissionRatio} + {#each submissionRatios as submissionRatio (submissionRatio.name)}
- {#each submissionCounts as submissionCount} + {#each submissionCounts as submissionCount (submissionCount.name)}
{submissionCount.name} : diff --git a/src/routes/(admin)/account_transfer/+page.svelte b/src/routes/(admin)/account_transfer/+page.svelte index 569ee8e6a..f8c26a952 100644 --- a/src/routes/(admin)/account_transfer/+page.svelte +++ b/src/routes/(admin)/account_transfer/+page.svelte @@ -22,12 +22,13 @@ import type { FloatingMessages } from '$lib/types/floating_message'; interface Props { - formAction?: string; // eslint-disable-next-line @typescript-eslint/no-explicit-any data: any; } - let { formAction = 'account_transfer', data }: Props = $props(); + let { data }: Props = $props(); + + let formAction = $state('account_transfer'); const { form, errors, message, submitting, enhance } = superForm(data.form); @@ -124,7 +125,7 @@
- {#each accountTransferMessages as accountTransferMessage} + {#each accountTransferMessages as accountTransferMessage, i (i)}
{#if accountTransferMessage.status} diff --git a/src/routes/(admin)/workbooks/order/_components/ColumnSelector.svelte b/src/routes/(admin)/workbooks/order/_components/ColumnSelector.svelte index 7e11fb38d..1042f1431 100644 --- a/src/routes/(admin)/workbooks/order/_components/ColumnSelector.svelte +++ b/src/routes/(admin)/workbooks/order/_components/ColumnSelector.svelte @@ -28,7 +28,7 @@
- {#each options as option} + {#each options as option (option.value)} {@const isSelected = selected.includes(option.value)}