Skip to content

feat: Enable sorting of workbook order in Kanban board (#943)#3252

Merged
KATO-Hiro merged 119 commits intostagingfrom
#943
Mar 16, 2026
Merged

feat: Enable sorting of workbook order in Kanban board (#943)#3252
KATO-Hiro merged 119 commits intostagingfrom
#943

Conversation

@KATO-Hiro
Copy link
Copy Markdown
Collaborator

@KATO-Hiro KATO-Hiro commented Mar 11, 2026

close #943

Summary by CodeRabbit

  • New Features

    • Admin-only workbook ordering page with drag-and-drop Kanban for managing problem set order (solution-category and curriculum-grade views)
    • One-click initialization to add unplaced problem sets to the board
    • Navbar link to the workbook ordering page
  • Bug Fixes / Access

    • Admin access enforced (unauthenticated users redirected to login; API returns proper HTTP errors)
  • Tests

    • Expanded end-to-end and unit tests for ordering, initialization, and access controls

KATO-Hiro and others added 30 commits March 1, 2026 05:53
…dates (#943)

- Move updatePlacements logic from form action to dedicated POST /workbooks/order endpoint
- Add E2E tests for Kanban board drag-and-drop persistence
- Fix URL param cleanup when switching between solution/curriculum tabs
- Add data-testid attributes to KanbanCard and KanbanColumn for testability

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix onDragEnd not updating card's column assignment on cross-column moves
- Add priority sort in buildInitialCards to ensure consistent initial order
- Replace data-testid with data-placement-id for semantic attribute usage
- Refactor E2E tests: use API direct calls via page.evaluate instead of mouse drag simulation
- Translate E2E test names to English and use getByRole/getByText locators
- Update bugfix.md with lessons learned

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@KATO-Hiro
Copy link
Copy Markdown
Collaborator Author

@coderabbitai resume

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 16, 2026

✅ Actions performed

Reviews resumed.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
docs/dev-notes/2026-02-28/workbook-order/plan.md (1)

127-127: Consider linking the remaining UX task to a tracked issue.

Line 127 has an open checklist item; adding an issue/PR reference would make follow-up ownership clearer and prevent it from getting stale in long-lived notes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/dev-notes/2026-02-28/workbook-order/plan.md` at line 127, Update the
open checklist item that says
"空のカンバンカラムに「ここに問題集をドロップできます」等のプレースホルダーメッセージを表示(UX改善)" to include a tracked issue
or PR reference and an owner (e.g., append "See #<issue-number> / assigned to
@<owner>"). Ensure the checklist line itself is edited to contain the issue/PR
link or shorthand and an assignee so follow-up is clear and actionable.
prisma/seed.ts (1)

355-371: Type signature mismatch with service function.

The parameter type { id: number; urlSlug: string | null }[] is narrower than what initializeSolutionPlacements expects (per context snippet 2, it accepts { id: number }[]). This works but causes an issue: the .find() on line 363 re-iterates unplacedSolution for every placement, resulting in O(n²) complexity.

Consider building a lookup map for urlSlug to improve performance and simplify the logic:

♻️ Proposed refactor using a Map for O(n) lookup
 async function addSolutionPlacements(unplacedSolution: { id: number; urlSlug: string | null }[]) {
   if (unplacedSolution.length === 0) {
     return;
   }

+  const urlSlugById = new Map(unplacedSolution.map((wb) => [wb.id, wb.urlSlug]));
+
   // Apply solutionCategoryMap overrides; workbooks not listed default to PENDING.
   const placements = initializeSolutionPlacements(unplacedSolution).map(
     (placement: PlacementCreate) => {
-      const workbooks = unplacedSolution.find((workbook) => workbook.id === placement.workBookId);
-      const category = workbooks?.urlSlug ? solutionCategoryMap[workbooks.urlSlug] : undefined;
+      const urlSlug = urlSlugById.get(placement.workBookId);
+      const category = urlSlug ? solutionCategoryMap[urlSlug] : undefined;
       return { ...placement, solutionCategory: category ?? 'PENDING' };
     },
   );

   await createWorkBookPlacements(placements);
   console.log(`Added ${placements.length} solution placements.`);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@prisma/seed.ts` around lines 355 - 371, The addSolutionPlacements function
re-scans unplacedSolution with .find for each placement causing O(n²) and its
parameter type doesn't match initializeSolutionPlacements expectations; fix it
by widening the parameter type to something that includes id and optional
urlSlug (e.g., { id: number; urlSlug?: string | null }[]) or match the
initializeSolutionPlacements signature, then build a Map keyed by id from
unplacedSolution (id -> urlSlug) and use that Map inside the placements mapping
to lookup urlSlug in O(1); keep using solutionCategoryMap to map urlSlug to a
category (falling back to 'PENDING'), then pass the resulting placements to
createWorkBookPlacements (referencing addSolutionPlacements,
initializeSolutionPlacements, createWorkBookPlacements, solutionCategoryMap, and
PlacementCreate).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/features/workbooks/fixtures/workbook_placements.ts`:
- Around line 80-87: Update the fixture entry for workbook id 99 in the
workbooksWithPlacements array so its placement matches the documented PENDING
state; replace the null placement on the workbook object (id: 99, title:
'未分類問題集') with the corresponding placement from solutionPlacements (the entry
with id 104 / SolutionCategory.PENDING, e.g. solutionPlacements[3]) so the
workbook's placement field points to that placement object instead of null.

---

Nitpick comments:
In `@docs/dev-notes/2026-02-28/workbook-order/plan.md`:
- Line 127: Update the open checklist item that says
"空のカンバンカラムに「ここに問題集をドロップできます」等のプレースホルダーメッセージを表示(UX改善)" to include a tracked issue
or PR reference and an owner (e.g., append "See #<issue-number> / assigned to
@<owner>"). Ensure the checklist line itself is edited to contain the issue/PR
link or shorthand and an assignee so follow-up is clear and actionable.

In `@prisma/seed.ts`:
- Around line 355-371: The addSolutionPlacements function re-scans
unplacedSolution with .find for each placement causing O(n²) and its parameter
type doesn't match initializeSolutionPlacements expectations; fix it by widening
the parameter type to something that includes id and optional urlSlug (e.g., {
id: number; urlSlug?: string | null }[]) or match the
initializeSolutionPlacements signature, then build a Map keyed by id from
unplacedSolution (id -> urlSlug) and use that Map inside the placements mapping
to lookup urlSlug in O(1); keep using solutionCategoryMap to map urlSlug to a
category (falling back to 'PENDING'), then pass the resulting placements to
createWorkBookPlacements (referencing addSolutionPlacements,
initializeSolutionPlacements, createWorkBookPlacements, solutionCategoryMap, and
PlacementCreate).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1ff925c1-ebc3-44ee-8060-1c5495e300ec

📥 Commits

Reviewing files that changed from the base of the PR and between 102f6d5 and aeecdcc.

📒 Files selected for processing (21)
  • .claude/rules/auth.md
  • .claude/rules/coding-style.md
  • .claude/rules/prisma-db.md
  • .claude/rules/svelte-components.md
  • .claude/rules/testing.md
  • .claude/skills/refactor-plan/SKILL.md
  • .claude/skills/refactor-plan/instructions.md
  • docs/dev-notes/2026-02-28/workbook-order/plan.md
  • prisma/seed.ts
  • src/features/workbooks/fixtures/solution_category_map.ts
  • src/features/workbooks/fixtures/workbook_placements.ts
  • src/features/workbooks/services/workbook_placements.test.ts
  • src/features/workbooks/services/workbook_placements.ts
  • src/features/workbooks/utils/workbooks.test.ts
  • src/features/workbooks/utils/workbooks.ts
  • src/routes/(admin)/workbooks/order/_components/KanbanBoard.svelte
  • src/routes/(admin)/workbooks/order/_components/KanbanCard.svelte
  • src/routes/(admin)/workbooks/order/_utils/kanban.test.ts
  • src/routes/(admin)/workbooks/order/_utils/kanban.ts
  • src/routes/workbooks/+page.server.ts
  • src/routes/workbooks/+page.svelte
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/features/workbooks/services/workbook_placements.test.ts
  • src/routes/(admin)/workbooks/order/_components/KanbanCard.svelte
  • src/routes/(admin)/workbooks/order/_components/KanbanBoard.svelte
  • src/features/workbooks/services/workbook_placements.ts
  • .claude/skills/refactor-plan/instructions.md
  • .claude/rules/coding-style.md

@KATO-Hiro
Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 16, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
tests/workbook_order.test.ts (2)

59-75: Restoration assumes original priorities were 1 and 2.

The try/finally pattern is good for ensuring cleanup runs even on assertion failure. However, the restoration still uses hardcoded priority values (1 and 2) rather than capturing the actual original priorities. If the seeded data evolves to use different priorities, these tests could corrupt state for subsequent tests.

Consider capturing the original state before mutation:

♻️ Snapshot-based restoration
     const [first, second] = cards;
+    const originalFirst = { ...first, priority: 1 }; // capture from actual data if available
+    const originalSecond = { ...second, priority: 2 };

     // Swap the first two cards via API
     await postUpdates(page, [
       {
         id: first.placementId,
         priority: 2,
         solutionCategory: SolutionCategory.PENDING,
         taskGrade: null,
       },
       // ...
     ]);

     try {
       // assertions...
     } finally {
-      await postUpdates(page, [
-        {
-          id: first.placementId,
-          priority: 1,
-          ...
-        },
-        ...
-      ]);
+      // Restore using captured original state
+      await postUpdates(page, [originalFirst, originalSecond].map(c => ({
+        id: c.placementId,
+        priority: c.priority,
+        solutionCategory: SolutionCategory.PENDING,
+        taskGrade: null,
+      })));
     }

Ideally, fetch actual priority values from the API or include them in getCardsInColumn return type.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/workbook_order.test.ts` around lines 59 - 75, The finally block
restores placements using hardcoded priorities (1 and 2) which can corrupt state
if seeded priorities change; before mutating, capture the original priority
values (e.g. call getCardsInColumn or fetch the placements so you can read
first.priority and second.priority into variables like originalFirstPriority and
originalSecondPriority) and then use those values when calling postUpdates
(referencing postUpdates, first.placementId, second.placementId and
getCardsInColumn) in the finally to restore the exact original state.

280-280: Fragile selector using .nth(1).

Using .nth(1) to select among multiple "ログイン" buttons is brittle—if the page layout changes, this test could click the wrong button or fail. Consider adding a data-testid to the target button or using a more specific selector (e.g., scoping to a form or container).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/workbook_order.test.ts` at line 280, The test uses a brittle selector
page.getByRole('button', { name: 'ログイン' }).nth(1).click(); which can break if
button order changes; replace this with a more specific selector by either
adding and using a data-testid on the intended button or scoping the query to
the parent container/form (e.g., locate the form or container element first and
call getByRole within it) so the test targets the exact login button reliably.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.claude/rules/svelte-components.md:
- Around line 76-80: The "Before" example is incorrect because
URLSearchParams.set() returns void and would pass undefined to replaceState;
change the code so the mutation happens on a URL object and a URL is returned to
the caller—e.g., replace the inline updateUrl implementation that does
replaceState(new URL($page.url).searchParams.set('tab', activeTab)) with a pure
function (buildUpdatedUrl(url: URL, activeTab: ActiveTab): URL) that creates a
new URL, calls url.searchParams.set('tab', activeTab) and returns the modified
URL, and then call replaceState(buildUpdatedUrl($page.url, activeTab), {}) from
the caller.

In `@tests/workbook_order.test.ts`:
- Around line 272-282: The loginAsAdmin test function currently hardcodes
ADMIN_USERNAME and ADMIN_PASSWORD; replace those constants by reading
credentials from environment variables (e.g., process.env.TEST_ADMIN_USERNAME
and process.env.TEST_ADMIN_PASSWORD) in the loginAsAdmin function, and fail fast
with a clear error if they are missing so tests don't silently use empty values;
update any test setup docs to instruct adding these keys to .env.test or CI
secrets and remove the hardcoded strings from the file.

---

Nitpick comments:
In `@tests/workbook_order.test.ts`:
- Around line 59-75: The finally block restores placements using hardcoded
priorities (1 and 2) which can corrupt state if seeded priorities change; before
mutating, capture the original priority values (e.g. call getCardsInColumn or
fetch the placements so you can read first.priority and second.priority into
variables like originalFirstPriority and originalSecondPriority) and then use
those values when calling postUpdates (referencing postUpdates,
first.placementId, second.placementId and getCardsInColumn) in the finally to
restore the exact original state.
- Line 280: The test uses a brittle selector page.getByRole('button', { name:
'ログイン' }).nth(1).click(); which can break if button order changes; replace this
with a more specific selector by either adding and using a data-testid on the
intended button or scoping the query to the parent container/form (e.g., locate
the form or container element first and call getByRole within it) so the test
targets the exact login button reliably.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a4f9c8ae-b232-4da9-a935-b00ed8cf6521

📥 Commits

Reviewing files that changed from the base of the PR and between aeecdcc and 83e6e90.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (6)
  • .claude/rules/svelte-components.md
  • .claude/rules/testing.md
  • AGENTS.md
  • package.json
  • src/lib/constants/http-response-status-codes.ts
  • tests/workbook_order.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • AGENTS.md

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
src/features/workbooks/services/workbook_placements/initializers.ts (1)

70-79: Non-null assertions rely on implicit contract with calcWorkBookGradeModes.

The ! assertions at lines 75, 91, and 92 assume that gradeModes contains entries for all workbook IDs. This is currently safe because calcWorkBookGradeModes iterates the same workbooks array and always calls gradeModes.set(workbook.id, ...) for each workbook (per context snippet from workbooks.ts:34-58).

However, this coupling is implicit. If calcWorkBookGradeModes were to change its behavior (e.g., skip certain workbooks), these assertions would throw at runtime.

Consider defensive handling if the implicit contract concerns you:

Optional defensive approach
 export function groupWorkbooksByGrade(
   workbooks: WorkBooksWithTasks,
   gradeModes: Map<number, TaskGrade>,
 ): Map<TaskGrade, number[]> {
   return workbooks.reduce((byGrade, workbook) => {
-    const grade = gradeModes.get(workbook.id)!;
+    const grade = gradeModes.get(workbook.id) ?? TaskGrade.PENDING;
     const ids = [...(byGrade.get(grade) ?? []), workbook.id].sort((a, b) => a - b);
     return byGrade.set(grade, ids);
   }, new Map<TaskGrade, number[]>());
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/workbooks/services/workbook_placements/initializers.ts` around
lines 70 - 79, The code in groupWorkbooksByGrade uses a non-null assertion on
gradeModes.get(workbook.id) which assumes calcWorkBookGradeModes always
populated the map; make this explicit and defensive by reading const grade =
gradeModes.get(workbook.id) and if grade is undefined either skip that workbook
or assign a safe default TaskGrade, then proceed to build the Map (update
groupWorkbooksByGrade accordingly); reference the gradeModes Map, the
workbook.id lookup, and the groupWorkbooksByGrade function to locate where to
replace the "!" assertion with a guarded check.
src/features/workbooks/services/workbook_placements/crud.test.ts (1)

221-232: Minor: Test description mentions "upsert" but validates "update" behavior.

The test name says "calls upsert" but the implementation uses updateWorkBookPlacements which performs updates, not upserts. This is a minor naming inconsistency that could cause confusion.

Suggested fix
-  test('returns null and calls upsert when all updates are valid', async () => {
+  test('returns null and calls update when all updates are valid', async () => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/workbooks/services/workbook_placements/crud.test.ts` around
lines 221 - 232, The test description is misleading: change the test name in the
validateAndUpdatePlacements suite to mention "update" (or "calls update")
instead of "upsert" since the test exercises updateWorkBookPlacements behavior
and mocks prisma.$transaction; update the test title string to something like
"returns null and calls update when all updates are valid" so it accurately
references validateAndUpdatePlacements and updateWorkBookPlacements.
.claude/rules/svelte-components.md (2)

34-41: Clarify the example to show $props() being referenced.

The text states "Referencing $props() inside $state() initializer triggers [a warning]", but the example doesn't explicitly show $props() being used. The variable initialCount appears without context, making it unclear how it relates to props.

📝 Suggested clarification
-Referencing `$props()` inside `$state()` initializer triggers "This reference only captures the initial value". Wrap with `untrack` if intentional:
+Referencing `$props()` inside `$state()` initializer triggers "This reference only captures the initial value". Wrap with `untrack` if intentional:

 ```svelte
+<script lang="ts">
+  interface Props {
+    initialCount: number;
+  }
+  let { initialCount }: Props = $props();
+
-let count = $state(untrack(() => initialCount)); // intentional: prop is initial seed only
+  // Using prop directly in $state() triggers warning:
+  // let count = $state(initialCount); // ⚠️ "This reference only captures the initial value"
+
+  // Fix: wrap with untrack() to signal intent
+  let count = $state(untrack(() => initialCount)); // ✓ intentional: prop is initial seed only
+</script>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @.claude/rules/svelte-components.md around lines 34 - 41, Update the example
to explicitly show reading a prop via $props() and how it interacts with
$state(): declare and extract initialCount from $props() (e.g., let {
initialCount } = $props()), show the problematic form that uses
$state(initialCount) (commented as triggering the "only captures the initial
value" warning), and then show the corrected form that wraps the prop access in
untrack inside $state(): use $state(untrack(() => initialCount)) and include
short inline comments indicating which line triggers the warning and which line
is the fix; reference the symbols $props, $state, untrack, and initialCount so
readers can find the relevant code.


</details>

---

`82-84`: **Consider expanding the placeholder function body.**

The "After" example shows `export function buildUpdatedUrl(url: URL, activeTab: ActiveTab): URL { ... }` with a placeholder body. Since this section teaches a refactoring pattern, showing the actual implementation would be more instructive.



<details>
<summary>📝 Suggested expansion</summary>

```diff
-// After: pure function in _utils/, side effect in caller
-export function buildUpdatedUrl(url: URL, activeTab: ActiveTab): URL { ... }
-// Caller: replaceState(buildUpdatedUrl($page.url, activeTab), {})
+// After: pure function in _utils/, side effect in caller
+export function buildUpdatedUrl(url: URL, activeTab: ActiveTab): URL {
+  const newUrl = new URL(url);
+  newUrl.searchParams.set('tab', activeTab);
+  return newUrl;
+}
+// Caller in component: replaceState(buildUpdatedUrl($page.url, activeTab), {})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.claude/rules/svelte-components.md around lines 82 - 84, The example uses a
placeholder body for buildUpdatedUrl which weakens the refactoring lesson;
replace the ellipsis with a concrete, pure implementation that accepts a URL and
ActiveTab, clones or constructs a new URL, updates the appropriate query
param/path segment to reflect the activeTab, and returns the new URL so the
caller can do replaceState(buildUpdatedUrl($page.url, activeTab), {}); locate
the exported function buildUpdatedUrl and update its body and ensure the usage
sites (e.g., replaceState(buildUpdatedUrl($page.url, activeTab), {}) or similar
callers) still work with the returned URL.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.claude/rules/coding-style.md:
- Around line 24-26: The guidance is incorrect: update the rules text to state
that in +page.server.ts both error() (for 4xx/5xx responses) and redirect() (for
3xx navigation) are valid, while in +server.ts API endpoints you should use
error() for HTTP error responses; also remove or correct the claim that throwing
redirect() returns HTML to fetch clients—explain that redirect() issues an HTTP
3xx with Location and that fetch clients receive the redirect response (subject
to fetch's redirect handling). Reference the symbols +page.server.ts,
+server.ts, redirect(), and error() so the wording replaces the two inaccurate
lines with the corrected behavior.

In @.claude/rules/svelte-components.md:
- Line 28: Update the rule text to clarify scope and provide a migration path:
state that the new rule applies to all stores (new and existing) and then add a
short migration section showing how to convert existing stores (e.g.,
active_problem_list_tab.svelte.ts and error_message.ts) to the class-based
pattern with the .svelte.ts extension, using the $state() rune and exporting a
singleton; specifically instruct to rename files to .svelte.ts if needed, wrap
writable/factory logic into a class with $state() accessor methods, and replace
direct properties with $state() usage, plus include examples and a checklist
(rename file, convert factory to class, add $state() runes, export singleton)
for maintainers to follow.

---

Nitpick comments:
In @.claude/rules/svelte-components.md:
- Around line 34-41: Update the example to explicitly show reading a prop via
$props() and how it interacts with $state(): declare and extract initialCount
from $props() (e.g., let { initialCount } = $props()), show the problematic form
that uses $state(initialCount) (commented as triggering the "only captures the
initial value" warning), and then show the corrected form that wraps the prop
access in untrack inside $state(): use $state(untrack(() => initialCount)) and
include short inline comments indicating which line triggers the warning and
which line is the fix; reference the symbols $props, $state, untrack, and
initialCount so readers can find the relevant code.
- Around line 82-84: The example uses a placeholder body for buildUpdatedUrl
which weakens the refactoring lesson; replace the ellipsis with a concrete, pure
implementation that accepts a URL and ActiveTab, clones or constructs a new URL,
updates the appropriate query param/path segment to reflect the activeTab, and
returns the new URL so the caller can do replaceState(buildUpdatedUrl($page.url,
activeTab), {}); locate the exported function buildUpdatedUrl and update its
body and ensure the usage sites (e.g., replaceState(buildUpdatedUrl($page.url,
activeTab), {}) or similar callers) still work with the returned URL.

In `@src/features/workbooks/services/workbook_placements/crud.test.ts`:
- Around line 221-232: The test description is misleading: change the test name
in the validateAndUpdatePlacements suite to mention "update" (or "calls update")
instead of "upsert" since the test exercises updateWorkBookPlacements behavior
and mocks prisma.$transaction; update the test title string to something like
"returns null and calls update when all updates are valid" so it accurately
references validateAndUpdatePlacements and updateWorkBookPlacements.

In `@src/features/workbooks/services/workbook_placements/initializers.ts`:
- Around line 70-79: The code in groupWorkbooksByGrade uses a non-null assertion
on gradeModes.get(workbook.id) which assumes calcWorkBookGradeModes always
populated the map; make this explicit and defensive by reading const grade =
gradeModes.get(workbook.id) and if grade is undefined either skip that workbook
or assign a safe default TaskGrade, then proceed to build the Map (update
groupWorkbooksByGrade accordingly); reference the gradeModes Map, the
workbook.id lookup, and the groupWorkbooksByGrade function to locate where to
replace the "!" assertion with a guarded check.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dd51a633-6c6d-4df9-88c1-0cb342a2182f

📥 Commits

Reviewing files that changed from the base of the PR and between f54f3b1 and 0ab9aa9.

📒 Files selected for processing (13)
  • .claude/rules/coding-style.md
  • .claude/rules/svelte-components.md
  • .claude/rules/testing.md
  • docs/dev-notes/2026-02-28/workbook-order/plan.md
  • prisma/seed.ts
  • src/features/workbooks/services/workbook_placements/crud.test.ts
  • src/features/workbooks/services/workbook_placements/crud.ts
  • src/features/workbooks/services/workbook_placements/initializers.test.ts
  • src/features/workbooks/services/workbook_placements/initializers.ts
  • src/features/workbooks/zod/schema.ts
  • src/routes/(admin)/workbooks/order/+page.server.ts
  • src/routes/(admin)/workbooks/order/+server.ts
  • src/routes/(admin)/workbooks/order/_components/KanbanColumn.svelte
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/features/workbooks/zod/schema.ts
  • src/routes/(admin)/workbooks/order/_components/KanbanColumn.svelte
  • src/routes/(admin)/workbooks/order/+page.server.ts
  • prisma/seed.ts
  • docs/dev-notes/2026-02-28/workbook-order/plan.md

…uirement

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
.claude/rules/svelte-components.md (1)

13-13: Clarify that runes are used as needed, not all simultaneously.

The current wording suggests every component must use all four runes ($props, $state, $derived, $effect), which contradicts Svelte 5 best practices. According to official documentation, each rune serves a specific purpose and should be used only when necessary. Consider rewording to "Use Runes mode and choose runes as needed per component" to reflect that $effect is an escape hatch and not all components require all runes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.claude/rules/svelte-components.md at line 13, Update the wording to clarify
that Runes mode is optional per component and runes ($props, $state, $derived,
$effect) are chosen as needed rather than required all at once; change the
sentence to something like "Use Runes mode and choose runes as needed per
component (e.g., $props, $state, $derived, and use $effect only as an escape
hatch)" so it explicitly states $effect is not mandatory and runes are applied
selectively based on component needs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.claude/rules/svelte-components.md:
- Around line 75-80: The "Before" example's updateUrl function calls
replaceState(url) with a missing required state argument; update the call in
updateUrl (the function named updateUrl that constructs a URL and sets
searchParams) to include a state value (e.g., replaceState(url, {}) or
replaceState(url, null) per your convention) so it matches SvelteKit's
replaceState(url, state) signature and is API-valid.

---

Nitpick comments:
In @.claude/rules/svelte-components.md:
- Line 13: Update the wording to clarify that Runes mode is optional per
component and runes ($props, $state, $derived, $effect) are chosen as needed
rather than required all at once; change the sentence to something like "Use
Runes mode and choose runes as needed per component (e.g., $props, $state,
$derived, and use $effect only as an escape hatch)" so it explicitly states
$effect is not mandatory and runes are applied selectively based on component
needs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6ed232bb-76be-4cfa-ac24-64fe982459e4

📥 Commits

Reviewing files that changed from the base of the PR and between 0ab9aa9 and a69d430.

📒 Files selected for processing (2)
  • .claude/rules/coding-style.md
  • .claude/rules/svelte-components.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • .claude/rules/coding-style.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator Author

@KATO-Hiro KATO-Hiro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@KATO-Hiro KATO-Hiro merged commit ba42850 into staging Mar 16, 2026
3 checks passed
@KATO-Hiro KATO-Hiro deleted the #943 branch March 16, 2026 14:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] (管理者のみ)問題集の順番を指定して登録・保存・表示できるようにしましょう

1 participant