Skip to content

feat: ヘッダに使用中のモデル名を表示 - #12

Merged
takecchi merged 1 commit into
mainfrom
feat/header-model-display
Jul 19, 2026
Merged

feat: ヘッダに使用中のモデル名を表示#12
takecchi merged 1 commit into
mainfrom
feat/header-model-display

Conversation

@takecchi

Copy link
Copy Markdown
Owner

概要

起動バナー(ヘッダ)に、現在使用する設定になっているモデル名を表示するようにしました。表示位置はカレントディレクトリ行のすぐ上です。

 ▄▄ ▄▄▄▄▄▄▄ ▄▄
 █████████████   ✻ codiva   2 セッション
██▀██▀███▀██▀██  並列 Claude Code セッションを git worktree 上で実行
██ █ █ ▀ █ █ ██  モデル: claude-opus-4-8      ← 追加
██ █       █ ██  /Users/takecchi/repo
▀   ▀▀▀▀▀▀▀   ▀

変更点

  • i18n: banner グループに model(name)defaultModel を追加(ja / en 両方)。UI に文字列を直書きせずカタログ経由(i18n 規約準拠)。
  • 配線: config.modelindex.tsx → App → SessionList → Banner へ props で伝播(cwd と同じ経路)。
  • 表示: config.model 未設定時は CLI 既定を示すプレースホルダ(「CLI 既定」/「CLI default」)を表示。
  • テスト: src/ui/banner.spec.tsx を追加し、モデル名表示・未設定時フォールバック・cwd 行より上に描画されることを検証。

テストプラン

  • npm run typecheck パス
  • npm run lint(Biome)新規エラーなし
  • npm test 全 352 テストパス(新規 banner.spec 含む)
  • ja / en 両言語で描画確認(モデル設定あり/なし)
  • 実端末での起動時見た目の最終確認(任意)

起動バナーのカレントディレクトリ行の上に、設定されているモデル
(config.model)を表示する。未設定時は「CLI 既定」/「CLI default」を表示。

- i18n banner グループに model/defaultModel を ja/en 両方追加
- config.model を index.tsx → App → SessionList → Banner へ配線
- Banner のレンダリング・順序を banner.spec.tsx で検証
@takecchi
takecchi merged commit 03ec34b into main Jul 19, 2026
1 check passed
@takecchi
takecchi deleted the feat/header-model-display branch July 19, 2026 07:45
takecchi added a commit that referenced this pull request Jul 31, 2026
## 背景 / 症状

1. 一覧の PR ステータス(`#<n>` やチェック)が**出るときと出ないときがある**
2. (レビュー中のご指摘)**セッション数に比例してポーリングが重くなる**/一度取得した情報がキャッシュされない

## 原因(実測で確認)

`utils/pr.ts` の `lookupPr` が **`gh` のあらゆる失敗を「PR が無い」と同じ `undefined`
に丸めていた**ため、`PrCoordinator` が `session.setPr(undefined)` を呼び、**表示中の
`#<n>` を消していた**。20 秒ごとのポーリングなので「消えて、しばらくして復活する」挙動になる。

このリポジトリで再現を確認:

```console
$ gh pr view codiva/github --json number   # exit 1
GraphQL: API rate limit already exceeded for user ID …
$ gh api rate_limit --jq .resources.graphql
{"limit":5000,"remaining":0,"used":5004}
```

- `gh pr view --json mergeable` は REST ではなく **GraphQL API**(5000
ポイント/時、REST とは別枠)を消費する
- このクォータは**同一アカウントの全ツールで共有**で、**セッション内の Claude が実行する `gh`** も同じ枠を食う
- そこへ codiva が「**全セッション × 20 秒 × 2 回**」(PR 情報用と `statusCheckRollup`
用)を上乗せしていた。10 セッションで 3600 req/h = **自分で枯らして自分の表示を壊していた**

加えて「PR が無い」と「確認できていない」が画面上どちらも空セルで区別できず、PR
番号は永続化されていないため再起動直後は毎回ゼロから取得していた。

## 対応

### 1. 失敗と「PR が無い」を区別する(根本原因)

```ts
type PrLookupResult =
  | { kind: 'found'; pr: PrInfo }
  | { kind: 'absent' }                                  // gh が答えた: PR は無い
  | { kind: 'unavailable'; reason: PrUnavailableReason } // gh が答えられなかった
```

失敗理由を stderr の文言から分類(`cli` / `auth` / `rate_limit` / `network` /
`unknown`)。`unavailable` のときは**直前の値を保持**し、`prLookup: 'error'`
を立てるだけにする。パース失敗も `absent` ではなく `unavailable` に倒す。

### 2. 番号とステータスを分けて、番号は永続キャッシュする

```ts
pr?: PrRef;           // { number, url } — ブランチに対して不変 → state.json に永続
prStatus?: PrStatus;  // { mergeStatus, checks, isDraft } — 揺れる → transient
```

- **番号が分かった時点で番号を表示**: 永続するので**再起動直後からグリフ無しの `#42`** が出る(従来は毎回 `⋯` から)
- **ステータスが分かった時点で表示**: 「番号は既知・ステータス未取得」は `prPollIntervalMs` が **0**
を返して即取得 → グリフが後から付く。ステータスは永続しない(前回終了時の古いグリフを見せない)
- reducer は**半分ずつ**比較して参照を維持 → チェックの進行だけで `state.json` が再保存されない

### 3. 表示(読み込み中 / 確認不能 / チェック)

| 表示 | 意味 |
|---|---|
| `⋯` | 確認中(1 回目の問い合わせが未完了) |
| (空) | このブランチに PR は無い |
| `#12` | PR あり、**ステータス未取得**(再起動直後など) |
| `✓ #12` | マージ可能(チェックも通過) |
| `● #12` | **チェック実行中** |
| `✗ #12` | **チェック失敗** またはコンフリクト |
| `⑂ #12` | マージ済み |
| `?` | **確認できなかった**(レート制限 / オフライン / 未認証) |

draft は番号を淡色に(従来は区別なし)。`gh` 未導入のときは印を出さない(全行に警告を出しても直せない)。

### 4. API コストをセッション数から切り離す

- **陳腐化ベースのポーリング**(`core/pr-refresh.ts`)— 20 秒 tick
は**スケジューラに降格**し、実際に叩くのは期限切れのものだけ。チェック実行中 20 秒 / マージ可否計算中 60 秒 / 落ち着いた PR
**3 分** / **merged と archived は永久に問い合わせない**
- **3 件以上まとまったら `gh pr list` 1 回に畳む**(`lookupPrs`)— 各セッションの HEAD
ブランチ(ローカル `git rev-parse`、API 不要)で突き合わせ。**10 セッションでも API 1 回**。1〜2 件は
`pr view` の方が安いので閾値を置く(list は全件の rollup を運ぶ)
- チェック取得を `pr view` 1 回に統合(毎ポーリング 2 回 → 1 回)
- `rate_limit` / `auth` / `cli` を検知したら **5
分**停止(`PR_LOOKUP_BACKOFF_MS`)、サイクルの**多重実行を禁止**(`gh` が 20 秒より遅いと重なっていた)

10 セッションで概算 **3600 → 20〜60 req/h**。

## 不変条件

- 状態遷移は reducer 経由のみ(`pr_lookup` イベント追加。`pr` は「`gh` が答えた」ときだけ流すので
`prLookup` も必ずクリア)
- `prStatus` / `prLookup` は transient(`state.json` にも
`persistRelevantChanged` にも入らない)。永続するのは番号・URL だけで、untrusted JSON も検証する
- `gh` を知るのは `utils/pr.ts` だけ / 色・記号は `theme.ts` 経由 / UI 文字列の追加なし(記号のみ)

## テスト

`lint` / `typecheck` / `test` / `build` 通過(**1621 tests**、statements
95.6%)。

- `src/utils/pr.spec.ts` — 実際の `gh` 文言(レート制限・未認証・ENOENT・オフライン・no
PR)の分類テーブル、チェック集約、バッチ(1 回で解決 / 失敗時に全件 unavailable / open を closed より優先 /
ページ切り詰め時の確認)
- `src/core/pr-refresh.spec.ts`(新規)— 状態ごとの再取得間隔とキャッシュ判定のテーブル
- `src/core/pr-coordinator.spec.ts`(新規)— 失敗時に PR 保持 / バックオフ / 多重実行禁止 /
merged は打ち切り / 点滅しない / バッチ切替 など 29 件
- `src/core/status-reducer.spec.ts` — 2 分割の比較と参照維持
- `src/core/persistence.spec.ts` — 番号だけ永続し、ステータスは永続しない(壊れた JSON も除去)
- `tests/restore.test.tsx` / `tests/app.test.tsx` —
再起動で番号が残り、最初のポーリングでグリフが付くところまで通す

### 実 API での検証(`gh` の実データ)

`gh pr list --json` の形状・並び順を実測し `docs/TECH_NOTES.md` に記録。`lookupPrs` を実
API に通して確認:

| ケース | 結果 |
|---|---|
| HEAD にある PR | `found` #78 mergeable / passing |
| PR 無し | **`absent`**(`unavailable` ではない) |
| マージ済み | `found` #77 **merged**(以後ポーリングしない) |

### 手動確認のお願い

実セッション(TTY + Claude 認証)が必要な体感確認はお願いします。`gh auth logout` 状態やクォータ枯渇時に一覧へ
`?` が出て番号が消えないこと、再起動直後に `#<n>` が即出ることを見ていただければ確実です。

🤖 Generated with [Claude Code](https://claude.com/claude-code)
takecchi added a commit that referenced this pull request Aug 6, 2026
## 背景

セッションが自分で別ブランチを切って `gh pr create` すると、1 セッションに複数の PR が紐づきます。しかし一覧の PR
セルは**セッションブランチ(`codiva/<slug>`)の PR しか出せず**、2 本目以降はどこにも表示されませんでした。

## 変更内容

### 検知(`core/pr-detect.ts` 新規・純粋)

- `gh pr create` を実行した **tool_use の結果**に出る PR URL を拾う(`sdk-parse` が
tool_use id を控えて tool_result と突き合わせる)。
- ログ全体から URL を拾わないのは誤検出を避けるため — `gh pr list` の出力や、`gh pr view` /
WebFetch で覗いただけの他人の PR まで「このセッションの PR」になってしまう。
- MCP の `create_pull_request` 系ツールもツール名で拾う。
- 参照の追加・除去は**変化が無ければ同じ配列参照を返す**(`state.json` を無駄に書き直さない / 再描画を増やさない)。

### 状態・永続化

- `SessionState.extraPrs`(識別のみ・**永続**)と
`prCreateToolIds`(結果待ちの対応付け・transient)を追加。
- `state.json` に保存・復元(壊れた要素は 1 件ずつ落とす)。reducer はブランチの PR と重複したら畳む。

### 表示

| 画面 | 表記 |
|---|---|
| 一覧 | `✓ #12 +2`(代表 + 残り件数)。PR 列は複数 PR の行があるときだけ 10 → 14 桁に広げる |
| 詳細 | `PR 3 件: ✓ #12 · #13 · #14`(複数 PR のときだけ 1 行。1 本ならログの縦幅を 1 行も譲らない)
|

- **代表はセッションブランチの PR**。`prStatus`(グリフ)を持つ唯一の PR で、クリック / `p`
で開く先でもあるため、見えている番号と開く先とグリフの意味を一致させた。
- セッションが自分で作った PR は codiva が追跡・操作しない(別ブランチのものを勝手に ready
化・マージしない)ので**番号のみ・グリフなし**。状態を知らないのに緑や赤で嘘をつかない。
- **`gh` の追加呼び出しはゼロ**(API クォータを増やさない)。
- 描画とクリック当たり判定は必ず同じ列幅(`core/list-hit.ts` の `prCellWidth`)を使う。
- `PrCell` / `prStatusBadge` は `ui/pr-cell.tsx` に共通化(一覧と詳細で同じグリフ・同じ色)。

### ドキュメント

`README.md`(利用者向けの表記説明)/ `docs/ARCHITECTURE.md`(状態フィールド・設計判断)/
`docs/TASKS.md`(Phase 27)/ `CLAUDE.md`(コードの地図)。

## テスト

- `src/core/pr-detect.spec.ts`(新規・テーブルドリブン): コマンド判定 / URL 抽出(GHE
ホスト・サブパス・重複・issues リンク)/ 参照同一性 / 上限 / 表示ヘルパ。
- `src/core/sdk-parse.spec.ts`: 実フィクスチャと同じメッセージ形で tool_use → tool_result
の対応、`gh pr list` を数えないこと、ブランチ PR と判明したら畳むこと。
- `src/core/persistence.spec.ts`: 往復と壊れた JSON の扱い。
- `src/core/list-hit.spec.ts`: 列幅の切替。
- `tests/app.test.tsx`: 一覧に `✓ #42 +2` が出て `#43` は出ないこと、詳細に全件が並ぶこと、1
本のときは詳細に PR 行を出さないこと。

### 動作確認

- [x] `npm run lint` / `npm run typecheck` / `npm test`(2,232 件)/ `npm
run build`
- [ ] 実 TTY + Claude 認証での手動確認(非対話セッションでは実行できないため未実施)。確認観点: 複数 PR
を出したセッションの行が `#12 +2` になり、PR セルのクリックで代表の PR が開く / 詳細ビューに全件が並ぶ / 再起動しても
`+n` が残る。

🤖 Generated with [Claude Code](https://claude.com/claude-code)
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