feat: alt screen でスクロールバックを無効化し上へのスクロールをロック - #5
Merged
Conversation
全画面レイアウトでも通常スクリーンバッファのままだったため、シェルの 過去出力がスクロールバックに残り上へスクロールできてしまっていた。 起動時に alt screen(\x1b[?1049h)へ入り、終了時に leave(\x1b[?1049l) することで、vim / htop と同様にスクロールをロックし、終了時は元の画面を 復元する。 - utils/alt-screen.ts: enter/leave の薄い I/O ラッパ。leave は冪等で、 クラッシュ時に端末を取り残さないよう process 'exit' に保険登録し、 明示 leave で解除する - index.tsx: TTY かつ起動時 rows >= MIN_FULLSCREEN_ROWS のときだけ enter (インライン描画フォールバック時は端末スクロールに頼るため通常バッファ)。 終了メッセージは leave 後に書き、シェル履歴へ残す - docs / rules を実装に合わせて更新
…l-lock # Conflicts: # src/index.tsx
6 tasks
takecchi
added a commit
that referenced
this pull request
Jul 18, 2026
## 概要 Phase 6 バックログの残り3項目を実装し、**Phase 6 を完了**する。 - 詳細ビューのログスクロール(過去ログを遡る手段) - 入力欄の複数行化(Shift+Enter 改行・内部スクロール、Claude Code 同等) - `includePartialMessages` によるストリーミング表示 いずれもコアロジックは純粋関数に切り出して TDD(テスト先行)。alt screen(#5)で端末スクロールバックを無効化した後なので、アプリ内スクロールの必要性が高い状態での実装。 ## 変更点 ### ログスクロール(詳細ビュー) - **`src/core/scroll.ts`(新規・純粋)**: `logWindow` / `scrollUp` / `scrollDown` / `pageStep`。`anchor` は `'bottom'`(末尾追従)か絶対 end index(**上スクロール中は固定** = 新着ログで表示位置がぶれない top-anchored)。`end` は anchor で厳密に、`start` は「埋まるぶん」だけ取り flex-end ビューポートがクリップ。 - **`src/core/layout.ts`**: `logViewportRows(rows)`(= `rows - DETAIL_CHROME_ROWS`)を追加。スクロール移動量を端末全体でなく**可視ログ高さ**基準に算出し、一度に画面外の行を飛び越えないようにする。`tailMessages` は `logWindow` に置き換えて削除。 - **`src/ui/session-detail.tsx`**: PgUp/PgDn を配線。追加指示の送信時は `'bottom'` へ戻す。上スクロール中はスクロールバック位置のインジケータ行を表示。 ### 複数行入力 - **`src/core/text-buffer.ts`(新規・純粋)**: `value + cursor` モデル。`insert` / `backspace` / `moveLeft/Right/Up/Down` / `visibleLineRange`。**サロゲートペアを跨いで**カーソル移動・削除(絵文字等で文字列が壊れない)。 - **`src/ui/input.ts`**: キー→操作の対応 `editText` / `resolveEnter`。Shift/Meta+Enter か**末尾バックスラッシュ+Enter**で改行(後者は Shift+Enter を送れない端末向けの堅牢なフォールバック)、他は送信。一覧ビューは矢印を行選択に温存、詳細ビューは矢印でフルにカーソル移動。 - **`src/ui/prompt-input.tsx`**: 複数行描画。`INPUT_MAX_ROWS` まで縦に伸び、超過分は `visibleLineRange` でカーソル付近を内部スクロール。空/1行時は 1 行高を維持(全画面レイアウトの高さ不変)。 ### ストリーミング表示 - **`src/core/session.ts`**: SDK query に `includePartialMessages: true`。 - **`src/core/status-reducer.ts`**: `stream_event` の `content_block_delta` / `text_delta`(非空)のみ `state.streamingText` に連結。確定 `assistant` / `result` / `user_input` / `aborted` / `archived` でクリア(確定ログが正)。`streamingText` は transient で**永続しない**。SDK 型(`SDKPartialAssistantMessage` / `BetaRawContentBlockDeltaEvent`)から形を確認して実装(想定書きしない規約)。 - **`src/ui/session-detail.tsx`**: 末尾にタイピング風プレビュー(末尾追従中のみ)。 ### ドキュメント - `docs/TASKS.md`(3項目チェック + 実績メモ、Phase 6 完了)/ `docs/ARCHITECTURE.md` / `docs/TECH_NOTES.md` / `.claude/rules/ink-components.md` を実装に合わせて更新。 ## 設計判断 - **純粋ロジックと I/O の分離**: スクロール・テキストバッファ・ストリーミング判定はすべて `core/` の純関数に置き、UI はキー→操作の対応と描画のみ。UI からロジックを排除(規約: architecture.md)。 - **top-anchored スクロール**: 上スクロール位置を絶対 end index で持つことで、新着ログ追記時に見ている位置がぶれない。 - **ページ移動量は可視ログ高さ基準**: 端末全体の rows を使うと固定 chrome ぶん多く飛び、未表示の行を飛ばしてしまうため `logViewportRows` を導入。 - **改行のフォールバック**: 端末が Shift+Enter を区別できないケースに備え、末尾バックスラッシュ+Enter でも改行できるようにした。 ## テスト計画 - [x] `src/core/scroll.spec.ts` / `src/core/text-buffer.spec.ts` / `status-reducer.spec.ts`(stream_event・aborted/archived クリア・空 delta no-op)をテーブル駆動で追加 - [x] `tests/app.test.tsx` に統合3本(PgUp/PgDn スクロール・ストリーミングプレビュー+`includePartialMessages` 検証・バックスラッシュ改行) - [x] `npm test`(**297 件 全緑**、coverage 閾値クリア) - [x] `npm run typecheck` / `npm run lint` / `npm run build` — 緑 - [x] `code-reviewer` エージェントの指摘(HIGH×2 / MEDIUM / LOW)をすべて修正 - [ ] 手動: 使い捨てリポジトリ + `claude` ログイン済みで `npm run dev` 起動 → 実端末での改行・ログスクロール・ストリーミングの体感確認(TTY + 認証が要るため未実施)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
概要
全画面レイアウト(#3)でも端末を上へスクロールできてしまう問題を修正する。原因は通常スクリーンバッファのまま描画しており、シェルの過去出力などのスクロールバックが残っていたこと。
起動時に alt screen(代替スクリーンバッファ,
\x1b[?1049h) へ入るようにした。alt screen にはスクロールバックが存在しないため、vim / htop と同様にスクロールがロックされる。終了時は\x1b[?1049lで leave し、元のシェル画面を復元する。変更点
src/utils/alt-screen.ts(新規): enter/leave の薄い I/O ラッパ。leaveは冪等。クラッシュ時に端末を alt screen に取り残さないようprocess.on('exit')に保険登録し、明示 leave で解除する。src/utils/alt-screen.spec.ts(新規): enter/leave シーケンス・冪等性・exit フックの登録/解除・exit イベント経由の復元をカバー。src/index.tsx: 合成ルートで配線。process.stdout.isTTYかつ起動時rows >= MIN_FULLSCREEN_ROWSのときだけ enter(インライン描画フォールバック時は端末スクロールに頼るため通常バッファのまま)。判定は起動時の一度きり(途中リサイズでバッファを切り替えると画面が壊れるため追従しない)。終了時の残存 worktree 案内は leave 後に書き、シェル履歴へ残す。TECH_NOTES.md/ARCHITECTURE.md/TASKS.md/.claude/rules/ink-components.mdを実装に合わせて更新。設計判断
utils/に配置(アーキテクチャ規約: 純粋ロジックと I/O の分離)。テスト計画
npm test— 147件 全緑(coverage 閾値クリア)npm run lint/npm run typecheck/npm run build— 緑npm run dev起動 → マウスホイールで上へスクロールできないこと