Skip to content

fix: Timer 発火の送信が History に残らない(send ロジックを共有サービス化)#1029

Merged
Kewton merged 1 commit into
developfrom
fix/1028-timer-history
Jul 7, 2026
Merged

fix: Timer 発火の送信が History に残らない(send ロジックを共有サービス化)#1029
Kewton merged 1 commit into
developfrom
fix/1028-timer-history

Conversation

@Kewton

@Kewton Kewton commented Jul 7, 2026

Copy link
Copy Markdown
Owner

概要

アクティビティバーの Timer 機能から送った指示が History(メッセージ履歴)に残らない問題を修正します(#1028)。対応B(送信ロジックの共有サービス化)を採用。

根本原因

History = chat_messages テーブル。通常送信(/api/worktrees/[id]/send)は tmux 送信とは別に createMessage(user 記録)+ startPolling(assistant 応答記録)を行うが、Timer 発火(timer-manager.ts executeTimer)は Issue #947cliTool.sendMessage に委譲した際、この記録ロジックをコピーしなかったため user・assistant 応答の両方が History に残らなかった

変更内容(対応B)

  • 新規 src/lib/session/send-user-message.ts: send route L262-385 のインライン記録ロジックを sendUserMessage() として抽出(savePendingAssistantResponse → send〔画像/Copilot 分岐含む〕→ createMessageupdateLastUserMessage/orphan 処理 → startPolling)。HTTP 層非依存の純粋サービス。
  • send/route.ts: POST を sendUserMessage() 利用へリファクタ(挙動不変、-146行)。
  • timer-manager.ts executeTimer: sendUserMessage() 経由に変更。timer_messages の status 遷移(sending/sent/failed)は維持。

→ Timer 送信も user メッセージと assistant 応答の両方が History に残る

テスト

  • 新規/更新: sendUserMessage / executeTimer のユニット新規9件 + timer 既存更新(計26件)
  • 全4ゲート合格: ESLint ✅ / tsc --noEmit ✅ / test:unit ✅(474ファイル・8431テスト)/ build ✅
  • 通常送信のリグレッションなしを base ブランチとの比較で確認(画像添付・Copilot 経路含む)

申し送り(スコープ外・別Issue推奨)

  • tests/integration/api-send-cli-tool.test.ts の1件(should use claude tool by default)は base ブランチでも同一に失敗する既存不具合(2引数アサート vs コードは3引数目に undefined)。fix: Timer 発火の送信が History に残らない(send ロジックを共有サービス化して修正) #1028 と無関係のため本PRでは未修正(CI の test:unit 対象外)。
  • 軽微な挙動差: 不正 imagePath で 400 拒否する際、旧コードは savePendingAssistantResponse 後、新コードは保存前に 400 を返す(検証の前倒し)。副作用・データ欠落なし。

Closes #1028

🤖 Generated with Claude Code

Timer 機能(Issue #534)から送った指示が History(chat_messages)に残らない
問題を修正。executeTimer が cliTool.sendMessage だけを呼び createMessage/
startPolling をスキップしていたのが原因(#947 の委譲変更が起点)。

- send route L262-385 のインライン記録ロジックを src/lib/session/
  send-user-message.ts の共通関数 sendUserMessage() として抽出
  (savePendingAssistantResponse → send(画像/Copilot分岐含む)→ createMessage
  → updateLastUserMessage/orphan処理 → startPolling)。HTTP 層非依存
- send route POST を sendUserMessage() 利用へリファクタ(挙動不変)
- timer-manager.ts executeTimer を sendUserMessage() 経由に変更。
  timer_messages の status 遷移(sending/sent/failed)は維持
- テスト: sendUserMessage / executeTimer のユニット新規9件 + timer 既存更新(計26件)

これにより Timer 送信も user メッセージと assistant 応答の両方が History に残る。

スコープ外(別Issue推奨):
- tests/integration/api-send-cli-tool.test.ts の1件は base でも失敗する既存不具合
  (2引数アサート vs 3引数呼び出し)で本修正と無関係(CI の test:unit 対象外)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Kewton Kewton added bug Something isn't working refactor Code refactoring / technical debt labels Jul 7, 2026
@Kewton
Kewton merged commit 8d50d15 into develop Jul 7, 2026
6 checks passed
Kewton added a commit that referenced this pull request Jul 8, 2026
* feat(files): スマホ版でファイルをダウンロード可能に(attachment配信+FileViewer DL導線)(#1024) (#1025)

スマホ版のファイルビューア(FileViewer, Issue #438 でモバイル専用化)から
任意種別のファイルをダウンロードできるようにする。

- B(サーバ): files/[...path] GET に download 分岐を追加し、生バイトを
  Content-Type: application/octet-stream + Content-Disposition: attachment で返却。
  filename は src/lib/http の content-disposition ヘルパーで RFC 準拠にサニタイズ
  (ASCII フォールバック + filename*=UTF-8'' パーセントエンコード、ヘッダ
  インジェクション回避)。既存 getWorktreeAndValidatePath でパス検証を再利用。
  base64 JSON を経由しないためプレビュー上限を超える大容量にも対応
- A(UI): FileViewer ツールバーにダウンロードボタンを追加し、同一オリジンの
  download URL へ遷移(iOS Safari 含めネイティブDLに乗せる)
- テスト新規29本(download API のヘッダ/パス検証/filename、content-disposition
  ヘルパー、FileViewer ボタン)
- C(独立ページのPDF分岐/DLリンク)は範囲外(モバイル動線が独立ページを使わないため)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(session): Timer 発火の送信を History に記録(send ロジックを共有サービス化)(#1028) (#1029)

Timer 機能(Issue #534)から送った指示が History(chat_messages)に残らない
問題を修正。executeTimer が cliTool.sendMessage だけを呼び createMessage/
startPolling をスキップしていたのが原因(#947 の委譲変更が起点)。

- send route L262-385 のインライン記録ロジックを src/lib/session/
  send-user-message.ts の共通関数 sendUserMessage() として抽出
  (savePendingAssistantResponse → send(画像/Copilot分岐含む)→ createMessage
  → updateLastUserMessage/orphan処理 → startPolling)。HTTP 層非依存
- send route POST を sendUserMessage() 利用へリファクタ(挙動不変)
- timer-manager.ts executeTimer を sendUserMessage() 経由に変更。
  timer_messages の status 遷移(sending/sent/failed)は維持
- テスト: sendUserMessage / executeTimer のユニット新規9件 + timer 既存更新(計26件)

これにより Timer 送信も user メッセージと assistant 応答の両方が History に残る。

スコープ外(別Issue推奨):
- tests/integration/api-send-cli-tool.test.ts の1件は base でも失敗する既存不具合
  (2引数アサート vs 3引数呼び出し)で本修正と無関係(CI の test:unit 対象外)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Kewton added a commit that referenced this pull request Jul 10, 2026
…詳細・編集 / #1036 コピー) (#1038)

* feat(files): スマホ版でファイルをダウンロード可能に(attachment配信+FileViewer DL導線)(#1024) (#1025)

スマホ版のファイルビューア(FileViewer, Issue #438 でモバイル専用化)から
任意種別のファイルをダウンロードできるようにする。

- B(サーバ): files/[...path] GET に download 分岐を追加し、生バイトを
  Content-Type: application/octet-stream + Content-Disposition: attachment で返却。
  filename は src/lib/http の content-disposition ヘルパーで RFC 準拠にサニタイズ
  (ASCII フォールバック + filename*=UTF-8'' パーセントエンコード、ヘッダ
  インジェクション回避)。既存 getWorktreeAndValidatePath でパス検証を再利用。
  base64 JSON を経由しないためプレビュー上限を超える大容量にも対応
- A(UI): FileViewer ツールバーにダウンロードボタンを追加し、同一オリジンの
  download URL へ遷移(iOS Safari 含めネイティブDLに乗せる)
- テスト新規29本(download API のヘッダ/パス検証/filename、content-disposition
  ヘルパー、FileViewer ボタン)
- C(独立ページのPDF分岐/DLリンク)は範囲外(モバイル動線が独立ページを使わないため)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(session): Timer 発火の送信を History に記録(send ロジックを共有サービス化)(#1028) (#1029)

Timer 機能(Issue #534)から送った指示が History(chat_messages)に残らない
問題を修正。executeTimer が cliTool.sendMessage だけを呼び createMessage/
startPolling をスキップしていたのが原因(#947 の委譲変更が起点)。

- send route L262-385 のインライン記録ロジックを src/lib/session/
  send-user-message.ts の共通関数 sendUserMessage() として抽出
  (savePendingAssistantResponse → send(画像/Copilot分岐含む)→ createMessage
  → updateLastUserMessage/orphan処理 → startPolling)。HTTP 層非依存
- send route POST を sendUserMessage() 利用へリファクタ(挙動不変)
- timer-manager.ts executeTimer を sendUserMessage() 経由に変更。
  timer_messages の status 遷移(sending/sent/failed)は維持
- テスト: sendUserMessage / executeTimer のユニット新規9件 + timer 既存更新(計26件)

これにより Timer 送信も user メッセージと assistant 応答の両方が History に残る。

スコープ外(別Issue推奨):
- tests/integration/api-send-cli-tool.test.ts の1件は base でも失敗する既存不具合
  (2引数アサート vs 3引数呼び出し)で本修正と無関係(CI の test:unit 対象外)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(todo): ブランチ単位ToDoを3状態化(未着手/仕掛/完了)(#1032) (#1033)

- migration v38: worktree_todos に status カラム追加(done=1→'done' backfill、CURRENT_SCHEMA_VERSION 37→38)
- DB層: status を真実源に done := status==='done' を派生(後方互換維持、writer整合)
- API/クライアント: PATCH で status ∈ {todo,doing,done} を検証・適用、done後方互換
- UI: 3状態巡回チップ+色バッジ+状態別件数、TodoPane全体を next-intl 化
- i18n: locales/{en,ja}/worktree.json に todo namespace 追加
- テスト: 既存5更新+新規 migration-v38(backfill検証)

Closes #1032

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(todo): ブランチ単位ToDoに詳細フィールド追加+クリックで件名/詳細を編集 (#1034) (#1035)

- migration v39: worktree_todos に detail カラム追加(TEXT NOT NULL DEFAULT ''、既存行は空詳細で移行)
- DB層: WorktreeTodo に detail 追加、create/update で detail 受理
- API: POST/PATCH で detail を検証・適用(MAX_TODO_DETAIL_LENGTH 新設)
- クライアント: WorktreeTodoItem.detail、create/update の detail 対応
- UI: 項目クリックで編集モーダル(Modal.tsx再利用)を開き件名+詳細を編集、詳細ありインジケータ
- i18n: locales/{en,ja}/worktree.json に詳細/編集の文言追加
- テスト: 既存6更新+新規 migration-v39、docs/module-reference.md 更新

Closes #1034

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(todo): ブランチ単位ToDoの詳細にコピーボタンを追加 (#1036) (#1037)

- UI: 編集モーダルの詳細欄に CopyButton を配置し詳細本文をコピー(詳細が空のときは非表示)
- 既存 src/components/common/CopyButton.tsx / clipboard-utils.ts を再利用(スマホHTTPは execCommand フォールバック)
- i18n: locales/{en,ja}/worktree.json に todo.copyDetail 追加
- テスト: TodoPane.test.tsx にコピー導線テスト3件追加
- migration/DB/API 変更なし(UIのみ)

Closes #1036

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Kewton added a commit that referenced this pull request Jul 13, 2026
… / integration復旧 #1102 / bug #1104) (#1106)

* feat(files): スマホ版でファイルをダウンロード可能に(attachment配信+FileViewer DL導線)(#1024) (#1025)

スマホ版のファイルビューア(FileViewer, Issue #438 でモバイル専用化)から
任意種別のファイルをダウンロードできるようにする。

- B(サーバ): files/[...path] GET に download 分岐を追加し、生バイトを
  Content-Type: application/octet-stream + Content-Disposition: attachment で返却。
  filename は src/lib/http の content-disposition ヘルパーで RFC 準拠にサニタイズ
  (ASCII フォールバック + filename*=UTF-8'' パーセントエンコード、ヘッダ
  インジェクション回避)。既存 getWorktreeAndValidatePath でパス検証を再利用。
  base64 JSON を経由しないためプレビュー上限を超える大容量にも対応
- A(UI): FileViewer ツールバーにダウンロードボタンを追加し、同一オリジンの
  download URL へ遷移(iOS Safari 含めネイティブDLに乗せる)
- テスト新規29本(download API のヘッダ/パス検証/filename、content-disposition
  ヘルパー、FileViewer ボタン)
- C(独立ページのPDF分岐/DLリンク)は範囲外(モバイル動線が独立ページを使わないため)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(session): Timer 発火の送信を History に記録(send ロジックを共有サービス化)(#1028) (#1029)

Timer 機能(Issue #534)から送った指示が History(chat_messages)に残らない
問題を修正。executeTimer が cliTool.sendMessage だけを呼び createMessage/
startPolling をスキップしていたのが原因(#947 の委譲変更が起点)。

- send route L262-385 のインライン記録ロジックを src/lib/session/
  send-user-message.ts の共通関数 sendUserMessage() として抽出
  (savePendingAssistantResponse → send(画像/Copilot分岐含む)→ createMessage
  → updateLastUserMessage/orphan処理 → startPolling)。HTTP 層非依存
- send route POST を sendUserMessage() 利用へリファクタ(挙動不変)
- timer-manager.ts executeTimer を sendUserMessage() 経由に変更。
  timer_messages の status 遷移(sending/sent/failed)は維持
- テスト: sendUserMessage / executeTimer のユニット新規9件 + timer 既存更新(計26件)

これにより Timer 送信も user メッセージと assistant 応答の両方が History に残る。

スコープ外(別Issue推奨):
- tests/integration/api-send-cli-tool.test.ts の1件は base でも失敗する既存不具合
  (2引数アサート vs 3引数呼び出し)で本修正と無関係(CI の test:unit 対象外)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(todo): ブランチ単位ToDoを3状態化(未着手/仕掛/完了)(#1032) (#1033)

- migration v38: worktree_todos に status カラム追加(done=1→'done' backfill、CURRENT_SCHEMA_VERSION 37→38)
- DB層: status を真実源に done := status==='done' を派生(後方互換維持、writer整合)
- API/クライアント: PATCH で status ∈ {todo,doing,done} を検証・適用、done後方互換
- UI: 3状態巡回チップ+色バッジ+状態別件数、TodoPane全体を next-intl 化
- i18n: locales/{en,ja}/worktree.json に todo namespace 追加
- テスト: 既存5更新+新規 migration-v38(backfill検証)

Closes #1032

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(todo): ブランチ単位ToDoに詳細フィールド追加+クリックで件名/詳細を編集 (#1034) (#1035)

- migration v39: worktree_todos に detail カラム追加(TEXT NOT NULL DEFAULT ''、既存行は空詳細で移行)
- DB層: WorktreeTodo に detail 追加、create/update で detail 受理
- API: POST/PATCH で detail を検証・適用(MAX_TODO_DETAIL_LENGTH 新設)
- クライアント: WorktreeTodoItem.detail、create/update の detail 対応
- UI: 項目クリックで編集モーダル(Modal.tsx再利用)を開き件名+詳細を編集、詳細ありインジケータ
- i18n: locales/{en,ja}/worktree.json に詳細/編集の文言追加
- テスト: 既存6更新+新規 migration-v39、docs/module-reference.md 更新

Closes #1034

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(todo): ブランチ単位ToDoの詳細にコピーボタンを追加 (#1036) (#1037)

- UI: 編集モーダルの詳細欄に CopyButton を配置し詳細本文をコピー(詳細が空のときは非表示)
- 既存 src/components/common/CopyButton.tsx / clipboard-utils.ts を再利用(スマホHTTPは execCommand フォールバック)
- i18n: locales/{en,ja}/worktree.json に todo.copyDetail 追加
- テスト: TodoPane.test.tsx にコピー導線テスト3件追加
- migration/DB/API 変更なし(UIのみ)

Closes #1036

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): セマンティックデザイントークン基盤を導入 (#1041) (#1054)

globals.css に :root(light)/.dark(dark) の CSS 変数
(background/foreground/surface/muted/border/input/ring/accent-50..700/
success/warning/danger/info) を定義し、tailwind.config.js の primary(cyan)・
cmd-bg-dark を semantic トークンへ置換。layout.tsx / MainLayout.tsx の
body 背景を bg-background に統一。既存の実効値を写し取り視覚は不変(no-op)。
docs/design-system.md を新設。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): cn()/cva によるバリアント管理基盤とプリミティブ改修 (#1042) (#1055)

clsx + tailwind-merge の cn() ヘルパーと class-variance-authority を導入し、
Button/Card/Badge/Modal を cva ベースへ移行。Button ghost バリアントの
dark: 指定漏れ(ダークモードで視認性劣化)を修正。既存 API は完全互換。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): アイコンを lucide-react に統一 (#1044) (#1056)

絵文字アイコン(🤖⚡✦💻)を廃止し lucide アイコンへ置換。共通レイアウト・ナビ
(Header/GlobalMobileNav/MobileTabBar/ActivityBar/terminal page)および worktree
各所のインライン SVG を lucide に統一。docs/design-system.md にアイコンサイズ規約を追記。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): next/font で Geist Sans/Mono を導入 (#1043) (#1057)

geist パッケージ(セルフホスト)で GeistSans/GeistMono の CSS 変数を <html> に適用し、
tailwind fontFamily の sans/mono を「Geist → 日本語(Hiragino/Noto Sans JP) →
システム」フォールバックで構成。Terminal.tsx(xterm)の等幅も Geist Mono を先頭指定。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): RadixベースUIプリミティブを拡充 (#1046) (#1058)

Input/Textarea/Select/Tabs/Tooltip/DropdownMenu/Switch/Skeleton を追加
(cva + cn, セマンティックトークン着色, ライト/ダーク対応)。Portal 系は
'use client' + Z_INDEX(POPOVER)で Modal と階層整合。Sessions ページの
フィルタ/ソートを新プリミティブへ移行(適用サンプル)。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): アクセント色を統一しセマンティックトークンへ置換 (#1045) (#1059)

cyan/blue 直書き(~600箇所 / 131ファイル)を #1041 のセマンティックトークンへ置換。
用途判定: インタラクティブ=accent / フォーカス=ring / 情報=info。gray 系は
surface/background/border/muted へ実効値を保って置換。例外(Terminal.tsx の
xterm ANSI 配色 / CLI ブランド色 / .scrollbar-thin 等の固定装飾色)は維持。
globals.css の .btn-primary・検索ハイライト等もトークン化。
敵対的レビュー指摘(focus:ring-accent→focus:ring-ring 統一、design-system.md
の例外記述修正)を反映。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): 主要ページを共通UIプリミティブへ移行 (#1047) (#1060)

layout(Header/Sidebar の検索・トグル・言語)/ home / repositories / review /
more / chat / login の生button・手組みフォーム・コピペカードを @/components/ui
プリミティブへ移行。機能・挙動(onClick/フォーム送信/バリデーション/キーボード)は維持。
敵対的レビュー指摘(chat系ボタン移行・ghost の dark hover)を反映。ネイティブ select・
複合 textarea 入力バー・極小アイコントグルは意図的に残置(PR の残置リスト参照)。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): globals.css の @apply クラスを廃止し cva プリミティブを自己完結化 (#1048) (#1062)

.btn/.btn-*/.card/.card-hover/.badge*/.input を globals.css から削除し、
Button/Card/Badge の cva 定義へ実クラスを内挿(旧 @apply 定義と同値、視覚不変)。
src/ 全体で削除クラスへの参照が 0 であることを確認。worktree の一部
(WorktreeDetailRefactored/LogViewer/WorktreeList/ExternalAppForm)をプリミティブへ移行し、
マジックナンバー入りインライン style を定数化。worktree/mobile/sidebar/common の
一括プリミティブ移行は規模の都合により #1061 へ分割。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): サーフェス・奥行きを再設計しモダンダークテーマへ刷新 (#1049) (#1063)

globals.css の :root/.dark トークン値を刷新(深い base + 微リフト surface +
ヘアライン border)。Header/GlobalMobileNav に backdrop-blur(supports-[backdrop-filter]
フォールバック付)。Card に elevated/interactive variant、h1-h4 見出しタイポを調整。
敵対的レビュー指摘を反映: 主要閲覧画面のコンテンツカード(sessions/home/review)を
bg-surface/border-border へ移行し背景反転による溶け込みを解消、ダーク入力を surface-2 で
凹み表現、Card interactive にキーボード focus 対応を追加。コントラストは WCAG AA 全 pass。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): モーション基盤とマイクロインタラクションを追加 (#1050) (#1064)

tailwindcss-animate を導入し、Modal/MobilePromptSheet の enter/exit、Radix data-state
連動(DropdownMenu/Select/Tooltip)、一覧の stagger、hover lift を統一適用。
prefers-reduced-motion 対応。ターミナル/仮想スクロールには非適用。敵対レビュー指摘
(reduced-motionでのtranslate漏れ→motion-safe化、sessionsエラー時のstagger再発火→list
マウント維持、Tooltip instant-open enter、home focus parity)を反映。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): Home をダッシュボード(bento グリッド)化 (#1052) (#1065)

Home を縦積みから bento グリッドへ再構成(統計/直近セッション/ToDo/クイックアクション)。
PC は CSS Grid(grid-cols-12 + col-span)、モバイルは1カラム縦積み(useIsMobile の JS 分岐を
増やさず Tailwind breakpoint)。既存 sessions 取得フック/API を再利用し新規 API なし。既存機能
(ToDo 追加/チェック/コピー、セッションカウント)を維持。空状態対応。敵対的レビュー指摘
(ToDo タイルの h-full・見出しパリティ)を反映。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): コマンドパレット(⌘K)を追加 (#1053) (#1066)

cmdk で ⌘K(mac)/Ctrl+K(win) のコマンドパレットを追加(Navigation / Worktrees 検索 / Actions)。
input/textarea/ターミナル入力中は非発火、SSR 安全('use client')、i18n(en/ja)、Modal 相当の z-index。
モバイルは GlobalMobileNav に起動導線。敵対的レビュー指摘を反映: open 時に入力へフォーカスし
キーボード操作(type/矢印/Enter)を復旧[HIGH]、worktree 一覧は共有キャッシュ context を再利用
(最新化+失敗時 Navigation fallback)、Escape の preventDefault/stopPropagation と close 時の
フォーカス復帰。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): エージェント/セッションステータスの視覚を強化 (#1051) (#1067)

StatusDot(running/waiting/idle/error)を新設。running はグロー+パルス+静的リング halo、
waiting は amber の弱点滅。サイドバー(BranchStatusIndicator)/ Home / Sessions を StatusDot 化。
prefers-reduced-motion でパルス停止(静的リングで running≠ready を維持)、ポーリングで再発火しない
CSS infinite アニメ。敵対的レビュー指摘(reduced-motion での running/ready 識別性、docs の実態訂正)を
反映。worktree 詳細/MobileHeader は従来スピナー表示のまま(docs に明記・今後の追随対象)。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ui): サイドバーがヘッダーに遮蔽される左上レイアウト破綻を修正 (#1070) (#1083)

showGlobalNav 時に固定サイドバーを top-16 h-[calc(100vh-4rem)] にオフセットし、
半透明ヘッダー(z-50, backdrop-blur)がサイドバー先頭64px(Branches 見出し+
view/sort/sync ツールバー行)を遮蔽してクリック不能+灰色の滲みを生む問題を解消。
あわせて border-gray-200/dark:border-gray-600 を border-border にトークン化。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): テーマ既定を system 化しヘッダーにトグルを追加 (#1071) (#1084)

next-themes を defaultTheme="system" enableSystem に変更し、ライト環境ユーザーへの
強制ダークを解消。Header 右クラスタに ThemeToggle を追加(発見可能性向上)。
globals.css に color-scheme(:root=light / .dark=dark)を宣言し、残存ネイティブ
コントロールの OS クローム事故(dark で白いドロップダウン等)を防止。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(hooks): useIsMobile を matchMedia ベースにし混成レイアウトバグを解消 (#1069) (#1085)

window.innerWidth 依存を window.matchMedia(`(max-width: ${breakpoint-1}px)`)に置換。
innerWidth と CSS ビューポートが乖離する環境(実測 innerWidth=846 vs
documentElement.clientWidth=390)では、Tailwind の md:(min-width:768px)と JS 分岐が
食い違い「CSS はモバイル・JS はデスクトップ」の混成レイアウトになっていた。
max-width:breakpoint-1 は md: の正確な補集合。change イベント購読+cleanup、
SSR 初期値 false、MOBILE_BREAKPOINT export と breakpoint オプションは維持。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): Home 第一フォールドを回収(バナー削除・見出し降格・tabular-nums) (#1072) (#1086)

陳腐化した Welcome バナー(#600 のナビ再編告知)を削除。h1「CommandMate」を機能的見出し
(HomeHeading: Overview + ライブサブライン)へ降格しヘッダーのワードマークとの同語反復を解消。
Home の数値・相対時刻・サイドバーのブランチ数バッジに tabular-nums を導入。HomeQuickActions の
inline SVG を lucide へ置換。相対時刻の短縮形は新設 date-utils、見出し文言は home i18n
namespace(en/ja)。機能(ToDo/カウント/遷移)は不変、サブラインは既存キャッシュから算出。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ライトモードのエレベーション階梯を反転(グレー地+白カード+2層影) (#1074) (#1087)

light を「白ページ+凹む灰カード」から Vercel/Linear 型の「グレー地ページ+
白カード+2層影」へ反転し、dark(#1049)と対のエレベーションモデルにする。
surface-2(740) < background(751) < surface(765) の「白カードが浮き、well が沈む」階梯。

- globals.css :root: --background 255→250 250 251 / --surface 248 250 252→255 255 255
  / --surface-2 241 245 249→244 246 250。.dark は不変(回帰なし)
- tailwind.config.js: 2層 boxShadow.sm を新規追加(md/lg は Tailwind 既定のまま)
- design-tokens/surface-depth テスト更新 + WCAG コントラスト検証を追加
  (foreground≥4.5 全面 / muted-fg≥4.5 page,card / ≥3 recessed well)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): サイドバーを --sidebar-* トークン化しテーマ追従に (#1073) (#1088)

bg-gray-800 text-white 固定でテーマ孤島だったサイドバーを、新設の
--sidebar / --sidebar-foreground / --sidebar-border / --sidebar-hover /
--sidebar-muted セマンティックトークンへ移行。light=slate-50系の明るいパネル、
dark=#141821(#1049階梯と整合)でテーマに追従する。standalone なリテラルRGB値
としており、#1074 の --surface 反転がサイドバー色へ滲まない。

- globals.css :root/.dark に --sidebar-* を定義、tailwind.config.js に登録
- Sidebar.tsx / sidebar/*(BranchListItem, SortSelectorBase)/ 検索Input
  / LocaleSwitcher / ThemeToggle の直書きを全てトークンへ置換
- 敵対的レビュー由来のライト回帰修正2件:
  - LogoutButton.tsx: 漏れていた text-gray-300 直書きを ThemeToggle と同じ
    テーマ中立トークン(text-muted-foreground/hover:bg-muted)へ(モバイル共用)
  - SortSelectorBase: 選択中ソートを text-accent-700 dark:text-accent-400 に
    (ライトのslate-50上で 1.73→約5.1:1 でAA達成、dark時は据え置き)
- docs/design-system.md に --sidebar-* を追記、既存テスト/期待値を更新

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ⌘K パレットを Raycast 級に強化(アイコン/StatusDot/kbd/Recents/ヘッダー導線)(#1077) (#1089)

- src/components/ui/Kbd.tsx を新設(semantic-token ベース、#1080 で再利用)し ui/index.ts に export 追加
- NAV 行に lucide アイコン、worktree 行に GitBranch + StatusDot(isProcessing/isWaitingForResponse/isSessionRunning から算出)
- バックドロップ/パネル/選択状態/入力/フッター文字色を semantic トークン化(bg-surface/backdrop-blur/ring-border/accent-500)
- キーボードガイドのフッター(↑↓/↵/esc、i18n)
- Recent グループ(localStorage cm.palette.recents、MRU<=8、空クエリ時のみ、dead worktree は無視)
- worktree を isSessionRunning 優先→updatedAt 降順ソート+空クエリ時 slice(0,8)
- Actions 拡充: Sync repositories(repositoryApi.sync + Toast)/ 言語切替 / Open GitHub
- Header に ⌘K 検索ピル(mount 後に platform 判定して ⌘K/Ctrl K を出し分け)
- #1053 の挙動契約(フォーカス/入力ガード/Escape/backdrop/data-testid)を維持
- 文言を locales/{en,ja}/commandPalette.json に追加、単体テスト更新

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ターミナルクロームを刷新(全周枠廃止・カード化・CLIセレクタDropdownMenu化)(#1079) (#1090)

worktree ターミナルの全周枠を廃し surface-2 カード+ヘアラインのカード化に統一。
CLI インスタンスセレクタを native <select> から Radix DropdownMenu(StatusDot+alias
+ChevronDown)へ置換し、value/onChange の単一選択セマンティクスと testid を保持。

- Terminal / ActivityBar / TerminalDisplay / TerminalSplitContainer /
  TerminalSplitPane / TerminalSplitPaneContent を surface/border トークンへ
- TerminalSplitPane: <select> → DropdownMenu(RadioGroup)。cli-selector testid 保持
- xterm の theme は意図的に固定ダーク(--surface-2 dark 相当)を維持
- StatusDot/status-colors.ts は不変(#1078 の領域)。locales worktree.json 追記
- 既存 terminal テスト(cli-tab-switching 含む)更新・全パス

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): worktree詳細/Chat のダーク直書き島を surface トークン化しテーマ追従に (#1075) (#1091)

HistoryPane / ConversationPairCard / AssistantMessageList / AssistantChatPanel /
HistorySearchBar / CopyButton と .assistant-md の常時ダーク直書きを surface/foreground
セマンティックトークンへ移行し、light/dark 両テーマで正しく描画されるようにする。

- globals.css .assistant-md: リンクを accent-700 dark:accent-400、code/pre を明色地に
  し両テーマ AA≥4.5:1。:root/.dark のトークン定義は不変
- 各コンテナ+全子孫を surface/surface-2/foreground/muted-foreground/border トークンへ
  (hidden-children 回避: 子の直書き light-on-dark を残さない)
- AssistantChatPanel/HistoryPane の native select/checkbox は色のみトークン化
  (Radix 化は #1076 の担当のため据え置き)
- ConversationPairCard: コピー/挿入/展開ツールバーの hover-reveal 化に伴い、
  タッチ端末で不可視化する回帰を [@media(hover:none)]:opacity-100 で解消
  (デスクトップの hover-reveal は維持。copy-user/assistant-message testid 保持)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): データ画面コントロールの意味論を整理(Review/Repositories/More)(#1081) (#1092)

表示のみの調整。絞り込み・編集・削除・可視性トグルのロジックは不変。

- Review: フィルタを CTA 風の primary/secondary ボタンから、件数付きの
  控えめなセグメンテッドコントロールへ(選択は bg-accent-500/15、CTA と区別)。
  件数は既存のフィルタ済み配列長から算出(新規 fetch なし)。
- Repositories: Enabled/Disabled バッジと可視性トグルの緑ピルを StatusDot 流の
  dot+muted テキストへ(トグルは操作可能なまま)。(none)→ muted の em-dash、
  パスは truncate+font-mono+full-path tooltip、Edit は ghost な Pencil アイコンボタン。
- More: ExternalAppsManager 側の重複 h2「External Apps」を削除(ページ側に一本化)。
- StatusDot/status-colors.ts は不変(#1078 の領域)。生 gray/cyan/blue の追加なし。
- 既存テスト更新+件数バッジ/単一見出しの検証テストを追加。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): コンポーザーをカード化しモバイルworktreeコントロールを整理 (#1080) (#1093)

- MessageInput をコンポーザーカード化(surface/border/shadow/focus-ring)し、送信
  ボタンを入力有無で filled(bg-accent-600)/ghost に切替。プレースホルダを i18n 短縮
- Auto-Yes をコンポーザー下辺のメタ行へ統合(ui/Switch 化)。右側に Kbd キーヒント
  (#1077 の ui/Kbd 再利用)。InterruptButton のオレンジ直値を text-danger ghost へ
- モバイル sticky 行をインスタンスタブ専用化(dark: バリアント+下線インジケータ)。
  検索/End を MobileTerminalActionsSheet(ボトムシート、End は確認付き)へ移設
- MobileTabBar を GlobalMobileNav 様式(bg-background/backdrop-blur/border-border、
  上辺インジケータ、Wrench アイコン、success/warning バッジ)へ統一
- テスト: 送信ボタン状態変化/新規シートを追加、ResizeObserver polyfill(Radix Switch)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): 稼働ステータスを StatusDot 一本化し idle ノイズを縮退 (#1078) (#1094)

青スピナー(border-info)と生 span を全て統一 StatusDot に置換し、稼働ステータスの
視覚言語を1系統に。idle ノイズを縮退(Sessions は running/waiting のみラベル付き、
idle は「+N idle」カウンタ / worktree ヘッダーは idle を StatusDot のみの円形ボタン
+Tooltip、幅超過は「+N」DropdownMenu オーバーフロー)。

- WorktreeDetailSubComponents / sessions/page / MobileHeader を StatusDot へ統一
- 新規 lib/agent-status-display.ts に classifyHeaderInstances 等の導出を集約
- status-colors.ts の type:'spinner'/border-info は @deprecated 化(ReviewTab が
  最後の消費者。消滅後に削除)
- 相対時刻 tabular-nums / プレビューは CSS truncate、DnD(handleAgentDragStart)と
  タブ切替 onClick は全ボタンで温存
- 敵対レビュー由来の仕上げ: 「+N」オーバーフローに稼働中(running/waiting)があれば
  集約 StatusDot グローを出し、稼働セッションが glow-less に埋もれないように。
  同 trigger の生グレーをセマンティックトークンへ

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ネイティブ checkbox/radio を Radix プリミティブに統一 (#1076) (#1095)

OS 標準の白いフォームコントロールが dark に浮く問題を解消。@radix-ui/react-checkbox
/ react-radio-group ベースの ui/Checkbox・ui/RadioGroup を新設し、残存していた
ネイティブ input(checkbox 12箇所/11ファイル・radio 6箇所/4ファイル)を全て移行。

- 新規 src/components/ui/{Checkbox,RadioGroup}.tsx(cn()+cva・セマンティックトークン・
  focus-visible:ring-ring)、ui/index.ts に export、依存2つを追加
- checked/onCheckedChange・value/onValueChange への適応、name/value/disabled/
  aria/data-testid とラベルクリックのトグルを全移行で保持
- 移行後 native type="checkbox"/"radio" は src/ 配下 0 件(完了性 grep 検証済)
- 敵対レビュー(binding/primitive-a11y/GitDangerZonePanel 密集)0件

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): トークン規律の確立(生gray/slate置換・focus-visible統一・CIガード)(#1082) (#1096)

移行済み領域の生 gray/slate 直書きをセマンティックトークンへ置換し、フォーカス表現を
focus-visible に統一、CI grep ガードでトークン違反の再流入を止める。機能・挙動は不変
(見た目/規律のみ)。

## プリミティブ是正
- Button: secondary/ghost を muted/foreground、danger を bg-danger トークンへ。
  `focus:` → `focus-visible:` + `ring-offset-background`(dark の白ハロー解消。Tabs/Switch が手本)。
- Input/Textarea/Select トリガー: `focus:` → `focus-visible:`(枠が反応するため offset なし)。
- Badge gray → bg-muted/text-muted-foreground、Modal → surface/border/foreground。

## in-scope スイープ(生 gray/slate → トークン)
- app(worktrees を除く)/ ui / layout / home / review / repository / common。
- gray-100/200→muted、gray-400/500/600→muted-foreground、gray-700/800/900→foreground、
  境界→border、フォーム枠→input。

## globals.css / metadata / docs
- body→text-foreground、.prose インラインコード→muted/foreground、scrollbar-thin をトークン化
  (--input / --muted-foreground)、`::selection` にアクセント淡色を追加。
- layout.tsx: title template(`%s | CommandMate`)+ viewport themeColor(light/dark)。
- docs/design-system.md にフォーカス表現・CI ガード・例外リストを同期。

## CI ガード
- `.github/workflows/ci-pr.yml` に `token-discipline` ジョブを追加(CLAUDE.md size check と同方式)。
  ホワイトリスト方式で移行済みディレクトリ限定 hard-fail。除外リストをスクリプト内に明記。
  違反注入時に fail することを確認済み。

## 判断メモ(要レビュー)
- `src/app/worktrees/**`(40 occ)はオーケストレーター集計では app(108) に含まれるが、
  ワークツリー領域(#1061・「触らない」)かつ常時ダークのターミナルページ/CLI ブランド色
  (bash=bg-gray-600)を含むため、スイープ・ガード双方から除外した。#1061 完了時に拡大。
- 軽微な見た目変化: danger の red-600→red-500(--danger)、secondary/ghost の muted シェード、
  SidebarToggle はテーマ追従化、ツールチップは反転サーフェス(bg-foreground/text-background)へ
  テーマ追従化、Home の Save&Resend ボタンを accent-600 + 白文字に正規化。

品質ゲート(lint / tsc --noEmit / test:unit / build)+ 新 CI ガード 全パス。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree/共通の生gray/buttonをトークン・プリミティブへ (#1061 群A 1/4) (#1097)

#1061 のファイル分割並列(群A: 21ファイル)。生 gray/slate Tailwind クラスを
#1040/#1082 のセマンティックトークンへ用途判定で置換、生 <button> を ui/Button へ。
挙動・ハンドラ・testid は不変。群A担当ファイルの raw gray = 0。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree詳細/markdown/fileの生gray/buttonをトークンへ (#1061 群B 2/4) (#1100)

#1061 分割並列(群B: 21ファイル)。WorktreeDetailSubComponents/MarkdownToolbar/
FileTreeView 等の生 gray/slate → セマンティックトークン、生 button → ui/Button。
ターミナル出力表示面(TerminalSplitPaneContent)はダーク維持のため意図的に非トークン化。
挙動・testid 不変。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree/mobile/gitの生gray/buttonをトークンへ (#1061 群C 3/4) (#1098)

#1061 のファイル分割並列(群C: 22ファイル)。生 gray/slate → セマンティックトークン、
生 <button> → ui/Button。挙動・testid 不変。群C担当ファイルの raw gray = 0。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree/git/todoの生gray/buttonをトークンへ (#1061 群D 4/4) (#1099)

#1061 分割並列(群D: 21ファイル)。生 gray/slate → セマンティックトークン、生 button
→ ui/Button。ターミナル出力表示面(TerminalSplitContainer/TerminalContainer)は
xterm と同様ダーク維持のため意図的に非トークン化(除外)。挙動・testid 不変。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci(guard): token-discipline ガードを worktree/mobile/external-apps へ拡大 (#1061) (#1101)

#1061 で worktree/mobile/external-apps の生 gray/slate をトークン化完了したため、
#1082 で導入した token-discipline CIガードの対象ディレクトリを拡大。
ターミナル出力表示面(*Terminal* ソース)は xterm 固定ダーク(#1079)と同様の
意図的ダーク島のため除外。app/worktrees も引き続き除外。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: integration スイートの drift を解消(モック/期待値追随・CI追加)(#1102) (#1103)

CI 非対象で長期 drift していた integration スイート(41 failed)を修復。実装バグを
隠さないよう vi.mock を importOriginal 方式へ書き換え、欠落 export を補完。

- 不完全 vi.mock を importOriginal ベースへ(欠落 export を実挙動ごと通す)
- config ドリフト(アップロード上限 5MB→20MB 等)の期待値を現行定数準拠へ
- undefined.length 系はモック戻り値を実 shape へ
- 再 drift 防止に ci-pr.yml へ Integration Tests ジョブ追加
- external-apps duplicate name → 500(期待409) は実装バグのため it.skip で quarantine

結果: test:integration 643 passed / 1 skipped(0 failed)。unit/lint/tsc 緑維持。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(external-apps): 重複nameで500ではなく409を返す (#1104) (#1105)

* fix(external-apps): 重複nameで500ではなく409を返す (#1104)

route.ts の catch を message.includes('UNIQUE constraint') 文字列判定から
ExternalAppDbError.code === 'DUPLICATE' の型/コード判定へ変更。DB層のエラー
ラップ後もメッセージに依存せず 409 を正しく返す。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(external-apps): 409 duplicate name テストを un-skip(#1104 で修正済み)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Kewton added a commit that referenced this pull request Jul 15, 2026
…ターミナル・tmux安定化 / 履歴CASCADE削除修正 #1153) (#1175)

* feat(files): スマホ版でファイルをダウンロード可能に(attachment配信+FileViewer DL導線)(#1024) (#1025)

スマホ版のファイルビューア(FileViewer, Issue #438 でモバイル専用化)から
任意種別のファイルをダウンロードできるようにする。

- B(サーバ): files/[...path] GET に download 分岐を追加し、生バイトを
  Content-Type: application/octet-stream + Content-Disposition: attachment で返却。
  filename は src/lib/http の content-disposition ヘルパーで RFC 準拠にサニタイズ
  (ASCII フォールバック + filename*=UTF-8'' パーセントエンコード、ヘッダ
  インジェクション回避)。既存 getWorktreeAndValidatePath でパス検証を再利用。
  base64 JSON を経由しないためプレビュー上限を超える大容量にも対応
- A(UI): FileViewer ツールバーにダウンロードボタンを追加し、同一オリジンの
  download URL へ遷移(iOS Safari 含めネイティブDLに乗せる)
- テスト新規29本(download API のヘッダ/パス検証/filename、content-disposition
  ヘルパー、FileViewer ボタン)
- C(独立ページのPDF分岐/DLリンク)は範囲外(モバイル動線が独立ページを使わないため)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(session): Timer 発火の送信を History に記録(send ロジックを共有サービス化)(#1028) (#1029)

Timer 機能(Issue #534)から送った指示が History(chat_messages)に残らない
問題を修正。executeTimer が cliTool.sendMessage だけを呼び createMessage/
startPolling をスキップしていたのが原因(#947 の委譲変更が起点)。

- send route L262-385 のインライン記録ロジックを src/lib/session/
  send-user-message.ts の共通関数 sendUserMessage() として抽出
  (savePendingAssistantResponse → send(画像/Copilot分岐含む)→ createMessage
  → updateLastUserMessage/orphan処理 → startPolling)。HTTP 層非依存
- send route POST を sendUserMessage() 利用へリファクタ(挙動不変)
- timer-manager.ts executeTimer を sendUserMessage() 経由に変更。
  timer_messages の status 遷移(sending/sent/failed)は維持
- テスト: sendUserMessage / executeTimer のユニット新規9件 + timer 既存更新(計26件)

これにより Timer 送信も user メッセージと assistant 応答の両方が History に残る。

スコープ外(別Issue推奨):
- tests/integration/api-send-cli-tool.test.ts の1件は base でも失敗する既存不具合
  (2引数アサート vs 3引数呼び出し)で本修正と無関係(CI の test:unit 対象外)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(todo): ブランチ単位ToDoを3状態化(未着手/仕掛/完了)(#1032) (#1033)

- migration v38: worktree_todos に status カラム追加(done=1→'done' backfill、CURRENT_SCHEMA_VERSION 37→38)
- DB層: status を真実源に done := status==='done' を派生(後方互換維持、writer整合)
- API/クライアント: PATCH で status ∈ {todo,doing,done} を検証・適用、done後方互換
- UI: 3状態巡回チップ+色バッジ+状態別件数、TodoPane全体を next-intl 化
- i18n: locales/{en,ja}/worktree.json に todo namespace 追加
- テスト: 既存5更新+新規 migration-v38(backfill検証)

Closes #1032

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(todo): ブランチ単位ToDoに詳細フィールド追加+クリックで件名/詳細を編集 (#1034) (#1035)

- migration v39: worktree_todos に detail カラム追加(TEXT NOT NULL DEFAULT ''、既存行は空詳細で移行)
- DB層: WorktreeTodo に detail 追加、create/update で detail 受理
- API: POST/PATCH で detail を検証・適用(MAX_TODO_DETAIL_LENGTH 新設)
- クライアント: WorktreeTodoItem.detail、create/update の detail 対応
- UI: 項目クリックで編集モーダル(Modal.tsx再利用)を開き件名+詳細を編集、詳細ありインジケータ
- i18n: locales/{en,ja}/worktree.json に詳細/編集の文言追加
- テスト: 既存6更新+新規 migration-v39、docs/module-reference.md 更新

Closes #1034

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(todo): ブランチ単位ToDoの詳細にコピーボタンを追加 (#1036) (#1037)

- UI: 編集モーダルの詳細欄に CopyButton を配置し詳細本文をコピー(詳細が空のときは非表示)
- 既存 src/components/common/CopyButton.tsx / clipboard-utils.ts を再利用(スマホHTTPは execCommand フォールバック)
- i18n: locales/{en,ja}/worktree.json に todo.copyDetail 追加
- テスト: TodoPane.test.tsx にコピー導線テスト3件追加
- migration/DB/API 変更なし(UIのみ)

Closes #1036

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): セマンティックデザイントークン基盤を導入 (#1041) (#1054)

globals.css に :root(light)/.dark(dark) の CSS 変数
(background/foreground/surface/muted/border/input/ring/accent-50..700/
success/warning/danger/info) を定義し、tailwind.config.js の primary(cyan)・
cmd-bg-dark を semantic トークンへ置換。layout.tsx / MainLayout.tsx の
body 背景を bg-background に統一。既存の実効値を写し取り視覚は不変(no-op)。
docs/design-system.md を新設。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): cn()/cva によるバリアント管理基盤とプリミティブ改修 (#1042) (#1055)

clsx + tailwind-merge の cn() ヘルパーと class-variance-authority を導入し、
Button/Card/Badge/Modal を cva ベースへ移行。Button ghost バリアントの
dark: 指定漏れ(ダークモードで視認性劣化)を修正。既存 API は完全互換。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): アイコンを lucide-react に統一 (#1044) (#1056)

絵文字アイコン(🤖⚡✦💻)を廃止し lucide アイコンへ置換。共通レイアウト・ナビ
(Header/GlobalMobileNav/MobileTabBar/ActivityBar/terminal page)および worktree
各所のインライン SVG を lucide に統一。docs/design-system.md にアイコンサイズ規約を追記。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): next/font で Geist Sans/Mono を導入 (#1043) (#1057)

geist パッケージ(セルフホスト)で GeistSans/GeistMono の CSS 変数を <html> に適用し、
tailwind fontFamily の sans/mono を「Geist → 日本語(Hiragino/Noto Sans JP) →
システム」フォールバックで構成。Terminal.tsx(xterm)の等幅も Geist Mono を先頭指定。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): RadixベースUIプリミティブを拡充 (#1046) (#1058)

Input/Textarea/Select/Tabs/Tooltip/DropdownMenu/Switch/Skeleton を追加
(cva + cn, セマンティックトークン着色, ライト/ダーク対応)。Portal 系は
'use client' + Z_INDEX(POPOVER)で Modal と階層整合。Sessions ページの
フィルタ/ソートを新プリミティブへ移行(適用サンプル)。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): アクセント色を統一しセマンティックトークンへ置換 (#1045) (#1059)

cyan/blue 直書き(~600箇所 / 131ファイル)を #1041 のセマンティックトークンへ置換。
用途判定: インタラクティブ=accent / フォーカス=ring / 情報=info。gray 系は
surface/background/border/muted へ実効値を保って置換。例外(Terminal.tsx の
xterm ANSI 配色 / CLI ブランド色 / .scrollbar-thin 等の固定装飾色)は維持。
globals.css の .btn-primary・検索ハイライト等もトークン化。
敵対的レビュー指摘(focus:ring-accent→focus:ring-ring 統一、design-system.md
の例外記述修正)を反映。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): 主要ページを共通UIプリミティブへ移行 (#1047) (#1060)

layout(Header/Sidebar の検索・トグル・言語)/ home / repositories / review /
more / chat / login の生button・手組みフォーム・コピペカードを @/components/ui
プリミティブへ移行。機能・挙動(onClick/フォーム送信/バリデーション/キーボード)は維持。
敵対的レビュー指摘(chat系ボタン移行・ghost の dark hover)を反映。ネイティブ select・
複合 textarea 入力バー・極小アイコントグルは意図的に残置(PR の残置リスト参照)。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): globals.css の @apply クラスを廃止し cva プリミティブを自己完結化 (#1048) (#1062)

.btn/.btn-*/.card/.card-hover/.badge*/.input を globals.css から削除し、
Button/Card/Badge の cva 定義へ実クラスを内挿(旧 @apply 定義と同値、視覚不変)。
src/ 全体で削除クラスへの参照が 0 であることを確認。worktree の一部
(WorktreeDetailRefactored/LogViewer/WorktreeList/ExternalAppForm)をプリミティブへ移行し、
マジックナンバー入りインライン style を定数化。worktree/mobile/sidebar/common の
一括プリミティブ移行は規模の都合により #1061 へ分割。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): サーフェス・奥行きを再設計しモダンダークテーマへ刷新 (#1049) (#1063)

globals.css の :root/.dark トークン値を刷新(深い base + 微リフト surface +
ヘアライン border)。Header/GlobalMobileNav に backdrop-blur(supports-[backdrop-filter]
フォールバック付)。Card に elevated/interactive variant、h1-h4 見出しタイポを調整。
敵対的レビュー指摘を反映: 主要閲覧画面のコンテンツカード(sessions/home/review)を
bg-surface/border-border へ移行し背景反転による溶け込みを解消、ダーク入力を surface-2 で
凹み表現、Card interactive にキーボード focus 対応を追加。コントラストは WCAG AA 全 pass。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): モーション基盤とマイクロインタラクションを追加 (#1050) (#1064)

tailwindcss-animate を導入し、Modal/MobilePromptSheet の enter/exit、Radix data-state
連動(DropdownMenu/Select/Tooltip)、一覧の stagger、hover lift を統一適用。
prefers-reduced-motion 対応。ターミナル/仮想スクロールには非適用。敵対レビュー指摘
(reduced-motionでのtranslate漏れ→motion-safe化、sessionsエラー時のstagger再発火→list
マウント維持、Tooltip instant-open enter、home focus parity)を反映。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): Home をダッシュボード(bento グリッド)化 (#1052) (#1065)

Home を縦積みから bento グリッドへ再構成(統計/直近セッション/ToDo/クイックアクション)。
PC は CSS Grid(grid-cols-12 + col-span)、モバイルは1カラム縦積み(useIsMobile の JS 分岐を
増やさず Tailwind breakpoint)。既存 sessions 取得フック/API を再利用し新規 API なし。既存機能
(ToDo 追加/チェック/コピー、セッションカウント)を維持。空状態対応。敵対的レビュー指摘
(ToDo タイルの h-full・見出しパリティ)を反映。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): コマンドパレット(⌘K)を追加 (#1053) (#1066)

cmdk で ⌘K(mac)/Ctrl+K(win) のコマンドパレットを追加(Navigation / Worktrees 検索 / Actions)。
input/textarea/ターミナル入力中は非発火、SSR 安全('use client')、i18n(en/ja)、Modal 相当の z-index。
モバイルは GlobalMobileNav に起動導線。敵対的レビュー指摘を反映: open 時に入力へフォーカスし
キーボード操作(type/矢印/Enter)を復旧[HIGH]、worktree 一覧は共有キャッシュ context を再利用
(最新化+失敗時 Navigation fallback)、Escape の preventDefault/stopPropagation と close 時の
フォーカス復帰。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): エージェント/セッションステータスの視覚を強化 (#1051) (#1067)

StatusDot(running/waiting/idle/error)を新設。running はグロー+パルス+静的リング halo、
waiting は amber の弱点滅。サイドバー(BranchStatusIndicator)/ Home / Sessions を StatusDot 化。
prefers-reduced-motion でパルス停止(静的リングで running≠ready を維持)、ポーリングで再発火しない
CSS infinite アニメ。敵対的レビュー指摘(reduced-motion での running/ready 識別性、docs の実態訂正)を
反映。worktree 詳細/MobileHeader は従来スピナー表示のまま(docs に明記・今後の追随対象)。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ui): サイドバーがヘッダーに遮蔽される左上レイアウト破綻を修正 (#1070) (#1083)

showGlobalNav 時に固定サイドバーを top-16 h-[calc(100vh-4rem)] にオフセットし、
半透明ヘッダー(z-50, backdrop-blur)がサイドバー先頭64px(Branches 見出し+
view/sort/sync ツールバー行)を遮蔽してクリック不能+灰色の滲みを生む問題を解消。
あわせて border-gray-200/dark:border-gray-600 を border-border にトークン化。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): テーマ既定を system 化しヘッダーにトグルを追加 (#1071) (#1084)

next-themes を defaultTheme="system" enableSystem に変更し、ライト環境ユーザーへの
強制ダークを解消。Header 右クラスタに ThemeToggle を追加(発見可能性向上)。
globals.css に color-scheme(:root=light / .dark=dark)を宣言し、残存ネイティブ
コントロールの OS クローム事故(dark で白いドロップダウン等)を防止。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(hooks): useIsMobile を matchMedia ベースにし混成レイアウトバグを解消 (#1069) (#1085)

window.innerWidth 依存を window.matchMedia(`(max-width: ${breakpoint-1}px)`)に置換。
innerWidth と CSS ビューポートが乖離する環境(実測 innerWidth=846 vs
documentElement.clientWidth=390)では、Tailwind の md:(min-width:768px)と JS 分岐が
食い違い「CSS はモバイル・JS はデスクトップ」の混成レイアウトになっていた。
max-width:breakpoint-1 は md: の正確な補集合。change イベント購読+cleanup、
SSR 初期値 false、MOBILE_BREAKPOINT export と breakpoint オプションは維持。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): Home 第一フォールドを回収(バナー削除・見出し降格・tabular-nums) (#1072) (#1086)

陳腐化した Welcome バナー(#600 のナビ再編告知)を削除。h1「CommandMate」を機能的見出し
(HomeHeading: Overview + ライブサブライン)へ降格しヘッダーのワードマークとの同語反復を解消。
Home の数値・相対時刻・サイドバーのブランチ数バッジに tabular-nums を導入。HomeQuickActions の
inline SVG を lucide へ置換。相対時刻の短縮形は新設 date-utils、見出し文言は home i18n
namespace(en/ja)。機能(ToDo/カウント/遷移)は不変、サブラインは既存キャッシュから算出。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ライトモードのエレベーション階梯を反転(グレー地+白カード+2層影) (#1074) (#1087)

light を「白ページ+凹む灰カード」から Vercel/Linear 型の「グレー地ページ+
白カード+2層影」へ反転し、dark(#1049)と対のエレベーションモデルにする。
surface-2(740) < background(751) < surface(765) の「白カードが浮き、well が沈む」階梯。

- globals.css :root: --background 255→250 250 251 / --surface 248 250 252→255 255 255
  / --surface-2 241 245 249→244 246 250。.dark は不変(回帰なし)
- tailwind.config.js: 2層 boxShadow.sm を新規追加(md/lg は Tailwind 既定のまま)
- design-tokens/surface-depth テスト更新 + WCAG コントラスト検証を追加
  (foreground≥4.5 全面 / muted-fg≥4.5 page,card / ≥3 recessed well)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): サイドバーを --sidebar-* トークン化しテーマ追従に (#1073) (#1088)

bg-gray-800 text-white 固定でテーマ孤島だったサイドバーを、新設の
--sidebar / --sidebar-foreground / --sidebar-border / --sidebar-hover /
--sidebar-muted セマンティックトークンへ移行。light=slate-50系の明るいパネル、
dark=#141821(#1049階梯と整合)でテーマに追従する。standalone なリテラルRGB値
としており、#1074 の --surface 反転がサイドバー色へ滲まない。

- globals.css :root/.dark に --sidebar-* を定義、tailwind.config.js に登録
- Sidebar.tsx / sidebar/*(BranchListItem, SortSelectorBase)/ 検索Input
  / LocaleSwitcher / ThemeToggle の直書きを全てトークンへ置換
- 敵対的レビュー由来のライト回帰修正2件:
  - LogoutButton.tsx: 漏れていた text-gray-300 直書きを ThemeToggle と同じ
    テーマ中立トークン(text-muted-foreground/hover:bg-muted)へ(モバイル共用)
  - SortSelectorBase: 選択中ソートを text-accent-700 dark:text-accent-400 に
    (ライトのslate-50上で 1.73→約5.1:1 でAA達成、dark時は据え置き)
- docs/design-system.md に --sidebar-* を追記、既存テスト/期待値を更新

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ⌘K パレットを Raycast 級に強化(アイコン/StatusDot/kbd/Recents/ヘッダー導線)(#1077) (#1089)

- src/components/ui/Kbd.tsx を新設(semantic-token ベース、#1080 で再利用)し ui/index.ts に export 追加
- NAV 行に lucide アイコン、worktree 行に GitBranch + StatusDot(isProcessing/isWaitingForResponse/isSessionRunning から算出)
- バックドロップ/パネル/選択状態/入力/フッター文字色を semantic トークン化(bg-surface/backdrop-blur/ring-border/accent-500)
- キーボードガイドのフッター(↑↓/↵/esc、i18n)
- Recent グループ(localStorage cm.palette.recents、MRU<=8、空クエリ時のみ、dead worktree は無視)
- worktree を isSessionRunning 優先→updatedAt 降順ソート+空クエリ時 slice(0,8)
- Actions 拡充: Sync repositories(repositoryApi.sync + Toast)/ 言語切替 / Open GitHub
- Header に ⌘K 検索ピル(mount 後に platform 判定して ⌘K/Ctrl K を出し分け)
- #1053 の挙動契約(フォーカス/入力ガード/Escape/backdrop/data-testid)を維持
- 文言を locales/{en,ja}/commandPalette.json に追加、単体テスト更新

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ターミナルクロームを刷新(全周枠廃止・カード化・CLIセレクタDropdownMenu化)(#1079) (#1090)

worktree ターミナルの全周枠を廃し surface-2 カード+ヘアラインのカード化に統一。
CLI インスタンスセレクタを native <select> から Radix DropdownMenu(StatusDot+alias
+ChevronDown)へ置換し、value/onChange の単一選択セマンティクスと testid を保持。

- Terminal / ActivityBar / TerminalDisplay / TerminalSplitContainer /
  TerminalSplitPane / TerminalSplitPaneContent を surface/border トークンへ
- TerminalSplitPane: <select> → DropdownMenu(RadioGroup)。cli-selector testid 保持
- xterm の theme は意図的に固定ダーク(--surface-2 dark 相当)を維持
- StatusDot/status-colors.ts は不変(#1078 の領域)。locales worktree.json 追記
- 既存 terminal テスト(cli-tab-switching 含む)更新・全パス

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): worktree詳細/Chat のダーク直書き島を surface トークン化しテーマ追従に (#1075) (#1091)

HistoryPane / ConversationPairCard / AssistantMessageList / AssistantChatPanel /
HistorySearchBar / CopyButton と .assistant-md の常時ダーク直書きを surface/foreground
セマンティックトークンへ移行し、light/dark 両テーマで正しく描画されるようにする。

- globals.css .assistant-md: リンクを accent-700 dark:accent-400、code/pre を明色地に
  し両テーマ AA≥4.5:1。:root/.dark のトークン定義は不変
- 各コンテナ+全子孫を surface/surface-2/foreground/muted-foreground/border トークンへ
  (hidden-children 回避: 子の直書き light-on-dark を残さない)
- AssistantChatPanel/HistoryPane の native select/checkbox は色のみトークン化
  (Radix 化は #1076 の担当のため据え置き)
- ConversationPairCard: コピー/挿入/展開ツールバーの hover-reveal 化に伴い、
  タッチ端末で不可視化する回帰を [@media(hover:none)]:opacity-100 で解消
  (デスクトップの hover-reveal は維持。copy-user/assistant-message testid 保持)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): データ画面コントロールの意味論を整理(Review/Repositories/More)(#1081) (#1092)

表示のみの調整。絞り込み・編集・削除・可視性トグルのロジックは不変。

- Review: フィルタを CTA 風の primary/secondary ボタンから、件数付きの
  控えめなセグメンテッドコントロールへ(選択は bg-accent-500/15、CTA と区別)。
  件数は既存のフィルタ済み配列長から算出(新規 fetch なし)。
- Repositories: Enabled/Disabled バッジと可視性トグルの緑ピルを StatusDot 流の
  dot+muted テキストへ(トグルは操作可能なまま)。(none)→ muted の em-dash、
  パスは truncate+font-mono+full-path tooltip、Edit は ghost な Pencil アイコンボタン。
- More: ExternalAppsManager 側の重複 h2「External Apps」を削除(ページ側に一本化)。
- StatusDot/status-colors.ts は不変(#1078 の領域)。生 gray/cyan/blue の追加なし。
- 既存テスト更新+件数バッジ/単一見出しの検証テストを追加。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): コンポーザーをカード化しモバイルworktreeコントロールを整理 (#1080) (#1093)

- MessageInput をコンポーザーカード化(surface/border/shadow/focus-ring)し、送信
  ボタンを入力有無で filled(bg-accent-600)/ghost に切替。プレースホルダを i18n 短縮
- Auto-Yes をコンポーザー下辺のメタ行へ統合(ui/Switch 化)。右側に Kbd キーヒント
  (#1077 の ui/Kbd 再利用)。InterruptButton のオレンジ直値を text-danger ghost へ
- モバイル sticky 行をインスタンスタブ専用化(dark: バリアント+下線インジケータ)。
  検索/End を MobileTerminalActionsSheet(ボトムシート、End は確認付き)へ移設
- MobileTabBar を GlobalMobileNav 様式(bg-background/backdrop-blur/border-border、
  上辺インジケータ、Wrench アイコン、success/warning バッジ)へ統一
- テスト: 送信ボタン状態変化/新規シートを追加、ResizeObserver polyfill(Radix Switch)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): 稼働ステータスを StatusDot 一本化し idle ノイズを縮退 (#1078) (#1094)

青スピナー(border-info)と生 span を全て統一 StatusDot に置換し、稼働ステータスの
視覚言語を1系統に。idle ノイズを縮退(Sessions は running/waiting のみラベル付き、
idle は「+N idle」カウンタ / worktree ヘッダーは idle を StatusDot のみの円形ボタン
+Tooltip、幅超過は「+N」DropdownMenu オーバーフロー)。

- WorktreeDetailSubComponents / sessions/page / MobileHeader を StatusDot へ統一
- 新規 lib/agent-status-display.ts に classifyHeaderInstances 等の導出を集約
- status-colors.ts の type:'spinner'/border-info は @deprecated 化(ReviewTab が
  最後の消費者。消滅後に削除)
- 相対時刻 tabular-nums / プレビューは CSS truncate、DnD(handleAgentDragStart)と
  タブ切替 onClick は全ボタンで温存
- 敵対レビュー由来の仕上げ: 「+N」オーバーフローに稼働中(running/waiting)があれば
  集約 StatusDot グローを出し、稼働セッションが glow-less に埋もれないように。
  同 trigger の生グレーをセマンティックトークンへ

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ネイティブ checkbox/radio を Radix プリミティブに統一 (#1076) (#1095)

OS 標準の白いフォームコントロールが dark に浮く問題を解消。@radix-ui/react-checkbox
/ react-radio-group ベースの ui/Checkbox・ui/RadioGroup を新設し、残存していた
ネイティブ input(checkbox 12箇所/11ファイル・radio 6箇所/4ファイル)を全て移行。

- 新規 src/components/ui/{Checkbox,RadioGroup}.tsx(cn()+cva・セマンティックトークン・
  focus-visible:ring-ring)、ui/index.ts に export、依存2つを追加
- checked/onCheckedChange・value/onValueChange への適応、name/value/disabled/
  aria/data-testid とラベルクリックのトグルを全移行で保持
- 移行後 native type="checkbox"/"radio" は src/ 配下 0 件(完了性 grep 検証済)
- 敵対レビュー(binding/primitive-a11y/GitDangerZonePanel 密集)0件

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): トークン規律の確立(生gray/slate置換・focus-visible統一・CIガード)(#1082) (#1096)

移行済み領域の生 gray/slate 直書きをセマンティックトークンへ置換し、フォーカス表現を
focus-visible に統一、CI grep ガードでトークン違反の再流入を止める。機能・挙動は不変
(見た目/規律のみ)。

## プリミティブ是正
- Button: secondary/ghost を muted/foreground、danger を bg-danger トークンへ。
  `focus:` → `focus-visible:` + `ring-offset-background`(dark の白ハロー解消。Tabs/Switch が手本)。
- Input/Textarea/Select トリガー: `focus:` → `focus-visible:`(枠が反応するため offset なし)。
- Badge gray → bg-muted/text-muted-foreground、Modal → surface/border/foreground。

## in-scope スイープ(生 gray/slate → トークン)
- app(worktrees を除く)/ ui / layout / home / review / repository / common。
- gray-100/200→muted、gray-400/500/600→muted-foreground、gray-700/800/900→foreground、
  境界→border、フォーム枠→input。

## globals.css / metadata / docs
- body→text-foreground、.prose インラインコード→muted/foreground、scrollbar-thin をトークン化
  (--input / --muted-foreground)、`::selection` にアクセント淡色を追加。
- layout.tsx: title template(`%s | CommandMate`)+ viewport themeColor(light/dark)。
- docs/design-system.md にフォーカス表現・CI ガード・例外リストを同期。

## CI ガード
- `.github/workflows/ci-pr.yml` に `token-discipline` ジョブを追加(CLAUDE.md size check と同方式)。
  ホワイトリスト方式で移行済みディレクトリ限定 hard-fail。除外リストをスクリプト内に明記。
  違反注入時に fail することを確認済み。

## 判断メモ(要レビュー)
- `src/app/worktrees/**`(40 occ)はオーケストレーター集計では app(108) に含まれるが、
  ワークツリー領域(#1061・「触らない」)かつ常時ダークのターミナルページ/CLI ブランド色
  (bash=bg-gray-600)を含むため、スイープ・ガード双方から除外した。#1061 完了時に拡大。
- 軽微な見た目変化: danger の red-600→red-500(--danger)、secondary/ghost の muted シェード、
  SidebarToggle はテーマ追従化、ツールチップは反転サーフェス(bg-foreground/text-background)へ
  テーマ追従化、Home の Save&Resend ボタンを accent-600 + 白文字に正規化。

品質ゲート(lint / tsc --noEmit / test:unit / build)+ 新 CI ガード 全パス。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree/共通の生gray/buttonをトークン・プリミティブへ (#1061 群A 1/4) (#1097)

#1061 のファイル分割並列(群A: 21ファイル)。生 gray/slate Tailwind クラスを
#1040/#1082 のセマンティックトークンへ用途判定で置換、生 <button> を ui/Button へ。
挙動・ハンドラ・testid は不変。群A担当ファイルの raw gray = 0。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree詳細/markdown/fileの生gray/buttonをトークンへ (#1061 群B 2/4) (#1100)

#1061 分割並列(群B: 21ファイル)。WorktreeDetailSubComponents/MarkdownToolbar/
FileTreeView 等の生 gray/slate → セマンティックトークン、生 button → ui/Button。
ターミナル出力表示面(TerminalSplitPaneContent)はダーク維持のため意図的に非トークン化。
挙動・testid 不変。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree/mobile/gitの生gray/buttonをトークンへ (#1061 群C 3/4) (#1098)

#1061 のファイル分割並列(群C: 22ファイル)。生 gray/slate → セマンティックトークン、
生 <button> → ui/Button。挙動・testid 不変。群C担当ファイルの raw gray = 0。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree/git/todoの生gray/buttonをトークンへ (#1061 群D 4/4) (#1099)

#1061 分割並列(群D: 21ファイル)。生 gray/slate → セマンティックトークン、生 button
→ ui/Button。ターミナル出力表示面(TerminalSplitContainer/TerminalContainer)は
xterm と同様ダーク維持のため意図的に非トークン化(除外)。挙動・testid 不変。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci(guard): token-discipline ガードを worktree/mobile/external-apps へ拡大 (#1061) (#1101)

#1061 で worktree/mobile/external-apps の生 gray/slate をトークン化完了したため、
#1082 で導入した token-discipline CIガードの対象ディレクトリを拡大。
ターミナル出力表示面(*Terminal* ソース)は xterm 固定ダーク(#1079)と同様の
意図的ダーク島のため除外。app/worktrees も引き続き除外。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: integration スイートの drift を解消(モック/期待値追随・CI追加)(#1102) (#1103)

CI 非対象で長期 drift していた integration スイート(41 failed)を修復。実装バグを
隠さないよう vi.mock を importOriginal 方式へ書き換え、欠落 export を補完。

- 不完全 vi.mock を importOriginal ベースへ(欠落 export を実挙動ごと通す)
- config ドリフト(アップロード上限 5MB→20MB 等)の期待値を現行定数準拠へ
- undefined.length 系はモック戻り値を実 shape へ
- 再 drift 防止に ci-pr.yml へ Integration Tests ジョブ追加
- external-apps duplicate name → 500(期待409) は実装バグのため it.skip で quarantine

結果: test:integration 643 passed / 1 skipped(0 failed)。unit/lint/tsc 緑維持。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(external-apps): 重複nameで500ではなく409を返す (#1104) (#1105)

* fix(external-apps): 重複nameで500ではなく409を返す (#1104)

route.ts の catch を message.includes('UNIQUE constraint') 文字列判定から
ExternalAppDbError.code === 'DUPLICATE' の型/コード判定へ変更。DB層のエラー
ラップ後もメッセージに依存せず 409 を正しく返す。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(external-apps): 409 duplicate name テストを un-skip(#1104 で修正済み)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(timer): Timer履歴の詳細モーダル表示と失敗理由の記録 (#1107) (#1109)

Schedule 側の execution_logs 方式を Timer へ横展開。

- DB: migration v40 で timer_messages に error 列(nullable)追加、schema 39→40
- timer-db: error 列の read/write 貫通、updateTimerStatus に optional error 引数、recoverStuckSendingTimers に固定リカバリ理由
- timer-manager: 送信失敗(stage/error)・例外(error.message)を error 列へ永続化(no_session は保存せず)
- API: GET timers レスポンスに error を追加
- UI: TimerDetailModal を新設し履歴行クリックで指示全文+メタ+失敗理由を表示(PC=Modal / mobile=FullScreenModal)
- i18n: schedule.json の timer ブロックに en/ja lockstep でラベル追加

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(files): Filesツリー展開状態のworktree単位永続化と表示リセット (#1108) (#1111)

useFileTabs / useFilePanelState の worktree 単位 localStorage パターンを踏襲。

- 新規 useFileTreeExpandedState: 展開集合を worktree 単位 localStorage(キー commandmate:file-tree-expanded:<worktreeId>) に永続化。lazy-init 復元をマウント再フェッチに乗せ、空集合はキー削除、worktree 間の漏洩なし
- useFileTabs: CLOSE_ALL / closeAllTabs() を追加
- FileTreeView: フック統合+ツールバーに「表示をリセット」ボタン(更新ボタンの隣・常時可視)+ onResetView prop
- Controller/Desktop/Mobile/Refactored: resetFileTreeView(検索クリア・全タブ閉じ・モバイルビューア閉)を配線し、初期表示に戻すフルリセットを実現

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(mobile): viewportFit:'cover' を追加しiOS safe-area対応を有効化 (#1132)

- src/app/layout.tsx の viewport エクスポートに viewportFit: 'cover' を追加。
  iOS は viewport-fit=cover 指定時のみ env(safe-area-inset-*) に非0値を
  入れるため、既存の pt-safe/pb-safe 等の safe-area 対応が実効化される
- GlobalMobileNav の未定義クラス safe-area-bottom を globals.css 定義済みの
  pb-safe に修正(cover 有効化後もパディングが効かない潜在バグ)
- viewport エクスポート値(viewportFit / themeColor)のユニットテストを追加

レンダリング検証: 本番ビルドで <meta name="viewport"> に
viewport-fit=cover が含まれることを確認済み

Refs #1131

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat(ui): ステータスカラーtintトークン導入とダーク非対応フィードバックUIの移行 (#1133)

- globals.css / tailwind.config.js に success/warning/danger/info × subtle/border/foreground の12トークンを両テーマで追加(ライト *-50 面 + *-800 前景、ダーク *-950 面 + *-300 前景、全て WCAG AA 準拠。根拠はコメントに記載)
- Toast / DefaultErrorFallback / History・Prompt・ConnectionErrorFallback / ErrorDisplay / PromptPanel を新トークンへ移行し、生パレット + dark: ペアを撤去(TerminalErrorFallback は常時ダーク島のため現状維持)
- tint 面内のソリッドアクションボタンは反転 tint(bg-{status}-foreground text-{status}-subtle)で両テーマ AA を確保
- docs/design-system.md にトークン表・使用ルールを追記
- tint トークン定義・移行 grep を検証する status-tint-tokens.test.ts と ErrorBoundary/フォールバックのレンダリングテストを追加、既存 Toast / design-tokens テストを追随

Refs #1112

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* refactor(ui): window.confirmを共通ConfirmDialogへ全面置換 (#1134)

- ConfirmDialog/ConfirmProvider/useConfirm を src/components/ui/ConfirmDialog.tsx に新設
  (variant default/danger、Escape/背景クリックでキャンセル、danger時はcancelに初期フォーカス、
  クローズ後に呼び出し元へフォーカス復帰)
- AppProviders に ConfirmProvider をマウント
- window.confirm 残存11箇所 + bare confirm 1箇所(WorktreeCard)を useConfirm へ置換
- confirm文言を next-intl 辞書へ移動 (review namespace 新設、home/worktree/common に追記)
- ESLint no-restricted-globals(confirm/alert) と no-restricted-properties(window.confirm) を追加
  (bare alert 3箇所は window.alert へ変更、alert置換自体は対象外)
- 既存テストを window.confirm モックからダイアログ操作へ追随、
  ConfirmDialog 単体テストと Timer履歴クリアの結合フローを追加

Refs #1113

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* chore(ui): UI死コード削除(WorktreeList・放棄infinite-scroll hooks・hasNewOutput配線) (#1135)

- WorktreeList.tsx(production importゼロの孤児コンポーネント)とbarrel export(index.ts)を削除
- 放棄された無限スクロール実装(useInfiniteMessages/useScrollObserver/useScrollRestoration)と
  対応テスト3本、孤児化するtypes/infinite-messages.tsを削除
- WorktreeDetailRefactored.tsxのhasNewOutput={false}死配線を撤去(MobileTabBar側は維持)
- 残置ファイルのstaleコメント修正(useVisibilityRecovery.ts/history-display-config.ts)
- useWebSocket.tsに@deprecated注記を付与(WebSocketストリーミング化で再設計・再利用予定)
- docs/architecture.md(ja/en)の削除対象ファイルへのstale参照行を削除

受入基準: 3グレップ全て0件成立、lint/tsc/test:unit(8746 pass)/build全パス

Refs #1115

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* fix(ui): 未定義animate-fade-*の定義とModal/Toast/ContextMenuのexitアニメーション実装 (#1114) (#1136)

* fix(ui): 未定義animate-fade-*の定義とModal/Toast/ContextMenuのexitアニメーション実装

- tailwind.config.jsにfade-in/fade-outキーフレームとanimate-fade-in/outを定義。
  usePromptAnimationが発行していたが未定義でno-opだった。クラス名を
  PromptPanel等が文字列比較で消費しているため、tailwindcss-animateへの
  書き換えではなくキーフレーム定義の追加を採用。duration/easingは
  --motion-duration-base / --motion-ease-out(#1050モーショントークン)準拠
- content globsにsrc/hooksを追加(フック発行クラスのpurge防止)
- useExitAnimation(open, duration)フックを新設: 閉要求後もduration msだけ
  マウントを維持しexitアニメーション再生を保証。render-phase同期により
  開閉ともに1フレームの遅延なし。再open時はタイマー解除
- Modal: data-state=closedでfade+zoom-out exit(200ms)後にアンマウント。
  exit中はbackdropクリック無効化
- Toast: fade+slide-out exit(200ms)再生後にonClose通知(アンマウント遅延)
- ContextMenu: fade+zoom-out exit(100ms、enterと同duration)。
  exit中はpointer-events-none
- EXIT_ANIMATION_DURATION_MS / CONTEXT_MENU_EXIT_DURATION_MSを
  ui-feedback-config.tsに追加しCSSとJSタイマーを同期
- reduced-motionはglobals.cssのグローバルキルスイッチで対応(CSS実装のみのため追加ガード不要)
- テスト: useExitAnimation単体、Modal/Toast/ContextMenuのexit挙動
  (閉操作後にアニメーション時間経過までDOM残留→アンマウント)、
  Tailwind実コンパイルによるCSS生成検証を追加

Refs #1114

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ui): ConfirmDialogをModal exitアニメーションに追随

- ConfirmDialog.test.tsx: 閉操作後の即時DOM除去期待を、200ms exit
  アニメーション完了後のアンマウント(waitForElementToBeRemoved)に更新
- ConfirmProvider: pendingクリア後のexit再生中にdescription等が空文字に
  なり内容がブランク表示される統合ドリフトを修正(直近optionsを保持し
  フェードアウト中も表示を維持)。cancelテストにブランク防止の検証を追加

Refs #1114

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat(ui): ヘッダーナビのアクティブインジケータ実装と角丸スケール規約の導入 (#1137)

- Header ナビに CSS-only の下線インジケータを実装(after 疑似要素の
  scale-x トランジション、--motion-ease-out / duration-200 準拠、
  motion-safe ガード)。アクティブ項目に aria-current="page" を付与
- Header のアクティブ状態レンダリングテストを追加(全 6 ルート)
- docs/design-system.md に角丸スケール規約を独立セクションとして追記
  (コントロール=rounded-md / コンテナ=rounded-lg / ポップアップ=rounded-md /
  チップ=rounded-full / 小型インライン=rounded-sm、裸 rounded 原則禁止)

角丸統一の実施範囲(明確な逸脱のみ、feature 配下の一括置換はしない):
- ui/Kbd・ui/Checkbox: 裸 rounded → rounded-sm
- layout/Sidebar のアイコンボタン3箇所: 裸 rounded → rounded-md
- layout/Header のコマンドパレットトリガー: rounded-lg → rounded-md

Refs #1119

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* refactor(ui): チャット履歴UIをConversationPairCard系デザインへ統一 (#1138)

MessageList/PromptMessageの視覚言語をConversationPairCardに統一
(案B: 構造・optimistic updateロジック維持、見た目のみ刷新)。

- 生chromatic色21箇所をステータスtintトークン(warning/success/info x
  subtle/border/foreground)へ移行し、生色のdark:ペアを撤去
- インラインSVGをlucide-reactへ統一(Check/X/CircleCheck/Clock/Loader2/
  MessageCircle)
- ユーザー/アシスタントメッセージをCPC言語のセクション(accent左ボーダー/
  surface-2 wash)+ hover-revealコピーツールバーへ刷新
  ([@media(hover:none)]:opacity-100 タッチフォールバック付き)
- markdown描画をprose→assistant-md(トークン追従)へ移行、ANSI/コード
  ブロックの常時ダーク島(#0d1117)は現状維持
- ファイルパスリンク/プロンプト応答(Yes/No・選択肢・テキスト入力)/
  ログリンクの機能パリティ維持、レンダリングテストを新規追加

Refs #1117

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat(ui): スケルトンローディング展開とSpinner統一・loading.tsx整備 (#1139)

- ui/Spinner.tsx 新設: 手書きインラインSVG/border系スピナー43箇所を共通プリミティブへ置換
- 主要画面にレイアウト整合スケルトンを実装(Home Session Overview/ToDo、Sessions一覧、Repositories一覧、HistoryPane(汎用Card型・loading分岐のみ)、ファイルツリー、AssistantChatPanel、Reviewタブ群)
- 裸テキストローディングをスケルトンへ置換(MessageList.tsxは#1117衝突回避のため対象外)
- 初回ロードのみスケルトン表示(再fetch時は既存コンテンツ維持の非ブロッキングパターン踏襲)
- 主要7ルートに loading.tsx(薄いRouteLoadingフォールバック)を追加
- AssistantChatPanelのローディングを導出状態化し空状態フラッシュを解消

Refs #1118

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* refactor(ui): 生chromatic色をtintトークンへ全面移行しCIガードを拡大 (#1140)

意味ベースでステータスtintトークンへ移行 (red→danger / green→success /
yellow・amber・orange→warning / blue・sky・purple・violet→info)。
淡色面は bg-{s}-subtle + border-{s}-border + text-{s}-foreground、
ソリッドは bg-{s} を使用し、chromatic用途の dark: ペアを撤去。

- ui/Badge.tsx・config/status-colors.ts をトークン化 (生パレットクラス撤去)
- error/ の常時ダーク島 TerminalErrorFallback を *Terminal* 名の別ファイルへ分離し、
  残る error/・auth/ の生gray/chromatic漏れを回収
- 検索ハイライト/ファイル種別アイコン等の装飾色もトークン化 (text-pink等スコープ外色は維持)
- CIガード token-discipline の grep パターンに chromatic 9色を追加し error/・auth/ を対象化。
  既存の *Terminal*・テスト・常時ダーク島 (worktrees/[id]/terminal 等) の除外は維持
- docs/design-system.md に chromatic 禁止/例外リストとCIガード仕様を追記
- 影響する class 検証テストを追随
  (Badge / ContextMenu / MarkdownEditor / MemoCard / dark-mode-foundation / recursive-delete)

chromatic grep 残数
(git grep -cE '(bg|text|border|ring)-(red|green|yellow|amber|orange|purple|violet|sky|blue)-[0-9]' -- src/):
  before: 254 行 (71 ファイル)
  after:  19 行 (サンクション済み例外のみ = *Terminal* 群 + src/app/worktrees/[id]/terminal/)
  in-scope (例外除く): 0

品質ゲート: npm run lint / npx tsc --noEmit / npm run test:unit (8818 passed) / npm run build 全パス

Refs #1116

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): View Transitions APIによるページ遷移クロスフェードを導入 (#1141)

主要ルート間(Home/Chat/Sessions/Repos/Review/More/worktree詳細)のコンテンツ
領域をクロスフェードするプログレッシブエンハンスメントを追加する。

実装方式は「自前の薄いラッパ(案B)」を採用し、next-view-transitions等の外部
依存は追加しない。理由:
- 受入基準が機能検出ガード/reduced-motion無効化の単体テストを要求しており、ガード
  関数を自前で保持することで直接テスト可能になる(ライブラリ委譲では不可)
- framer-motion不採用方針(docs/design-system.md #1050)と同様にCSS駆動とし、JSは
  startViewTransition呼び出しのガードのみに限定してバンドルを軽量に保つ
- Next15/React19ネイティブView Transitionsへの載せ替えが容易(抽象が薄い)
- ライブラリ導入時のLink全面置換を避け、変更をnavシェルとプログラム遷移に限定

主な構成:
- src/lib/view-transitions: supportsViewTransitions/prefersReducedMotion/
  startViewTransition。非対応・reduced-motion時は即時遷移フォールバック
- ViewTransitionsProvider: startViewTransitionをApp Routerナビゲーションへ橋渡し。
  usePathnameのコミット監視で新DOM確定後にresolveし、同一ルート等の非コミット時は
  COMMIT_SAFETY_TIMEOUT_MS=500で確実にsettle
- TransitionLink / useViewTransitionRouter: Header・GlobalMobileNav・Sidebarで使用。
  修飾クリック/別タブ/外部/同一ルートはブラウザ既定にフォールスルー
- globals.css: [data-view-transition="content"](AppShellの<main>のみ)をクロス
  フェードし、永続シェル(ヘッダー/サイドバー/ボトムナビ)はrootを固定して除外。
  ::view-transition背景を--backgroundに設定しダークテーマの白フラッシュを防止。
  200ms(--motion-duration-base)/ --motion-ease-out準拠、reduced-motionで無効化

Refs #1122

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* perf(ui): HistoryPaneを@tanstack/react-virtualで仮想化し250件上限を撤廃 (#1123) (#1142)

* perf(ui): HistoryPaneを@tanstack/react-virtualで仮想化し250件上限を撤廃

履歴リスト(HistoryPane)を仮想化し、可視領域+オーバースキャンのみを
マウントするよう変更。従来はカードをフラット描画し、250件上限は性能緩和策
だった。仮想化により描画コストが O(可視行) となり、上限を1000へ緩和した。

主な変更:
- FilePanelContent の流儀に揃え useVirtualizer + measureElement(可変高)で
  ConversationPairCard を仮想描画。展開/折りたたみ・コードブロック等の高さ
  変化は measureElement が自動再計測。
- スクロール位置維持: 追記は react-virtual が自動維持。最下部閲覧中
  (isNearBottom)のみ新規ペアを追従(follow/maintain を分離)。
- 検索ジャンプ整合: 対象行を scrollToIndex で materialize してからハイライト
  適用。マウント窓の変化(renderedRange)でハイライトを再適用。
- カードのアンマウントで展開状態が飛ばないよう、展開状態はリスト側/親
  (useConversationHistory + 検索auto-expand)で保持(#1121 の pending 挿入も
  この item 管理の上に載せやすい構成)。
- 250件上限を緩和: HISTORY_DISPLAY_LIMIT_OPTIONS に 500/1000 を追加
  (MAX_MESSAGES_LIMIT=1000 に自動追従、APIの取得上限も1000へ)。
- src/lib/history-virtualization.ts: overscan/estimate/しきい値の定数と純粋な
  isNearBottom を分離しユニットテスト可能に。
- テスト: jsdom は無レイアウトのため offsetHeight を与える helper
  (tests/helpers/virtual-layout.ts)と1000件fixtureを追加。仮想化のDOM件数・
  追従/維持判定・展開折りたたみ・検索名前空間を網羅。

1000件fixture before/after(jsdom初期描画。体感FPSは手動検証):
- マウントカード数: 1000 → 16
- DOMノード数: 26,001 → 449(約58分の1)
- 初期描画時間: 289ms → 24ms(約12倍高速)

品質ゲート: lint / tsc --noEmit / test:unit(8855 passing) / build 全パス。

Refs #1123

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ui): HistoryPane仮想化のゼロ計測フォールバックで本文が消える問題を修正

WorktreeDetail経由の統合テスト(jsdomはレイアウト高さ0)で virtualizer が
アイテムを0件しか materialize せず、メッセージ本文が描画されず getByText が
失敗していた(tests/integration/worktree-detail-integration.test.tsx の
'renders user and assistant messages')。

修正: virtualizer がビューポート未計測でアイテム0件のとき(初回レンダー/SSR/
jsdom 等のレイアウト0環境)、先頭 HISTORY_FALLBACK_RENDER_COUNT 件を通常フロー
で描画するフォールバックを HistoryPane に追加。実ブラウザでは layout-effect の
計測直後に仮想化リストへ切り替わる(フォールバック描画は塗り前の一時コミット)。
本番の仮想化保証を損なわないよう件数は小さく固定。SSRのハイドレーション整合
(server/client双方が同じ先頭スライスを描画)にも寄与する。

単体テスト側は既存の virtual-layout ヘルパーで実 virtualizer 経路を検証し続ける
ため、この修正はそれらに影響しない。

品質ゲート: lint / tsc --noEmit / test:unit(8855) / test:integration(648) /
build 全パス。

Refs #1123

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(realtime): ターミナル出力・セッションステータスをWebSocketストリーミング化 (#1120) (#1143)

* feat(realtime): ターミナル出力・セッションステータスをWebSocketストリーミング化

実装済みだが未接続だったWebSocketインフラをクライアントへ接続し、ポーリングを
フォールバックとして残しつつサーバ→クライアントの一方向pushへ移行する。

- useWebSocket再設計: 単一WS接続・指数バックオフ再接続(1s..30s)・visibility連携・
  room購読再送・エンベロープparse。RealtimeProvider/useRealtimeで共有1本化+
  listener fan-out+購読refカウント(Provider未装着時はpollingフォールバック)
- セッションステータスpush: send時にrunning遷移をbroadcast、kill時のstopと合わせ
  サイドバー状態ドットを即時反映。WS接続中はuseWorktreesCacheのポーリングを間引き
- ターミナルストリーミング: response-poller tickからterminal_snapshotをpush
  (buildCurrentOutputでpull/push一致、購読者0でtmux captureをスキップ)。
  WS接続中はuseTerminalPanePollingを15sフォールバックへ間引き、version guardで
  stale破棄(既存stale-response/visibility堅牢性を維持)
- 差分適用: computeTerminalUpdateで追記/リセットを判定しTerminalDisplayを
  keyed chunk追記化、全置換を廃止してテキスト選択を維持(ANSI境界退避付き)
- モバイル新着出力バッジをpush駆動で復活、接続状態インジケータを追加
- 認証: Cookie自動付与でWS upgrade、認証失敗時は既存機構が接続拒否

テスト: useWebSocket(購読/再接続/バックオフ)・差分適用(追記/リセット/ANSI境界)・
WS切断→ポーリング復帰→WS再接続の統合テスト・認証拒否のE2E統合テストを追加。
lint/tsc/test:unit/test:integration/build 全パス。

Refs #1120

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(realtime): useRealtimeConnection等のNULバイト混入を除去

useRealtimeConnection.tsx の room購読キー生成で空白(0x20)が2箇所NULバイト
(0x00)に化けており、gitがバイナリ扱いしdiff/レビュー不能になっていた。
本来の ' ' 区切り(useWorktreesCache と同一パターン)へ復元し、ロジックは不変。

- src/hooks/useRealtimeConnection.tsx: `.join('\0')`/`.split('\0')` → `.join(' ')`/`.split(' ')`
- src/components/worktree/FileTreeView.tsx: 既存のセンチネル `'\0unavailable'` から
  NULを除去し `'unavailable'`(JSON.stringify結果と衝突しない性質は維持)

src/配下のNULバイト混入は0件を確認。lint/tsc/test:unit/test:integration/build 全パス。

Refs #1120

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(chat): メッセージ送信のOptimistic UIを実装 (#1144)

送信瞬間に pending 吹き出しを履歴末尾へ即時挿入し、サーバ echo で実データへ
照合置換(重複表示なし)、失敗時はエラー表示+再試行/破棄する Optimistic UI を
追加。

- usePendingMessages 層: serverMessages に pending をマージし ChatMessage
  (optimisticState 付き)として表示側へ渡す。content 一致+作成時 baseline 外+
  consumedServerIds の一意消費で照合(連投 1:1/既存同一文言の誤照合回避)。
  API reject/timeout(30s) で error→retry/discard。
- ConversationPairCard/HistoryPane: sending(opacity低下+スピナー)と
  error(danger トークン+Retry/Discard)を描画。pending 中は
  「Waiting for response…」を抑止。
- MessageInput: onOptimisticSend 提供時は送信を委譲し composer を即時クリア
  (draft も除去)。他サーフェスは従来の await→clear を維持。
- TerminalSplitPaneContent: 各 split の useSplitMessages に usePendingMessages を
  重ね、送信→即時表示→onSent refetch で照合。破棄時は既存 insert 経路で draft 復元。
- 仮想化リストと整合: pending も pair 化され virtualizer item として最下部追従。

テスト: usePendingMessages(挿入/照合/失敗/連投/タイムアウト/retry/discard)、
ConversationPairCard(sending/error/retry/discard)。

Refs #1121

* fix(mobile): useIsMobileを描画前レイアウトエフェクトで解決し初回デスクトップ描画フリップを解消 (#1145)

## 事象
useIsMobile がSSR安全のため初期値 false で始まり、実際の検出を
passive な useEffect(ペイント後実行)で行っていたため、モバイル実機の
ハードリロードで毎回デスクトップUIが一瞬描画されてからモバイルUIへ
フリップしていた(AppShell の固定サイドバー / WorktreeDetail の
デスクトップ⇔モバイル分岐でのフラッシュ・CLS)。

## 対策の選定(案A/B/C のトレードオフ)
- 案A(CSSで両ツリーを出し分け): 初回フレームは正しいが、重い
  WorktreeDetail が二重マウントし、両分岐の terminal ペインが同時に
  ポーリングを開始する。ヘビーツリーには不採用。
- 案B(UAヒント+cookie): ブレークポイントは viewport 幅基準
  (max-width:767px)で UA の mobile とは一致せず(狭幅デスクトップ/
  タブレット)、真の hydration mismatch を再導入する。不採用。
- 案C(採用・描画前レイアウトエフェクト版): SSR/初回クライアント
  レンダリングは決定的に false(デスクトップ)を返し SSR HTML と一致
  するため hydration mismatch 警告は出ない。検出は useLayoutEffect
  (isomorphic)で行い、commit 後・ブラウザ描画前に flush されるため、
  補正後の isMobile 値が最初のフレームに反映される。モバイルでは
  デスクトップツリーは描画される前に差し替えられる。

## ヘビーツリーの二重マウント / ポーリング配慮
- 主ポーラーは分岐に依存しない単一の useWorktreeDetailController が
  一度だけ生成する(分岐は表示のみ)。
- 非表示側の表示ツリーは同一 commit 内でペイント前に破棄されるため、
  passive な useEffect ベースの terminal ポーリングは発火しない。
- 主要なコールドロード経路(キャッシュ無し→LoadingIndicator)では
  どちらの分岐の子もマウントされない。

## その他
- SSR での useLayoutEffect 警告は isomorphic パターン
  (server では useEffect)で回避。
- 既存の useIsMobile 単体テスト(初期値・リサイズ切替)は不変で継続。
- hydrateRoot を用いた新規テストで「hydration mismatch 警告が出ない」
  「モバイルビューポートでモバイル値へ解決」「デスクトップは false 維持
  (回帰なし)」を自動検証。

品質ゲート: lint / tsc --noEmit / test:unit / test:integration / build 全て成功。

Refs #1126

* feat(pwa): PWA対応(manifest+Service Worker+インストール可能化) (#1146)

Web App Manifest(manifest.ts)、手書きService Worker、インストール可能化を
実装。「外出先からエージェントを監視する」プロダクト特性に沿ってホーム画面
から独立アプリ(standalone)として起動可能にする。

Service Worker技術選定: 手書きvanilla SW(next-pwa/Serwist/Workbox不採用)。
理由:
- custom server(server.ts)がWebSocket upgradeを保持しており、next-pwa等が
  前提とするデフォルトNextサーバ構成と噛み合わない
- 本Issueが要求するallowlist方式(キャッシュ対象を明示列挙)を直接表現でき、
  監査容易・小規模・後続#1125 Web Pushの拡張が容易

キャッシュ戦略(allowlist):
- /api・/login・/proxy・非GET・クロスオリジン → network-only(キャッシュ禁止)
- /_next/static/・/icons/・manifest/favicon → cache-first
- navigation → offline-fallback(認証コンテンツを避けページ本体は非キャッシュ)
判定はcache-policy.ts(単体テスト済み)を単一情報源とし、public/sw.jsが同定数を
鏡写し、sw-file.test.tsで両者の同期を担保。

更新戦略: install時はskipWaitingせず、更新検知でトースト表示→リロードで
SKIP_WAITINGを送信しcontrollerchangeで新版に切替。

その他:
- 本番のみSW登録(shouldRegisterServiceWorker)、dev/test・非セキュアは無効化
- オフラインフォールバック画面(/offline、トークン準拠、再接続ボタン)
- iOS standalone用にappleWebAppメタ(capable/title/status-bar-style)を付与
- manifest/sw/offlineをAUTH_EXCLUDED_PATHSに追加(auth有効時もinstallable/
  precache可、いずれも非機密リソース)
- HTTPS/LAN配信時のSW/インストール制限をREADMEに注記

Refs #1124

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(mobile): モバイルA11y改善(focus trap・44pxタップターゲット・tablistキーボード) (#1147)

モバイルのアクセシビリティ/エルゴノミクスの穴を塞ぐ。

focus trap は共通フック useFocusTrap に1実装集約:
- 判断理由: Modal/シート類は独自ポータル+enter/exitアニメ(useExitAnimation
  /usePromptAnimation)+スワイプ dismiss を持つ bespoke 実装。@radix-ui/
  react-dialog は未導入で、置換すると3面すべてとそのテストを書き直す必要がある。
  共通フックなら既存機構を壊さず trap のみ追加でき、issue の「1実装に集約」に沿う。
- 挙動: 初期フォーカスをダイアログへ移動 / Tab・Shift+Tab をダイアログ内で循環 /
  閉時に呼び出し元へフォーカス復帰。autoFocus 等で消費側が内部フォーカスを
  管理している場合は初期フォーカス奪取・復帰の両方をスキップ(ConfirmDialog 互換)。
- 適用: ui/Modal(role=dialog/aria-modal/aria-labelledby も付与)・
  MobilePromptSheet・MobileTerminalActionsSheet。

44px タップターゲット(視覚サイズは不変、ヒットエリアのみ拡大):
- エージェントインスタンスタブ(WorktreeDetailRefactored)
- History サブタブ Message/Git(WorktreeDetailMobile)
- MobileTabBar の各タブ

MobileTabBar に ARIA tabs パターン完成:
- 矢印キー(左右/上下)+Home/End 移動、ラップアラウンド
- roving tabindex(activeタブのみ tabindex=0)、選択はフォーカス追従

touch-manipulation をボタン系プリミティブ(ui/Button)とモバイル操作面へ付与し
ダブルタップズームの遅延を抑止。

title のみだった WorktreeCard のお気に入りトグル(アイコンのみ)へ aria-label を
付与し、タッチ/スクリーンリーダーからも到達可能に。

スワイプジェスチャー等は本Issueスコープ外のため変更していない。

Refs #1127

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(mobile): ジェスチャー強化(スワイプタブ切替・pull-to-refresh・キーボード追従) (#1148)

- useSwipeGesture に axis/方向ロック/エッジ開始制限/onSwipeMove を後方互換で追加
  (既存 MarkdownEditor の四方向スワイプと #299 スクロール抑制は不変)
- worktree詳細のモバイルタブを水平スワイプで切替。方向ロックで縦スクロール中は
  無効化、しきい値60px、ターミナルタブはエッジ開始領域に限定して誤発火を抑止。
  MobileTabBar インジケータは activeTab 共有で自動同期
- Sessions/Repositories に pull-to-refresh を新設(usePullToRefresh + PullToRefresh)。
  最上部のみ発火し overscroll-behavior:contain + preventDefault でネイティブPTR二重発火を防止
- MessageInput を useVirtualKeyboard に接続しキーボード追従(keyboardAware opt-in)、
  入力要素へ enterKeyHint/inputMode を付与
- MobilePromptSheet のアドホックスワイプを useSwipeGesture へ統合(focus trap ref を
  マージして #1127 の挙動を維持、finger-follow は onSwipeMove で継続)

Refs #1128

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(pwa): エージェント状態のWeb Push通知(プロンプト待ち/完了) (#1149)

- push_subscriptions テーブル(migration v41)と CRUD モジュールを追加
- VAPID/web-push 送信層: 種別フィルタ・デバウンス・410自動削除、機密(endpoint/秘密鍵)は非ログ
- プロンプト検出/セッション完了時に response-checker からファンアウト(検出ロジックは不変・fire-and-forget)
- 購読登録/解除/…
Kewton added a commit that referenced this pull request Jul 15, 2026
… E2EのCI組込み #1180 / ちらつき解消 #1184) (#1191)

* feat(files): スマホ版でファイルをダウンロード可能に(attachment配信+FileViewer DL導線)(#1024) (#1025)

スマホ版のファイルビューア(FileViewer, Issue #438 でモバイル専用化)から
任意種別のファイルをダウンロードできるようにする。

- B(サーバ): files/[...path] GET に download 分岐を追加し、生バイトを
  Content-Type: application/octet-stream + Content-Disposition: attachment で返却。
  filename は src/lib/http の content-disposition ヘルパーで RFC 準拠にサニタイズ
  (ASCII フォールバック + filename*=UTF-8'' パーセントエンコード、ヘッダ
  インジェクション回避)。既存 getWorktreeAndValidatePath でパス検証を再利用。
  base64 JSON を経由しないためプレビュー上限を超える大容量にも対応
- A(UI): FileViewer ツールバーにダウンロードボタンを追加し、同一オリジンの
  download URL へ遷移(iOS Safari 含めネイティブDLに乗せる)
- テスト新規29本(download API のヘッダ/パス検証/filename、content-disposition
  ヘルパー、FileViewer ボタン)
- C(独立ページのPDF分岐/DLリンク)は範囲外(モバイル動線が独立ページを使わないため)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(session): Timer 発火の送信を History に記録(send ロジックを共有サービス化)(#1028) (#1029)

Timer 機能(Issue #534)から送った指示が History(chat_messages)に残らない
問題を修正。executeTimer が cliTool.sendMessage だけを呼び createMessage/
startPolling をスキップしていたのが原因(#947 の委譲変更が起点)。

- send route L262-385 のインライン記録ロジックを src/lib/session/
  send-user-message.ts の共通関数 sendUserMessage() として抽出
  (savePendingAssistantResponse → send(画像/Copilot分岐含む)→ createMessage
  → updateLastUserMessage/orphan処理 → startPolling)。HTTP 層非依存
- send route POST を sendUserMessage() 利用へリファクタ(挙動不変)
- timer-manager.ts executeTimer を sendUserMessage() 経由に変更。
  timer_messages の status 遷移(sending/sent/failed)は維持
- テスト: sendUserMessage / executeTimer のユニット新規9件 + timer 既存更新(計26件)

これにより Timer 送信も user メッセージと assistant 応答の両方が History に残る。

スコープ外(別Issue推奨):
- tests/integration/api-send-cli-tool.test.ts の1件は base でも失敗する既存不具合
  (2引数アサート vs 3引数呼び出し)で本修正と無関係(CI の test:unit 対象外)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(todo): ブランチ単位ToDoを3状態化(未着手/仕掛/完了)(#1032) (#1033)

- migration v38: worktree_todos に status カラム追加(done=1→'done' backfill、CURRENT_SCHEMA_VERSION 37→38)
- DB層: status を真実源に done := status==='done' を派生(後方互換維持、writer整合)
- API/クライアント: PATCH で status ∈ {todo,doing,done} を検証・適用、done後方互換
- UI: 3状態巡回チップ+色バッジ+状態別件数、TodoPane全体を next-intl 化
- i18n: locales/{en,ja}/worktree.json に todo namespace 追加
- テスト: 既存5更新+新規 migration-v38(backfill検証)

Closes #1032

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(todo): ブランチ単位ToDoに詳細フィールド追加+クリックで件名/詳細を編集 (#1034) (#1035)

- migration v39: worktree_todos に detail カラム追加(TEXT NOT NULL DEFAULT ''、既存行は空詳細で移行)
- DB層: WorktreeTodo に detail 追加、create/update で detail 受理
- API: POST/PATCH で detail を検証・適用(MAX_TODO_DETAIL_LENGTH 新設)
- クライアント: WorktreeTodoItem.detail、create/update の detail 対応
- UI: 項目クリックで編集モーダル(Modal.tsx再利用)を開き件名+詳細を編集、詳細ありインジケータ
- i18n: locales/{en,ja}/worktree.json に詳細/編集の文言追加
- テスト: 既存6更新+新規 migration-v39、docs/module-reference.md 更新

Closes #1034

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(todo): ブランチ単位ToDoの詳細にコピーボタンを追加 (#1036) (#1037)

- UI: 編集モーダルの詳細欄に CopyButton を配置し詳細本文をコピー(詳細が空のときは非表示)
- 既存 src/components/common/CopyButton.tsx / clipboard-utils.ts を再利用(スマホHTTPは execCommand フォールバック)
- i18n: locales/{en,ja}/worktree.json に todo.copyDetail 追加
- テスト: TodoPane.test.tsx にコピー導線テスト3件追加
- migration/DB/API 変更なし(UIのみ)

Closes #1036

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): セマンティックデザイントークン基盤を導入 (#1041) (#1054)

globals.css に :root(light)/.dark(dark) の CSS 変数
(background/foreground/surface/muted/border/input/ring/accent-50..700/
success/warning/danger/info) を定義し、tailwind.config.js の primary(cyan)・
cmd-bg-dark を semantic トークンへ置換。layout.tsx / MainLayout.tsx の
body 背景を bg-background に統一。既存の実効値を写し取り視覚は不変(no-op)。
docs/design-system.md を新設。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): cn()/cva によるバリアント管理基盤とプリミティブ改修 (#1042) (#1055)

clsx + tailwind-merge の cn() ヘルパーと class-variance-authority を導入し、
Button/Card/Badge/Modal を cva ベースへ移行。Button ghost バリアントの
dark: 指定漏れ(ダークモードで視認性劣化)を修正。既存 API は完全互換。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): アイコンを lucide-react に統一 (#1044) (#1056)

絵文字アイコン(🤖⚡✦💻)を廃止し lucide アイコンへ置換。共通レイアウト・ナビ
(Header/GlobalMobileNav/MobileTabBar/ActivityBar/terminal page)および worktree
各所のインライン SVG を lucide に統一。docs/design-system.md にアイコンサイズ規約を追記。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): next/font で Geist Sans/Mono を導入 (#1043) (#1057)

geist パッケージ(セルフホスト)で GeistSans/GeistMono の CSS 変数を <html> に適用し、
tailwind fontFamily の sans/mono を「Geist → 日本語(Hiragino/Noto Sans JP) →
システム」フォールバックで構成。Terminal.tsx(xterm)の等幅も Geist Mono を先頭指定。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): RadixベースUIプリミティブを拡充 (#1046) (#1058)

Input/Textarea/Select/Tabs/Tooltip/DropdownMenu/Switch/Skeleton を追加
(cva + cn, セマンティックトークン着色, ライト/ダーク対応)。Portal 系は
'use client' + Z_INDEX(POPOVER)で Modal と階層整合。Sessions ページの
フィルタ/ソートを新プリミティブへ移行(適用サンプル)。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): アクセント色を統一しセマンティックトークンへ置換 (#1045) (#1059)

cyan/blue 直書き(~600箇所 / 131ファイル)を #1041 のセマンティックトークンへ置換。
用途判定: インタラクティブ=accent / フォーカス=ring / 情報=info。gray 系は
surface/background/border/muted へ実効値を保って置換。例外(Terminal.tsx の
xterm ANSI 配色 / CLI ブランド色 / .scrollbar-thin 等の固定装飾色)は維持。
globals.css の .btn-primary・検索ハイライト等もトークン化。
敵対的レビュー指摘(focus:ring-accent→focus:ring-ring 統一、design-system.md
の例外記述修正)を反映。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): 主要ページを共通UIプリミティブへ移行 (#1047) (#1060)

layout(Header/Sidebar の検索・トグル・言語)/ home / repositories / review /
more / chat / login の生button・手組みフォーム・コピペカードを @/components/ui
プリミティブへ移行。機能・挙動(onClick/フォーム送信/バリデーション/キーボード)は維持。
敵対的レビュー指摘(chat系ボタン移行・ghost の dark hover)を反映。ネイティブ select・
複合 textarea 入力バー・極小アイコントグルは意図的に残置(PR の残置リスト参照)。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): globals.css の @apply クラスを廃止し cva プリミティブを自己完結化 (#1048) (#1062)

.btn/.btn-*/.card/.card-hover/.badge*/.input を globals.css から削除し、
Button/Card/Badge の cva 定義へ実クラスを内挿(旧 @apply 定義と同値、視覚不変)。
src/ 全体で削除クラスへの参照が 0 であることを確認。worktree の一部
(WorktreeDetailRefactored/LogViewer/WorktreeList/ExternalAppForm)をプリミティブへ移行し、
マジックナンバー入りインライン style を定数化。worktree/mobile/sidebar/common の
一括プリミティブ移行は規模の都合により #1061 へ分割。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): サーフェス・奥行きを再設計しモダンダークテーマへ刷新 (#1049) (#1063)

globals.css の :root/.dark トークン値を刷新(深い base + 微リフト surface +
ヘアライン border)。Header/GlobalMobileNav に backdrop-blur(supports-[backdrop-filter]
フォールバック付)。Card に elevated/interactive variant、h1-h4 見出しタイポを調整。
敵対的レビュー指摘を反映: 主要閲覧画面のコンテンツカード(sessions/home/review)を
bg-surface/border-border へ移行し背景反転による溶け込みを解消、ダーク入力を surface-2 で
凹み表現、Card interactive にキーボード focus 対応を追加。コントラストは WCAG AA 全 pass。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): モーション基盤とマイクロインタラクションを追加 (#1050) (#1064)

tailwindcss-animate を導入し、Modal/MobilePromptSheet の enter/exit、Radix data-state
連動(DropdownMenu/Select/Tooltip)、一覧の stagger、hover lift を統一適用。
prefers-reduced-motion 対応。ターミナル/仮想スクロールには非適用。敵対レビュー指摘
(reduced-motionでのtranslate漏れ→motion-safe化、sessionsエラー時のstagger再発火→list
マウント維持、Tooltip instant-open enter、home focus parity)を反映。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): Home をダッシュボード(bento グリッド)化 (#1052) (#1065)

Home を縦積みから bento グリッドへ再構成(統計/直近セッション/ToDo/クイックアクション)。
PC は CSS Grid(grid-cols-12 + col-span)、モバイルは1カラム縦積み(useIsMobile の JS 分岐を
増やさず Tailwind breakpoint)。既存 sessions 取得フック/API を再利用し新規 API なし。既存機能
(ToDo 追加/チェック/コピー、セッションカウント)を維持。空状態対応。敵対的レビュー指摘
(ToDo タイルの h-full・見出しパリティ)を反映。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): コマンドパレット(⌘K)を追加 (#1053) (#1066)

cmdk で ⌘K(mac)/Ctrl+K(win) のコマンドパレットを追加(Navigation / Worktrees 検索 / Actions)。
input/textarea/ターミナル入力中は非発火、SSR 安全('use client')、i18n(en/ja)、Modal 相当の z-index。
モバイルは GlobalMobileNav に起動導線。敵対的レビュー指摘を反映: open 時に入力へフォーカスし
キーボード操作(type/矢印/Enter)を復旧[HIGH]、worktree 一覧は共有キャッシュ context を再利用
(最新化+失敗時 Navigation fallback)、Escape の preventDefault/stopPropagation と close 時の
フォーカス復帰。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): エージェント/セッションステータスの視覚を強化 (#1051) (#1067)

StatusDot(running/waiting/idle/error)を新設。running はグロー+パルス+静的リング halo、
waiting は amber の弱点滅。サイドバー(BranchStatusIndicator)/ Home / Sessions を StatusDot 化。
prefers-reduced-motion でパルス停止(静的リングで running≠ready を維持)、ポーリングで再発火しない
CSS infinite アニメ。敵対的レビュー指摘(reduced-motion での running/ready 識別性、docs の実態訂正)を
反映。worktree 詳細/MobileHeader は従来スピナー表示のまま(docs に明記・今後の追随対象)。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ui): サイドバーがヘッダーに遮蔽される左上レイアウト破綻を修正 (#1070) (#1083)

showGlobalNav 時に固定サイドバーを top-16 h-[calc(100vh-4rem)] にオフセットし、
半透明ヘッダー(z-50, backdrop-blur)がサイドバー先頭64px(Branches 見出し+
view/sort/sync ツールバー行)を遮蔽してクリック不能+灰色の滲みを生む問題を解消。
あわせて border-gray-200/dark:border-gray-600 を border-border にトークン化。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): テーマ既定を system 化しヘッダーにトグルを追加 (#1071) (#1084)

next-themes を defaultTheme="system" enableSystem に変更し、ライト環境ユーザーへの
強制ダークを解消。Header 右クラスタに ThemeToggle を追加(発見可能性向上)。
globals.css に color-scheme(:root=light / .dark=dark)を宣言し、残存ネイティブ
コントロールの OS クローム事故(dark で白いドロップダウン等)を防止。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(hooks): useIsMobile を matchMedia ベースにし混成レイアウトバグを解消 (#1069) (#1085)

window.innerWidth 依存を window.matchMedia(`(max-width: ${breakpoint-1}px)`)に置換。
innerWidth と CSS ビューポートが乖離する環境(実測 innerWidth=846 vs
documentElement.clientWidth=390)では、Tailwind の md:(min-width:768px)と JS 分岐が
食い違い「CSS はモバイル・JS はデスクトップ」の混成レイアウトになっていた。
max-width:breakpoint-1 は md: の正確な補集合。change イベント購読+cleanup、
SSR 初期値 false、MOBILE_BREAKPOINT export と breakpoint オプションは維持。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): Home 第一フォールドを回収(バナー削除・見出し降格・tabular-nums) (#1072) (#1086)

陳腐化した Welcome バナー(#600 のナビ再編告知)を削除。h1「CommandMate」を機能的見出し
(HomeHeading: Overview + ライブサブライン)へ降格しヘッダーのワードマークとの同語反復を解消。
Home の数値・相対時刻・サイドバーのブランチ数バッジに tabular-nums を導入。HomeQuickActions の
inline SVG を lucide へ置換。相対時刻の短縮形は新設 date-utils、見出し文言は home i18n
namespace(en/ja)。機能(ToDo/カウント/遷移)は不変、サブラインは既存キャッシュから算出。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ライトモードのエレベーション階梯を反転(グレー地+白カード+2層影) (#1074) (#1087)

light を「白ページ+凹む灰カード」から Vercel/Linear 型の「グレー地ページ+
白カード+2層影」へ反転し、dark(#1049)と対のエレベーションモデルにする。
surface-2(740) < background(751) < surface(765) の「白カードが浮き、well が沈む」階梯。

- globals.css :root: --background 255→250 250 251 / --surface 248 250 252→255 255 255
  / --surface-2 241 245 249→244 246 250。.dark は不変(回帰なし)
- tailwind.config.js: 2層 boxShadow.sm を新規追加(md/lg は Tailwind 既定のまま)
- design-tokens/surface-depth テスト更新 + WCAG コントラスト検証を追加
  (foreground≥4.5 全面 / muted-fg≥4.5 page,card / ≥3 recessed well)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): サイドバーを --sidebar-* トークン化しテーマ追従に (#1073) (#1088)

bg-gray-800 text-white 固定でテーマ孤島だったサイドバーを、新設の
--sidebar / --sidebar-foreground / --sidebar-border / --sidebar-hover /
--sidebar-muted セマンティックトークンへ移行。light=slate-50系の明るいパネル、
dark=#141821(#1049階梯と整合)でテーマに追従する。standalone なリテラルRGB値
としており、#1074 の --surface 反転がサイドバー色へ滲まない。

- globals.css :root/.dark に --sidebar-* を定義、tailwind.config.js に登録
- Sidebar.tsx / sidebar/*(BranchListItem, SortSelectorBase)/ 検索Input
  / LocaleSwitcher / ThemeToggle の直書きを全てトークンへ置換
- 敵対的レビュー由来のライト回帰修正2件:
  - LogoutButton.tsx: 漏れていた text-gray-300 直書きを ThemeToggle と同じ
    テーマ中立トークン(text-muted-foreground/hover:bg-muted)へ(モバイル共用)
  - SortSelectorBase: 選択中ソートを text-accent-700 dark:text-accent-400 に
    (ライトのslate-50上で 1.73→約5.1:1 でAA達成、dark時は据え置き)
- docs/design-system.md に --sidebar-* を追記、既存テスト/期待値を更新

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ⌘K パレットを Raycast 級に強化(アイコン/StatusDot/kbd/Recents/ヘッダー導線)(#1077) (#1089)

- src/components/ui/Kbd.tsx を新設(semantic-token ベース、#1080 で再利用)し ui/index.ts に export 追加
- NAV 行に lucide アイコン、worktree 行に GitBranch + StatusDot(isProcessing/isWaitingForResponse/isSessionRunning から算出)
- バックドロップ/パネル/選択状態/入力/フッター文字色を semantic トークン化(bg-surface/backdrop-blur/ring-border/accent-500)
- キーボードガイドのフッター(↑↓/↵/esc、i18n)
- Recent グループ(localStorage cm.palette.recents、MRU<=8、空クエリ時のみ、dead worktree は無視)
- worktree を isSessionRunning 優先→updatedAt 降順ソート+空クエリ時 slice(0,8)
- Actions 拡充: Sync repositories(repositoryApi.sync + Toast)/ 言語切替 / Open GitHub
- Header に ⌘K 検索ピル(mount 後に platform 判定して ⌘K/Ctrl K を出し分け)
- #1053 の挙動契約(フォーカス/入力ガード/Escape/backdrop/data-testid)を維持
- 文言を locales/{en,ja}/commandPalette.json に追加、単体テスト更新

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ターミナルクロームを刷新(全周枠廃止・カード化・CLIセレクタDropdownMenu化)(#1079) (#1090)

worktree ターミナルの全周枠を廃し surface-2 カード+ヘアラインのカード化に統一。
CLI インスタンスセレクタを native <select> から Radix DropdownMenu(StatusDot+alias
+ChevronDown)へ置換し、value/onChange の単一選択セマンティクスと testid を保持。

- Terminal / ActivityBar / TerminalDisplay / TerminalSplitContainer /
  TerminalSplitPane / TerminalSplitPaneContent を surface/border トークンへ
- TerminalSplitPane: <select> → DropdownMenu(RadioGroup)。cli-selector testid 保持
- xterm の theme は意図的に固定ダーク(--surface-2 dark 相当)を維持
- StatusDot/status-colors.ts は不変(#1078 の領域)。locales worktree.json 追記
- 既存 terminal テスト(cli-tab-switching 含む)更新・全パス

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): worktree詳細/Chat のダーク直書き島を surface トークン化しテーマ追従に (#1075) (#1091)

HistoryPane / ConversationPairCard / AssistantMessageList / AssistantChatPanel /
HistorySearchBar / CopyButton と .assistant-md の常時ダーク直書きを surface/foreground
セマンティックトークンへ移行し、light/dark 両テーマで正しく描画されるようにする。

- globals.css .assistant-md: リンクを accent-700 dark:accent-400、code/pre を明色地に
  し両テーマ AA≥4.5:1。:root/.dark のトークン定義は不変
- 各コンテナ+全子孫を surface/surface-2/foreground/muted-foreground/border トークンへ
  (hidden-children 回避: 子の直書き light-on-dark を残さない)
- AssistantChatPanel/HistoryPane の native select/checkbox は色のみトークン化
  (Radix 化は #1076 の担当のため据え置き)
- ConversationPairCard: コピー/挿入/展開ツールバーの hover-reveal 化に伴い、
  タッチ端末で不可視化する回帰を [@media(hover:none)]:opacity-100 で解消
  (デスクトップの hover-reveal は維持。copy-user/assistant-message testid 保持)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): データ画面コントロールの意味論を整理(Review/Repositories/More)(#1081) (#1092)

表示のみの調整。絞り込み・編集・削除・可視性トグルのロジックは不変。

- Review: フィルタを CTA 風の primary/secondary ボタンから、件数付きの
  控えめなセグメンテッドコントロールへ(選択は bg-accent-500/15、CTA と区別)。
  件数は既存のフィルタ済み配列長から算出(新規 fetch なし)。
- Repositories: Enabled/Disabled バッジと可視性トグルの緑ピルを StatusDot 流の
  dot+muted テキストへ(トグルは操作可能なまま)。(none)→ muted の em-dash、
  パスは truncate+font-mono+full-path tooltip、Edit は ghost な Pencil アイコンボタン。
- More: ExternalAppsManager 側の重複 h2「External Apps」を削除(ページ側に一本化)。
- StatusDot/status-colors.ts は不変(#1078 の領域)。生 gray/cyan/blue の追加なし。
- 既存テスト更新+件数バッジ/単一見出しの検証テストを追加。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): コンポーザーをカード化しモバイルworktreeコントロールを整理 (#1080) (#1093)

- MessageInput をコンポーザーカード化(surface/border/shadow/focus-ring)し、送信
  ボタンを入力有無で filled(bg-accent-600)/ghost に切替。プレースホルダを i18n 短縮
- Auto-Yes をコンポーザー下辺のメタ行へ統合(ui/Switch 化)。右側に Kbd キーヒント
  (#1077 の ui/Kbd 再利用)。InterruptButton のオレンジ直値を text-danger ghost へ
- モバイル sticky 行をインスタンスタブ専用化(dark: バリアント+下線インジケータ)。
  検索/End を MobileTerminalActionsSheet(ボトムシート、End は確認付き)へ移設
- MobileTabBar を GlobalMobileNav 様式(bg-background/backdrop-blur/border-border、
  上辺インジケータ、Wrench アイコン、success/warning バッジ)へ統一
- テスト: 送信ボタン状態変化/新規シートを追加、ResizeObserver polyfill(Radix Switch)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): 稼働ステータスを StatusDot 一本化し idle ノイズを縮退 (#1078) (#1094)

青スピナー(border-info)と生 span を全て統一 StatusDot に置換し、稼働ステータスの
視覚言語を1系統に。idle ノイズを縮退(Sessions は running/waiting のみラベル付き、
idle は「+N idle」カウンタ / worktree ヘッダーは idle を StatusDot のみの円形ボタン
+Tooltip、幅超過は「+N」DropdownMenu オーバーフロー)。

- WorktreeDetailSubComponents / sessions/page / MobileHeader を StatusDot へ統一
- 新規 lib/agent-status-display.ts に classifyHeaderInstances 等の導出を集約
- status-colors.ts の type:'spinner'/border-info は @deprecated 化(ReviewTab が
  最後の消費者。消滅後に削除)
- 相対時刻 tabular-nums / プレビューは CSS truncate、DnD(handleAgentDragStart)と
  タブ切替 onClick は全ボタンで温存
- 敵対レビュー由来の仕上げ: 「+N」オーバーフローに稼働中(running/waiting)があれば
  集約 StatusDot グローを出し、稼働セッションが glow-less に埋もれないように。
  同 trigger の生グレーをセマンティックトークンへ

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ネイティブ checkbox/radio を Radix プリミティブに統一 (#1076) (#1095)

OS 標準の白いフォームコントロールが dark に浮く問題を解消。@radix-ui/react-checkbox
/ react-radio-group ベースの ui/Checkbox・ui/RadioGroup を新設し、残存していた
ネイティブ input(checkbox 12箇所/11ファイル・radio 6箇所/4ファイル)を全て移行。

- 新規 src/components/ui/{Checkbox,RadioGroup}.tsx(cn()+cva・セマンティックトークン・
  focus-visible:ring-ring)、ui/index.ts に export、依存2つを追加
- checked/onCheckedChange・value/onValueChange への適応、name/value/disabled/
  aria/data-testid とラベルクリックのトグルを全移行で保持
- 移行後 native type="checkbox"/"radio" は src/ 配下 0 件(完了性 grep 検証済)
- 敵対レビュー(binding/primitive-a11y/GitDangerZonePanel 密集)0件

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): トークン規律の確立(生gray/slate置換・focus-visible統一・CIガード)(#1082) (#1096)

移行済み領域の生 gray/slate 直書きをセマンティックトークンへ置換し、フォーカス表現を
focus-visible に統一、CI grep ガードでトークン違反の再流入を止める。機能・挙動は不変
(見た目/規律のみ)。

## プリミティブ是正
- Button: secondary/ghost を muted/foreground、danger を bg-danger トークンへ。
  `focus:` → `focus-visible:` + `ring-offset-background`(dark の白ハロー解消。Tabs/Switch が手本)。
- Input/Textarea/Select トリガー: `focus:` → `focus-visible:`(枠が反応するため offset なし)。
- Badge gray → bg-muted/text-muted-foreground、Modal → surface/border/foreground。

## in-scope スイープ(生 gray/slate → トークン)
- app(worktrees を除く)/ ui / layout / home / review / repository / common。
- gray-100/200→muted、gray-400/500/600→muted-foreground、gray-700/800/900→foreground、
  境界→border、フォーム枠→input。

## globals.css / metadata / docs
- body→text-foreground、.prose インラインコード→muted/foreground、scrollbar-thin をトークン化
  (--input / --muted-foreground)、`::selection` にアクセント淡色を追加。
- layout.tsx: title template(`%s | CommandMate`)+ viewport themeColor(light/dark)。
- docs/design-system.md にフォーカス表現・CI ガード・例外リストを同期。

## CI ガード
- `.github/workflows/ci-pr.yml` に `token-discipline` ジョブを追加(CLAUDE.md size check と同方式)。
  ホワイトリスト方式で移行済みディレクトリ限定 hard-fail。除外リストをスクリプト内に明記。
  違反注入時に fail することを確認済み。

## 判断メモ(要レビュー)
- `src/app/worktrees/**`(40 occ)はオーケストレーター集計では app(108) に含まれるが、
  ワークツリー領域(#1061・「触らない」)かつ常時ダークのターミナルページ/CLI ブランド色
  (bash=bg-gray-600)を含むため、スイープ・ガード双方から除外した。#1061 完了時に拡大。
- 軽微な見た目変化: danger の red-600→red-500(--danger)、secondary/ghost の muted シェード、
  SidebarToggle はテーマ追従化、ツールチップは反転サーフェス(bg-foreground/text-background)へ
  テーマ追従化、Home の Save&Resend ボタンを accent-600 + 白文字に正規化。

品質ゲート(lint / tsc --noEmit / test:unit / build)+ 新 CI ガード 全パス。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree/共通の生gray/buttonをトークン・プリミティブへ (#1061 群A 1/4) (#1097)

#1061 のファイル分割並列(群A: 21ファイル)。生 gray/slate Tailwind クラスを
#1040/#1082 のセマンティックトークンへ用途判定で置換、生 <button> を ui/Button へ。
挙動・ハンドラ・testid は不変。群A担当ファイルの raw gray = 0。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree詳細/markdown/fileの生gray/buttonをトークンへ (#1061 群B 2/4) (#1100)

#1061 分割並列(群B: 21ファイル)。WorktreeDetailSubComponents/MarkdownToolbar/
FileTreeView 等の生 gray/slate → セマンティックトークン、生 button → ui/Button。
ターミナル出力表示面(TerminalSplitPaneContent)はダーク維持のため意図的に非トークン化。
挙動・testid 不変。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree/mobile/gitの生gray/buttonをトークンへ (#1061 群C 3/4) (#1098)

#1061 のファイル分割並列(群C: 22ファイル)。生 gray/slate → セマンティックトークン、
生 <button> → ui/Button。挙動・testid 不変。群C担当ファイルの raw gray = 0。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree/git/todoの生gray/buttonをトークンへ (#1061 群D 4/4) (#1099)

#1061 分割並列(群D: 21ファイル)。生 gray/slate → セマンティックトークン、生 button
→ ui/Button。ターミナル出力表示面(TerminalSplitContainer/TerminalContainer)は
xterm と同様ダーク維持のため意図的に非トークン化(除外)。挙動・testid 不変。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci(guard): token-discipline ガードを worktree/mobile/external-apps へ拡大 (#1061) (#1101)

#1061 で worktree/mobile/external-apps の生 gray/slate をトークン化完了したため、
#1082 で導入した token-discipline CIガードの対象ディレクトリを拡大。
ターミナル出力表示面(*Terminal* ソース)は xterm 固定ダーク(#1079)と同様の
意図的ダーク島のため除外。app/worktrees も引き続き除外。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: integration スイートの drift を解消(モック/期待値追随・CI追加)(#1102) (#1103)

CI 非対象で長期 drift していた integration スイート(41 failed)を修復。実装バグを
隠さないよう vi.mock を importOriginal 方式へ書き換え、欠落 export を補完。

- 不完全 vi.mock を importOriginal ベースへ(欠落 export を実挙動ごと通す)
- config ドリフト(アップロード上限 5MB→20MB 等)の期待値を現行定数準拠へ
- undefined.length 系はモック戻り値を実 shape へ
- 再 drift 防止に ci-pr.yml へ Integration Tests ジョブ追加
- external-apps duplicate name → 500(期待409) は実装バグのため it.skip で quarantine

結果: test:integration 643 passed / 1 skipped(0 failed)。unit/lint/tsc 緑維持。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(external-apps): 重複nameで500ではなく409を返す (#1104) (#1105)

* fix(external-apps): 重複nameで500ではなく409を返す (#1104)

route.ts の catch を message.includes('UNIQUE constraint') 文字列判定から
ExternalAppDbError.code === 'DUPLICATE' の型/コード判定へ変更。DB層のエラー
ラップ後もメッセージに依存せず 409 を正しく返す。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(external-apps): 409 duplicate name テストを un-skip(#1104 で修正済み)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(timer): Timer履歴の詳細モーダル表示と失敗理由の記録 (#1107) (#1109)

Schedule 側の execution_logs 方式を Timer へ横展開。

- DB: migration v40 で timer_messages に error 列(nullable)追加、schema 39→40
- timer-db: error 列の read/write 貫通、updateTimerStatus に optional error 引数、recoverStuckSendingTimers に固定リカバリ理由
- timer-manager: 送信失敗(stage/error)・例外(error.message)を error 列へ永続化(no_session は保存せず)
- API: GET timers レスポンスに error を追加
- UI: TimerDetailModal を新設し履歴行クリックで指示全文+メタ+失敗理由を表示(PC=Modal / mobile=FullScreenModal)
- i18n: schedule.json の timer ブロックに en/ja lockstep でラベル追加

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(files): Filesツリー展開状態のworktree単位永続化と表示リセット (#1108) (#1111)

useFileTabs / useFilePanelState の worktree 単位 localStorage パターンを踏襲。

- 新規 useFileTreeExpandedState: 展開集合を worktree 単位 localStorage(キー commandmate:file-tree-expanded:<worktreeId>) に永続化。lazy-init 復元をマウント再フェッチに乗せ、空集合はキー削除、worktree 間の漏洩なし
- useFileTabs: CLOSE_ALL / closeAllTabs() を追加
- FileTreeView: フック統合+ツールバーに「表示をリセット」ボタン(更新ボタンの隣・常時可視)+ onResetView prop
- Controller/Desktop/Mobile/Refactored: resetFileTreeView(検索クリア・全タブ閉じ・モバイルビューア閉)を配線し、初期表示に戻すフルリセットを実現

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(mobile): viewportFit:'cover' を追加しiOS safe-area対応を有効化 (#1132)

- src/app/layout.tsx の viewport エクスポートに viewportFit: 'cover' を追加。
  iOS は viewport-fit=cover 指定時のみ env(safe-area-inset-*) に非0値を
  入れるため、既存の pt-safe/pb-safe 等の safe-area 対応が実効化される
- GlobalMobileNav の未定義クラス safe-area-bottom を globals.css 定義済みの
  pb-safe に修正(cover 有効化後もパディングが効かない潜在バグ)
- viewport エクスポート値(viewportFit / themeColor)のユニットテストを追加

レンダリング検証: 本番ビルドで <meta name="viewport"> に
viewport-fit=cover が含まれることを確認済み

Refs #1131

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat(ui): ステータスカラーtintトークン導入とダーク非対応フィードバックUIの移行 (#1133)

- globals.css / tailwind.config.js に success/warning/danger/info × subtle/border/foreground の12トークンを両テーマで追加(ライト *-50 面 + *-800 前景、ダーク *-950 面 + *-300 前景、全て WCAG AA 準拠。根拠はコメントに記載)
- Toast / DefaultErrorFallback / History・Prompt・ConnectionErrorFallback / ErrorDisplay / PromptPanel を新トークンへ移行し、生パレット + dark: ペアを撤去(TerminalErrorFallback は常時ダーク島のため現状維持)
- tint 面内のソリッドアクションボタンは反転 tint(bg-{status}-foreground text-{status}-subtle)で両テーマ AA を確保
- docs/design-system.md にトークン表・使用ルールを追記
- tint トークン定義・移行 grep を検証する status-tint-tokens.test.ts と ErrorBoundary/フォールバックのレンダリングテストを追加、既存 Toast / design-tokens テストを追随

Refs #1112

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* refactor(ui): window.confirmを共通ConfirmDialogへ全面置換 (#1134)

- ConfirmDialog/ConfirmProvider/useConfirm を src/components/ui/ConfirmDialog.tsx に新設
  (variant default/danger、Escape/背景クリックでキャンセル、danger時はcancelに初期フォーカス、
  クローズ後に呼び出し元へフォーカス復帰)
- AppProviders に ConfirmProvider をマウント
- window.confirm 残存11箇所 + bare confirm 1箇所(WorktreeCard)を useConfirm へ置換
- confirm文言を next-intl 辞書へ移動 (review namespace 新設、home/worktree/common に追記)
- ESLint no-restricted-globals(confirm/alert) と no-restricted-properties(window.confirm) を追加
  (bare alert 3箇所は window.alert へ変更、alert置換自体は対象外)
- 既存テストを window.confirm モックからダイアログ操作へ追随、
  ConfirmDialog 単体テストと Timer履歴クリアの結合フローを追加

Refs #1113

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* chore(ui): UI死コード削除(WorktreeList・放棄infinite-scroll hooks・hasNewOutput配線) (#1135)

- WorktreeList.tsx(production importゼロの孤児コンポーネント)とbarrel export(index.ts)を削除
- 放棄された無限スクロール実装(useInfiniteMessages/useScrollObserver/useScrollRestoration)と
  対応テスト3本、孤児化するtypes/infinite-messages.tsを削除
- WorktreeDetailRefactored.tsxのhasNewOutput={false}死配線を撤去(MobileTabBar側は維持)
- 残置ファイルのstaleコメント修正(useVisibilityRecovery.ts/history-display-config.ts)
- useWebSocket.tsに@deprecated注記を付与(WebSocketストリーミング化で再設計・再利用予定)
- docs/architecture.md(ja/en)の削除対象ファイルへのstale参照行を削除

受入基準: 3グレップ全て0件成立、lint/tsc/test:unit(8746 pass)/build全パス

Refs #1115

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* fix(ui): 未定義animate-fade-*の定義とModal/Toast/ContextMenuのexitアニメーション実装 (#1114) (#1136)

* fix(ui): 未定義animate-fade-*の定義とModal/Toast/ContextMenuのexitアニメーション実装

- tailwind.config.jsにfade-in/fade-outキーフレームとanimate-fade-in/outを定義。
  usePromptAnimationが発行していたが未定義でno-opだった。クラス名を
  PromptPanel等が文字列比較で消費しているため、tailwindcss-animateへの
  書き換えではなくキーフレーム定義の追加を採用。duration/easingは
  --motion-duration-base / --motion-ease-out(#1050モーショントークン)準拠
- content globsにsrc/hooksを追加(フック発行クラスのpurge防止)
- useExitAnimation(open, duration)フックを新設: 閉要求後もduration msだけ
  マウントを維持しexitアニメーション再生を保証。render-phase同期により
  開閉ともに1フレームの遅延なし。再open時はタイマー解除
- Modal: data-state=closedでfade+zoom-out exit(200ms)後にアンマウント。
  exit中はbackdropクリック無効化
- Toast: fade+slide-out exit(200ms)再生後にonClose通知(アンマウント遅延)
- ContextMenu: fade+zoom-out exit(100ms、enterと同duration)。
  exit中はpointer-events-none
- EXIT_ANIMATION_DURATION_MS / CONTEXT_MENU_EXIT_DURATION_MSを
  ui-feedback-config.tsに追加しCSSとJSタイマーを同期
- reduced-motionはglobals.cssのグローバルキルスイッチで対応(CSS実装のみのため追加ガード不要)
- テスト: useExitAnimation単体、Modal/Toast/ContextMenuのexit挙動
  (閉操作後にアニメーション時間経過までDOM残留→アンマウント)、
  Tailwind実コンパイルによるCSS生成検証を追加

Refs #1114

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ui): ConfirmDialogをModal exitアニメーションに追随

- ConfirmDialog.test.tsx: 閉操作後の即時DOM除去期待を、200ms exit
  アニメーション完了後のアンマウント(waitForElementToBeRemoved)に更新
- ConfirmProvider: pendingクリア後のexit再生中にdescription等が空文字に
  なり内容がブランク表示される統合ドリフトを修正(直近optionsを保持し
  フェードアウト中も表示を維持)。cancelテストにブランク防止の検証を追加

Refs #1114

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat(ui): ヘッダーナビのアクティブインジケータ実装と角丸スケール規約の導入 (#1137)

- Header ナビに CSS-only の下線インジケータを実装(after 疑似要素の
  scale-x トランジション、--motion-ease-out / duration-200 準拠、
  motion-safe ガード)。アクティブ項目に aria-current="page" を付与
- Header のアクティブ状態レンダリングテストを追加(全 6 ルート)
- docs/design-system.md に角丸スケール規約を独立セクションとして追記
  (コントロール=rounded-md / コンテナ=rounded-lg / ポップアップ=rounded-md /
  チップ=rounded-full / 小型インライン=rounded-sm、裸 rounded 原則禁止)

角丸統一の実施範囲(明確な逸脱のみ、feature 配下の一括置換はしない):
- ui/Kbd・ui/Checkbox: 裸 rounded → rounded-sm
- layout/Sidebar のアイコンボタン3箇所: 裸 rounded → rounded-md
- layout/Header のコマンドパレットトリガー: rounded-lg → rounded-md

Refs #1119

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* refactor(ui): チャット履歴UIをConversationPairCard系デザインへ統一 (#1138)

MessageList/PromptMessageの視覚言語をConversationPairCardに統一
(案B: 構造・optimistic updateロジック維持、見た目のみ刷新)。

- 生chromatic色21箇所をステータスtintトークン(warning/success/info x
  subtle/border/foreground)へ移行し、生色のdark:ペアを撤去
- インラインSVGをlucide-reactへ統一(Check/X/CircleCheck/Clock/Loader2/
  MessageCircle)
- ユーザー/アシスタントメッセージをCPC言語のセクション(accent左ボーダー/
  surface-2 wash)+ hover-revealコピーツールバーへ刷新
  ([@media(hover:none)]:opacity-100 タッチフォールバック付き)
- markdown描画をprose→assistant-md(トークン追従)へ移行、ANSI/コード
  ブロックの常時ダーク島(#0d1117)は現状維持
- ファイルパスリンク/プロンプト応答(Yes/No・選択肢・テキスト入力)/
  ログリンクの機能パリティ維持、レンダリングテストを新規追加

Refs #1117

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat(ui): スケルトンローディング展開とSpinner統一・loading.tsx整備 (#1139)

- ui/Spinner.tsx 新設: 手書きインラインSVG/border系スピナー43箇所を共通プリミティブへ置換
- 主要画面にレイアウト整合スケルトンを実装(Home Session Overview/ToDo、Sessions一覧、Repositories一覧、HistoryPane(汎用Card型・loading分岐のみ)、ファイルツリー、AssistantChatPanel、Reviewタブ群)
- 裸テキストローディングをスケルトンへ置換(MessageList.tsxは#1117衝突回避のため対象外)
- 初回ロードのみスケルトン表示(再fetch時は既存コンテンツ維持の非ブロッキングパターン踏襲)
- 主要7ルートに loading.tsx(薄いRouteLoadingフォールバック)を追加
- AssistantChatPanelのローディングを導出状態化し空状態フラッシュを解消

Refs #1118

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* refactor(ui): 生chromatic色をtintトークンへ全面移行しCIガードを拡大 (#1140)

意味ベースでステータスtintトークンへ移行 (red→danger / green→success /
yellow・amber・orange→warning / blue・sky・purple・violet→info)。
淡色面は bg-{s}-subtle + border-{s}-border + text-{s}-foreground、
ソリッドは bg-{s} を使用し、chromatic用途の dark: ペアを撤去。

- ui/Badge.tsx・config/status-colors.ts をトークン化 (生パレットクラス撤去)
- error/ の常時ダーク島 TerminalErrorFallback を *Terminal* 名の別ファイルへ分離し、
  残る error/・auth/ の生gray/chromatic漏れを回収
- 検索ハイライト/ファイル種別アイコン等の装飾色もトークン化 (text-pink等スコープ外色は維持)
- CIガード token-discipline の grep パターンに chromatic 9色を追加し error/・auth/ を対象化。
  既存の *Terminal*・テスト・常時ダーク島 (worktrees/[id]/terminal 等) の除外は維持
- docs/design-system.md に chromatic 禁止/例外リストとCIガード仕様を追記
- 影響する class 検証テストを追随
  (Badge / ContextMenu / MarkdownEditor / MemoCard / dark-mode-foundation / recursive-delete)

chromatic grep 残数
(git grep -cE '(bg|text|border|ring)-(red|green|yellow|amber|orange|purple|violet|sky|blue)-[0-9]' -- src/):
  before: 254 行 (71 ファイル)
  after:  19 行 (サンクション済み例外のみ = *Terminal* 群 + src/app/worktrees/[id]/terminal/)
  in-scope (例外除く): 0

品質ゲート: npm run lint / npx tsc --noEmit / npm run test:unit (8818 passed) / npm run build 全パス

Refs #1116

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): View Transitions APIによるページ遷移クロスフェードを導入 (#1141)

主要ルート間(Home/Chat/Sessions/Repos/Review/More/worktree詳細)のコンテンツ
領域をクロスフェードするプログレッシブエンハンスメントを追加する。

実装方式は「自前の薄いラッパ(案B)」を採用し、next-view-transitions等の外部
依存は追加しない。理由:
- 受入基準が機能検出ガード/reduced-motion無効化の単体テストを要求しており、ガード
  関数を自前で保持することで直接テスト可能になる(ライブラリ委譲では不可)
- framer-motion不採用方針(docs/design-system.md #1050)と同様にCSS駆動とし、JSは
  startViewTransition呼び出しのガードのみに限定してバンドルを軽量に保つ
- Next15/React19ネイティブView Transitionsへの載せ替えが容易(抽象が薄い)
- ライブラリ導入時のLink全面置換を避け、変更をnavシェルとプログラム遷移に限定

主な構成:
- src/lib/view-transitions: supportsViewTransitions/prefersReducedMotion/
  startViewTransition。非対応・reduced-motion時は即時遷移フォールバック
- ViewTransitionsProvider: startViewTransitionをApp Routerナビゲーションへ橋渡し。
  usePathnameのコミット監視で新DOM確定後にresolveし、同一ルート等の非コミット時は
  COMMIT_SAFETY_TIMEOUT_MS=500で確実にsettle
- TransitionLink / useViewTransitionRouter: Header・GlobalMobileNav・Sidebarで使用。
  修飾クリック/別タブ/外部/同一ルートはブラウザ既定にフォールスルー
- globals.css: [data-view-transition="content"](AppShellの<main>のみ)をクロス
  フェードし、永続シェル(ヘッダー/サイドバー/ボトムナビ)はrootを固定して除外。
  ::view-transition背景を--backgroundに設定しダークテーマの白フラッシュを防止。
  200ms(--motion-duration-base)/ --motion-ease-out準拠、reduced-motionで無効化

Refs #1122

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* perf(ui): HistoryPaneを@tanstack/react-virtualで仮想化し250件上限を撤廃 (#1123) (#1142)

* perf(ui): HistoryPaneを@tanstack/react-virtualで仮想化し250件上限を撤廃

履歴リスト(HistoryPane)を仮想化し、可視領域+オーバースキャンのみを
マウントするよう変更。従来はカードをフラット描画し、250件上限は性能緩和策
だった。仮想化により描画コストが O(可視行) となり、上限を1000へ緩和した。

主な変更:
- FilePanelContent の流儀に揃え useVirtualizer + measureElement(可変高)で
  ConversationPairCard を仮想描画。展開/折りたたみ・コードブロック等の高さ
  変化は measureElement が自動再計測。
- スクロール位置維持: 追記は react-virtual が自動維持。最下部閲覧中
  (isNearBottom)のみ新規ペアを追従(follow/maintain を分離)。
- 検索ジャンプ整合: 対象行を scrollToIndex で materialize してからハイライト
  適用。マウント窓の変化(renderedRange)でハイライトを再適用。
- カードのアンマウントで展開状態が飛ばないよう、展開状態はリスト側/親
  (useConversationHistory + 検索auto-expand)で保持(#1121 の pending 挿入も
  この item 管理の上に載せやすい構成)。
- 250件上限を緩和: HISTORY_DISPLAY_LIMIT_OPTIONS に 500/1000 を追加
  (MAX_MESSAGES_LIMIT=1000 に自動追従、APIの取得上限も1000へ)。
- src/lib/history-virtualization.ts: overscan/estimate/しきい値の定数と純粋な
  isNearBottom を分離しユニットテスト可能に。
- テスト: jsdom は無レイアウトのため offsetHeight を与える helper
  (tests/helpers/virtual-layout.ts)と1000件fixtureを追加。仮想化のDOM件数・
  追従/維持判定・展開折りたたみ・検索名前空間を網羅。

1000件fixture before/after(jsdom初期描画。体感FPSは手動検証):
- マウントカード数: 1000 → 16
- DOMノード数: 26,001 → 449(約58分の1)
- 初期描画時間: 289ms → 24ms(約12倍高速)

品質ゲート: lint / tsc --noEmit / test:unit(8855 passing) / build 全パス。

Refs #1123

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ui): HistoryPane仮想化のゼロ計測フォールバックで本文が消える問題を修正

WorktreeDetail経由の統合テスト(jsdomはレイアウト高さ0)で virtualizer が
アイテムを0件しか materialize せず、メッセージ本文が描画されず getByText が
失敗していた(tests/integration/worktree-detail-integration.test.tsx の
'renders user and assistant messages')。

修正: virtualizer がビューポート未計測でアイテム0件のとき(初回レンダー/SSR/
jsdom 等のレイアウト0環境)、先頭 HISTORY_FALLBACK_RENDER_COUNT 件を通常フロー
で描画するフォールバックを HistoryPane に追加。実ブラウザでは layout-effect の
計測直後に仮想化リストへ切り替わる(フォールバック描画は塗り前の一時コミット)。
本番の仮想化保証を損なわないよう件数は小さく固定。SSRのハイドレーション整合
(server/client双方が同じ先頭スライスを描画)にも寄与する。

単体テスト側は既存の virtual-layout ヘルパーで実 virtualizer 経路を検証し続ける
ため、この修正はそれらに影響しない。

品質ゲート: lint / tsc --noEmit / test:unit(8855) / test:integration(648) /
build 全パス。

Refs #1123

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(realtime): ターミナル出力・セッションステータスをWebSocketストリーミング化 (#1120) (#1143)

* feat(realtime): ターミナル出力・セッションステータスをWebSocketストリーミング化

実装済みだが未接続だったWebSocketインフラをクライアントへ接続し、ポーリングを
フォールバックとして残しつつサーバ→クライアントの一方向pushへ移行する。

- useWebSocket再設計: 単一WS接続・指数バックオフ再接続(1s..30s)・visibility連携・
  room購読再送・エンベロープparse。RealtimeProvider/useRealtimeで共有1本化+
  listener fan-out+購読refカウント(Provider未装着時はpollingフォールバック)
- セッションステータスpush: send時にrunning遷移をbroadcast、kill時のstopと合わせ
  サイドバー状態ドットを即時反映。WS接続中はuseWorktreesCacheのポーリングを間引き
- ターミナルストリーミング: response-poller tickからterminal_snapshotをpush
  (buildCurrentOutputでpull/push一致、購読者0でtmux captureをスキップ)。
  WS接続中はuseTerminalPanePollingを15sフォールバックへ間引き、version guardで
  stale破棄(既存stale-response/visibility堅牢性を維持)
- 差分適用: computeTerminalUpdateで追記/リセットを判定しTerminalDisplayを
  keyed chunk追記化、全置換を廃止してテキスト選択を維持(ANSI境界退避付き)
- モバイル新着出力バッジをpush駆動で復活、接続状態インジケータを追加
- 認証: Cookie自動付与でWS upgrade、認証失敗時は既存機構が接続拒否

テスト: useWebSocket(購読/再接続/バックオフ)・差分適用(追記/リセット/ANSI境界)・
WS切断→ポーリング復帰→WS再接続の統合テスト・認証拒否のE2E統合テストを追加。
lint/tsc/test:unit/test:integration/build 全パス。

Refs #1120

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(realtime): useRealtimeConnection等のNULバイト混入を除去

useRealtimeConnection.tsx の room購読キー生成で空白(0x20)が2箇所NULバイト
(0x00)に化けており、gitがバイナリ扱いしdiff/レビュー不能になっていた。
本来の ' ' 区切り(useWorktreesCache と同一パターン)へ復元し、ロジックは不変。

- src/hooks/useRealtimeConnection.tsx: `.join('\0')`/`.split('\0')` → `.join(' ')`/`.split(' ')`
- src/components/worktree/FileTreeView.tsx: 既存のセンチネル `'\0unavailable'` から
  NULを除去し `'unavailable'`(JSON.stringify結果と衝突しない性質は維持)

src/配下のNULバイト混入は0件を確認。lint/tsc/test:unit/test:integration/build 全パス。

Refs #1120

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(chat): メッセージ送信のOptimistic UIを実装 (#1144)

送信瞬間に pending 吹き出しを履歴末尾へ即時挿入し、サーバ echo で実データへ
照合置換(重複表示なし)、失敗時はエラー表示+再試行/破棄する Optimistic UI を
追加。

- usePendingMessages 層: serverMessages に pending をマージし ChatMessage
  (optimisticState 付き)として表示側へ渡す。content 一致+作成時 baseline 外+
  consumedServerIds の一意消費で照合(連投 1:1/既存同一文言の誤照合回避)。
  API reject/timeout(30s) で error→retry/discard。
- ConversationPairCard/HistoryPane: sending(opacity低下+スピナー)と
  error(danger トークン+Retry/Discard)を描画。pending 中は
  「Waiting for response…」を抑止。
- MessageInput: onOptimisticSend 提供時は送信を委譲し composer を即時クリア
  (draft も除去)。他サーフェスは従来の await→clear を維持。
- TerminalSplitPaneContent: 各 split の useSplitMessages に usePendingMessages を
  重ね、送信→即時表示→onSent refetch で照合。破棄時は既存 insert 経路で draft 復元。
- 仮想化リストと整合: pending も pair 化され virtualizer item として最下部追従。

テスト: usePendingMessages(挿入/照合/失敗/連投/タイムアウト/retry/discard)、
ConversationPairCard(sending/error/retry/discard)。

Refs #1121

* fix(mobile): useIsMobileを描画前レイアウトエフェクトで解決し初回デスクトップ描画フリップを解消 (#1145)

## 事象
useIsMobile がSSR安全のため初期値 false で始まり、実際の検出を
passive な useEffect(ペイント後実行)で行っていたため、モバイル実機の
ハードリロードで毎回デスクトップUIが一瞬描画されてからモバイルUIへ
フリップしていた(AppShell の固定サイドバー / WorktreeDetail の
デスクトップ⇔モバイル分岐でのフラッシュ・CLS)。

## 対策の選定(案A/B/C のトレードオフ)
- 案A(CSSで両ツリーを出し分け): 初回フレームは正しいが、重い
  WorktreeDetail が二重マウントし、両分岐の terminal ペインが同時に
  ポーリングを開始する。ヘビーツリーには不採用。
- 案B(UAヒント+cookie): ブレークポイントは viewport 幅基準
  (max-width:767px)で UA の mobile とは一致せず(狭幅デスクトップ/
  タブレット)、真の hydration mismatch を再導入する。不採用。
- 案C(採用・描画前レイアウトエフェクト版): SSR/初回クライアント
  レンダリングは決定的に false(デスクトップ)を返し SSR HTML と一致
  するため hydration mismatch 警告は出ない。検出は useLayoutEffect
  (isomorphic)で行い、commit 後・ブラウザ描画前に flush されるため、
  補正後の isMobile 値が最初のフレームに反映される。モバイルでは
  デスクトップツリーは描画される前に差し替えられる。

## ヘビーツリーの二重マウント / ポーリング配慮
- 主ポーラーは分岐に依存しない単一の useWorktreeDetailController が
  一度だけ生成する(分岐は表示のみ)。
- 非表示側の表示ツリーは同一 commit 内でペイント前に破棄されるため、
  passive な useEffect ベースの terminal ポーリングは発火しない。
- 主要なコールドロード経路(キャッシュ無し→LoadingIndicator)では
  どちらの分岐の子もマウントされない。

## その他
- SSR での useLayoutEffect 警告は isomorphic パターン
  (server では useEffect)で回避。
- 既存の useIsMobile 単体テスト(初期値・リサイズ切替)は不変で継続。
- hydrateRoot を用いた新規テストで「hydration mismatch 警告が出ない」
  「モバイルビューポートでモバイル値へ解決」「デスクトップは false 維持
  (回帰なし)」を自動検証。

品質ゲート: lint / tsc --noEmit / test:unit / test:integration / build 全て成功。

Refs #1126

* feat(pwa): PWA対応(manifest+Service Worker+インストール可能化) (#1146)

Web App Manifest(manifest.ts)、手書きService Worker、インストール可能化を
実装。「外出先からエージェントを監視する」プロダクト特性に沿ってホーム画面
から独立アプリ(standalone)として起動可能にする。

Service Worker技術選定: 手書きvanilla SW(next-pwa/Serwist/Workbox不採用)。
理由:
- custom server(server.ts)がWebSocket upgradeを保持しており、next-pwa等が
  前提とするデフォルトNextサーバ構成と噛み合わない
- 本Issueが要求するallowlist方式(キャッシュ対象を明示列挙)を直接表現でき、
  監査容易・小規模・後続#1125 Web Pushの拡張が容易

キャッシュ戦略(allowlist):
- /api・/login・/proxy・非GET・クロスオリジン → network-only(キャッシュ禁止)
- /_next/static/・/icons/・manifest/favicon → cache-first
- navigation → offline-fallback(認証コンテンツを避けページ本体は非キャッシュ)
判定はcache-policy.ts(単体テスト済み)を単一情報源とし、public/sw.jsが同定数を
鏡写し、sw-file.test.tsで両者の同期を担保。

更新戦略: install時はskipWaitingせず、更新検知でトースト表示→リロードで
SKIP_WAITINGを送信しcontrollerchangeで新版に切替。

その他:
- 本番のみSW登録(shouldRegisterServiceWorker)、dev/test・非セキュアは無効化
- オフラインフォールバック画面(/offline、トークン準拠、再接続ボタン)
- iOS standalone用にappleWebAppメタ(capable/title/status-bar-style)を付与
- manifest/sw/offlineをAUTH_EXCLUDED_PATHSに追加(auth有効時もinstallable/
  precache可、いずれも非機密リソース)
- HTTPS/LAN配信時のSW/インストール制限をREADMEに注記

Refs #1124

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(mobile): モバイルA11y改善(focus trap・44pxタップターゲット・tablistキーボード) (#1147)

モバイルのアクセシビリティ/エルゴノミクスの穴を塞ぐ。

focus trap は共通フック useFocusTrap に1実装集約:
- 判断理由: Modal/シート類は独自ポータル+enter/exitアニメ(useExitAnimation
  /usePromptAnimation)+スワイプ dismiss を持つ bespoke 実装。@radix-ui/
  react-dialog は未導入で、置換すると3面すべてとそのテストを書き直す必要がある。
  共通フックなら既存機構を壊さず trap のみ追加でき、issue の「1実装に集約」に沿う。
- 挙動: 初期フォーカスをダイアログへ移動 / Tab・Shift+Tab をダイアログ内で循環 /
  閉時に呼び出し元へフォーカス復帰。autoFocus 等で消費側が内部フォーカスを
  管理している場合は初期フォーカス奪取・復帰の両方をスキップ(ConfirmDialog 互換)。
- 適用: ui/Modal(role=dialog/aria-modal/aria-labelledby も付与)・
  MobilePromptSheet・MobileTerminalActionsSheet。

44px タップターゲット(視覚サイズは不変、ヒットエリアのみ拡大):
- エージェントインスタンスタブ(WorktreeDetailRefactored)
- History サブタブ Message/Git(WorktreeDetailMobile)
- MobileTabBar の各タブ

MobileTabBar に ARIA tabs パターン完成:
- 矢印キー(左右/上下)+Home/End 移動、ラップアラウンド
- roving tabindex(activeタブのみ tabindex=0)、選択はフォーカス追従

touch-manipulation をボタン系プリミティブ(ui/Button)とモバイル操作面へ付与し
ダブルタップズームの遅延を抑止。

title のみだった WorktreeCard のお気に入りトグル(アイコンのみ)へ aria-label を
付与し、タッチ/スクリーンリーダーからも到達可能に。

スワイプジェスチャー等は本Issueスコープ外のため変更していない。

Refs #1127

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(mobile): ジェスチャー強化(スワイプタブ切替・pull-to-refresh・キーボード追従) (#1148)

- useSwipeGesture に axis/方向ロック/エッジ開始制限/onSwipeMove を後方互換で追加
  (既存 MarkdownEditor の四方向スワイプと #299 スクロール抑制は不変)
- worktree詳細のモバイルタブを水平スワイプで切替。方向ロックで縦スクロール中は
  無効化、しきい値60px、ターミナルタブはエッジ開始領域に限定して誤発火を抑止。
  MobileTabBar インジケータは activeTab 共有で自動同期
- Sessions/Repositories に pull-to-refresh を新設(usePullToRefresh + PullToRefresh)。
  最上部のみ発火し overscroll-behavior:contain + preventDefault でネイティブPTR二重発火を防止
- MessageInput を useVirtualKeyboard に接続しキーボード追従(keyboardAware opt-in)、
  入力要素へ enterKeyHint/inputMode を付与
- MobilePromptSheet のアドホックスワイプを useSwipeGesture へ統合(focus trap ref を
  マージして #1127 の挙動を維持、finger-follow は onSwipeMove で継続)

Refs #1128

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(pwa): エージェント状態のWeb Push通知(プロンプト待ち/完了) (#1149)

- push_subscriptions テーブル(migration v41)と CRUD モジュールを追加
- VAPID/web-push 送信層: 種別フィルタ・デバウンス・410自動削除、機密(endpoint/秘密鍵)は非ログ
- プロンプト検出/セッション完了時に response-checker からファンアウト(検出ロジックは不変・fire-and-forget)
- 購読登録/解除/種別設定/VAPID公開鍵の…
Kewton added a commit that referenced this pull request Jul 16, 2026
* feat(files): スマホ版でファイルをダウンロード可能に(attachment配信+FileViewer DL導線)(#1024) (#1025)

スマホ版のファイルビューア(FileViewer, Issue #438 でモバイル専用化)から
任意種別のファイルをダウンロードできるようにする。

- B(サーバ): files/[...path] GET に download 分岐を追加し、生バイトを
  Content-Type: application/octet-stream + Content-Disposition: attachment で返却。
  filename は src/lib/http の content-disposition ヘルパーで RFC 準拠にサニタイズ
  (ASCII フォールバック + filename*=UTF-8'' パーセントエンコード、ヘッダ
  インジェクション回避)。既存 getWorktreeAndValidatePath でパス検証を再利用。
  base64 JSON を経由しないためプレビュー上限を超える大容量にも対応
- A(UI): FileViewer ツールバーにダウンロードボタンを追加し、同一オリジンの
  download URL へ遷移(iOS Safari 含めネイティブDLに乗せる)
- テスト新規29本(download API のヘッダ/パス検証/filename、content-disposition
  ヘルパー、FileViewer ボタン)
- C(独立ページのPDF分岐/DLリンク)は範囲外(モバイル動線が独立ページを使わないため)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(session): Timer 発火の送信を History に記録(send ロジックを共有サービス化)(#1028) (#1029)

Timer 機能(Issue #534)から送った指示が History(chat_messages)に残らない
問題を修正。executeTimer が cliTool.sendMessage だけを呼び createMessage/
startPolling をスキップしていたのが原因(#947 の委譲変更が起点)。

- send route L262-385 のインライン記録ロジックを src/lib/session/
  send-user-message.ts の共通関数 sendUserMessage() として抽出
  (savePendingAssistantResponse → send(画像/Copilot分岐含む)→ createMessage
  → updateLastUserMessage/orphan処理 → startPolling)。HTTP 層非依存
- send route POST を sendUserMessage() 利用へリファクタ(挙動不変)
- timer-manager.ts executeTimer を sendUserMessage() 経由に変更。
  timer_messages の status 遷移(sending/sent/failed)は維持
- テスト: sendUserMessage / executeTimer のユニット新規9件 + timer 既存更新(計26件)

これにより Timer 送信も user メッセージと assistant 応答の両方が History に残る。

スコープ外(別Issue推奨):
- tests/integration/api-send-cli-tool.test.ts の1件は base でも失敗する既存不具合
  (2引数アサート vs 3引数呼び出し)で本修正と無関係(CI の test:unit 対象外)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(todo): ブランチ単位ToDoを3状態化(未着手/仕掛/完了)(#1032) (#1033)

- migration v38: worktree_todos に status カラム追加(done=1→'done' backfill、CURRENT_SCHEMA_VERSION 37→38)
- DB層: status を真実源に done := status==='done' を派生(後方互換維持、writer整合)
- API/クライアント: PATCH で status ∈ {todo,doing,done} を検証・適用、done後方互換
- UI: 3状態巡回チップ+色バッジ+状態別件数、TodoPane全体を next-intl 化
- i18n: locales/{en,ja}/worktree.json に todo namespace 追加
- テスト: 既存5更新+新規 migration-v38(backfill検証)

Closes #1032

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(todo): ブランチ単位ToDoに詳細フィールド追加+クリックで件名/詳細を編集 (#1034) (#1035)

- migration v39: worktree_todos に detail カラム追加(TEXT NOT NULL DEFAULT ''、既存行は空詳細で移行)
- DB層: WorktreeTodo に detail 追加、create/update で detail 受理
- API: POST/PATCH で detail を検証・適用(MAX_TODO_DETAIL_LENGTH 新設)
- クライアント: WorktreeTodoItem.detail、create/update の detail 対応
- UI: 項目クリックで編集モーダル(Modal.tsx再利用)を開き件名+詳細を編集、詳細ありインジケータ
- i18n: locales/{en,ja}/worktree.json に詳細/編集の文言追加
- テスト: 既存6更新+新規 migration-v39、docs/module-reference.md 更新

Closes #1034

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(todo): ブランチ単位ToDoの詳細にコピーボタンを追加 (#1036) (#1037)

- UI: 編集モーダルの詳細欄に CopyButton を配置し詳細本文をコピー(詳細が空のときは非表示)
- 既存 src/components/common/CopyButton.tsx / clipboard-utils.ts を再利用(スマホHTTPは execCommand フォールバック)
- i18n: locales/{en,ja}/worktree.json に todo.copyDetail 追加
- テスト: TodoPane.test.tsx にコピー導線テスト3件追加
- migration/DB/API 変更なし(UIのみ)

Closes #1036

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): セマンティックデザイントークン基盤を導入 (#1041) (#1054)

globals.css に :root(light)/.dark(dark) の CSS 変数
(background/foreground/surface/muted/border/input/ring/accent-50..700/
success/warning/danger/info) を定義し、tailwind.config.js の primary(cyan)・
cmd-bg-dark を semantic トークンへ置換。layout.tsx / MainLayout.tsx の
body 背景を bg-background に統一。既存の実効値を写し取り視覚は不変(no-op)。
docs/design-system.md を新設。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): cn()/cva によるバリアント管理基盤とプリミティブ改修 (#1042) (#1055)

clsx + tailwind-merge の cn() ヘルパーと class-variance-authority を導入し、
Button/Card/Badge/Modal を cva ベースへ移行。Button ghost バリアントの
dark: 指定漏れ(ダークモードで視認性劣化)を修正。既存 API は完全互換。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): アイコンを lucide-react に統一 (#1044) (#1056)

絵文字アイコン(🤖⚡✦💻)を廃止し lucide アイコンへ置換。共通レイアウト・ナビ
(Header/GlobalMobileNav/MobileTabBar/ActivityBar/terminal page)および worktree
各所のインライン SVG を lucide に統一。docs/design-system.md にアイコンサイズ規約を追記。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): next/font で Geist Sans/Mono を導入 (#1043) (#1057)

geist パッケージ(セルフホスト)で GeistSans/GeistMono の CSS 変数を <html> に適用し、
tailwind fontFamily の sans/mono を「Geist → 日本語(Hiragino/Noto Sans JP) →
システム」フォールバックで構成。Terminal.tsx(xterm)の等幅も Geist Mono を先頭指定。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): RadixベースUIプリミティブを拡充 (#1046) (#1058)

Input/Textarea/Select/Tabs/Tooltip/DropdownMenu/Switch/Skeleton を追加
(cva + cn, セマンティックトークン着色, ライト/ダーク対応)。Portal 系は
'use client' + Z_INDEX(POPOVER)で Modal と階層整合。Sessions ページの
フィルタ/ソートを新プリミティブへ移行(適用サンプル)。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): アクセント色を統一しセマンティックトークンへ置換 (#1045) (#1059)

cyan/blue 直書き(~600箇所 / 131ファイル)を #1041 のセマンティックトークンへ置換。
用途判定: インタラクティブ=accent / フォーカス=ring / 情報=info。gray 系は
surface/background/border/muted へ実効値を保って置換。例外(Terminal.tsx の
xterm ANSI 配色 / CLI ブランド色 / .scrollbar-thin 等の固定装飾色)は維持。
globals.css の .btn-primary・検索ハイライト等もトークン化。
敵対的レビュー指摘(focus:ring-accent→focus:ring-ring 統一、design-system.md
の例外記述修正)を反映。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): 主要ページを共通UIプリミティブへ移行 (#1047) (#1060)

layout(Header/Sidebar の検索・トグル・言語)/ home / repositories / review /
more / chat / login の生button・手組みフォーム・コピペカードを @/components/ui
プリミティブへ移行。機能・挙動(onClick/フォーム送信/バリデーション/キーボード)は維持。
敵対的レビュー指摘(chat系ボタン移行・ghost の dark hover)を反映。ネイティブ select・
複合 textarea 入力バー・極小アイコントグルは意図的に残置(PR の残置リスト参照)。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): globals.css の @apply クラスを廃止し cva プリミティブを自己完結化 (#1048) (#1062)

.btn/.btn-*/.card/.card-hover/.badge*/.input を globals.css から削除し、
Button/Card/Badge の cva 定義へ実クラスを内挿(旧 @apply 定義と同値、視覚不変)。
src/ 全体で削除クラスへの参照が 0 であることを確認。worktree の一部
(WorktreeDetailRefactored/LogViewer/WorktreeList/ExternalAppForm)をプリミティブへ移行し、
マジックナンバー入りインライン style を定数化。worktree/mobile/sidebar/common の
一括プリミティブ移行は規模の都合により #1061 へ分割。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): サーフェス・奥行きを再設計しモダンダークテーマへ刷新 (#1049) (#1063)

globals.css の :root/.dark トークン値を刷新(深い base + 微リフト surface +
ヘアライン border)。Header/GlobalMobileNav に backdrop-blur(supports-[backdrop-filter]
フォールバック付)。Card に elevated/interactive variant、h1-h4 見出しタイポを調整。
敵対的レビュー指摘を反映: 主要閲覧画面のコンテンツカード(sessions/home/review)を
bg-surface/border-border へ移行し背景反転による溶け込みを解消、ダーク入力を surface-2 で
凹み表現、Card interactive にキーボード focus 対応を追加。コントラストは WCAG AA 全 pass。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): モーション基盤とマイクロインタラクションを追加 (#1050) (#1064)

tailwindcss-animate を導入し、Modal/MobilePromptSheet の enter/exit、Radix data-state
連動(DropdownMenu/Select/Tooltip)、一覧の stagger、hover lift を統一適用。
prefers-reduced-motion 対応。ターミナル/仮想スクロールには非適用。敵対レビュー指摘
(reduced-motionでのtranslate漏れ→motion-safe化、sessionsエラー時のstagger再発火→list
マウント維持、Tooltip instant-open enter、home focus parity)を反映。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): Home をダッシュボード(bento グリッド)化 (#1052) (#1065)

Home を縦積みから bento グリッドへ再構成(統計/直近セッション/ToDo/クイックアクション)。
PC は CSS Grid(grid-cols-12 + col-span)、モバイルは1カラム縦積み(useIsMobile の JS 分岐を
増やさず Tailwind breakpoint)。既存 sessions 取得フック/API を再利用し新規 API なし。既存機能
(ToDo 追加/チェック/コピー、セッションカウント)を維持。空状態対応。敵対的レビュー指摘
(ToDo タイルの h-full・見出しパリティ)を反映。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): コマンドパレット(⌘K)を追加 (#1053) (#1066)

cmdk で ⌘K(mac)/Ctrl+K(win) のコマンドパレットを追加(Navigation / Worktrees 検索 / Actions)。
input/textarea/ターミナル入力中は非発火、SSR 安全('use client')、i18n(en/ja)、Modal 相当の z-index。
モバイルは GlobalMobileNav に起動導線。敵対的レビュー指摘を反映: open 時に入力へフォーカスし
キーボード操作(type/矢印/Enter)を復旧[HIGH]、worktree 一覧は共有キャッシュ context を再利用
(最新化+失敗時 Navigation fallback)、Escape の preventDefault/stopPropagation と close 時の
フォーカス復帰。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): エージェント/セッションステータスの視覚を強化 (#1051) (#1067)

StatusDot(running/waiting/idle/error)を新設。running はグロー+パルス+静的リング halo、
waiting は amber の弱点滅。サイドバー(BranchStatusIndicator)/ Home / Sessions を StatusDot 化。
prefers-reduced-motion でパルス停止(静的リングで running≠ready を維持)、ポーリングで再発火しない
CSS infinite アニメ。敵対的レビュー指摘(reduced-motion での running/ready 識別性、docs の実態訂正)を
反映。worktree 詳細/MobileHeader は従来スピナー表示のまま(docs に明記・今後の追随対象)。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ui): サイドバーがヘッダーに遮蔽される左上レイアウト破綻を修正 (#1070) (#1083)

showGlobalNav 時に固定サイドバーを top-16 h-[calc(100vh-4rem)] にオフセットし、
半透明ヘッダー(z-50, backdrop-blur)がサイドバー先頭64px(Branches 見出し+
view/sort/sync ツールバー行)を遮蔽してクリック不能+灰色の滲みを生む問題を解消。
あわせて border-gray-200/dark:border-gray-600 を border-border にトークン化。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): テーマ既定を system 化しヘッダーにトグルを追加 (#1071) (#1084)

next-themes を defaultTheme="system" enableSystem に変更し、ライト環境ユーザーへの
強制ダークを解消。Header 右クラスタに ThemeToggle を追加(発見可能性向上)。
globals.css に color-scheme(:root=light / .dark=dark)を宣言し、残存ネイティブ
コントロールの OS クローム事故(dark で白いドロップダウン等)を防止。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(hooks): useIsMobile を matchMedia ベースにし混成レイアウトバグを解消 (#1069) (#1085)

window.innerWidth 依存を window.matchMedia(`(max-width: ${breakpoint-1}px)`)に置換。
innerWidth と CSS ビューポートが乖離する環境(実測 innerWidth=846 vs
documentElement.clientWidth=390)では、Tailwind の md:(min-width:768px)と JS 分岐が
食い違い「CSS はモバイル・JS はデスクトップ」の混成レイアウトになっていた。
max-width:breakpoint-1 は md: の正確な補集合。change イベント購読+cleanup、
SSR 初期値 false、MOBILE_BREAKPOINT export と breakpoint オプションは維持。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): Home 第一フォールドを回収(バナー削除・見出し降格・tabular-nums) (#1072) (#1086)

陳腐化した Welcome バナー(#600 のナビ再編告知)を削除。h1「CommandMate」を機能的見出し
(HomeHeading: Overview + ライブサブライン)へ降格しヘッダーのワードマークとの同語反復を解消。
Home の数値・相対時刻・サイドバーのブランチ数バッジに tabular-nums を導入。HomeQuickActions の
inline SVG を lucide へ置換。相対時刻の短縮形は新設 date-utils、見出し文言は home i18n
namespace(en/ja)。機能(ToDo/カウント/遷移)は不変、サブラインは既存キャッシュから算出。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ライトモードのエレベーション階梯を反転(グレー地+白カード+2層影) (#1074) (#1087)

light を「白ページ+凹む灰カード」から Vercel/Linear 型の「グレー地ページ+
白カード+2層影」へ反転し、dark(#1049)と対のエレベーションモデルにする。
surface-2(740) < background(751) < surface(765) の「白カードが浮き、well が沈む」階梯。

- globals.css :root: --background 255→250 250 251 / --surface 248 250 252→255 255 255
  / --surface-2 241 245 249→244 246 250。.dark は不変(回帰なし)
- tailwind.config.js: 2層 boxShadow.sm を新規追加(md/lg は Tailwind 既定のまま)
- design-tokens/surface-depth テスト更新 + WCAG コントラスト検証を追加
  (foreground≥4.5 全面 / muted-fg≥4.5 page,card / ≥3 recessed well)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): サイドバーを --sidebar-* トークン化しテーマ追従に (#1073) (#1088)

bg-gray-800 text-white 固定でテーマ孤島だったサイドバーを、新設の
--sidebar / --sidebar-foreground / --sidebar-border / --sidebar-hover /
--sidebar-muted セマンティックトークンへ移行。light=slate-50系の明るいパネル、
dark=#141821(#1049階梯と整合)でテーマに追従する。standalone なリテラルRGB値
としており、#1074 の --surface 反転がサイドバー色へ滲まない。

- globals.css :root/.dark に --sidebar-* を定義、tailwind.config.js に登録
- Sidebar.tsx / sidebar/*(BranchListItem, SortSelectorBase)/ 検索Input
  / LocaleSwitcher / ThemeToggle の直書きを全てトークンへ置換
- 敵対的レビュー由来のライト回帰修正2件:
  - LogoutButton.tsx: 漏れていた text-gray-300 直書きを ThemeToggle と同じ
    テーマ中立トークン(text-muted-foreground/hover:bg-muted)へ(モバイル共用)
  - SortSelectorBase: 選択中ソートを text-accent-700 dark:text-accent-400 に
    (ライトのslate-50上で 1.73→約5.1:1 でAA達成、dark時は据え置き)
- docs/design-system.md に --sidebar-* を追記、既存テスト/期待値を更新

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ⌘K パレットを Raycast 級に強化(アイコン/StatusDot/kbd/Recents/ヘッダー導線)(#1077) (#1089)

- src/components/ui/Kbd.tsx を新設(semantic-token ベース、#1080 で再利用)し ui/index.ts に export 追加
- NAV 行に lucide アイコン、worktree 行に GitBranch + StatusDot(isProcessing/isWaitingForResponse/isSessionRunning から算出)
- バックドロップ/パネル/選択状態/入力/フッター文字色を semantic トークン化(bg-surface/backdrop-blur/ring-border/accent-500)
- キーボードガイドのフッター(↑↓/↵/esc、i18n)
- Recent グループ(localStorage cm.palette.recents、MRU<=8、空クエリ時のみ、dead worktree は無視)
- worktree を isSessionRunning 優先→updatedAt 降順ソート+空クエリ時 slice(0,8)
- Actions 拡充: Sync repositories(repositoryApi.sync + Toast)/ 言語切替 / Open GitHub
- Header に ⌘K 検索ピル(mount 後に platform 判定して ⌘K/Ctrl K を出し分け)
- #1053 の挙動契約(フォーカス/入力ガード/Escape/backdrop/data-testid)を維持
- 文言を locales/{en,ja}/commandPalette.json に追加、単体テスト更新

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ターミナルクロームを刷新(全周枠廃止・カード化・CLIセレクタDropdownMenu化)(#1079) (#1090)

worktree ターミナルの全周枠を廃し surface-2 カード+ヘアラインのカード化に統一。
CLI インスタンスセレクタを native <select> から Radix DropdownMenu(StatusDot+alias
+ChevronDown)へ置換し、value/onChange の単一選択セマンティクスと testid を保持。

- Terminal / ActivityBar / TerminalDisplay / TerminalSplitContainer /
  TerminalSplitPane / TerminalSplitPaneContent を surface/border トークンへ
- TerminalSplitPane: <select> → DropdownMenu(RadioGroup)。cli-selector testid 保持
- xterm の theme は意図的に固定ダーク(--surface-2 dark 相当)を維持
- StatusDot/status-colors.ts は不変(#1078 の領域)。locales worktree.json 追記
- 既存 terminal テスト(cli-tab-switching 含む)更新・全パス

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): worktree詳細/Chat のダーク直書き島を surface トークン化しテーマ追従に (#1075) (#1091)

HistoryPane / ConversationPairCard / AssistantMessageList / AssistantChatPanel /
HistorySearchBar / CopyButton と .assistant-md の常時ダーク直書きを surface/foreground
セマンティックトークンへ移行し、light/dark 両テーマで正しく描画されるようにする。

- globals.css .assistant-md: リンクを accent-700 dark:accent-400、code/pre を明色地に
  し両テーマ AA≥4.5:1。:root/.dark のトークン定義は不変
- 各コンテナ+全子孫を surface/surface-2/foreground/muted-foreground/border トークンへ
  (hidden-children 回避: 子の直書き light-on-dark を残さない)
- AssistantChatPanel/HistoryPane の native select/checkbox は色のみトークン化
  (Radix 化は #1076 の担当のため据え置き)
- ConversationPairCard: コピー/挿入/展開ツールバーの hover-reveal 化に伴い、
  タッチ端末で不可視化する回帰を [@media(hover:none)]:opacity-100 で解消
  (デスクトップの hover-reveal は維持。copy-user/assistant-message testid 保持)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): データ画面コントロールの意味論を整理(Review/Repositories/More)(#1081) (#1092)

表示のみの調整。絞り込み・編集・削除・可視性トグルのロジックは不変。

- Review: フィルタを CTA 風の primary/secondary ボタンから、件数付きの
  控えめなセグメンテッドコントロールへ(選択は bg-accent-500/15、CTA と区別)。
  件数は既存のフィルタ済み配列長から算出(新規 fetch なし)。
- Repositories: Enabled/Disabled バッジと可視性トグルの緑ピルを StatusDot 流の
  dot+muted テキストへ(トグルは操作可能なまま)。(none)→ muted の em-dash、
  パスは truncate+font-mono+full-path tooltip、Edit は ghost な Pencil アイコンボタン。
- More: ExternalAppsManager 側の重複 h2「External Apps」を削除(ページ側に一本化)。
- StatusDot/status-colors.ts は不変(#1078 の領域)。生 gray/cyan/blue の追加なし。
- 既存テスト更新+件数バッジ/単一見出しの検証テストを追加。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): コンポーザーをカード化しモバイルworktreeコントロールを整理 (#1080) (#1093)

- MessageInput をコンポーザーカード化(surface/border/shadow/focus-ring)し、送信
  ボタンを入力有無で filled(bg-accent-600)/ghost に切替。プレースホルダを i18n 短縮
- Auto-Yes をコンポーザー下辺のメタ行へ統合(ui/Switch 化)。右側に Kbd キーヒント
  (#1077 の ui/Kbd 再利用)。InterruptButton のオレンジ直値を text-danger ghost へ
- モバイル sticky 行をインスタンスタブ専用化(dark: バリアント+下線インジケータ)。
  検索/End を MobileTerminalActionsSheet(ボトムシート、End は確認付き)へ移設
- MobileTabBar を GlobalMobileNav 様式(bg-background/backdrop-blur/border-border、
  上辺インジケータ、Wrench アイコン、success/warning バッジ)へ統一
- テスト: 送信ボタン状態変化/新規シートを追加、ResizeObserver polyfill(Radix Switch)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): 稼働ステータスを StatusDot 一本化し idle ノイズを縮退 (#1078) (#1094)

青スピナー(border-info)と生 span を全て統一 StatusDot に置換し、稼働ステータスの
視覚言語を1系統に。idle ノイズを縮退(Sessions は running/waiting のみラベル付き、
idle は「+N idle」カウンタ / worktree ヘッダーは idle を StatusDot のみの円形ボタン
+Tooltip、幅超過は「+N」DropdownMenu オーバーフロー)。

- WorktreeDetailSubComponents / sessions/page / MobileHeader を StatusDot へ統一
- 新規 lib/agent-status-display.ts に classifyHeaderInstances 等の導出を集約
- status-colors.ts の type:'spinner'/border-info は @deprecated 化(ReviewTab が
  最後の消費者。消滅後に削除)
- 相対時刻 tabular-nums / プレビューは CSS truncate、DnD(handleAgentDragStart)と
  タブ切替 onClick は全ボタンで温存
- 敵対レビュー由来の仕上げ: 「+N」オーバーフローに稼働中(running/waiting)があれば
  集約 StatusDot グローを出し、稼働セッションが glow-less に埋もれないように。
  同 trigger の生グレーをセマンティックトークンへ

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ネイティブ checkbox/radio を Radix プリミティブに統一 (#1076) (#1095)

OS 標準の白いフォームコントロールが dark に浮く問題を解消。@radix-ui/react-checkbox
/ react-radio-group ベースの ui/Checkbox・ui/RadioGroup を新設し、残存していた
ネイティブ input(checkbox 12箇所/11ファイル・radio 6箇所/4ファイル)を全て移行。

- 新規 src/components/ui/{Checkbox,RadioGroup}.tsx(cn()+cva・セマンティックトークン・
  focus-visible:ring-ring)、ui/index.ts に export、依存2つを追加
- checked/onCheckedChange・value/onValueChange への適応、name/value/disabled/
  aria/data-testid とラベルクリックのトグルを全移行で保持
- 移行後 native type="checkbox"/"radio" は src/ 配下 0 件(完了性 grep 検証済)
- 敵対レビュー(binding/primitive-a11y/GitDangerZonePanel 密集)0件

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): トークン規律の確立(生gray/slate置換・focus-visible統一・CIガード)(#1082) (#1096)

移行済み領域の生 gray/slate 直書きをセマンティックトークンへ置換し、フォーカス表現を
focus-visible に統一、CI grep ガードでトークン違反の再流入を止める。機能・挙動は不変
(見た目/規律のみ)。

## プリミティブ是正
- Button: secondary/ghost を muted/foreground、danger を bg-danger トークンへ。
  `focus:` → `focus-visible:` + `ring-offset-background`(dark の白ハロー解消。Tabs/Switch が手本)。
- Input/Textarea/Select トリガー: `focus:` → `focus-visible:`(枠が反応するため offset なし)。
- Badge gray → bg-muted/text-muted-foreground、Modal → surface/border/foreground。

## in-scope スイープ(生 gray/slate → トークン)
- app(worktrees を除く)/ ui / layout / home / review / repository / common。
- gray-100/200→muted、gray-400/500/600→muted-foreground、gray-700/800/900→foreground、
  境界→border、フォーム枠→input。

## globals.css / metadata / docs
- body→text-foreground、.prose インラインコード→muted/foreground、scrollbar-thin をトークン化
  (--input / --muted-foreground)、`::selection` にアクセント淡色を追加。
- layout.tsx: title template(`%s | CommandMate`)+ viewport themeColor(light/dark)。
- docs/design-system.md にフォーカス表現・CI ガード・例外リストを同期。

## CI ガード
- `.github/workflows/ci-pr.yml` に `token-discipline` ジョブを追加(CLAUDE.md size check と同方式)。
  ホワイトリスト方式で移行済みディレクトリ限定 hard-fail。除外リストをスクリプト内に明記。
  違反注入時に fail することを確認済み。

## 判断メモ(要レビュー)
- `src/app/worktrees/**`(40 occ)はオーケストレーター集計では app(108) に含まれるが、
  ワークツリー領域(#1061・「触らない」)かつ常時ダークのターミナルページ/CLI ブランド色
  (bash=bg-gray-600)を含むため、スイープ・ガード双方から除外した。#1061 完了時に拡大。
- 軽微な見た目変化: danger の red-600→red-500(--danger)、secondary/ghost の muted シェード、
  SidebarToggle はテーマ追従化、ツールチップは反転サーフェス(bg-foreground/text-background)へ
  テーマ追従化、Home の Save&Resend ボタンを accent-600 + 白文字に正規化。

品質ゲート(lint / tsc --noEmit / test:unit / build)+ 新 CI ガード 全パス。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree/共通の生gray/buttonをトークン・プリミティブへ (#1061 群A 1/4) (#1097)

#1061 のファイル分割並列(群A: 21ファイル)。生 gray/slate Tailwind クラスを
#1040/#1082 のセマンティックトークンへ用途判定で置換、生 <button> を ui/Button へ。
挙動・ハンドラ・testid は不変。群A担当ファイルの raw gray = 0。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree詳細/markdown/fileの生gray/buttonをトークンへ (#1061 群B 2/4) (#1100)

#1061 分割並列(群B: 21ファイル)。WorktreeDetailSubComponents/MarkdownToolbar/
FileTreeView 等の生 gray/slate → セマンティックトークン、生 button → ui/Button。
ターミナル出力表示面(TerminalSplitPaneContent)はダーク維持のため意図的に非トークン化。
挙動・testid 不変。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree/mobile/gitの生gray/buttonをトークンへ (#1061 群C 3/4) (#1098)

#1061 のファイル分割並列(群C: 22ファイル)。生 gray/slate → セマンティックトークン、
生 <button> → ui/Button。挙動・testid 不変。群C担当ファイルの raw gray = 0。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree/git/todoの生gray/buttonをトークンへ (#1061 群D 4/4) (#1099)

#1061 分割並列(群D: 21ファイル)。生 gray/slate → セマンティックトークン、生 button
→ ui/Button。ターミナル出力表示面(TerminalSplitContainer/TerminalContainer)は
xterm と同様ダーク維持のため意図的に非トークン化(除外)。挙動・testid 不変。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci(guard): token-discipline ガードを worktree/mobile/external-apps へ拡大 (#1061) (#1101)

#1061 で worktree/mobile/external-apps の生 gray/slate をトークン化完了したため、
#1082 で導入した token-discipline CIガードの対象ディレクトリを拡大。
ターミナル出力表示面(*Terminal* ソース)は xterm 固定ダーク(#1079)と同様の
意図的ダーク島のため除外。app/worktrees も引き続き除外。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: integration スイートの drift を解消(モック/期待値追随・CI追加)(#1102) (#1103)

CI 非対象で長期 drift していた integration スイート(41 failed)を修復。実装バグを
隠さないよう vi.mock を importOriginal 方式へ書き換え、欠落 export を補完。

- 不完全 vi.mock を importOriginal ベースへ(欠落 export を実挙動ごと通す)
- config ドリフト(アップロード上限 5MB→20MB 等)の期待値を現行定数準拠へ
- undefined.length 系はモック戻り値を実 shape へ
- 再 drift 防止に ci-pr.yml へ Integration Tests ジョブ追加
- external-apps duplicate name → 500(期待409) は実装バグのため it.skip で quarantine

結果: test:integration 643 passed / 1 skipped(0 failed)。unit/lint/tsc 緑維持。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(external-apps): 重複nameで500ではなく409を返す (#1104) (#1105)

* fix(external-apps): 重複nameで500ではなく409を返す (#1104)

route.ts の catch を message.includes('UNIQUE constraint') 文字列判定から
ExternalAppDbError.code === 'DUPLICATE' の型/コード判定へ変更。DB層のエラー
ラップ後もメッセージに依存せず 409 を正しく返す。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(external-apps): 409 duplicate name テストを un-skip(#1104 で修正済み)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(timer): Timer履歴の詳細モーダル表示と失敗理由の記録 (#1107) (#1109)

Schedule 側の execution_logs 方式を Timer へ横展開。

- DB: migration v40 で timer_messages に error 列(nullable)追加、schema 39→40
- timer-db: error 列の read/write 貫通、updateTimerStatus に optional error 引数、recoverStuckSendingTimers に固定リカバリ理由
- timer-manager: 送信失敗(stage/error)・例外(error.message)を error 列へ永続化(no_session は保存せず)
- API: GET timers レスポンスに error を追加
- UI: TimerDetailModal を新設し履歴行クリックで指示全文+メタ+失敗理由を表示(PC=Modal / mobile=FullScreenModal)
- i18n: schedule.json の timer ブロックに en/ja lockstep でラベル追加

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(files): Filesツリー展開状態のworktree単位永続化と表示リセット (#1108) (#1111)

useFileTabs / useFilePanelState の worktree 単位 localStorage パターンを踏襲。

- 新規 useFileTreeExpandedState: 展開集合を worktree 単位 localStorage(キー commandmate:file-tree-expanded:<worktreeId>) に永続化。lazy-init 復元をマウント再フェッチに乗せ、空集合はキー削除、worktree 間の漏洩なし
- useFileTabs: CLOSE_ALL / closeAllTabs() を追加
- FileTreeView: フック統合+ツールバーに「表示をリセット」ボタン(更新ボタンの隣・常時可視)+ onResetView prop
- Controller/Desktop/Mobile/Refactored: resetFileTreeView(検索クリア・全タブ閉じ・モバイルビューア閉)を配線し、初期表示に戻すフルリセットを実現

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(mobile): viewportFit:'cover' を追加しiOS safe-area対応を有効化 (#1132)

- src/app/layout.tsx の viewport エクスポートに viewportFit: 'cover' を追加。
  iOS は viewport-fit=cover 指定時のみ env(safe-area-inset-*) に非0値を
  入れるため、既存の pt-safe/pb-safe 等の safe-area 対応が実効化される
- GlobalMobileNav の未定義クラス safe-area-bottom を globals.css 定義済みの
  pb-safe に修正(cover 有効化後もパディングが効かない潜在バグ)
- viewport エクスポート値(viewportFit / themeColor)のユニットテストを追加

レンダリング検証: 本番ビルドで <meta name="viewport"> に
viewport-fit=cover が含まれることを確認済み

Refs #1131

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat(ui): ステータスカラーtintトークン導入とダーク非対応フィードバックUIの移行 (#1133)

- globals.css / tailwind.config.js に success/warning/danger/info × subtle/border/foreground の12トークンを両テーマで追加(ライト *-50 面 + *-800 前景、ダーク *-950 面 + *-300 前景、全て WCAG AA 準拠。根拠はコメントに記載)
- Toast / DefaultErrorFallback / History・Prompt・ConnectionErrorFallback / ErrorDisplay / PromptPanel を新トークンへ移行し、生パレット + dark: ペアを撤去(TerminalErrorFallback は常時ダーク島のため現状維持)
- tint 面内のソリッドアクションボタンは反転 tint(bg-{status}-foreground text-{status}-subtle)で両テーマ AA を確保
- docs/design-system.md にトークン表・使用ルールを追記
- tint トークン定義・移行 grep を検証する status-tint-tokens.test.ts と ErrorBoundary/フォールバックのレンダリングテストを追加、既存 Toast / design-tokens テストを追随

Refs #1112

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* refactor(ui): window.confirmを共通ConfirmDialogへ全面置換 (#1134)

- ConfirmDialog/ConfirmProvider/useConfirm を src/components/ui/ConfirmDialog.tsx に新設
  (variant default/danger、Escape/背景クリックでキャンセル、danger時はcancelに初期フォーカス、
  クローズ後に呼び出し元へフォーカス復帰)
- AppProviders に ConfirmProvider をマウント
- window.confirm 残存11箇所 + bare confirm 1箇所(WorktreeCard)を useConfirm へ置換
- confirm文言を next-intl 辞書へ移動 (review namespace 新設、home/worktree/common に追記)
- ESLint no-restricted-globals(confirm/alert) と no-restricted-properties(window.confirm) を追加
  (bare alert 3箇所は window.alert へ変更、alert置換自体は対象外)
- 既存テストを window.confirm モックからダイアログ操作へ追随、
  ConfirmDialog 単体テストと Timer履歴クリアの結合フローを追加

Refs #1113

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* chore(ui): UI死コード削除(WorktreeList・放棄infinite-scroll hooks・hasNewOutput配線) (#1135)

- WorktreeList.tsx(production importゼロの孤児コンポーネント)とbarrel export(index.ts)を削除
- 放棄された無限スクロール実装(useInfiniteMessages/useScrollObserver/useScrollRestoration)と
  対応テスト3本、孤児化するtypes/infinite-messages.tsを削除
- WorktreeDetailRefactored.tsxのhasNewOutput={false}死配線を撤去(MobileTabBar側は維持)
- 残置ファイルのstaleコメント修正(useVisibilityRecovery.ts/history-display-config.ts)
- useWebSocket.tsに@deprecated注記を付与(WebSocketストリーミング化で再設計・再利用予定)
- docs/architecture.md(ja/en)の削除対象ファイルへのstale参照行を削除

受入基準: 3グレップ全て0件成立、lint/tsc/test:unit(8746 pass)/build全パス

Refs #1115

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* fix(ui): 未定義animate-fade-*の定義とModal/Toast/ContextMenuのexitアニメーション実装 (#1114) (#1136)

* fix(ui): 未定義animate-fade-*の定義とModal/Toast/ContextMenuのexitアニメーション実装

- tailwind.config.jsにfade-in/fade-outキーフレームとanimate-fade-in/outを定義。
  usePromptAnimationが発行していたが未定義でno-opだった。クラス名を
  PromptPanel等が文字列比較で消費しているため、tailwindcss-animateへの
  書き換えではなくキーフレーム定義の追加を採用。duration/easingは
  --motion-duration-base / --motion-ease-out(#1050モーショントークン)準拠
- content globsにsrc/hooksを追加(フック発行クラスのpurge防止)
- useExitAnimation(open, duration)フックを新設: 閉要求後もduration msだけ
  マウントを維持しexitアニメーション再生を保証。render-phase同期により
  開閉ともに1フレームの遅延なし。再open時はタイマー解除
- Modal: data-state=closedでfade+zoom-out exit(200ms)後にアンマウント。
  exit中はbackdropクリック無効化
- Toast: fade+slide-out exit(200ms)再生後にonClose通知(アンマウント遅延)
- ContextMenu: fade+zoom-out exit(100ms、enterと同duration)。
  exit中はpointer-events-none
- EXIT_ANIMATION_DURATION_MS / CONTEXT_MENU_EXIT_DURATION_MSを
  ui-feedback-config.tsに追加しCSSとJSタイマーを同期
- reduced-motionはglobals.cssのグローバルキルスイッチで対応(CSS実装のみのため追加ガード不要)
- テスト: useExitAnimation単体、Modal/Toast/ContextMenuのexit挙動
  (閉操作後にアニメーション時間経過までDOM残留→アンマウント)、
  Tailwind実コンパイルによるCSS生成検証を追加

Refs #1114

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ui): ConfirmDialogをModal exitアニメーションに追随

- ConfirmDialog.test.tsx: 閉操作後の即時DOM除去期待を、200ms exit
  アニメーション完了後のアンマウント(waitForElementToBeRemoved)に更新
- ConfirmProvider: pendingクリア後のexit再生中にdescription等が空文字に
  なり内容がブランク表示される統合ドリフトを修正(直近optionsを保持し
  フェードアウト中も表示を維持)。cancelテストにブランク防止の検証を追加

Refs #1114

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat(ui): ヘッダーナビのアクティブインジケータ実装と角丸スケール規約の導入 (#1137)

- Header ナビに CSS-only の下線インジケータを実装(after 疑似要素の
  scale-x トランジション、--motion-ease-out / duration-200 準拠、
  motion-safe ガード)。アクティブ項目に aria-current="page" を付与
- Header のアクティブ状態レンダリングテストを追加(全 6 ルート)
- docs/design-system.md に角丸スケール規約を独立セクションとして追記
  (コントロール=rounded-md / コンテナ=rounded-lg / ポップアップ=rounded-md /
  チップ=rounded-full / 小型インライン=rounded-sm、裸 rounded 原則禁止)

角丸統一の実施範囲(明確な逸脱のみ、feature 配下の一括置換はしない):
- ui/Kbd・ui/Checkbox: 裸 rounded → rounded-sm
- layout/Sidebar のアイコンボタン3箇所: 裸 rounded → rounded-md
- layout/Header のコマンドパレットトリガー: rounded-lg → rounded-md

Refs #1119

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* refactor(ui): チャット履歴UIをConversationPairCard系デザインへ統一 (#1138)

MessageList/PromptMessageの視覚言語をConversationPairCardに統一
(案B: 構造・optimistic updateロジック維持、見た目のみ刷新)。

- 生chromatic色21箇所をステータスtintトークン(warning/success/info x
  subtle/border/foreground)へ移行し、生色のdark:ペアを撤去
- インラインSVGをlucide-reactへ統一(Check/X/CircleCheck/Clock/Loader2/
  MessageCircle)
- ユーザー/アシスタントメッセージをCPC言語のセクション(accent左ボーダー/
  surface-2 wash)+ hover-revealコピーツールバーへ刷新
  ([@media(hover:none)]:opacity-100 タッチフォールバック付き)
- markdown描画をprose→assistant-md(トークン追従)へ移行、ANSI/コード
  ブロックの常時ダーク島(#0d1117)は現状維持
- ファイルパスリンク/プロンプト応答(Yes/No・選択肢・テキスト入力)/
  ログリンクの機能パリティ維持、レンダリングテストを新規追加

Refs #1117

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat(ui): スケルトンローディング展開とSpinner統一・loading.tsx整備 (#1139)

- ui/Spinner.tsx 新設: 手書きインラインSVG/border系スピナー43箇所を共通プリミティブへ置換
- 主要画面にレイアウト整合スケルトンを実装(Home Session Overview/ToDo、Sessions一覧、Repositories一覧、HistoryPane(汎用Card型・loading分岐のみ)、ファイルツリー、AssistantChatPanel、Reviewタブ群)
- 裸テキストローディングをスケルトンへ置換(MessageList.tsxは#1117衝突回避のため対象外)
- 初回ロードのみスケルトン表示(再fetch時は既存コンテンツ維持の非ブロッキングパターン踏襲)
- 主要7ルートに loading.tsx(薄いRouteLoadingフォールバック)を追加
- AssistantChatPanelのローディングを導出状態化し空状態フラッシュを解消

Refs #1118

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* refactor(ui): 生chromatic色をtintトークンへ全面移行しCIガードを拡大 (#1140)

意味ベースでステータスtintトークンへ移行 (red→danger / green→success /
yellow・amber・orange→warning / blue・sky・purple・violet→info)。
淡色面は bg-{s}-subtle + border-{s}-border + text-{s}-foreground、
ソリッドは bg-{s} を使用し、chromatic用途の dark: ペアを撤去。

- ui/Badge.tsx・config/status-colors.ts をトークン化 (生パレットクラス撤去)
- error/ の常時ダーク島 TerminalErrorFallback を *Terminal* 名の別ファイルへ分離し、
  残る error/・auth/ の生gray/chromatic漏れを回収
- 検索ハイライト/ファイル種別アイコン等の装飾色もトークン化 (text-pink等スコープ外色は維持)
- CIガード token-discipline の grep パターンに chromatic 9色を追加し error/・auth/ を対象化。
  既存の *Terminal*・テスト・常時ダーク島 (worktrees/[id]/terminal 等) の除外は維持
- docs/design-system.md に chromatic 禁止/例外リストとCIガード仕様を追記
- 影響する class 検証テストを追随
  (Badge / ContextMenu / MarkdownEditor / MemoCard / dark-mode-foundation / recursive-delete)

chromatic grep 残数
(git grep -cE '(bg|text|border|ring)-(red|green|yellow|amber|orange|purple|violet|sky|blue)-[0-9]' -- src/):
  before: 254 行 (71 ファイル)
  after:  19 行 (サンクション済み例外のみ = *Terminal* 群 + src/app/worktrees/[id]/terminal/)
  in-scope (例外除く): 0

品質ゲート: npm run lint / npx tsc --noEmit / npm run test:unit (8818 passed) / npm run build 全パス

Refs #1116

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): View Transitions APIによるページ遷移クロスフェードを導入 (#1141)

主要ルート間(Home/Chat/Sessions/Repos/Review/More/worktree詳細)のコンテンツ
領域をクロスフェードするプログレッシブエンハンスメントを追加する。

実装方式は「自前の薄いラッパ(案B)」を採用し、next-view-transitions等の外部
依存は追加しない。理由:
- 受入基準が機能検出ガード/reduced-motion無効化の単体テストを要求しており、ガード
  関数を自前で保持することで直接テスト可能になる(ライブラリ委譲では不可)
- framer-motion不採用方針(docs/design-system.md #1050)と同様にCSS駆動とし、JSは
  startViewTransition呼び出しのガードのみに限定してバンドルを軽量に保つ
- Next15/React19ネイティブView Transitionsへの載せ替えが容易(抽象が薄い)
- ライブラリ導入時のLink全面置換を避け、変更をnavシェルとプログラム遷移に限定

主な構成:
- src/lib/view-transitions: supportsViewTransitions/prefersReducedMotion/
  startViewTransition。非対応・reduced-motion時は即時遷移フォールバック
- ViewTransitionsProvider: startViewTransitionをApp Routerナビゲーションへ橋渡し。
  usePathnameのコミット監視で新DOM確定後にresolveし、同一ルート等の非コミット時は
  COMMIT_SAFETY_TIMEOUT_MS=500で確実にsettle
- TransitionLink / useViewTransitionRouter: Header・GlobalMobileNav・Sidebarで使用。
  修飾クリック/別タブ/外部/同一ルートはブラウザ既定にフォールスルー
- globals.css: [data-view-transition="content"](AppShellの<main>のみ)をクロス
  フェードし、永続シェル(ヘッダー/サイドバー/ボトムナビ)はrootを固定して除外。
  ::view-transition背景を--backgroundに設定しダークテーマの白フラッシュを防止。
  200ms(--motion-duration-base)/ --motion-ease-out準拠、reduced-motionで無効化

Refs #1122

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* perf(ui): HistoryPaneを@tanstack/react-virtualで仮想化し250件上限を撤廃 (#1123) (#1142)

* perf(ui): HistoryPaneを@tanstack/react-virtualで仮想化し250件上限を撤廃

履歴リスト(HistoryPane)を仮想化し、可視領域+オーバースキャンのみを
マウントするよう変更。従来はカードをフラット描画し、250件上限は性能緩和策
だった。仮想化により描画コストが O(可視行) となり、上限を1000へ緩和した。

主な変更:
- FilePanelContent の流儀に揃え useVirtualizer + measureElement(可変高)で
  ConversationPairCard を仮想描画。展開/折りたたみ・コードブロック等の高さ
  変化は measureElement が自動再計測。
- スクロール位置維持: 追記は react-virtual が自動維持。最下部閲覧中
  (isNearBottom)のみ新規ペアを追従(follow/maintain を分離)。
- 検索ジャンプ整合: 対象行を scrollToIndex で materialize してからハイライト
  適用。マウント窓の変化(renderedRange)でハイライトを再適用。
- カードのアンマウントで展開状態が飛ばないよう、展開状態はリスト側/親
  (useConversationHistory + 検索auto-expand)で保持(#1121 の pending 挿入も
  この item 管理の上に載せやすい構成)。
- 250件上限を緩和: HISTORY_DISPLAY_LIMIT_OPTIONS に 500/1000 を追加
  (MAX_MESSAGES_LIMIT=1000 に自動追従、APIの取得上限も1000へ)。
- src/lib/history-virtualization.ts: overscan/estimate/しきい値の定数と純粋な
  isNearBottom を分離しユニットテスト可能に。
- テスト: jsdom は無レイアウトのため offsetHeight を与える helper
  (tests/helpers/virtual-layout.ts)と1000件fixtureを追加。仮想化のDOM件数・
  追従/維持判定・展開折りたたみ・検索名前空間を網羅。

1000件fixture before/after(jsdom初期描画。体感FPSは手動検証):
- マウントカード数: 1000 → 16
- DOMノード数: 26,001 → 449(約58分の1)
- 初期描画時間: 289ms → 24ms(約12倍高速)

品質ゲート: lint / tsc --noEmit / test:unit(8855 passing) / build 全パス。

Refs #1123

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ui): HistoryPane仮想化のゼロ計測フォールバックで本文が消える問題を修正

WorktreeDetail経由の統合テスト(jsdomはレイアウト高さ0)で virtualizer が
アイテムを0件しか materialize せず、メッセージ本文が描画されず getByText が
失敗していた(tests/integration/worktree-detail-integration.test.tsx の
'renders user and assistant messages')。

修正: virtualizer がビューポート未計測でアイテム0件のとき(初回レンダー/SSR/
jsdom 等のレイアウト0環境)、先頭 HISTORY_FALLBACK_RENDER_COUNT 件を通常フロー
で描画するフォールバックを HistoryPane に追加。実ブラウザでは layout-effect の
計測直後に仮想化リストへ切り替わる(フォールバック描画は塗り前の一時コミット)。
本番の仮想化保証を損なわないよう件数は小さく固定。SSRのハイドレーション整合
(server/client双方が同じ先頭スライスを描画)にも寄与する。

単体テスト側は既存の virtual-layout ヘルパーで実 virtualizer 経路を検証し続ける
ため、この修正はそれらに影響しない。

品質ゲート: lint / tsc --noEmit / test:unit(8855) / test:integration(648) /
build 全パス。

Refs #1123

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(realtime): ターミナル出力・セッションステータスをWebSocketストリーミング化 (#1120) (#1143)

* feat(realtime): ターミナル出力・セッションステータスをWebSocketストリーミング化

実装済みだが未接続だったWebSocketインフラをクライアントへ接続し、ポーリングを
フォールバックとして残しつつサーバ→クライアントの一方向pushへ移行する。

- useWebSocket再設計: 単一WS接続・指数バックオフ再接続(1s..30s)・visibility連携・
  room購読再送・エンベロープparse。RealtimeProvider/useRealtimeで共有1本化+
  listener fan-out+購読refカウント(Provider未装着時はpollingフォールバック)
- セッションステータスpush: send時にrunning遷移をbroadcast、kill時のstopと合わせ
  サイドバー状態ドットを即時反映。WS接続中はuseWorktreesCacheのポーリングを間引き
- ターミナルストリーミング: response-poller tickからterminal_snapshotをpush
  (buildCurrentOutputでpull/push一致、購読者0でtmux captureをスキップ)。
  WS接続中はuseTerminalPanePollingを15sフォールバックへ間引き、version guardで
  stale破棄(既存stale-response/visibility堅牢性を維持)
- 差分適用: computeTerminalUpdateで追記/リセットを判定しTerminalDisplayを
  keyed chunk追記化、全置換を廃止してテキスト選択を維持(ANSI境界退避付き)
- モバイル新着出力バッジをpush駆動で復活、接続状態インジケータを追加
- 認証: Cookie自動付与でWS upgrade、認証失敗時は既存機構が接続拒否

テスト: useWebSocket(購読/再接続/バックオフ)・差分適用(追記/リセット/ANSI境界)・
WS切断→ポーリング復帰→WS再接続の統合テスト・認証拒否のE2E統合テストを追加。
lint/tsc/test:unit/test:integration/build 全パス。

Refs #1120

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(realtime): useRealtimeConnection等のNULバイト混入を除去

useRealtimeConnection.tsx の room購読キー生成で空白(0x20)が2箇所NULバイト
(0x00)に化けており、gitがバイナリ扱いしdiff/レビュー不能になっていた。
本来の ' ' 区切り(useWorktreesCache と同一パターン)へ復元し、ロジックは不変。

- src/hooks/useRealtimeConnection.tsx: `.join('\0')`/`.split('\0')` → `.join(' ')`/`.split(' ')`
- src/components/worktree/FileTreeView.tsx: 既存のセンチネル `'\0unavailable'` から
  NULを除去し `'unavailable'`(JSON.stringify結果と衝突しない性質は維持)

src/配下のNULバイト混入は0件を確認。lint/tsc/test:unit/test:integration/build 全パス。

Refs #1120

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(chat): メッセージ送信のOptimistic UIを実装 (#1144)

送信瞬間に pending 吹き出しを履歴末尾へ即時挿入し、サーバ echo で実データへ
照合置換(重複表示なし)、失敗時はエラー表示+再試行/破棄する Optimistic UI を
追加。

- usePendingMessages 層: serverMessages に pending をマージし ChatMessage
  (optimisticState 付き)として表示側へ渡す。content 一致+作成時 baseline 外+
  consumedServerIds の一意消費で照合(連投 1:1/既存同一文言の誤照合回避)。
  API reject/timeout(30s) で error→retry/discard。
- ConversationPairCard/HistoryPane: sending(opacity低下+スピナー)と
  error(danger トークン+Retry/Discard)を描画。pending 中は
  「Waiting for response…」を抑止。
- MessageInput: onOptimisticSend 提供時は送信を委譲し composer を即時クリア
  (draft も除去)。他サーフェスは従来の await→clear を維持。
- TerminalSplitPaneContent: 各 split の useSplitMessages に usePendingMessages を
  重ね、送信→即時表示→onSent refetch で照合。破棄時は既存 insert 経路で draft 復元。
- 仮想化リストと整合: pending も pair 化され virtualizer item として最下部追従。

テスト: usePendingMessages(挿入/照合/失敗/連投/タイムアウト/retry/discard)、
ConversationPairCard(sending/error/retry/discard)。

Refs #1121

* fix(mobile): useIsMobileを描画前レイアウトエフェクトで解決し初回デスクトップ描画フリップを解消 (#1145)

## 事象
useIsMobile がSSR安全のため初期値 false で始まり、実際の検出を
passive な useEffect(ペイント後実行)で行っていたため、モバイル実機の
ハードリロードで毎回デスクトップUIが一瞬描画されてからモバイルUIへ
フリップしていた(AppShell の固定サイドバー / WorktreeDetail の
デスクトップ⇔モバイル分岐でのフラッシュ・CLS)。

## 対策の選定(案A/B/C のトレードオフ)
- 案A(CSSで両ツリーを出し分け): 初回フレームは正しいが、重い
  WorktreeDetail が二重マウントし、両分岐の terminal ペインが同時に
  ポーリングを開始する。ヘビーツリーには不採用。
- 案B(UAヒント+cookie): ブレークポイントは viewport 幅基準
  (max-width:767px)で UA の mobile とは一致せず(狭幅デスクトップ/
  タブレット)、真の hydration mismatch を再導入する。不採用。
- 案C(採用・描画前レイアウトエフェクト版): SSR/初回クライアント
  レンダリングは決定的に false(デスクトップ)を返し SSR HTML と一致
  するため hydration mismatch 警告は出ない。検出は useLayoutEffect
  (isomorphic)で行い、commit 後・ブラウザ描画前に flush されるため、
  補正後の isMobile 値が最初のフレームに反映される。モバイルでは
  デスクトップツリーは描画される前に差し替えられる。

## ヘビーツリーの二重マウント / ポーリング配慮
- 主ポーラーは分岐に依存しない単一の useWorktreeDetailController が
  一度だけ生成する(分岐は表示のみ)。
- 非表示側の表示ツリーは同一 commit 内でペイント前に破棄されるため、
  passive な useEffect ベースの terminal ポーリングは発火しない。
- 主要なコールドロード経路(キャッシュ無し→LoadingIndicator)では
  どちらの分岐の子もマウントされない。

## その他
- SSR での useLayoutEffect 警告は isomorphic パターン
  (server では useEffect)で回避。
- 既存の useIsMobile 単体テスト(初期値・リサイズ切替)は不変で継続。
- hydrateRoot を用いた新規テストで「hydration mismatch 警告が出ない」
  「モバイルビューポートでモバイル値へ解決」「デスクトップは false 維持
  (回帰なし)」を自動検証。

品質ゲート: lint / tsc --noEmit / test:unit / test:integration / build 全て成功。

Refs #1126

* feat(pwa): PWA対応(manifest+Service Worker+インストール可能化) (#1146)

Web App Manifest(manifest.ts)、手書きService Worker、インストール可能化を
実装。「外出先からエージェントを監視する」プロダクト特性に沿ってホーム画面
から独立アプリ(standalone)として起動可能にする。

Service Worker技術選定: 手書きvanilla SW(next-pwa/Serwist/Workbox不採用)。
理由:
- custom server(server.ts)がWebSocket upgradeを保持しており、next-pwa等が
  前提とするデフォルトNextサーバ構成と噛み合わない
- 本Issueが要求するallowlist方式(キャッシュ対象を明示列挙)を直接表現でき、
  監査容易・小規模・後続#1125 Web Pushの拡張が容易

キャッシュ戦略(allowlist):
- /api・/login・/proxy・非GET・クロスオリジン → network-only(キャッシュ禁止)
- /_next/static/・/icons/・manifest/favicon → cache-first
- navigation → offline-fallback(認証コンテンツを避けページ本体は非キャッシュ)
判定はcache-policy.ts(単体テスト済み)を単一情報源とし、public/sw.jsが同定数を
鏡写し、sw-file.test.tsで両者の同期を担保。

更新戦略: install時はskipWaitingせず、更新検知でトースト表示→リロードで
SKIP_WAITINGを送信しcontrollerchangeで新版に切替。

その他:
- 本番のみSW登録(shouldRegisterServiceWorker)、dev/test・非セキュアは無効化
- オフラインフォールバック画面(/offline、トークン準拠、再接続ボタン)
- iOS standalone用にappleWebAppメタ(capable/title/status-bar-style)を付与
- manifest/sw/offlineをAUTH_EXCLUDED_PATHSに追加(auth有効時もinstallable/
  precache可、いずれも非機密リソース)
- HTTPS/LAN配信時のSW/インストール制限をREADMEに注記

Refs #1124

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(mobile): モバイルA11y改善(focus trap・44pxタップターゲット・tablistキーボード) (#1147)

モバイルのアクセシビリティ/エルゴノミクスの穴を塞ぐ。

focus trap は共通フック useFocusTrap に1実装集約:
- 判断理由: Modal/シート類は独自ポータル+enter/exitアニメ(useExitAnimation
  /usePromptAnimation)+スワイプ dismiss を持つ bespoke 実装。@radix-ui/
  react-dialog は未導入で、置換すると3面すべてとそのテストを書き直す必要がある。
  共通フックなら既存機構を壊さず trap のみ追加でき、issue の「1実装に集約」に沿う。
- 挙動: 初期フォーカスをダイアログへ移動 / Tab・Shift+Tab をダイアログ内で循環 /
  閉時に呼び出し元へフォーカス復帰。autoFocus 等で消費側が内部フォーカスを
  管理している場合は初期フォーカス奪取・復帰の両方をスキップ(ConfirmDialog 互換)。
- 適用: ui/Modal(role=dialog/aria-modal/aria-labelledby も付与)・
  MobilePromptSheet・MobileTerminalActionsSheet。

44px タップターゲット(視覚サイズは不変、ヒットエリアのみ拡大):
- エージェントインスタンスタブ(WorktreeDetailRefactored)
- History サブタブ Message/Git(WorktreeDetailMobile)
- MobileTabBar の各タブ

MobileTabBar に ARIA tabs パターン完成:
- 矢印キー(左右/上下)+Home/End 移動、ラップアラウンド
- roving tabindex(activeタブのみ tabindex=0)、選択はフォーカス追従

touch-manipulation をボタン系プリミティブ(ui/Button)とモバイル操作面へ付与し
ダブルタップズームの遅延を抑止。

title のみだった WorktreeCard のお気に入りトグル(アイコンのみ)へ aria-label を
付与し、タッチ/スクリーンリーダーからも到達可能に。

スワイプジェスチャー等は本Issueスコープ外のため変更していない。

Refs #1127

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(mobile): ジェスチャー強化(スワイプタブ切替・pull-to-refresh・キーボード追従) (#1148)

- useSwipeGesture に axis/方向ロック/エッジ開始制限/onSwipeMove を後方互換で追加
  (既存 MarkdownEditor の四方向スワイプと #299 スクロール抑制は不変)
- worktree詳細のモバイルタブを水平スワイプで切替。方向ロックで縦スクロール中は
  無効化、しきい値60px、ターミナルタブはエッジ開始領域に限定して誤発火を抑止。
  MobileTabBar インジケータは activeTab 共有で自動同期
- Sessions/Repositories に pull-to-refresh を新設(usePullToRefresh + PullToRefresh)。
  最上部のみ発火し overscroll-behavior:contain + preventDefault でネイティブPTR二重発火を防止
- MessageInput を useVirtualKeyboard に接続しキーボード追従(keyboardAware opt-in)、
  入力要素へ enterKeyHint/inputMode を付与
- MobilePromptSheet のアドホックスワイプを useSwipeGesture へ統合(focus trap ref を
  マージして #1127 の挙動を維持、finger-follow は onSwipeMove で継続)

Refs #1128

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(pwa): エージェント状態のWeb Push通知(プロンプト待ち/完了) (#1149)

- push_subscriptions テーブル(migration v41)と CRUD モジュールを追加
- VAPID/web-push 送信層: 種別フィルタ・デバウンス・410自動削除、機密(endpoint/秘密鍵)は非ログ
- プロンプト検出/セッション完了時に response-checker からファンアウト(検出ロジックは不変・fire-and-forget)
- 購読登録/解除/種別設定/VAPID公開鍵の認証必須API(グローバルmiddleware配下)
- Service Worker に push/notificationclick ハンドラ(該当worktreeへdeep link・…
Kewton added a commit that referenced this pull request Jul 17, 2026
* feat(files): スマホ版でファイルをダウンロード可能に(attachment配信+FileViewer DL導線)(#1024) (#1025)

スマホ版のファイルビューア(FileViewer, Issue #438 でモバイル専用化)から
任意種別のファイルをダウンロードできるようにする。

- B(サーバ): files/[...path] GET に download 分岐を追加し、生バイトを
  Content-Type: application/octet-stream + Content-Disposition: attachment で返却。
  filename は src/lib/http の content-disposition ヘルパーで RFC 準拠にサニタイズ
  (ASCII フォールバック + filename*=UTF-8'' パーセントエンコード、ヘッダ
  インジェクション回避)。既存 getWorktreeAndValidatePath でパス検証を再利用。
  base64 JSON を経由しないためプレビュー上限を超える大容量にも対応
- A(UI): FileViewer ツールバーにダウンロードボタンを追加し、同一オリジンの
  download URL へ遷移(iOS Safari 含めネイティブDLに乗せる)
- テスト新規29本(download API のヘッダ/パス検証/filename、content-disposition
  ヘルパー、FileViewer ボタン)
- C(独立ページのPDF分岐/DLリンク)は範囲外(モバイル動線が独立ページを使わないため)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(session): Timer 発火の送信を History に記録(send ロジックを共有サービス化)(#1028) (#1029)

Timer 機能(Issue #534)から送った指示が History(chat_messages)に残らない
問題を修正。executeTimer が cliTool.sendMessage だけを呼び createMessage/
startPolling をスキップしていたのが原因(#947 の委譲変更が起点)。

- send route L262-385 のインライン記録ロジックを src/lib/session/
  send-user-message.ts の共通関数 sendUserMessage() として抽出
  (savePendingAssistantResponse → send(画像/Copilot分岐含む)→ createMessage
  → updateLastUserMessage/orphan処理 → startPolling)。HTTP 層非依存
- send route POST を sendUserMessage() 利用へリファクタ(挙動不変)
- timer-manager.ts executeTimer を sendUserMessage() 経由に変更。
  timer_messages の status 遷移(sending/sent/failed)は維持
- テスト: sendUserMessage / executeTimer のユニット新規9件 + timer 既存更新(計26件)

これにより Timer 送信も user メッセージと assistant 応答の両方が History に残る。

スコープ外(別Issue推奨):
- tests/integration/api-send-cli-tool.test.ts の1件は base でも失敗する既存不具合
  (2引数アサート vs 3引数呼び出し)で本修正と無関係(CI の test:unit 対象外)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(todo): ブランチ単位ToDoを3状態化(未着手/仕掛/完了)(#1032) (#1033)

- migration v38: worktree_todos に status カラム追加(done=1→'done' backfill、CURRENT_SCHEMA_VERSION 37→38)
- DB層: status を真実源に done := status==='done' を派生(後方互換維持、writer整合)
- API/クライアント: PATCH で status ∈ {todo,doing,done} を検証・適用、done後方互換
- UI: 3状態巡回チップ+色バッジ+状態別件数、TodoPane全体を next-intl 化
- i18n: locales/{en,ja}/worktree.json に todo namespace 追加
- テスト: 既存5更新+新規 migration-v38(backfill検証)

Closes #1032

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(todo): ブランチ単位ToDoに詳細フィールド追加+クリックで件名/詳細を編集 (#1034) (#1035)

- migration v39: worktree_todos に detail カラム追加(TEXT NOT NULL DEFAULT ''、既存行は空詳細で移行)
- DB層: WorktreeTodo に detail 追加、create/update で detail 受理
- API: POST/PATCH で detail を検証・適用(MAX_TODO_DETAIL_LENGTH 新設)
- クライアント: WorktreeTodoItem.detail、create/update の detail 対応
- UI: 項目クリックで編集モーダル(Modal.tsx再利用)を開き件名+詳細を編集、詳細ありインジケータ
- i18n: locales/{en,ja}/worktree.json に詳細/編集の文言追加
- テスト: 既存6更新+新規 migration-v39、docs/module-reference.md 更新

Closes #1034

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(todo): ブランチ単位ToDoの詳細にコピーボタンを追加 (#1036) (#1037)

- UI: 編集モーダルの詳細欄に CopyButton を配置し詳細本文をコピー(詳細が空のときは非表示)
- 既存 src/components/common/CopyButton.tsx / clipboard-utils.ts を再利用(スマホHTTPは execCommand フォールバック)
- i18n: locales/{en,ja}/worktree.json に todo.copyDetail 追加
- テスト: TodoPane.test.tsx にコピー導線テスト3件追加
- migration/DB/API 変更なし(UIのみ)

Closes #1036

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): セマンティックデザイントークン基盤を導入 (#1041) (#1054)

globals.css に :root(light)/.dark(dark) の CSS 変数
(background/foreground/surface/muted/border/input/ring/accent-50..700/
success/warning/danger/info) を定義し、tailwind.config.js の primary(cyan)・
cmd-bg-dark を semantic トークンへ置換。layout.tsx / MainLayout.tsx の
body 背景を bg-background に統一。既存の実効値を写し取り視覚は不変(no-op)。
docs/design-system.md を新設。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): cn()/cva によるバリアント管理基盤とプリミティブ改修 (#1042) (#1055)

clsx + tailwind-merge の cn() ヘルパーと class-variance-authority を導入し、
Button/Card/Badge/Modal を cva ベースへ移行。Button ghost バリアントの
dark: 指定漏れ(ダークモードで視認性劣化)を修正。既存 API は完全互換。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): アイコンを lucide-react に統一 (#1044) (#1056)

絵文字アイコン(🤖⚡✦💻)を廃止し lucide アイコンへ置換。共通レイアウト・ナビ
(Header/GlobalMobileNav/MobileTabBar/ActivityBar/terminal page)および worktree
各所のインライン SVG を lucide に統一。docs/design-system.md にアイコンサイズ規約を追記。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): next/font で Geist Sans/Mono を導入 (#1043) (#1057)

geist パッケージ(セルフホスト)で GeistSans/GeistMono の CSS 変数を <html> に適用し、
tailwind fontFamily の sans/mono を「Geist → 日本語(Hiragino/Noto Sans JP) →
システム」フォールバックで構成。Terminal.tsx(xterm)の等幅も Geist Mono を先頭指定。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): RadixベースUIプリミティブを拡充 (#1046) (#1058)

Input/Textarea/Select/Tabs/Tooltip/DropdownMenu/Switch/Skeleton を追加
(cva + cn, セマンティックトークン着色, ライト/ダーク対応)。Portal 系は
'use client' + Z_INDEX(POPOVER)で Modal と階層整合。Sessions ページの
フィルタ/ソートを新プリミティブへ移行(適用サンプル)。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): アクセント色を統一しセマンティックトークンへ置換 (#1045) (#1059)

cyan/blue 直書き(~600箇所 / 131ファイル)を #1041 のセマンティックトークンへ置換。
用途判定: インタラクティブ=accent / フォーカス=ring / 情報=info。gray 系は
surface/background/border/muted へ実効値を保って置換。例外(Terminal.tsx の
xterm ANSI 配色 / CLI ブランド色 / .scrollbar-thin 等の固定装飾色)は維持。
globals.css の .btn-primary・検索ハイライト等もトークン化。
敵対的レビュー指摘(focus:ring-accent→focus:ring-ring 統一、design-system.md
の例外記述修正)を反映。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): 主要ページを共通UIプリミティブへ移行 (#1047) (#1060)

layout(Header/Sidebar の検索・トグル・言語)/ home / repositories / review /
more / chat / login の生button・手組みフォーム・コピペカードを @/components/ui
プリミティブへ移行。機能・挙動(onClick/フォーム送信/バリデーション/キーボード)は維持。
敵対的レビュー指摘(chat系ボタン移行・ghost の dark hover)を反映。ネイティブ select・
複合 textarea 入力バー・極小アイコントグルは意図的に残置(PR の残置リスト参照)。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): globals.css の @apply クラスを廃止し cva プリミティブを自己完結化 (#1048) (#1062)

.btn/.btn-*/.card/.card-hover/.badge*/.input を globals.css から削除し、
Button/Card/Badge の cva 定義へ実クラスを内挿(旧 @apply 定義と同値、視覚不変)。
src/ 全体で削除クラスへの参照が 0 であることを確認。worktree の一部
(WorktreeDetailRefactored/LogViewer/WorktreeList/ExternalAppForm)をプリミティブへ移行し、
マジックナンバー入りインライン style を定数化。worktree/mobile/sidebar/common の
一括プリミティブ移行は規模の都合により #1061 へ分割。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): サーフェス・奥行きを再設計しモダンダークテーマへ刷新 (#1049) (#1063)

globals.css の :root/.dark トークン値を刷新(深い base + 微リフト surface +
ヘアライン border)。Header/GlobalMobileNav に backdrop-blur(supports-[backdrop-filter]
フォールバック付)。Card に elevated/interactive variant、h1-h4 見出しタイポを調整。
敵対的レビュー指摘を反映: 主要閲覧画面のコンテンツカード(sessions/home/review)を
bg-surface/border-border へ移行し背景反転による溶け込みを解消、ダーク入力を surface-2 で
凹み表現、Card interactive にキーボード focus 対応を追加。コントラストは WCAG AA 全 pass。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): モーション基盤とマイクロインタラクションを追加 (#1050) (#1064)

tailwindcss-animate を導入し、Modal/MobilePromptSheet の enter/exit、Radix data-state
連動(DropdownMenu/Select/Tooltip)、一覧の stagger、hover lift を統一適用。
prefers-reduced-motion 対応。ターミナル/仮想スクロールには非適用。敵対レビュー指摘
(reduced-motionでのtranslate漏れ→motion-safe化、sessionsエラー時のstagger再発火→list
マウント維持、Tooltip instant-open enter、home focus parity)を反映。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): Home をダッシュボード(bento グリッド)化 (#1052) (#1065)

Home を縦積みから bento グリッドへ再構成(統計/直近セッション/ToDo/クイックアクション)。
PC は CSS Grid(grid-cols-12 + col-span)、モバイルは1カラム縦積み(useIsMobile の JS 分岐を
増やさず Tailwind breakpoint)。既存 sessions 取得フック/API を再利用し新規 API なし。既存機能
(ToDo 追加/チェック/コピー、セッションカウント)を維持。空状態対応。敵対的レビュー指摘
(ToDo タイルの h-full・見出しパリティ)を反映。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): コマンドパレット(⌘K)を追加 (#1053) (#1066)

cmdk で ⌘K(mac)/Ctrl+K(win) のコマンドパレットを追加(Navigation / Worktrees 検索 / Actions)。
input/textarea/ターミナル入力中は非発火、SSR 安全('use client')、i18n(en/ja)、Modal 相当の z-index。
モバイルは GlobalMobileNav に起動導線。敵対的レビュー指摘を反映: open 時に入力へフォーカスし
キーボード操作(type/矢印/Enter)を復旧[HIGH]、worktree 一覧は共有キャッシュ context を再利用
(最新化+失敗時 Navigation fallback)、Escape の preventDefault/stopPropagation と close 時の
フォーカス復帰。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): エージェント/セッションステータスの視覚を強化 (#1051) (#1067)

StatusDot(running/waiting/idle/error)を新設。running はグロー+パルス+静的リング halo、
waiting は amber の弱点滅。サイドバー(BranchStatusIndicator)/ Home / Sessions を StatusDot 化。
prefers-reduced-motion でパルス停止(静的リングで running≠ready を維持)、ポーリングで再発火しない
CSS infinite アニメ。敵対的レビュー指摘(reduced-motion での running/ready 識別性、docs の実態訂正)を
反映。worktree 詳細/MobileHeader は従来スピナー表示のまま(docs に明記・今後の追随対象)。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ui): サイドバーがヘッダーに遮蔽される左上レイアウト破綻を修正 (#1070) (#1083)

showGlobalNav 時に固定サイドバーを top-16 h-[calc(100vh-4rem)] にオフセットし、
半透明ヘッダー(z-50, backdrop-blur)がサイドバー先頭64px(Branches 見出し+
view/sort/sync ツールバー行)を遮蔽してクリック不能+灰色の滲みを生む問題を解消。
あわせて border-gray-200/dark:border-gray-600 を border-border にトークン化。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): テーマ既定を system 化しヘッダーにトグルを追加 (#1071) (#1084)

next-themes を defaultTheme="system" enableSystem に変更し、ライト環境ユーザーへの
強制ダークを解消。Header 右クラスタに ThemeToggle を追加(発見可能性向上)。
globals.css に color-scheme(:root=light / .dark=dark)を宣言し、残存ネイティブ
コントロールの OS クローム事故(dark で白いドロップダウン等)を防止。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(hooks): useIsMobile を matchMedia ベースにし混成レイアウトバグを解消 (#1069) (#1085)

window.innerWidth 依存を window.matchMedia(`(max-width: ${breakpoint-1}px)`)に置換。
innerWidth と CSS ビューポートが乖離する環境(実測 innerWidth=846 vs
documentElement.clientWidth=390)では、Tailwind の md:(min-width:768px)と JS 分岐が
食い違い「CSS はモバイル・JS はデスクトップ」の混成レイアウトになっていた。
max-width:breakpoint-1 は md: の正確な補集合。change イベント購読+cleanup、
SSR 初期値 false、MOBILE_BREAKPOINT export と breakpoint オプションは維持。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): Home 第一フォールドを回収(バナー削除・見出し降格・tabular-nums) (#1072) (#1086)

陳腐化した Welcome バナー(#600 のナビ再編告知)を削除。h1「CommandMate」を機能的見出し
(HomeHeading: Overview + ライブサブライン)へ降格しヘッダーのワードマークとの同語反復を解消。
Home の数値・相対時刻・サイドバーのブランチ数バッジに tabular-nums を導入。HomeQuickActions の
inline SVG を lucide へ置換。相対時刻の短縮形は新設 date-utils、見出し文言は home i18n
namespace(en/ja)。機能(ToDo/カウント/遷移)は不変、サブラインは既存キャッシュから算出。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ライトモードのエレベーション階梯を反転(グレー地+白カード+2層影) (#1074) (#1087)

light を「白ページ+凹む灰カード」から Vercel/Linear 型の「グレー地ページ+
白カード+2層影」へ反転し、dark(#1049)と対のエレベーションモデルにする。
surface-2(740) < background(751) < surface(765) の「白カードが浮き、well が沈む」階梯。

- globals.css :root: --background 255→250 250 251 / --surface 248 250 252→255 255 255
  / --surface-2 241 245 249→244 246 250。.dark は不変(回帰なし)
- tailwind.config.js: 2層 boxShadow.sm を新規追加(md/lg は Tailwind 既定のまま)
- design-tokens/surface-depth テスト更新 + WCAG コントラスト検証を追加
  (foreground≥4.5 全面 / muted-fg≥4.5 page,card / ≥3 recessed well)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): サイドバーを --sidebar-* トークン化しテーマ追従に (#1073) (#1088)

bg-gray-800 text-white 固定でテーマ孤島だったサイドバーを、新設の
--sidebar / --sidebar-foreground / --sidebar-border / --sidebar-hover /
--sidebar-muted セマンティックトークンへ移行。light=slate-50系の明るいパネル、
dark=#141821(#1049階梯と整合)でテーマに追従する。standalone なリテラルRGB値
としており、#1074 の --surface 反転がサイドバー色へ滲まない。

- globals.css :root/.dark に --sidebar-* を定義、tailwind.config.js に登録
- Sidebar.tsx / sidebar/*(BranchListItem, SortSelectorBase)/ 検索Input
  / LocaleSwitcher / ThemeToggle の直書きを全てトークンへ置換
- 敵対的レビュー由来のライト回帰修正2件:
  - LogoutButton.tsx: 漏れていた text-gray-300 直書きを ThemeToggle と同じ
    テーマ中立トークン(text-muted-foreground/hover:bg-muted)へ(モバイル共用)
  - SortSelectorBase: 選択中ソートを text-accent-700 dark:text-accent-400 に
    (ライトのslate-50上で 1.73→約5.1:1 でAA達成、dark時は据え置き)
- docs/design-system.md に --sidebar-* を追記、既存テスト/期待値を更新

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ⌘K パレットを Raycast 級に強化(アイコン/StatusDot/kbd/Recents/ヘッダー導線)(#1077) (#1089)

- src/components/ui/Kbd.tsx を新設(semantic-token ベース、#1080 で再利用)し ui/index.ts に export 追加
- NAV 行に lucide アイコン、worktree 行に GitBranch + StatusDot(isProcessing/isWaitingForResponse/isSessionRunning から算出)
- バックドロップ/パネル/選択状態/入力/フッター文字色を semantic トークン化(bg-surface/backdrop-blur/ring-border/accent-500)
- キーボードガイドのフッター(↑↓/↵/esc、i18n)
- Recent グループ(localStorage cm.palette.recents、MRU<=8、空クエリ時のみ、dead worktree は無視)
- worktree を isSessionRunning 優先→updatedAt 降順ソート+空クエリ時 slice(0,8)
- Actions 拡充: Sync repositories(repositoryApi.sync + Toast)/ 言語切替 / Open GitHub
- Header に ⌘K 検索ピル(mount 後に platform 判定して ⌘K/Ctrl K を出し分け)
- #1053 の挙動契約(フォーカス/入力ガード/Escape/backdrop/data-testid)を維持
- 文言を locales/{en,ja}/commandPalette.json に追加、単体テスト更新

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ターミナルクロームを刷新(全周枠廃止・カード化・CLIセレクタDropdownMenu化)(#1079) (#1090)

worktree ターミナルの全周枠を廃し surface-2 カード+ヘアラインのカード化に統一。
CLI インスタンスセレクタを native <select> から Radix DropdownMenu(StatusDot+alias
+ChevronDown)へ置換し、value/onChange の単一選択セマンティクスと testid を保持。

- Terminal / ActivityBar / TerminalDisplay / TerminalSplitContainer /
  TerminalSplitPane / TerminalSplitPaneContent を surface/border トークンへ
- TerminalSplitPane: <select> → DropdownMenu(RadioGroup)。cli-selector testid 保持
- xterm の theme は意図的に固定ダーク(--surface-2 dark 相当)を維持
- StatusDot/status-colors.ts は不変(#1078 の領域)。locales worktree.json 追記
- 既存 terminal テスト(cli-tab-switching 含む)更新・全パス

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): worktree詳細/Chat のダーク直書き島を surface トークン化しテーマ追従に (#1075) (#1091)

HistoryPane / ConversationPairCard / AssistantMessageList / AssistantChatPanel /
HistorySearchBar / CopyButton と .assistant-md の常時ダーク直書きを surface/foreground
セマンティックトークンへ移行し、light/dark 両テーマで正しく描画されるようにする。

- globals.css .assistant-md: リンクを accent-700 dark:accent-400、code/pre を明色地に
  し両テーマ AA≥4.5:1。:root/.dark のトークン定義は不変
- 各コンテナ+全子孫を surface/surface-2/foreground/muted-foreground/border トークンへ
  (hidden-children 回避: 子の直書き light-on-dark を残さない)
- AssistantChatPanel/HistoryPane の native select/checkbox は色のみトークン化
  (Radix 化は #1076 の担当のため据え置き)
- ConversationPairCard: コピー/挿入/展開ツールバーの hover-reveal 化に伴い、
  タッチ端末で不可視化する回帰を [@media(hover:none)]:opacity-100 で解消
  (デスクトップの hover-reveal は維持。copy-user/assistant-message testid 保持)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): データ画面コントロールの意味論を整理(Review/Repositories/More)(#1081) (#1092)

表示のみの調整。絞り込み・編集・削除・可視性トグルのロジックは不変。

- Review: フィルタを CTA 風の primary/secondary ボタンから、件数付きの
  控えめなセグメンテッドコントロールへ(選択は bg-accent-500/15、CTA と区別)。
  件数は既存のフィルタ済み配列長から算出(新規 fetch なし)。
- Repositories: Enabled/Disabled バッジと可視性トグルの緑ピルを StatusDot 流の
  dot+muted テキストへ(トグルは操作可能なまま)。(none)→ muted の em-dash、
  パスは truncate+font-mono+full-path tooltip、Edit は ghost な Pencil アイコンボタン。
- More: ExternalAppsManager 側の重複 h2「External Apps」を削除(ページ側に一本化)。
- StatusDot/status-colors.ts は不変(#1078 の領域)。生 gray/cyan/blue の追加なし。
- 既存テスト更新+件数バッジ/単一見出しの検証テストを追加。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): コンポーザーをカード化しモバイルworktreeコントロールを整理 (#1080) (#1093)

- MessageInput をコンポーザーカード化(surface/border/shadow/focus-ring)し、送信
  ボタンを入力有無で filled(bg-accent-600)/ghost に切替。プレースホルダを i18n 短縮
- Auto-Yes をコンポーザー下辺のメタ行へ統合(ui/Switch 化)。右側に Kbd キーヒント
  (#1077 の ui/Kbd 再利用)。InterruptButton のオレンジ直値を text-danger ghost へ
- モバイル sticky 行をインスタンスタブ専用化(dark: バリアント+下線インジケータ)。
  検索/End を MobileTerminalActionsSheet(ボトムシート、End は確認付き)へ移設
- MobileTabBar を GlobalMobileNav 様式(bg-background/backdrop-blur/border-border、
  上辺インジケータ、Wrench アイコン、success/warning バッジ)へ統一
- テスト: 送信ボタン状態変化/新規シートを追加、ResizeObserver polyfill(Radix Switch)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): 稼働ステータスを StatusDot 一本化し idle ノイズを縮退 (#1078) (#1094)

青スピナー(border-info)と生 span を全て統一 StatusDot に置換し、稼働ステータスの
視覚言語を1系統に。idle ノイズを縮退(Sessions は running/waiting のみラベル付き、
idle は「+N idle」カウンタ / worktree ヘッダーは idle を StatusDot のみの円形ボタン
+Tooltip、幅超過は「+N」DropdownMenu オーバーフロー)。

- WorktreeDetailSubComponents / sessions/page / MobileHeader を StatusDot へ統一
- 新規 lib/agent-status-display.ts に classifyHeaderInstances 等の導出を集約
- status-colors.ts の type:'spinner'/border-info は @deprecated 化(ReviewTab が
  最後の消費者。消滅後に削除)
- 相対時刻 tabular-nums / プレビューは CSS truncate、DnD(handleAgentDragStart)と
  タブ切替 onClick は全ボタンで温存
- 敵対レビュー由来の仕上げ: 「+N」オーバーフローに稼働中(running/waiting)があれば
  集約 StatusDot グローを出し、稼働セッションが glow-less に埋もれないように。
  同 trigger の生グレーをセマンティックトークンへ

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ネイティブ checkbox/radio を Radix プリミティブに統一 (#1076) (#1095)

OS 標準の白いフォームコントロールが dark に浮く問題を解消。@radix-ui/react-checkbox
/ react-radio-group ベースの ui/Checkbox・ui/RadioGroup を新設し、残存していた
ネイティブ input(checkbox 12箇所/11ファイル・radio 6箇所/4ファイル)を全て移行。

- 新規 src/components/ui/{Checkbox,RadioGroup}.tsx(cn()+cva・セマンティックトークン・
  focus-visible:ring-ring)、ui/index.ts に export、依存2つを追加
- checked/onCheckedChange・value/onValueChange への適応、name/value/disabled/
  aria/data-testid とラベルクリックのトグルを全移行で保持
- 移行後 native type="checkbox"/"radio" は src/ 配下 0 件(完了性 grep 検証済)
- 敵対レビュー(binding/primitive-a11y/GitDangerZonePanel 密集)0件

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): トークン規律の確立(生gray/slate置換・focus-visible統一・CIガード)(#1082) (#1096)

移行済み領域の生 gray/slate 直書きをセマンティックトークンへ置換し、フォーカス表現を
focus-visible に統一、CI grep ガードでトークン違反の再流入を止める。機能・挙動は不変
(見た目/規律のみ)。

## プリミティブ是正
- Button: secondary/ghost を muted/foreground、danger を bg-danger トークンへ。
  `focus:` → `focus-visible:` + `ring-offset-background`(dark の白ハロー解消。Tabs/Switch が手本)。
- Input/Textarea/Select トリガー: `focus:` → `focus-visible:`(枠が反応するため offset なし)。
- Badge gray → bg-muted/text-muted-foreground、Modal → surface/border/foreground。

## in-scope スイープ(生 gray/slate → トークン)
- app(worktrees を除く)/ ui / layout / home / review / repository / common。
- gray-100/200→muted、gray-400/500/600→muted-foreground、gray-700/800/900→foreground、
  境界→border、フォーム枠→input。

## globals.css / metadata / docs
- body→text-foreground、.prose インラインコード→muted/foreground、scrollbar-thin をトークン化
  (--input / --muted-foreground)、`::selection` にアクセント淡色を追加。
- layout.tsx: title template(`%s | CommandMate`)+ viewport themeColor(light/dark)。
- docs/design-system.md にフォーカス表現・CI ガード・例外リストを同期。

## CI ガード
- `.github/workflows/ci-pr.yml` に `token-discipline` ジョブを追加(CLAUDE.md size check と同方式)。
  ホワイトリスト方式で移行済みディレクトリ限定 hard-fail。除外リストをスクリプト内に明記。
  違反注入時に fail することを確認済み。

## 判断メモ(要レビュー)
- `src/app/worktrees/**`(40 occ)はオーケストレーター集計では app(108) に含まれるが、
  ワークツリー領域(#1061・「触らない」)かつ常時ダークのターミナルページ/CLI ブランド色
  (bash=bg-gray-600)を含むため、スイープ・ガード双方から除外した。#1061 完了時に拡大。
- 軽微な見た目変化: danger の red-600→red-500(--danger)、secondary/ghost の muted シェード、
  SidebarToggle はテーマ追従化、ツールチップは反転サーフェス(bg-foreground/text-background)へ
  テーマ追従化、Home の Save&Resend ボタンを accent-600 + 白文字に正規化。

品質ゲート(lint / tsc --noEmit / test:unit / build)+ 新 CI ガード 全パス。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree/共通の生gray/buttonをトークン・プリミティブへ (#1061 群A 1/4) (#1097)

#1061 のファイル分割並列(群A: 21ファイル)。生 gray/slate Tailwind クラスを
#1040/#1082 のセマンティックトークンへ用途判定で置換、生 <button> を ui/Button へ。
挙動・ハンドラ・testid は不変。群A担当ファイルの raw gray = 0。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree詳細/markdown/fileの生gray/buttonをトークンへ (#1061 群B 2/4) (#1100)

#1061 分割並列(群B: 21ファイル)。WorktreeDetailSubComponents/MarkdownToolbar/
FileTreeView 等の生 gray/slate → セマンティックトークン、生 button → ui/Button。
ターミナル出力表示面(TerminalSplitPaneContent)はダーク維持のため意図的に非トークン化。
挙動・testid 不変。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree/mobile/gitの生gray/buttonをトークンへ (#1061 群C 3/4) (#1098)

#1061 のファイル分割並列(群C: 22ファイル)。生 gray/slate → セマンティックトークン、
生 <button> → ui/Button。挙動・testid 不変。群C担当ファイルの raw gray = 0。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree/git/todoの生gray/buttonをトークンへ (#1061 群D 4/4) (#1099)

#1061 分割並列(群D: 21ファイル)。生 gray/slate → セマンティックトークン、生 button
→ ui/Button。ターミナル出力表示面(TerminalSplitContainer/TerminalContainer)は
xterm と同様ダーク維持のため意図的に非トークン化(除外)。挙動・testid 不変。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci(guard): token-discipline ガードを worktree/mobile/external-apps へ拡大 (#1061) (#1101)

#1061 で worktree/mobile/external-apps の生 gray/slate をトークン化完了したため、
#1082 で導入した token-discipline CIガードの対象ディレクトリを拡大。
ターミナル出力表示面(*Terminal* ソース)は xterm 固定ダーク(#1079)と同様の
意図的ダーク島のため除外。app/worktrees も引き続き除外。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: integration スイートの drift を解消(モック/期待値追随・CI追加)(#1102) (#1103)

CI 非対象で長期 drift していた integration スイート(41 failed)を修復。実装バグを
隠さないよう vi.mock を importOriginal 方式へ書き換え、欠落 export を補完。

- 不完全 vi.mock を importOriginal ベースへ(欠落 export を実挙動ごと通す)
- config ドリフト(アップロード上限 5MB→20MB 等)の期待値を現行定数準拠へ
- undefined.length 系はモック戻り値を実 shape へ
- 再 drift 防止に ci-pr.yml へ Integration Tests ジョブ追加
- external-apps duplicate name → 500(期待409) は実装バグのため it.skip で quarantine

結果: test:integration 643 passed / 1 skipped(0 failed)。unit/lint/tsc 緑維持。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(external-apps): 重複nameで500ではなく409を返す (#1104) (#1105)

* fix(external-apps): 重複nameで500ではなく409を返す (#1104)

route.ts の catch を message.includes('UNIQUE constraint') 文字列判定から
ExternalAppDbError.code === 'DUPLICATE' の型/コード判定へ変更。DB層のエラー
ラップ後もメッセージに依存せず 409 を正しく返す。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(external-apps): 409 duplicate name テストを un-skip(#1104 で修正済み)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(timer): Timer履歴の詳細モーダル表示と失敗理由の記録 (#1107) (#1109)

Schedule 側の execution_logs 方式を Timer へ横展開。

- DB: migration v40 で timer_messages に error 列(nullable)追加、schema 39→40
- timer-db: error 列の read/write 貫通、updateTimerStatus に optional error 引数、recoverStuckSendingTimers に固定リカバリ理由
- timer-manager: 送信失敗(stage/error)・例外(error.message)を error 列へ永続化(no_session は保存せず)
- API: GET timers レスポンスに error を追加
- UI: TimerDetailModal を新設し履歴行クリックで指示全文+メタ+失敗理由を表示(PC=Modal / mobile=FullScreenModal)
- i18n: schedule.json の timer ブロックに en/ja lockstep でラベル追加

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(files): Filesツリー展開状態のworktree単位永続化と表示リセット (#1108) (#1111)

useFileTabs / useFilePanelState の worktree 単位 localStorage パターンを踏襲。

- 新規 useFileTreeExpandedState: 展開集合を worktree 単位 localStorage(キー commandmate:file-tree-expanded:<worktreeId>) に永続化。lazy-init 復元をマウント再フェッチに乗せ、空集合はキー削除、worktree 間の漏洩なし
- useFileTabs: CLOSE_ALL / closeAllTabs() を追加
- FileTreeView: フック統合+ツールバーに「表示をリセット」ボタン(更新ボタンの隣・常時可視)+ onResetView prop
- Controller/Desktop/Mobile/Refactored: resetFileTreeView(検索クリア・全タブ閉じ・モバイルビューア閉)を配線し、初期表示に戻すフルリセットを実現

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(mobile): viewportFit:'cover' を追加しiOS safe-area対応を有効化 (#1132)

- src/app/layout.tsx の viewport エクスポートに viewportFit: 'cover' を追加。
  iOS は viewport-fit=cover 指定時のみ env(safe-area-inset-*) に非0値を
  入れるため、既存の pt-safe/pb-safe 等の safe-area 対応が実効化される
- GlobalMobileNav の未定義クラス safe-area-bottom を globals.css 定義済みの
  pb-safe に修正(cover 有効化後もパディングが効かない潜在バグ)
- viewport エクスポート値(viewportFit / themeColor)のユニットテストを追加

レンダリング検証: 本番ビルドで <meta name="viewport"> に
viewport-fit=cover が含まれることを確認済み

Refs #1131

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat(ui): ステータスカラーtintトークン導入とダーク非対応フィードバックUIの移行 (#1133)

- globals.css / tailwind.config.js に success/warning/danger/info × subtle/border/foreground の12トークンを両テーマで追加(ライト *-50 面 + *-800 前景、ダーク *-950 面 + *-300 前景、全て WCAG AA 準拠。根拠はコメントに記載)
- Toast / DefaultErrorFallback / History・Prompt・ConnectionErrorFallback / ErrorDisplay / PromptPanel を新トークンへ移行し、生パレット + dark: ペアを撤去(TerminalErrorFallback は常時ダーク島のため現状維持)
- tint 面内のソリッドアクションボタンは反転 tint(bg-{status}-foreground text-{status}-subtle)で両テーマ AA を確保
- docs/design-system.md にトークン表・使用ルールを追記
- tint トークン定義・移行 grep を検証する status-tint-tokens.test.ts と ErrorBoundary/フォールバックのレンダリングテストを追加、既存 Toast / design-tokens テストを追随

Refs #1112

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* refactor(ui): window.confirmを共通ConfirmDialogへ全面置換 (#1134)

- ConfirmDialog/ConfirmProvider/useConfirm を src/components/ui/ConfirmDialog.tsx に新設
  (variant default/danger、Escape/背景クリックでキャンセル、danger時はcancelに初期フォーカス、
  クローズ後に呼び出し元へフォーカス復帰)
- AppProviders に ConfirmProvider をマウント
- window.confirm 残存11箇所 + bare confirm 1箇所(WorktreeCard)を useConfirm へ置換
- confirm文言を next-intl 辞書へ移動 (review namespace 新設、home/worktree/common に追記)
- ESLint no-restricted-globals(confirm/alert) と no-restricted-properties(window.confirm) を追加
  (bare alert 3箇所は window.alert へ変更、alert置換自体は対象外)
- 既存テストを window.confirm モックからダイアログ操作へ追随、
  ConfirmDialog 単体テストと Timer履歴クリアの結合フローを追加

Refs #1113

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* chore(ui): UI死コード削除(WorktreeList・放棄infinite-scroll hooks・hasNewOutput配線) (#1135)

- WorktreeList.tsx(production importゼロの孤児コンポーネント)とbarrel export(index.ts)を削除
- 放棄された無限スクロール実装(useInfiniteMessages/useScrollObserver/useScrollRestoration)と
  対応テスト3本、孤児化するtypes/infinite-messages.tsを削除
- WorktreeDetailRefactored.tsxのhasNewOutput={false}死配線を撤去(MobileTabBar側は維持)
- 残置ファイルのstaleコメント修正(useVisibilityRecovery.ts/history-display-config.ts)
- useWebSocket.tsに@deprecated注記を付与(WebSocketストリーミング化で再設計・再利用予定)
- docs/architecture.md(ja/en)の削除対象ファイルへのstale参照行を削除

受入基準: 3グレップ全て0件成立、lint/tsc/test:unit(8746 pass)/build全パス

Refs #1115

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* fix(ui): 未定義animate-fade-*の定義とModal/Toast/ContextMenuのexitアニメーション実装 (#1114) (#1136)

* fix(ui): 未定義animate-fade-*の定義とModal/Toast/ContextMenuのexitアニメーション実装

- tailwind.config.jsにfade-in/fade-outキーフレームとanimate-fade-in/outを定義。
  usePromptAnimationが発行していたが未定義でno-opだった。クラス名を
  PromptPanel等が文字列比較で消費しているため、tailwindcss-animateへの
  書き換えではなくキーフレーム定義の追加を採用。duration/easingは
  --motion-duration-base / --motion-ease-out(#1050モーショントークン)準拠
- content globsにsrc/hooksを追加(フック発行クラスのpurge防止)
- useExitAnimation(open, duration)フックを新設: 閉要求後もduration msだけ
  マウントを維持しexitアニメーション再生を保証。render-phase同期により
  開閉ともに1フレームの遅延なし。再open時はタイマー解除
- Modal: data-state=closedでfade+zoom-out exit(200ms)後にアンマウント。
  exit中はbackdropクリック無効化
- Toast: fade+slide-out exit(200ms)再生後にonClose通知(アンマウント遅延)
- ContextMenu: fade+zoom-out exit(100ms、enterと同duration)。
  exit中はpointer-events-none
- EXIT_ANIMATION_DURATION_MS / CONTEXT_MENU_EXIT_DURATION_MSを
  ui-feedback-config.tsに追加しCSSとJSタイマーを同期
- reduced-motionはglobals.cssのグローバルキルスイッチで対応(CSS実装のみのため追加ガード不要)
- テスト: useExitAnimation単体、Modal/Toast/ContextMenuのexit挙動
  (閉操作後にアニメーション時間経過までDOM残留→アンマウント)、
  Tailwind実コンパイルによるCSS生成検証を追加

Refs #1114

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ui): ConfirmDialogをModal exitアニメーションに追随

- ConfirmDialog.test.tsx: 閉操作後の即時DOM除去期待を、200ms exit
  アニメーション完了後のアンマウント(waitForElementToBeRemoved)に更新
- ConfirmProvider: pendingクリア後のexit再生中にdescription等が空文字に
  なり内容がブランク表示される統合ドリフトを修正(直近optionsを保持し
  フェードアウト中も表示を維持)。cancelテストにブランク防止の検証を追加

Refs #1114

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat(ui): ヘッダーナビのアクティブインジケータ実装と角丸スケール規約の導入 (#1137)

- Header ナビに CSS-only の下線インジケータを実装(after 疑似要素の
  scale-x トランジション、--motion-ease-out / duration-200 準拠、
  motion-safe ガード)。アクティブ項目に aria-current="page" を付与
- Header のアクティブ状態レンダリングテストを追加(全 6 ルート)
- docs/design-system.md に角丸スケール規約を独立セクションとして追記
  (コントロール=rounded-md / コンテナ=rounded-lg / ポップアップ=rounded-md /
  チップ=rounded-full / 小型インライン=rounded-sm、裸 rounded 原則禁止)

角丸統一の実施範囲(明確な逸脱のみ、feature 配下の一括置換はしない):
- ui/Kbd・ui/Checkbox: 裸 rounded → rounded-sm
- layout/Sidebar のアイコンボタン3箇所: 裸 rounded → rounded-md
- layout/Header のコマンドパレットトリガー: rounded-lg → rounded-md

Refs #1119

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* refactor(ui): チャット履歴UIをConversationPairCard系デザインへ統一 (#1138)

MessageList/PromptMessageの視覚言語をConversationPairCardに統一
(案B: 構造・optimistic updateロジック維持、見た目のみ刷新)。

- 生chromatic色21箇所をステータスtintトークン(warning/success/info x
  subtle/border/foreground)へ移行し、生色のdark:ペアを撤去
- インラインSVGをlucide-reactへ統一(Check/X/CircleCheck/Clock/Loader2/
  MessageCircle)
- ユーザー/アシスタントメッセージをCPC言語のセクション(accent左ボーダー/
  surface-2 wash)+ hover-revealコピーツールバーへ刷新
  ([@media(hover:none)]:opacity-100 タッチフォールバック付き)
- markdown描画をprose→assistant-md(トークン追従)へ移行、ANSI/コード
  ブロックの常時ダーク島(#0d1117)は現状維持
- ファイルパスリンク/プロンプト応答(Yes/No・選択肢・テキスト入力)/
  ログリンクの機能パリティ維持、レンダリングテストを新規追加

Refs #1117

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat(ui): スケルトンローディング展開とSpinner統一・loading.tsx整備 (#1139)

- ui/Spinner.tsx 新設: 手書きインラインSVG/border系スピナー43箇所を共通プリミティブへ置換
- 主要画面にレイアウト整合スケルトンを実装(Home Session Overview/ToDo、Sessions一覧、Repositories一覧、HistoryPane(汎用Card型・loading分岐のみ)、ファイルツリー、AssistantChatPanel、Reviewタブ群)
- 裸テキストローディングをスケルトンへ置換(MessageList.tsxは#1117衝突回避のため対象外)
- 初回ロードのみスケルトン表示(再fetch時は既存コンテンツ維持の非ブロッキングパターン踏襲)
- 主要7ルートに loading.tsx(薄いRouteLoadingフォールバック)を追加
- AssistantChatPanelのローディングを導出状態化し空状態フラッシュを解消

Refs #1118

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* refactor(ui): 生chromatic色をtintトークンへ全面移行しCIガードを拡大 (#1140)

意味ベースでステータスtintトークンへ移行 (red→danger / green→success /
yellow・amber・orange→warning / blue・sky・purple・violet→info)。
淡色面は bg-{s}-subtle + border-{s}-border + text-{s}-foreground、
ソリッドは bg-{s} を使用し、chromatic用途の dark: ペアを撤去。

- ui/Badge.tsx・config/status-colors.ts をトークン化 (生パレットクラス撤去)
- error/ の常時ダーク島 TerminalErrorFallback を *Terminal* 名の別ファイルへ分離し、
  残る error/・auth/ の生gray/chromatic漏れを回収
- 検索ハイライト/ファイル種別アイコン等の装飾色もトークン化 (text-pink等スコープ外色は維持)
- CIガード token-discipline の grep パターンに chromatic 9色を追加し error/・auth/ を対象化。
  既存の *Terminal*・テスト・常時ダーク島 (worktrees/[id]/terminal 等) の除外は維持
- docs/design-system.md に chromatic 禁止/例外リストとCIガード仕様を追記
- 影響する class 検証テストを追随
  (Badge / ContextMenu / MarkdownEditor / MemoCard / dark-mode-foundation / recursive-delete)

chromatic grep 残数
(git grep -cE '(bg|text|border|ring)-(red|green|yellow|amber|orange|purple|violet|sky|blue)-[0-9]' -- src/):
  before: 254 行 (71 ファイル)
  after:  19 行 (サンクション済み例外のみ = *Terminal* 群 + src/app/worktrees/[id]/terminal/)
  in-scope (例外除く): 0

品質ゲート: npm run lint / npx tsc --noEmit / npm run test:unit (8818 passed) / npm run build 全パス

Refs #1116

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): View Transitions APIによるページ遷移クロスフェードを導入 (#1141)

主要ルート間(Home/Chat/Sessions/Repos/Review/More/worktree詳細)のコンテンツ
領域をクロスフェードするプログレッシブエンハンスメントを追加する。

実装方式は「自前の薄いラッパ(案B)」を採用し、next-view-transitions等の外部
依存は追加しない。理由:
- 受入基準が機能検出ガード/reduced-motion無効化の単体テストを要求しており、ガード
  関数を自前で保持することで直接テスト可能になる(ライブラリ委譲では不可)
- framer-motion不採用方針(docs/design-system.md #1050)と同様にCSS駆動とし、JSは
  startViewTransition呼び出しのガードのみに限定してバンドルを軽量に保つ
- Next15/React19ネイティブView Transitionsへの載せ替えが容易(抽象が薄い)
- ライブラリ導入時のLink全面置換を避け、変更をnavシェルとプログラム遷移に限定

主な構成:
- src/lib/view-transitions: supportsViewTransitions/prefersReducedMotion/
  startViewTransition。非対応・reduced-motion時は即時遷移フォールバック
- ViewTransitionsProvider: startViewTransitionをApp Routerナビゲーションへ橋渡し。
  usePathnameのコミット監視で新DOM確定後にresolveし、同一ルート等の非コミット時は
  COMMIT_SAFETY_TIMEOUT_MS=500で確実にsettle
- TransitionLink / useViewTransitionRouter: Header・GlobalMobileNav・Sidebarで使用。
  修飾クリック/別タブ/外部/同一ルートはブラウザ既定にフォールスルー
- globals.css: [data-view-transition="content"](AppShellの<main>のみ)をクロス
  フェードし、永続シェル(ヘッダー/サイドバー/ボトムナビ)はrootを固定して除外。
  ::view-transition背景を--backgroundに設定しダークテーマの白フラッシュを防止。
  200ms(--motion-duration-base)/ --motion-ease-out準拠、reduced-motionで無効化

Refs #1122

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* perf(ui): HistoryPaneを@tanstack/react-virtualで仮想化し250件上限を撤廃 (#1123) (#1142)

* perf(ui): HistoryPaneを@tanstack/react-virtualで仮想化し250件上限を撤廃

履歴リスト(HistoryPane)を仮想化し、可視領域+オーバースキャンのみを
マウントするよう変更。従来はカードをフラット描画し、250件上限は性能緩和策
だった。仮想化により描画コストが O(可視行) となり、上限を1000へ緩和した。

主な変更:
- FilePanelContent の流儀に揃え useVirtualizer + measureElement(可変高)で
  ConversationPairCard を仮想描画。展開/折りたたみ・コードブロック等の高さ
  変化は measureElement が自動再計測。
- スクロール位置維持: 追記は react-virtual が自動維持。最下部閲覧中
  (isNearBottom)のみ新規ペアを追従(follow/maintain を分離)。
- 検索ジャンプ整合: 対象行を scrollToIndex で materialize してからハイライト
  適用。マウント窓の変化(renderedRange)でハイライトを再適用。
- カードのアンマウントで展開状態が飛ばないよう、展開状態はリスト側/親
  (useConversationHistory + 検索auto-expand)で保持(#1121 の pending 挿入も
  この item 管理の上に載せやすい構成)。
- 250件上限を緩和: HISTORY_DISPLAY_LIMIT_OPTIONS に 500/1000 を追加
  (MAX_MESSAGES_LIMIT=1000 に自動追従、APIの取得上限も1000へ)。
- src/lib/history-virtualization.ts: overscan/estimate/しきい値の定数と純粋な
  isNearBottom を分離しユニットテスト可能に。
- テスト: jsdom は無レイアウトのため offsetHeight を与える helper
  (tests/helpers/virtual-layout.ts)と1000件fixtureを追加。仮想化のDOM件数・
  追従/維持判定・展開折りたたみ・検索名前空間を網羅。

1000件fixture before/after(jsdom初期描画。体感FPSは手動検証):
- マウントカード数: 1000 → 16
- DOMノード数: 26,001 → 449(約58分の1)
- 初期描画時間: 289ms → 24ms(約12倍高速)

品質ゲート: lint / tsc --noEmit / test:unit(8855 passing) / build 全パス。

Refs #1123

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ui): HistoryPane仮想化のゼロ計測フォールバックで本文が消える問題を修正

WorktreeDetail経由の統合テスト(jsdomはレイアウト高さ0)で virtualizer が
アイテムを0件しか materialize せず、メッセージ本文が描画されず getByText が
失敗していた(tests/integration/worktree-detail-integration.test.tsx の
'renders user and assistant messages')。

修正: virtualizer がビューポート未計測でアイテム0件のとき(初回レンダー/SSR/
jsdom 等のレイアウト0環境)、先頭 HISTORY_FALLBACK_RENDER_COUNT 件を通常フロー
で描画するフォールバックを HistoryPane に追加。実ブラウザでは layout-effect の
計測直後に仮想化リストへ切り替わる(フォールバック描画は塗り前の一時コミット)。
本番の仮想化保証を損なわないよう件数は小さく固定。SSRのハイドレーション整合
(server/client双方が同じ先頭スライスを描画)にも寄与する。

単体テスト側は既存の virtual-layout ヘルパーで実 virtualizer 経路を検証し続ける
ため、この修正はそれらに影響しない。

品質ゲート: lint / tsc --noEmit / test:unit(8855) / test:integration(648) /
build 全パス。

Refs #1123

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(realtime): ターミナル出力・セッションステータスをWebSocketストリーミング化 (#1120) (#1143)

* feat(realtime): ターミナル出力・セッションステータスをWebSocketストリーミング化

実装済みだが未接続だったWebSocketインフラをクライアントへ接続し、ポーリングを
フォールバックとして残しつつサーバ→クライアントの一方向pushへ移行する。

- useWebSocket再設計: 単一WS接続・指数バックオフ再接続(1s..30s)・visibility連携・
  room購読再送・エンベロープparse。RealtimeProvider/useRealtimeで共有1本化+
  listener fan-out+購読refカウント(Provider未装着時はpollingフォールバック)
- セッションステータスpush: send時にrunning遷移をbroadcast、kill時のstopと合わせ
  サイドバー状態ドットを即時反映。WS接続中はuseWorktreesCacheのポーリングを間引き
- ターミナルストリーミング: response-poller tickからterminal_snapshotをpush
  (buildCurrentOutputでpull/push一致、購読者0でtmux captureをスキップ)。
  WS接続中はuseTerminalPanePollingを15sフォールバックへ間引き、version guardで
  stale破棄(既存stale-response/visibility堅牢性を維持)
- 差分適用: computeTerminalUpdateで追記/リセットを判定しTerminalDisplayを
  keyed chunk追記化、全置換を廃止してテキスト選択を維持(ANSI境界退避付き)
- モバイル新着出力バッジをpush駆動で復活、接続状態インジケータを追加
- 認証: Cookie自動付与でWS upgrade、認証失敗時は既存機構が接続拒否

テスト: useWebSocket(購読/再接続/バックオフ)・差分適用(追記/リセット/ANSI境界)・
WS切断→ポーリング復帰→WS再接続の統合テスト・認証拒否のE2E統合テストを追加。
lint/tsc/test:unit/test:integration/build 全パス。

Refs #1120

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(realtime): useRealtimeConnection等のNULバイト混入を除去

useRealtimeConnection.tsx の room購読キー生成で空白(0x20)が2箇所NULバイト
(0x00)に化けており、gitがバイナリ扱いしdiff/レビュー不能になっていた。
本来の ' ' 区切り(useWorktreesCache と同一パターン)へ復元し、ロジックは不変。

- src/hooks/useRealtimeConnection.tsx: `.join('\0')`/`.split('\0')` → `.join(' ')`/`.split(' ')`
- src/components/worktree/FileTreeView.tsx: 既存のセンチネル `'\0unavailable'` から
  NULを除去し `'unavailable'`(JSON.stringify結果と衝突しない性質は維持)

src/配下のNULバイト混入は0件を確認。lint/tsc/test:unit/test:integration/build 全パス。

Refs #1120

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(chat): メッセージ送信のOptimistic UIを実装 (#1144)

送信瞬間に pending 吹き出しを履歴末尾へ即時挿入し、サーバ echo で実データへ
照合置換(重複表示なし)、失敗時はエラー表示+再試行/破棄する Optimistic UI を
追加。

- usePendingMessages 層: serverMessages に pending をマージし ChatMessage
  (optimisticState 付き)として表示側へ渡す。content 一致+作成時 baseline 外+
  consumedServerIds の一意消費で照合(連投 1:1/既存同一文言の誤照合回避)。
  API reject/timeout(30s) で error→retry/discard。
- ConversationPairCard/HistoryPane: sending(opacity低下+スピナー)と
  error(danger トークン+Retry/Discard)を描画。pending 中は
  「Waiting for response…」を抑止。
- MessageInput: onOptimisticSend 提供時は送信を委譲し composer を即時クリア
  (draft も除去)。他サーフェスは従来の await→clear を維持。
- TerminalSplitPaneContent: 各 split の useSplitMessages に usePendingMessages を
  重ね、送信→即時表示→onSent refetch で照合。破棄時は既存 insert 経路で draft 復元。
- 仮想化リストと整合: pending も pair 化され virtualizer item として最下部追従。

テスト: usePendingMessages(挿入/照合/失敗/連投/タイムアウト/retry/discard)、
ConversationPairCard(sending/error/retry/discard)。

Refs #1121

* fix(mobile): useIsMobileを描画前レイアウトエフェクトで解決し初回デスクトップ描画フリップを解消 (#1145)

## 事象
useIsMobile がSSR安全のため初期値 false で始まり、実際の検出を
passive な useEffect(ペイント後実行)で行っていたため、モバイル実機の
ハードリロードで毎回デスクトップUIが一瞬描画されてからモバイルUIへ
フリップしていた(AppShell の固定サイドバー / WorktreeDetail の
デスクトップ⇔モバイル分岐でのフラッシュ・CLS)。

## 対策の選定(案A/B/C のトレードオフ)
- 案A(CSSで両ツリーを出し分け): 初回フレームは正しいが、重い
  WorktreeDetail が二重マウントし、両分岐の terminal ペインが同時に
  ポーリングを開始する。ヘビーツリーには不採用。
- 案B(UAヒント+cookie): ブレークポイントは viewport 幅基準
  (max-width:767px)で UA の mobile とは一致せず(狭幅デスクトップ/
  タブレット)、真の hydration mismatch を再導入する。不採用。
- 案C(採用・描画前レイアウトエフェクト版): SSR/初回クライアント
  レンダリングは決定的に false(デスクトップ)を返し SSR HTML と一致
  するため hydration mismatch 警告は出ない。検出は useLayoutEffect
  (isomorphic)で行い、commit 後・ブラウザ描画前に flush されるため、
  補正後の isMobile 値が最初のフレームに反映される。モバイルでは
  デスクトップツリーは描画される前に差し替えられる。

## ヘビーツリーの二重マウント / ポーリング配慮
- 主ポーラーは分岐に依存しない単一の useWorktreeDetailController が
  一度だけ生成する(分岐は表示のみ)。
- 非表示側の表示ツリーは同一 commit 内でペイント前に破棄されるため、
  passive な useEffect ベースの terminal ポーリングは発火しない。
- 主要なコールドロード経路(キャッシュ無し→LoadingIndicator)では
  どちらの分岐の子もマウントされない。

## その他
- SSR での useLayoutEffect 警告は isomorphic パターン
  (server では useEffect)で回避。
- 既存の useIsMobile 単体テスト(初期値・リサイズ切替)は不変で継続。
- hydrateRoot を用いた新規テストで「hydration mismatch 警告が出ない」
  「モバイルビューポートでモバイル値へ解決」「デスクトップは false 維持
  (回帰なし)」を自動検証。

品質ゲート: lint / tsc --noEmit / test:unit / test:integration / build 全て成功。

Refs #1126

* feat(pwa): PWA対応(manifest+Service Worker+インストール可能化) (#1146)

Web App Manifest(manifest.ts)、手書きService Worker、インストール可能化を
実装。「外出先からエージェントを監視する」プロダクト特性に沿ってホーム画面
から独立アプリ(standalone)として起動可能にする。

Service Worker技術選定: 手書きvanilla SW(next-pwa/Serwist/Workbox不採用)。
理由:
- custom server(server.ts)がWebSocket upgradeを保持しており、next-pwa等が
  前提とするデフォルトNextサーバ構成と噛み合わない
- 本Issueが要求するallowlist方式(キャッシュ対象を明示列挙)を直接表現でき、
  監査容易・小規模・後続#1125 Web Pushの拡張が容易

キャッシュ戦略(allowlist):
- /api・/login・/proxy・非GET・クロスオリジン → network-only(キャッシュ禁止)
- /_next/static/・/icons/・manifest/favicon → cache-first
- navigation → offline-fallback(認証コンテンツを避けページ本体は非キャッシュ)
判定はcache-policy.ts(単体テスト済み)を単一情報源とし、public/sw.jsが同定数を
鏡写し、sw-file.test.tsで両者の同期を担保。

更新戦略: install時はskipWaitingせず、更新検知でトースト表示→リロードで
SKIP_WAITINGを送信しcontrollerchangeで新版に切替。

その他:
- 本番のみSW登録(shouldRegisterServiceWorker)、dev/test・非セキュアは無効化
- オフラインフォールバック画面(/offline、トークン準拠、再接続ボタン)
- iOS standalone用にappleWebAppメタ(capable/title/status-bar-style)を付与
- manifest/sw/offlineをAUTH_EXCLUDED_PATHSに追加(auth有効時もinstallable/
  precache可、いずれも非機密リソース)
- HTTPS/LAN配信時のSW/インストール制限をREADMEに注記

Refs #1124

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(mobile): モバイルA11y改善(focus trap・44pxタップターゲット・tablistキーボード) (#1147)

モバイルのアクセシビリティ/エルゴノミクスの穴を塞ぐ。

focus trap は共通フック useFocusTrap に1実装集約:
- 判断理由: Modal/シート類は独自ポータル+enter/exitアニメ(useExitAnimation
  /usePromptAnimation)+スワイプ dismiss を持つ bespoke 実装。@radix-ui/
  react-dialog は未導入で、置換すると3面すべてとそのテストを書き直す必要がある。
  共通フックなら既存機構を壊さず trap のみ追加でき、issue の「1実装に集約」に沿う。
- 挙動: 初期フォーカスをダイアログへ移動 / Tab・Shift+Tab をダイアログ内で循環 /
  閉時に呼び出し元へフォーカス復帰。autoFocus 等で消費側が内部フォーカスを
  管理している場合は初期フォーカス奪取・復帰の両方をスキップ(ConfirmDialog 互換)。
- 適用: ui/Modal(role=dialog/aria-modal/aria-labelledby も付与)・
  MobilePromptSheet・MobileTerminalActionsSheet。

44px タップターゲット(視覚サイズは不変、ヒットエリアのみ拡大):
- エージェントインスタンスタブ(WorktreeDetailRefactored)
- History サブタブ Message/Git(WorktreeDetailMobile)
- MobileTabBar の各タブ

MobileTabBar に ARIA tabs パターン完成:
- 矢印キー(左右/上下)+Home/End 移動、ラップアラウンド
- roving tabindex(activeタブのみ tabindex=0)、選択はフォーカス追従

touch-manipulation をボタン系プリミティブ(ui/Button)とモバイル操作面へ付与し
ダブルタップズームの遅延を抑止。

title のみだった WorktreeCard のお気に入りトグル(アイコンのみ)へ aria-label を
付与し、タッチ/スクリーンリーダーからも到達可能に。

スワイプジェスチャー等は本Issueスコープ外のため変更していない。

Refs #1127

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(mobile): ジェスチャー強化(スワイプタブ切替・pull-to-refresh・キーボード追従) (#1148)

- useSwipeGesture に axis/方向ロック/エッジ開始制限/onSwipeMove を後方互換で追加
  (既存 MarkdownEditor の四方向スワイプと #299 スクロール抑制は不変)
- worktree詳細のモバイルタブを水平スワイプで切替。方向ロックで縦スクロール中は
  無効化、しきい値60px、ターミナルタブはエッジ開始領域に限定して誤発火を抑止。
  MobileTabBar インジケータは activeTab 共有で自動同期
- Sessions/Repositories に pull-to-refresh を新設(usePullToRefresh + PullToRefresh)。
  最上部のみ発火し overscroll-behavior:contain + preventDefault でネイティブPTR二重発火を防止
- MessageInput を useVirtualKeyboard に接続しキーボード追従(keyboardAware opt-in)、
  入力要素へ enterKeyHint/inputMode を付与
- MobilePromptSheet のアドホックスワイプを useSwipeGesture へ統合(focus trap ref を
  マージして #1127 の挙動を維持、finger-follow は onSwipeMove で継続)

Refs #1128

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(pwa): エージェント状態のWeb Push通知(プロンプト待ち/完了) (#1149)

- push_subscriptions テーブル(migration v41)と CRUD モジュールを追加
- VAPID/web-push 送信層: 種別フィルタ・デバウンス・410自動削除、機密(endpoint/秘密鍵)は非ログ
- プロンプト検出/セッション完了時に response-checker からファンアウト(検出ロジックは不変・fire-and-forget)
- 購読登録/解除/種別設定/VAPID公開鍵の認証必須API(グローバルmiddleware配下)
- Service Worker に push/notificationclick ハンドラ(該当worktreeへdeep link・…
Kewton added a commit that referenced this pull request Jul 17, 2026
* feat(files): スマホ版でファイルをダウンロード可能に(attachment配信+FileViewer DL導線)(#1024) (#1025)

スマホ版のファイルビューア(FileViewer, Issue #438 でモバイル専用化)から
任意種別のファイルをダウンロードできるようにする。

- B(サーバ): files/[...path] GET に download 分岐を追加し、生バイトを
  Content-Type: application/octet-stream + Content-Disposition: attachment で返却。
  filename は src/lib/http の content-disposition ヘルパーで RFC 準拠にサニタイズ
  (ASCII フォールバック + filename*=UTF-8'' パーセントエンコード、ヘッダ
  インジェクション回避)。既存 getWorktreeAndValidatePath でパス検証を再利用。
  base64 JSON を経由しないためプレビュー上限を超える大容量にも対応
- A(UI): FileViewer ツールバーにダウンロードボタンを追加し、同一オリジンの
  download URL へ遷移(iOS Safari 含めネイティブDLに乗せる)
- テスト新規29本(download API のヘッダ/パス検証/filename、content-disposition
  ヘルパー、FileViewer ボタン)
- C(独立ページのPDF分岐/DLリンク)は範囲外(モバイル動線が独立ページを使わないため)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(session): Timer 発火の送信を History に記録(send ロジックを共有サービス化)(#1028) (#1029)

Timer 機能(Issue #534)から送った指示が History(chat_messages)に残らない
問題を修正。executeTimer が cliTool.sendMessage だけを呼び createMessage/
startPolling をスキップしていたのが原因(#947 の委譲変更が起点)。

- send route L262-385 のインライン記録ロジックを src/lib/session/
  send-user-message.ts の共通関数 sendUserMessage() として抽出
  (savePendingAssistantResponse → send(画像/Copilot分岐含む)→ createMessage
  → updateLastUserMessage/orphan処理 → startPolling)。HTTP 層非依存
- send route POST を sendUserMessage() 利用へリファクタ(挙動不変)
- timer-manager.ts executeTimer を sendUserMessage() 経由に変更。
  timer_messages の status 遷移(sending/sent/failed)は維持
- テスト: sendUserMessage / executeTimer のユニット新規9件 + timer 既存更新(計26件)

これにより Timer 送信も user メッセージと assistant 応答の両方が History に残る。

スコープ外(別Issue推奨):
- tests/integration/api-send-cli-tool.test.ts の1件は base でも失敗する既存不具合
  (2引数アサート vs 3引数呼び出し)で本修正と無関係(CI の test:unit 対象外)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(todo): ブランチ単位ToDoを3状態化(未着手/仕掛/完了)(#1032) (#1033)

- migration v38: worktree_todos に status カラム追加(done=1→'done' backfill、CURRENT_SCHEMA_VERSION 37→38)
- DB層: status を真実源に done := status==='done' を派生(後方互換維持、writer整合)
- API/クライアント: PATCH で status ∈ {todo,doing,done} を検証・適用、done後方互換
- UI: 3状態巡回チップ+色バッジ+状態別件数、TodoPane全体を next-intl 化
- i18n: locales/{en,ja}/worktree.json に todo namespace 追加
- テスト: 既存5更新+新規 migration-v38(backfill検証)

Closes #1032

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(todo): ブランチ単位ToDoに詳細フィールド追加+クリックで件名/詳細を編集 (#1034) (#1035)

- migration v39: worktree_todos に detail カラム追加(TEXT NOT NULL DEFAULT ''、既存行は空詳細で移行)
- DB層: WorktreeTodo に detail 追加、create/update で detail 受理
- API: POST/PATCH で detail を検証・適用(MAX_TODO_DETAIL_LENGTH 新設)
- クライアント: WorktreeTodoItem.detail、create/update の detail 対応
- UI: 項目クリックで編集モーダル(Modal.tsx再利用)を開き件名+詳細を編集、詳細ありインジケータ
- i18n: locales/{en,ja}/worktree.json に詳細/編集の文言追加
- テスト: 既存6更新+新規 migration-v39、docs/module-reference.md 更新

Closes #1034

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(todo): ブランチ単位ToDoの詳細にコピーボタンを追加 (#1036) (#1037)

- UI: 編集モーダルの詳細欄に CopyButton を配置し詳細本文をコピー(詳細が空のときは非表示)
- 既存 src/components/common/CopyButton.tsx / clipboard-utils.ts を再利用(スマホHTTPは execCommand フォールバック)
- i18n: locales/{en,ja}/worktree.json に todo.copyDetail 追加
- テスト: TodoPane.test.tsx にコピー導線テスト3件追加
- migration/DB/API 変更なし(UIのみ)

Closes #1036

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): セマンティックデザイントークン基盤を導入 (#1041) (#1054)

globals.css に :root(light)/.dark(dark) の CSS 変数
(background/foreground/surface/muted/border/input/ring/accent-50..700/
success/warning/danger/info) を定義し、tailwind.config.js の primary(cyan)・
cmd-bg-dark を semantic トークンへ置換。layout.tsx / MainLayout.tsx の
body 背景を bg-background に統一。既存の実効値を写し取り視覚は不変(no-op)。
docs/design-system.md を新設。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): cn()/cva によるバリアント管理基盤とプリミティブ改修 (#1042) (#1055)

clsx + tailwind-merge の cn() ヘルパーと class-variance-authority を導入し、
Button/Card/Badge/Modal を cva ベースへ移行。Button ghost バリアントの
dark: 指定漏れ(ダークモードで視認性劣化)を修正。既存 API は完全互換。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): アイコンを lucide-react に統一 (#1044) (#1056)

絵文字アイコン(🤖⚡✦💻)を廃止し lucide アイコンへ置換。共通レイアウト・ナビ
(Header/GlobalMobileNav/MobileTabBar/ActivityBar/terminal page)および worktree
各所のインライン SVG を lucide に統一。docs/design-system.md にアイコンサイズ規約を追記。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): next/font で Geist Sans/Mono を導入 (#1043) (#1057)

geist パッケージ(セルフホスト)で GeistSans/GeistMono の CSS 変数を <html> に適用し、
tailwind fontFamily の sans/mono を「Geist → 日本語(Hiragino/Noto Sans JP) →
システム」フォールバックで構成。Terminal.tsx(xterm)の等幅も Geist Mono を先頭指定。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): RadixベースUIプリミティブを拡充 (#1046) (#1058)

Input/Textarea/Select/Tabs/Tooltip/DropdownMenu/Switch/Skeleton を追加
(cva + cn, セマンティックトークン着色, ライト/ダーク対応)。Portal 系は
'use client' + Z_INDEX(POPOVER)で Modal と階層整合。Sessions ページの
フィルタ/ソートを新プリミティブへ移行(適用サンプル)。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): アクセント色を統一しセマンティックトークンへ置換 (#1045) (#1059)

cyan/blue 直書き(~600箇所 / 131ファイル)を #1041 のセマンティックトークンへ置換。
用途判定: インタラクティブ=accent / フォーカス=ring / 情報=info。gray 系は
surface/background/border/muted へ実効値を保って置換。例外(Terminal.tsx の
xterm ANSI 配色 / CLI ブランド色 / .scrollbar-thin 等の固定装飾色)は維持。
globals.css の .btn-primary・検索ハイライト等もトークン化。
敵対的レビュー指摘(focus:ring-accent→focus:ring-ring 統一、design-system.md
の例外記述修正)を反映。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): 主要ページを共通UIプリミティブへ移行 (#1047) (#1060)

layout(Header/Sidebar の検索・トグル・言語)/ home / repositories / review /
more / chat / login の生button・手組みフォーム・コピペカードを @/components/ui
プリミティブへ移行。機能・挙動(onClick/フォーム送信/バリデーション/キーボード)は維持。
敵対的レビュー指摘(chat系ボタン移行・ghost の dark hover)を反映。ネイティブ select・
複合 textarea 入力バー・極小アイコントグルは意図的に残置(PR の残置リスト参照)。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): globals.css の @apply クラスを廃止し cva プリミティブを自己完結化 (#1048) (#1062)

.btn/.btn-*/.card/.card-hover/.badge*/.input を globals.css から削除し、
Button/Card/Badge の cva 定義へ実クラスを内挿(旧 @apply 定義と同値、視覚不変)。
src/ 全体で削除クラスへの参照が 0 であることを確認。worktree の一部
(WorktreeDetailRefactored/LogViewer/WorktreeList/ExternalAppForm)をプリミティブへ移行し、
マジックナンバー入りインライン style を定数化。worktree/mobile/sidebar/common の
一括プリミティブ移行は規模の都合により #1061 へ分割。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): サーフェス・奥行きを再設計しモダンダークテーマへ刷新 (#1049) (#1063)

globals.css の :root/.dark トークン値を刷新(深い base + 微リフト surface +
ヘアライン border)。Header/GlobalMobileNav に backdrop-blur(supports-[backdrop-filter]
フォールバック付)。Card に elevated/interactive variant、h1-h4 見出しタイポを調整。
敵対的レビュー指摘を反映: 主要閲覧画面のコンテンツカード(sessions/home/review)を
bg-surface/border-border へ移行し背景反転による溶け込みを解消、ダーク入力を surface-2 で
凹み表現、Card interactive にキーボード focus 対応を追加。コントラストは WCAG AA 全 pass。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): モーション基盤とマイクロインタラクションを追加 (#1050) (#1064)

tailwindcss-animate を導入し、Modal/MobilePromptSheet の enter/exit、Radix data-state
連動(DropdownMenu/Select/Tooltip)、一覧の stagger、hover lift を統一適用。
prefers-reduced-motion 対応。ターミナル/仮想スクロールには非適用。敵対レビュー指摘
(reduced-motionでのtranslate漏れ→motion-safe化、sessionsエラー時のstagger再発火→list
マウント維持、Tooltip instant-open enter、home focus parity)を反映。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): Home をダッシュボード(bento グリッド)化 (#1052) (#1065)

Home を縦積みから bento グリッドへ再構成(統計/直近セッション/ToDo/クイックアクション)。
PC は CSS Grid(grid-cols-12 + col-span)、モバイルは1カラム縦積み(useIsMobile の JS 分岐を
増やさず Tailwind breakpoint)。既存 sessions 取得フック/API を再利用し新規 API なし。既存機能
(ToDo 追加/チェック/コピー、セッションカウント)を維持。空状態対応。敵対的レビュー指摘
(ToDo タイルの h-full・見出しパリティ)を反映。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): コマンドパレット(⌘K)を追加 (#1053) (#1066)

cmdk で ⌘K(mac)/Ctrl+K(win) のコマンドパレットを追加(Navigation / Worktrees 検索 / Actions)。
input/textarea/ターミナル入力中は非発火、SSR 安全('use client')、i18n(en/ja)、Modal 相当の z-index。
モバイルは GlobalMobileNav に起動導線。敵対的レビュー指摘を反映: open 時に入力へフォーカスし
キーボード操作(type/矢印/Enter)を復旧[HIGH]、worktree 一覧は共有キャッシュ context を再利用
(最新化+失敗時 Navigation fallback)、Escape の preventDefault/stopPropagation と close 時の
フォーカス復帰。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): エージェント/セッションステータスの視覚を強化 (#1051) (#1067)

StatusDot(running/waiting/idle/error)を新設。running はグロー+パルス+静的リング halo、
waiting は amber の弱点滅。サイドバー(BranchStatusIndicator)/ Home / Sessions を StatusDot 化。
prefers-reduced-motion でパルス停止(静的リングで running≠ready を維持)、ポーリングで再発火しない
CSS infinite アニメ。敵対的レビュー指摘(reduced-motion での running/ready 識別性、docs の実態訂正)を
反映。worktree 詳細/MobileHeader は従来スピナー表示のまま(docs に明記・今後の追随対象)。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ui): サイドバーがヘッダーに遮蔽される左上レイアウト破綻を修正 (#1070) (#1083)

showGlobalNav 時に固定サイドバーを top-16 h-[calc(100vh-4rem)] にオフセットし、
半透明ヘッダー(z-50, backdrop-blur)がサイドバー先頭64px(Branches 見出し+
view/sort/sync ツールバー行)を遮蔽してクリック不能+灰色の滲みを生む問題を解消。
あわせて border-gray-200/dark:border-gray-600 を border-border にトークン化。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): テーマ既定を system 化しヘッダーにトグルを追加 (#1071) (#1084)

next-themes を defaultTheme="system" enableSystem に変更し、ライト環境ユーザーへの
強制ダークを解消。Header 右クラスタに ThemeToggle を追加(発見可能性向上)。
globals.css に color-scheme(:root=light / .dark=dark)を宣言し、残存ネイティブ
コントロールの OS クローム事故(dark で白いドロップダウン等)を防止。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(hooks): useIsMobile を matchMedia ベースにし混成レイアウトバグを解消 (#1069) (#1085)

window.innerWidth 依存を window.matchMedia(`(max-width: ${breakpoint-1}px)`)に置換。
innerWidth と CSS ビューポートが乖離する環境(実測 innerWidth=846 vs
documentElement.clientWidth=390)では、Tailwind の md:(min-width:768px)と JS 分岐が
食い違い「CSS はモバイル・JS はデスクトップ」の混成レイアウトになっていた。
max-width:breakpoint-1 は md: の正確な補集合。change イベント購読+cleanup、
SSR 初期値 false、MOBILE_BREAKPOINT export と breakpoint オプションは維持。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): Home 第一フォールドを回収(バナー削除・見出し降格・tabular-nums) (#1072) (#1086)

陳腐化した Welcome バナー(#600 のナビ再編告知)を削除。h1「CommandMate」を機能的見出し
(HomeHeading: Overview + ライブサブライン)へ降格しヘッダーのワードマークとの同語反復を解消。
Home の数値・相対時刻・サイドバーのブランチ数バッジに tabular-nums を導入。HomeQuickActions の
inline SVG を lucide へ置換。相対時刻の短縮形は新設 date-utils、見出し文言は home i18n
namespace(en/ja)。機能(ToDo/カウント/遷移)は不変、サブラインは既存キャッシュから算出。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ライトモードのエレベーション階梯を反転(グレー地+白カード+2層影) (#1074) (#1087)

light を「白ページ+凹む灰カード」から Vercel/Linear 型の「グレー地ページ+
白カード+2層影」へ反転し、dark(#1049)と対のエレベーションモデルにする。
surface-2(740) < background(751) < surface(765) の「白カードが浮き、well が沈む」階梯。

- globals.css :root: --background 255→250 250 251 / --surface 248 250 252→255 255 255
  / --surface-2 241 245 249→244 246 250。.dark は不変(回帰なし)
- tailwind.config.js: 2層 boxShadow.sm を新規追加(md/lg は Tailwind 既定のまま)
- design-tokens/surface-depth テスト更新 + WCAG コントラスト検証を追加
  (foreground≥4.5 全面 / muted-fg≥4.5 page,card / ≥3 recessed well)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): サイドバーを --sidebar-* トークン化しテーマ追従に (#1073) (#1088)

bg-gray-800 text-white 固定でテーマ孤島だったサイドバーを、新設の
--sidebar / --sidebar-foreground / --sidebar-border / --sidebar-hover /
--sidebar-muted セマンティックトークンへ移行。light=slate-50系の明るいパネル、
dark=#141821(#1049階梯と整合)でテーマに追従する。standalone なリテラルRGB値
としており、#1074 の --surface 反転がサイドバー色へ滲まない。

- globals.css :root/.dark に --sidebar-* を定義、tailwind.config.js に登録
- Sidebar.tsx / sidebar/*(BranchListItem, SortSelectorBase)/ 検索Input
  / LocaleSwitcher / ThemeToggle の直書きを全てトークンへ置換
- 敵対的レビュー由来のライト回帰修正2件:
  - LogoutButton.tsx: 漏れていた text-gray-300 直書きを ThemeToggle と同じ
    テーマ中立トークン(text-muted-foreground/hover:bg-muted)へ(モバイル共用)
  - SortSelectorBase: 選択中ソートを text-accent-700 dark:text-accent-400 に
    (ライトのslate-50上で 1.73→約5.1:1 でAA達成、dark時は据え置き)
- docs/design-system.md に --sidebar-* を追記、既存テスト/期待値を更新

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ⌘K パレットを Raycast 級に強化(アイコン/StatusDot/kbd/Recents/ヘッダー導線)(#1077) (#1089)

- src/components/ui/Kbd.tsx を新設(semantic-token ベース、#1080 で再利用)し ui/index.ts に export 追加
- NAV 行に lucide アイコン、worktree 行に GitBranch + StatusDot(isProcessing/isWaitingForResponse/isSessionRunning から算出)
- バックドロップ/パネル/選択状態/入力/フッター文字色を semantic トークン化(bg-surface/backdrop-blur/ring-border/accent-500)
- キーボードガイドのフッター(↑↓/↵/esc、i18n)
- Recent グループ(localStorage cm.palette.recents、MRU<=8、空クエリ時のみ、dead worktree は無視)
- worktree を isSessionRunning 優先→updatedAt 降順ソート+空クエリ時 slice(0,8)
- Actions 拡充: Sync repositories(repositoryApi.sync + Toast)/ 言語切替 / Open GitHub
- Header に ⌘K 検索ピル(mount 後に platform 判定して ⌘K/Ctrl K を出し分け)
- #1053 の挙動契約(フォーカス/入力ガード/Escape/backdrop/data-testid)を維持
- 文言を locales/{en,ja}/commandPalette.json に追加、単体テスト更新

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ターミナルクロームを刷新(全周枠廃止・カード化・CLIセレクタDropdownMenu化)(#1079) (#1090)

worktree ターミナルの全周枠を廃し surface-2 カード+ヘアラインのカード化に統一。
CLI インスタンスセレクタを native <select> から Radix DropdownMenu(StatusDot+alias
+ChevronDown)へ置換し、value/onChange の単一選択セマンティクスと testid を保持。

- Terminal / ActivityBar / TerminalDisplay / TerminalSplitContainer /
  TerminalSplitPane / TerminalSplitPaneContent を surface/border トークンへ
- TerminalSplitPane: <select> → DropdownMenu(RadioGroup)。cli-selector testid 保持
- xterm の theme は意図的に固定ダーク(--surface-2 dark 相当)を維持
- StatusDot/status-colors.ts は不変(#1078 の領域)。locales worktree.json 追記
- 既存 terminal テスト(cli-tab-switching 含む)更新・全パス

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): worktree詳細/Chat のダーク直書き島を surface トークン化しテーマ追従に (#1075) (#1091)

HistoryPane / ConversationPairCard / AssistantMessageList / AssistantChatPanel /
HistorySearchBar / CopyButton と .assistant-md の常時ダーク直書きを surface/foreground
セマンティックトークンへ移行し、light/dark 両テーマで正しく描画されるようにする。

- globals.css .assistant-md: リンクを accent-700 dark:accent-400、code/pre を明色地に
  し両テーマ AA≥4.5:1。:root/.dark のトークン定義は不変
- 各コンテナ+全子孫を surface/surface-2/foreground/muted-foreground/border トークンへ
  (hidden-children 回避: 子の直書き light-on-dark を残さない)
- AssistantChatPanel/HistoryPane の native select/checkbox は色のみトークン化
  (Radix 化は #1076 の担当のため据え置き)
- ConversationPairCard: コピー/挿入/展開ツールバーの hover-reveal 化に伴い、
  タッチ端末で不可視化する回帰を [@media(hover:none)]:opacity-100 で解消
  (デスクトップの hover-reveal は維持。copy-user/assistant-message testid 保持)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): データ画面コントロールの意味論を整理(Review/Repositories/More)(#1081) (#1092)

表示のみの調整。絞り込み・編集・削除・可視性トグルのロジックは不変。

- Review: フィルタを CTA 風の primary/secondary ボタンから、件数付きの
  控えめなセグメンテッドコントロールへ(選択は bg-accent-500/15、CTA と区別)。
  件数は既存のフィルタ済み配列長から算出(新規 fetch なし)。
- Repositories: Enabled/Disabled バッジと可視性トグルの緑ピルを StatusDot 流の
  dot+muted テキストへ(トグルは操作可能なまま)。(none)→ muted の em-dash、
  パスは truncate+font-mono+full-path tooltip、Edit は ghost な Pencil アイコンボタン。
- More: ExternalAppsManager 側の重複 h2「External Apps」を削除(ページ側に一本化)。
- StatusDot/status-colors.ts は不変(#1078 の領域)。生 gray/cyan/blue の追加なし。
- 既存テスト更新+件数バッジ/単一見出しの検証テストを追加。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): コンポーザーをカード化しモバイルworktreeコントロールを整理 (#1080) (#1093)

- MessageInput をコンポーザーカード化(surface/border/shadow/focus-ring)し、送信
  ボタンを入力有無で filled(bg-accent-600)/ghost に切替。プレースホルダを i18n 短縮
- Auto-Yes をコンポーザー下辺のメタ行へ統合(ui/Switch 化)。右側に Kbd キーヒント
  (#1077 の ui/Kbd 再利用)。InterruptButton のオレンジ直値を text-danger ghost へ
- モバイル sticky 行をインスタンスタブ専用化(dark: バリアント+下線インジケータ)。
  検索/End を MobileTerminalActionsSheet(ボトムシート、End は確認付き)へ移設
- MobileTabBar を GlobalMobileNav 様式(bg-background/backdrop-blur/border-border、
  上辺インジケータ、Wrench アイコン、success/warning バッジ)へ統一
- テスト: 送信ボタン状態変化/新規シートを追加、ResizeObserver polyfill(Radix Switch)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): 稼働ステータスを StatusDot 一本化し idle ノイズを縮退 (#1078) (#1094)

青スピナー(border-info)と生 span を全て統一 StatusDot に置換し、稼働ステータスの
視覚言語を1系統に。idle ノイズを縮退(Sessions は running/waiting のみラベル付き、
idle は「+N idle」カウンタ / worktree ヘッダーは idle を StatusDot のみの円形ボタン
+Tooltip、幅超過は「+N」DropdownMenu オーバーフロー)。

- WorktreeDetailSubComponents / sessions/page / MobileHeader を StatusDot へ統一
- 新規 lib/agent-status-display.ts に classifyHeaderInstances 等の導出を集約
- status-colors.ts の type:'spinner'/border-info は @deprecated 化(ReviewTab が
  最後の消費者。消滅後に削除)
- 相対時刻 tabular-nums / プレビューは CSS truncate、DnD(handleAgentDragStart)と
  タブ切替 onClick は全ボタンで温存
- 敵対レビュー由来の仕上げ: 「+N」オーバーフローに稼働中(running/waiting)があれば
  集約 StatusDot グローを出し、稼働セッションが glow-less に埋もれないように。
  同 trigger の生グレーをセマンティックトークンへ

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ネイティブ checkbox/radio を Radix プリミティブに統一 (#1076) (#1095)

OS 標準の白いフォームコントロールが dark に浮く問題を解消。@radix-ui/react-checkbox
/ react-radio-group ベースの ui/Checkbox・ui/RadioGroup を新設し、残存していた
ネイティブ input(checkbox 12箇所/11ファイル・radio 6箇所/4ファイル)を全て移行。

- 新規 src/components/ui/{Checkbox,RadioGroup}.tsx(cn()+cva・セマンティックトークン・
  focus-visible:ring-ring)、ui/index.ts に export、依存2つを追加
- checked/onCheckedChange・value/onValueChange への適応、name/value/disabled/
  aria/data-testid とラベルクリックのトグルを全移行で保持
- 移行後 native type="checkbox"/"radio" は src/ 配下 0 件(完了性 grep 検証済)
- 敵対レビュー(binding/primitive-a11y/GitDangerZonePanel 密集)0件

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): トークン規律の確立(生gray/slate置換・focus-visible統一・CIガード)(#1082) (#1096)

移行済み領域の生 gray/slate 直書きをセマンティックトークンへ置換し、フォーカス表現を
focus-visible に統一、CI grep ガードでトークン違反の再流入を止める。機能・挙動は不変
(見た目/規律のみ)。

## プリミティブ是正
- Button: secondary/ghost を muted/foreground、danger を bg-danger トークンへ。
  `focus:` → `focus-visible:` + `ring-offset-background`(dark の白ハロー解消。Tabs/Switch が手本)。
- Input/Textarea/Select トリガー: `focus:` → `focus-visible:`(枠が反応するため offset なし)。
- Badge gray → bg-muted/text-muted-foreground、Modal → surface/border/foreground。

## in-scope スイープ(生 gray/slate → トークン)
- app(worktrees を除く)/ ui / layout / home / review / repository / common。
- gray-100/200→muted、gray-400/500/600→muted-foreground、gray-700/800/900→foreground、
  境界→border、フォーム枠→input。

## globals.css / metadata / docs
- body→text-foreground、.prose インラインコード→muted/foreground、scrollbar-thin をトークン化
  (--input / --muted-foreground)、`::selection` にアクセント淡色を追加。
- layout.tsx: title template(`%s | CommandMate`)+ viewport themeColor(light/dark)。
- docs/design-system.md にフォーカス表現・CI ガード・例外リストを同期。

## CI ガード
- `.github/workflows/ci-pr.yml` に `token-discipline` ジョブを追加(CLAUDE.md size check と同方式)。
  ホワイトリスト方式で移行済みディレクトリ限定 hard-fail。除外リストをスクリプト内に明記。
  違反注入時に fail することを確認済み。

## 判断メモ(要レビュー)
- `src/app/worktrees/**`(40 occ)はオーケストレーター集計では app(108) に含まれるが、
  ワークツリー領域(#1061・「触らない」)かつ常時ダークのターミナルページ/CLI ブランド色
  (bash=bg-gray-600)を含むため、スイープ・ガード双方から除外した。#1061 完了時に拡大。
- 軽微な見た目変化: danger の red-600→red-500(--danger)、secondary/ghost の muted シェード、
  SidebarToggle はテーマ追従化、ツールチップは反転サーフェス(bg-foreground/text-background)へ
  テーマ追従化、Home の Save&Resend ボタンを accent-600 + 白文字に正規化。

品質ゲート(lint / tsc --noEmit / test:unit / build)+ 新 CI ガード 全パス。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree/共通の生gray/buttonをトークン・プリミティブへ (#1061 群A 1/4) (#1097)

#1061 のファイル分割並列(群A: 21ファイル)。生 gray/slate Tailwind クラスを
#1040/#1082 のセマンティックトークンへ用途判定で置換、生 <button> を ui/Button へ。
挙動・ハンドラ・testid は不変。群A担当ファイルの raw gray = 0。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree詳細/markdown/fileの生gray/buttonをトークンへ (#1061 群B 2/4) (#1100)

#1061 分割並列(群B: 21ファイル)。WorktreeDetailSubComponents/MarkdownToolbar/
FileTreeView 等の生 gray/slate → セマンティックトークン、生 button → ui/Button。
ターミナル出力表示面(TerminalSplitPaneContent)はダーク維持のため意図的に非トークン化。
挙動・testid 不変。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree/mobile/gitの生gray/buttonをトークンへ (#1061 群C 3/4) (#1098)

#1061 のファイル分割並列(群C: 22ファイル)。生 gray/slate → セマンティックトークン、
生 <button> → ui/Button。挙動・testid 不変。群C担当ファイルの raw gray = 0。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree/git/todoの生gray/buttonをトークンへ (#1061 群D 4/4) (#1099)

#1061 分割並列(群D: 21ファイル)。生 gray/slate → セマンティックトークン、生 button
→ ui/Button。ターミナル出力表示面(TerminalSplitContainer/TerminalContainer)は
xterm と同様ダーク維持のため意図的に非トークン化(除外)。挙動・testid 不変。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci(guard): token-discipline ガードを worktree/mobile/external-apps へ拡大 (#1061) (#1101)

#1061 で worktree/mobile/external-apps の生 gray/slate をトークン化完了したため、
#1082 で導入した token-discipline CIガードの対象ディレクトリを拡大。
ターミナル出力表示面(*Terminal* ソース)は xterm 固定ダーク(#1079)と同様の
意図的ダーク島のため除外。app/worktrees も引き続き除外。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: integration スイートの drift を解消(モック/期待値追随・CI追加)(#1102) (#1103)

CI 非対象で長期 drift していた integration スイート(41 failed)を修復。実装バグを
隠さないよう vi.mock を importOriginal 方式へ書き換え、欠落 export を補完。

- 不完全 vi.mock を importOriginal ベースへ(欠落 export を実挙動ごと通す)
- config ドリフト(アップロード上限 5MB→20MB 等)の期待値を現行定数準拠へ
- undefined.length 系はモック戻り値を実 shape へ
- 再 drift 防止に ci-pr.yml へ Integration Tests ジョブ追加
- external-apps duplicate name → 500(期待409) は実装バグのため it.skip で quarantine

結果: test:integration 643 passed / 1 skipped(0 failed)。unit/lint/tsc 緑維持。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(external-apps): 重複nameで500ではなく409を返す (#1104) (#1105)

* fix(external-apps): 重複nameで500ではなく409を返す (#1104)

route.ts の catch を message.includes('UNIQUE constraint') 文字列判定から
ExternalAppDbError.code === 'DUPLICATE' の型/コード判定へ変更。DB層のエラー
ラップ後もメッセージに依存せず 409 を正しく返す。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(external-apps): 409 duplicate name テストを un-skip(#1104 で修正済み)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(timer): Timer履歴の詳細モーダル表示と失敗理由の記録 (#1107) (#1109)

Schedule 側の execution_logs 方式を Timer へ横展開。

- DB: migration v40 で timer_messages に error 列(nullable)追加、schema 39→40
- timer-db: error 列の read/write 貫通、updateTimerStatus に optional error 引数、recoverStuckSendingTimers に固定リカバリ理由
- timer-manager: 送信失敗(stage/error)・例外(error.message)を error 列へ永続化(no_session は保存せず)
- API: GET timers レスポンスに error を追加
- UI: TimerDetailModal を新設し履歴行クリックで指示全文+メタ+失敗理由を表示(PC=Modal / mobile=FullScreenModal)
- i18n: schedule.json の timer ブロックに en/ja lockstep でラベル追加

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(files): Filesツリー展開状態のworktree単位永続化と表示リセット (#1108) (#1111)

useFileTabs / useFilePanelState の worktree 単位 localStorage パターンを踏襲。

- 新規 useFileTreeExpandedState: 展開集合を worktree 単位 localStorage(キー commandmate:file-tree-expanded:<worktreeId>) に永続化。lazy-init 復元をマウント再フェッチに乗せ、空集合はキー削除、worktree 間の漏洩なし
- useFileTabs: CLOSE_ALL / closeAllTabs() を追加
- FileTreeView: フック統合+ツールバーに「表示をリセット」ボタン(更新ボタンの隣・常時可視)+ onResetView prop
- Controller/Desktop/Mobile/Refactored: resetFileTreeView(検索クリア・全タブ閉じ・モバイルビューア閉)を配線し、初期表示に戻すフルリセットを実現

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(mobile): viewportFit:'cover' を追加しiOS safe-area対応を有効化 (#1132)

- src/app/layout.tsx の viewport エクスポートに viewportFit: 'cover' を追加。
  iOS は viewport-fit=cover 指定時のみ env(safe-area-inset-*) に非0値を
  入れるため、既存の pt-safe/pb-safe 等の safe-area 対応が実効化される
- GlobalMobileNav の未定義クラス safe-area-bottom を globals.css 定義済みの
  pb-safe に修正(cover 有効化後もパディングが効かない潜在バグ)
- viewport エクスポート値(viewportFit / themeColor)のユニットテストを追加

レンダリング検証: 本番ビルドで <meta name="viewport"> に
viewport-fit=cover が含まれることを確認済み

Refs #1131

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat(ui): ステータスカラーtintトークン導入とダーク非対応フィードバックUIの移行 (#1133)

- globals.css / tailwind.config.js に success/warning/danger/info × subtle/border/foreground の12トークンを両テーマで追加(ライト *-50 面 + *-800 前景、ダーク *-950 面 + *-300 前景、全て WCAG AA 準拠。根拠はコメントに記載)
- Toast / DefaultErrorFallback / History・Prompt・ConnectionErrorFallback / ErrorDisplay / PromptPanel を新トークンへ移行し、生パレット + dark: ペアを撤去(TerminalErrorFallback は常時ダーク島のため現状維持)
- tint 面内のソリッドアクションボタンは反転 tint(bg-{status}-foreground text-{status}-subtle)で両テーマ AA を確保
- docs/design-system.md にトークン表・使用ルールを追記
- tint トークン定義・移行 grep を検証する status-tint-tokens.test.ts と ErrorBoundary/フォールバックのレンダリングテストを追加、既存 Toast / design-tokens テストを追随

Refs #1112

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* refactor(ui): window.confirmを共通ConfirmDialogへ全面置換 (#1134)

- ConfirmDialog/ConfirmProvider/useConfirm を src/components/ui/ConfirmDialog.tsx に新設
  (variant default/danger、Escape/背景クリックでキャンセル、danger時はcancelに初期フォーカス、
  クローズ後に呼び出し元へフォーカス復帰)
- AppProviders に ConfirmProvider をマウント
- window.confirm 残存11箇所 + bare confirm 1箇所(WorktreeCard)を useConfirm へ置換
- confirm文言を next-intl 辞書へ移動 (review namespace 新設、home/worktree/common に追記)
- ESLint no-restricted-globals(confirm/alert) と no-restricted-properties(window.confirm) を追加
  (bare alert 3箇所は window.alert へ変更、alert置換自体は対象外)
- 既存テストを window.confirm モックからダイアログ操作へ追随、
  ConfirmDialog 単体テストと Timer履歴クリアの結合フローを追加

Refs #1113

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* chore(ui): UI死コード削除(WorktreeList・放棄infinite-scroll hooks・hasNewOutput配線) (#1135)

- WorktreeList.tsx(production importゼロの孤児コンポーネント)とbarrel export(index.ts)を削除
- 放棄された無限スクロール実装(useInfiniteMessages/useScrollObserver/useScrollRestoration)と
  対応テスト3本、孤児化するtypes/infinite-messages.tsを削除
- WorktreeDetailRefactored.tsxのhasNewOutput={false}死配線を撤去(MobileTabBar側は維持)
- 残置ファイルのstaleコメント修正(useVisibilityRecovery.ts/history-display-config.ts)
- useWebSocket.tsに@deprecated注記を付与(WebSocketストリーミング化で再設計・再利用予定)
- docs/architecture.md(ja/en)の削除対象ファイルへのstale参照行を削除

受入基準: 3グレップ全て0件成立、lint/tsc/test:unit(8746 pass)/build全パス

Refs #1115

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* fix(ui): 未定義animate-fade-*の定義とModal/Toast/ContextMenuのexitアニメーション実装 (#1114) (#1136)

* fix(ui): 未定義animate-fade-*の定義とModal/Toast/ContextMenuのexitアニメーション実装

- tailwind.config.jsにfade-in/fade-outキーフレームとanimate-fade-in/outを定義。
  usePromptAnimationが発行していたが未定義でno-opだった。クラス名を
  PromptPanel等が文字列比較で消費しているため、tailwindcss-animateへの
  書き換えではなくキーフレーム定義の追加を採用。duration/easingは
  --motion-duration-base / --motion-ease-out(#1050モーショントークン)準拠
- content globsにsrc/hooksを追加(フック発行クラスのpurge防止)
- useExitAnimation(open, duration)フックを新設: 閉要求後もduration msだけ
  マウントを維持しexitアニメーション再生を保証。render-phase同期により
  開閉ともに1フレームの遅延なし。再open時はタイマー解除
- Modal: data-state=closedでfade+zoom-out exit(200ms)後にアンマウント。
  exit中はbackdropクリック無効化
- Toast: fade+slide-out exit(200ms)再生後にonClose通知(アンマウント遅延)
- ContextMenu: fade+zoom-out exit(100ms、enterと同duration)。
  exit中はpointer-events-none
- EXIT_ANIMATION_DURATION_MS / CONTEXT_MENU_EXIT_DURATION_MSを
  ui-feedback-config.tsに追加しCSSとJSタイマーを同期
- reduced-motionはglobals.cssのグローバルキルスイッチで対応(CSS実装のみのため追加ガード不要)
- テスト: useExitAnimation単体、Modal/Toast/ContextMenuのexit挙動
  (閉操作後にアニメーション時間経過までDOM残留→アンマウント)、
  Tailwind実コンパイルによるCSS生成検証を追加

Refs #1114

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ui): ConfirmDialogをModal exitアニメーションに追随

- ConfirmDialog.test.tsx: 閉操作後の即時DOM除去期待を、200ms exit
  アニメーション完了後のアンマウント(waitForElementToBeRemoved)に更新
- ConfirmProvider: pendingクリア後のexit再生中にdescription等が空文字に
  なり内容がブランク表示される統合ドリフトを修正(直近optionsを保持し
  フェードアウト中も表示を維持)。cancelテストにブランク防止の検証を追加

Refs #1114

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat(ui): ヘッダーナビのアクティブインジケータ実装と角丸スケール規約の導入 (#1137)

- Header ナビに CSS-only の下線インジケータを実装(after 疑似要素の
  scale-x トランジション、--motion-ease-out / duration-200 準拠、
  motion-safe ガード)。アクティブ項目に aria-current="page" を付与
- Header のアクティブ状態レンダリングテストを追加(全 6 ルート)
- docs/design-system.md に角丸スケール規約を独立セクションとして追記
  (コントロール=rounded-md / コンテナ=rounded-lg / ポップアップ=rounded-md /
  チップ=rounded-full / 小型インライン=rounded-sm、裸 rounded 原則禁止)

角丸統一の実施範囲(明確な逸脱のみ、feature 配下の一括置換はしない):
- ui/Kbd・ui/Checkbox: 裸 rounded → rounded-sm
- layout/Sidebar のアイコンボタン3箇所: 裸 rounded → rounded-md
- layout/Header のコマンドパレットトリガー: rounded-lg → rounded-md

Refs #1119

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* refactor(ui): チャット履歴UIをConversationPairCard系デザインへ統一 (#1138)

MessageList/PromptMessageの視覚言語をConversationPairCardに統一
(案B: 構造・optimistic updateロジック維持、見た目のみ刷新)。

- 生chromatic色21箇所をステータスtintトークン(warning/success/info x
  subtle/border/foreground)へ移行し、生色のdark:ペアを撤去
- インラインSVGをlucide-reactへ統一(Check/X/CircleCheck/Clock/Loader2/
  MessageCircle)
- ユーザー/アシスタントメッセージをCPC言語のセクション(accent左ボーダー/
  surface-2 wash)+ hover-revealコピーツールバーへ刷新
  ([@media(hover:none)]:opacity-100 タッチフォールバック付き)
- markdown描画をprose→assistant-md(トークン追従)へ移行、ANSI/コード
  ブロックの常時ダーク島(#0d1117)は現状維持
- ファイルパスリンク/プロンプト応答(Yes/No・選択肢・テキスト入力)/
  ログリンクの機能パリティ維持、レンダリングテストを新規追加

Refs #1117

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat(ui): スケルトンローディング展開とSpinner統一・loading.tsx整備 (#1139)

- ui/Spinner.tsx 新設: 手書きインラインSVG/border系スピナー43箇所を共通プリミティブへ置換
- 主要画面にレイアウト整合スケルトンを実装(Home Session Overview/ToDo、Sessions一覧、Repositories一覧、HistoryPane(汎用Card型・loading分岐のみ)、ファイルツリー、AssistantChatPanel、Reviewタブ群)
- 裸テキストローディングをスケルトンへ置換(MessageList.tsxは#1117衝突回避のため対象外)
- 初回ロードのみスケルトン表示(再fetch時は既存コンテンツ維持の非ブロッキングパターン踏襲)
- 主要7ルートに loading.tsx(薄いRouteLoadingフォールバック)を追加
- AssistantChatPanelのローディングを導出状態化し空状態フラッシュを解消

Refs #1118

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* refactor(ui): 生chromatic色をtintトークンへ全面移行しCIガードを拡大 (#1140)

意味ベースでステータスtintトークンへ移行 (red→danger / green→success /
yellow・amber・orange→warning / blue・sky・purple・violet→info)。
淡色面は bg-{s}-subtle + border-{s}-border + text-{s}-foreground、
ソリッドは bg-{s} を使用し、chromatic用途の dark: ペアを撤去。

- ui/Badge.tsx・config/status-colors.ts をトークン化 (生パレットクラス撤去)
- error/ の常時ダーク島 TerminalErrorFallback を *Terminal* 名の別ファイルへ分離し、
  残る error/・auth/ の生gray/chromatic漏れを回収
- 検索ハイライト/ファイル種別アイコン等の装飾色もトークン化 (text-pink等スコープ外色は維持)
- CIガード token-discipline の grep パターンに chromatic 9色を追加し error/・auth/ を対象化。
  既存の *Terminal*・テスト・常時ダーク島 (worktrees/[id]/terminal 等) の除外は維持
- docs/design-system.md に chromatic 禁止/例外リストとCIガード仕様を追記
- 影響する class 検証テストを追随
  (Badge / ContextMenu / MarkdownEditor / MemoCard / dark-mode-foundation / recursive-delete)

chromatic grep 残数
(git grep -cE '(bg|text|border|ring)-(red|green|yellow|amber|orange|purple|violet|sky|blue)-[0-9]' -- src/):
  before: 254 行 (71 ファイル)
  after:  19 行 (サンクション済み例外のみ = *Terminal* 群 + src/app/worktrees/[id]/terminal/)
  in-scope (例外除く): 0

品質ゲート: npm run lint / npx tsc --noEmit / npm run test:unit (8818 passed) / npm run build 全パス

Refs #1116

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): View Transitions APIによるページ遷移クロスフェードを導入 (#1141)

主要ルート間(Home/Chat/Sessions/Repos/Review/More/worktree詳細)のコンテンツ
領域をクロスフェードするプログレッシブエンハンスメントを追加する。

実装方式は「自前の薄いラッパ(案B)」を採用し、next-view-transitions等の外部
依存は追加しない。理由:
- 受入基準が機能検出ガード/reduced-motion無効化の単体テストを要求しており、ガード
  関数を自前で保持することで直接テスト可能になる(ライブラリ委譲では不可)
- framer-motion不採用方針(docs/design-system.md #1050)と同様にCSS駆動とし、JSは
  startViewTransition呼び出しのガードのみに限定してバンドルを軽量に保つ
- Next15/React19ネイティブView Transitionsへの載せ替えが容易(抽象が薄い)
- ライブラリ導入時のLink全面置換を避け、変更をnavシェルとプログラム遷移に限定

主な構成:
- src/lib/view-transitions: supportsViewTransitions/prefersReducedMotion/
  startViewTransition。非対応・reduced-motion時は即時遷移フォールバック
- ViewTransitionsProvider: startViewTransitionをApp Routerナビゲーションへ橋渡し。
  usePathnameのコミット監視で新DOM確定後にresolveし、同一ルート等の非コミット時は
  COMMIT_SAFETY_TIMEOUT_MS=500で確実にsettle
- TransitionLink / useViewTransitionRouter: Header・GlobalMobileNav・Sidebarで使用。
  修飾クリック/別タブ/外部/同一ルートはブラウザ既定にフォールスルー
- globals.css: [data-view-transition="content"](AppShellの<main>のみ)をクロス
  フェードし、永続シェル(ヘッダー/サイドバー/ボトムナビ)はrootを固定して除外。
  ::view-transition背景を--backgroundに設定しダークテーマの白フラッシュを防止。
  200ms(--motion-duration-base)/ --motion-ease-out準拠、reduced-motionで無効化

Refs #1122

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* perf(ui): HistoryPaneを@tanstack/react-virtualで仮想化し250件上限を撤廃 (#1123) (#1142)

* perf(ui): HistoryPaneを@tanstack/react-virtualで仮想化し250件上限を撤廃

履歴リスト(HistoryPane)を仮想化し、可視領域+オーバースキャンのみを
マウントするよう変更。従来はカードをフラット描画し、250件上限は性能緩和策
だった。仮想化により描画コストが O(可視行) となり、上限を1000へ緩和した。

主な変更:
- FilePanelContent の流儀に揃え useVirtualizer + measureElement(可変高)で
  ConversationPairCard を仮想描画。展開/折りたたみ・コードブロック等の高さ
  変化は measureElement が自動再計測。
- スクロール位置維持: 追記は react-virtual が自動維持。最下部閲覧中
  (isNearBottom)のみ新規ペアを追従(follow/maintain を分離)。
- 検索ジャンプ整合: 対象行を scrollToIndex で materialize してからハイライト
  適用。マウント窓の変化(renderedRange)でハイライトを再適用。
- カードのアンマウントで展開状態が飛ばないよう、展開状態はリスト側/親
  (useConversationHistory + 検索auto-expand)で保持(#1121 の pending 挿入も
  この item 管理の上に載せやすい構成)。
- 250件上限を緩和: HISTORY_DISPLAY_LIMIT_OPTIONS に 500/1000 を追加
  (MAX_MESSAGES_LIMIT=1000 に自動追従、APIの取得上限も1000へ)。
- src/lib/history-virtualization.ts: overscan/estimate/しきい値の定数と純粋な
  isNearBottom を分離しユニットテスト可能に。
- テスト: jsdom は無レイアウトのため offsetHeight を与える helper
  (tests/helpers/virtual-layout.ts)と1000件fixtureを追加。仮想化のDOM件数・
  追従/維持判定・展開折りたたみ・検索名前空間を網羅。

1000件fixture before/after(jsdom初期描画。体感FPSは手動検証):
- マウントカード数: 1000 → 16
- DOMノード数: 26,001 → 449(約58分の1)
- 初期描画時間: 289ms → 24ms(約12倍高速)

品質ゲート: lint / tsc --noEmit / test:unit(8855 passing) / build 全パス。

Refs #1123

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ui): HistoryPane仮想化のゼロ計測フォールバックで本文が消える問題を修正

WorktreeDetail経由の統合テスト(jsdomはレイアウト高さ0)で virtualizer が
アイテムを0件しか materialize せず、メッセージ本文が描画されず getByText が
失敗していた(tests/integration/worktree-detail-integration.test.tsx の
'renders user and assistant messages')。

修正: virtualizer がビューポート未計測でアイテム0件のとき(初回レンダー/SSR/
jsdom 等のレイアウト0環境)、先頭 HISTORY_FALLBACK_RENDER_COUNT 件を通常フロー
で描画するフォールバックを HistoryPane に追加。実ブラウザでは layout-effect の
計測直後に仮想化リストへ切り替わる(フォールバック描画は塗り前の一時コミット)。
本番の仮想化保証を損なわないよう件数は小さく固定。SSRのハイドレーション整合
(server/client双方が同じ先頭スライスを描画)にも寄与する。

単体テスト側は既存の virtual-layout ヘルパーで実 virtualizer 経路を検証し続ける
ため、この修正はそれらに影響しない。

品質ゲート: lint / tsc --noEmit / test:unit(8855) / test:integration(648) /
build 全パス。

Refs #1123

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(realtime): ターミナル出力・セッションステータスをWebSocketストリーミング化 (#1120) (#1143)

* feat(realtime): ターミナル出力・セッションステータスをWebSocketストリーミング化

実装済みだが未接続だったWebSocketインフラをクライアントへ接続し、ポーリングを
フォールバックとして残しつつサーバ→クライアントの一方向pushへ移行する。

- useWebSocket再設計: 単一WS接続・指数バックオフ再接続(1s..30s)・visibility連携・
  room購読再送・エンベロープparse。RealtimeProvider/useRealtimeで共有1本化+
  listener fan-out+購読refカウント(Provider未装着時はpollingフォールバック)
- セッションステータスpush: send時にrunning遷移をbroadcast、kill時のstopと合わせ
  サイドバー状態ドットを即時反映。WS接続中はuseWorktreesCacheのポーリングを間引き
- ターミナルストリーミング: response-poller tickからterminal_snapshotをpush
  (buildCurrentOutputでpull/push一致、購読者0でtmux captureをスキップ)。
  WS接続中はuseTerminalPanePollingを15sフォールバックへ間引き、version guardで
  stale破棄(既存stale-response/visibility堅牢性を維持)
- 差分適用: computeTerminalUpdateで追記/リセットを判定しTerminalDisplayを
  keyed chunk追記化、全置換を廃止してテキスト選択を維持(ANSI境界退避付き)
- モバイル新着出力バッジをpush駆動で復活、接続状態インジケータを追加
- 認証: Cookie自動付与でWS upgrade、認証失敗時は既存機構が接続拒否

テスト: useWebSocket(購読/再接続/バックオフ)・差分適用(追記/リセット/ANSI境界)・
WS切断→ポーリング復帰→WS再接続の統合テスト・認証拒否のE2E統合テストを追加。
lint/tsc/test:unit/test:integration/build 全パス。

Refs #1120

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(realtime): useRealtimeConnection等のNULバイト混入を除去

useRealtimeConnection.tsx の room購読キー生成で空白(0x20)が2箇所NULバイト
(0x00)に化けており、gitがバイナリ扱いしdiff/レビュー不能になっていた。
本来の ' ' 区切り(useWorktreesCache と同一パターン)へ復元し、ロジックは不変。

- src/hooks/useRealtimeConnection.tsx: `.join('\0')`/`.split('\0')` → `.join(' ')`/`.split(' ')`
- src/components/worktree/FileTreeView.tsx: 既存のセンチネル `'\0unavailable'` から
  NULを除去し `'unavailable'`(JSON.stringify結果と衝突しない性質は維持)

src/配下のNULバイト混入は0件を確認。lint/tsc/test:unit/test:integration/build 全パス。

Refs #1120

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(chat): メッセージ送信のOptimistic UIを実装 (#1144)

送信瞬間に pending 吹き出しを履歴末尾へ即時挿入し、サーバ echo で実データへ
照合置換(重複表示なし)、失敗時はエラー表示+再試行/破棄する Optimistic UI を
追加。

- usePendingMessages 層: serverMessages に pending をマージし ChatMessage
  (optimisticState 付き)として表示側へ渡す。content 一致+作成時 baseline 外+
  consumedServerIds の一意消費で照合(連投 1:1/既存同一文言の誤照合回避)。
  API reject/timeout(30s) で error→retry/discard。
- ConversationPairCard/HistoryPane: sending(opacity低下+スピナー)と
  error(danger トークン+Retry/Discard)を描画。pending 中は
  「Waiting for response…」を抑止。
- MessageInput: onOptimisticSend 提供時は送信を委譲し composer を即時クリア
  (draft も除去)。他サーフェスは従来の await→clear を維持。
- TerminalSplitPaneContent: 各 split の useSplitMessages に usePendingMessages を
  重ね、送信→即時表示→onSent refetch で照合。破棄時は既存 insert 経路で draft 復元。
- 仮想化リストと整合: pending も pair 化され virtualizer item として最下部追従。

テスト: usePendingMessages(挿入/照合/失敗/連投/タイムアウト/retry/discard)、
ConversationPairCard(sending/error/retry/discard)。

Refs #1121

* fix(mobile): useIsMobileを描画前レイアウトエフェクトで解決し初回デスクトップ描画フリップを解消 (#1145)

## 事象
useIsMobile がSSR安全のため初期値 false で始まり、実際の検出を
passive な useEffect(ペイント後実行)で行っていたため、モバイル実機の
ハードリロードで毎回デスクトップUIが一瞬描画されてからモバイルUIへ
フリップしていた(AppShell の固定サイドバー / WorktreeDetail の
デスクトップ⇔モバイル分岐でのフラッシュ・CLS)。

## 対策の選定(案A/B/C のトレードオフ)
- 案A(CSSで両ツリーを出し分け): 初回フレームは正しいが、重い
  WorktreeDetail が二重マウントし、両分岐の terminal ペインが同時に
  ポーリングを開始する。ヘビーツリーには不採用。
- 案B(UAヒント+cookie): ブレークポイントは viewport 幅基準
  (max-width:767px)で UA の mobile とは一致せず(狭幅デスクトップ/
  タブレット)、真の hydration mismatch を再導入する。不採用。
- 案C(採用・描画前レイアウトエフェクト版): SSR/初回クライアント
  レンダリングは決定的に false(デスクトップ)を返し SSR HTML と一致
  するため hydration mismatch 警告は出ない。検出は useLayoutEffect
  (isomorphic)で行い、commit 後・ブラウザ描画前に flush されるため、
  補正後の isMobile 値が最初のフレームに反映される。モバイルでは
  デスクトップツリーは描画される前に差し替えられる。

## ヘビーツリーの二重マウント / ポーリング配慮
- 主ポーラーは分岐に依存しない単一の useWorktreeDetailController が
  一度だけ生成する(分岐は表示のみ)。
- 非表示側の表示ツリーは同一 commit 内でペイント前に破棄されるため、
  passive な useEffect ベースの terminal ポーリングは発火しない。
- 主要なコールドロード経路(キャッシュ無し→LoadingIndicator)では
  どちらの分岐の子もマウントされない。

## その他
- SSR での useLayoutEffect 警告は isomorphic パターン
  (server では useEffect)で回避。
- 既存の useIsMobile 単体テスト(初期値・リサイズ切替)は不変で継続。
- hydrateRoot を用いた新規テストで「hydration mismatch 警告が出ない」
  「モバイルビューポートでモバイル値へ解決」「デスクトップは false 維持
  (回帰なし)」を自動検証。

品質ゲート: lint / tsc --noEmit / test:unit / test:integration / build 全て成功。

Refs #1126

* feat(pwa): PWA対応(manifest+Service Worker+インストール可能化) (#1146)

Web App Manifest(manifest.ts)、手書きService Worker、インストール可能化を
実装。「外出先からエージェントを監視する」プロダクト特性に沿ってホーム画面
から独立アプリ(standalone)として起動可能にする。

Service Worker技術選定: 手書きvanilla SW(next-pwa/Serwist/Workbox不採用)。
理由:
- custom server(server.ts)がWebSocket upgradeを保持しており、next-pwa等が
  前提とするデフォルトNextサーバ構成と噛み合わない
- 本Issueが要求するallowlist方式(キャッシュ対象を明示列挙)を直接表現でき、
  監査容易・小規模・後続#1125 Web Pushの拡張が容易

キャッシュ戦略(allowlist):
- /api・/login・/proxy・非GET・クロスオリジン → network-only(キャッシュ禁止)
- /_next/static/・/icons/・manifest/favicon → cache-first
- navigation → offline-fallback(認証コンテンツを避けページ本体は非キャッシュ)
判定はcache-policy.ts(単体テスト済み)を単一情報源とし、public/sw.jsが同定数を
鏡写し、sw-file.test.tsで両者の同期を担保。

更新戦略: install時はskipWaitingせず、更新検知でトースト表示→リロードで
SKIP_WAITINGを送信しcontrollerchangeで新版に切替。

その他:
- 本番のみSW登録(shouldRegisterServiceWorker)、dev/test・非セキュアは無効化
- オフラインフォールバック画面(/offline、トークン準拠、再接続ボタン)
- iOS standalone用にappleWebAppメタ(capable/title/status-bar-style)を付与
- manifest/sw/offlineをAUTH_EXCLUDED_PATHSに追加(auth有効時もinstallable/
  precache可、いずれも非機密リソース)
- HTTPS/LAN配信時のSW/インストール制限をREADMEに注記

Refs #1124

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(mobile): モバイルA11y改善(focus trap・44pxタップターゲット・tablistキーボード) (#1147)

モバイルのアクセシビリティ/エルゴノミクスの穴を塞ぐ。

focus trap は共通フック useFocusTrap に1実装集約:
- 判断理由: Modal/シート類は独自ポータル+enter/exitアニメ(useExitAnimation
  /usePromptAnimation)+スワイプ dismiss を持つ bespoke 実装。@radix-ui/
  react-dialog は未導入で、置換すると3面すべてとそのテストを書き直す必要がある。
  共通フックなら既存機構を壊さず trap のみ追加でき、issue の「1実装に集約」に沿う。
- 挙動: 初期フォーカスをダイアログへ移動 / Tab・Shift+Tab をダイアログ内で循環 /
  閉時に呼び出し元へフォーカス復帰。autoFocus 等で消費側が内部フォーカスを
  管理している場合は初期フォーカス奪取・復帰の両方をスキップ(ConfirmDialog 互換)。
- 適用: ui/Modal(role=dialog/aria-modal/aria-labelledby も付与)・
  MobilePromptSheet・MobileTerminalActionsSheet。

44px タップターゲット(視覚サイズは不変、ヒットエリアのみ拡大):
- エージェントインスタンスタブ(WorktreeDetailRefactored)
- History サブタブ Message/Git(WorktreeDetailMobile)
- MobileTabBar の各タブ

MobileTabBar に ARIA tabs パターン完成:
- 矢印キー(左右/上下)+Home/End 移動、ラップアラウンド
- roving tabindex(activeタブのみ tabindex=0)、選択はフォーカス追従

touch-manipulation をボタン系プリミティブ(ui/Button)とモバイル操作面へ付与し
ダブルタップズームの遅延を抑止。

title のみだった WorktreeCard のお気に入りトグル(アイコンのみ)へ aria-label を
付与し、タッチ/スクリーンリーダーからも到達可能に。

スワイプジェスチャー等は本Issueスコープ外のため変更していない。

Refs #1127

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(mobile): ジェスチャー強化(スワイプタブ切替・pull-to-refresh・キーボード追従) (#1148)

- useSwipeGesture に axis/方向ロック/エッジ開始制限/onSwipeMove を後方互換で追加
  (既存 MarkdownEditor の四方向スワイプと #299 スクロール抑制は不変)
- worktree詳細のモバイルタブを水平スワイプで切替。方向ロックで縦スクロール中は
  無効化、しきい値60px、ターミナルタブはエッジ開始領域に限定して誤発火を抑止。
  MobileTabBar インジケータは activeTab 共有で自動同期
- Sessions/Repositories に pull-to-refresh を新設(usePullToRefresh + PullToRefresh)。
  最上部のみ発火し overscroll-behavior:contain + preventDefault でネイティブPTR二重発火を防止
- MessageInput を useVirtualKeyboard に接続しキーボード追従(keyboardAware opt-in)、
  入力要素へ enterKeyHint/inputMode を付与
- MobilePromptSheet のアドホックスワイプを useSwipeGesture へ統合(focus trap ref を
  マージして #1127 の挙動を維持、finger-follow は onSwipeMove で継続)

Refs #1128

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(pwa): エージェント状態のWeb Push通知(プロンプト待ち/完了) (#1149)

- push_subscriptions テーブル(migration v41)と CRUD モジュールを追加
- VAPID/web-push 送信層: 種別フィルタ・デバウンス・410自動削除、機密(endpoint/秘密鍵)は非ログ
- プロンプト検出/セッション完了時に response-checker からファンアウト(検出ロジックは不変・fire-and-forget)
- 購読登録/解除/種別設定/VAPID公開鍵の認証必須API(グローバルmiddleware配下)
- Service Worker に push/notificationclick ハンドラ(該当worktreeへdeep link・…
Kewton added a commit that referenced this pull request Jul 17, 2026
* feat(files): スマホ版でファイルをダウンロード可能に(attachment配信+FileViewer DL導線)(#1024) (#1025)

スマホ版のファイルビューア(FileViewer, Issue #438 でモバイル専用化)から
任意種別のファイルをダウンロードできるようにする。

- B(サーバ): files/[...path] GET に download 分岐を追加し、生バイトを
  Content-Type: application/octet-stream + Content-Disposition: attachment で返却。
  filename は src/lib/http の content-disposition ヘルパーで RFC 準拠にサニタイズ
  (ASCII フォールバック + filename*=UTF-8'' パーセントエンコード、ヘッダ
  インジェクション回避)。既存 getWorktreeAndValidatePath でパス検証を再利用。
  base64 JSON を経由しないためプレビュー上限を超える大容量にも対応
- A(UI): FileViewer ツールバーにダウンロードボタンを追加し、同一オリジンの
  download URL へ遷移(iOS Safari 含めネイティブDLに乗せる)
- テスト新規29本(download API のヘッダ/パス検証/filename、content-disposition
  ヘルパー、FileViewer ボタン)
- C(独立ページのPDF分岐/DLリンク)は範囲外(モバイル動線が独立ページを使わないため)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(session): Timer 発火の送信を History に記録(send ロジックを共有サービス化)(#1028) (#1029)

Timer 機能(Issue #534)から送った指示が History(chat_messages)に残らない
問題を修正。executeTimer が cliTool.sendMessage だけを呼び createMessage/
startPolling をスキップしていたのが原因(#947 の委譲変更が起点)。

- send route L262-385 のインライン記録ロジックを src/lib/session/
  send-user-message.ts の共通関数 sendUserMessage() として抽出
  (savePendingAssistantResponse → send(画像/Copilot分岐含む)→ createMessage
  → updateLastUserMessage/orphan処理 → startPolling)。HTTP 層非依存
- send route POST を sendUserMessage() 利用へリファクタ(挙動不変)
- timer-manager.ts executeTimer を sendUserMessage() 経由に変更。
  timer_messages の status 遷移(sending/sent/failed)は維持
- テスト: sendUserMessage / executeTimer のユニット新規9件 + timer 既存更新(計26件)

これにより Timer 送信も user メッセージと assistant 応答の両方が History に残る。

スコープ外(別Issue推奨):
- tests/integration/api-send-cli-tool.test.ts の1件は base でも失敗する既存不具合
  (2引数アサート vs 3引数呼び出し)で本修正と無関係(CI の test:unit 対象外)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(todo): ブランチ単位ToDoを3状態化(未着手/仕掛/完了)(#1032) (#1033)

- migration v38: worktree_todos に status カラム追加(done=1→'done' backfill、CURRENT_SCHEMA_VERSION 37→38)
- DB層: status を真実源に done := status==='done' を派生(後方互換維持、writer整合)
- API/クライアント: PATCH で status ∈ {todo,doing,done} を検証・適用、done後方互換
- UI: 3状態巡回チップ+色バッジ+状態別件数、TodoPane全体を next-intl 化
- i18n: locales/{en,ja}/worktree.json に todo namespace 追加
- テスト: 既存5更新+新規 migration-v38(backfill検証)

Closes #1032

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(todo): ブランチ単位ToDoに詳細フィールド追加+クリックで件名/詳細を編集 (#1034) (#1035)

- migration v39: worktree_todos に detail カラム追加(TEXT NOT NULL DEFAULT ''、既存行は空詳細で移行)
- DB層: WorktreeTodo に detail 追加、create/update で detail 受理
- API: POST/PATCH で detail を検証・適用(MAX_TODO_DETAIL_LENGTH 新設)
- クライアント: WorktreeTodoItem.detail、create/update の detail 対応
- UI: 項目クリックで編集モーダル(Modal.tsx再利用)を開き件名+詳細を編集、詳細ありインジケータ
- i18n: locales/{en,ja}/worktree.json に詳細/編集の文言追加
- テスト: 既存6更新+新規 migration-v39、docs/module-reference.md 更新

Closes #1034

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(todo): ブランチ単位ToDoの詳細にコピーボタンを追加 (#1036) (#1037)

- UI: 編集モーダルの詳細欄に CopyButton を配置し詳細本文をコピー(詳細が空のときは非表示)
- 既存 src/components/common/CopyButton.tsx / clipboard-utils.ts を再利用(スマホHTTPは execCommand フォールバック)
- i18n: locales/{en,ja}/worktree.json に todo.copyDetail 追加
- テスト: TodoPane.test.tsx にコピー導線テスト3件追加
- migration/DB/API 変更なし(UIのみ)

Closes #1036

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): セマンティックデザイントークン基盤を導入 (#1041) (#1054)

globals.css に :root(light)/.dark(dark) の CSS 変数
(background/foreground/surface/muted/border/input/ring/accent-50..700/
success/warning/danger/info) を定義し、tailwind.config.js の primary(cyan)・
cmd-bg-dark を semantic トークンへ置換。layout.tsx / MainLayout.tsx の
body 背景を bg-background に統一。既存の実効値を写し取り視覚は不変(no-op)。
docs/design-system.md を新設。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): cn()/cva によるバリアント管理基盤とプリミティブ改修 (#1042) (#1055)

clsx + tailwind-merge の cn() ヘルパーと class-variance-authority を導入し、
Button/Card/Badge/Modal を cva ベースへ移行。Button ghost バリアントの
dark: 指定漏れ(ダークモードで視認性劣化)を修正。既存 API は完全互換。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): アイコンを lucide-react に統一 (#1044) (#1056)

絵文字アイコン(🤖⚡✦💻)を廃止し lucide アイコンへ置換。共通レイアウト・ナビ
(Header/GlobalMobileNav/MobileTabBar/ActivityBar/terminal page)および worktree
各所のインライン SVG を lucide に統一。docs/design-system.md にアイコンサイズ規約を追記。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): next/font で Geist Sans/Mono を導入 (#1043) (#1057)

geist パッケージ(セルフホスト)で GeistSans/GeistMono の CSS 変数を <html> に適用し、
tailwind fontFamily の sans/mono を「Geist → 日本語(Hiragino/Noto Sans JP) →
システム」フォールバックで構成。Terminal.tsx(xterm)の等幅も Geist Mono を先頭指定。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): RadixベースUIプリミティブを拡充 (#1046) (#1058)

Input/Textarea/Select/Tabs/Tooltip/DropdownMenu/Switch/Skeleton を追加
(cva + cn, セマンティックトークン着色, ライト/ダーク対応)。Portal 系は
'use client' + Z_INDEX(POPOVER)で Modal と階層整合。Sessions ページの
フィルタ/ソートを新プリミティブへ移行(適用サンプル)。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): アクセント色を統一しセマンティックトークンへ置換 (#1045) (#1059)

cyan/blue 直書き(~600箇所 / 131ファイル)を #1041 のセマンティックトークンへ置換。
用途判定: インタラクティブ=accent / フォーカス=ring / 情報=info。gray 系は
surface/background/border/muted へ実効値を保って置換。例外(Terminal.tsx の
xterm ANSI 配色 / CLI ブランド色 / .scrollbar-thin 等の固定装飾色)は維持。
globals.css の .btn-primary・検索ハイライト等もトークン化。
敵対的レビュー指摘(focus:ring-accent→focus:ring-ring 統一、design-system.md
の例外記述修正)を反映。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): 主要ページを共通UIプリミティブへ移行 (#1047) (#1060)

layout(Header/Sidebar の検索・トグル・言語)/ home / repositories / review /
more / chat / login の生button・手組みフォーム・コピペカードを @/components/ui
プリミティブへ移行。機能・挙動(onClick/フォーム送信/バリデーション/キーボード)は維持。
敵対的レビュー指摘(chat系ボタン移行・ghost の dark hover)を反映。ネイティブ select・
複合 textarea 入力バー・極小アイコントグルは意図的に残置(PR の残置リスト参照)。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): globals.css の @apply クラスを廃止し cva プリミティブを自己完結化 (#1048) (#1062)

.btn/.btn-*/.card/.card-hover/.badge*/.input を globals.css から削除し、
Button/Card/Badge の cva 定義へ実クラスを内挿(旧 @apply 定義と同値、視覚不変)。
src/ 全体で削除クラスへの参照が 0 であることを確認。worktree の一部
(WorktreeDetailRefactored/LogViewer/WorktreeList/ExternalAppForm)をプリミティブへ移行し、
マジックナンバー入りインライン style を定数化。worktree/mobile/sidebar/common の
一括プリミティブ移行は規模の都合により #1061 へ分割。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): サーフェス・奥行きを再設計しモダンダークテーマへ刷新 (#1049) (#1063)

globals.css の :root/.dark トークン値を刷新(深い base + 微リフト surface +
ヘアライン border)。Header/GlobalMobileNav に backdrop-blur(supports-[backdrop-filter]
フォールバック付)。Card に elevated/interactive variant、h1-h4 見出しタイポを調整。
敵対的レビュー指摘を反映: 主要閲覧画面のコンテンツカード(sessions/home/review)を
bg-surface/border-border へ移行し背景反転による溶け込みを解消、ダーク入力を surface-2 で
凹み表現、Card interactive にキーボード focus 対応を追加。コントラストは WCAG AA 全 pass。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): モーション基盤とマイクロインタラクションを追加 (#1050) (#1064)

tailwindcss-animate を導入し、Modal/MobilePromptSheet の enter/exit、Radix data-state
連動(DropdownMenu/Select/Tooltip)、一覧の stagger、hover lift を統一適用。
prefers-reduced-motion 対応。ターミナル/仮想スクロールには非適用。敵対レビュー指摘
(reduced-motionでのtranslate漏れ→motion-safe化、sessionsエラー時のstagger再発火→list
マウント維持、Tooltip instant-open enter、home focus parity)を反映。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): Home をダッシュボード(bento グリッド)化 (#1052) (#1065)

Home を縦積みから bento グリッドへ再構成(統計/直近セッション/ToDo/クイックアクション)。
PC は CSS Grid(grid-cols-12 + col-span)、モバイルは1カラム縦積み(useIsMobile の JS 分岐を
増やさず Tailwind breakpoint)。既存 sessions 取得フック/API を再利用し新規 API なし。既存機能
(ToDo 追加/チェック/コピー、セッションカウント)を維持。空状態対応。敵対的レビュー指摘
(ToDo タイルの h-full・見出しパリティ)を反映。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): コマンドパレット(⌘K)を追加 (#1053) (#1066)

cmdk で ⌘K(mac)/Ctrl+K(win) のコマンドパレットを追加(Navigation / Worktrees 検索 / Actions)。
input/textarea/ターミナル入力中は非発火、SSR 安全('use client')、i18n(en/ja)、Modal 相当の z-index。
モバイルは GlobalMobileNav に起動導線。敵対的レビュー指摘を反映: open 時に入力へフォーカスし
キーボード操作(type/矢印/Enter)を復旧[HIGH]、worktree 一覧は共有キャッシュ context を再利用
(最新化+失敗時 Navigation fallback)、Escape の preventDefault/stopPropagation と close 時の
フォーカス復帰。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): エージェント/セッションステータスの視覚を強化 (#1051) (#1067)

StatusDot(running/waiting/idle/error)を新設。running はグロー+パルス+静的リング halo、
waiting は amber の弱点滅。サイドバー(BranchStatusIndicator)/ Home / Sessions を StatusDot 化。
prefers-reduced-motion でパルス停止(静的リングで running≠ready を維持)、ポーリングで再発火しない
CSS infinite アニメ。敵対的レビュー指摘(reduced-motion での running/ready 識別性、docs の実態訂正)を
反映。worktree 詳細/MobileHeader は従来スピナー表示のまま(docs に明記・今後の追随対象)。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ui): サイドバーがヘッダーに遮蔽される左上レイアウト破綻を修正 (#1070) (#1083)

showGlobalNav 時に固定サイドバーを top-16 h-[calc(100vh-4rem)] にオフセットし、
半透明ヘッダー(z-50, backdrop-blur)がサイドバー先頭64px(Branches 見出し+
view/sort/sync ツールバー行)を遮蔽してクリック不能+灰色の滲みを生む問題を解消。
あわせて border-gray-200/dark:border-gray-600 を border-border にトークン化。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): テーマ既定を system 化しヘッダーにトグルを追加 (#1071) (#1084)

next-themes を defaultTheme="system" enableSystem に変更し、ライト環境ユーザーへの
強制ダークを解消。Header 右クラスタに ThemeToggle を追加(発見可能性向上)。
globals.css に color-scheme(:root=light / .dark=dark)を宣言し、残存ネイティブ
コントロールの OS クローム事故(dark で白いドロップダウン等)を防止。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(hooks): useIsMobile を matchMedia ベースにし混成レイアウトバグを解消 (#1069) (#1085)

window.innerWidth 依存を window.matchMedia(`(max-width: ${breakpoint-1}px)`)に置換。
innerWidth と CSS ビューポートが乖離する環境(実測 innerWidth=846 vs
documentElement.clientWidth=390)では、Tailwind の md:(min-width:768px)と JS 分岐が
食い違い「CSS はモバイル・JS はデスクトップ」の混成レイアウトになっていた。
max-width:breakpoint-1 は md: の正確な補集合。change イベント購読+cleanup、
SSR 初期値 false、MOBILE_BREAKPOINT export と breakpoint オプションは維持。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): Home 第一フォールドを回収(バナー削除・見出し降格・tabular-nums) (#1072) (#1086)

陳腐化した Welcome バナー(#600 のナビ再編告知)を削除。h1「CommandMate」を機能的見出し
(HomeHeading: Overview + ライブサブライン)へ降格しヘッダーのワードマークとの同語反復を解消。
Home の数値・相対時刻・サイドバーのブランチ数バッジに tabular-nums を導入。HomeQuickActions の
inline SVG を lucide へ置換。相対時刻の短縮形は新設 date-utils、見出し文言は home i18n
namespace(en/ja)。機能(ToDo/カウント/遷移)は不変、サブラインは既存キャッシュから算出。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ライトモードのエレベーション階梯を反転(グレー地+白カード+2層影) (#1074) (#1087)

light を「白ページ+凹む灰カード」から Vercel/Linear 型の「グレー地ページ+
白カード+2層影」へ反転し、dark(#1049)と対のエレベーションモデルにする。
surface-2(740) < background(751) < surface(765) の「白カードが浮き、well が沈む」階梯。

- globals.css :root: --background 255→250 250 251 / --surface 248 250 252→255 255 255
  / --surface-2 241 245 249→244 246 250。.dark は不変(回帰なし)
- tailwind.config.js: 2層 boxShadow.sm を新規追加(md/lg は Tailwind 既定のまま)
- design-tokens/surface-depth テスト更新 + WCAG コントラスト検証を追加
  (foreground≥4.5 全面 / muted-fg≥4.5 page,card / ≥3 recessed well)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): サイドバーを --sidebar-* トークン化しテーマ追従に (#1073) (#1088)

bg-gray-800 text-white 固定でテーマ孤島だったサイドバーを、新設の
--sidebar / --sidebar-foreground / --sidebar-border / --sidebar-hover /
--sidebar-muted セマンティックトークンへ移行。light=slate-50系の明るいパネル、
dark=#141821(#1049階梯と整合)でテーマに追従する。standalone なリテラルRGB値
としており、#1074 の --surface 反転がサイドバー色へ滲まない。

- globals.css :root/.dark に --sidebar-* を定義、tailwind.config.js に登録
- Sidebar.tsx / sidebar/*(BranchListItem, SortSelectorBase)/ 検索Input
  / LocaleSwitcher / ThemeToggle の直書きを全てトークンへ置換
- 敵対的レビュー由来のライト回帰修正2件:
  - LogoutButton.tsx: 漏れていた text-gray-300 直書きを ThemeToggle と同じ
    テーマ中立トークン(text-muted-foreground/hover:bg-muted)へ(モバイル共用)
  - SortSelectorBase: 選択中ソートを text-accent-700 dark:text-accent-400 に
    (ライトのslate-50上で 1.73→約5.1:1 でAA達成、dark時は据え置き)
- docs/design-system.md に --sidebar-* を追記、既存テスト/期待値を更新

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ⌘K パレットを Raycast 級に強化(アイコン/StatusDot/kbd/Recents/ヘッダー導線)(#1077) (#1089)

- src/components/ui/Kbd.tsx を新設(semantic-token ベース、#1080 で再利用)し ui/index.ts に export 追加
- NAV 行に lucide アイコン、worktree 行に GitBranch + StatusDot(isProcessing/isWaitingForResponse/isSessionRunning から算出)
- バックドロップ/パネル/選択状態/入力/フッター文字色を semantic トークン化(bg-surface/backdrop-blur/ring-border/accent-500)
- キーボードガイドのフッター(↑↓/↵/esc、i18n)
- Recent グループ(localStorage cm.palette.recents、MRU<=8、空クエリ時のみ、dead worktree は無視)
- worktree を isSessionRunning 優先→updatedAt 降順ソート+空クエリ時 slice(0,8)
- Actions 拡充: Sync repositories(repositoryApi.sync + Toast)/ 言語切替 / Open GitHub
- Header に ⌘K 検索ピル(mount 後に platform 判定して ⌘K/Ctrl K を出し分け)
- #1053 の挙動契約(フォーカス/入力ガード/Escape/backdrop/data-testid)を維持
- 文言を locales/{en,ja}/commandPalette.json に追加、単体テスト更新

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ターミナルクロームを刷新(全周枠廃止・カード化・CLIセレクタDropdownMenu化)(#1079) (#1090)

worktree ターミナルの全周枠を廃し surface-2 カード+ヘアラインのカード化に統一。
CLI インスタンスセレクタを native <select> から Radix DropdownMenu(StatusDot+alias
+ChevronDown)へ置換し、value/onChange の単一選択セマンティクスと testid を保持。

- Terminal / ActivityBar / TerminalDisplay / TerminalSplitContainer /
  TerminalSplitPane / TerminalSplitPaneContent を surface/border トークンへ
- TerminalSplitPane: <select> → DropdownMenu(RadioGroup)。cli-selector testid 保持
- xterm の theme は意図的に固定ダーク(--surface-2 dark 相当)を維持
- StatusDot/status-colors.ts は不変(#1078 の領域)。locales worktree.json 追記
- 既存 terminal テスト(cli-tab-switching 含む)更新・全パス

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): worktree詳細/Chat のダーク直書き島を surface トークン化しテーマ追従に (#1075) (#1091)

HistoryPane / ConversationPairCard / AssistantMessageList / AssistantChatPanel /
HistorySearchBar / CopyButton と .assistant-md の常時ダーク直書きを surface/foreground
セマンティックトークンへ移行し、light/dark 両テーマで正しく描画されるようにする。

- globals.css .assistant-md: リンクを accent-700 dark:accent-400、code/pre を明色地に
  し両テーマ AA≥4.5:1。:root/.dark のトークン定義は不変
- 各コンテナ+全子孫を surface/surface-2/foreground/muted-foreground/border トークンへ
  (hidden-children 回避: 子の直書き light-on-dark を残さない)
- AssistantChatPanel/HistoryPane の native select/checkbox は色のみトークン化
  (Radix 化は #1076 の担当のため据え置き)
- ConversationPairCard: コピー/挿入/展開ツールバーの hover-reveal 化に伴い、
  タッチ端末で不可視化する回帰を [@media(hover:none)]:opacity-100 で解消
  (デスクトップの hover-reveal は維持。copy-user/assistant-message testid 保持)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): データ画面コントロールの意味論を整理(Review/Repositories/More)(#1081) (#1092)

表示のみの調整。絞り込み・編集・削除・可視性トグルのロジックは不変。

- Review: フィルタを CTA 風の primary/secondary ボタンから、件数付きの
  控えめなセグメンテッドコントロールへ(選択は bg-accent-500/15、CTA と区別)。
  件数は既存のフィルタ済み配列長から算出(新規 fetch なし)。
- Repositories: Enabled/Disabled バッジと可視性トグルの緑ピルを StatusDot 流の
  dot+muted テキストへ(トグルは操作可能なまま)。(none)→ muted の em-dash、
  パスは truncate+font-mono+full-path tooltip、Edit は ghost な Pencil アイコンボタン。
- More: ExternalAppsManager 側の重複 h2「External Apps」を削除(ページ側に一本化)。
- StatusDot/status-colors.ts は不変(#1078 の領域)。生 gray/cyan/blue の追加なし。
- 既存テスト更新+件数バッジ/単一見出しの検証テストを追加。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): コンポーザーをカード化しモバイルworktreeコントロールを整理 (#1080) (#1093)

- MessageInput をコンポーザーカード化(surface/border/shadow/focus-ring)し、送信
  ボタンを入力有無で filled(bg-accent-600)/ghost に切替。プレースホルダを i18n 短縮
- Auto-Yes をコンポーザー下辺のメタ行へ統合(ui/Switch 化)。右側に Kbd キーヒント
  (#1077 の ui/Kbd 再利用)。InterruptButton のオレンジ直値を text-danger ghost へ
- モバイル sticky 行をインスタンスタブ専用化(dark: バリアント+下線インジケータ)。
  検索/End を MobileTerminalActionsSheet(ボトムシート、End は確認付き)へ移設
- MobileTabBar を GlobalMobileNav 様式(bg-background/backdrop-blur/border-border、
  上辺インジケータ、Wrench アイコン、success/warning バッジ)へ統一
- テスト: 送信ボタン状態変化/新規シートを追加、ResizeObserver polyfill(Radix Switch)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): 稼働ステータスを StatusDot 一本化し idle ノイズを縮退 (#1078) (#1094)

青スピナー(border-info)と生 span を全て統一 StatusDot に置換し、稼働ステータスの
視覚言語を1系統に。idle ノイズを縮退(Sessions は running/waiting のみラベル付き、
idle は「+N idle」カウンタ / worktree ヘッダーは idle を StatusDot のみの円形ボタン
+Tooltip、幅超過は「+N」DropdownMenu オーバーフロー)。

- WorktreeDetailSubComponents / sessions/page / MobileHeader を StatusDot へ統一
- 新規 lib/agent-status-display.ts に classifyHeaderInstances 等の導出を集約
- status-colors.ts の type:'spinner'/border-info は @deprecated 化(ReviewTab が
  最後の消費者。消滅後に削除)
- 相対時刻 tabular-nums / プレビューは CSS truncate、DnD(handleAgentDragStart)と
  タブ切替 onClick は全ボタンで温存
- 敵対レビュー由来の仕上げ: 「+N」オーバーフローに稼働中(running/waiting)があれば
  集約 StatusDot グローを出し、稼働セッションが glow-less に埋もれないように。
  同 trigger の生グレーをセマンティックトークンへ

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ネイティブ checkbox/radio を Radix プリミティブに統一 (#1076) (#1095)

OS 標準の白いフォームコントロールが dark に浮く問題を解消。@radix-ui/react-checkbox
/ react-radio-group ベースの ui/Checkbox・ui/RadioGroup を新設し、残存していた
ネイティブ input(checkbox 12箇所/11ファイル・radio 6箇所/4ファイル)を全て移行。

- 新規 src/components/ui/{Checkbox,RadioGroup}.tsx(cn()+cva・セマンティックトークン・
  focus-visible:ring-ring)、ui/index.ts に export、依存2つを追加
- checked/onCheckedChange・value/onValueChange への適応、name/value/disabled/
  aria/data-testid とラベルクリックのトグルを全移行で保持
- 移行後 native type="checkbox"/"radio" は src/ 配下 0 件(完了性 grep 検証済)
- 敵対レビュー(binding/primitive-a11y/GitDangerZonePanel 密集)0件

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): トークン規律の確立(生gray/slate置換・focus-visible統一・CIガード)(#1082) (#1096)

移行済み領域の生 gray/slate 直書きをセマンティックトークンへ置換し、フォーカス表現を
focus-visible に統一、CI grep ガードでトークン違反の再流入を止める。機能・挙動は不変
(見た目/規律のみ)。

## プリミティブ是正
- Button: secondary/ghost を muted/foreground、danger を bg-danger トークンへ。
  `focus:` → `focus-visible:` + `ring-offset-background`(dark の白ハロー解消。Tabs/Switch が手本)。
- Input/Textarea/Select トリガー: `focus:` → `focus-visible:`(枠が反応するため offset なし)。
- Badge gray → bg-muted/text-muted-foreground、Modal → surface/border/foreground。

## in-scope スイープ(生 gray/slate → トークン)
- app(worktrees を除く)/ ui / layout / home / review / repository / common。
- gray-100/200→muted、gray-400/500/600→muted-foreground、gray-700/800/900→foreground、
  境界→border、フォーム枠→input。

## globals.css / metadata / docs
- body→text-foreground、.prose インラインコード→muted/foreground、scrollbar-thin をトークン化
  (--input / --muted-foreground)、`::selection` にアクセント淡色を追加。
- layout.tsx: title template(`%s | CommandMate`)+ viewport themeColor(light/dark)。
- docs/design-system.md にフォーカス表現・CI ガード・例外リストを同期。

## CI ガード
- `.github/workflows/ci-pr.yml` に `token-discipline` ジョブを追加(CLAUDE.md size check と同方式)。
  ホワイトリスト方式で移行済みディレクトリ限定 hard-fail。除外リストをスクリプト内に明記。
  違反注入時に fail することを確認済み。

## 判断メモ(要レビュー)
- `src/app/worktrees/**`(40 occ)はオーケストレーター集計では app(108) に含まれるが、
  ワークツリー領域(#1061・「触らない」)かつ常時ダークのターミナルページ/CLI ブランド色
  (bash=bg-gray-600)を含むため、スイープ・ガード双方から除外した。#1061 完了時に拡大。
- 軽微な見た目変化: danger の red-600→red-500(--danger)、secondary/ghost の muted シェード、
  SidebarToggle はテーマ追従化、ツールチップは反転サーフェス(bg-foreground/text-background)へ
  テーマ追従化、Home の Save&Resend ボタンを accent-600 + 白文字に正規化。

品質ゲート(lint / tsc --noEmit / test:unit / build)+ 新 CI ガード 全パス。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree/共通の生gray/buttonをトークン・プリミティブへ (#1061 群A 1/4) (#1097)

#1061 のファイル分割並列(群A: 21ファイル)。生 gray/slate Tailwind クラスを
#1040/#1082 のセマンティックトークンへ用途判定で置換、生 <button> を ui/Button へ。
挙動・ハンドラ・testid は不変。群A担当ファイルの raw gray = 0。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree詳細/markdown/fileの生gray/buttonをトークンへ (#1061 群B 2/4) (#1100)

#1061 分割並列(群B: 21ファイル)。WorktreeDetailSubComponents/MarkdownToolbar/
FileTreeView 等の生 gray/slate → セマンティックトークン、生 button → ui/Button。
ターミナル出力表示面(TerminalSplitPaneContent)はダーク維持のため意図的に非トークン化。
挙動・testid 不変。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree/mobile/gitの生gray/buttonをトークンへ (#1061 群C 3/4) (#1098)

#1061 のファイル分割並列(群C: 22ファイル)。生 gray/slate → セマンティックトークン、
生 <button> → ui/Button。挙動・testid 不変。群C担当ファイルの raw gray = 0。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree/git/todoの生gray/buttonをトークンへ (#1061 群D 4/4) (#1099)

#1061 分割並列(群D: 21ファイル)。生 gray/slate → セマンティックトークン、生 button
→ ui/Button。ターミナル出力表示面(TerminalSplitContainer/TerminalContainer)は
xterm と同様ダーク維持のため意図的に非トークン化(除外)。挙動・testid 不変。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci(guard): token-discipline ガードを worktree/mobile/external-apps へ拡大 (#1061) (#1101)

#1061 で worktree/mobile/external-apps の生 gray/slate をトークン化完了したため、
#1082 で導入した token-discipline CIガードの対象ディレクトリを拡大。
ターミナル出力表示面(*Terminal* ソース)は xterm 固定ダーク(#1079)と同様の
意図的ダーク島のため除外。app/worktrees も引き続き除外。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: integration スイートの drift を解消(モック/期待値追随・CI追加)(#1102) (#1103)

CI 非対象で長期 drift していた integration スイート(41 failed)を修復。実装バグを
隠さないよう vi.mock を importOriginal 方式へ書き換え、欠落 export を補完。

- 不完全 vi.mock を importOriginal ベースへ(欠落 export を実挙動ごと通す)
- config ドリフト(アップロード上限 5MB→20MB 等)の期待値を現行定数準拠へ
- undefined.length 系はモック戻り値を実 shape へ
- 再 drift 防止に ci-pr.yml へ Integration Tests ジョブ追加
- external-apps duplicate name → 500(期待409) は実装バグのため it.skip で quarantine

結果: test:integration 643 passed / 1 skipped(0 failed)。unit/lint/tsc 緑維持。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(external-apps): 重複nameで500ではなく409を返す (#1104) (#1105)

* fix(external-apps): 重複nameで500ではなく409を返す (#1104)

route.ts の catch を message.includes('UNIQUE constraint') 文字列判定から
ExternalAppDbError.code === 'DUPLICATE' の型/コード判定へ変更。DB層のエラー
ラップ後もメッセージに依存せず 409 を正しく返す。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(external-apps): 409 duplicate name テストを un-skip(#1104 で修正済み)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(timer): Timer履歴の詳細モーダル表示と失敗理由の記録 (#1107) (#1109)

Schedule 側の execution_logs 方式を Timer へ横展開。

- DB: migration v40 で timer_messages に error 列(nullable)追加、schema 39→40
- timer-db: error 列の read/write 貫通、updateTimerStatus に optional error 引数、recoverStuckSendingTimers に固定リカバリ理由
- timer-manager: 送信失敗(stage/error)・例外(error.message)を error 列へ永続化(no_session は保存せず)
- API: GET timers レスポンスに error を追加
- UI: TimerDetailModal を新設し履歴行クリックで指示全文+メタ+失敗理由を表示(PC=Modal / mobile=FullScreenModal)
- i18n: schedule.json の timer ブロックに en/ja lockstep でラベル追加

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(files): Filesツリー展開状態のworktree単位永続化と表示リセット (#1108) (#1111)

useFileTabs / useFilePanelState の worktree 単位 localStorage パターンを踏襲。

- 新規 useFileTreeExpandedState: 展開集合を worktree 単位 localStorage(キー commandmate:file-tree-expanded:<worktreeId>) に永続化。lazy-init 復元をマウント再フェッチに乗せ、空集合はキー削除、worktree 間の漏洩なし
- useFileTabs: CLOSE_ALL / closeAllTabs() を追加
- FileTreeView: フック統合+ツールバーに「表示をリセット」ボタン(更新ボタンの隣・常時可視)+ onResetView prop
- Controller/Desktop/Mobile/Refactored: resetFileTreeView(検索クリア・全タブ閉じ・モバイルビューア閉)を配線し、初期表示に戻すフルリセットを実現

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(mobile): viewportFit:'cover' を追加しiOS safe-area対応を有効化 (#1132)

- src/app/layout.tsx の viewport エクスポートに viewportFit: 'cover' を追加。
  iOS は viewport-fit=cover 指定時のみ env(safe-area-inset-*) に非0値を
  入れるため、既存の pt-safe/pb-safe 等の safe-area 対応が実効化される
- GlobalMobileNav の未定義クラス safe-area-bottom を globals.css 定義済みの
  pb-safe に修正(cover 有効化後もパディングが効かない潜在バグ)
- viewport エクスポート値(viewportFit / themeColor)のユニットテストを追加

レンダリング検証: 本番ビルドで <meta name="viewport"> に
viewport-fit=cover が含まれることを確認済み

Refs #1131

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat(ui): ステータスカラーtintトークン導入とダーク非対応フィードバックUIの移行 (#1133)

- globals.css / tailwind.config.js に success/warning/danger/info × subtle/border/foreground の12トークンを両テーマで追加(ライト *-50 面 + *-800 前景、ダーク *-950 面 + *-300 前景、全て WCAG AA 準拠。根拠はコメントに記載)
- Toast / DefaultErrorFallback / History・Prompt・ConnectionErrorFallback / ErrorDisplay / PromptPanel を新トークンへ移行し、生パレット + dark: ペアを撤去(TerminalErrorFallback は常時ダーク島のため現状維持)
- tint 面内のソリッドアクションボタンは反転 tint(bg-{status}-foreground text-{status}-subtle)で両テーマ AA を確保
- docs/design-system.md にトークン表・使用ルールを追記
- tint トークン定義・移行 grep を検証する status-tint-tokens.test.ts と ErrorBoundary/フォールバックのレンダリングテストを追加、既存 Toast / design-tokens テストを追随

Refs #1112

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* refactor(ui): window.confirmを共通ConfirmDialogへ全面置換 (#1134)

- ConfirmDialog/ConfirmProvider/useConfirm を src/components/ui/ConfirmDialog.tsx に新設
  (variant default/danger、Escape/背景クリックでキャンセル、danger時はcancelに初期フォーカス、
  クローズ後に呼び出し元へフォーカス復帰)
- AppProviders に ConfirmProvider をマウント
- window.confirm 残存11箇所 + bare confirm 1箇所(WorktreeCard)を useConfirm へ置換
- confirm文言を next-intl 辞書へ移動 (review namespace 新設、home/worktree/common に追記)
- ESLint no-restricted-globals(confirm/alert) と no-restricted-properties(window.confirm) を追加
  (bare alert 3箇所は window.alert へ変更、alert置換自体は対象外)
- 既存テストを window.confirm モックからダイアログ操作へ追随、
  ConfirmDialog 単体テストと Timer履歴クリアの結合フローを追加

Refs #1113

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* chore(ui): UI死コード削除(WorktreeList・放棄infinite-scroll hooks・hasNewOutput配線) (#1135)

- WorktreeList.tsx(production importゼロの孤児コンポーネント)とbarrel export(index.ts)を削除
- 放棄された無限スクロール実装(useInfiniteMessages/useScrollObserver/useScrollRestoration)と
  対応テスト3本、孤児化するtypes/infinite-messages.tsを削除
- WorktreeDetailRefactored.tsxのhasNewOutput={false}死配線を撤去(MobileTabBar側は維持)
- 残置ファイルのstaleコメント修正(useVisibilityRecovery.ts/history-display-config.ts)
- useWebSocket.tsに@deprecated注記を付与(WebSocketストリーミング化で再設計・再利用予定)
- docs/architecture.md(ja/en)の削除対象ファイルへのstale参照行を削除

受入基準: 3グレップ全て0件成立、lint/tsc/test:unit(8746 pass)/build全パス

Refs #1115

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* fix(ui): 未定義animate-fade-*の定義とModal/Toast/ContextMenuのexitアニメーション実装 (#1114) (#1136)

* fix(ui): 未定義animate-fade-*の定義とModal/Toast/ContextMenuのexitアニメーション実装

- tailwind.config.jsにfade-in/fade-outキーフレームとanimate-fade-in/outを定義。
  usePromptAnimationが発行していたが未定義でno-opだった。クラス名を
  PromptPanel等が文字列比較で消費しているため、tailwindcss-animateへの
  書き換えではなくキーフレーム定義の追加を採用。duration/easingは
  --motion-duration-base / --motion-ease-out(#1050モーショントークン)準拠
- content globsにsrc/hooksを追加(フック発行クラスのpurge防止)
- useExitAnimation(open, duration)フックを新設: 閉要求後もduration msだけ
  マウントを維持しexitアニメーション再生を保証。render-phase同期により
  開閉ともに1フレームの遅延なし。再open時はタイマー解除
- Modal: data-state=closedでfade+zoom-out exit(200ms)後にアンマウント。
  exit中はbackdropクリック無効化
- Toast: fade+slide-out exit(200ms)再生後にonClose通知(アンマウント遅延)
- ContextMenu: fade+zoom-out exit(100ms、enterと同duration)。
  exit中はpointer-events-none
- EXIT_ANIMATION_DURATION_MS / CONTEXT_MENU_EXIT_DURATION_MSを
  ui-feedback-config.tsに追加しCSSとJSタイマーを同期
- reduced-motionはglobals.cssのグローバルキルスイッチで対応(CSS実装のみのため追加ガード不要)
- テスト: useExitAnimation単体、Modal/Toast/ContextMenuのexit挙動
  (閉操作後にアニメーション時間経過までDOM残留→アンマウント)、
  Tailwind実コンパイルによるCSS生成検証を追加

Refs #1114

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ui): ConfirmDialogをModal exitアニメーションに追随

- ConfirmDialog.test.tsx: 閉操作後の即時DOM除去期待を、200ms exit
  アニメーション完了後のアンマウント(waitForElementToBeRemoved)に更新
- ConfirmProvider: pendingクリア後のexit再生中にdescription等が空文字に
  なり内容がブランク表示される統合ドリフトを修正(直近optionsを保持し
  フェードアウト中も表示を維持)。cancelテストにブランク防止の検証を追加

Refs #1114

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat(ui): ヘッダーナビのアクティブインジケータ実装と角丸スケール規約の導入 (#1137)

- Header ナビに CSS-only の下線インジケータを実装(after 疑似要素の
  scale-x トランジション、--motion-ease-out / duration-200 準拠、
  motion-safe ガード)。アクティブ項目に aria-current="page" を付与
- Header のアクティブ状態レンダリングテストを追加(全 6 ルート)
- docs/design-system.md に角丸スケール規約を独立セクションとして追記
  (コントロール=rounded-md / コンテナ=rounded-lg / ポップアップ=rounded-md /
  チップ=rounded-full / 小型インライン=rounded-sm、裸 rounded 原則禁止)

角丸統一の実施範囲(明確な逸脱のみ、feature 配下の一括置換はしない):
- ui/Kbd・ui/Checkbox: 裸 rounded → rounded-sm
- layout/Sidebar のアイコンボタン3箇所: 裸 rounded → rounded-md
- layout/Header のコマンドパレットトリガー: rounded-lg → rounded-md

Refs #1119

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* refactor(ui): チャット履歴UIをConversationPairCard系デザインへ統一 (#1138)

MessageList/PromptMessageの視覚言語をConversationPairCardに統一
(案B: 構造・optimistic updateロジック維持、見た目のみ刷新)。

- 生chromatic色21箇所をステータスtintトークン(warning/success/info x
  subtle/border/foreground)へ移行し、生色のdark:ペアを撤去
- インラインSVGをlucide-reactへ統一(Check/X/CircleCheck/Clock/Loader2/
  MessageCircle)
- ユーザー/アシスタントメッセージをCPC言語のセクション(accent左ボーダー/
  surface-2 wash)+ hover-revealコピーツールバーへ刷新
  ([@media(hover:none)]:opacity-100 タッチフォールバック付き)
- markdown描画をprose→assistant-md(トークン追従)へ移行、ANSI/コード
  ブロックの常時ダーク島(#0d1117)は現状維持
- ファイルパスリンク/プロンプト応答(Yes/No・選択肢・テキスト入力)/
  ログリンクの機能パリティ維持、レンダリングテストを新規追加

Refs #1117

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat(ui): スケルトンローディング展開とSpinner統一・loading.tsx整備 (#1139)

- ui/Spinner.tsx 新設: 手書きインラインSVG/border系スピナー43箇所を共通プリミティブへ置換
- 主要画面にレイアウト整合スケルトンを実装(Home Session Overview/ToDo、Sessions一覧、Repositories一覧、HistoryPane(汎用Card型・loading分岐のみ)、ファイルツリー、AssistantChatPanel、Reviewタブ群)
- 裸テキストローディングをスケルトンへ置換(MessageList.tsxは#1117衝突回避のため対象外)
- 初回ロードのみスケルトン表示(再fetch時は既存コンテンツ維持の非ブロッキングパターン踏襲)
- 主要7ルートに loading.tsx(薄いRouteLoadingフォールバック)を追加
- AssistantChatPanelのローディングを導出状態化し空状態フラッシュを解消

Refs #1118

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* refactor(ui): 生chromatic色をtintトークンへ全面移行しCIガードを拡大 (#1140)

意味ベースでステータスtintトークンへ移行 (red→danger / green→success /
yellow・amber・orange→warning / blue・sky・purple・violet→info)。
淡色面は bg-{s}-subtle + border-{s}-border + text-{s}-foreground、
ソリッドは bg-{s} を使用し、chromatic用途の dark: ペアを撤去。

- ui/Badge.tsx・config/status-colors.ts をトークン化 (生パレットクラス撤去)
- error/ の常時ダーク島 TerminalErrorFallback を *Terminal* 名の別ファイルへ分離し、
  残る error/・auth/ の生gray/chromatic漏れを回収
- 検索ハイライト/ファイル種別アイコン等の装飾色もトークン化 (text-pink等スコープ外色は維持)
- CIガード token-discipline の grep パターンに chromatic 9色を追加し error/・auth/ を対象化。
  既存の *Terminal*・テスト・常時ダーク島 (worktrees/[id]/terminal 等) の除外は維持
- docs/design-system.md に chromatic 禁止/例外リストとCIガード仕様を追記
- 影響する class 検証テストを追随
  (Badge / ContextMenu / MarkdownEditor / MemoCard / dark-mode-foundation / recursive-delete)

chromatic grep 残数
(git grep -cE '(bg|text|border|ring)-(red|green|yellow|amber|orange|purple|violet|sky|blue)-[0-9]' -- src/):
  before: 254 行 (71 ファイル)
  after:  19 行 (サンクション済み例外のみ = *Terminal* 群 + src/app/worktrees/[id]/terminal/)
  in-scope (例外除く): 0

品質ゲート: npm run lint / npx tsc --noEmit / npm run test:unit (8818 passed) / npm run build 全パス

Refs #1116

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): View Transitions APIによるページ遷移クロスフェードを導入 (#1141)

主要ルート間(Home/Chat/Sessions/Repos/Review/More/worktree詳細)のコンテンツ
領域をクロスフェードするプログレッシブエンハンスメントを追加する。

実装方式は「自前の薄いラッパ(案B)」を採用し、next-view-transitions等の外部
依存は追加しない。理由:
- 受入基準が機能検出ガード/reduced-motion無効化の単体テストを要求しており、ガード
  関数を自前で保持することで直接テスト可能になる(ライブラリ委譲では不可)
- framer-motion不採用方針(docs/design-system.md #1050)と同様にCSS駆動とし、JSは
  startViewTransition呼び出しのガードのみに限定してバンドルを軽量に保つ
- Next15/React19ネイティブView Transitionsへの載せ替えが容易(抽象が薄い)
- ライブラリ導入時のLink全面置換を避け、変更をnavシェルとプログラム遷移に限定

主な構成:
- src/lib/view-transitions: supportsViewTransitions/prefersReducedMotion/
  startViewTransition。非対応・reduced-motion時は即時遷移フォールバック
- ViewTransitionsProvider: startViewTransitionをApp Routerナビゲーションへ橋渡し。
  usePathnameのコミット監視で新DOM確定後にresolveし、同一ルート等の非コミット時は
  COMMIT_SAFETY_TIMEOUT_MS=500で確実にsettle
- TransitionLink / useViewTransitionRouter: Header・GlobalMobileNav・Sidebarで使用。
  修飾クリック/別タブ/外部/同一ルートはブラウザ既定にフォールスルー
- globals.css: [data-view-transition="content"](AppShellの<main>のみ)をクロス
  フェードし、永続シェル(ヘッダー/サイドバー/ボトムナビ)はrootを固定して除外。
  ::view-transition背景を--backgroundに設定しダークテーマの白フラッシュを防止。
  200ms(--motion-duration-base)/ --motion-ease-out準拠、reduced-motionで無効化

Refs #1122

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* perf(ui): HistoryPaneを@tanstack/react-virtualで仮想化し250件上限を撤廃 (#1123) (#1142)

* perf(ui): HistoryPaneを@tanstack/react-virtualで仮想化し250件上限を撤廃

履歴リスト(HistoryPane)を仮想化し、可視領域+オーバースキャンのみを
マウントするよう変更。従来はカードをフラット描画し、250件上限は性能緩和策
だった。仮想化により描画コストが O(可視行) となり、上限を1000へ緩和した。

主な変更:
- FilePanelContent の流儀に揃え useVirtualizer + measureElement(可変高)で
  ConversationPairCard を仮想描画。展開/折りたたみ・コードブロック等の高さ
  変化は measureElement が自動再計測。
- スクロール位置維持: 追記は react-virtual が自動維持。最下部閲覧中
  (isNearBottom)のみ新規ペアを追従(follow/maintain を分離)。
- 検索ジャンプ整合: 対象行を scrollToIndex で materialize してからハイライト
  適用。マウント窓の変化(renderedRange)でハイライトを再適用。
- カードのアンマウントで展開状態が飛ばないよう、展開状態はリスト側/親
  (useConversationHistory + 検索auto-expand)で保持(#1121 の pending 挿入も
  この item 管理の上に載せやすい構成)。
- 250件上限を緩和: HISTORY_DISPLAY_LIMIT_OPTIONS に 500/1000 を追加
  (MAX_MESSAGES_LIMIT=1000 に自動追従、APIの取得上限も1000へ)。
- src/lib/history-virtualization.ts: overscan/estimate/しきい値の定数と純粋な
  isNearBottom を分離しユニットテスト可能に。
- テスト: jsdom は無レイアウトのため offsetHeight を与える helper
  (tests/helpers/virtual-layout.ts)と1000件fixtureを追加。仮想化のDOM件数・
  追従/維持判定・展開折りたたみ・検索名前空間を網羅。

1000件fixture before/after(jsdom初期描画。体感FPSは手動検証):
- マウントカード数: 1000 → 16
- DOMノード数: 26,001 → 449(約58分の1)
- 初期描画時間: 289ms → 24ms(約12倍高速)

品質ゲート: lint / tsc --noEmit / test:unit(8855 passing) / build 全パス。

Refs #1123

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ui): HistoryPane仮想化のゼロ計測フォールバックで本文が消える問題を修正

WorktreeDetail経由の統合テスト(jsdomはレイアウト高さ0)で virtualizer が
アイテムを0件しか materialize せず、メッセージ本文が描画されず getByText が
失敗していた(tests/integration/worktree-detail-integration.test.tsx の
'renders user and assistant messages')。

修正: virtualizer がビューポート未計測でアイテム0件のとき(初回レンダー/SSR/
jsdom 等のレイアウト0環境)、先頭 HISTORY_FALLBACK_RENDER_COUNT 件を通常フロー
で描画するフォールバックを HistoryPane に追加。実ブラウザでは layout-effect の
計測直後に仮想化リストへ切り替わる(フォールバック描画は塗り前の一時コミット)。
本番の仮想化保証を損なわないよう件数は小さく固定。SSRのハイドレーション整合
(server/client双方が同じ先頭スライスを描画)にも寄与する。

単体テスト側は既存の virtual-layout ヘルパーで実 virtualizer 経路を検証し続ける
ため、この修正はそれらに影響しない。

品質ゲート: lint / tsc --noEmit / test:unit(8855) / test:integration(648) /
build 全パス。

Refs #1123

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(realtime): ターミナル出力・セッションステータスをWebSocketストリーミング化 (#1120) (#1143)

* feat(realtime): ターミナル出力・セッションステータスをWebSocketストリーミング化

実装済みだが未接続だったWebSocketインフラをクライアントへ接続し、ポーリングを
フォールバックとして残しつつサーバ→クライアントの一方向pushへ移行する。

- useWebSocket再設計: 単一WS接続・指数バックオフ再接続(1s..30s)・visibility連携・
  room購読再送・エンベロープparse。RealtimeProvider/useRealtimeで共有1本化+
  listener fan-out+購読refカウント(Provider未装着時はpollingフォールバック)
- セッションステータスpush: send時にrunning遷移をbroadcast、kill時のstopと合わせ
  サイドバー状態ドットを即時反映。WS接続中はuseWorktreesCacheのポーリングを間引き
- ターミナルストリーミング: response-poller tickからterminal_snapshotをpush
  (buildCurrentOutputでpull/push一致、購読者0でtmux captureをスキップ)。
  WS接続中はuseTerminalPanePollingを15sフォールバックへ間引き、version guardで
  stale破棄(既存stale-response/visibility堅牢性を維持)
- 差分適用: computeTerminalUpdateで追記/リセットを判定しTerminalDisplayを
  keyed chunk追記化、全置換を廃止してテキスト選択を維持(ANSI境界退避付き)
- モバイル新着出力バッジをpush駆動で復活、接続状態インジケータを追加
- 認証: Cookie自動付与でWS upgrade、認証失敗時は既存機構が接続拒否

テスト: useWebSocket(購読/再接続/バックオフ)・差分適用(追記/リセット/ANSI境界)・
WS切断→ポーリング復帰→WS再接続の統合テスト・認証拒否のE2E統合テストを追加。
lint/tsc/test:unit/test:integration/build 全パス。

Refs #1120

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(realtime): useRealtimeConnection等のNULバイト混入を除去

useRealtimeConnection.tsx の room購読キー生成で空白(0x20)が2箇所NULバイト
(0x00)に化けており、gitがバイナリ扱いしdiff/レビュー不能になっていた。
本来の ' ' 区切り(useWorktreesCache と同一パターン)へ復元し、ロジックは不変。

- src/hooks/useRealtimeConnection.tsx: `.join('\0')`/`.split('\0')` → `.join(' ')`/`.split(' ')`
- src/components/worktree/FileTreeView.tsx: 既存のセンチネル `'\0unavailable'` から
  NULを除去し `'unavailable'`(JSON.stringify結果と衝突しない性質は維持)

src/配下のNULバイト混入は0件を確認。lint/tsc/test:unit/test:integration/build 全パス。

Refs #1120

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(chat): メッセージ送信のOptimistic UIを実装 (#1144)

送信瞬間に pending 吹き出しを履歴末尾へ即時挿入し、サーバ echo で実データへ
照合置換(重複表示なし)、失敗時はエラー表示+再試行/破棄する Optimistic UI を
追加。

- usePendingMessages 層: serverMessages に pending をマージし ChatMessage
  (optimisticState 付き)として表示側へ渡す。content 一致+作成時 baseline 外+
  consumedServerIds の一意消費で照合(連投 1:1/既存同一文言の誤照合回避)。
  API reject/timeout(30s) で error→retry/discard。
- ConversationPairCard/HistoryPane: sending(opacity低下+スピナー)と
  error(danger トークン+Retry/Discard)を描画。pending 中は
  「Waiting for response…」を抑止。
- MessageInput: onOptimisticSend 提供時は送信を委譲し composer を即時クリア
  (draft も除去)。他サーフェスは従来の await→clear を維持。
- TerminalSplitPaneContent: 各 split の useSplitMessages に usePendingMessages を
  重ね、送信→即時表示→onSent refetch で照合。破棄時は既存 insert 経路で draft 復元。
- 仮想化リストと整合: pending も pair 化され virtualizer item として最下部追従。

テスト: usePendingMessages(挿入/照合/失敗/連投/タイムアウト/retry/discard)、
ConversationPairCard(sending/error/retry/discard)。

Refs #1121

* fix(mobile): useIsMobileを描画前レイアウトエフェクトで解決し初回デスクトップ描画フリップを解消 (#1145)

## 事象
useIsMobile がSSR安全のため初期値 false で始まり、実際の検出を
passive な useEffect(ペイント後実行)で行っていたため、モバイル実機の
ハードリロードで毎回デスクトップUIが一瞬描画されてからモバイルUIへ
フリップしていた(AppShell の固定サイドバー / WorktreeDetail の
デスクトップ⇔モバイル分岐でのフラッシュ・CLS)。

## 対策の選定(案A/B/C のトレードオフ)
- 案A(CSSで両ツリーを出し分け): 初回フレームは正しいが、重い
  WorktreeDetail が二重マウントし、両分岐の terminal ペインが同時に
  ポーリングを開始する。ヘビーツリーには不採用。
- 案B(UAヒント+cookie): ブレークポイントは viewport 幅基準
  (max-width:767px)で UA の mobile とは一致せず(狭幅デスクトップ/
  タブレット)、真の hydration mismatch を再導入する。不採用。
- 案C(採用・描画前レイアウトエフェクト版): SSR/初回クライアント
  レンダリングは決定的に false(デスクトップ)を返し SSR HTML と一致
  するため hydration mismatch 警告は出ない。検出は useLayoutEffect
  (isomorphic)で行い、commit 後・ブラウザ描画前に flush されるため、
  補正後の isMobile 値が最初のフレームに反映される。モバイルでは
  デスクトップツリーは描画される前に差し替えられる。

## ヘビーツリーの二重マウント / ポーリング配慮
- 主ポーラーは分岐に依存しない単一の useWorktreeDetailController が
  一度だけ生成する(分岐は表示のみ)。
- 非表示側の表示ツリーは同一 commit 内でペイント前に破棄されるため、
  passive な useEffect ベースの terminal ポーリングは発火しない。
- 主要なコールドロード経路(キャッシュ無し→LoadingIndicator)では
  どちらの分岐の子もマウントされない。

## その他
- SSR での useLayoutEffect 警告は isomorphic パターン
  (server では useEffect)で回避。
- 既存の useIsMobile 単体テスト(初期値・リサイズ切替)は不変で継続。
- hydrateRoot を用いた新規テストで「hydration mismatch 警告が出ない」
  「モバイルビューポートでモバイル値へ解決」「デスクトップは false 維持
  (回帰なし)」を自動検証。

品質ゲート: lint / tsc --noEmit / test:unit / test:integration / build 全て成功。

Refs #1126

* feat(pwa): PWA対応(manifest+Service Worker+インストール可能化) (#1146)

Web App Manifest(manifest.ts)、手書きService Worker、インストール可能化を
実装。「外出先からエージェントを監視する」プロダクト特性に沿ってホーム画面
から独立アプリ(standalone)として起動可能にする。

Service Worker技術選定: 手書きvanilla SW(next-pwa/Serwist/Workbox不採用)。
理由:
- custom server(server.ts)がWebSocket upgradeを保持しており、next-pwa等が
  前提とするデフォルトNextサーバ構成と噛み合わない
- 本Issueが要求するallowlist方式(キャッシュ対象を明示列挙)を直接表現でき、
  監査容易・小規模・後続#1125 Web Pushの拡張が容易

キャッシュ戦略(allowlist):
- /api・/login・/proxy・非GET・クロスオリジン → network-only(キャッシュ禁止)
- /_next/static/・/icons/・manifest/favicon → cache-first
- navigation → offline-fallback(認証コンテンツを避けページ本体は非キャッシュ)
判定はcache-policy.ts(単体テスト済み)を単一情報源とし、public/sw.jsが同定数を
鏡写し、sw-file.test.tsで両者の同期を担保。

更新戦略: install時はskipWaitingせず、更新検知でトースト表示→リロードで
SKIP_WAITINGを送信しcontrollerchangeで新版に切替。

その他:
- 本番のみSW登録(shouldRegisterServiceWorker)、dev/test・非セキュアは無効化
- オフラインフォールバック画面(/offline、トークン準拠、再接続ボタン)
- iOS standalone用にappleWebAppメタ(capable/title/status-bar-style)を付与
- manifest/sw/offlineをAUTH_EXCLUDED_PATHSに追加(auth有効時もinstallable/
  precache可、いずれも非機密リソース)
- HTTPS/LAN配信時のSW/インストール制限をREADMEに注記

Refs #1124

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(mobile): モバイルA11y改善(focus trap・44pxタップターゲット・tablistキーボード) (#1147)

モバイルのアクセシビリティ/エルゴノミクスの穴を塞ぐ。

focus trap は共通フック useFocusTrap に1実装集約:
- 判断理由: Modal/シート類は独自ポータル+enter/exitアニメ(useExitAnimation
  /usePromptAnimation)+スワイプ dismiss を持つ bespoke 実装。@radix-ui/
  react-dialog は未導入で、置換すると3面すべてとそのテストを書き直す必要がある。
  共通フックなら既存機構を壊さず trap のみ追加でき、issue の「1実装に集約」に沿う。
- 挙動: 初期フォーカスをダイアログへ移動 / Tab・Shift+Tab をダイアログ内で循環 /
  閉時に呼び出し元へフォーカス復帰。autoFocus 等で消費側が内部フォーカスを
  管理している場合は初期フォーカス奪取・復帰の両方をスキップ(ConfirmDialog 互換)。
- 適用: ui/Modal(role=dialog/aria-modal/aria-labelledby も付与)・
  MobilePromptSheet・MobileTerminalActionsSheet。

44px タップターゲット(視覚サイズは不変、ヒットエリアのみ拡大):
- エージェントインスタンスタブ(WorktreeDetailRefactored)
- History サブタブ Message/Git(WorktreeDetailMobile)
- MobileTabBar の各タブ

MobileTabBar に ARIA tabs パターン完成:
- 矢印キー(左右/上下)+Home/End 移動、ラップアラウンド
- roving tabindex(activeタブのみ tabindex=0)、選択はフォーカス追従

touch-manipulation をボタン系プリミティブ(ui/Button)とモバイル操作面へ付与し
ダブルタップズームの遅延を抑止。

title のみだった WorktreeCard のお気に入りトグル(アイコンのみ)へ aria-label を
付与し、タッチ/スクリーンリーダーからも到達可能に。

スワイプジェスチャー等は本Issueスコープ外のため変更していない。

Refs #1127

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(mobile): ジェスチャー強化(スワイプタブ切替・pull-to-refresh・キーボード追従) (#1148)

- useSwipeGesture に axis/方向ロック/エッジ開始制限/onSwipeMove を後方互換で追加
  (既存 MarkdownEditor の四方向スワイプと #299 スクロール抑制は不変)
- worktree詳細のモバイルタブを水平スワイプで切替。方向ロックで縦スクロール中は
  無効化、しきい値60px、ターミナルタブはエッジ開始領域に限定して誤発火を抑止。
  MobileTabBar インジケータは activeTab 共有で自動同期
- Sessions/Repositories に pull-to-refresh を新設(usePullToRefresh + PullToRefresh)。
  最上部のみ発火し overscroll-behavior:contain + preventDefault でネイティブPTR二重発火を防止
- MessageInput を useVirtualKeyboard に接続しキーボード追従(keyboardAware opt-in)、
  入力要素へ enterKeyHint/inputMode を付与
- MobilePromptSheet のアドホックスワイプを useSwipeGesture へ統合(focus trap ref を
  マージして #1127 の挙動を維持、finger-follow は onSwipeMove で継続)

Refs #1128

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(pwa): エージェント状態のWeb Push通知(プロンプト待ち/完了) (#1149)

- push_subscriptions テーブル(migration v41)と CRUD モジュールを追加
- VAPID/web-push 送信層: 種別フィルタ・デバウンス・410自動削除、機密(endpoint/秘密鍵)は非ログ
- プロンプト検出/セッション完了時に response-checker からファンアウト(検出ロジックは不変・fire-and-forget)
- 購読登録/解除/種別設定/VAPID公開鍵の認証必須API(グローバルmiddleware配下)
- Service Worker に push/notificationclick ハンドラ(該当worktreeへdeep link・…
Kewton added a commit that referenced this pull request Jul 18, 2026
* feat(files): スマホ版でファイルをダウンロード可能に(attachment配信+FileViewer DL導線)(#1024) (#1025)

スマホ版のファイルビューア(FileViewer, Issue #438 でモバイル専用化)から
任意種別のファイルをダウンロードできるようにする。

- B(サーバ): files/[...path] GET に download 分岐を追加し、生バイトを
  Content-Type: application/octet-stream + Content-Disposition: attachment で返却。
  filename は src/lib/http の content-disposition ヘルパーで RFC 準拠にサニタイズ
  (ASCII フォールバック + filename*=UTF-8'' パーセントエンコード、ヘッダ
  インジェクション回避)。既存 getWorktreeAndValidatePath でパス検証を再利用。
  base64 JSON を経由しないためプレビュー上限を超える大容量にも対応
- A(UI): FileViewer ツールバーにダウンロードボタンを追加し、同一オリジンの
  download URL へ遷移(iOS Safari 含めネイティブDLに乗せる)
- テスト新規29本(download API のヘッダ/パス検証/filename、content-disposition
  ヘルパー、FileViewer ボタン)
- C(独立ページのPDF分岐/DLリンク)は範囲外(モバイル動線が独立ページを使わないため)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(session): Timer 発火の送信を History に記録(send ロジックを共有サービス化)(#1028) (#1029)

Timer 機能(Issue #534)から送った指示が History(chat_messages)に残らない
問題を修正。executeTimer が cliTool.sendMessage だけを呼び createMessage/
startPolling をスキップしていたのが原因(#947 の委譲変更が起点)。

- send route L262-385 のインライン記録ロジックを src/lib/session/
  send-user-message.ts の共通関数 sendUserMessage() として抽出
  (savePendingAssistantResponse → send(画像/Copilot分岐含む)→ createMessage
  → updateLastUserMessage/orphan処理 → startPolling)。HTTP 層非依存
- send route POST を sendUserMessage() 利用へリファクタ(挙動不変)
- timer-manager.ts executeTimer を sendUserMessage() 経由に変更。
  timer_messages の status 遷移(sending/sent/failed)は維持
- テスト: sendUserMessage / executeTimer のユニット新規9件 + timer 既存更新(計26件)

これにより Timer 送信も user メッセージと assistant 応答の両方が History に残る。

スコープ外(別Issue推奨):
- tests/integration/api-send-cli-tool.test.ts の1件は base でも失敗する既存不具合
  (2引数アサート vs 3引数呼び出し)で本修正と無関係(CI の test:unit 対象外)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(todo): ブランチ単位ToDoを3状態化(未着手/仕掛/完了)(#1032) (#1033)

- migration v38: worktree_todos に status カラム追加(done=1→'done' backfill、CURRENT_SCHEMA_VERSION 37→38)
- DB層: status を真実源に done := status==='done' を派生(後方互換維持、writer整合)
- API/クライアント: PATCH で status ∈ {todo,doing,done} を検証・適用、done後方互換
- UI: 3状態巡回チップ+色バッジ+状態別件数、TodoPane全体を next-intl 化
- i18n: locales/{en,ja}/worktree.json に todo namespace 追加
- テスト: 既存5更新+新規 migration-v38(backfill検証)

Closes #1032

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(todo): ブランチ単位ToDoに詳細フィールド追加+クリックで件名/詳細を編集 (#1034) (#1035)

- migration v39: worktree_todos に detail カラム追加(TEXT NOT NULL DEFAULT ''、既存行は空詳細で移行)
- DB層: WorktreeTodo に detail 追加、create/update で detail 受理
- API: POST/PATCH で detail を検証・適用(MAX_TODO_DETAIL_LENGTH 新設)
- クライアント: WorktreeTodoItem.detail、create/update の detail 対応
- UI: 項目クリックで編集モーダル(Modal.tsx再利用)を開き件名+詳細を編集、詳細ありインジケータ
- i18n: locales/{en,ja}/worktree.json に詳細/編集の文言追加
- テスト: 既存6更新+新規 migration-v39、docs/module-reference.md 更新

Closes #1034

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(todo): ブランチ単位ToDoの詳細にコピーボタンを追加 (#1036) (#1037)

- UI: 編集モーダルの詳細欄に CopyButton を配置し詳細本文をコピー(詳細が空のときは非表示)
- 既存 src/components/common/CopyButton.tsx / clipboard-utils.ts を再利用(スマホHTTPは execCommand フォールバック)
- i18n: locales/{en,ja}/worktree.json に todo.copyDetail 追加
- テスト: TodoPane.test.tsx にコピー導線テスト3件追加
- migration/DB/API 変更なし(UIのみ)

Closes #1036

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): セマンティックデザイントークン基盤を導入 (#1041) (#1054)

globals.css に :root(light)/.dark(dark) の CSS 変数
(background/foreground/surface/muted/border/input/ring/accent-50..700/
success/warning/danger/info) を定義し、tailwind.config.js の primary(cyan)・
cmd-bg-dark を semantic トークンへ置換。layout.tsx / MainLayout.tsx の
body 背景を bg-background に統一。既存の実効値を写し取り視覚は不変(no-op)。
docs/design-system.md を新設。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): cn()/cva によるバリアント管理基盤とプリミティブ改修 (#1042) (#1055)

clsx + tailwind-merge の cn() ヘルパーと class-variance-authority を導入し、
Button/Card/Badge/Modal を cva ベースへ移行。Button ghost バリアントの
dark: 指定漏れ(ダークモードで視認性劣化)を修正。既存 API は完全互換。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): アイコンを lucide-react に統一 (#1044) (#1056)

絵文字アイコン(🤖⚡✦💻)を廃止し lucide アイコンへ置換。共通レイアウト・ナビ
(Header/GlobalMobileNav/MobileTabBar/ActivityBar/terminal page)および worktree
各所のインライン SVG を lucide に統一。docs/design-system.md にアイコンサイズ規約を追記。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): next/font で Geist Sans/Mono を導入 (#1043) (#1057)

geist パッケージ(セルフホスト)で GeistSans/GeistMono の CSS 変数を <html> に適用し、
tailwind fontFamily の sans/mono を「Geist → 日本語(Hiragino/Noto Sans JP) →
システム」フォールバックで構成。Terminal.tsx(xterm)の等幅も Geist Mono を先頭指定。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): RadixベースUIプリミティブを拡充 (#1046) (#1058)

Input/Textarea/Select/Tabs/Tooltip/DropdownMenu/Switch/Skeleton を追加
(cva + cn, セマンティックトークン着色, ライト/ダーク対応)。Portal 系は
'use client' + Z_INDEX(POPOVER)で Modal と階層整合。Sessions ページの
フィルタ/ソートを新プリミティブへ移行(適用サンプル)。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): アクセント色を統一しセマンティックトークンへ置換 (#1045) (#1059)

cyan/blue 直書き(~600箇所 / 131ファイル)を #1041 のセマンティックトークンへ置換。
用途判定: インタラクティブ=accent / フォーカス=ring / 情報=info。gray 系は
surface/background/border/muted へ実効値を保って置換。例外(Terminal.tsx の
xterm ANSI 配色 / CLI ブランド色 / .scrollbar-thin 等の固定装飾色)は維持。
globals.css の .btn-primary・検索ハイライト等もトークン化。
敵対的レビュー指摘(focus:ring-accent→focus:ring-ring 統一、design-system.md
の例外記述修正)を反映。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): 主要ページを共通UIプリミティブへ移行 (#1047) (#1060)

layout(Header/Sidebar の検索・トグル・言語)/ home / repositories / review /
more / chat / login の生button・手組みフォーム・コピペカードを @/components/ui
プリミティブへ移行。機能・挙動(onClick/フォーム送信/バリデーション/キーボード)は維持。
敵対的レビュー指摘(chat系ボタン移行・ghost の dark hover)を反映。ネイティブ select・
複合 textarea 入力バー・極小アイコントグルは意図的に残置(PR の残置リスト参照)。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): globals.css の @apply クラスを廃止し cva プリミティブを自己完結化 (#1048) (#1062)

.btn/.btn-*/.card/.card-hover/.badge*/.input を globals.css から削除し、
Button/Card/Badge の cva 定義へ実クラスを内挿(旧 @apply 定義と同値、視覚不変)。
src/ 全体で削除クラスへの参照が 0 であることを確認。worktree の一部
(WorktreeDetailRefactored/LogViewer/WorktreeList/ExternalAppForm)をプリミティブへ移行し、
マジックナンバー入りインライン style を定数化。worktree/mobile/sidebar/common の
一括プリミティブ移行は規模の都合により #1061 へ分割。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): サーフェス・奥行きを再設計しモダンダークテーマへ刷新 (#1049) (#1063)

globals.css の :root/.dark トークン値を刷新(深い base + 微リフト surface +
ヘアライン border)。Header/GlobalMobileNav に backdrop-blur(supports-[backdrop-filter]
フォールバック付)。Card に elevated/interactive variant、h1-h4 見出しタイポを調整。
敵対的レビュー指摘を反映: 主要閲覧画面のコンテンツカード(sessions/home/review)を
bg-surface/border-border へ移行し背景反転による溶け込みを解消、ダーク入力を surface-2 で
凹み表現、Card interactive にキーボード focus 対応を追加。コントラストは WCAG AA 全 pass。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): モーション基盤とマイクロインタラクションを追加 (#1050) (#1064)

tailwindcss-animate を導入し、Modal/MobilePromptSheet の enter/exit、Radix data-state
連動(DropdownMenu/Select/Tooltip)、一覧の stagger、hover lift を統一適用。
prefers-reduced-motion 対応。ターミナル/仮想スクロールには非適用。敵対レビュー指摘
(reduced-motionでのtranslate漏れ→motion-safe化、sessionsエラー時のstagger再発火→list
マウント維持、Tooltip instant-open enter、home focus parity)を反映。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): Home をダッシュボード(bento グリッド)化 (#1052) (#1065)

Home を縦積みから bento グリッドへ再構成(統計/直近セッション/ToDo/クイックアクション)。
PC は CSS Grid(grid-cols-12 + col-span)、モバイルは1カラム縦積み(useIsMobile の JS 分岐を
増やさず Tailwind breakpoint)。既存 sessions 取得フック/API を再利用し新規 API なし。既存機能
(ToDo 追加/チェック/コピー、セッションカウント)を維持。空状態対応。敵対的レビュー指摘
(ToDo タイルの h-full・見出しパリティ)を反映。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): コマンドパレット(⌘K)を追加 (#1053) (#1066)

cmdk で ⌘K(mac)/Ctrl+K(win) のコマンドパレットを追加(Navigation / Worktrees 検索 / Actions)。
input/textarea/ターミナル入力中は非発火、SSR 安全('use client')、i18n(en/ja)、Modal 相当の z-index。
モバイルは GlobalMobileNav に起動導線。敵対的レビュー指摘を反映: open 時に入力へフォーカスし
キーボード操作(type/矢印/Enter)を復旧[HIGH]、worktree 一覧は共有キャッシュ context を再利用
(最新化+失敗時 Navigation fallback)、Escape の preventDefault/stopPropagation と close 時の
フォーカス復帰。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): エージェント/セッションステータスの視覚を強化 (#1051) (#1067)

StatusDot(running/waiting/idle/error)を新設。running はグロー+パルス+静的リング halo、
waiting は amber の弱点滅。サイドバー(BranchStatusIndicator)/ Home / Sessions を StatusDot 化。
prefers-reduced-motion でパルス停止(静的リングで running≠ready を維持)、ポーリングで再発火しない
CSS infinite アニメ。敵対的レビュー指摘(reduced-motion での running/ready 識別性、docs の実態訂正)を
反映。worktree 詳細/MobileHeader は従来スピナー表示のまま(docs に明記・今後の追随対象)。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ui): サイドバーがヘッダーに遮蔽される左上レイアウト破綻を修正 (#1070) (#1083)

showGlobalNav 時に固定サイドバーを top-16 h-[calc(100vh-4rem)] にオフセットし、
半透明ヘッダー(z-50, backdrop-blur)がサイドバー先頭64px(Branches 見出し+
view/sort/sync ツールバー行)を遮蔽してクリック不能+灰色の滲みを生む問題を解消。
あわせて border-gray-200/dark:border-gray-600 を border-border にトークン化。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): テーマ既定を system 化しヘッダーにトグルを追加 (#1071) (#1084)

next-themes を defaultTheme="system" enableSystem に変更し、ライト環境ユーザーへの
強制ダークを解消。Header 右クラスタに ThemeToggle を追加(発見可能性向上)。
globals.css に color-scheme(:root=light / .dark=dark)を宣言し、残存ネイティブ
コントロールの OS クローム事故(dark で白いドロップダウン等)を防止。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(hooks): useIsMobile を matchMedia ベースにし混成レイアウトバグを解消 (#1069) (#1085)

window.innerWidth 依存を window.matchMedia(`(max-width: ${breakpoint-1}px)`)に置換。
innerWidth と CSS ビューポートが乖離する環境(実測 innerWidth=846 vs
documentElement.clientWidth=390)では、Tailwind の md:(min-width:768px)と JS 分岐が
食い違い「CSS はモバイル・JS はデスクトップ」の混成レイアウトになっていた。
max-width:breakpoint-1 は md: の正確な補集合。change イベント購読+cleanup、
SSR 初期値 false、MOBILE_BREAKPOINT export と breakpoint オプションは維持。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): Home 第一フォールドを回収(バナー削除・見出し降格・tabular-nums) (#1072) (#1086)

陳腐化した Welcome バナー(#600 のナビ再編告知)を削除。h1「CommandMate」を機能的見出し
(HomeHeading: Overview + ライブサブライン)へ降格しヘッダーのワードマークとの同語反復を解消。
Home の数値・相対時刻・サイドバーのブランチ数バッジに tabular-nums を導入。HomeQuickActions の
inline SVG を lucide へ置換。相対時刻の短縮形は新設 date-utils、見出し文言は home i18n
namespace(en/ja)。機能(ToDo/カウント/遷移)は不変、サブラインは既存キャッシュから算出。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ライトモードのエレベーション階梯を反転(グレー地+白カード+2層影) (#1074) (#1087)

light を「白ページ+凹む灰カード」から Vercel/Linear 型の「グレー地ページ+
白カード+2層影」へ反転し、dark(#1049)と対のエレベーションモデルにする。
surface-2(740) < background(751) < surface(765) の「白カードが浮き、well が沈む」階梯。

- globals.css :root: --background 255→250 250 251 / --surface 248 250 252→255 255 255
  / --surface-2 241 245 249→244 246 250。.dark は不変(回帰なし)
- tailwind.config.js: 2層 boxShadow.sm を新規追加(md/lg は Tailwind 既定のまま)
- design-tokens/surface-depth テスト更新 + WCAG コントラスト検証を追加
  (foreground≥4.5 全面 / muted-fg≥4.5 page,card / ≥3 recessed well)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): サイドバーを --sidebar-* トークン化しテーマ追従に (#1073) (#1088)

bg-gray-800 text-white 固定でテーマ孤島だったサイドバーを、新設の
--sidebar / --sidebar-foreground / --sidebar-border / --sidebar-hover /
--sidebar-muted セマンティックトークンへ移行。light=slate-50系の明るいパネル、
dark=#141821(#1049階梯と整合)でテーマに追従する。standalone なリテラルRGB値
としており、#1074 の --surface 反転がサイドバー色へ滲まない。

- globals.css :root/.dark に --sidebar-* を定義、tailwind.config.js に登録
- Sidebar.tsx / sidebar/*(BranchListItem, SortSelectorBase)/ 検索Input
  / LocaleSwitcher / ThemeToggle の直書きを全てトークンへ置換
- 敵対的レビュー由来のライト回帰修正2件:
  - LogoutButton.tsx: 漏れていた text-gray-300 直書きを ThemeToggle と同じ
    テーマ中立トークン(text-muted-foreground/hover:bg-muted)へ(モバイル共用)
  - SortSelectorBase: 選択中ソートを text-accent-700 dark:text-accent-400 に
    (ライトのslate-50上で 1.73→約5.1:1 でAA達成、dark時は据え置き)
- docs/design-system.md に --sidebar-* を追記、既存テスト/期待値を更新

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ⌘K パレットを Raycast 級に強化(アイコン/StatusDot/kbd/Recents/ヘッダー導線)(#1077) (#1089)

- src/components/ui/Kbd.tsx を新設(semantic-token ベース、#1080 で再利用)し ui/index.ts に export 追加
- NAV 行に lucide アイコン、worktree 行に GitBranch + StatusDot(isProcessing/isWaitingForResponse/isSessionRunning から算出)
- バックドロップ/パネル/選択状態/入力/フッター文字色を semantic トークン化(bg-surface/backdrop-blur/ring-border/accent-500)
- キーボードガイドのフッター(↑↓/↵/esc、i18n)
- Recent グループ(localStorage cm.palette.recents、MRU<=8、空クエリ時のみ、dead worktree は無視)
- worktree を isSessionRunning 優先→updatedAt 降順ソート+空クエリ時 slice(0,8)
- Actions 拡充: Sync repositories(repositoryApi.sync + Toast)/ 言語切替 / Open GitHub
- Header に ⌘K 検索ピル(mount 後に platform 判定して ⌘K/Ctrl K を出し分け)
- #1053 の挙動契約(フォーカス/入力ガード/Escape/backdrop/data-testid)を維持
- 文言を locales/{en,ja}/commandPalette.json に追加、単体テスト更新

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ターミナルクロームを刷新(全周枠廃止・カード化・CLIセレクタDropdownMenu化)(#1079) (#1090)

worktree ターミナルの全周枠を廃し surface-2 カード+ヘアラインのカード化に統一。
CLI インスタンスセレクタを native <select> から Radix DropdownMenu(StatusDot+alias
+ChevronDown)へ置換し、value/onChange の単一選択セマンティクスと testid を保持。

- Terminal / ActivityBar / TerminalDisplay / TerminalSplitContainer /
  TerminalSplitPane / TerminalSplitPaneContent を surface/border トークンへ
- TerminalSplitPane: <select> → DropdownMenu(RadioGroup)。cli-selector testid 保持
- xterm の theme は意図的に固定ダーク(--surface-2 dark 相当)を維持
- StatusDot/status-colors.ts は不変(#1078 の領域)。locales worktree.json 追記
- 既存 terminal テスト(cli-tab-switching 含む)更新・全パス

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): worktree詳細/Chat のダーク直書き島を surface トークン化しテーマ追従に (#1075) (#1091)

HistoryPane / ConversationPairCard / AssistantMessageList / AssistantChatPanel /
HistorySearchBar / CopyButton と .assistant-md の常時ダーク直書きを surface/foreground
セマンティックトークンへ移行し、light/dark 両テーマで正しく描画されるようにする。

- globals.css .assistant-md: リンクを accent-700 dark:accent-400、code/pre を明色地に
  し両テーマ AA≥4.5:1。:root/.dark のトークン定義は不変
- 各コンテナ+全子孫を surface/surface-2/foreground/muted-foreground/border トークンへ
  (hidden-children 回避: 子の直書き light-on-dark を残さない)
- AssistantChatPanel/HistoryPane の native select/checkbox は色のみトークン化
  (Radix 化は #1076 の担当のため据え置き)
- ConversationPairCard: コピー/挿入/展開ツールバーの hover-reveal 化に伴い、
  タッチ端末で不可視化する回帰を [@media(hover:none)]:opacity-100 で解消
  (デスクトップの hover-reveal は維持。copy-user/assistant-message testid 保持)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): データ画面コントロールの意味論を整理(Review/Repositories/More)(#1081) (#1092)

表示のみの調整。絞り込み・編集・削除・可視性トグルのロジックは不変。

- Review: フィルタを CTA 風の primary/secondary ボタンから、件数付きの
  控えめなセグメンテッドコントロールへ(選択は bg-accent-500/15、CTA と区別)。
  件数は既存のフィルタ済み配列長から算出(新規 fetch なし)。
- Repositories: Enabled/Disabled バッジと可視性トグルの緑ピルを StatusDot 流の
  dot+muted テキストへ(トグルは操作可能なまま)。(none)→ muted の em-dash、
  パスは truncate+font-mono+full-path tooltip、Edit は ghost な Pencil アイコンボタン。
- More: ExternalAppsManager 側の重複 h2「External Apps」を削除(ページ側に一本化)。
- StatusDot/status-colors.ts は不変(#1078 の領域)。生 gray/cyan/blue の追加なし。
- 既存テスト更新+件数バッジ/単一見出しの検証テストを追加。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): コンポーザーをカード化しモバイルworktreeコントロールを整理 (#1080) (#1093)

- MessageInput をコンポーザーカード化(surface/border/shadow/focus-ring)し、送信
  ボタンを入力有無で filled(bg-accent-600)/ghost に切替。プレースホルダを i18n 短縮
- Auto-Yes をコンポーザー下辺のメタ行へ統合(ui/Switch 化)。右側に Kbd キーヒント
  (#1077 の ui/Kbd 再利用)。InterruptButton のオレンジ直値を text-danger ghost へ
- モバイル sticky 行をインスタンスタブ専用化(dark: バリアント+下線インジケータ)。
  検索/End を MobileTerminalActionsSheet(ボトムシート、End は確認付き)へ移設
- MobileTabBar を GlobalMobileNav 様式(bg-background/backdrop-blur/border-border、
  上辺インジケータ、Wrench アイコン、success/warning バッジ)へ統一
- テスト: 送信ボタン状態変化/新規シートを追加、ResizeObserver polyfill(Radix Switch)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): 稼働ステータスを StatusDot 一本化し idle ノイズを縮退 (#1078) (#1094)

青スピナー(border-info)と生 span を全て統一 StatusDot に置換し、稼働ステータスの
視覚言語を1系統に。idle ノイズを縮退(Sessions は running/waiting のみラベル付き、
idle は「+N idle」カウンタ / worktree ヘッダーは idle を StatusDot のみの円形ボタン
+Tooltip、幅超過は「+N」DropdownMenu オーバーフロー)。

- WorktreeDetailSubComponents / sessions/page / MobileHeader を StatusDot へ統一
- 新規 lib/agent-status-display.ts に classifyHeaderInstances 等の導出を集約
- status-colors.ts の type:'spinner'/border-info は @deprecated 化(ReviewTab が
  最後の消費者。消滅後に削除)
- 相対時刻 tabular-nums / プレビューは CSS truncate、DnD(handleAgentDragStart)と
  タブ切替 onClick は全ボタンで温存
- 敵対レビュー由来の仕上げ: 「+N」オーバーフローに稼働中(running/waiting)があれば
  集約 StatusDot グローを出し、稼働セッションが glow-less に埋もれないように。
  同 trigger の生グレーをセマンティックトークンへ

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ネイティブ checkbox/radio を Radix プリミティブに統一 (#1076) (#1095)

OS 標準の白いフォームコントロールが dark に浮く問題を解消。@radix-ui/react-checkbox
/ react-radio-group ベースの ui/Checkbox・ui/RadioGroup を新設し、残存していた
ネイティブ input(checkbox 12箇所/11ファイル・radio 6箇所/4ファイル)を全て移行。

- 新規 src/components/ui/{Checkbox,RadioGroup}.tsx(cn()+cva・セマンティックトークン・
  focus-visible:ring-ring)、ui/index.ts に export、依存2つを追加
- checked/onCheckedChange・value/onValueChange への適応、name/value/disabled/
  aria/data-testid とラベルクリックのトグルを全移行で保持
- 移行後 native type="checkbox"/"radio" は src/ 配下 0 件(完了性 grep 検証済)
- 敵対レビュー(binding/primitive-a11y/GitDangerZonePanel 密集)0件

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): トークン規律の確立(生gray/slate置換・focus-visible統一・CIガード)(#1082) (#1096)

移行済み領域の生 gray/slate 直書きをセマンティックトークンへ置換し、フォーカス表現を
focus-visible に統一、CI grep ガードでトークン違反の再流入を止める。機能・挙動は不変
(見た目/規律のみ)。

## プリミティブ是正
- Button: secondary/ghost を muted/foreground、danger を bg-danger トークンへ。
  `focus:` → `focus-visible:` + `ring-offset-background`(dark の白ハロー解消。Tabs/Switch が手本)。
- Input/Textarea/Select トリガー: `focus:` → `focus-visible:`(枠が反応するため offset なし)。
- Badge gray → bg-muted/text-muted-foreground、Modal → surface/border/foreground。

## in-scope スイープ(生 gray/slate → トークン)
- app(worktrees を除く)/ ui / layout / home / review / repository / common。
- gray-100/200→muted、gray-400/500/600→muted-foreground、gray-700/800/900→foreground、
  境界→border、フォーム枠→input。

## globals.css / metadata / docs
- body→text-foreground、.prose インラインコード→muted/foreground、scrollbar-thin をトークン化
  (--input / --muted-foreground)、`::selection` にアクセント淡色を追加。
- layout.tsx: title template(`%s | CommandMate`)+ viewport themeColor(light/dark)。
- docs/design-system.md にフォーカス表現・CI ガード・例外リストを同期。

## CI ガード
- `.github/workflows/ci-pr.yml` に `token-discipline` ジョブを追加(CLAUDE.md size check と同方式)。
  ホワイトリスト方式で移行済みディレクトリ限定 hard-fail。除外リストをスクリプト内に明記。
  違反注入時に fail することを確認済み。

## 判断メモ(要レビュー)
- `src/app/worktrees/**`(40 occ)はオーケストレーター集計では app(108) に含まれるが、
  ワークツリー領域(#1061・「触らない」)かつ常時ダークのターミナルページ/CLI ブランド色
  (bash=bg-gray-600)を含むため、スイープ・ガード双方から除外した。#1061 完了時に拡大。
- 軽微な見た目変化: danger の red-600→red-500(--danger)、secondary/ghost の muted シェード、
  SidebarToggle はテーマ追従化、ツールチップは反転サーフェス(bg-foreground/text-background)へ
  テーマ追従化、Home の Save&Resend ボタンを accent-600 + 白文字に正規化。

品質ゲート(lint / tsc --noEmit / test:unit / build)+ 新 CI ガード 全パス。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree/共通の生gray/buttonをトークン・プリミティブへ (#1061 群A 1/4) (#1097)

#1061 のファイル分割並列(群A: 21ファイル)。生 gray/slate Tailwind クラスを
#1040/#1082 のセマンティックトークンへ用途判定で置換、生 <button> を ui/Button へ。
挙動・ハンドラ・testid は不変。群A担当ファイルの raw gray = 0。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree詳細/markdown/fileの生gray/buttonをトークンへ (#1061 群B 2/4) (#1100)

#1061 分割並列(群B: 21ファイル)。WorktreeDetailSubComponents/MarkdownToolbar/
FileTreeView 等の生 gray/slate → セマンティックトークン、生 button → ui/Button。
ターミナル出力表示面(TerminalSplitPaneContent)はダーク維持のため意図的に非トークン化。
挙動・testid 不変。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree/mobile/gitの生gray/buttonをトークンへ (#1061 群C 3/4) (#1098)

#1061 のファイル分割並列(群C: 22ファイル)。生 gray/slate → セマンティックトークン、
生 <button> → ui/Button。挙動・testid 不変。群C担当ファイルの raw gray = 0。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree/git/todoの生gray/buttonをトークンへ (#1061 群D 4/4) (#1099)

#1061 分割並列(群D: 21ファイル)。生 gray/slate → セマンティックトークン、生 button
→ ui/Button。ターミナル出力表示面(TerminalSplitContainer/TerminalContainer)は
xterm と同様ダーク維持のため意図的に非トークン化(除外)。挙動・testid 不変。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci(guard): token-discipline ガードを worktree/mobile/external-apps へ拡大 (#1061) (#1101)

#1061 で worktree/mobile/external-apps の生 gray/slate をトークン化完了したため、
#1082 で導入した token-discipline CIガードの対象ディレクトリを拡大。
ターミナル出力表示面(*Terminal* ソース)は xterm 固定ダーク(#1079)と同様の
意図的ダーク島のため除外。app/worktrees も引き続き除外。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: integration スイートの drift を解消(モック/期待値追随・CI追加)(#1102) (#1103)

CI 非対象で長期 drift していた integration スイート(41 failed)を修復。実装バグを
隠さないよう vi.mock を importOriginal 方式へ書き換え、欠落 export を補完。

- 不完全 vi.mock を importOriginal ベースへ(欠落 export を実挙動ごと通す)
- config ドリフト(アップロード上限 5MB→20MB 等)の期待値を現行定数準拠へ
- undefined.length 系はモック戻り値を実 shape へ
- 再 drift 防止に ci-pr.yml へ Integration Tests ジョブ追加
- external-apps duplicate name → 500(期待409) は実装バグのため it.skip で quarantine

結果: test:integration 643 passed / 1 skipped(0 failed)。unit/lint/tsc 緑維持。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(external-apps): 重複nameで500ではなく409を返す (#1104) (#1105)

* fix(external-apps): 重複nameで500ではなく409を返す (#1104)

route.ts の catch を message.includes('UNIQUE constraint') 文字列判定から
ExternalAppDbError.code === 'DUPLICATE' の型/コード判定へ変更。DB層のエラー
ラップ後もメッセージに依存せず 409 を正しく返す。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(external-apps): 409 duplicate name テストを un-skip(#1104 で修正済み)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(timer): Timer履歴の詳細モーダル表示と失敗理由の記録 (#1107) (#1109)

Schedule 側の execution_logs 方式を Timer へ横展開。

- DB: migration v40 で timer_messages に error 列(nullable)追加、schema 39→40
- timer-db: error 列の read/write 貫通、updateTimerStatus に optional error 引数、recoverStuckSendingTimers に固定リカバリ理由
- timer-manager: 送信失敗(stage/error)・例外(error.message)を error 列へ永続化(no_session は保存せず)
- API: GET timers レスポンスに error を追加
- UI: TimerDetailModal を新設し履歴行クリックで指示全文+メタ+失敗理由を表示(PC=Modal / mobile=FullScreenModal)
- i18n: schedule.json の timer ブロックに en/ja lockstep でラベル追加

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(files): Filesツリー展開状態のworktree単位永続化と表示リセット (#1108) (#1111)

useFileTabs / useFilePanelState の worktree 単位 localStorage パターンを踏襲。

- 新規 useFileTreeExpandedState: 展開集合を worktree 単位 localStorage(キー commandmate:file-tree-expanded:<worktreeId>) に永続化。lazy-init 復元をマウント再フェッチに乗せ、空集合はキー削除、worktree 間の漏洩なし
- useFileTabs: CLOSE_ALL / closeAllTabs() を追加
- FileTreeView: フック統合+ツールバーに「表示をリセット」ボタン(更新ボタンの隣・常時可視)+ onResetView prop
- Controller/Desktop/Mobile/Refactored: resetFileTreeView(検索クリア・全タブ閉じ・モバイルビューア閉)を配線し、初期表示に戻すフルリセットを実現

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(mobile): viewportFit:'cover' を追加しiOS safe-area対応を有効化 (#1132)

- src/app/layout.tsx の viewport エクスポートに viewportFit: 'cover' を追加。
  iOS は viewport-fit=cover 指定時のみ env(safe-area-inset-*) に非0値を
  入れるため、既存の pt-safe/pb-safe 等の safe-area 対応が実効化される
- GlobalMobileNav の未定義クラス safe-area-bottom を globals.css 定義済みの
  pb-safe に修正(cover 有効化後もパディングが効かない潜在バグ)
- viewport エクスポート値(viewportFit / themeColor)のユニットテストを追加

レンダリング検証: 本番ビルドで <meta name="viewport"> に
viewport-fit=cover が含まれることを確認済み

Refs #1131

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat(ui): ステータスカラーtintトークン導入とダーク非対応フィードバックUIの移行 (#1133)

- globals.css / tailwind.config.js に success/warning/danger/info × subtle/border/foreground の12トークンを両テーマで追加(ライト *-50 面 + *-800 前景、ダーク *-950 面 + *-300 前景、全て WCAG AA 準拠。根拠はコメントに記載)
- Toast / DefaultErrorFallback / History・Prompt・ConnectionErrorFallback / ErrorDisplay / PromptPanel を新トークンへ移行し、生パレット + dark: ペアを撤去(TerminalErrorFallback は常時ダーク島のため現状維持)
- tint 面内のソリッドアクションボタンは反転 tint(bg-{status}-foreground text-{status}-subtle)で両テーマ AA を確保
- docs/design-system.md にトークン表・使用ルールを追記
- tint トークン定義・移行 grep を検証する status-tint-tokens.test.ts と ErrorBoundary/フォールバックのレンダリングテストを追加、既存 Toast / design-tokens テストを追随

Refs #1112

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* refactor(ui): window.confirmを共通ConfirmDialogへ全面置換 (#1134)

- ConfirmDialog/ConfirmProvider/useConfirm を src/components/ui/ConfirmDialog.tsx に新設
  (variant default/danger、Escape/背景クリックでキャンセル、danger時はcancelに初期フォーカス、
  クローズ後に呼び出し元へフォーカス復帰)
- AppProviders に ConfirmProvider をマウント
- window.confirm 残存11箇所 + bare confirm 1箇所(WorktreeCard)を useConfirm へ置換
- confirm文言を next-intl 辞書へ移動 (review namespace 新設、home/worktree/common に追記)
- ESLint no-restricted-globals(confirm/alert) と no-restricted-properties(window.confirm) を追加
  (bare alert 3箇所は window.alert へ変更、alert置換自体は対象外)
- 既存テストを window.confirm モックからダイアログ操作へ追随、
  ConfirmDialog 単体テストと Timer履歴クリアの結合フローを追加

Refs #1113

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* chore(ui): UI死コード削除(WorktreeList・放棄infinite-scroll hooks・hasNewOutput配線) (#1135)

- WorktreeList.tsx(production importゼロの孤児コンポーネント)とbarrel export(index.ts)を削除
- 放棄された無限スクロール実装(useInfiniteMessages/useScrollObserver/useScrollRestoration)と
  対応テスト3本、孤児化するtypes/infinite-messages.tsを削除
- WorktreeDetailRefactored.tsxのhasNewOutput={false}死配線を撤去(MobileTabBar側は維持)
- 残置ファイルのstaleコメント修正(useVisibilityRecovery.ts/history-display-config.ts)
- useWebSocket.tsに@deprecated注記を付与(WebSocketストリーミング化で再設計・再利用予定)
- docs/architecture.md(ja/en)の削除対象ファイルへのstale参照行を削除

受入基準: 3グレップ全て0件成立、lint/tsc/test:unit(8746 pass)/build全パス

Refs #1115

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* fix(ui): 未定義animate-fade-*の定義とModal/Toast/ContextMenuのexitアニメーション実装 (#1114) (#1136)

* fix(ui): 未定義animate-fade-*の定義とModal/Toast/ContextMenuのexitアニメーション実装

- tailwind.config.jsにfade-in/fade-outキーフレームとanimate-fade-in/outを定義。
  usePromptAnimationが発行していたが未定義でno-opだった。クラス名を
  PromptPanel等が文字列比較で消費しているため、tailwindcss-animateへの
  書き換えではなくキーフレーム定義の追加を採用。duration/easingは
  --motion-duration-base / --motion-ease-out(#1050モーショントークン)準拠
- content globsにsrc/hooksを追加(フック発行クラスのpurge防止)
- useExitAnimation(open, duration)フックを新設: 閉要求後もduration msだけ
  マウントを維持しexitアニメーション再生を保証。render-phase同期により
  開閉ともに1フレームの遅延なし。再open時はタイマー解除
- Modal: data-state=closedでfade+zoom-out exit(200ms)後にアンマウント。
  exit中はbackdropクリック無効化
- Toast: fade+slide-out exit(200ms)再生後にonClose通知(アンマウント遅延)
- ContextMenu: fade+zoom-out exit(100ms、enterと同duration)。
  exit中はpointer-events-none
- EXIT_ANIMATION_DURATION_MS / CONTEXT_MENU_EXIT_DURATION_MSを
  ui-feedback-config.tsに追加しCSSとJSタイマーを同期
- reduced-motionはglobals.cssのグローバルキルスイッチで対応(CSS実装のみのため追加ガード不要)
- テスト: useExitAnimation単体、Modal/Toast/ContextMenuのexit挙動
  (閉操作後にアニメーション時間経過までDOM残留→アンマウント)、
  Tailwind実コンパイルによるCSS生成検証を追加

Refs #1114

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ui): ConfirmDialogをModal exitアニメーションに追随

- ConfirmDialog.test.tsx: 閉操作後の即時DOM除去期待を、200ms exit
  アニメーション完了後のアンマウント(waitForElementToBeRemoved)に更新
- ConfirmProvider: pendingクリア後のexit再生中にdescription等が空文字に
  なり内容がブランク表示される統合ドリフトを修正(直近optionsを保持し
  フェードアウト中も表示を維持)。cancelテストにブランク防止の検証を追加

Refs #1114

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat(ui): ヘッダーナビのアクティブインジケータ実装と角丸スケール規約の導入 (#1137)

- Header ナビに CSS-only の下線インジケータを実装(after 疑似要素の
  scale-x トランジション、--motion-ease-out / duration-200 準拠、
  motion-safe ガード)。アクティブ項目に aria-current="page" を付与
- Header のアクティブ状態レンダリングテストを追加(全 6 ルート)
- docs/design-system.md に角丸スケール規約を独立セクションとして追記
  (コントロール=rounded-md / コンテナ=rounded-lg / ポップアップ=rounded-md /
  チップ=rounded-full / 小型インライン=rounded-sm、裸 rounded 原則禁止)

角丸統一の実施範囲(明確な逸脱のみ、feature 配下の一括置換はしない):
- ui/Kbd・ui/Checkbox: 裸 rounded → rounded-sm
- layout/Sidebar のアイコンボタン3箇所: 裸 rounded → rounded-md
- layout/Header のコマンドパレットトリガー: rounded-lg → rounded-md

Refs #1119

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* refactor(ui): チャット履歴UIをConversationPairCard系デザインへ統一 (#1138)

MessageList/PromptMessageの視覚言語をConversationPairCardに統一
(案B: 構造・optimistic updateロジック維持、見た目のみ刷新)。

- 生chromatic色21箇所をステータスtintトークン(warning/success/info x
  subtle/border/foreground)へ移行し、生色のdark:ペアを撤去
- インラインSVGをlucide-reactへ統一(Check/X/CircleCheck/Clock/Loader2/
  MessageCircle)
- ユーザー/アシスタントメッセージをCPC言語のセクション(accent左ボーダー/
  surface-2 wash)+ hover-revealコピーツールバーへ刷新
  ([@media(hover:none)]:opacity-100 タッチフォールバック付き)
- markdown描画をprose→assistant-md(トークン追従)へ移行、ANSI/コード
  ブロックの常時ダーク島(#0d1117)は現状維持
- ファイルパスリンク/プロンプト応答(Yes/No・選択肢・テキスト入力)/
  ログリンクの機能パリティ維持、レンダリングテストを新規追加

Refs #1117

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat(ui): スケルトンローディング展開とSpinner統一・loading.tsx整備 (#1139)

- ui/Spinner.tsx 新設: 手書きインラインSVG/border系スピナー43箇所を共通プリミティブへ置換
- 主要画面にレイアウト整合スケルトンを実装(Home Session Overview/ToDo、Sessions一覧、Repositories一覧、HistoryPane(汎用Card型・loading分岐のみ)、ファイルツリー、AssistantChatPanel、Reviewタブ群)
- 裸テキストローディングをスケルトンへ置換(MessageList.tsxは#1117衝突回避のため対象外)
- 初回ロードのみスケルトン表示(再fetch時は既存コンテンツ維持の非ブロッキングパターン踏襲)
- 主要7ルートに loading.tsx(薄いRouteLoadingフォールバック)を追加
- AssistantChatPanelのローディングを導出状態化し空状態フラッシュを解消

Refs #1118

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* refactor(ui): 生chromatic色をtintトークンへ全面移行しCIガードを拡大 (#1140)

意味ベースでステータスtintトークンへ移行 (red→danger / green→success /
yellow・amber・orange→warning / blue・sky・purple・violet→info)。
淡色面は bg-{s}-subtle + border-{s}-border + text-{s}-foreground、
ソリッドは bg-{s} を使用し、chromatic用途の dark: ペアを撤去。

- ui/Badge.tsx・config/status-colors.ts をトークン化 (生パレットクラス撤去)
- error/ の常時ダーク島 TerminalErrorFallback を *Terminal* 名の別ファイルへ分離し、
  残る error/・auth/ の生gray/chromatic漏れを回収
- 検索ハイライト/ファイル種別アイコン等の装飾色もトークン化 (text-pink等スコープ外色は維持)
- CIガード token-discipline の grep パターンに chromatic 9色を追加し error/・auth/ を対象化。
  既存の *Terminal*・テスト・常時ダーク島 (worktrees/[id]/terminal 等) の除外は維持
- docs/design-system.md に chromatic 禁止/例外リストとCIガード仕様を追記
- 影響する class 検証テストを追随
  (Badge / ContextMenu / MarkdownEditor / MemoCard / dark-mode-foundation / recursive-delete)

chromatic grep 残数
(git grep -cE '(bg|text|border|ring)-(red|green|yellow|amber|orange|purple|violet|sky|blue)-[0-9]' -- src/):
  before: 254 行 (71 ファイル)
  after:  19 行 (サンクション済み例外のみ = *Terminal* 群 + src/app/worktrees/[id]/terminal/)
  in-scope (例外除く): 0

品質ゲート: npm run lint / npx tsc --noEmit / npm run test:unit (8818 passed) / npm run build 全パス

Refs #1116

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): View Transitions APIによるページ遷移クロスフェードを導入 (#1141)

主要ルート間(Home/Chat/Sessions/Repos/Review/More/worktree詳細)のコンテンツ
領域をクロスフェードするプログレッシブエンハンスメントを追加する。

実装方式は「自前の薄いラッパ(案B)」を採用し、next-view-transitions等の外部
依存は追加しない。理由:
- 受入基準が機能検出ガード/reduced-motion無効化の単体テストを要求しており、ガード
  関数を自前で保持することで直接テスト可能になる(ライブラリ委譲では不可)
- framer-motion不採用方針(docs/design-system.md #1050)と同様にCSS駆動とし、JSは
  startViewTransition呼び出しのガードのみに限定してバンドルを軽量に保つ
- Next15/React19ネイティブView Transitionsへの載せ替えが容易(抽象が薄い)
- ライブラリ導入時のLink全面置換を避け、変更をnavシェルとプログラム遷移に限定

主な構成:
- src/lib/view-transitions: supportsViewTransitions/prefersReducedMotion/
  startViewTransition。非対応・reduced-motion時は即時遷移フォールバック
- ViewTransitionsProvider: startViewTransitionをApp Routerナビゲーションへ橋渡し。
  usePathnameのコミット監視で新DOM確定後にresolveし、同一ルート等の非コミット時は
  COMMIT_SAFETY_TIMEOUT_MS=500で確実にsettle
- TransitionLink / useViewTransitionRouter: Header・GlobalMobileNav・Sidebarで使用。
  修飾クリック/別タブ/外部/同一ルートはブラウザ既定にフォールスルー
- globals.css: [data-view-transition="content"](AppShellの<main>のみ)をクロス
  フェードし、永続シェル(ヘッダー/サイドバー/ボトムナビ)はrootを固定して除外。
  ::view-transition背景を--backgroundに設定しダークテーマの白フラッシュを防止。
  200ms(--motion-duration-base)/ --motion-ease-out準拠、reduced-motionで無効化

Refs #1122

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* perf(ui): HistoryPaneを@tanstack/react-virtualで仮想化し250件上限を撤廃 (#1123) (#1142)

* perf(ui): HistoryPaneを@tanstack/react-virtualで仮想化し250件上限を撤廃

履歴リスト(HistoryPane)を仮想化し、可視領域+オーバースキャンのみを
マウントするよう変更。従来はカードをフラット描画し、250件上限は性能緩和策
だった。仮想化により描画コストが O(可視行) となり、上限を1000へ緩和した。

主な変更:
- FilePanelContent の流儀に揃え useVirtualizer + measureElement(可変高)で
  ConversationPairCard を仮想描画。展開/折りたたみ・コードブロック等の高さ
  変化は measureElement が自動再計測。
- スクロール位置維持: 追記は react-virtual が自動維持。最下部閲覧中
  (isNearBottom)のみ新規ペアを追従(follow/maintain を分離)。
- 検索ジャンプ整合: 対象行を scrollToIndex で materialize してからハイライト
  適用。マウント窓の変化(renderedRange)でハイライトを再適用。
- カードのアンマウントで展開状態が飛ばないよう、展開状態はリスト側/親
  (useConversationHistory + 検索auto-expand)で保持(#1121 の pending 挿入も
  この item 管理の上に載せやすい構成)。
- 250件上限を緩和: HISTORY_DISPLAY_LIMIT_OPTIONS に 500/1000 を追加
  (MAX_MESSAGES_LIMIT=1000 に自動追従、APIの取得上限も1000へ)。
- src/lib/history-virtualization.ts: overscan/estimate/しきい値の定数と純粋な
  isNearBottom を分離しユニットテスト可能に。
- テスト: jsdom は無レイアウトのため offsetHeight を与える helper
  (tests/helpers/virtual-layout.ts)と1000件fixtureを追加。仮想化のDOM件数・
  追従/維持判定・展開折りたたみ・検索名前空間を網羅。

1000件fixture before/after(jsdom初期描画。体感FPSは手動検証):
- マウントカード数: 1000 → 16
- DOMノード数: 26,001 → 449(約58分の1)
- 初期描画時間: 289ms → 24ms(約12倍高速)

品質ゲート: lint / tsc --noEmit / test:unit(8855 passing) / build 全パス。

Refs #1123

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ui): HistoryPane仮想化のゼロ計測フォールバックで本文が消える問題を修正

WorktreeDetail経由の統合テスト(jsdomはレイアウト高さ0)で virtualizer が
アイテムを0件しか materialize せず、メッセージ本文が描画されず getByText が
失敗していた(tests/integration/worktree-detail-integration.test.tsx の
'renders user and assistant messages')。

修正: virtualizer がビューポート未計測でアイテム0件のとき(初回レンダー/SSR/
jsdom 等のレイアウト0環境)、先頭 HISTORY_FALLBACK_RENDER_COUNT 件を通常フロー
で描画するフォールバックを HistoryPane に追加。実ブラウザでは layout-effect の
計測直後に仮想化リストへ切り替わる(フォールバック描画は塗り前の一時コミット)。
本番の仮想化保証を損なわないよう件数は小さく固定。SSRのハイドレーション整合
(server/client双方が同じ先頭スライスを描画)にも寄与する。

単体テスト側は既存の virtual-layout ヘルパーで実 virtualizer 経路を検証し続ける
ため、この修正はそれらに影響しない。

品質ゲート: lint / tsc --noEmit / test:unit(8855) / test:integration(648) /
build 全パス。

Refs #1123

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(realtime): ターミナル出力・セッションステータスをWebSocketストリーミング化 (#1120) (#1143)

* feat(realtime): ターミナル出力・セッションステータスをWebSocketストリーミング化

実装済みだが未接続だったWebSocketインフラをクライアントへ接続し、ポーリングを
フォールバックとして残しつつサーバ→クライアントの一方向pushへ移行する。

- useWebSocket再設計: 単一WS接続・指数バックオフ再接続(1s..30s)・visibility連携・
  room購読再送・エンベロープparse。RealtimeProvider/useRealtimeで共有1本化+
  listener fan-out+購読refカウント(Provider未装着時はpollingフォールバック)
- セッションステータスpush: send時にrunning遷移をbroadcast、kill時のstopと合わせ
  サイドバー状態ドットを即時反映。WS接続中はuseWorktreesCacheのポーリングを間引き
- ターミナルストリーミング: response-poller tickからterminal_snapshotをpush
  (buildCurrentOutputでpull/push一致、購読者0でtmux captureをスキップ)。
  WS接続中はuseTerminalPanePollingを15sフォールバックへ間引き、version guardで
  stale破棄(既存stale-response/visibility堅牢性を維持)
- 差分適用: computeTerminalUpdateで追記/リセットを判定しTerminalDisplayを
  keyed chunk追記化、全置換を廃止してテキスト選択を維持(ANSI境界退避付き)
- モバイル新着出力バッジをpush駆動で復活、接続状態インジケータを追加
- 認証: Cookie自動付与でWS upgrade、認証失敗時は既存機構が接続拒否

テスト: useWebSocket(購読/再接続/バックオフ)・差分適用(追記/リセット/ANSI境界)・
WS切断→ポーリング復帰→WS再接続の統合テスト・認証拒否のE2E統合テストを追加。
lint/tsc/test:unit/test:integration/build 全パス。

Refs #1120

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(realtime): useRealtimeConnection等のNULバイト混入を除去

useRealtimeConnection.tsx の room購読キー生成で空白(0x20)が2箇所NULバイト
(0x00)に化けており、gitがバイナリ扱いしdiff/レビュー不能になっていた。
本来の ' ' 区切り(useWorktreesCache と同一パターン)へ復元し、ロジックは不変。

- src/hooks/useRealtimeConnection.tsx: `.join('\0')`/`.split('\0')` → `.join(' ')`/`.split(' ')`
- src/components/worktree/FileTreeView.tsx: 既存のセンチネル `'\0unavailable'` から
  NULを除去し `'unavailable'`(JSON.stringify結果と衝突しない性質は維持)

src/配下のNULバイト混入は0件を確認。lint/tsc/test:unit/test:integration/build 全パス。

Refs #1120

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(chat): メッセージ送信のOptimistic UIを実装 (#1144)

送信瞬間に pending 吹き出しを履歴末尾へ即時挿入し、サーバ echo で実データへ
照合置換(重複表示なし)、失敗時はエラー表示+再試行/破棄する Optimistic UI を
追加。

- usePendingMessages 層: serverMessages に pending をマージし ChatMessage
  (optimisticState 付き)として表示側へ渡す。content 一致+作成時 baseline 外+
  consumedServerIds の一意消費で照合(連投 1:1/既存同一文言の誤照合回避)。
  API reject/timeout(30s) で error→retry/discard。
- ConversationPairCard/HistoryPane: sending(opacity低下+スピナー)と
  error(danger トークン+Retry/Discard)を描画。pending 中は
  「Waiting for response…」を抑止。
- MessageInput: onOptimisticSend 提供時は送信を委譲し composer を即時クリア
  (draft も除去)。他サーフェスは従来の await→clear を維持。
- TerminalSplitPaneContent: 各 split の useSplitMessages に usePendingMessages を
  重ね、送信→即時表示→onSent refetch で照合。破棄時は既存 insert 経路で draft 復元。
- 仮想化リストと整合: pending も pair 化され virtualizer item として最下部追従。

テスト: usePendingMessages(挿入/照合/失敗/連投/タイムアウト/retry/discard)、
ConversationPairCard(sending/error/retry/discard)。

Refs #1121

* fix(mobile): useIsMobileを描画前レイアウトエフェクトで解決し初回デスクトップ描画フリップを解消 (#1145)

## 事象
useIsMobile がSSR安全のため初期値 false で始まり、実際の検出を
passive な useEffect(ペイント後実行)で行っていたため、モバイル実機の
ハードリロードで毎回デスクトップUIが一瞬描画されてからモバイルUIへ
フリップしていた(AppShell の固定サイドバー / WorktreeDetail の
デスクトップ⇔モバイル分岐でのフラッシュ・CLS)。

## 対策の選定(案A/B/C のトレードオフ)
- 案A(CSSで両ツリーを出し分け): 初回フレームは正しいが、重い
  WorktreeDetail が二重マウントし、両分岐の terminal ペインが同時に
  ポーリングを開始する。ヘビーツリーには不採用。
- 案B(UAヒント+cookie): ブレークポイントは viewport 幅基準
  (max-width:767px)で UA の mobile とは一致せず(狭幅デスクトップ/
  タブレット)、真の hydration mismatch を再導入する。不採用。
- 案C(採用・描画前レイアウトエフェクト版): SSR/初回クライアント
  レンダリングは決定的に false(デスクトップ)を返し SSR HTML と一致
  するため hydration mismatch 警告は出ない。検出は useLayoutEffect
  (isomorphic)で行い、commit 後・ブラウザ描画前に flush されるため、
  補正後の isMobile 値が最初のフレームに反映される。モバイルでは
  デスクトップツリーは描画される前に差し替えられる。

## ヘビーツリーの二重マウント / ポーリング配慮
- 主ポーラーは分岐に依存しない単一の useWorktreeDetailController が
  一度だけ生成する(分岐は表示のみ)。
- 非表示側の表示ツリーは同一 commit 内でペイント前に破棄されるため、
  passive な useEffect ベースの terminal ポーリングは発火しない。
- 主要なコールドロード経路(キャッシュ無し→LoadingIndicator)では
  どちらの分岐の子もマウントされない。

## その他
- SSR での useLayoutEffect 警告は isomorphic パターン
  (server では useEffect)で回避。
- 既存の useIsMobile 単体テスト(初期値・リサイズ切替)は不変で継続。
- hydrateRoot を用いた新規テストで「hydration mismatch 警告が出ない」
  「モバイルビューポートでモバイル値へ解決」「デスクトップは false 維持
  (回帰なし)」を自動検証。

品質ゲート: lint / tsc --noEmit / test:unit / test:integration / build 全て成功。

Refs #1126

* feat(pwa): PWA対応(manifest+Service Worker+インストール可能化) (#1146)

Web App Manifest(manifest.ts)、手書きService Worker、インストール可能化を
実装。「外出先からエージェントを監視する」プロダクト特性に沿ってホーム画面
から独立アプリ(standalone)として起動可能にする。

Service Worker技術選定: 手書きvanilla SW(next-pwa/Serwist/Workbox不採用)。
理由:
- custom server(server.ts)がWebSocket upgradeを保持しており、next-pwa等が
  前提とするデフォルトNextサーバ構成と噛み合わない
- 本Issueが要求するallowlist方式(キャッシュ対象を明示列挙)を直接表現でき、
  監査容易・小規模・後続#1125 Web Pushの拡張が容易

キャッシュ戦略(allowlist):
- /api・/login・/proxy・非GET・クロスオリジン → network-only(キャッシュ禁止)
- /_next/static/・/icons/・manifest/favicon → cache-first
- navigation → offline-fallback(認証コンテンツを避けページ本体は非キャッシュ)
判定はcache-policy.ts(単体テスト済み)を単一情報源とし、public/sw.jsが同定数を
鏡写し、sw-file.test.tsで両者の同期を担保。

更新戦略: install時はskipWaitingせず、更新検知でトースト表示→リロードで
SKIP_WAITINGを送信しcontrollerchangeで新版に切替。

その他:
- 本番のみSW登録(shouldRegisterServiceWorker)、dev/test・非セキュアは無効化
- オフラインフォールバック画面(/offline、トークン準拠、再接続ボタン)
- iOS standalone用にappleWebAppメタ(capable/title/status-bar-style)を付与
- manifest/sw/offlineをAUTH_EXCLUDED_PATHSに追加(auth有効時もinstallable/
  precache可、いずれも非機密リソース)
- HTTPS/LAN配信時のSW/インストール制限をREADMEに注記

Refs #1124

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(mobile): モバイルA11y改善(focus trap・44pxタップターゲット・tablistキーボード) (#1147)

モバイルのアクセシビリティ/エルゴノミクスの穴を塞ぐ。

focus trap は共通フック useFocusTrap に1実装集約:
- 判断理由: Modal/シート類は独自ポータル+enter/exitアニメ(useExitAnimation
  /usePromptAnimation)+スワイプ dismiss を持つ bespoke 実装。@radix-ui/
  react-dialog は未導入で、置換すると3面すべてとそのテストを書き直す必要がある。
  共通フックなら既存機構を壊さず trap のみ追加でき、issue の「1実装に集約」に沿う。
- 挙動: 初期フォーカスをダイアログへ移動 / Tab・Shift+Tab をダイアログ内で循環 /
  閉時に呼び出し元へフォーカス復帰。autoFocus 等で消費側が内部フォーカスを
  管理している場合は初期フォーカス奪取・復帰の両方をスキップ(ConfirmDialog 互換)。
- 適用: ui/Modal(role=dialog/aria-modal/aria-labelledby も付与)・
  MobilePromptSheet・MobileTerminalActionsSheet。

44px タップターゲット(視覚サイズは不変、ヒットエリアのみ拡大):
- エージェントインスタンスタブ(WorktreeDetailRefactored)
- History サブタブ Message/Git(WorktreeDetailMobile)
- MobileTabBar の各タブ

MobileTabBar に ARIA tabs パターン完成:
- 矢印キー(左右/上下)+Home/End 移動、ラップアラウンド
- roving tabindex(activeタブのみ tabindex=0)、選択はフォーカス追従

touch-manipulation をボタン系プリミティブ(ui/Button)とモバイル操作面へ付与し
ダブルタップズームの遅延を抑止。

title のみだった WorktreeCard のお気に入りトグル(アイコンのみ)へ aria-label を
付与し、タッチ/スクリーンリーダーからも到達可能に。

スワイプジェスチャー等は本Issueスコープ外のため変更していない。

Refs #1127

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(mobile): ジェスチャー強化(スワイプタブ切替・pull-to-refresh・キーボード追従) (#1148)

- useSwipeGesture に axis/方向ロック/エッジ開始制限/onSwipeMove を後方互換で追加
  (既存 MarkdownEditor の四方向スワイプと #299 スクロール抑制は不変)
- worktree詳細のモバイルタブを水平スワイプで切替。方向ロックで縦スクロール中は
  無効化、しきい値60px、ターミナルタブはエッジ開始領域に限定して誤発火を抑止。
  MobileTabBar インジケータは activeTab 共有で自動同期
- Sessions/Repositories に pull-to-refresh を新設(usePullToRefresh + PullToRefresh)。
  最上部のみ発火し overscroll-behavior:contain + preventDefault でネイティブPTR二重発火を防止
- MessageInput を useVirtualKeyboard に接続しキーボード追従(keyboardAware opt-in)、
  入力要素へ enterKeyHint/inputMode を付与
- MobilePromptSheet のアドホックスワイプを useSwipeGesture へ統合(focus trap ref を
  マージして #1127 の挙動を維持、finger-follow は onSwipeMove で継続)

Refs #1128

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(pwa): エージェント状態のWeb Push通知(プロンプト待ち/完了) (#1149)

- push_subscriptions テーブル(migration v41)と CRUD モジュールを追加
- VAPID/web-push 送信層: 種別フィルタ・デバウンス・410自動削除、機密(endpoint/秘密鍵)は非ログ
- プロンプト検出/セッション完了時に response-checker からファンアウト(検出ロジックは不変・fire-and-forget)
- 購読登録/解除/種別設定/VAPID公開鍵の認証必須API(グローバルmiddleware配下)
- Service Worker に push/notificationclick ハンドラ(該当worktreeへdeep link・…
Kewton added a commit that referenced this pull request Jul 18, 2026
* feat(files): スマホ版でファイルをダウンロード可能に(attachment配信+FileViewer DL導線)(#1024) (#1025)

スマホ版のファイルビューア(FileViewer, Issue #438 でモバイル専用化)から
任意種別のファイルをダウンロードできるようにする。

- B(サーバ): files/[...path] GET に download 分岐を追加し、生バイトを
  Content-Type: application/octet-stream + Content-Disposition: attachment で返却。
  filename は src/lib/http の content-disposition ヘルパーで RFC 準拠にサニタイズ
  (ASCII フォールバック + filename*=UTF-8'' パーセントエンコード、ヘッダ
  インジェクション回避)。既存 getWorktreeAndValidatePath でパス検証を再利用。
  base64 JSON を経由しないためプレビュー上限を超える大容量にも対応
- A(UI): FileViewer ツールバーにダウンロードボタンを追加し、同一オリジンの
  download URL へ遷移(iOS Safari 含めネイティブDLに乗せる)
- テスト新規29本(download API のヘッダ/パス検証/filename、content-disposition
  ヘルパー、FileViewer ボタン)
- C(独立ページのPDF分岐/DLリンク)は範囲外(モバイル動線が独立ページを使わないため)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(session): Timer 発火の送信を History に記録(send ロジックを共有サービス化)(#1028) (#1029)

Timer 機能(Issue #534)から送った指示が History(chat_messages)に残らない
問題を修正。executeTimer が cliTool.sendMessage だけを呼び createMessage/
startPolling をスキップしていたのが原因(#947 の委譲変更が起点)。

- send route L262-385 のインライン記録ロジックを src/lib/session/
  send-user-message.ts の共通関数 sendUserMessage() として抽出
  (savePendingAssistantResponse → send(画像/Copilot分岐含む)→ createMessage
  → updateLastUserMessage/orphan処理 → startPolling)。HTTP 層非依存
- send route POST を sendUserMessage() 利用へリファクタ(挙動不変)
- timer-manager.ts executeTimer を sendUserMessage() 経由に変更。
  timer_messages の status 遷移(sending/sent/failed)は維持
- テスト: sendUserMessage / executeTimer のユニット新規9件 + timer 既存更新(計26件)

これにより Timer 送信も user メッセージと assistant 応答の両方が History に残る。

スコープ外(別Issue推奨):
- tests/integration/api-send-cli-tool.test.ts の1件は base でも失敗する既存不具合
  (2引数アサート vs 3引数呼び出し)で本修正と無関係(CI の test:unit 対象外)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(todo): ブランチ単位ToDoを3状態化(未着手/仕掛/完了)(#1032) (#1033)

- migration v38: worktree_todos に status カラム追加(done=1→'done' backfill、CURRENT_SCHEMA_VERSION 37→38)
- DB層: status を真実源に done := status==='done' を派生(後方互換維持、writer整合)
- API/クライアント: PATCH で status ∈ {todo,doing,done} を検証・適用、done後方互換
- UI: 3状態巡回チップ+色バッジ+状態別件数、TodoPane全体を next-intl 化
- i18n: locales/{en,ja}/worktree.json に todo namespace 追加
- テスト: 既存5更新+新規 migration-v38(backfill検証)

Closes #1032

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(todo): ブランチ単位ToDoに詳細フィールド追加+クリックで件名/詳細を編集 (#1034) (#1035)

- migration v39: worktree_todos に detail カラム追加(TEXT NOT NULL DEFAULT ''、既存行は空詳細で移行)
- DB層: WorktreeTodo に detail 追加、create/update で detail 受理
- API: POST/PATCH で detail を検証・適用(MAX_TODO_DETAIL_LENGTH 新設)
- クライアント: WorktreeTodoItem.detail、create/update の detail 対応
- UI: 項目クリックで編集モーダル(Modal.tsx再利用)を開き件名+詳細を編集、詳細ありインジケータ
- i18n: locales/{en,ja}/worktree.json に詳細/編集の文言追加
- テスト: 既存6更新+新規 migration-v39、docs/module-reference.md 更新

Closes #1034

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(todo): ブランチ単位ToDoの詳細にコピーボタンを追加 (#1036) (#1037)

- UI: 編集モーダルの詳細欄に CopyButton を配置し詳細本文をコピー(詳細が空のときは非表示)
- 既存 src/components/common/CopyButton.tsx / clipboard-utils.ts を再利用(スマホHTTPは execCommand フォールバック)
- i18n: locales/{en,ja}/worktree.json に todo.copyDetail 追加
- テスト: TodoPane.test.tsx にコピー導線テスト3件追加
- migration/DB/API 変更なし(UIのみ)

Closes #1036

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): セマンティックデザイントークン基盤を導入 (#1041) (#1054)

globals.css に :root(light)/.dark(dark) の CSS 変数
(background/foreground/surface/muted/border/input/ring/accent-50..700/
success/warning/danger/info) を定義し、tailwind.config.js の primary(cyan)・
cmd-bg-dark を semantic トークンへ置換。layout.tsx / MainLayout.tsx の
body 背景を bg-background に統一。既存の実効値を写し取り視覚は不変(no-op)。
docs/design-system.md を新設。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): cn()/cva によるバリアント管理基盤とプリミティブ改修 (#1042) (#1055)

clsx + tailwind-merge の cn() ヘルパーと class-variance-authority を導入し、
Button/Card/Badge/Modal を cva ベースへ移行。Button ghost バリアントの
dark: 指定漏れ(ダークモードで視認性劣化)を修正。既存 API は完全互換。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): アイコンを lucide-react に統一 (#1044) (#1056)

絵文字アイコン(🤖⚡✦💻)を廃止し lucide アイコンへ置換。共通レイアウト・ナビ
(Header/GlobalMobileNav/MobileTabBar/ActivityBar/terminal page)および worktree
各所のインライン SVG を lucide に統一。docs/design-system.md にアイコンサイズ規約を追記。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): next/font で Geist Sans/Mono を導入 (#1043) (#1057)

geist パッケージ(セルフホスト)で GeistSans/GeistMono の CSS 変数を <html> に適用し、
tailwind fontFamily の sans/mono を「Geist → 日本語(Hiragino/Noto Sans JP) →
システム」フォールバックで構成。Terminal.tsx(xterm)の等幅も Geist Mono を先頭指定。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): RadixベースUIプリミティブを拡充 (#1046) (#1058)

Input/Textarea/Select/Tabs/Tooltip/DropdownMenu/Switch/Skeleton を追加
(cva + cn, セマンティックトークン着色, ライト/ダーク対応)。Portal 系は
'use client' + Z_INDEX(POPOVER)で Modal と階層整合。Sessions ページの
フィルタ/ソートを新プリミティブへ移行(適用サンプル)。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): アクセント色を統一しセマンティックトークンへ置換 (#1045) (#1059)

cyan/blue 直書き(~600箇所 / 131ファイル)を #1041 のセマンティックトークンへ置換。
用途判定: インタラクティブ=accent / フォーカス=ring / 情報=info。gray 系は
surface/background/border/muted へ実効値を保って置換。例外(Terminal.tsx の
xterm ANSI 配色 / CLI ブランド色 / .scrollbar-thin 等の固定装飾色)は維持。
globals.css の .btn-primary・検索ハイライト等もトークン化。
敵対的レビュー指摘(focus:ring-accent→focus:ring-ring 統一、design-system.md
の例外記述修正)を反映。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): 主要ページを共通UIプリミティブへ移行 (#1047) (#1060)

layout(Header/Sidebar の検索・トグル・言語)/ home / repositories / review /
more / chat / login の生button・手組みフォーム・コピペカードを @/components/ui
プリミティブへ移行。機能・挙動(onClick/フォーム送信/バリデーション/キーボード)は維持。
敵対的レビュー指摘(chat系ボタン移行・ghost の dark hover)を反映。ネイティブ select・
複合 textarea 入力バー・極小アイコントグルは意図的に残置(PR の残置リスト参照)。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): globals.css の @apply クラスを廃止し cva プリミティブを自己完結化 (#1048) (#1062)

.btn/.btn-*/.card/.card-hover/.badge*/.input を globals.css から削除し、
Button/Card/Badge の cva 定義へ実クラスを内挿(旧 @apply 定義と同値、視覚不変)。
src/ 全体で削除クラスへの参照が 0 であることを確認。worktree の一部
(WorktreeDetailRefactored/LogViewer/WorktreeList/ExternalAppForm)をプリミティブへ移行し、
マジックナンバー入りインライン style を定数化。worktree/mobile/sidebar/common の
一括プリミティブ移行は規模の都合により #1061 へ分割。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): サーフェス・奥行きを再設計しモダンダークテーマへ刷新 (#1049) (#1063)

globals.css の :root/.dark トークン値を刷新(深い base + 微リフト surface +
ヘアライン border)。Header/GlobalMobileNav に backdrop-blur(supports-[backdrop-filter]
フォールバック付)。Card に elevated/interactive variant、h1-h4 見出しタイポを調整。
敵対的レビュー指摘を反映: 主要閲覧画面のコンテンツカード(sessions/home/review)を
bg-surface/border-border へ移行し背景反転による溶け込みを解消、ダーク入力を surface-2 で
凹み表現、Card interactive にキーボード focus 対応を追加。コントラストは WCAG AA 全 pass。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): モーション基盤とマイクロインタラクションを追加 (#1050) (#1064)

tailwindcss-animate を導入し、Modal/MobilePromptSheet の enter/exit、Radix data-state
連動(DropdownMenu/Select/Tooltip)、一覧の stagger、hover lift を統一適用。
prefers-reduced-motion 対応。ターミナル/仮想スクロールには非適用。敵対レビュー指摘
(reduced-motionでのtranslate漏れ→motion-safe化、sessionsエラー時のstagger再発火→list
マウント維持、Tooltip instant-open enter、home focus parity)を反映。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): Home をダッシュボード(bento グリッド)化 (#1052) (#1065)

Home を縦積みから bento グリッドへ再構成(統計/直近セッション/ToDo/クイックアクション)。
PC は CSS Grid(grid-cols-12 + col-span)、モバイルは1カラム縦積み(useIsMobile の JS 分岐を
増やさず Tailwind breakpoint)。既存 sessions 取得フック/API を再利用し新規 API なし。既存機能
(ToDo 追加/チェック/コピー、セッションカウント)を維持。空状態対応。敵対的レビュー指摘
(ToDo タイルの h-full・見出しパリティ)を反映。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): コマンドパレット(⌘K)を追加 (#1053) (#1066)

cmdk で ⌘K(mac)/Ctrl+K(win) のコマンドパレットを追加(Navigation / Worktrees 検索 / Actions)。
input/textarea/ターミナル入力中は非発火、SSR 安全('use client')、i18n(en/ja)、Modal 相当の z-index。
モバイルは GlobalMobileNav に起動導線。敵対的レビュー指摘を反映: open 時に入力へフォーカスし
キーボード操作(type/矢印/Enter)を復旧[HIGH]、worktree 一覧は共有キャッシュ context を再利用
(最新化+失敗時 Navigation fallback)、Escape の preventDefault/stopPropagation と close 時の
フォーカス復帰。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): エージェント/セッションステータスの視覚を強化 (#1051) (#1067)

StatusDot(running/waiting/idle/error)を新設。running はグロー+パルス+静的リング halo、
waiting は amber の弱点滅。サイドバー(BranchStatusIndicator)/ Home / Sessions を StatusDot 化。
prefers-reduced-motion でパルス停止(静的リングで running≠ready を維持)、ポーリングで再発火しない
CSS infinite アニメ。敵対的レビュー指摘(reduced-motion での running/ready 識別性、docs の実態訂正)を
反映。worktree 詳細/MobileHeader は従来スピナー表示のまま(docs に明記・今後の追随対象)。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ui): サイドバーがヘッダーに遮蔽される左上レイアウト破綻を修正 (#1070) (#1083)

showGlobalNav 時に固定サイドバーを top-16 h-[calc(100vh-4rem)] にオフセットし、
半透明ヘッダー(z-50, backdrop-blur)がサイドバー先頭64px(Branches 見出し+
view/sort/sync ツールバー行)を遮蔽してクリック不能+灰色の滲みを生む問題を解消。
あわせて border-gray-200/dark:border-gray-600 を border-border にトークン化。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): テーマ既定を system 化しヘッダーにトグルを追加 (#1071) (#1084)

next-themes を defaultTheme="system" enableSystem に変更し、ライト環境ユーザーへの
強制ダークを解消。Header 右クラスタに ThemeToggle を追加(発見可能性向上)。
globals.css に color-scheme(:root=light / .dark=dark)を宣言し、残存ネイティブ
コントロールの OS クローム事故(dark で白いドロップダウン等)を防止。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(hooks): useIsMobile を matchMedia ベースにし混成レイアウトバグを解消 (#1069) (#1085)

window.innerWidth 依存を window.matchMedia(`(max-width: ${breakpoint-1}px)`)に置換。
innerWidth と CSS ビューポートが乖離する環境(実測 innerWidth=846 vs
documentElement.clientWidth=390)では、Tailwind の md:(min-width:768px)と JS 分岐が
食い違い「CSS はモバイル・JS はデスクトップ」の混成レイアウトになっていた。
max-width:breakpoint-1 は md: の正確な補集合。change イベント購読+cleanup、
SSR 初期値 false、MOBILE_BREAKPOINT export と breakpoint オプションは維持。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): Home 第一フォールドを回収(バナー削除・見出し降格・tabular-nums) (#1072) (#1086)

陳腐化した Welcome バナー(#600 のナビ再編告知)を削除。h1「CommandMate」を機能的見出し
(HomeHeading: Overview + ライブサブライン)へ降格しヘッダーのワードマークとの同語反復を解消。
Home の数値・相対時刻・サイドバーのブランチ数バッジに tabular-nums を導入。HomeQuickActions の
inline SVG を lucide へ置換。相対時刻の短縮形は新設 date-utils、見出し文言は home i18n
namespace(en/ja)。機能(ToDo/カウント/遷移)は不変、サブラインは既存キャッシュから算出。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ライトモードのエレベーション階梯を反転(グレー地+白カード+2層影) (#1074) (#1087)

light を「白ページ+凹む灰カード」から Vercel/Linear 型の「グレー地ページ+
白カード+2層影」へ反転し、dark(#1049)と対のエレベーションモデルにする。
surface-2(740) < background(751) < surface(765) の「白カードが浮き、well が沈む」階梯。

- globals.css :root: --background 255→250 250 251 / --surface 248 250 252→255 255 255
  / --surface-2 241 245 249→244 246 250。.dark は不変(回帰なし)
- tailwind.config.js: 2層 boxShadow.sm を新規追加(md/lg は Tailwind 既定のまま)
- design-tokens/surface-depth テスト更新 + WCAG コントラスト検証を追加
  (foreground≥4.5 全面 / muted-fg≥4.5 page,card / ≥3 recessed well)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): サイドバーを --sidebar-* トークン化しテーマ追従に (#1073) (#1088)

bg-gray-800 text-white 固定でテーマ孤島だったサイドバーを、新設の
--sidebar / --sidebar-foreground / --sidebar-border / --sidebar-hover /
--sidebar-muted セマンティックトークンへ移行。light=slate-50系の明るいパネル、
dark=#141821(#1049階梯と整合)でテーマに追従する。standalone なリテラルRGB値
としており、#1074 の --surface 反転がサイドバー色へ滲まない。

- globals.css :root/.dark に --sidebar-* を定義、tailwind.config.js に登録
- Sidebar.tsx / sidebar/*(BranchListItem, SortSelectorBase)/ 検索Input
  / LocaleSwitcher / ThemeToggle の直書きを全てトークンへ置換
- 敵対的レビュー由来のライト回帰修正2件:
  - LogoutButton.tsx: 漏れていた text-gray-300 直書きを ThemeToggle と同じ
    テーマ中立トークン(text-muted-foreground/hover:bg-muted)へ(モバイル共用)
  - SortSelectorBase: 選択中ソートを text-accent-700 dark:text-accent-400 に
    (ライトのslate-50上で 1.73→約5.1:1 でAA達成、dark時は据え置き)
- docs/design-system.md に --sidebar-* を追記、既存テスト/期待値を更新

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ⌘K パレットを Raycast 級に強化(アイコン/StatusDot/kbd/Recents/ヘッダー導線)(#1077) (#1089)

- src/components/ui/Kbd.tsx を新設(semantic-token ベース、#1080 で再利用)し ui/index.ts に export 追加
- NAV 行に lucide アイコン、worktree 行に GitBranch + StatusDot(isProcessing/isWaitingForResponse/isSessionRunning から算出)
- バックドロップ/パネル/選択状態/入力/フッター文字色を semantic トークン化(bg-surface/backdrop-blur/ring-border/accent-500)
- キーボードガイドのフッター(↑↓/↵/esc、i18n)
- Recent グループ(localStorage cm.palette.recents、MRU<=8、空クエリ時のみ、dead worktree は無視)
- worktree を isSessionRunning 優先→updatedAt 降順ソート+空クエリ時 slice(0,8)
- Actions 拡充: Sync repositories(repositoryApi.sync + Toast)/ 言語切替 / Open GitHub
- Header に ⌘K 検索ピル(mount 後に platform 判定して ⌘K/Ctrl K を出し分け)
- #1053 の挙動契約(フォーカス/入力ガード/Escape/backdrop/data-testid)を維持
- 文言を locales/{en,ja}/commandPalette.json に追加、単体テスト更新

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ターミナルクロームを刷新(全周枠廃止・カード化・CLIセレクタDropdownMenu化)(#1079) (#1090)

worktree ターミナルの全周枠を廃し surface-2 カード+ヘアラインのカード化に統一。
CLI インスタンスセレクタを native <select> から Radix DropdownMenu(StatusDot+alias
+ChevronDown)へ置換し、value/onChange の単一選択セマンティクスと testid を保持。

- Terminal / ActivityBar / TerminalDisplay / TerminalSplitContainer /
  TerminalSplitPane / TerminalSplitPaneContent を surface/border トークンへ
- TerminalSplitPane: <select> → DropdownMenu(RadioGroup)。cli-selector testid 保持
- xterm の theme は意図的に固定ダーク(--surface-2 dark 相当)を維持
- StatusDot/status-colors.ts は不変(#1078 の領域)。locales worktree.json 追記
- 既存 terminal テスト(cli-tab-switching 含む)更新・全パス

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): worktree詳細/Chat のダーク直書き島を surface トークン化しテーマ追従に (#1075) (#1091)

HistoryPane / ConversationPairCard / AssistantMessageList / AssistantChatPanel /
HistorySearchBar / CopyButton と .assistant-md の常時ダーク直書きを surface/foreground
セマンティックトークンへ移行し、light/dark 両テーマで正しく描画されるようにする。

- globals.css .assistant-md: リンクを accent-700 dark:accent-400、code/pre を明色地に
  し両テーマ AA≥4.5:1。:root/.dark のトークン定義は不変
- 各コンテナ+全子孫を surface/surface-2/foreground/muted-foreground/border トークンへ
  (hidden-children 回避: 子の直書き light-on-dark を残さない)
- AssistantChatPanel/HistoryPane の native select/checkbox は色のみトークン化
  (Radix 化は #1076 の担当のため据え置き)
- ConversationPairCard: コピー/挿入/展開ツールバーの hover-reveal 化に伴い、
  タッチ端末で不可視化する回帰を [@media(hover:none)]:opacity-100 で解消
  (デスクトップの hover-reveal は維持。copy-user/assistant-message testid 保持)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): データ画面コントロールの意味論を整理(Review/Repositories/More)(#1081) (#1092)

表示のみの調整。絞り込み・編集・削除・可視性トグルのロジックは不変。

- Review: フィルタを CTA 風の primary/secondary ボタンから、件数付きの
  控えめなセグメンテッドコントロールへ(選択は bg-accent-500/15、CTA と区別)。
  件数は既存のフィルタ済み配列長から算出(新規 fetch なし)。
- Repositories: Enabled/Disabled バッジと可視性トグルの緑ピルを StatusDot 流の
  dot+muted テキストへ(トグルは操作可能なまま)。(none)→ muted の em-dash、
  パスは truncate+font-mono+full-path tooltip、Edit は ghost な Pencil アイコンボタン。
- More: ExternalAppsManager 側の重複 h2「External Apps」を削除(ページ側に一本化)。
- StatusDot/status-colors.ts は不変(#1078 の領域)。生 gray/cyan/blue の追加なし。
- 既存テスト更新+件数バッジ/単一見出しの検証テストを追加。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): コンポーザーをカード化しモバイルworktreeコントロールを整理 (#1080) (#1093)

- MessageInput をコンポーザーカード化(surface/border/shadow/focus-ring)し、送信
  ボタンを入力有無で filled(bg-accent-600)/ghost に切替。プレースホルダを i18n 短縮
- Auto-Yes をコンポーザー下辺のメタ行へ統合(ui/Switch 化)。右側に Kbd キーヒント
  (#1077 の ui/Kbd 再利用)。InterruptButton のオレンジ直値を text-danger ghost へ
- モバイル sticky 行をインスタンスタブ専用化(dark: バリアント+下線インジケータ)。
  検索/End を MobileTerminalActionsSheet(ボトムシート、End は確認付き)へ移設
- MobileTabBar を GlobalMobileNav 様式(bg-background/backdrop-blur/border-border、
  上辺インジケータ、Wrench アイコン、success/warning バッジ)へ統一
- テスト: 送信ボタン状態変化/新規シートを追加、ResizeObserver polyfill(Radix Switch)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): 稼働ステータスを StatusDot 一本化し idle ノイズを縮退 (#1078) (#1094)

青スピナー(border-info)と生 span を全て統一 StatusDot に置換し、稼働ステータスの
視覚言語を1系統に。idle ノイズを縮退(Sessions は running/waiting のみラベル付き、
idle は「+N idle」カウンタ / worktree ヘッダーは idle を StatusDot のみの円形ボタン
+Tooltip、幅超過は「+N」DropdownMenu オーバーフロー)。

- WorktreeDetailSubComponents / sessions/page / MobileHeader を StatusDot へ統一
- 新規 lib/agent-status-display.ts に classifyHeaderInstances 等の導出を集約
- status-colors.ts の type:'spinner'/border-info は @deprecated 化(ReviewTab が
  最後の消費者。消滅後に削除)
- 相対時刻 tabular-nums / プレビューは CSS truncate、DnD(handleAgentDragStart)と
  タブ切替 onClick は全ボタンで温存
- 敵対レビュー由来の仕上げ: 「+N」オーバーフローに稼働中(running/waiting)があれば
  集約 StatusDot グローを出し、稼働セッションが glow-less に埋もれないように。
  同 trigger の生グレーをセマンティックトークンへ

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ネイティブ checkbox/radio を Radix プリミティブに統一 (#1076) (#1095)

OS 標準の白いフォームコントロールが dark に浮く問題を解消。@radix-ui/react-checkbox
/ react-radio-group ベースの ui/Checkbox・ui/RadioGroup を新設し、残存していた
ネイティブ input(checkbox 12箇所/11ファイル・radio 6箇所/4ファイル)を全て移行。

- 新規 src/components/ui/{Checkbox,RadioGroup}.tsx(cn()+cva・セマンティックトークン・
  focus-visible:ring-ring)、ui/index.ts に export、依存2つを追加
- checked/onCheckedChange・value/onValueChange への適応、name/value/disabled/
  aria/data-testid とラベルクリックのトグルを全移行で保持
- 移行後 native type="checkbox"/"radio" は src/ 配下 0 件(完了性 grep 検証済)
- 敵対レビュー(binding/primitive-a11y/GitDangerZonePanel 密集)0件

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): トークン規律の確立(生gray/slate置換・focus-visible統一・CIガード)(#1082) (#1096)

移行済み領域の生 gray/slate 直書きをセマンティックトークンへ置換し、フォーカス表現を
focus-visible に統一、CI grep ガードでトークン違反の再流入を止める。機能・挙動は不変
(見た目/規律のみ)。

## プリミティブ是正
- Button: secondary/ghost を muted/foreground、danger を bg-danger トークンへ。
  `focus:` → `focus-visible:` + `ring-offset-background`(dark の白ハロー解消。Tabs/Switch が手本)。
- Input/Textarea/Select トリガー: `focus:` → `focus-visible:`(枠が反応するため offset なし)。
- Badge gray → bg-muted/text-muted-foreground、Modal → surface/border/foreground。

## in-scope スイープ(生 gray/slate → トークン)
- app(worktrees を除く)/ ui / layout / home / review / repository / common。
- gray-100/200→muted、gray-400/500/600→muted-foreground、gray-700/800/900→foreground、
  境界→border、フォーム枠→input。

## globals.css / metadata / docs
- body→text-foreground、.prose インラインコード→muted/foreground、scrollbar-thin をトークン化
  (--input / --muted-foreground)、`::selection` にアクセント淡色を追加。
- layout.tsx: title template(`%s | CommandMate`)+ viewport themeColor(light/dark)。
- docs/design-system.md にフォーカス表現・CI ガード・例外リストを同期。

## CI ガード
- `.github/workflows/ci-pr.yml` に `token-discipline` ジョブを追加(CLAUDE.md size check と同方式)。
  ホワイトリスト方式で移行済みディレクトリ限定 hard-fail。除外リストをスクリプト内に明記。
  違反注入時に fail することを確認済み。

## 判断メモ(要レビュー)
- `src/app/worktrees/**`(40 occ)はオーケストレーター集計では app(108) に含まれるが、
  ワークツリー領域(#1061・「触らない」)かつ常時ダークのターミナルページ/CLI ブランド色
  (bash=bg-gray-600)を含むため、スイープ・ガード双方から除外した。#1061 完了時に拡大。
- 軽微な見た目変化: danger の red-600→red-500(--danger)、secondary/ghost の muted シェード、
  SidebarToggle はテーマ追従化、ツールチップは反転サーフェス(bg-foreground/text-background)へ
  テーマ追従化、Home の Save&Resend ボタンを accent-600 + 白文字に正規化。

品質ゲート(lint / tsc --noEmit / test:unit / build)+ 新 CI ガード 全パス。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree/共通の生gray/buttonをトークン・プリミティブへ (#1061 群A 1/4) (#1097)

#1061 のファイル分割並列(群A: 21ファイル)。生 gray/slate Tailwind クラスを
#1040/#1082 のセマンティックトークンへ用途判定で置換、生 <button> を ui/Button へ。
挙動・ハンドラ・testid は不変。群A担当ファイルの raw gray = 0。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree詳細/markdown/fileの生gray/buttonをトークンへ (#1061 群B 2/4) (#1100)

#1061 分割並列(群B: 21ファイル)。WorktreeDetailSubComponents/MarkdownToolbar/
FileTreeView 等の生 gray/slate → セマンティックトークン、生 button → ui/Button。
ターミナル出力表示面(TerminalSplitPaneContent)はダーク維持のため意図的に非トークン化。
挙動・testid 不変。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree/mobile/gitの生gray/buttonをトークンへ (#1061 群C 3/4) (#1098)

#1061 のファイル分割並列(群C: 22ファイル)。生 gray/slate → セマンティックトークン、
生 <button> → ui/Button。挙動・testid 不変。群C担当ファイルの raw gray = 0。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree/git/todoの生gray/buttonをトークンへ (#1061 群D 4/4) (#1099)

#1061 分割並列(群D: 21ファイル)。生 gray/slate → セマンティックトークン、生 button
→ ui/Button。ターミナル出力表示面(TerminalSplitContainer/TerminalContainer)は
xterm と同様ダーク維持のため意図的に非トークン化(除外)。挙動・testid 不変。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci(guard): token-discipline ガードを worktree/mobile/external-apps へ拡大 (#1061) (#1101)

#1061 で worktree/mobile/external-apps の生 gray/slate をトークン化完了したため、
#1082 で導入した token-discipline CIガードの対象ディレクトリを拡大。
ターミナル出力表示面(*Terminal* ソース)は xterm 固定ダーク(#1079)と同様の
意図的ダーク島のため除外。app/worktrees も引き続き除外。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: integration スイートの drift を解消(モック/期待値追随・CI追加)(#1102) (#1103)

CI 非対象で長期 drift していた integration スイート(41 failed)を修復。実装バグを
隠さないよう vi.mock を importOriginal 方式へ書き換え、欠落 export を補完。

- 不完全 vi.mock を importOriginal ベースへ(欠落 export を実挙動ごと通す)
- config ドリフト(アップロード上限 5MB→20MB 等)の期待値を現行定数準拠へ
- undefined.length 系はモック戻り値を実 shape へ
- 再 drift 防止に ci-pr.yml へ Integration Tests ジョブ追加
- external-apps duplicate name → 500(期待409) は実装バグのため it.skip で quarantine

結果: test:integration 643 passed / 1 skipped(0 failed)。unit/lint/tsc 緑維持。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(external-apps): 重複nameで500ではなく409を返す (#1104) (#1105)

* fix(external-apps): 重複nameで500ではなく409を返す (#1104)

route.ts の catch を message.includes('UNIQUE constraint') 文字列判定から
ExternalAppDbError.code === 'DUPLICATE' の型/コード判定へ変更。DB層のエラー
ラップ後もメッセージに依存せず 409 を正しく返す。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(external-apps): 409 duplicate name テストを un-skip(#1104 で修正済み)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(timer): Timer履歴の詳細モーダル表示と失敗理由の記録 (#1107) (#1109)

Schedule 側の execution_logs 方式を Timer へ横展開。

- DB: migration v40 で timer_messages に error 列(nullable)追加、schema 39→40
- timer-db: error 列の read/write 貫通、updateTimerStatus に optional error 引数、recoverStuckSendingTimers に固定リカバリ理由
- timer-manager: 送信失敗(stage/error)・例外(error.message)を error 列へ永続化(no_session は保存せず)
- API: GET timers レスポンスに error を追加
- UI: TimerDetailModal を新設し履歴行クリックで指示全文+メタ+失敗理由を表示(PC=Modal / mobile=FullScreenModal)
- i18n: schedule.json の timer ブロックに en/ja lockstep でラベル追加

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(files): Filesツリー展開状態のworktree単位永続化と表示リセット (#1108) (#1111)

useFileTabs / useFilePanelState の worktree 単位 localStorage パターンを踏襲。

- 新規 useFileTreeExpandedState: 展開集合を worktree 単位 localStorage(キー commandmate:file-tree-expanded:<worktreeId>) に永続化。lazy-init 復元をマウント再フェッチに乗せ、空集合はキー削除、worktree 間の漏洩なし
- useFileTabs: CLOSE_ALL / closeAllTabs() を追加
- FileTreeView: フック統合+ツールバーに「表示をリセット」ボタン(更新ボタンの隣・常時可視)+ onResetView prop
- Controller/Desktop/Mobile/Refactored: resetFileTreeView(検索クリア・全タブ閉じ・モバイルビューア閉)を配線し、初期表示に戻すフルリセットを実現

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(mobile): viewportFit:'cover' を追加しiOS safe-area対応を有効化 (#1132)

- src/app/layout.tsx の viewport エクスポートに viewportFit: 'cover' を追加。
  iOS は viewport-fit=cover 指定時のみ env(safe-area-inset-*) に非0値を
  入れるため、既存の pt-safe/pb-safe 等の safe-area 対応が実効化される
- GlobalMobileNav の未定義クラス safe-area-bottom を globals.css 定義済みの
  pb-safe に修正(cover 有効化後もパディングが効かない潜在バグ)
- viewport エクスポート値(viewportFit / themeColor)のユニットテストを追加

レンダリング検証: 本番ビルドで <meta name="viewport"> に
viewport-fit=cover が含まれることを確認済み

Refs #1131

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat(ui): ステータスカラーtintトークン導入とダーク非対応フィードバックUIの移行 (#1133)

- globals.css / tailwind.config.js に success/warning/danger/info × subtle/border/foreground の12トークンを両テーマで追加(ライト *-50 面 + *-800 前景、ダーク *-950 面 + *-300 前景、全て WCAG AA 準拠。根拠はコメントに記載)
- Toast / DefaultErrorFallback / History・Prompt・ConnectionErrorFallback / ErrorDisplay / PromptPanel を新トークンへ移行し、生パレット + dark: ペアを撤去(TerminalErrorFallback は常時ダーク島のため現状維持)
- tint 面内のソリッドアクションボタンは反転 tint(bg-{status}-foreground text-{status}-subtle)で両テーマ AA を確保
- docs/design-system.md にトークン表・使用ルールを追記
- tint トークン定義・移行 grep を検証する status-tint-tokens.test.ts と ErrorBoundary/フォールバックのレンダリングテストを追加、既存 Toast / design-tokens テストを追随

Refs #1112

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* refactor(ui): window.confirmを共通ConfirmDialogへ全面置換 (#1134)

- ConfirmDialog/ConfirmProvider/useConfirm を src/components/ui/ConfirmDialog.tsx に新設
  (variant default/danger、Escape/背景クリックでキャンセル、danger時はcancelに初期フォーカス、
  クローズ後に呼び出し元へフォーカス復帰)
- AppProviders に ConfirmProvider をマウント
- window.confirm 残存11箇所 + bare confirm 1箇所(WorktreeCard)を useConfirm へ置換
- confirm文言を next-intl 辞書へ移動 (review namespace 新設、home/worktree/common に追記)
- ESLint no-restricted-globals(confirm/alert) と no-restricted-properties(window.confirm) を追加
  (bare alert 3箇所は window.alert へ変更、alert置換自体は対象外)
- 既存テストを window.confirm モックからダイアログ操作へ追随、
  ConfirmDialog 単体テストと Timer履歴クリアの結合フローを追加

Refs #1113

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* chore(ui): UI死コード削除(WorktreeList・放棄infinite-scroll hooks・hasNewOutput配線) (#1135)

- WorktreeList.tsx(production importゼロの孤児コンポーネント)とbarrel export(index.ts)を削除
- 放棄された無限スクロール実装(useInfiniteMessages/useScrollObserver/useScrollRestoration)と
  対応テスト3本、孤児化するtypes/infinite-messages.tsを削除
- WorktreeDetailRefactored.tsxのhasNewOutput={false}死配線を撤去(MobileTabBar側は維持)
- 残置ファイルのstaleコメント修正(useVisibilityRecovery.ts/history-display-config.ts)
- useWebSocket.tsに@deprecated注記を付与(WebSocketストリーミング化で再設計・再利用予定)
- docs/architecture.md(ja/en)の削除対象ファイルへのstale参照行を削除

受入基準: 3グレップ全て0件成立、lint/tsc/test:unit(8746 pass)/build全パス

Refs #1115

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* fix(ui): 未定義animate-fade-*の定義とModal/Toast/ContextMenuのexitアニメーション実装 (#1114) (#1136)

* fix(ui): 未定義animate-fade-*の定義とModal/Toast/ContextMenuのexitアニメーション実装

- tailwind.config.jsにfade-in/fade-outキーフレームとanimate-fade-in/outを定義。
  usePromptAnimationが発行していたが未定義でno-opだった。クラス名を
  PromptPanel等が文字列比較で消費しているため、tailwindcss-animateへの
  書き換えではなくキーフレーム定義の追加を採用。duration/easingは
  --motion-duration-base / --motion-ease-out(#1050モーショントークン)準拠
- content globsにsrc/hooksを追加(フック発行クラスのpurge防止)
- useExitAnimation(open, duration)フックを新設: 閉要求後もduration msだけ
  マウントを維持しexitアニメーション再生を保証。render-phase同期により
  開閉ともに1フレームの遅延なし。再open時はタイマー解除
- Modal: data-state=closedでfade+zoom-out exit(200ms)後にアンマウント。
  exit中はbackdropクリック無効化
- Toast: fade+slide-out exit(200ms)再生後にonClose通知(アンマウント遅延)
- ContextMenu: fade+zoom-out exit(100ms、enterと同duration)。
  exit中はpointer-events-none
- EXIT_ANIMATION_DURATION_MS / CONTEXT_MENU_EXIT_DURATION_MSを
  ui-feedback-config.tsに追加しCSSとJSタイマーを同期
- reduced-motionはglobals.cssのグローバルキルスイッチで対応(CSS実装のみのため追加ガード不要)
- テスト: useExitAnimation単体、Modal/Toast/ContextMenuのexit挙動
  (閉操作後にアニメーション時間経過までDOM残留→アンマウント)、
  Tailwind実コンパイルによるCSS生成検証を追加

Refs #1114

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ui): ConfirmDialogをModal exitアニメーションに追随

- ConfirmDialog.test.tsx: 閉操作後の即時DOM除去期待を、200ms exit
  アニメーション完了後のアンマウント(waitForElementToBeRemoved)に更新
- ConfirmProvider: pendingクリア後のexit再生中にdescription等が空文字に
  なり内容がブランク表示される統合ドリフトを修正(直近optionsを保持し
  フェードアウト中も表示を維持)。cancelテストにブランク防止の検証を追加

Refs #1114

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat(ui): ヘッダーナビのアクティブインジケータ実装と角丸スケール規約の導入 (#1137)

- Header ナビに CSS-only の下線インジケータを実装(after 疑似要素の
  scale-x トランジション、--motion-ease-out / duration-200 準拠、
  motion-safe ガード)。アクティブ項目に aria-current="page" を付与
- Header のアクティブ状態レンダリングテストを追加(全 6 ルート)
- docs/design-system.md に角丸スケール規約を独立セクションとして追記
  (コントロール=rounded-md / コンテナ=rounded-lg / ポップアップ=rounded-md /
  チップ=rounded-full / 小型インライン=rounded-sm、裸 rounded 原則禁止)

角丸統一の実施範囲(明確な逸脱のみ、feature 配下の一括置換はしない):
- ui/Kbd・ui/Checkbox: 裸 rounded → rounded-sm
- layout/Sidebar のアイコンボタン3箇所: 裸 rounded → rounded-md
- layout/Header のコマンドパレットトリガー: rounded-lg → rounded-md

Refs #1119

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* refactor(ui): チャット履歴UIをConversationPairCard系デザインへ統一 (#1138)

MessageList/PromptMessageの視覚言語をConversationPairCardに統一
(案B: 構造・optimistic updateロジック維持、見た目のみ刷新)。

- 生chromatic色21箇所をステータスtintトークン(warning/success/info x
  subtle/border/foreground)へ移行し、生色のdark:ペアを撤去
- インラインSVGをlucide-reactへ統一(Check/X/CircleCheck/Clock/Loader2/
  MessageCircle)
- ユーザー/アシスタントメッセージをCPC言語のセクション(accent左ボーダー/
  surface-2 wash)+ hover-revealコピーツールバーへ刷新
  ([@media(hover:none)]:opacity-100 タッチフォールバック付き)
- markdown描画をprose→assistant-md(トークン追従)へ移行、ANSI/コード
  ブロックの常時ダーク島(#0d1117)は現状維持
- ファイルパスリンク/プロンプト応答(Yes/No・選択肢・テキスト入力)/
  ログリンクの機能パリティ維持、レンダリングテストを新規追加

Refs #1117

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat(ui): スケルトンローディング展開とSpinner統一・loading.tsx整備 (#1139)

- ui/Spinner.tsx 新設: 手書きインラインSVG/border系スピナー43箇所を共通プリミティブへ置換
- 主要画面にレイアウト整合スケルトンを実装(Home Session Overview/ToDo、Sessions一覧、Repositories一覧、HistoryPane(汎用Card型・loading分岐のみ)、ファイルツリー、AssistantChatPanel、Reviewタブ群)
- 裸テキストローディングをスケルトンへ置換(MessageList.tsxは#1117衝突回避のため対象外)
- 初回ロードのみスケルトン表示(再fetch時は既存コンテンツ維持の非ブロッキングパターン踏襲)
- 主要7ルートに loading.tsx(薄いRouteLoadingフォールバック)を追加
- AssistantChatPanelのローディングを導出状態化し空状態フラッシュを解消

Refs #1118

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* refactor(ui): 生chromatic色をtintトークンへ全面移行しCIガードを拡大 (#1140)

意味ベースでステータスtintトークンへ移行 (red→danger / green→success /
yellow・amber・orange→warning / blue・sky・purple・violet→info)。
淡色面は bg-{s}-subtle + border-{s}-border + text-{s}-foreground、
ソリッドは bg-{s} を使用し、chromatic用途の dark: ペアを撤去。

- ui/Badge.tsx・config/status-colors.ts をトークン化 (生パレットクラス撤去)
- error/ の常時ダーク島 TerminalErrorFallback を *Terminal* 名の別ファイルへ分離し、
  残る error/・auth/ の生gray/chromatic漏れを回収
- 検索ハイライト/ファイル種別アイコン等の装飾色もトークン化 (text-pink等スコープ外色は維持)
- CIガード token-discipline の grep パターンに chromatic 9色を追加し error/・auth/ を対象化。
  既存の *Terminal*・テスト・常時ダーク島 (worktrees/[id]/terminal 等) の除外は維持
- docs/design-system.md に chromatic 禁止/例外リストとCIガード仕様を追記
- 影響する class 検証テストを追随
  (Badge / ContextMenu / MarkdownEditor / MemoCard / dark-mode-foundation / recursive-delete)

chromatic grep 残数
(git grep -cE '(bg|text|border|ring)-(red|green|yellow|amber|orange|purple|violet|sky|blue)-[0-9]' -- src/):
  before: 254 行 (71 ファイル)
  after:  19 行 (サンクション済み例外のみ = *Terminal* 群 + src/app/worktrees/[id]/terminal/)
  in-scope (例外除く): 0

品質ゲート: npm run lint / npx tsc --noEmit / npm run test:unit (8818 passed) / npm run build 全パス

Refs #1116

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): View Transitions APIによるページ遷移クロスフェードを導入 (#1141)

主要ルート間(Home/Chat/Sessions/Repos/Review/More/worktree詳細)のコンテンツ
領域をクロスフェードするプログレッシブエンハンスメントを追加する。

実装方式は「自前の薄いラッパ(案B)」を採用し、next-view-transitions等の外部
依存は追加しない。理由:
- 受入基準が機能検出ガード/reduced-motion無効化の単体テストを要求しており、ガード
  関数を自前で保持することで直接テスト可能になる(ライブラリ委譲では不可)
- framer-motion不採用方針(docs/design-system.md #1050)と同様にCSS駆動とし、JSは
  startViewTransition呼び出しのガードのみに限定してバンドルを軽量に保つ
- Next15/React19ネイティブView Transitionsへの載せ替えが容易(抽象が薄い)
- ライブラリ導入時のLink全面置換を避け、変更をnavシェルとプログラム遷移に限定

主な構成:
- src/lib/view-transitions: supportsViewTransitions/prefersReducedMotion/
  startViewTransition。非対応・reduced-motion時は即時遷移フォールバック
- ViewTransitionsProvider: startViewTransitionをApp Routerナビゲーションへ橋渡し。
  usePathnameのコミット監視で新DOM確定後にresolveし、同一ルート等の非コミット時は
  COMMIT_SAFETY_TIMEOUT_MS=500で確実にsettle
- TransitionLink / useViewTransitionRouter: Header・GlobalMobileNav・Sidebarで使用。
  修飾クリック/別タブ/外部/同一ルートはブラウザ既定にフォールスルー
- globals.css: [data-view-transition="content"](AppShellの<main>のみ)をクロス
  フェードし、永続シェル(ヘッダー/サイドバー/ボトムナビ)はrootを固定して除外。
  ::view-transition背景を--backgroundに設定しダークテーマの白フラッシュを防止。
  200ms(--motion-duration-base)/ --motion-ease-out準拠、reduced-motionで無効化

Refs #1122

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* perf(ui): HistoryPaneを@tanstack/react-virtualで仮想化し250件上限を撤廃 (#1123) (#1142)

* perf(ui): HistoryPaneを@tanstack/react-virtualで仮想化し250件上限を撤廃

履歴リスト(HistoryPane)を仮想化し、可視領域+オーバースキャンのみを
マウントするよう変更。従来はカードをフラット描画し、250件上限は性能緩和策
だった。仮想化により描画コストが O(可視行) となり、上限を1000へ緩和した。

主な変更:
- FilePanelContent の流儀に揃え useVirtualizer + measureElement(可変高)で
  ConversationPairCard を仮想描画。展開/折りたたみ・コードブロック等の高さ
  変化は measureElement が自動再計測。
- スクロール位置維持: 追記は react-virtual が自動維持。最下部閲覧中
  (isNearBottom)のみ新規ペアを追従(follow/maintain を分離)。
- 検索ジャンプ整合: 対象行を scrollToIndex で materialize してからハイライト
  適用。マウント窓の変化(renderedRange)でハイライトを再適用。
- カードのアンマウントで展開状態が飛ばないよう、展開状態はリスト側/親
  (useConversationHistory + 検索auto-expand)で保持(#1121 の pending 挿入も
  この item 管理の上に載せやすい構成)。
- 250件上限を緩和: HISTORY_DISPLAY_LIMIT_OPTIONS に 500/1000 を追加
  (MAX_MESSAGES_LIMIT=1000 に自動追従、APIの取得上限も1000へ)。
- src/lib/history-virtualization.ts: overscan/estimate/しきい値の定数と純粋な
  isNearBottom を分離しユニットテスト可能に。
- テスト: jsdom は無レイアウトのため offsetHeight を与える helper
  (tests/helpers/virtual-layout.ts)と1000件fixtureを追加。仮想化のDOM件数・
  追従/維持判定・展開折りたたみ・検索名前空間を網羅。

1000件fixture before/after(jsdom初期描画。体感FPSは手動検証):
- マウントカード数: 1000 → 16
- DOMノード数: 26,001 → 449(約58分の1)
- 初期描画時間: 289ms → 24ms(約12倍高速)

品質ゲート: lint / tsc --noEmit / test:unit(8855 passing) / build 全パス。

Refs #1123

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ui): HistoryPane仮想化のゼロ計測フォールバックで本文が消える問題を修正

WorktreeDetail経由の統合テスト(jsdomはレイアウト高さ0)で virtualizer が
アイテムを0件しか materialize せず、メッセージ本文が描画されず getByText が
失敗していた(tests/integration/worktree-detail-integration.test.tsx の
'renders user and assistant messages')。

修正: virtualizer がビューポート未計測でアイテム0件のとき(初回レンダー/SSR/
jsdom 等のレイアウト0環境)、先頭 HISTORY_FALLBACK_RENDER_COUNT 件を通常フロー
で描画するフォールバックを HistoryPane に追加。実ブラウザでは layout-effect の
計測直後に仮想化リストへ切り替わる(フォールバック描画は塗り前の一時コミット)。
本番の仮想化保証を損なわないよう件数は小さく固定。SSRのハイドレーション整合
(server/client双方が同じ先頭スライスを描画)にも寄与する。

単体テスト側は既存の virtual-layout ヘルパーで実 virtualizer 経路を検証し続ける
ため、この修正はそれらに影響しない。

品質ゲート: lint / tsc --noEmit / test:unit(8855) / test:integration(648) /
build 全パス。

Refs #1123

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(realtime): ターミナル出力・セッションステータスをWebSocketストリーミング化 (#1120) (#1143)

* feat(realtime): ターミナル出力・セッションステータスをWebSocketストリーミング化

実装済みだが未接続だったWebSocketインフラをクライアントへ接続し、ポーリングを
フォールバックとして残しつつサーバ→クライアントの一方向pushへ移行する。

- useWebSocket再設計: 単一WS接続・指数バックオフ再接続(1s..30s)・visibility連携・
  room購読再送・エンベロープparse。RealtimeProvider/useRealtimeで共有1本化+
  listener fan-out+購読refカウント(Provider未装着時はpollingフォールバック)
- セッションステータスpush: send時にrunning遷移をbroadcast、kill時のstopと合わせ
  サイドバー状態ドットを即時反映。WS接続中はuseWorktreesCacheのポーリングを間引き
- ターミナルストリーミング: response-poller tickからterminal_snapshotをpush
  (buildCurrentOutputでpull/push一致、購読者0でtmux captureをスキップ)。
  WS接続中はuseTerminalPanePollingを15sフォールバックへ間引き、version guardで
  stale破棄(既存stale-response/visibility堅牢性を維持)
- 差分適用: computeTerminalUpdateで追記/リセットを判定しTerminalDisplayを
  keyed chunk追記化、全置換を廃止してテキスト選択を維持(ANSI境界退避付き)
- モバイル新着出力バッジをpush駆動で復活、接続状態インジケータを追加
- 認証: Cookie自動付与でWS upgrade、認証失敗時は既存機構が接続拒否

テスト: useWebSocket(購読/再接続/バックオフ)・差分適用(追記/リセット/ANSI境界)・
WS切断→ポーリング復帰→WS再接続の統合テスト・認証拒否のE2E統合テストを追加。
lint/tsc/test:unit/test:integration/build 全パス。

Refs #1120

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(realtime): useRealtimeConnection等のNULバイト混入を除去

useRealtimeConnection.tsx の room購読キー生成で空白(0x20)が2箇所NULバイト
(0x00)に化けており、gitがバイナリ扱いしdiff/レビュー不能になっていた。
本来の ' ' 区切り(useWorktreesCache と同一パターン)へ復元し、ロジックは不変。

- src/hooks/useRealtimeConnection.tsx: `.join('\0')`/`.split('\0')` → `.join(' ')`/`.split(' ')`
- src/components/worktree/FileTreeView.tsx: 既存のセンチネル `'\0unavailable'` から
  NULを除去し `'unavailable'`(JSON.stringify結果と衝突しない性質は維持)

src/配下のNULバイト混入は0件を確認。lint/tsc/test:unit/test:integration/build 全パス。

Refs #1120

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(chat): メッセージ送信のOptimistic UIを実装 (#1144)

送信瞬間に pending 吹き出しを履歴末尾へ即時挿入し、サーバ echo で実データへ
照合置換(重複表示なし)、失敗時はエラー表示+再試行/破棄する Optimistic UI を
追加。

- usePendingMessages 層: serverMessages に pending をマージし ChatMessage
  (optimisticState 付き)として表示側へ渡す。content 一致+作成時 baseline 外+
  consumedServerIds の一意消費で照合(連投 1:1/既存同一文言の誤照合回避)。
  API reject/timeout(30s) で error→retry/discard。
- ConversationPairCard/HistoryPane: sending(opacity低下+スピナー)と
  error(danger トークン+Retry/Discard)を描画。pending 中は
  「Waiting for response…」を抑止。
- MessageInput: onOptimisticSend 提供時は送信を委譲し composer を即時クリア
  (draft も除去)。他サーフェスは従来の await→clear を維持。
- TerminalSplitPaneContent: 各 split の useSplitMessages に usePendingMessages を
  重ね、送信→即時表示→onSent refetch で照合。破棄時は既存 insert 経路で draft 復元。
- 仮想化リストと整合: pending も pair 化され virtualizer item として最下部追従。

テスト: usePendingMessages(挿入/照合/失敗/連投/タイムアウト/retry/discard)、
ConversationPairCard(sending/error/retry/discard)。

Refs #1121

* fix(mobile): useIsMobileを描画前レイアウトエフェクトで解決し初回デスクトップ描画フリップを解消 (#1145)

## 事象
useIsMobile がSSR安全のため初期値 false で始まり、実際の検出を
passive な useEffect(ペイント後実行)で行っていたため、モバイル実機の
ハードリロードで毎回デスクトップUIが一瞬描画されてからモバイルUIへ
フリップしていた(AppShell の固定サイドバー / WorktreeDetail の
デスクトップ⇔モバイル分岐でのフラッシュ・CLS)。

## 対策の選定(案A/B/C のトレードオフ)
- 案A(CSSで両ツリーを出し分け): 初回フレームは正しいが、重い
  WorktreeDetail が二重マウントし、両分岐の terminal ペインが同時に
  ポーリングを開始する。ヘビーツリーには不採用。
- 案B(UAヒント+cookie): ブレークポイントは viewport 幅基準
  (max-width:767px)で UA の mobile とは一致せず(狭幅デスクトップ/
  タブレット)、真の hydration mismatch を再導入する。不採用。
- 案C(採用・描画前レイアウトエフェクト版): SSR/初回クライアント
  レンダリングは決定的に false(デスクトップ)を返し SSR HTML と一致
  するため hydration mismatch 警告は出ない。検出は useLayoutEffect
  (isomorphic)で行い、commit 後・ブラウザ描画前に flush されるため、
  補正後の isMobile 値が最初のフレームに反映される。モバイルでは
  デスクトップツリーは描画される前に差し替えられる。

## ヘビーツリーの二重マウント / ポーリング配慮
- 主ポーラーは分岐に依存しない単一の useWorktreeDetailController が
  一度だけ生成する(分岐は表示のみ)。
- 非表示側の表示ツリーは同一 commit 内でペイント前に破棄されるため、
  passive な useEffect ベースの terminal ポーリングは発火しない。
- 主要なコールドロード経路(キャッシュ無し→LoadingIndicator)では
  どちらの分岐の子もマウントされない。

## その他
- SSR での useLayoutEffect 警告は isomorphic パターン
  (server では useEffect)で回避。
- 既存の useIsMobile 単体テスト(初期値・リサイズ切替)は不変で継続。
- hydrateRoot を用いた新規テストで「hydration mismatch 警告が出ない」
  「モバイルビューポートでモバイル値へ解決」「デスクトップは false 維持
  (回帰なし)」を自動検証。

品質ゲート: lint / tsc --noEmit / test:unit / test:integration / build 全て成功。

Refs #1126

* feat(pwa): PWA対応(manifest+Service Worker+インストール可能化) (#1146)

Web App Manifest(manifest.ts)、手書きService Worker、インストール可能化を
実装。「外出先からエージェントを監視する」プロダクト特性に沿ってホーム画面
から独立アプリ(standalone)として起動可能にする。

Service Worker技術選定: 手書きvanilla SW(next-pwa/Serwist/Workbox不採用)。
理由:
- custom server(server.ts)がWebSocket upgradeを保持しており、next-pwa等が
  前提とするデフォルトNextサーバ構成と噛み合わない
- 本Issueが要求するallowlist方式(キャッシュ対象を明示列挙)を直接表現でき、
  監査容易・小規模・後続#1125 Web Pushの拡張が容易

キャッシュ戦略(allowlist):
- /api・/login・/proxy・非GET・クロスオリジン → network-only(キャッシュ禁止)
- /_next/static/・/icons/・manifest/favicon → cache-first
- navigation → offline-fallback(認証コンテンツを避けページ本体は非キャッシュ)
判定はcache-policy.ts(単体テスト済み)を単一情報源とし、public/sw.jsが同定数を
鏡写し、sw-file.test.tsで両者の同期を担保。

更新戦略: install時はskipWaitingせず、更新検知でトースト表示→リロードで
SKIP_WAITINGを送信しcontrollerchangeで新版に切替。

その他:
- 本番のみSW登録(shouldRegisterServiceWorker)、dev/test・非セキュアは無効化
- オフラインフォールバック画面(/offline、トークン準拠、再接続ボタン)
- iOS standalone用にappleWebAppメタ(capable/title/status-bar-style)を付与
- manifest/sw/offlineをAUTH_EXCLUDED_PATHSに追加(auth有効時もinstallable/
  precache可、いずれも非機密リソース)
- HTTPS/LAN配信時のSW/インストール制限をREADMEに注記

Refs #1124

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(mobile): モバイルA11y改善(focus trap・44pxタップターゲット・tablistキーボード) (#1147)

モバイルのアクセシビリティ/エルゴノミクスの穴を塞ぐ。

focus trap は共通フック useFocusTrap に1実装集約:
- 判断理由: Modal/シート類は独自ポータル+enter/exitアニメ(useExitAnimation
  /usePromptAnimation)+スワイプ dismiss を持つ bespoke 実装。@radix-ui/
  react-dialog は未導入で、置換すると3面すべてとそのテストを書き直す必要がある。
  共通フックなら既存機構を壊さず trap のみ追加でき、issue の「1実装に集約」に沿う。
- 挙動: 初期フォーカスをダイアログへ移動 / Tab・Shift+Tab をダイアログ内で循環 /
  閉時に呼び出し元へフォーカス復帰。autoFocus 等で消費側が内部フォーカスを
  管理している場合は初期フォーカス奪取・復帰の両方をスキップ(ConfirmDialog 互換)。
- 適用: ui/Modal(role=dialog/aria-modal/aria-labelledby も付与)・
  MobilePromptSheet・MobileTerminalActionsSheet。

44px タップターゲット(視覚サイズは不変、ヒットエリアのみ拡大):
- エージェントインスタンスタブ(WorktreeDetailRefactored)
- History サブタブ Message/Git(WorktreeDetailMobile)
- MobileTabBar の各タブ

MobileTabBar に ARIA tabs パターン完成:
- 矢印キー(左右/上下)+Home/End 移動、ラップアラウンド
- roving tabindex(activeタブのみ tabindex=0)、選択はフォーカス追従

touch-manipulation をボタン系プリミティブ(ui/Button)とモバイル操作面へ付与し
ダブルタップズームの遅延を抑止。

title のみだった WorktreeCard のお気に入りトグル(アイコンのみ)へ aria-label を
付与し、タッチ/スクリーンリーダーからも到達可能に。

スワイプジェスチャー等は本Issueスコープ外のため変更していない。

Refs #1127

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(mobile): ジェスチャー強化(スワイプタブ切替・pull-to-refresh・キーボード追従) (#1148)

- useSwipeGesture に axis/方向ロック/エッジ開始制限/onSwipeMove を後方互換で追加
  (既存 MarkdownEditor の四方向スワイプと #299 スクロール抑制は不変)
- worktree詳細のモバイルタブを水平スワイプで切替。方向ロックで縦スクロール中は
  無効化、しきい値60px、ターミナルタブはエッジ開始領域に限定して誤発火を抑止。
  MobileTabBar インジケータは activeTab 共有で自動同期
- Sessions/Repositories に pull-to-refresh を新設(usePullToRefresh + PullToRefresh)。
  最上部のみ発火し overscroll-behavior:contain + preventDefault でネイティブPTR二重発火を防止
- MessageInput を useVirtualKeyboard に接続しキーボード追従(keyboardAware opt-in)、
  入力要素へ enterKeyHint/inputMode を付与
- MobilePromptSheet のアドホックスワイプを useSwipeGesture へ統合(focus trap ref を
  マージして #1127 の挙動を維持、finger-follow は onSwipeMove で継続)

Refs #1128

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(pwa): エージェント状態のWeb Push通知(プロンプト待ち/完了) (#1149)

- push_subscriptions テーブル(migration v41)と CRUD モジュールを追加
- VAPID/web-push 送信層: 種別フィルタ・デバウンス・410自動削除、機密(endpoint/秘密鍵)は非ログ
- プロンプト検出/セッション完了時に response-checker からファンアウト(検出ロジックは不変・fire-and-forget)
- 購読登録/解除/種別設定/VAPID公開鍵の認証必須API(グローバルmiddleware配下)
- Service Worker に push/notificationclick ハンドラ(該当worktreeへdeep link・…
Kewton added a commit that referenced this pull request Jul 19, 2026
* feat(files): スマホ版でファイルをダウンロード可能に(attachment配信+FileViewer DL導線)(#1024) (#1025)

スマホ版のファイルビューア(FileViewer, Issue #438 でモバイル専用化)から
任意種別のファイルをダウンロードできるようにする。

- B(サーバ): files/[...path] GET に download 分岐を追加し、生バイトを
  Content-Type: application/octet-stream + Content-Disposition: attachment で返却。
  filename は src/lib/http の content-disposition ヘルパーで RFC 準拠にサニタイズ
  (ASCII フォールバック + filename*=UTF-8'' パーセントエンコード、ヘッダ
  インジェクション回避)。既存 getWorktreeAndValidatePath でパス検証を再利用。
  base64 JSON を経由しないためプレビュー上限を超える大容量にも対応
- A(UI): FileViewer ツールバーにダウンロードボタンを追加し、同一オリジンの
  download URL へ遷移(iOS Safari 含めネイティブDLに乗せる)
- テスト新規29本(download API のヘッダ/パス検証/filename、content-disposition
  ヘルパー、FileViewer ボタン)
- C(独立ページのPDF分岐/DLリンク)は範囲外(モバイル動線が独立ページを使わないため)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(session): Timer 発火の送信を History に記録(send ロジックを共有サービス化)(#1028) (#1029)

Timer 機能(Issue #534)から送った指示が History(chat_messages)に残らない
問題を修正。executeTimer が cliTool.sendMessage だけを呼び createMessage/
startPolling をスキップしていたのが原因(#947 の委譲変更が起点)。

- send route L262-385 のインライン記録ロジックを src/lib/session/
  send-user-message.ts の共通関数 sendUserMessage() として抽出
  (savePendingAssistantResponse → send(画像/Copilot分岐含む)→ createMessage
  → updateLastUserMessage/orphan処理 → startPolling)。HTTP 層非依存
- send route POST を sendUserMessage() 利用へリファクタ(挙動不変)
- timer-manager.ts executeTimer を sendUserMessage() 経由に変更。
  timer_messages の status 遷移(sending/sent/failed)は維持
- テスト: sendUserMessage / executeTimer のユニット新規9件 + timer 既存更新(計26件)

これにより Timer 送信も user メッセージと assistant 応答の両方が History に残る。

スコープ外(別Issue推奨):
- tests/integration/api-send-cli-tool.test.ts の1件は base でも失敗する既存不具合
  (2引数アサート vs 3引数呼び出し)で本修正と無関係(CI の test:unit 対象外)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(todo): ブランチ単位ToDoを3状態化(未着手/仕掛/完了)(#1032) (#1033)

- migration v38: worktree_todos に status カラム追加(done=1→'done' backfill、CURRENT_SCHEMA_VERSION 37→38)
- DB層: status を真実源に done := status==='done' を派生(後方互換維持、writer整合)
- API/クライアント: PATCH で status ∈ {todo,doing,done} を検証・適用、done後方互換
- UI: 3状態巡回チップ+色バッジ+状態別件数、TodoPane全体を next-intl 化
- i18n: locales/{en,ja}/worktree.json に todo namespace 追加
- テスト: 既存5更新+新規 migration-v38(backfill検証)

Closes #1032

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(todo): ブランチ単位ToDoに詳細フィールド追加+クリックで件名/詳細を編集 (#1034) (#1035)

- migration v39: worktree_todos に detail カラム追加(TEXT NOT NULL DEFAULT ''、既存行は空詳細で移行)
- DB層: WorktreeTodo に detail 追加、create/update で detail 受理
- API: POST/PATCH で detail を検証・適用(MAX_TODO_DETAIL_LENGTH 新設)
- クライアント: WorktreeTodoItem.detail、create/update の detail 対応
- UI: 項目クリックで編集モーダル(Modal.tsx再利用)を開き件名+詳細を編集、詳細ありインジケータ
- i18n: locales/{en,ja}/worktree.json に詳細/編集の文言追加
- テスト: 既存6更新+新規 migration-v39、docs/module-reference.md 更新

Closes #1034

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(todo): ブランチ単位ToDoの詳細にコピーボタンを追加 (#1036) (#1037)

- UI: 編集モーダルの詳細欄に CopyButton を配置し詳細本文をコピー(詳細が空のときは非表示)
- 既存 src/components/common/CopyButton.tsx / clipboard-utils.ts を再利用(スマホHTTPは execCommand フォールバック)
- i18n: locales/{en,ja}/worktree.json に todo.copyDetail 追加
- テスト: TodoPane.test.tsx にコピー導線テスト3件追加
- migration/DB/API 変更なし(UIのみ)

Closes #1036

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): セマンティックデザイントークン基盤を導入 (#1041) (#1054)

globals.css に :root(light)/.dark(dark) の CSS 変数
(background/foreground/surface/muted/border/input/ring/accent-50..700/
success/warning/danger/info) を定義し、tailwind.config.js の primary(cyan)・
cmd-bg-dark を semantic トークンへ置換。layout.tsx / MainLayout.tsx の
body 背景を bg-background に統一。既存の実効値を写し取り視覚は不変(no-op)。
docs/design-system.md を新設。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): cn()/cva によるバリアント管理基盤とプリミティブ改修 (#1042) (#1055)

clsx + tailwind-merge の cn() ヘルパーと class-variance-authority を導入し、
Button/Card/Badge/Modal を cva ベースへ移行。Button ghost バリアントの
dark: 指定漏れ(ダークモードで視認性劣化)を修正。既存 API は完全互換。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): アイコンを lucide-react に統一 (#1044) (#1056)

絵文字アイコン(🤖⚡✦💻)を廃止し lucide アイコンへ置換。共通レイアウト・ナビ
(Header/GlobalMobileNav/MobileTabBar/ActivityBar/terminal page)および worktree
各所のインライン SVG を lucide に統一。docs/design-system.md にアイコンサイズ規約を追記。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): next/font で Geist Sans/Mono を導入 (#1043) (#1057)

geist パッケージ(セルフホスト)で GeistSans/GeistMono の CSS 変数を <html> に適用し、
tailwind fontFamily の sans/mono を「Geist → 日本語(Hiragino/Noto Sans JP) →
システム」フォールバックで構成。Terminal.tsx(xterm)の等幅も Geist Mono を先頭指定。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): RadixベースUIプリミティブを拡充 (#1046) (#1058)

Input/Textarea/Select/Tabs/Tooltip/DropdownMenu/Switch/Skeleton を追加
(cva + cn, セマンティックトークン着色, ライト/ダーク対応)。Portal 系は
'use client' + Z_INDEX(POPOVER)で Modal と階層整合。Sessions ページの
フィルタ/ソートを新プリミティブへ移行(適用サンプル)。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): アクセント色を統一しセマンティックトークンへ置換 (#1045) (#1059)

cyan/blue 直書き(~600箇所 / 131ファイル)を #1041 のセマンティックトークンへ置換。
用途判定: インタラクティブ=accent / フォーカス=ring / 情報=info。gray 系は
surface/background/border/muted へ実効値を保って置換。例外(Terminal.tsx の
xterm ANSI 配色 / CLI ブランド色 / .scrollbar-thin 等の固定装飾色)は維持。
globals.css の .btn-primary・検索ハイライト等もトークン化。
敵対的レビュー指摘(focus:ring-accent→focus:ring-ring 統一、design-system.md
の例外記述修正)を反映。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): 主要ページを共通UIプリミティブへ移行 (#1047) (#1060)

layout(Header/Sidebar の検索・トグル・言語)/ home / repositories / review /
more / chat / login の生button・手組みフォーム・コピペカードを @/components/ui
プリミティブへ移行。機能・挙動(onClick/フォーム送信/バリデーション/キーボード)は維持。
敵対的レビュー指摘(chat系ボタン移行・ghost の dark hover)を反映。ネイティブ select・
複合 textarea 入力バー・極小アイコントグルは意図的に残置(PR の残置リスト参照)。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): globals.css の @apply クラスを廃止し cva プリミティブを自己完結化 (#1048) (#1062)

.btn/.btn-*/.card/.card-hover/.badge*/.input を globals.css から削除し、
Button/Card/Badge の cva 定義へ実クラスを内挿(旧 @apply 定義と同値、視覚不変)。
src/ 全体で削除クラスへの参照が 0 であることを確認。worktree の一部
(WorktreeDetailRefactored/LogViewer/WorktreeList/ExternalAppForm)をプリミティブへ移行し、
マジックナンバー入りインライン style を定数化。worktree/mobile/sidebar/common の
一括プリミティブ移行は規模の都合により #1061 へ分割。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): サーフェス・奥行きを再設計しモダンダークテーマへ刷新 (#1049) (#1063)

globals.css の :root/.dark トークン値を刷新(深い base + 微リフト surface +
ヘアライン border)。Header/GlobalMobileNav に backdrop-blur(supports-[backdrop-filter]
フォールバック付)。Card に elevated/interactive variant、h1-h4 見出しタイポを調整。
敵対的レビュー指摘を反映: 主要閲覧画面のコンテンツカード(sessions/home/review)を
bg-surface/border-border へ移行し背景反転による溶け込みを解消、ダーク入力を surface-2 で
凹み表現、Card interactive にキーボード focus 対応を追加。コントラストは WCAG AA 全 pass。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): モーション基盤とマイクロインタラクションを追加 (#1050) (#1064)

tailwindcss-animate を導入し、Modal/MobilePromptSheet の enter/exit、Radix data-state
連動(DropdownMenu/Select/Tooltip)、一覧の stagger、hover lift を統一適用。
prefers-reduced-motion 対応。ターミナル/仮想スクロールには非適用。敵対レビュー指摘
(reduced-motionでのtranslate漏れ→motion-safe化、sessionsエラー時のstagger再発火→list
マウント維持、Tooltip instant-open enter、home focus parity)を反映。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): Home をダッシュボード(bento グリッド)化 (#1052) (#1065)

Home を縦積みから bento グリッドへ再構成(統計/直近セッション/ToDo/クイックアクション)。
PC は CSS Grid(grid-cols-12 + col-span)、モバイルは1カラム縦積み(useIsMobile の JS 分岐を
増やさず Tailwind breakpoint)。既存 sessions 取得フック/API を再利用し新規 API なし。既存機能
(ToDo 追加/チェック/コピー、セッションカウント)を維持。空状態対応。敵対的レビュー指摘
(ToDo タイルの h-full・見出しパリティ)を反映。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): コマンドパレット(⌘K)を追加 (#1053) (#1066)

cmdk で ⌘K(mac)/Ctrl+K(win) のコマンドパレットを追加(Navigation / Worktrees 検索 / Actions)。
input/textarea/ターミナル入力中は非発火、SSR 安全('use client')、i18n(en/ja)、Modal 相当の z-index。
モバイルは GlobalMobileNav に起動導線。敵対的レビュー指摘を反映: open 時に入力へフォーカスし
キーボード操作(type/矢印/Enter)を復旧[HIGH]、worktree 一覧は共有キャッシュ context を再利用
(最新化+失敗時 Navigation fallback)、Escape の preventDefault/stopPropagation と close 時の
フォーカス復帰。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): エージェント/セッションステータスの視覚を強化 (#1051) (#1067)

StatusDot(running/waiting/idle/error)を新設。running はグロー+パルス+静的リング halo、
waiting は amber の弱点滅。サイドバー(BranchStatusIndicator)/ Home / Sessions を StatusDot 化。
prefers-reduced-motion でパルス停止(静的リングで running≠ready を維持)、ポーリングで再発火しない
CSS infinite アニメ。敵対的レビュー指摘(reduced-motion での running/ready 識別性、docs の実態訂正)を
反映。worktree 詳細/MobileHeader は従来スピナー表示のまま(docs に明記・今後の追随対象)。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ui): サイドバーがヘッダーに遮蔽される左上レイアウト破綻を修正 (#1070) (#1083)

showGlobalNav 時に固定サイドバーを top-16 h-[calc(100vh-4rem)] にオフセットし、
半透明ヘッダー(z-50, backdrop-blur)がサイドバー先頭64px(Branches 見出し+
view/sort/sync ツールバー行)を遮蔽してクリック不能+灰色の滲みを生む問題を解消。
あわせて border-gray-200/dark:border-gray-600 を border-border にトークン化。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): テーマ既定を system 化しヘッダーにトグルを追加 (#1071) (#1084)

next-themes を defaultTheme="system" enableSystem に変更し、ライト環境ユーザーへの
強制ダークを解消。Header 右クラスタに ThemeToggle を追加(発見可能性向上)。
globals.css に color-scheme(:root=light / .dark=dark)を宣言し、残存ネイティブ
コントロールの OS クローム事故(dark で白いドロップダウン等)を防止。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(hooks): useIsMobile を matchMedia ベースにし混成レイアウトバグを解消 (#1069) (#1085)

window.innerWidth 依存を window.matchMedia(`(max-width: ${breakpoint-1}px)`)に置換。
innerWidth と CSS ビューポートが乖離する環境(実測 innerWidth=846 vs
documentElement.clientWidth=390)では、Tailwind の md:(min-width:768px)と JS 分岐が
食い違い「CSS はモバイル・JS はデスクトップ」の混成レイアウトになっていた。
max-width:breakpoint-1 は md: の正確な補集合。change イベント購読+cleanup、
SSR 初期値 false、MOBILE_BREAKPOINT export と breakpoint オプションは維持。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): Home 第一フォールドを回収(バナー削除・見出し降格・tabular-nums) (#1072) (#1086)

陳腐化した Welcome バナー(#600 のナビ再編告知)を削除。h1「CommandMate」を機能的見出し
(HomeHeading: Overview + ライブサブライン)へ降格しヘッダーのワードマークとの同語反復を解消。
Home の数値・相対時刻・サイドバーのブランチ数バッジに tabular-nums を導入。HomeQuickActions の
inline SVG を lucide へ置換。相対時刻の短縮形は新設 date-utils、見出し文言は home i18n
namespace(en/ja)。機能(ToDo/カウント/遷移)は不変、サブラインは既存キャッシュから算出。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ライトモードのエレベーション階梯を反転(グレー地+白カード+2層影) (#1074) (#1087)

light を「白ページ+凹む灰カード」から Vercel/Linear 型の「グレー地ページ+
白カード+2層影」へ反転し、dark(#1049)と対のエレベーションモデルにする。
surface-2(740) < background(751) < surface(765) の「白カードが浮き、well が沈む」階梯。

- globals.css :root: --background 255→250 250 251 / --surface 248 250 252→255 255 255
  / --surface-2 241 245 249→244 246 250。.dark は不変(回帰なし)
- tailwind.config.js: 2層 boxShadow.sm を新規追加(md/lg は Tailwind 既定のまま)
- design-tokens/surface-depth テスト更新 + WCAG コントラスト検証を追加
  (foreground≥4.5 全面 / muted-fg≥4.5 page,card / ≥3 recessed well)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): サイドバーを --sidebar-* トークン化しテーマ追従に (#1073) (#1088)

bg-gray-800 text-white 固定でテーマ孤島だったサイドバーを、新設の
--sidebar / --sidebar-foreground / --sidebar-border / --sidebar-hover /
--sidebar-muted セマンティックトークンへ移行。light=slate-50系の明るいパネル、
dark=#141821(#1049階梯と整合)でテーマに追従する。standalone なリテラルRGB値
としており、#1074 の --surface 反転がサイドバー色へ滲まない。

- globals.css :root/.dark に --sidebar-* を定義、tailwind.config.js に登録
- Sidebar.tsx / sidebar/*(BranchListItem, SortSelectorBase)/ 検索Input
  / LocaleSwitcher / ThemeToggle の直書きを全てトークンへ置換
- 敵対的レビュー由来のライト回帰修正2件:
  - LogoutButton.tsx: 漏れていた text-gray-300 直書きを ThemeToggle と同じ
    テーマ中立トークン(text-muted-foreground/hover:bg-muted)へ(モバイル共用)
  - SortSelectorBase: 選択中ソートを text-accent-700 dark:text-accent-400 に
    (ライトのslate-50上で 1.73→約5.1:1 でAA達成、dark時は据え置き)
- docs/design-system.md に --sidebar-* を追記、既存テスト/期待値を更新

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ⌘K パレットを Raycast 級に強化(アイコン/StatusDot/kbd/Recents/ヘッダー導線)(#1077) (#1089)

- src/components/ui/Kbd.tsx を新設(semantic-token ベース、#1080 で再利用)し ui/index.ts に export 追加
- NAV 行に lucide アイコン、worktree 行に GitBranch + StatusDot(isProcessing/isWaitingForResponse/isSessionRunning から算出)
- バックドロップ/パネル/選択状態/入力/フッター文字色を semantic トークン化(bg-surface/backdrop-blur/ring-border/accent-500)
- キーボードガイドのフッター(↑↓/↵/esc、i18n)
- Recent グループ(localStorage cm.palette.recents、MRU<=8、空クエリ時のみ、dead worktree は無視)
- worktree を isSessionRunning 優先→updatedAt 降順ソート+空クエリ時 slice(0,8)
- Actions 拡充: Sync repositories(repositoryApi.sync + Toast)/ 言語切替 / Open GitHub
- Header に ⌘K 検索ピル(mount 後に platform 判定して ⌘K/Ctrl K を出し分け)
- #1053 の挙動契約(フォーカス/入力ガード/Escape/backdrop/data-testid)を維持
- 文言を locales/{en,ja}/commandPalette.json に追加、単体テスト更新

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ターミナルクロームを刷新(全周枠廃止・カード化・CLIセレクタDropdownMenu化)(#1079) (#1090)

worktree ターミナルの全周枠を廃し surface-2 カード+ヘアラインのカード化に統一。
CLI インスタンスセレクタを native <select> から Radix DropdownMenu(StatusDot+alias
+ChevronDown)へ置換し、value/onChange の単一選択セマンティクスと testid を保持。

- Terminal / ActivityBar / TerminalDisplay / TerminalSplitContainer /
  TerminalSplitPane / TerminalSplitPaneContent を surface/border トークンへ
- TerminalSplitPane: <select> → DropdownMenu(RadioGroup)。cli-selector testid 保持
- xterm の theme は意図的に固定ダーク(--surface-2 dark 相当)を維持
- StatusDot/status-colors.ts は不変(#1078 の領域)。locales worktree.json 追記
- 既存 terminal テスト(cli-tab-switching 含む)更新・全パス

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): worktree詳細/Chat のダーク直書き島を surface トークン化しテーマ追従に (#1075) (#1091)

HistoryPane / ConversationPairCard / AssistantMessageList / AssistantChatPanel /
HistorySearchBar / CopyButton と .assistant-md の常時ダーク直書きを surface/foreground
セマンティックトークンへ移行し、light/dark 両テーマで正しく描画されるようにする。

- globals.css .assistant-md: リンクを accent-700 dark:accent-400、code/pre を明色地に
  し両テーマ AA≥4.5:1。:root/.dark のトークン定義は不変
- 各コンテナ+全子孫を surface/surface-2/foreground/muted-foreground/border トークンへ
  (hidden-children 回避: 子の直書き light-on-dark を残さない)
- AssistantChatPanel/HistoryPane の native select/checkbox は色のみトークン化
  (Radix 化は #1076 の担当のため据え置き)
- ConversationPairCard: コピー/挿入/展開ツールバーの hover-reveal 化に伴い、
  タッチ端末で不可視化する回帰を [@media(hover:none)]:opacity-100 で解消
  (デスクトップの hover-reveal は維持。copy-user/assistant-message testid 保持)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): データ画面コントロールの意味論を整理(Review/Repositories/More)(#1081) (#1092)

表示のみの調整。絞り込み・編集・削除・可視性トグルのロジックは不変。

- Review: フィルタを CTA 風の primary/secondary ボタンから、件数付きの
  控えめなセグメンテッドコントロールへ(選択は bg-accent-500/15、CTA と区別)。
  件数は既存のフィルタ済み配列長から算出(新規 fetch なし)。
- Repositories: Enabled/Disabled バッジと可視性トグルの緑ピルを StatusDot 流の
  dot+muted テキストへ(トグルは操作可能なまま)。(none)→ muted の em-dash、
  パスは truncate+font-mono+full-path tooltip、Edit は ghost な Pencil アイコンボタン。
- More: ExternalAppsManager 側の重複 h2「External Apps」を削除(ページ側に一本化)。
- StatusDot/status-colors.ts は不変(#1078 の領域)。生 gray/cyan/blue の追加なし。
- 既存テスト更新+件数バッジ/単一見出しの検証テストを追加。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): コンポーザーをカード化しモバイルworktreeコントロールを整理 (#1080) (#1093)

- MessageInput をコンポーザーカード化(surface/border/shadow/focus-ring)し、送信
  ボタンを入力有無で filled(bg-accent-600)/ghost に切替。プレースホルダを i18n 短縮
- Auto-Yes をコンポーザー下辺のメタ行へ統合(ui/Switch 化)。右側に Kbd キーヒント
  (#1077 の ui/Kbd 再利用)。InterruptButton のオレンジ直値を text-danger ghost へ
- モバイル sticky 行をインスタンスタブ専用化(dark: バリアント+下線インジケータ)。
  検索/End を MobileTerminalActionsSheet(ボトムシート、End は確認付き)へ移設
- MobileTabBar を GlobalMobileNav 様式(bg-background/backdrop-blur/border-border、
  上辺インジケータ、Wrench アイコン、success/warning バッジ)へ統一
- テスト: 送信ボタン状態変化/新規シートを追加、ResizeObserver polyfill(Radix Switch)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): 稼働ステータスを StatusDot 一本化し idle ノイズを縮退 (#1078) (#1094)

青スピナー(border-info)と生 span を全て統一 StatusDot に置換し、稼働ステータスの
視覚言語を1系統に。idle ノイズを縮退(Sessions は running/waiting のみラベル付き、
idle は「+N idle」カウンタ / worktree ヘッダーは idle を StatusDot のみの円形ボタン
+Tooltip、幅超過は「+N」DropdownMenu オーバーフロー)。

- WorktreeDetailSubComponents / sessions/page / MobileHeader を StatusDot へ統一
- 新規 lib/agent-status-display.ts に classifyHeaderInstances 等の導出を集約
- status-colors.ts の type:'spinner'/border-info は @deprecated 化(ReviewTab が
  最後の消費者。消滅後に削除)
- 相対時刻 tabular-nums / プレビューは CSS truncate、DnD(handleAgentDragStart)と
  タブ切替 onClick は全ボタンで温存
- 敵対レビュー由来の仕上げ: 「+N」オーバーフローに稼働中(running/waiting)があれば
  集約 StatusDot グローを出し、稼働セッションが glow-less に埋もれないように。
  同 trigger の生グレーをセマンティックトークンへ

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): ネイティブ checkbox/radio を Radix プリミティブに統一 (#1076) (#1095)

OS 標準の白いフォームコントロールが dark に浮く問題を解消。@radix-ui/react-checkbox
/ react-radio-group ベースの ui/Checkbox・ui/RadioGroup を新設し、残存していた
ネイティブ input(checkbox 12箇所/11ファイル・radio 6箇所/4ファイル)を全て移行。

- 新規 src/components/ui/{Checkbox,RadioGroup}.tsx(cn()+cva・セマンティックトークン・
  focus-visible:ring-ring)、ui/index.ts に export、依存2つを追加
- checked/onCheckedChange・value/onValueChange への適応、name/value/disabled/
  aria/data-testid とラベルクリックのトグルを全移行で保持
- 移行後 native type="checkbox"/"radio" は src/ 配下 0 件(完了性 grep 検証済)
- 敵対レビュー(binding/primitive-a11y/GitDangerZonePanel 密集)0件

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): トークン規律の確立(生gray/slate置換・focus-visible統一・CIガード)(#1082) (#1096)

移行済み領域の生 gray/slate 直書きをセマンティックトークンへ置換し、フォーカス表現を
focus-visible に統一、CI grep ガードでトークン違反の再流入を止める。機能・挙動は不変
(見た目/規律のみ)。

## プリミティブ是正
- Button: secondary/ghost を muted/foreground、danger を bg-danger トークンへ。
  `focus:` → `focus-visible:` + `ring-offset-background`(dark の白ハロー解消。Tabs/Switch が手本)。
- Input/Textarea/Select トリガー: `focus:` → `focus-visible:`(枠が反応するため offset なし)。
- Badge gray → bg-muted/text-muted-foreground、Modal → surface/border/foreground。

## in-scope スイープ(生 gray/slate → トークン)
- app(worktrees を除く)/ ui / layout / home / review / repository / common。
- gray-100/200→muted、gray-400/500/600→muted-foreground、gray-700/800/900→foreground、
  境界→border、フォーム枠→input。

## globals.css / metadata / docs
- body→text-foreground、.prose インラインコード→muted/foreground、scrollbar-thin をトークン化
  (--input / --muted-foreground)、`::selection` にアクセント淡色を追加。
- layout.tsx: title template(`%s | CommandMate`)+ viewport themeColor(light/dark)。
- docs/design-system.md にフォーカス表現・CI ガード・例外リストを同期。

## CI ガード
- `.github/workflows/ci-pr.yml` に `token-discipline` ジョブを追加(CLAUDE.md size check と同方式)。
  ホワイトリスト方式で移行済みディレクトリ限定 hard-fail。除外リストをスクリプト内に明記。
  違反注入時に fail することを確認済み。

## 判断メモ(要レビュー)
- `src/app/worktrees/**`(40 occ)はオーケストレーター集計では app(108) に含まれるが、
  ワークツリー領域(#1061・「触らない」)かつ常時ダークのターミナルページ/CLI ブランド色
  (bash=bg-gray-600)を含むため、スイープ・ガード双方から除外した。#1061 完了時に拡大。
- 軽微な見た目変化: danger の red-600→red-500(--danger)、secondary/ghost の muted シェード、
  SidebarToggle はテーマ追従化、ツールチップは反転サーフェス(bg-foreground/text-background)へ
  テーマ追従化、Home の Save&Resend ボタンを accent-600 + 白文字に正規化。

品質ゲート(lint / tsc --noEmit / test:unit / build)+ 新 CI ガード 全パス。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree/共通の生gray/buttonをトークン・プリミティブへ (#1061 群A 1/4) (#1097)

#1061 のファイル分割並列(群A: 21ファイル)。生 gray/slate Tailwind クラスを
#1040/#1082 のセマンティックトークンへ用途判定で置換、生 <button> を ui/Button へ。
挙動・ハンドラ・testid は不変。群A担当ファイルの raw gray = 0。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree詳細/markdown/fileの生gray/buttonをトークンへ (#1061 群B 2/4) (#1100)

#1061 分割並列(群B: 21ファイル)。WorktreeDetailSubComponents/MarkdownToolbar/
FileTreeView 等の生 gray/slate → セマンティックトークン、生 button → ui/Button。
ターミナル出力表示面(TerminalSplitPaneContent)はダーク維持のため意図的に非トークン化。
挙動・testid 不変。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree/mobile/gitの生gray/buttonをトークンへ (#1061 群C 3/4) (#1098)

#1061 のファイル分割並列(群C: 22ファイル)。生 gray/slate → セマンティックトークン、
生 <button> → ui/Button。挙動・testid 不変。群C担当ファイルの raw gray = 0。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ui): worktree/git/todoの生gray/buttonをトークンへ (#1061 群D 4/4) (#1099)

#1061 分割並列(群D: 21ファイル)。生 gray/slate → セマンティックトークン、生 button
→ ui/Button。ターミナル出力表示面(TerminalSplitContainer/TerminalContainer)は
xterm と同様ダーク維持のため意図的に非トークン化(除外)。挙動・testid 不変。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci(guard): token-discipline ガードを worktree/mobile/external-apps へ拡大 (#1061) (#1101)

#1061 で worktree/mobile/external-apps の生 gray/slate をトークン化完了したため、
#1082 で導入した token-discipline CIガードの対象ディレクトリを拡大。
ターミナル出力表示面(*Terminal* ソース)は xterm 固定ダーク(#1079)と同様の
意図的ダーク島のため除外。app/worktrees も引き続き除外。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: integration スイートの drift を解消(モック/期待値追随・CI追加)(#1102) (#1103)

CI 非対象で長期 drift していた integration スイート(41 failed)を修復。実装バグを
隠さないよう vi.mock を importOriginal 方式へ書き換え、欠落 export を補完。

- 不完全 vi.mock を importOriginal ベースへ(欠落 export を実挙動ごと通す)
- config ドリフト(アップロード上限 5MB→20MB 等)の期待値を現行定数準拠へ
- undefined.length 系はモック戻り値を実 shape へ
- 再 drift 防止に ci-pr.yml へ Integration Tests ジョブ追加
- external-apps duplicate name → 500(期待409) は実装バグのため it.skip で quarantine

結果: test:integration 643 passed / 1 skipped(0 failed)。unit/lint/tsc 緑維持。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(external-apps): 重複nameで500ではなく409を返す (#1104) (#1105)

* fix(external-apps): 重複nameで500ではなく409を返す (#1104)

route.ts の catch を message.includes('UNIQUE constraint') 文字列判定から
ExternalAppDbError.code === 'DUPLICATE' の型/コード判定へ変更。DB層のエラー
ラップ後もメッセージに依存せず 409 を正しく返す。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(external-apps): 409 duplicate name テストを un-skip(#1104 で修正済み)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(timer): Timer履歴の詳細モーダル表示と失敗理由の記録 (#1107) (#1109)

Schedule 側の execution_logs 方式を Timer へ横展開。

- DB: migration v40 で timer_messages に error 列(nullable)追加、schema 39→40
- timer-db: error 列の read/write 貫通、updateTimerStatus に optional error 引数、recoverStuckSendingTimers に固定リカバリ理由
- timer-manager: 送信失敗(stage/error)・例外(error.message)を error 列へ永続化(no_session は保存せず)
- API: GET timers レスポンスに error を追加
- UI: TimerDetailModal を新設し履歴行クリックで指示全文+メタ+失敗理由を表示(PC=Modal / mobile=FullScreenModal)
- i18n: schedule.json の timer ブロックに en/ja lockstep でラベル追加

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(files): Filesツリー展開状態のworktree単位永続化と表示リセット (#1108) (#1111)

useFileTabs / useFilePanelState の worktree 単位 localStorage パターンを踏襲。

- 新規 useFileTreeExpandedState: 展開集合を worktree 単位 localStorage(キー commandmate:file-tree-expanded:<worktreeId>) に永続化。lazy-init 復元をマウント再フェッチに乗せ、空集合はキー削除、worktree 間の漏洩なし
- useFileTabs: CLOSE_ALL / closeAllTabs() を追加
- FileTreeView: フック統合+ツールバーに「表示をリセット」ボタン(更新ボタンの隣・常時可視)+ onResetView prop
- Controller/Desktop/Mobile/Refactored: resetFileTreeView(検索クリア・全タブ閉じ・モバイルビューア閉)を配線し、初期表示に戻すフルリセットを実現

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(mobile): viewportFit:'cover' を追加しiOS safe-area対応を有効化 (#1132)

- src/app/layout.tsx の viewport エクスポートに viewportFit: 'cover' を追加。
  iOS は viewport-fit=cover 指定時のみ env(safe-area-inset-*) に非0値を
  入れるため、既存の pt-safe/pb-safe 等の safe-area 対応が実効化される
- GlobalMobileNav の未定義クラス safe-area-bottom を globals.css 定義済みの
  pb-safe に修正(cover 有効化後もパディングが効かない潜在バグ)
- viewport エクスポート値(viewportFit / themeColor)のユニットテストを追加

レンダリング検証: 本番ビルドで <meta name="viewport"> に
viewport-fit=cover が含まれることを確認済み

Refs #1131

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat(ui): ステータスカラーtintトークン導入とダーク非対応フィードバックUIの移行 (#1133)

- globals.css / tailwind.config.js に success/warning/danger/info × subtle/border/foreground の12トークンを両テーマで追加(ライト *-50 面 + *-800 前景、ダーク *-950 面 + *-300 前景、全て WCAG AA 準拠。根拠はコメントに記載)
- Toast / DefaultErrorFallback / History・Prompt・ConnectionErrorFallback / ErrorDisplay / PromptPanel を新トークンへ移行し、生パレット + dark: ペアを撤去(TerminalErrorFallback は常時ダーク島のため現状維持)
- tint 面内のソリッドアクションボタンは反転 tint(bg-{status}-foreground text-{status}-subtle)で両テーマ AA を確保
- docs/design-system.md にトークン表・使用ルールを追記
- tint トークン定義・移行 grep を検証する status-tint-tokens.test.ts と ErrorBoundary/フォールバックのレンダリングテストを追加、既存 Toast / design-tokens テストを追随

Refs #1112

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* refactor(ui): window.confirmを共通ConfirmDialogへ全面置換 (#1134)

- ConfirmDialog/ConfirmProvider/useConfirm を src/components/ui/ConfirmDialog.tsx に新設
  (variant default/danger、Escape/背景クリックでキャンセル、danger時はcancelに初期フォーカス、
  クローズ後に呼び出し元へフォーカス復帰)
- AppProviders に ConfirmProvider をマウント
- window.confirm 残存11箇所 + bare confirm 1箇所(WorktreeCard)を useConfirm へ置換
- confirm文言を next-intl 辞書へ移動 (review namespace 新設、home/worktree/common に追記)
- ESLint no-restricted-globals(confirm/alert) と no-restricted-properties(window.confirm) を追加
  (bare alert 3箇所は window.alert へ変更、alert置換自体は対象外)
- 既存テストを window.confirm モックからダイアログ操作へ追随、
  ConfirmDialog 単体テストと Timer履歴クリアの結合フローを追加

Refs #1113

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* chore(ui): UI死コード削除(WorktreeList・放棄infinite-scroll hooks・hasNewOutput配線) (#1135)

- WorktreeList.tsx(production importゼロの孤児コンポーネント)とbarrel export(index.ts)を削除
- 放棄された無限スクロール実装(useInfiniteMessages/useScrollObserver/useScrollRestoration)と
  対応テスト3本、孤児化するtypes/infinite-messages.tsを削除
- WorktreeDetailRefactored.tsxのhasNewOutput={false}死配線を撤去(MobileTabBar側は維持)
- 残置ファイルのstaleコメント修正(useVisibilityRecovery.ts/history-display-config.ts)
- useWebSocket.tsに@deprecated注記を付与(WebSocketストリーミング化で再設計・再利用予定)
- docs/architecture.md(ja/en)の削除対象ファイルへのstale参照行を削除

受入基準: 3グレップ全て0件成立、lint/tsc/test:unit(8746 pass)/build全パス

Refs #1115

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* fix(ui): 未定義animate-fade-*の定義とModal/Toast/ContextMenuのexitアニメーション実装 (#1114) (#1136)

* fix(ui): 未定義animate-fade-*の定義とModal/Toast/ContextMenuのexitアニメーション実装

- tailwind.config.jsにfade-in/fade-outキーフレームとanimate-fade-in/outを定義。
  usePromptAnimationが発行していたが未定義でno-opだった。クラス名を
  PromptPanel等が文字列比較で消費しているため、tailwindcss-animateへの
  書き換えではなくキーフレーム定義の追加を採用。duration/easingは
  --motion-duration-base / --motion-ease-out(#1050モーショントークン)準拠
- content globsにsrc/hooksを追加(フック発行クラスのpurge防止)
- useExitAnimation(open, duration)フックを新設: 閉要求後もduration msだけ
  マウントを維持しexitアニメーション再生を保証。render-phase同期により
  開閉ともに1フレームの遅延なし。再open時はタイマー解除
- Modal: data-state=closedでfade+zoom-out exit(200ms)後にアンマウント。
  exit中はbackdropクリック無効化
- Toast: fade+slide-out exit(200ms)再生後にonClose通知(アンマウント遅延)
- ContextMenu: fade+zoom-out exit(100ms、enterと同duration)。
  exit中はpointer-events-none
- EXIT_ANIMATION_DURATION_MS / CONTEXT_MENU_EXIT_DURATION_MSを
  ui-feedback-config.tsに追加しCSSとJSタイマーを同期
- reduced-motionはglobals.cssのグローバルキルスイッチで対応(CSS実装のみのため追加ガード不要)
- テスト: useExitAnimation単体、Modal/Toast/ContextMenuのexit挙動
  (閉操作後にアニメーション時間経過までDOM残留→アンマウント)、
  Tailwind実コンパイルによるCSS生成検証を追加

Refs #1114

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ui): ConfirmDialogをModal exitアニメーションに追随

- ConfirmDialog.test.tsx: 閉操作後の即時DOM除去期待を、200ms exit
  アニメーション完了後のアンマウント(waitForElementToBeRemoved)に更新
- ConfirmProvider: pendingクリア後のexit再生中にdescription等が空文字に
  なり内容がブランク表示される統合ドリフトを修正(直近optionsを保持し
  フェードアウト中も表示を維持)。cancelテストにブランク防止の検証を追加

Refs #1114

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat(ui): ヘッダーナビのアクティブインジケータ実装と角丸スケール規約の導入 (#1137)

- Header ナビに CSS-only の下線インジケータを実装(after 疑似要素の
  scale-x トランジション、--motion-ease-out / duration-200 準拠、
  motion-safe ガード)。アクティブ項目に aria-current="page" を付与
- Header のアクティブ状態レンダリングテストを追加(全 6 ルート)
- docs/design-system.md に角丸スケール規約を独立セクションとして追記
  (コントロール=rounded-md / コンテナ=rounded-lg / ポップアップ=rounded-md /
  チップ=rounded-full / 小型インライン=rounded-sm、裸 rounded 原則禁止)

角丸統一の実施範囲(明確な逸脱のみ、feature 配下の一括置換はしない):
- ui/Kbd・ui/Checkbox: 裸 rounded → rounded-sm
- layout/Sidebar のアイコンボタン3箇所: 裸 rounded → rounded-md
- layout/Header のコマンドパレットトリガー: rounded-lg → rounded-md

Refs #1119

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* refactor(ui): チャット履歴UIをConversationPairCard系デザインへ統一 (#1138)

MessageList/PromptMessageの視覚言語をConversationPairCardに統一
(案B: 構造・optimistic updateロジック維持、見た目のみ刷新)。

- 生chromatic色21箇所をステータスtintトークン(warning/success/info x
  subtle/border/foreground)へ移行し、生色のdark:ペアを撤去
- インラインSVGをlucide-reactへ統一(Check/X/CircleCheck/Clock/Loader2/
  MessageCircle)
- ユーザー/アシスタントメッセージをCPC言語のセクション(accent左ボーダー/
  surface-2 wash)+ hover-revealコピーツールバーへ刷新
  ([@media(hover:none)]:opacity-100 タッチフォールバック付き)
- markdown描画をprose→assistant-md(トークン追従)へ移行、ANSI/コード
  ブロックの常時ダーク島(#0d1117)は現状維持
- ファイルパスリンク/プロンプト応答(Yes/No・選択肢・テキスト入力)/
  ログリンクの機能パリティ維持、レンダリングテストを新規追加

Refs #1117

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat(ui): スケルトンローディング展開とSpinner統一・loading.tsx整備 (#1139)

- ui/Spinner.tsx 新設: 手書きインラインSVG/border系スピナー43箇所を共通プリミティブへ置換
- 主要画面にレイアウト整合スケルトンを実装(Home Session Overview/ToDo、Sessions一覧、Repositories一覧、HistoryPane(汎用Card型・loading分岐のみ)、ファイルツリー、AssistantChatPanel、Reviewタブ群)
- 裸テキストローディングをスケルトンへ置換(MessageList.tsxは#1117衝突回避のため対象外)
- 初回ロードのみスケルトン表示(再fetch時は既存コンテンツ維持の非ブロッキングパターン踏襲)
- 主要7ルートに loading.tsx(薄いRouteLoadingフォールバック)を追加
- AssistantChatPanelのローディングを導出状態化し空状態フラッシュを解消

Refs #1118

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* refactor(ui): 生chromatic色をtintトークンへ全面移行しCIガードを拡大 (#1140)

意味ベースでステータスtintトークンへ移行 (red→danger / green→success /
yellow・amber・orange→warning / blue・sky・purple・violet→info)。
淡色面は bg-{s}-subtle + border-{s}-border + text-{s}-foreground、
ソリッドは bg-{s} を使用し、chromatic用途の dark: ペアを撤去。

- ui/Badge.tsx・config/status-colors.ts をトークン化 (生パレットクラス撤去)
- error/ の常時ダーク島 TerminalErrorFallback を *Terminal* 名の別ファイルへ分離し、
  残る error/・auth/ の生gray/chromatic漏れを回収
- 検索ハイライト/ファイル種別アイコン等の装飾色もトークン化 (text-pink等スコープ外色は維持)
- CIガード token-discipline の grep パターンに chromatic 9色を追加し error/・auth/ を対象化。
  既存の *Terminal*・テスト・常時ダーク島 (worktrees/[id]/terminal 等) の除外は維持
- docs/design-system.md に chromatic 禁止/例外リストとCIガード仕様を追記
- 影響する class 検証テストを追随
  (Badge / ContextMenu / MarkdownEditor / MemoCard / dark-mode-foundation / recursive-delete)

chromatic grep 残数
(git grep -cE '(bg|text|border|ring)-(red|green|yellow|amber|orange|purple|violet|sky|blue)-[0-9]' -- src/):
  before: 254 行 (71 ファイル)
  after:  19 行 (サンクション済み例外のみ = *Terminal* 群 + src/app/worktrees/[id]/terminal/)
  in-scope (例外除く): 0

品質ゲート: npm run lint / npx tsc --noEmit / npm run test:unit (8818 passed) / npm run build 全パス

Refs #1116

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(ui): View Transitions APIによるページ遷移クロスフェードを導入 (#1141)

主要ルート間(Home/Chat/Sessions/Repos/Review/More/worktree詳細)のコンテンツ
領域をクロスフェードするプログレッシブエンハンスメントを追加する。

実装方式は「自前の薄いラッパ(案B)」を採用し、next-view-transitions等の外部
依存は追加しない。理由:
- 受入基準が機能検出ガード/reduced-motion無効化の単体テストを要求しており、ガード
  関数を自前で保持することで直接テスト可能になる(ライブラリ委譲では不可)
- framer-motion不採用方針(docs/design-system.md #1050)と同様にCSS駆動とし、JSは
  startViewTransition呼び出しのガードのみに限定してバンドルを軽量に保つ
- Next15/React19ネイティブView Transitionsへの載せ替えが容易(抽象が薄い)
- ライブラリ導入時のLink全面置換を避け、変更をnavシェルとプログラム遷移に限定

主な構成:
- src/lib/view-transitions: supportsViewTransitions/prefersReducedMotion/
  startViewTransition。非対応・reduced-motion時は即時遷移フォールバック
- ViewTransitionsProvider: startViewTransitionをApp Routerナビゲーションへ橋渡し。
  usePathnameのコミット監視で新DOM確定後にresolveし、同一ルート等の非コミット時は
  COMMIT_SAFETY_TIMEOUT_MS=500で確実にsettle
- TransitionLink / useViewTransitionRouter: Header・GlobalMobileNav・Sidebarで使用。
  修飾クリック/別タブ/外部/同一ルートはブラウザ既定にフォールスルー
- globals.css: [data-view-transition="content"](AppShellの<main>のみ)をクロス
  フェードし、永続シェル(ヘッダー/サイドバー/ボトムナビ)はrootを固定して除外。
  ::view-transition背景を--backgroundに設定しダークテーマの白フラッシュを防止。
  200ms(--motion-duration-base)/ --motion-ease-out準拠、reduced-motionで無効化

Refs #1122

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* perf(ui): HistoryPaneを@tanstack/react-virtualで仮想化し250件上限を撤廃 (#1123) (#1142)

* perf(ui): HistoryPaneを@tanstack/react-virtualで仮想化し250件上限を撤廃

履歴リスト(HistoryPane)を仮想化し、可視領域+オーバースキャンのみを
マウントするよう変更。従来はカードをフラット描画し、250件上限は性能緩和策
だった。仮想化により描画コストが O(可視行) となり、上限を1000へ緩和した。

主な変更:
- FilePanelContent の流儀に揃え useVirtualizer + measureElement(可変高)で
  ConversationPairCard を仮想描画。展開/折りたたみ・コードブロック等の高さ
  変化は measureElement が自動再計測。
- スクロール位置維持: 追記は react-virtual が自動維持。最下部閲覧中
  (isNearBottom)のみ新規ペアを追従(follow/maintain を分離)。
- 検索ジャンプ整合: 対象行を scrollToIndex で materialize してからハイライト
  適用。マウント窓の変化(renderedRange)でハイライトを再適用。
- カードのアンマウントで展開状態が飛ばないよう、展開状態はリスト側/親
  (useConversationHistory + 検索auto-expand)で保持(#1121 の pending 挿入も
  この item 管理の上に載せやすい構成)。
- 250件上限を緩和: HISTORY_DISPLAY_LIMIT_OPTIONS に 500/1000 を追加
  (MAX_MESSAGES_LIMIT=1000 に自動追従、APIの取得上限も1000へ)。
- src/lib/history-virtualization.ts: overscan/estimate/しきい値の定数と純粋な
  isNearBottom を分離しユニットテスト可能に。
- テスト: jsdom は無レイアウトのため offsetHeight を与える helper
  (tests/helpers/virtual-layout.ts)と1000件fixtureを追加。仮想化のDOM件数・
  追従/維持判定・展開折りたたみ・検索名前空間を網羅。

1000件fixture before/after(jsdom初期描画。体感FPSは手動検証):
- マウントカード数: 1000 → 16
- DOMノード数: 26,001 → 449(約58分の1)
- 初期描画時間: 289ms → 24ms(約12倍高速)

品質ゲート: lint / tsc --noEmit / test:unit(8855 passing) / build 全パス。

Refs #1123

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ui): HistoryPane仮想化のゼロ計測フォールバックで本文が消える問題を修正

WorktreeDetail経由の統合テスト(jsdomはレイアウト高さ0)で virtualizer が
アイテムを0件しか materialize せず、メッセージ本文が描画されず getByText が
失敗していた(tests/integration/worktree-detail-integration.test.tsx の
'renders user and assistant messages')。

修正: virtualizer がビューポート未計測でアイテム0件のとき(初回レンダー/SSR/
jsdom 等のレイアウト0環境)、先頭 HISTORY_FALLBACK_RENDER_COUNT 件を通常フロー
で描画するフォールバックを HistoryPane に追加。実ブラウザでは layout-effect の
計測直後に仮想化リストへ切り替わる(フォールバック描画は塗り前の一時コミット)。
本番の仮想化保証を損なわないよう件数は小さく固定。SSRのハイドレーション整合
(server/client双方が同じ先頭スライスを描画)にも寄与する。

単体テスト側は既存の virtual-layout ヘルパーで実 virtualizer 経路を検証し続ける
ため、この修正はそれらに影響しない。

品質ゲート: lint / tsc --noEmit / test:unit(8855) / test:integration(648) /
build 全パス。

Refs #1123

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(realtime): ターミナル出力・セッションステータスをWebSocketストリーミング化 (#1120) (#1143)

* feat(realtime): ターミナル出力・セッションステータスをWebSocketストリーミング化

実装済みだが未接続だったWebSocketインフラをクライアントへ接続し、ポーリングを
フォールバックとして残しつつサーバ→クライアントの一方向pushへ移行する。

- useWebSocket再設計: 単一WS接続・指数バックオフ再接続(1s..30s)・visibility連携・
  room購読再送・エンベロープparse。RealtimeProvider/useRealtimeで共有1本化+
  listener fan-out+購読refカウント(Provider未装着時はpollingフォールバック)
- セッションステータスpush: send時にrunning遷移をbroadcast、kill時のstopと合わせ
  サイドバー状態ドットを即時反映。WS接続中はuseWorktreesCacheのポーリングを間引き
- ターミナルストリーミング: response-poller tickからterminal_snapshotをpush
  (buildCurrentOutputでpull/push一致、購読者0でtmux captureをスキップ)。
  WS接続中はuseTerminalPanePollingを15sフォールバックへ間引き、version guardで
  stale破棄(既存stale-response/visibility堅牢性を維持)
- 差分適用: computeTerminalUpdateで追記/リセットを判定しTerminalDisplayを
  keyed chunk追記化、全置換を廃止してテキスト選択を維持(ANSI境界退避付き)
- モバイル新着出力バッジをpush駆動で復活、接続状態インジケータを追加
- 認証: Cookie自動付与でWS upgrade、認証失敗時は既存機構が接続拒否

テスト: useWebSocket(購読/再接続/バックオフ)・差分適用(追記/リセット/ANSI境界)・
WS切断→ポーリング復帰→WS再接続の統合テスト・認証拒否のE2E統合テストを追加。
lint/tsc/test:unit/test:integration/build 全パス。

Refs #1120

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(realtime): useRealtimeConnection等のNULバイト混入を除去

useRealtimeConnection.tsx の room購読キー生成で空白(0x20)が2箇所NULバイト
(0x00)に化けており、gitがバイナリ扱いしdiff/レビュー不能になっていた。
本来の ' ' 区切り(useWorktreesCache と同一パターン)へ復元し、ロジックは不変。

- src/hooks/useRealtimeConnection.tsx: `.join('\0')`/`.split('\0')` → `.join(' ')`/`.split(' ')`
- src/components/worktree/FileTreeView.tsx: 既存のセンチネル `'\0unavailable'` から
  NULを除去し `'unavailable'`(JSON.stringify結果と衝突しない性質は維持)

src/配下のNULバイト混入は0件を確認。lint/tsc/test:unit/test:integration/build 全パス。

Refs #1120

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(chat): メッセージ送信のOptimistic UIを実装 (#1144)

送信瞬間に pending 吹き出しを履歴末尾へ即時挿入し、サーバ echo で実データへ
照合置換(重複表示なし)、失敗時はエラー表示+再試行/破棄する Optimistic UI を
追加。

- usePendingMessages 層: serverMessages に pending をマージし ChatMessage
  (optimisticState 付き)として表示側へ渡す。content 一致+作成時 baseline 外+
  consumedServerIds の一意消費で照合(連投 1:1/既存同一文言の誤照合回避)。
  API reject/timeout(30s) で error→retry/discard。
- ConversationPairCard/HistoryPane: sending(opacity低下+スピナー)と
  error(danger トークン+Retry/Discard)を描画。pending 中は
  「Waiting for response…」を抑止。
- MessageInput: onOptimisticSend 提供時は送信を委譲し composer を即時クリア
  (draft も除去)。他サーフェスは従来の await→clear を維持。
- TerminalSplitPaneContent: 各 split の useSplitMessages に usePendingMessages を
  重ね、送信→即時表示→onSent refetch で照合。破棄時は既存 insert 経路で draft 復元。
- 仮想化リストと整合: pending も pair 化され virtualizer item として最下部追従。

テスト: usePendingMessages(挿入/照合/失敗/連投/タイムアウト/retry/discard)、
ConversationPairCard(sending/error/retry/discard)。

Refs #1121

* fix(mobile): useIsMobileを描画前レイアウトエフェクトで解決し初回デスクトップ描画フリップを解消 (#1145)

## 事象
useIsMobile がSSR安全のため初期値 false で始まり、実際の検出を
passive な useEffect(ペイント後実行)で行っていたため、モバイル実機の
ハードリロードで毎回デスクトップUIが一瞬描画されてからモバイルUIへ
フリップしていた(AppShell の固定サイドバー / WorktreeDetail の
デスクトップ⇔モバイル分岐でのフラッシュ・CLS)。

## 対策の選定(案A/B/C のトレードオフ)
- 案A(CSSで両ツリーを出し分け): 初回フレームは正しいが、重い
  WorktreeDetail が二重マウントし、両分岐の terminal ペインが同時に
  ポーリングを開始する。ヘビーツリーには不採用。
- 案B(UAヒント+cookie): ブレークポイントは viewport 幅基準
  (max-width:767px)で UA の mobile とは一致せず(狭幅デスクトップ/
  タブレット)、真の hydration mismatch を再導入する。不採用。
- 案C(採用・描画前レイアウトエフェクト版): SSR/初回クライアント
  レンダリングは決定的に false(デスクトップ)を返し SSR HTML と一致
  するため hydration mismatch 警告は出ない。検出は useLayoutEffect
  (isomorphic)で行い、commit 後・ブラウザ描画前に flush されるため、
  補正後の isMobile 値が最初のフレームに反映される。モバイルでは
  デスクトップツリーは描画される前に差し替えられる。

## ヘビーツリーの二重マウント / ポーリング配慮
- 主ポーラーは分岐に依存しない単一の useWorktreeDetailController が
  一度だけ生成する(分岐は表示のみ)。
- 非表示側の表示ツリーは同一 commit 内でペイント前に破棄されるため、
  passive な useEffect ベースの terminal ポーリングは発火しない。
- 主要なコールドロード経路(キャッシュ無し→LoadingIndicator)では
  どちらの分岐の子もマウントされない。

## その他
- SSR での useLayoutEffect 警告は isomorphic パターン
  (server では useEffect)で回避。
- 既存の useIsMobile 単体テスト(初期値・リサイズ切替)は不変で継続。
- hydrateRoot を用いた新規テストで「hydration mismatch 警告が出ない」
  「モバイルビューポートでモバイル値へ解決」「デスクトップは false 維持
  (回帰なし)」を自動検証。

品質ゲート: lint / tsc --noEmit / test:unit / test:integration / build 全て成功。

Refs #1126

* feat(pwa): PWA対応(manifest+Service Worker+インストール可能化) (#1146)

Web App Manifest(manifest.ts)、手書きService Worker、インストール可能化を
実装。「外出先からエージェントを監視する」プロダクト特性に沿ってホーム画面
から独立アプリ(standalone)として起動可能にする。

Service Worker技術選定: 手書きvanilla SW(next-pwa/Serwist/Workbox不採用)。
理由:
- custom server(server.ts)がWebSocket upgradeを保持しており、next-pwa等が
  前提とするデフォルトNextサーバ構成と噛み合わない
- 本Issueが要求するallowlist方式(キャッシュ対象を明示列挙)を直接表現でき、
  監査容易・小規模・後続#1125 Web Pushの拡張が容易

キャッシュ戦略(allowlist):
- /api・/login・/proxy・非GET・クロスオリジン → network-only(キャッシュ禁止)
- /_next/static/・/icons/・manifest/favicon → cache-first
- navigation → offline-fallback(認証コンテンツを避けページ本体は非キャッシュ)
判定はcache-policy.ts(単体テスト済み)を単一情報源とし、public/sw.jsが同定数を
鏡写し、sw-file.test.tsで両者の同期を担保。

更新戦略: install時はskipWaitingせず、更新検知でトースト表示→リロードで
SKIP_WAITINGを送信しcontrollerchangeで新版に切替。

その他:
- 本番のみSW登録(shouldRegisterServiceWorker)、dev/test・非セキュアは無効化
- オフラインフォールバック画面(/offline、トークン準拠、再接続ボタン)
- iOS standalone用にappleWebAppメタ(capable/title/status-bar-style)を付与
- manifest/sw/offlineをAUTH_EXCLUDED_PATHSに追加(auth有効時もinstallable/
  precache可、いずれも非機密リソース)
- HTTPS/LAN配信時のSW/インストール制限をREADMEに注記

Refs #1124

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(mobile): モバイルA11y改善(focus trap・44pxタップターゲット・tablistキーボード) (#1147)

モバイルのアクセシビリティ/エルゴノミクスの穴を塞ぐ。

focus trap は共通フック useFocusTrap に1実装集約:
- 判断理由: Modal/シート類は独自ポータル+enter/exitアニメ(useExitAnimation
  /usePromptAnimation)+スワイプ dismiss を持つ bespoke 実装。@radix-ui/
  react-dialog は未導入で、置換すると3面すべてとそのテストを書き直す必要がある。
  共通フックなら既存機構を壊さず trap のみ追加でき、issue の「1実装に集約」に沿う。
- 挙動: 初期フォーカスをダイアログへ移動 / Tab・Shift+Tab をダイアログ内で循環 /
  閉時に呼び出し元へフォーカス復帰。autoFocus 等で消費側が内部フォーカスを
  管理している場合は初期フォーカス奪取・復帰の両方をスキップ(ConfirmDialog 互換)。
- 適用: ui/Modal(role=dialog/aria-modal/aria-labelledby も付与)・
  MobilePromptSheet・MobileTerminalActionsSheet。

44px タップターゲット(視覚サイズは不変、ヒットエリアのみ拡大):
- エージェントインスタンスタブ(WorktreeDetailRefactored)
- History サブタブ Message/Git(WorktreeDetailMobile)
- MobileTabBar の各タブ

MobileTabBar に ARIA tabs パターン完成:
- 矢印キー(左右/上下)+Home/End 移動、ラップアラウンド
- roving tabindex(activeタブのみ tabindex=0)、選択はフォーカス追従

touch-manipulation をボタン系プリミティブ(ui/Button)とモバイル操作面へ付与し
ダブルタップズームの遅延を抑止。

title のみだった WorktreeCard のお気に入りトグル(アイコンのみ)へ aria-label を
付与し、タッチ/スクリーンリーダーからも到達可能に。

スワイプジェスチャー等は本Issueスコープ外のため変更していない。

Refs #1127

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(mobile): ジェスチャー強化(スワイプタブ切替・pull-to-refresh・キーボード追従) (#1148)

- useSwipeGesture に axis/方向ロック/エッジ開始制限/onSwipeMove を後方互換で追加
  (既存 MarkdownEditor の四方向スワイプと #299 スクロール抑制は不変)
- worktree詳細のモバイルタブを水平スワイプで切替。方向ロックで縦スクロール中は
  無効化、しきい値60px、ターミナルタブはエッジ開始領域に限定して誤発火を抑止。
  MobileTabBar インジケータは activeTab 共有で自動同期
- Sessions/Repositories に pull-to-refresh を新設(usePullToRefresh + PullToRefresh)。
  最上部のみ発火し overscroll-behavior:contain + preventDefault でネイティブPTR二重発火を防止
- MessageInput を useVirtualKeyboard に接続しキーボード追従(keyboardAware opt-in)、
  入力要素へ enterKeyHint/inputMode を付与
- MobilePromptSheet のアドホックスワイプを useSwipeGesture へ統合(focus trap ref を
  マージして #1127 の挙動を維持、finger-follow は onSwipeMove で継続)

Refs #1128

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(pwa): エージェント状態のWeb Push通知(プロンプト待ち/完了) (#1149)

- push_subscriptions テーブル(migration v41)と CRUD モジュールを追加
- VAPID/web-push 送信層: 種別フィルタ・デバウンス・410自動削除、機密(endpoint/秘密鍵)は非ログ
- プロンプト検出/セッション完了時に response-checker からファンアウト(検出ロジックは不変・fire-and-forget)
- 購読登録/解除/種別設定/VAPID公開鍵の認証必須API(グローバルmiddleware配下)
- Service Worker に push/notificationclick ハンドラ(該当worktreeへdeep link・…
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working refactor Code refactoring / technical debt

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant