Skip to content

fix: 質問待ちの詳細ビューで会話ログを遡れるようにする(Tab で切替) - #125

Merged
takecchi merged 1 commit into
mainfrom
codiva/tab-tab
Aug 13, 2026
Merged

fix: 質問待ちの詳細ビューで会話ログを遡れるようにする(Tab で切替)#125
takecchi merged 1 commit into
mainfrom
codiva/tab-tab

Conversation

@takecchi

Copy link
Copy Markdown
Owner

背景

セッション詳細ビューは pendingPermission があるあいだキーを全部 PermissionDialog へ委譲していたため、↑↓ / PgUp / PgDn がすべて選択肢の移動に食われ、会話ログを 1 行も遡れなかった。結果、「何の話か分からないまま質問に答える」しかない状態だった。

一覧画面には同じ問題への答え(ListFocus = composer / dialog / list を Tab で回す)が既にあるので、詳細ビューにも同じ考え方(↑↓ が指す対象を一意にする)を持ち込む。

変更

  • DetailFocusdialog / log)を追加し、許可/質問待ちのあいだ Tab で往復する。
    • 既定は dialog — 回答は待たせている用事なので、そこへ辿り着くために Tab を踏ませない(一覧の zoneForRow と同じ方針)。
    • log ゾーンでは ↑↓ / PgUp / PgDn がログのスクロールに戻る。ダイアログは active={false}表示のまま(質問文を読みながら遡れる)。
    • 回答へ戻る経路は Tab / Esc / ダイアログのクリックonActivate)の 3 つ。
    • 回答の途中経過(何問目か・チェック済みの選択肢・書きかけの自由記述)は内部 state なのでアンマウントしない(一覧と同じ)。
    • pending が解けたらゾーンを dialog へ畳む(次の質問でキーの意味が勝手に変わらないように)。
  • マウス: ホイールはゾーンに関係なく通す(押下と違い副作用が無く、背景を読む第一の手段)。押下系は log ゾーンだけ通すので、範囲選択と URL クリックも普段どおり使える(dialog ゾーンでは従来どおり飲む)。
  • 文言: detail.helpLog / detail.dialogInactiveHelp を ja/en に追加し、detail.helpPendingTab の案内を足した。PermissionDialog の「表示のみ」ヒントは view から渡す(inactiveHint)— 一覧の「↑↓: セッション切替」は詳細では嘘になるため、共有コンポーネントに view の知識を持たせない。
  • ドキュメント: .claude/rules/ink-components.md(ゾーンの不変条件・マウスの例外)/ docs/ARCHITECTURE.mdSessionDetail の責務)/ README.md(利用者向けのキー表)を実装より先に更新。

テスト

tests/app.test.tsx に統合テストを追加(許可待ちでも Tab でログを遡れ、Tab で回答へ戻れる):

  • dialog ゾーンでは でログが動かない(ダイアログのもの)
  • ホイールは dialog ゾーンでも効く
  • Tablog ゾーンでフッタ/ダイアログの案内が入れ替わり、質問文は表示されたまま
  • log ゾーンの でログを遡れ、「過去ログを表示中」が出る
  • Tab で戻ってそのまま y で許可できる(往復でダイアログが壊れない)
  • lint(Biome)/ typecheck / testtests/app.test.tsx 99 件)/ build の 4 点

体感確認(実 TTY + 認証が必要)はお願いします: 詳細ビューで質問が来ている状態で Tab↑↓ でログが遡れること、Tab / クリックで回答へ戻れることを確認してください。

🤖 Generated with Claude Code

セッション詳細では `pendingPermission` があいだキーを全部 PermissionDialog へ
委譲していたため、↑↓/PgUp/PgDn がすべて選択肢の移動に食われて**会話ログを 1 行も
遡れなかった**。質問の背景(何をしようとしているのか)が分からないまま回答する
しかない状態だったので、一覧の `ListFocus` と同じようにゾーンを分ける。

- `DetailFocus`(dialog / log)を追加し、`Tab` で往復する。既定は `dialog`
  (回答は待たせている用事なので、そこへ辿り着くのに Tab を踏ませない)。
- `log` ゾーンでは ↑↓ / PgUp / PgDn がログのスクロールに戻り、ダイアログは
  `active={false}` で表示のまま残す(回答の途中経過は内部 state なので
  アンマウントしない)。`Esc` とダイアログのクリックでも回答へ戻れる。
- ホイールはゾーンに関係なく通す(押下と違って副作用が無く、背景を読む第一の手段)。
  `log` ゾーンでは押下系も通すので範囲選択・URL クリックも普段どおり使える。
- pending が解けたらゾーンは `dialog` へ畳む(次の質問でキーの意味が変わらないように)。
- 「表示のみ」の案内は view から渡す(`inactiveHint`)。一覧の
  「↑↓: セッション切替」は詳細では嘘になるため。

@takecchi takecchi left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

レビューしました。追加の指摘事項はありません。LGTM です。

DetailFocus によってダイアログとログのキー操作が明確に分離されており、ダイアログをアンマウントせず active だけを切り替えるため、回答途中の内部状態も保持されます。ホイール、クリック、Esc、Ctrl+C の各経路についても既存の PermissionDialog 側ハンドラーとの競合を確認しましたが、問題は見つかりませんでした。

確認結果:

  • 追加された「許可待ちでも Tab でログを遡れ、Tab で回答へ戻れる」テスト成功
  • npm run typecheck 成功
  • npm run build 成功
  • GitHub CI 成功

補足: ローカルで全テストを並列実行した際、今回の差分外にある既存の非同期テストがタイミング依存で失敗しました(hooks.spec.tsx 3件、app.test.tsx 5件)。hooks.spec.tsx は単独再実行で 7/7 成功し、対象の新規統合テストも成功しているため、本PRの不具合とは判断していません。

@takecchi
takecchi merged commit d09f4ad into main Aug 13, 2026
1 check passed
@takecchi
takecchi deleted the codiva/tab-tab branch August 13, 2026 07:52
takecchi added a commit that referenced this pull request Aug 20, 2026
## 背景(報告された問題)

「ask が画面に出ているとき、下の詳細ログは隠れるのか」を調べたところ、**重なるのではなく押し潰されて**いました。ダイアログはログの兄弟で
`flexShrink={0}`、ログ側が `flexGrow={1}` なので、ダイアログの高さぶんログの可視行がそのまま減ります。

実測(幅 100 桁・選択肢 4 件+説明あり・詳細ビュー):

| 端末の行数 | ask 表示前のログ行数 | 表示後(修正前) | 表示後(修正後) |
|---|---|---|---|
| 24 | 16 | **0** | 5 |
| 30 | 22 | 5 | 5 |
| 40 | 32 | 15 | 15 |
| 50 | 42 | 25 | 25 |

24 行の端末では `Tab: ログを遡る` に切り替えても表示行が 0 なので、**質問の背景を読む手段が事実上なかった**(#125
で入れた Tab の意味が失われる)状態でした。

## 変更

コマンドパレットの `paletteMaxRows`(「ログを数行残す」上限)と同じ構図を ask にも入れました。

- **`core/layout.ts`** — `dialogMaxRows(rows,
view)`。ダイアログの縦幅(枠込み)に上限を設け、下に `DIALOG_CONTENT_RESERVE`(5
行)を必ず残す。ダイアログは入力欄の位置に出るので `COMPOSER_ROWS`(3) は戻して数える。極端に低い端末では
`DIALOG_MIN_ROWS` を下限にする。
- **`core/choice-lines.ts`** — 純関数 `choiceView(heights, cursor, cap)`。1
件 = 1 行ではない(ラベルの折返し + 説明)ので行数で詰める窓計算。カーソルの件は必ず窓に入れ、溢れた端には「↑↓ 他 N 件」用に 1
行予約する。下端アンカーなので `listView` / `visibleLineRange` と同じスクロール感(↑↓
でカーソルを送ると窓がついてくる)。
- **`ui/permission-dialog.tsx`** —
実際に描く固定部分(枠・見出し・質問文の折返し・区切り+相談する・ヒント・自由記述の入力欄)から選択肢ブロックの予算を出して窓表示。**クリックの当たり判定も描いた窓で逆算**する(上端インジケータ
1 行ぶんずれる)。
- **`ui/composer.tsx`** — 入力欄が描く行数 `drawnRows` を `ComposerController`
に公開(自由記述モードの高さ見積りに使う。既に内部で計算していた値)。
- **`ui/session-detail.tsx` / `ui/session-list.tsx`** —
`maxRows={dialogMaxRows(rows, …)}` を渡す。
- **i18n** — `permission.moreAbove` / `moreBelow`(ja/en 対で追加)。
- **docs** — README(詳細ビューの Tab 節)と
`.claude/rules/ink-components.md`(「`flexShrink={0}`
は縮む役を他へ回すだけなので、ダイアログ自身にも上限を持たせる」)を追記。

24 行の端末では選択肢 5 件が「1 件 + `↓ 他 4 件`」に畳まれ、ログは 5 行残ります。

## 対象外

- **ツール許可ダイアログ(`ToolDialog`)には上限を入れていません。** 入力サマリが 200 文字で切られているため最大でも
10 行程度で、ログを潰す規模になりません(許可対象のコマンドを黙って隠す方が有害)。
- 自由記述モード(「自分で入力する」)は入力欄 3 行ぶん余分に要るので、24 行の端末ではログが 2 行まで縮みます(0 にはなりません)。

## テスト

- `src/core/choice-lines.spec.ts` — `choiceView` のテーブルドリブン(先頭 / 末尾 / 中間
/ 範囲外カーソル、描画行数が cap 以下、↓ で単調にスクロール、1 件が cap より高い場合、cap < 1)
- `src/core/layout.spec.ts` — `dialogMaxRows`(chrome の差引・24 行端末で reserve
が残る・下限・単調増加)
- `tests/app.test.tsx` — 24 行の端末で ask を出し、ログの席が残る / `↓ 他 4 件` が出る / ↓
で案D まで辿れる / **窓の中の選択肢をクリック → Enter で正しい回答が返る**(当たり判定のずれの番人)
- `tests/commands.test.tsx` — `/prompt` エディタのドラッグ選択テストが固定 `flush()`
でフレームを掴んでいてフルスイートで落ちたので `settle()` に変更(helpers.ts の注記どおり)

`lint` / `typecheck` / `test`(2927 passed) / `build` の 4 点をローカルで通しています。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant