Skip to content

feat: alt screen でスクロールバックを無効化し上へのスクロールをロック - #5

Merged
takecchi merged 2 commits into
mainfrom
feat/alt-screen-scroll-lock
Jul 18, 2026
Merged

feat: alt screen でスクロールバックを無効化し上へのスクロールをロック#5
takecchi merged 2 commits into
mainfrom
feat/alt-screen-scroll-lock

Conversation

@takecchi

Copy link
Copy Markdown
Owner

概要

全画面レイアウト(#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 後に書き、シェル履歴へ残す。
  • docs / rules: TECH_NOTES.md / ARCHITECTURE.md / TASKS.md / .claude/rules/ink-components.md を実装に合わせて更新。

設計判断

  • alt screen の enter/leave は純粋ロジックではなく端末への I/O なので utils/ に配置(アーキテクチャ規約: 純粋ロジックと I/O の分離)。
  • フォールバック時(非 TTY / 低い端末)は enter しない — インライン描画は端末スクロールに依存するため。

テスト計画

  • npm test — 147件 全緑(coverage 閾値クリア)
  • npm run lint / npm run typecheck / npm run build — 緑
  • 手動: 使い捨てリポジトリで npm run dev 起動 → マウスホイールで上へスクロールできないこと
  • 手動: 終了後にシェル画面が復元され、残存 worktree 案内が履歴に残ること
  • 手動: 低い端末(< 16 行)でインライン描画へフォールバックし、通常どおりスクロールできること

takecchi added 2 commits July 19, 2026 01:49
全画面レイアウトでも通常スクリーンバッファのままだったため、シェルの
過去出力がスクロールバックに残り上へスクロールできてしまっていた。
起動時に 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 を実装に合わせて更新
@takecchi
takecchi merged commit fd375ef into main Jul 18, 2026
1 check passed
@takecchi
takecchi deleted the feat/alt-screen-scroll-lock branch July 18, 2026 16:56
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 + 認証が要るため未実施)
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