Skip to content

feat: 端末縦幅いっぱいの全画面レイアウト(100dvh 相当) - #3

Merged
takecchi merged 1 commit into
mainfrom
feat/fullscreen-layout
Jul 18, 2026
Merged

feat: 端末縦幅いっぱいの全画面レイアウト(100dvh 相当)#3
takecchi merged 1 commit into
mainfrom
feat/fullscreen-layout

Conversation

@takecchi

Copy link
Copy Markdown
Owner

概要

TUI が端末の縦幅いっぱいに広がらない問題を解消しました。原因は Ink がコンテンツの高さぶんしか描画しないインラインレンダラであること(web の height: 100dvh に相当する概念がない)。root の <Box>useWindowSize() の rows を height 指定することで、Claude Code と同じ「起動すると画面全体が TUI になり、入力欄が最下部に固定される」構図にしています。

主な変更

全画面レイアウト(app.tsx

  • root Box に端末 rows を height 指定(リサイズ追従)+ overflow="hidden"(フレームが端末高さを超えると Ink が全画面クリアにフォールバックしてちらつくため、超過は必ずクリップ)
  • 極小端末(16行未満)はインライン描画にフォールバック: height 固定のままだと入力欄・フッタごとクリップされ操作不能になるため、従来どおりコンテンツ高さで描画し端末スクロールに任せる

一覧ビュー

  • セッション一覧を flexGrow + overflowY="hidden" にし、入力欄+フッタを画面最下部に固定

詳細ビュー

  • <Static> ログを廃止し末尾ビューポートへ(flexGrow + justifyContent="flex-end" + overflowY="hidden"。最新行が下端、溢れた古い行は上へクリップ)。<Static> はスクロールバック側に書き出す仕組みのため、全画面レイアウトでは画面外に消えて両立しない
  • ステータスヘッダ(タイトル/バッジ/ブランチ/進捗)を上部固定に移動、diff サマリ・操作エラーは下部ブロックへ

core

  • core/layout.ts 新設(純関数 + co-located spec): isFullscreenViewport(rows) / tailMessages(messages, rows)(描画ノード数の上限。slice(-0) が全件になる事故もガード)

ドキュメント

  • ARCHITECTURE / TECH_NOTES / .claude/rules/ink-components.md を新レイアウトに合わせて更新
  • TASKS.md の Backlog に積み残し(ログスクロール、入力欄の複数行化)を追記

テスト

  • npm test143 tests)/ npm run typecheck / npm run lint / npm run build すべてグリーン
  • 追加テスト(ink-testing-library は rows を注入できず実端末サイズに依存して非決定的になるため、Ink 本体の render + 寸法固定 fake stdout/stdin で検証):
    • フレームが端末高さちょうど(rows=20)で、フッタが最下段に来る
    • rows=8 ではインラインフォールバックし、フッタまで描画される
    • 詳細ビューでログ40行がフレーム内に収まり、最新行が見え古い行はクリップされる
    • core/layout.spec.ts: isFullscreenViewport / tailMessages のテーブルドリブンテスト
  • code-reviewer エージェントの指摘(極小端末でフッタが消える等)はフォールバックで解消済み
  • 擬似TTY(script + 30×120)でビルド済みバイナリの実描画を確認

手動確認 TODO

  • 実端末で npm run dev し、起動時に画面全体が TUI になることを確認
  • ウィンドウリサイズに追従することを確認
  • tmux の低いペイン(<16行)でインラインフォールバックすることを確認
  • 詳細ビューでログが流れても入力欄が最下部に固定されたままなことを確認

補足

  • 参考: Claude Code 本体は Ink をフォークして alt-screen + 独自 ScrollBox(仮想スクロール)で全画面化している。本 PR は素の Ink 7 の公式機能(useWindowSize + フルスクリーンフレーム描画)による最小構成
  • 積み残し(Backlog 追記済み): 詳細ログの遡りスクロール、入力欄の複数行化(Shift+Enter / 内部スクロール)、ダッシュボード化

🤖 Generated with Claude Code

Ink はコンテンツの高さぶんしか描画しないインラインレンダラのため、
root Box に useWindowSize() の rows を height 指定して全画面化する。

- 一覧: セッション一覧を flexGrow にし、入力欄+フッタを画面最下部に固定
- 詳細: <Static> を末尾ビューポート(flex-end + overflowY hidden)に置換、
  ステータスヘッダを上部固定へ移動
- 極小端末(16行未満)はインライン描画にフォールバック
  (height 固定のクリップで入力欄・フッタが消えるのを防ぐ)
- core/layout.ts: isFullscreenViewport / tailMessages を純関数として追加
- 寸法固定 fake stdout で全画面/フォールバック/末尾クリップを検証する
  統合テストを追加
- docs / .claude/rules を実装に合わせて更新
@takecchi
takecchi merged commit 35144f8 into main Jul 18, 2026
1 check passed
@takecchi
takecchi deleted the feat/fullscreen-layout branch July 18, 2026 16:28
takecchi added a commit that referenced this pull request Jul 18, 2026
## 概要

Phase 6(MVP後バックログ)のうち **4項目** を実装しました(`includePartialMessages`
によるストリーミング表示は今回対象外)。純粋ロジックは `core/`、副作用は `utils/`/合成ルートという既存の分離を踏襲しています。

着手項目はユーザーと相談のうえ決定(TASKS.md の「着手前にユーザーと相談」に従う)。

## 実装内容

### 1. 設定ファイル拡張 (#2)
- `CodivaConfig` に `model` / `effort` / `permissionMode` /
`maxBudgetUsd` / `notifications` を追加
- 検証は `toConfig()` に集約(不正値は静かに既定へフォールバック、TUI をクラッシュさせない)
- `SessionOptions` に束ね、`SessionManager → Session → SDK Options` へ注入
- `permissionMode` 未指定時は従来どおり `acceptEdits`

### 2. コスト表示 (#3)
- 純粋な `core/cost.ts`(`totalCostUsd()` / `formatUsd()`)を追加
- 一覧のバナーに合計コスト、詳細に各セッションのコスト行
- reducer は既に `result.total_cost_usd` を `state.totalCostUsd`
に保持していたため、導出のみ追加

### 3. デスクトップ通知 (#5)
- 発火判定は純粋な `core/notify.ts` の `notificationFor(prev, next, messages)` —
**状態遷移時のみ**発火(連続更新で鳴り続けない)
- 実 I/O は `utils/notify.ts`(darwin=`osascript` /
linux=`notify-send`、文字列は **argv 渡し**で注入防止、missing binary 等は握り潰す
best-effort)
- `SessionManager.onTransition` に配線、`config.notifications: false` で無効化

### 4. セッション復元 (#1)
- 永続スナップショットの型・変換・検証は純粋な `core/persistence.ts`(`toPersistedSession` /
`restoredSessionState` / `fromPersistedJson`)
- ファイル I/O は
`utils/state-store.ts`(`<repo>/.codiva/state.json`、破損時は空へフォールバック、起動時に存在しない
worktree を prune)
- `Session` は `resume`(SDK session id)/ `restored`(復元済み
state)を受け、**復元セッションは起動時にサブプロセスを立てず、最初の追加指示で遅延 resume**(起動時のサブプロセス乱立を回避)
- 終了時は `stop()`(quiet 停止)で実行中セッションも resumable のまま保存(`abort()` = failed 化
と区別)
- `SessionManager.restore()` / `persistableState()` を追加。id/slug
を予約して新規セッションとの衝突を回避

## テスト計画

- [x] `npm test` — **234件全緑**(旧131 → +103)
- [x] coverage — Statements 95.5% / Branches 85% / Functions 89.6% /
Lines 96.5%(core・utils とも 80% 閾値超)
- [x] `npm run typecheck` — 緑
- [x] `npm run lint` — 緑(既存の banner.tsx `paint` 未使用引数 warning のみ、本PR無関係)
- [x] `npm run build` — 緑
- 新規 spec: `config`(拡張) / `cost` / `notify`(core+utils) / `persistence`
/ `state-store`
- 統合テスト `tests/restore.test.tsx`: run → persist → 新 manager restore →
追加指示で `resume` が query options に載る、までを実 `Session` で通し検証
- [ ] **手動受け入れ(未実施)**: 実 Claude セッションでの resume 挙動・OS 通知の実表示は TTY +
認証が要るため未確認。仕組みは統合テストで検証済み

## ドキュメント

`TASKS.md`(チェック更新+実績メモ)、`ARCHITECTURE.md`(新モジュール・責務・「Phase 6
機能」節・決定表)、`TECH_NOTES.md`(Options の resume/model/effort/maxBudgetUsd
を実装済みに更新)を同期済み。

🤖 Generated with [Claude Code](https://claude.com/claude-code)
takecchi added a commit that referenced this pull request Jul 18, 2026
## 概要


全画面レイアウト(#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 しない — インライン描画は端末スクロールに依存するため。

## テスト計画

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