fix: Files タブの refetch でスクロール位置がリセットされる問題を修正 (#706)#707
Merged
Conversation
- FileTreeView: limit full-screen loading/error to initial mount (rootItems empty) - FileTreeView: add non-destructive refetch indicator (aria-live=polite) and error banner with retry button - FileTreeView: extract reloadTreeWithExpandedDirs via useCallback + mountedRef guard - WorktreeDetailRefactored: skip first-poll false-positive refresh by treating null prevTreeHashRef as baseline-only - tests: add 5 new tests for refetch indicator / non-destructive error / retry path Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 15, 2026
Kewton
added a commit
that referenced
this pull request
May 17, 2026
… (#718) * feat: 履歴(History)表示件数を50〜250件で選択可能にする (#701) (#702) * feat(history): allow selecting history display limit (50-250) Issue #701 - Add HistoryDisplayLimit selector (50/100/150/200/250) to HistoryPane header - Persist selection in localStorage (commandmate:historyDisplayLimit) - Raise messages API upper bound from 100 to MAX_MESSAGES_LIMIT (250) - Centralize options/MAX/DEFAULT in src/config/history-display-config.ts - Propagate selection through WorktreeDetailRefactored (PC + Mobile) - worktreeApi.getMessages: optional limit argument - useInfiniteMessages: default pageSize references DEFAULT_MESSAGES_LIMIT - Tests: boundary (1/250/251/0/abc), DB getMessages limit=250, useInfiniteMessages hasMore at pageSize=250, config Single Source of Truth Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(history-display-config): derive MAX_MESSAGES_LIMIT from union type - Replace brittle `as 250` literal cast with `HistoryDisplayLimit` type derivation so extending HISTORY_DISPLAY_LIMIT_OPTIONS no longer requires touching MAX_MESSAGES_LIMIT. - Tighten comments and adjust wording for clarity. Issue #701 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(claude-md): add history-display-config.ts to module reference (#701) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(detection): handle Claude v2.1.142 skill approval prompt summary line (#704) (#705) Claude Code v2.1.142 renders trailing summary lines like "… +1 pending" below the real options in skill approval prompts ("Use skill ...?"). NORMAL_OPTION_PATTERN was matching these as option N with label="pending", which poisoned isValidPrecedingOption() and caused every real option above (1./2./3.) to be rejected — collectedOptions ended up with length < 2, returning no_prompt. As a result Yes/No UI never appeared and Auto-Yes did not fire. Defense-in-depth fix: - S1: SUMMARY_LINE_PATTERN early-continue in Pass 2 loop (tight anchored pattern, no free `(.+)` capture, keyword whitelist pending|more) so legitimate labels mentioning these words are preserved. - S2: CLAUDE_PROMPT_FOOTER_PATTERN trims effectiveEnd to the "Esc to cancel · Tab to amend" footer, putting the summary line outside the scan window entirely. Fallback-safe: no footer found => effectiveEnd untouched, so Codex/Gemini/OpenCode/Copilot detection paths are unaffected. - S3: 3-layer regression tests across prompt-detector, status-detector, and auto-yes-resolver covering the real Claude v2.1.142 fixture plus FP-suppression for option labels containing "pending"/"more". Quality gates: lint 0/0, tsc 0, unit 6486 passed / 7 skipped, build OK. * fix(files): preserve scroll position during tree refetch (#706) (#707) - FileTreeView: limit full-screen loading/error to initial mount (rootItems empty) - FileTreeView: add non-destructive refetch indicator (aria-live=polite) and error banner with retry button - FileTreeView: extract reloadTreeWithExpandedDirs via useCallback + mountedRef guard - WorktreeDetailRefactored: skip first-poll false-positive refresh by treating null prevTreeHashRef as baseline-only - tests: add 5 new tests for refetch indicator / non-destructive error / retry path Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * perf(sync): parallelize scanMultipleRepositories with Promise.allSettled (#711) (#712) Each `git worktree list` spawns an independent child process, so running them sequentially via `for ... await` made the sync endpoint scale linearly in the number of repositories. Switch to `Promise.allSettled(...map(...))` so all repository scans run concurrently while preserving the prior "one failure does not abort the rest" semantics. Logs continue to include `repoPath` so the (now unordered) output can still be correlated. The unit test mock for `child_process` had to be switched from auto-mock to a factory mock: vitest's auto-mock preserves `util.promisify.custom` from the real `exec`, which made `promisify(exec)` bypass `mockImplementation` entirely. The factory returns a fresh `vi.fn()` without that symbol so callback-style mocking works. Closes #711 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * perf(sidebar): adaptive polling interval for active/idle transitions (#710) (#713) useWorktreesCache のポーリング間隔(active=5s/idle=30s)が startPolling() 呼び出し時点で固定され、worktrees の active/idle 状態が遷移しても interval が更新されない問題を修正。 - currentIntervalRef: useRef<number | null> を追加し活動中の interval を追跡 - startPolling/stopPolling/hasActiveSession を useCallback でフック直下にリフトアップ - worktrees-change useEffect で desired interval を再計算し差分時のみ startPolling を再実行 - document.hidden 中・初期化前(ref === null)はガードして no-op - 既存の visibilitychange ハンドラの責務は維持 Closes #710 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * perf(sidebar): consolidate useWorktreesCache via Provider Context (#709) (#714) Sessions ページが useWorktreesCache() を直接呼び出していたため、 WorktreesCacheProvider と合わせて /api/worktrees ポーリングが2系統 並行で動いていた問題を解消。 - WorktreesCacheProvider に WorktreesCacheContext を新設し useWorktreesCacheContext() フックを export - src/app/sessions/page.tsx を Context 経由参照に変更 - useWorktreesCache フック自体は不変 (テスト/単体利用は維持) - 新規テスト tests/unit/components/providers/WorktreesCacheProvider.test.tsx Closes #709 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * perf(db): add composite index including role column on chat_messages (#708) (#715) chat_messages の相関サブクエリ (role='assistant'/'user' AND archived=0 の MAX(timestamp)/ORDER BY timestamp DESC LIMIT 1) が、既存 idx_messages_archived(worktree_id, archived, timestamp DESC) では role 列を 含まないため行スキャンに陥り、件数増加に伴い線形劣化していた。 Migration v32 で複合インデックス idx_messages_worktree_role_archived_time(worktree_id, role, archived, timestamp DESC) を新設し、既存 idx_messages_archived を DROP。 他の role なしクエリ (getMessages, getLastMessage, deleteAllMessages) は idx_messages_worktree_time(worktree_id, timestamp DESC) で従来同等に動作する。 - src/lib/db/migrations/v32-add-messages-role-composite-index.ts: 新規 - src/lib/db/migrations/index.ts / runner.ts: v32 登録、CURRENT_SCHEMA_VERSION = 32 - src/lib/db/init-db.ts: 初期化パスを新インデックスへ置換 - tests/unit/lib/db-migrations.test.ts: v32 describe ブロック追加、 v22 旧インデックスアサーションを rollback to 31 経由に修正 - tests/unit/lib/db/chat-db-explain-plan.test.ts: EXPLAIN QUERY PLAN で対象4クエリが新インデックスを使うことを検証 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat: Worktree詳細 HistoryPaneにメッセージテキスト検索機能を追加 (#716) (#717) * feat(history): add text search to HistoryPane (#716) Adds a per-pane text search bar to Worktree HistoryPane with namespace-isolated CSS Custom Highlight API rendering so it can coexist with the TerminalPane search (Issue #47). Key design choices (3-stage reviewed): - Preserve OCP: existing applyTerminalHighlights / clearTerminalHighlights signatures unchanged. New applyHistoryHighlights / clearHistoryHighlights share the internal engine via HighlightNamespace. - ConversationPairCard receives no new props; only data-message-id is added so memo is preserved. useConversationHistory is untouched. - HistoryPane manages autoExpandedIds internally to force-expand truncated assistant messages that contain hits, then applies highlights with a strict useLayoutEffect order (save scroll → restore scroll (skipped during search) → autoExpandedIds → applyHistoryHighlights) so textContent stays aligned with message.content. - Debounce (300ms), min-query length (2), and max-matches (500) are shared via newly exported SEARCH_DEBOUNCE_MS / SEARCH_MIN_QUERY_LENGTH / TERMINAL_SEARCH_MAX_MATCHES from useTerminalSearch. Tests: 47 new cases across useHistorySearch, HistorySearchBar, terminal- highlight, and HistoryPane. Full unit suite: 6558 passed / 0 failed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(history-search): DRY debounce timer + unify selector escape (#716) Small post-implementation cleanups identified during the refactoring phase. No behavior changes; all 6558 unit tests still pass. useHistorySearch.ts: - Extract clearDebounceTimer() helper (DRY: clearTimeout/null pattern was duplicated across scheduleSearch / closeSearch / onCompositionStart / unmount cleanup). HistoryPane.tsx: - Replace custom escapeAttrValue() with native CSS.escape() via a small findMessageElement() helper. Removes a homemade escape function that duplicated standards-track functionality and unifies the two inconsistent selector-building sites (one used CSS.escape, the other the custom helper). - Capture the current match element during the highlight loop so the scrollIntoView path no longer issues a second querySelector for the same node. Constraints preserved: - terminal-highlight.ts public API untouched (OCP). - ConversationPairCard props unchanged (memo preserved). - Effect declaration order (1)->(2)->(3)->(4) and HISTORY_SEARCH_NAMESPACE isolation unchanged (design policy v1.2 core). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(history): update CLAUDE.md and implementation-history for #716 Add HistoryPane text search feature entry and document new modules: useHistorySearch hook, HistorySearchBar component, terminal-highlight namespace separation (HISTORY_SEARCH_NAMESPACE), and data-message-id attribution in ConversationPairCard. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7 tasks
Kewton
added a commit
that referenced
this pull request
May 27, 2026
* feat: 履歴(History)表示件数を50〜250件で選択可能にする (#701) (#702) * feat(history): allow selecting history display limit (50-250) Issue #701 - Add HistoryDisplayLimit selector (50/100/150/200/250) to HistoryPane header - Persist selection in localStorage (commandmate:historyDisplayLimit) - Raise messages API upper bound from 100 to MAX_MESSAGES_LIMIT (250) - Centralize options/MAX/DEFAULT in src/config/history-display-config.ts - Propagate selection through WorktreeDetailRefactored (PC + Mobile) - worktreeApi.getMessages: optional limit argument - useInfiniteMessages: default pageSize references DEFAULT_MESSAGES_LIMIT - Tests: boundary (1/250/251/0/abc), DB getMessages limit=250, useInfiniteMessages hasMore at pageSize=250, config Single Source of Truth Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(history-display-config): derive MAX_MESSAGES_LIMIT from union type - Replace brittle `as 250` literal cast with `HistoryDisplayLimit` type derivation so extending HISTORY_DISPLAY_LIMIT_OPTIONS no longer requires touching MAX_MESSAGES_LIMIT. - Tighten comments and adjust wording for clarity. Issue #701 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(claude-md): add history-display-config.ts to module reference (#701) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(detection): handle Claude v2.1.142 skill approval prompt summary line (#704) (#705) Claude Code v2.1.142 renders trailing summary lines like "… +1 pending" below the real options in skill approval prompts ("Use skill ...?"). NORMAL_OPTION_PATTERN was matching these as option N with label="pending", which poisoned isValidPrecedingOption() and caused every real option above (1./2./3.) to be rejected — collectedOptions ended up with length < 2, returning no_prompt. As a result Yes/No UI never appeared and Auto-Yes did not fire. Defense-in-depth fix: - S1: SUMMARY_LINE_PATTERN early-continue in Pass 2 loop (tight anchored pattern, no free `(.+)` capture, keyword whitelist pending|more) so legitimate labels mentioning these words are preserved. - S2: CLAUDE_PROMPT_FOOTER_PATTERN trims effectiveEnd to the "Esc to cancel · Tab to amend" footer, putting the summary line outside the scan window entirely. Fallback-safe: no footer found => effectiveEnd untouched, so Codex/Gemini/OpenCode/Copilot detection paths are unaffected. - S3: 3-layer regression tests across prompt-detector, status-detector, and auto-yes-resolver covering the real Claude v2.1.142 fixture plus FP-suppression for option labels containing "pending"/"more". Quality gates: lint 0/0, tsc 0, unit 6486 passed / 7 skipped, build OK. * fix(files): preserve scroll position during tree refetch (#706) (#707) - FileTreeView: limit full-screen loading/error to initial mount (rootItems empty) - FileTreeView: add non-destructive refetch indicator (aria-live=polite) and error banner with retry button - FileTreeView: extract reloadTreeWithExpandedDirs via useCallback + mountedRef guard - WorktreeDetailRefactored: skip first-poll false-positive refresh by treating null prevTreeHashRef as baseline-only - tests: add 5 new tests for refetch indicator / non-destructive error / retry path Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * perf(sync): parallelize scanMultipleRepositories with Promise.allSettled (#711) (#712) Each `git worktree list` spawns an independent child process, so running them sequentially via `for ... await` made the sync endpoint scale linearly in the number of repositories. Switch to `Promise.allSettled(...map(...))` so all repository scans run concurrently while preserving the prior "one failure does not abort the rest" semantics. Logs continue to include `repoPath` so the (now unordered) output can still be correlated. The unit test mock for `child_process` had to be switched from auto-mock to a factory mock: vitest's auto-mock preserves `util.promisify.custom` from the real `exec`, which made `promisify(exec)` bypass `mockImplementation` entirely. The factory returns a fresh `vi.fn()` without that symbol so callback-style mocking works. Closes #711 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * perf(sidebar): adaptive polling interval for active/idle transitions (#710) (#713) useWorktreesCache のポーリング間隔(active=5s/idle=30s)が startPolling() 呼び出し時点で固定され、worktrees の active/idle 状態が遷移しても interval が更新されない問題を修正。 - currentIntervalRef: useRef<number | null> を追加し活動中の interval を追跡 - startPolling/stopPolling/hasActiveSession を useCallback でフック直下にリフトアップ - worktrees-change useEffect で desired interval を再計算し差分時のみ startPolling を再実行 - document.hidden 中・初期化前(ref === null)はガードして no-op - 既存の visibilitychange ハンドラの責務は維持 Closes #710 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * perf(sidebar): consolidate useWorktreesCache via Provider Context (#709) (#714) Sessions ページが useWorktreesCache() を直接呼び出していたため、 WorktreesCacheProvider と合わせて /api/worktrees ポーリングが2系統 並行で動いていた問題を解消。 - WorktreesCacheProvider に WorktreesCacheContext を新設し useWorktreesCacheContext() フックを export - src/app/sessions/page.tsx を Context 経由参照に変更 - useWorktreesCache フック自体は不変 (テスト/単体利用は維持) - 新規テスト tests/unit/components/providers/WorktreesCacheProvider.test.tsx Closes #709 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * perf(db): add composite index including role column on chat_messages (#708) (#715) chat_messages の相関サブクエリ (role='assistant'/'user' AND archived=0 の MAX(timestamp)/ORDER BY timestamp DESC LIMIT 1) が、既存 idx_messages_archived(worktree_id, archived, timestamp DESC) では role 列を 含まないため行スキャンに陥り、件数増加に伴い線形劣化していた。 Migration v32 で複合インデックス idx_messages_worktree_role_archived_time(worktree_id, role, archived, timestamp DESC) を新設し、既存 idx_messages_archived を DROP。 他の role なしクエリ (getMessages, getLastMessage, deleteAllMessages) は idx_messages_worktree_time(worktree_id, timestamp DESC) で従来同等に動作する。 - src/lib/db/migrations/v32-add-messages-role-composite-index.ts: 新規 - src/lib/db/migrations/index.ts / runner.ts: v32 登録、CURRENT_SCHEMA_VERSION = 32 - src/lib/db/init-db.ts: 初期化パスを新インデックスへ置換 - tests/unit/lib/db-migrations.test.ts: v32 describe ブロック追加、 v22 旧インデックスアサーションを rollback to 31 経由に修正 - tests/unit/lib/db/chat-db-explain-plan.test.ts: EXPLAIN QUERY PLAN で対象4クエリが新インデックスを使うことを検証 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat: Worktree詳細 HistoryPaneにメッセージテキスト検索機能を追加 (#716) (#717) * feat(history): add text search to HistoryPane (#716) Adds a per-pane text search bar to Worktree HistoryPane with namespace-isolated CSS Custom Highlight API rendering so it can coexist with the TerminalPane search (Issue #47). Key design choices (3-stage reviewed): - Preserve OCP: existing applyTerminalHighlights / clearTerminalHighlights signatures unchanged. New applyHistoryHighlights / clearHistoryHighlights share the internal engine via HighlightNamespace. - ConversationPairCard receives no new props; only data-message-id is added so memo is preserved. useConversationHistory is untouched. - HistoryPane manages autoExpandedIds internally to force-expand truncated assistant messages that contain hits, then applies highlights with a strict useLayoutEffect order (save scroll → restore scroll (skipped during search) → autoExpandedIds → applyHistoryHighlights) so textContent stays aligned with message.content. - Debounce (300ms), min-query length (2), and max-matches (500) are shared via newly exported SEARCH_DEBOUNCE_MS / SEARCH_MIN_QUERY_LENGTH / TERMINAL_SEARCH_MAX_MATCHES from useTerminalSearch. Tests: 47 new cases across useHistorySearch, HistorySearchBar, terminal- highlight, and HistoryPane. Full unit suite: 6558 passed / 0 failed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(history-search): DRY debounce timer + unify selector escape (#716) Small post-implementation cleanups identified during the refactoring phase. No behavior changes; all 6558 unit tests still pass. useHistorySearch.ts: - Extract clearDebounceTimer() helper (DRY: clearTimeout/null pattern was duplicated across scheduleSearch / closeSearch / onCompositionStart / unmount cleanup). HistoryPane.tsx: - Replace custom escapeAttrValue() with native CSS.escape() via a small findMessageElement() helper. Removes a homemade escape function that duplicated standards-track functionality and unifies the two inconsistent selector-building sites (one used CSS.escape, the other the custom helper). - Capture the current match element during the highlight loop so the scrollIntoView path no longer issues a second querySelector for the same node. Constraints preserved: - terminal-highlight.ts public API untouched (OCP). - ConversationPairCard props unchanged (memo preserved). - Effect declaration order (1)->(2)->(3)->(4) and HISTORY_SEARCH_NAMESPACE isolation unchanged (design policy v1.2 core). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(history): update CLAUDE.md and implementation-history for #716 Add HistoryPane text search feature entry and document new modules: useHistorySearch hook, HistorySearchBar component, terminal-highlight namespace separation (HISTORY_SEARCH_NAMESPACE), and data-message-id attribution in ConversationPairCard. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(executor): diagnose execFile maxBuffer errors and bump limit to 10MB (#719) (#720) Node v24 で execFile の `ERR_CHILD_PROCESS_STDIO_MAXBUFFER` が `failed/exit_code=null` のまま原因不明で終了していた問題を修正。 - error.code が文字列のとき parseInt で NaN → null になっていたのを `typeof errCode === 'number'` での厳密判定に変更(型安全)。 - `stdout || stderr || error.message` の優先順位で巨大 stdout が error.message を呑み込んでいたため、必ず先頭に Error/Code/Signal/Reason サマリを置き、続けて --- stdout --- / --- stderr --- セクションで 両方を保存するように変更。 - ERR_CHILD_PROCESS_STDIO_MAXBUFFER の場合は専用 Reason 行を付加し、 timeout ではなく failed として扱う(時間切れと出力過多は意味が異なる)。 - MAX_OUTPUT_SIZE を 1MB → 10MB に暫定緩和(spawn + rolling buffer 化は 別 Issue で根本対策)。 Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
FileTreeViewの loading/error 早期 return を初回マウント時 (rootItems未取得時) に限定し、refetch 中は既存ツリー DOM を保持したまま控えめインジケーター・非破壊エラーバナーを表示WorktreeDetailRefactoredのprevTreeHashRefを初回ポーリング時はベースライン記録のみとし、偽陽性 refresh 発火を抑止主な変更
src/components/worktree/FileTreeView.tsxisInitialLoading/isInitialErrorフラグで全画面表示を初回限定化。refetch 中はツールバー右端にdata-testid="file-tree-refetch-indicator"(role="status",aria-live="polite")を表示。refetch エラー時は既存ツリー上部に非破壊バナーdata-testid="file-tree-refetch-error"と再試行ボタンdata-testid="file-tree-refetch-retry-button"。reloadTreeWithExpandedDirsをuseCallbackで抽出、mountedRefでアンマウント後の setState を抑止src/components/worktree/WorktreeDetailRefactored.tsxprevTreeHashRef.current === nullのときはベースライン記録のみとし、初回ポーリングでの偽陽性 refresh を回避tests/unit/components/worktree/FileTreeView.test.tsx検証結果
npm run lint: ✅ passnpx tsc --noEmit: ✅ passnpm run test:unit: ✅ 6491 passed / 7 skipped(343 ファイル全て pass、回帰 0 件)FileTreeView.test.tsx単体: ✅ 69 tests pass(新規 5 件 + 既存 Issue Filesにてファイルまたはディレクトリを作成時対象のディレクトリの状態が更新されずにおそらく初期状態に戻る #164/ルートディレクトリにディレクトリを追加出来ない #300 含む)受入条件チェック
npm run test:unitで 0 件の回帰Test plan
Closes #706
🤖 Generated with Claude Code