# history — AppPromoVideo の作業ログ **この file は台帳ではなく履歴です。** 「今どうなっているか」は `CLAUDE.md`、決定と Phase は `specs/NN_*.md`、名詞は `data_contract.yaml`、罠は `failures.md` が正本。ここに書くのは **いつ何が起きて何が分かったか**だけで、ここを読まないと現状が分からない状態にはしない。 追記は新しいものを末尾へ。過去のエントリは**その時点の記録として書き換えない** — 後で覆った事実は 新しいエントリに「反証」として書き、古いエントリはそのまま残す。 --- ## 2026-09-07 - リサーチ (Kataribe grep / Memoria recall / 環境実測) → ユーザー決定 2 点 (Tauri+Vue / 走査は CLI 委任) → 台帳凍結 → spec 01 Phase 0 着地 → **ユーザー査読 11 点を rev2 に反映** (promo_core 20 + cli_runner 15 = 35 green・clippy clean。image_gen / pipeline / app は未作成)。 ## 2026-09-08 ### fixture 採取 (Phase A の前提) - 採取スクリプトがユーザー実行で 2 段落ち (PowerShell `2>` の二重オープン + 残留 claude.exe / `Start-Process` の引用符欠落) → `failures.md` #2 #3 に記録して修正。 - 2 回目も 401 × 10 回再試行 (2.5 分) で失敗 → 失敗ログを fixture 化し、`ApiRetry` 解析と `retry_notice` (再試行を進捗に出す) を追加。原因 = CLI の OAuth 期限切れ。 User スコープの `ANTHROPIC_API_KEY` を適用して成功 fixture を採取 (`claude_json_schema_ok.jsonl`) → `structured_output` 確定。 - **反証 1 件**: 成功 run にも 401 の再試行が 7 回混じる → 「認証再試行の初回で打ち切る」案は撤回 (failures #4)。 ### Phase A Done (Windows 実機) `cli_runner::runner::run` (spawn / stdin・一時ファイル / 行ストリーム / timeout / cancel / 終端 auth で打ち切り) + `tree_kill` (Job Object。孫が timeout 後に消えるのを PoC で固定) + `fake_cli` 7 モード。50 green。 Unix の pgid 経路は未コンパイル。 ### Phase B Done `crates/pipeline` (collect / TaskRunner trait / stages の再生成ループ / export / `promo` CLI) + promo_core の prompts・export。65 green。 **live 1 本** (Kataribe、sonnet): 0.99 USD / 4 分 22 秒、7 シーン、再生成 1 回発火。 ### Phase C Done `crates/image_gen` (provider.rs = Kataribe の写し・不触 / generator trait / refs / comfy_wait / palette) + promo_core style + pipeline reference + `promo images`。93 green (+ live 5 ignored)。 **live: Gemini 2/2 (17 s)**。live から visual_identity の英語固定と「UI が映るシーンを最低 1 つ」を追加。 ComfyUI / OpenAI の live は未実施。 ### Phase D 実装 `app/` (Tauri 2 + Vue 3)。16 command / event `promo-progress` / 設定 2 タブ / perProvider スロット / settings.json ミラー。trait に Send 境界を追加 (Tauri async command の要求)。 ### GUI フィードバック → 認証の切り分け スナップショットのドロップ / Ctrl+V / 横並び + 拡大 (SnapshotStrip / Lightbox)。 **GUI からの実行が 401** → 7 通りの切り分けで未再現 (failures #7)、有力仮説 = 起動元端末の `ANTHROPIC_API_KEY` が別物。処方 = `cli_runner::env_scrub` (ホスト結合変数を子に渡さない) + 設定に認証診断 + 「OAuth ログインを使う」チェック → **OAuth ON で通過** (端末の鍵が無効で確定)。 ### rev3 — モデルは舞台を描き、Rust が画面を貼る 「スクショに全く従わない」→ 製品カットは**モデルが背景、Rust が実スクショを合成** (`image_gen::compose`)。 カット = product | mood、`motion_prompt` (i2v 用)、コピー先は MiniMax 限定 (Veo / Sora は指示に従わない)。102 green。 **live 成功** (Fuseforks → MiniMax i2v、ユーザー「綺麗にできた」)。 ### 見出しの焼き込み (opt-in・既定 OFF) `image_gen::fonts` (システム + app_data/fonts) + `image_gen::caption` (ab_glyph)。 GUI 設定にフォント一覧・プレビュー。アプリで焼くか手で焼くかはユーザーのアンケート待ち。107 green。 ### 初回コミット Initial commit `a75c3bc` の上に 4 本 (台帳と足場 / crates / app / failures #8 の回収)。 罠 #8 が `app/failures.md` に書かれていて root に無かった — GUI 作業中に cwd が `app/` だったための分裂。 ### Phase E rev4 — tree の出所を git に `promo brief` を 7 リポジトリに当てたら RepoBrief の tree が生成物で埋まった (failures #9)。 **tree の出所を `git ls-files` に**、追跡された生成物は**機械生成名で 1 行に畳む**、 **鍵に見える名前は tree に載せない** (契約 `RepoBrief.tree_source`)。 件数による畳み込みは実装前に棄却 (Fuseforks `specs/` 52 と outcast `.sqlx/` 49 は 3 件差で分離不能)。 実測: mxf-tool 314→51 / CC-Sakura 400(切り捨て)→122 / outcast 280→214 / Verificator 114→68 / Kataribe 188→176 / Fuseforks 141→107 / KindleScan 25→17。112 green。 **live (Verificator、sonnet、snapshot 0 枚)**: analyze 0.2857 USD / 27.9 s + plan **attempts 1** 0.2710 USD / 81.8 s = 0.557 USD / 110 s、6 シーン。解析は Python コアの中身 (PyAV インメモリ / Fraction の 2 ポインタ結合 / 1 パーセンタイル検出) を正しく拾った。**別リポジトリで LLM 2 段は一発通過。** **反証**: 「Claude デスクトップの子セッション内では OAuth が継承されない」は**現行 CLI では成立しない** (`claude -p` が通る)。failures #4 / #7 の記述は claude 2.1.223 時点のもの。 ### rev5 — 面の傾き 合成 live で、背景がローアングルなのに貼った UI が正対のままだった (failures #10)。 `Scene.plate_tilt` (yaw/pitch ±35 度) を LLM が書き、`PlateMode` で貼り方を切り替える — **perspective** (既定、射影変換で面を倒す) / **frontal** (正対固定 + 背景のアングル語を検査で弾く)。 検査と本文がモードに依存するので `validate_scene_plan` / `scene_prompt` が `PlateMode` を取る。 傾き 0 は従来の overlay 経路のまま (画素等価を PoC で固定)。117 green。 **live 検算** (AppPromoVideo 自身 + 実 GUI スクショ): LLM の傾き指定は意味と一致 — `Top-down view` → pitch -20、`three-quarter angle` → yaw +18、正対の背景は null。 **やらかし**: `cargo fmt --all` を打ったところ `rustfmt.toml` が無いため既定の 100 桁で全ファイルが 整形され直し、`image_gen/src/provider.rs` (「Kataribe の写し・不触」と明記したファイル) まで書き換わった。 21 ファイルを HEAD に戻して回収。**このリポジトリで `cargo fmt --all` を打たない** (130 桁で書かれている)。 ### rev6 — 出力解像度 MiniMax 実測で「判別しにくい文字は作り変えられる」(ユーザー) → canvas 1344×768 が窓 1282×842 より低く、 **構造的に必ず 0.711 倍に縮んでいた** (failures #11)。`canvas_for_snapshot` で比率を保ったまま等倍に 収まる大きさへ拡げる (実測 1890×1080、縮小率 1.000、長辺上限 3840px)。上限で縮小が残る時は進捗に警告。 `fit_to_canvas` で mood カットの JPEG 1376×768 も同寸 PNG に揃える。120 green。 ### Remotion は採用しない (ユーザー判断) 北極星「動画そのものは作らない」を守る。検討の記録は `specs/01` の「検討した代案」に。 公式 MCP (`@remotion/mcp`) は非推奨で Agent Skills 推奨、という事実もそこに含む。 ### CLAUDE.md をルーター化 (ユーザー指示) Remotion の Agent Skills の構造 (2.5 KB のルーター → 11 KB の REFERENCE → 2〜5 KB の葉) を見て、 毎ターン読まれる `CLAUDE.md` (13 KB、うち大半が履歴) を分離。履歴はこの file へ。 ### rev7 — run の履歴 (ユーザー指摘「再起動すると出力が揮発」) 調べたら揮発ではなく**破壊**だった: `_Promo_Package` に run の識別子が無く、2 回目が 1 回目を 上書きしていた (failures #12)。`/runs//` に隔離し、`app_data/runs.json` に索引を持たせ、 GUI に一覧・復元・2 run の左右比較・削除を足した。crates 122 / backend 7 / vitest 10 green。 気づかなかった理由: 開発中は毎回別のリポジトリで試しており、同じアプリに 2 回続けて回したのは perspective / frontal の対を作った時が初めて。その時は手で別フォルダにコピーしてから回していたので、 **自分で回避策を打っていたことに気づいていなかった**。 ### public 化に備えて fixture を伏せた `crates/cli_runner/fixtures/*.jsonl` に home path (`C:/Users/...`) とセッション UUID が残っていたので `scripts/redact_stream.py` を拡張して潰した (`memory_paths` / `terminal_slash_commands` を落とし、 入れ子のどこに現れるか分からない home path と UUID は行を JSON に直した後で正規表現で置換)。 UUID は空にせず `` に置き換える — `session_id` が空でないことを前提にしたテストがあるため。 潰した後も workspace 122 green。API キー本体は元から入っていない (`apiKeySource` の名前だけ)。 ### rev7 の live 確認 (ユーザー実機) 同じアプリを 2 回実行して履歴に 2 行 (19:36 / 19:40、4 と 5 シーン、どちらも gemini、0.944 / 0.919 USD)。 **上書きは起きず、左右比較も描画された。** スクリーンショットから回帰を 1 件回収 — 「面」列が `Perspective` (Rust の識別子) で出ていた。`RunRecord` の aspect / language / plate_mode は `format!("{:?}")` ではなく **契約の表記** (`16:9` / `ja` / `perspective`) を入れる。 既存の 2 行は表示専用なのでそのまま残す。比較画像に `max-height: 46vh` を入れてダイアログ内に収めた。 ### bundle identifier を jp.outcasts.apppromovideo へ (public 化の直前、ユーザー判断) `jp.outcasts.apppromo` だった。兄弟アプリ (`jp.outcasts.concordia` / `.fuseforks`) は末尾が製品名なので、 ここだけ略称になっていた。identifier は **app_data (`%APPDATA%//`: .env / settings.json / runs.json / fonts/ / snapshots/) と WebView プロファイル (`%LOCALAPPDATA%//EBWebView/`) の場所を決める**ため、 配布後に変えると使う人の設定・キー・履歴が黙って消える。**公開前にしか変えられない**ので今やった。 移行は Roaming のフォルダ名変更で済ませ、WebView プロファイルは作り直させた (設定は settings.json ミラーから復元される — ミラーはこの事故のために作った機構、Kataribe 2026-08-28 実機)。 localStorage の接頭辞 `apppromo.` は identifier とは別物なので揃えていない (揃えると既存キーの移行が要る)。 ### identifier 移行の live 確認 (ユーザー実機) 新 identifier で起動し、**履歴 3 行が読め、設定も復元された**。WebView プロファイルを移していないので localStorage は空から始まったが、リポジトリパス・スナップショット・世界観テキストが戻っている = `settings.json` ミラー (Kataribe 2026-08-28 の実機事故を受けて作った機構) が**初めて本番で機能した**。 同じ画面で rev7 の左右比較 (perspective 19:40 / frontal 21:52、scene 4) も動作。 **観察 (仮説つき)**: frontal の run だけ 1.422 USD で、perspective の 0.919 / 0.944 の約 1.5 倍。 frontal は `ProductBackdropAngled` の検査が増えるので再生成が発火した可能性が高いが、 **attempts も違反種別も永続化していないので確認できない**。測るには `RunRecord` に attempts と 違反種別を持たせる必要がある (未実施)。 ### rev8 — 見出しの焼き込みを既定 ON へ (開いている判断 2 の決着) ユーザーから「テロップとコピー字幕を追加できますか」→ 調べたら**焼き込みは rev3 期から入っていて、 既定が OFF なだけ**だった。字幕ファイル (SRT / WebVTT) を提案したが「そんな本格的なものはいらない、 画像に文字を入れるくらい」で不採用。既定 ON + フォントの自動選択のみ実施。 `pickCaptionFont` (frontend の純関数、Red 5 本 → 実装): ①日本語グリフ必須 ②app_data/fonts のものを 最優先 ③**太めのゴシック優先** (画像に焼くので細い明朝・教科書体は写真の上で読めない) ④同点は family 名。 `store.ensureCaptionFont()` を `makeImages()` の先頭に置いたので、設定画面を開かなくても効く。 選べなければ焼かずに進み理由を出す = 「ON なのに何も起きない」を無言にしない。vitest 10→15 green。 ### rev8 の穴: 既定 ON は保存済み設定に負ける GUI 確認の直前に気づいた。`migrateImageGenSettings` が `enabled: c.enabled === true` だったので、 ①保存済みの `false` (旧既定が書かれたもの) が勝つ ②`enabled` キーが**無い**保存も `false` になる (`undefined === true`)。②は既定へ落とすべきなのでバグ。`typeof c.enabled === "boolean" ? c.enabled : 既定` に直した (Red 1 本)。①は仕様どおり — 明示された false はユーザーの意思かもしれないので上書きしない。 既存ユーザー (= マスター) は GUI で 1 度チェックを入れる必要がある。vitest 15→17 green。 ### 見出しの豆腐を切り分け → 実バグは優先語が ASCII だけだったこと (failures #13) ユーザー実機で豆腐 (□)。仮説を 4 つ潰した (フォントにグリフが無い / burn_caption が壊れている / 出力が壊れている / 自動選択が Hack を選んだ) — **全部外れ**。豆腐の画像は実行中の Lightbox が 前の run の画像を映していたもので、出力ファイル自体は正しく焼けていた。 ただし切り分けの過程で実バグが出た: `pickCaptionFont` の優先語が ASCII だけで、実機の family 名は `游ゴシック` / `メイリオ` / `HGPゴシックE` と日本語表記。当たらないと同点になり localeCompare で ASCII 名が勝つため、`Aharoni Bold` が `游ゴシック` に勝っていた。手元で `Noto Sans JP` が 選ばれていたのは Noto が ASCII 名だった偶然。優先語に日本語表記と半角カナを追加 (vitest 17→18)。 診断用の probe は撤去。 ### 見出しの live 確認 → スコア式の符号反転を発見 (failures #14) ユーザー実機で見出しが正しく焼かれた (`シーン構成もプロンプトも、まとめて出力。`)。**開いている判断 2 は決着。** ただし自動選択の経路は通っていない — ユーザーが `NotoSansCJKjp-Black.otf` を手で選んだため。 そのフォント名を見て気づいた: `pickCaptionFont` のスコアが `100 - index*10` で、優先リストを 7→12 語に伸ばしたときに末尾 (`"sans"`) が **-10** になり、何にも当たらない 0 に負けるようになっていた。 `"Noto Sans CJK JP"` は `"noto sans jp"` に当たらず (`cjk` が挟まる) `"sans"` にだけ当たるので該当。 `(PREFER.length - hit) * 10` に変更 (vitest 18→19)。**同じセッション内で自分がリストを伸ばして壊していた。** ### rev9 — 見出しを 1 枚ごとに変えられるようにした ユーザー要望「1 枚ごとに位置を決めたい。今あるところは既定として、結果ペインで位置・フォント・色・ 大きさを変えられたら」。壁は**焼いた後の 1 枚しか残っていなかった**こと — 変えるには背景から作り直すしかなく、 位置を試すたびに別の絵になる。ユーザー判断でディスク約 2 倍を受け入れ、焼く前の合成を `/base/` に残した。 `reburn_caption` は base から焼いて直下の完成品を置き換える。**生成 API を呼ばないので無料**、毎回 base から 焼くので**何度やっても劣化しない**。上書きは `PromoJson.caption_overrides` に入るので run を開き直しても効く。 LLM のスキーマ (`Scene`) には足していない — 埋めるのは人。全フィールド `Option` でフィールド単位に既定へ落ちる。 crates 122→126 / backend 8 / vitest 19 green。**live 未実施、rev9 より前の run は base/ が無いので焼き直せない。** ### rev10 — base/ に置くのを合成画像から背景へ (failures #15) ユーザーの問い「背景だけ生成させて、文字はアプリで焼き込んでいるのではないのですか?」。 **その認識が正しく、私の説明が誤っていた** — 焼く行為は無料で、コストは焼く対象を作り直すことにあった (背景はローカル変数で一度も書き出していなかった)。答えるために合成を読み直したら、rev9 の欠陥に気づいた: `base/` の合成画像には `with_caption_band` が帯の位置を焼き込んでおり、位置を変えると重なる。 `base/` を背景に変え、焼き直しを**合成からやり直す**形に。`layout_for` を帯を決める唯一の場所にして 生成と共有 (生成側もジョブ既定でなくカットごとの位置で合成するようになった)。 `PromoJson.plate_mode` を追加し、焼き直しの傾き適用を promo.json 自身が決めるようにした。 crates 126→127 / backend 8 / vitest 19 green。**live 未実施、rev10 より前の run は焼き直せない。** ## 2026-09-09 ### rev11 — はめ込みも 1 枚ごとに変えられるように rev10 で合成からやり直せるようになった副産物として、傾き・大きさ・位置も無料で変えられる。 ユーザー要望を受けて UI まで通した。`Layout` に横方向のずらしが無く中央固定だったので追加 (正対・傾きの両経路)。`PlateOverride` は全フィールド `Option` で、傾きの上書きは LLM の `scene.plate_tilt` に勝つが触っていない軸は残る。縦位置を指定すると見出しの帯のずらしを置き換える。 範囲は Rust 側で丸める (UI の入力を信用しない)。`copy_text` が無い product カットでも はめ込みだけ触れるようにした。crates 127→129 / backend 8 / vitest 19 green。**live 未実施。** ### rev12 — スライダー / ドラッグ / コピー文の書き換え GUI 実測を受けて。数値入力では当たりが分からないという指摘への処方 (スライダー + プレビューの ドラッグ、**離した時だけ**焼き直す) と、コピー文そのものを直す機能。 コピー文は `scene.copy_text` を**その場で**書き換える — `scenes_markdown` もクリップボードも plan を 読むので、それだけで全部揃う。書き換えたら scenes.md も書き直す。LLM が最初に書いた文は `PromoJson.original_copy` に生成時 1 度だけ控えて、戻せるようにした。 crates 129→130 / backend 8 / vitest 19 green。**live 未実施、焼き直しの所要時間は未計測。** ### rev13 — 重さの正体は debug ビルドだった (failures #16) ユーザー「スライダーいじったときに重すぎる」。提案は「確定してから焼くべきかも」。測ったら 1886² の再合成が release 252 ms / **debug 17.9 s** で 71 倍差。GUI は debug の exe を使うのでこれが主因。 `[profile.dev.package.] opt-level = 3` を両 workspace に置いて **17.9 s → 0.64 s**。 提案どおりスライダーとドラッグは値を変えるだけにし、焼き直しは「適用」に集約した (0.64 s でも触るたびに走らせれば操作にならない)。 **最初の計測は嘘だった** — 単色画像で測って PNG 81 KB、実物 (写真) の 3〜14 MB と桁が違った。 ノイズ画像で測り直した。合成データで性能を測るときは圧縮の効きに注意する。 あわせてスナップショットの選び直し (`PlateOverride.snapshot_index`) を追加。LLM が選んだ画面が コピー文に合わないことがあるため。人の指定が LLM に勝つ。 ### rev14 — 見出しの縦位置と、予定位置の枠 GUI 実測を受けた要望 2 件。①見出しの縦位置を数値で ②スライダーを動かしている間、予定位置を枠で重ねる。 枠の座標は **backend が合成と同じ `layout_for` + `plate_quad` から出す**。TS 側に射影の数式を写すと 必ず食い違うので、そこは避けた。画像を作らずヘッダから寸法を読むだけなので、動かしている間ずっと呼べる。 `plate_quad` は合成の実測位置と ±2px で一致することを PoC で固定した (実際に合成して画素を測って突合)。 枠を画像に重ねるため、プレビューは `object-fit: contain` をやめて画像そのものの箱にした (contain だとレターボックス分ズレる)。crates 131→132 / backend 8 / vitest 19 green。**live 未実施。** ### 枠のズレは描画側だった (failures #17) ユーザー「位置がズレているかな」。実 run で `plate_quad` の座標と画像上の実際の位置を突き合わせたら **数ピクセル以内で一致** (差は落ち影)。最初の測定は差分に焼いた文字が混ざって嘘をついていた。 真因は CSS — `position: absolute; inset: 0` だけでは SVG は伸びず、置換要素なので固有サイズ (既定 300×150) で描かれる。`width/height: 100%` を明示して解決。 あわせて枠は**いじっている間だけ**出すようにした (適用すると消える)。診断用の probe は撤去。 ### 2026-09-09 のまとめ (セッション区切り) この日は rev11〜14 + 修正 2 件。**焼き直し = 合成のやり直し**という土台 (rev10) の上に、 はめ込み (rev11) → スライダーとコピー文 (rev12) → 重さの実測と選び直し (rev13) → 縦位置と枠 (rev14) と積んだ。罠は #15〜#17。 **この日に自分の測定が 3 回嘘をついた**、というのが一番の収穫: 1. 性能を単色画像で測って PNG 81 KB (実物は写真で 3〜14 MB)。圧縮の効きが実態とずれた 2. 枠の検算で、差分に焼いた文字が混ざって「上端 404 が 114 に見えた」 3. pickCaptionFont の最初のテストが、AVOID とタイブレークで偶然通った (実装を検証していなかった) いずれも「測った」「通った」で止めていたら誤った結論に着地していた。 **測定そのものが何を含んでいるかを数える**のが、測る前と同じくらい要る。 ## 2026-09-09 (続き) — rev15: 再生成の回数と種別を残す CLAUDE.md「次の候補」の 1 件 (`RunRecord` に attempts と違反種別) に着手。ユーザー指定。 **データは最初からあった。** `StageReport` (crates/pipeline/src/stages.rs) が `attempts` と `violations_per_attempt` を持ち、CLI は stderr に、GUI は進捗ログに出していた。捨てていたのは 永続化の一箇所だけ。 **`RunRecord` だけでは足りないと判断した (指摘してから実装)**。契約上この索引はキャッシュで正本は `run_dir/promo.json` (決定 28)。索引だけに持たせると ①`forget_run` や索引破損で測定値が消える ②`promo` CLI には索引が無いので、同一リポジトリで両モードを回す一番安い対照実験が数えられない。 ②が効いたので、`plate_mode` が rev10 でした判断 (「索引に頼らず promo.json 自身が持つ」) に倣い、 **promo.json を正本、`RunRecord` をその写し**にした。 **持つのは種別であって文ではない。** `describe_violation` は scene_id や語が埋まる人・LLM 向けの文なので、 同じ種類の違反でも文字列が変わり数えられない。`promo_core::violation_kind` を新設 (variant と 1:1、 snake_case、promo.json に残るので契約)。全 variant を並べる網羅テストを置いた。 **記録が無いことを「1 回」に落とさない。** `PromoJson.run_stats` は `Option`、`RunRecord.plan_attempts` の 0 は「記録なし」。rev14 以前の 3 行を 1 と描くと集計に偽の分母が混ざる。Rust 2 本 + vitest 1 本で固定し、 GUI は「—」と出す。2 回以上は色を変える。`analyze` は構造検査が無く常に 1 なので列に持たせていない。 Red は**API 不在によるコンパイル失敗**で、振る舞いの Red ではない (新設 API なので構造的にそうなる)。 crates 132→138 / vitest 19→22 / backend 8→10、両ワークスペース clippy clean。 **既にある反証を spec に書いた**: rev5 の live は **perspective** なのに `ProductBackdropDrawsScreen` で attempts 2 になっており (0.714 USD)、**perspective でも再生成は起きるし、起きても 1.5 倍にはならなかった**。 frontal の 1.5 倍を attempts だけで説明できる保証は無い。rev15 が用意したのは説明ではなく数える手段。 **接地の限界**: **live で 1 件も記録していない。** GUI は再ビルドしていないので画面も見ていない (私は headless)。仮説の検証には perspective / frontal を各数本走らせる必要があり、それは LLM 費用のかかる作業。 過去の 3 行は遡って埋められない。 ## 2026-09-09 (続き) — rev16: 編集はダイアログで ユーザー判断「トグルのアコーディオン表示ではなく、ダイアログページにしよう。大きい画面で確認できるようにしたい」。 `CaptionEditor` は結果ペインの列の中に畳まれていて、絵が `max-width: 420px` に縛られていた。 焼き上がりを確かめる用途にはこれが足りない。左が絵・右がつまみの 2 段組ダイアログにして、 絵は `92vh` から他の要素を引いた残りまで使う。幅 1000px 未満は 1 列に落とす。 **ついでに塞いだ穴が 1 つ**: 従来プレビューは `isProduct` の中にしか無く、**mood カットの見出しを 変えても焼き上がりを見る場所が無かった**。ダイアログでは mood にも出す (ドラッグははめ込みの 操作なので product のみのまま)。 **閉じる作法**: `dirty` なら確認を挟む (閉じる / 背景クリック / Esc)。折り畳みだった頃は畳んでも 値がその場に残っていたが、ダイアログは閉じると視界から消えるため、同じ扱いにはできない。 **枠の重ね方は rev14 のまま**触っていない — `.stage` を画像に縮め (`display: inline-block`)、 SVG に `width/height: 100%` を明示する (failures #17)。ポリゴンは canvas 比の 0..100 座標なので 表示サイズが変わっても追従するはず。 `Teleport` で body へ出した。`.center` は `overflow: auto` なので `position: fixed` はそのままでも 効くが、祖先に `transform` が入ると包含ブロックが移って壊れる。履歴・設定と同じ形に揃えた。 **接地の限界**: **私は画面を見ていない。** vue-tsc + vite build + vitest 22 green は通ったが、 これらはレイアウトが意図どおりに見えるかを何も検証しない。`max-height: calc(92vh - 190px)` の 190px は他の要素の高さの**見積もりで実測していない**。ダイアログでの枠のズレも未確認 (rev14 の ±2px 実測は 420px 幅のパネルでのもの)。純関数が増えていないので PoC も無い。 ## 2026-09-09 (続き) — rev17 / rev18: 縦位置が届いていなかった / 枠に見出しを含める **rev17 は不具合の修正。** ユーザー報告「スライダーを動かしてもプレビューの文字が変化しない」。 `y_ratio` を全ファイル grep したところ、**焼き直し (`reburn_caption`) だけが `Caption` を手で 組み直していて `y_ratio` を写していなかった**。rev14 で足したフィールドが片方の経路にしか行っていない。 保存経路は別なので値は promo.json に残り、「設定は効いているのに絵が動かない」に化けていた。 組み立てを `CaptionSpec::to_caption` 1 箇所に寄せ、`burn_caption` の縦位置の式も `caption_top` に統合。 **Red は実測した。** `cap.y_ratio = self.y_ratio;` の 1 行を消すと PoC 2 本が落ちる (片方は「縦位置を変えても画像がバイト等価」= マスターが見た症状そのもの)。戻して green。 これは今日 3 度目の「通ったテストが実装を検証していない」を避ける検算で、今回は先に回した。 **rev18 はユーザー判断 2 件。** ①「位置」の選択は縦位置スライダーが上位互換なので撤去 ②文字の位置と大きさも枠で見せる。 ②のために**版組みを焼き込みから取り出した** (`caption_layout`)。自動縮小の後の `px`、行送り、 行ごとの矩形を返し、`burn_caption` はその結果をそのまま描く。`plate_quad` と同じ作法で、 枠と焼き上がりが構造的に一致する。PoC で「焼いた墨が枠の中に収まる」ところまで見た。 ①には落とし穴があった。`layout_for` は**帯をどちら側に空けるか**に `position` を使っている。 選択を外すだけだと、見出しを上へ動かしても面が下の帯を避け続ける。`effective_position()` で `y_ratio` から導くようにした (上半分なら Top)。**UI の撤去がレイアウトの入力を切っていた**ので、 撤去したら grep の型どおり `position` を全部たどって拾った。 `plate_preview` は mood でも動くようになり (`quad` が Option)、編集中のコピー文を引数で受ける。 フォントは数 MB〜数十 MB あるので直前の 1 つだけ持ち回す。 crates 141→148 / backend 10 / vitest 22、両ワークスペース clippy clean。 **接地の限界**: **画面は見ていない。** 枠が文字と重なって見えるかは未確認で、PoC が保証するのは 焼き上がりの墨が枠の中にあることだけ。行送り (`px * 1.3`) は字面より高いので**枠は文字よりやや 大きく見える**はず。フォント切り替えを繰り返す操作では毎回読み直しになる (未実測)。 ## 2026-09-09 (続き) — rev19: run にスナップショットを足せるようにする ユーザー判断「コピーにあった画像が無いときにスクショを取り直してそれを貼るということもあり得るので、 リスト以外にも新たに足せるようにしたい」。用途が「撮り直して貼る」なので、**主経路は Ctrl+V** にした (Win+Shift+S はクリップボードに入る)。ファイル選択も残してある。 **先に命名を 1 箇所へ寄せた。** `snapshot_paths[i]` ↔ `snapshots/snapshot_{i+1:02}.*` という契約の式が 書き出し (write_package) / 読み出し (read_run_snapshot) / 追加 の 3 箇所に散る形になるところだった。 今日 failures #18 に書いた処方の一般形をそのまま当てて `promo_core::snapshot_prefix` / `snapshot_file_name` にし、PoC を置いた。**この式がずれると `snapshot_index` が別の画像を指す**。 **設計で判断した点 3 つ。** ①**番号は一覧の長さで決める。ファイルを数えない** — 欠番や孤児のあるフォルダでファイルを数えると 番号がずれる。②**同じ番号の孤児は拡張子を問わず先に消す** — `snapshot_03.png` と `.jpg` が並ぶと 前方一致がどちらを拾うか決まらない。③**重複排除はしない** — 撮り直しは同じパスのまま中身が変わるので、 パスで弾くと新しい画像を拒むことになる (入力ペインの `mergePaths` とはここが逆)。 **貼り付けの所有権**。`paste` は window にしか来ず、入力ペインの `SnapshotStrip` が常時待ち受けている。 そのままだと貼った画像が run にも次回の入力にも入る。`pasteFocus.pasteOwner` で所有者を 1 つに決め、 ダイアログが開いている間は入力ペイン側が拾わないようにした (vitest 3 本)。 ドラッグ&ドロップは足していない — `onDragDropEvent` は webview に 1 つの待ち受けで、入力ペインが持っている。 crates 148→150 / vitest 22→25 / backend 10、両ワークスペース clippy clean。 **接地の限界**: **画面は見ていない。** 貼り付けはクリップボード → FileReader → base64 → backend と 実機でしか通らない経路。孤児の掃除と番号決めは PoC が無い (ファイルシステム側)。 何十枚も足したときの一覧の見え方も未確認。 ## 2026-09-09 (続き) — rev20: rev19 の設計を正す ユーザー判断「というか入力のスナップショットに足したら、それをリストで開けるようにすればいいんじゃない? 今は最初に解析を押したときのスナップショットしか足せなくなっている」。 **rev19 は問題の読み違いだった。** 「足せない」という報告に対して**足す口を新設した**が、入力ペインには 既にドロップ / 貼り付け / ファイル選択が揃っている。同じ機能を 2 箇所に置いたせいで貼り付けの 所有権という調停が必要になり、そのための機構 (`pasteFocus`) まで生えた。**1 日と保たずに消えた。** 本当に足りなかったのは取り込み口ではなく、**run を作った後に入力ペインへ足した画像が ダイアログの一覧に出ないこと**。一覧は run 作成時 (最初に「解析」を押した時) の `snapshot_paths` で固定されていた。 rev20: ダイアログから取り込み口を撤去し、一覧を `snapshotChoices(runPaths, inputPaths)` (純関数、vitest 4 本) で組む。run の番号順が先、 入力ペインで後から足したぶんが下に「入力に追加」として付く。選ばれた時に `add_run_snapshot` で run へ写す (rev19 の機構はここで使う — `read_run_snapshot` は run の写しを読むので、 入力ペインのパスを直に指すことはできない)。 **入力ペインから外されても run のぶんは一覧に残す。** run は自己完結していて (rev10)、 入力の一覧はこの run の履歴ではない。消すと過去の run の再現ができなくなる。 crates 150 / backend 10 / vitest 26 (pasteFocus 3 本を落とし snapshotChoices 4 本を足した)、 両ワークスペース clippy clean。 **一般化 (自分向け)**: 「足せない」に対して入口を作るのが早すぎた。**足りないのは入口ではなく 見えている範囲**、という形は他にもありそう。機能を足す前に「既にある入口から入ったものが、 なぜここに出てこないのか」を先に問う。 **未解決**: `snapshotChoices` はパスで突き合わせるので、**同じパスのまま中身を撮り直した場合**は run の古い写しが選ばれ続ける (run が自己完結する以上そうなるが、撮り直しの用途とは噛み合わない)。 今は入力ペインで別名の画像として足す必要がある。 ## 2026-09-09 (続き) — rev21: つまみは実効値を指す ユーザー指摘「0° は正面で、傾き画像にしたときは傾き数値の° にしたほうがよい」。 つまみの位置を `yaw ?? 0` で決めていたが、上書きが無いときに実際に効くのは **LLM が書いた `scene.plate_tilt`**。**絵が 18° 傾いていてもスライダーは 0° を指していた** (failures #19)。 処方は今日 3 回目の同じ形 — **実効値は合成が使う関数に出させる**。`plate_preview` が `tilt_of` の値を そのまま返し、つまみの基準にする。表示は `tiltValue` / `tiltLabel` (純関数、vitest 4 本) で `0° (正面)` / `18° (LLM)` / `18°` の 3 通り。**出どころまで出す**のは、数字だけだと 「自分で 18° にした」と「LLM が 18° と書いた」が区別できず、「既定に戻す」を押すべきか判断できないため。 **指摘の外で同じ型を 1 件見つけたので直した。** 適用して開き直すと全つまみが既定に戻る。 `promo.json` には `plate_overrides` / `caption_overrides` が保存されているのに、ダイアログは 毎回 null から始めていた。さらに `reburn_caption` は data URL しか返さないので、frontend の `promo` は 適用後も古いままだった。→ 開いたときに保存済みの上書きを読み戻し、`reburn_caption` は **書き換えた後の `promo`** も返すようにした (`generate_images` と同じ形)。**これは私の判断で入れたもの。** crates 150 / backend 10 / vitest 26→30、両ワークスペース clippy clean。 **今日 3 つ並んだので failures #19 に一般化を書いた。** #17 は「計算は正しいが描画がズレる」、#18 は「値は保存されるが効かない」、#19 は 「値は効いているが表示されない」。**3 つとも片側だけを見て検証を終えている。** 処方も同型で、実効値を出すのは合成・焼き込みが実際に使う関数 (`tilt_of` / `caption_layout` / `plate_quad`) にする。UI 側で「たぶんこれが既定」を組み立てた時点で嘘が入る。 **接地の限界**: **画面は見ていない。** つまみが実際に動いて見えるかは未確認。 保存済みの上書きの読み戻しには PoC が無い (store と props に依存する)。 ## 2026-09-09 (続き) — rev22: 既定は正面固定へ (開いている判断 1 の決着) ユーザーが X に投稿した 15 秒の実物つきで「Minimax はうまくできています。ただ斜めにすると 動画が動かしすぎるので、正面がデフォルトのほうがいいかもね」。**rev5 から開いていた唯一の判断が閉じた。** `PlateMode` の既定を `Frontal` に。静止画としての整合 (背景のパースと面のパースが合う) と、 そこから起こした**動画の落ち着き**は別の話で、製品の目的は後者だった。rev5 で「どちらが動画として 良いかは未決だから両方残す」と書いたのは正しく、その未決がここで埋まった形。傾ける経路は残す。 **効く範囲を正確に**: 既存の run は影響なし (rev10 で promo.json 自身が plate_mode を持つ)。 保存済みの設定も上書きしない (rev8 の precedent — 明示された値はユーザーの意思かもしれない)。 つまり**この判断を出したユーザー自身の GUI には効かない** — localStorage に perspective が入っている。 設定で 1 度切り替える必要がある。rev8 と同じ形なので、同じ落とし穴を踏まないよう先に申告した。 **副作用が 1 つある。** frontal では `ProductBackdropAngled` の検査が効くので、 2026-09-08 に「frontal の run だけ費用 1.5 倍」と観測した経路が**既定になる**。 rev15 で attempts と違反種別を残すようにしたので、**次の live からは推測ではなく数えられる。** 順番が逆だったら、既定を変えた後で「なぜ高いのか分からない」に戻っていた。 crates 150→152 / vitest 30 / backend 10。 **接地の限界**: 根拠は**ユーザーの実機観測 1 件**で対照実験ではない (同じ素材で両モードを通して 比べたわけではない)。「動かしすぎ」の定量化もしていない。私は動画を見ていない。 それでも既定を変えたのは、これが製品の使い手の判断であり、両方の経路が残っているため。 ## 2026-09-09 セッション締め **出口まで届いた日。** MiniMax i2v で作った 15 秒が X に投稿され、その実機観測から rev5 以来の唯一の開いていた判断 (`PlateMode` の既定) が閉じた。 **この日の着地**: rev15 (再生成の記録) / rev16 (編集をダイアログへ) / rev17 (縦位置が焼き直しに 届いていなかった、failures #18) / rev18 (位置の選択を撤去・見出しの枠) / rev19→20 (スナップショットの 取り込み口、作って撤去) / rev21 (つまみは実効値、failures #19) / rev22 (既定 frontal)。 コミット 2 本 (`719de1f` / `df934eb`)、`origin/main` に push 済み。28 コミット。 crates 132→152 / vitest 19→30 / backend 8→10。 **この日の型**: 表示・保存・実効値のうち**どれか 1 つだけを見て検証を終えていた**バグが 3 つ出た (#17 描画 / #18 保存されるが効かない / #19 効いているが表示されない)。処方はどれも同じで、 **実効値は合成・焼き込みが実際に使う関数から取る** (`tilt_of` / `caption_layout` / `plate_quad`)。 UI 側で「たぶんこれが既定」を組み立てた時点で嘘が入る。 **この日の私の誤り**: rev19 で「足せない」に対して足す口を新設したが、足りないのは入口ではなく **見えている範囲**だった (rev20 で撤去)。機能を足す前に「既にある入口から入ったものが、 なぜここに出てこないのか」を先に問う。 **次の主題**: **UI を簡単にする** (ユーザー)。X で今どきの UI デザインを探して持ち込む予定なので、 **参照が来てから着手する**。 **持ち越し**: ①frontal の費用 1.5 倍の理由 — `RunStats` は入れたが live 記録はまだ 0 件 ②`snapshotChoices` がパス一致なので、同名で撮り直すと run の古い写しが選ばれ続ける ③rev16〜21 の GUI 目視が未実施 (私は headless) ④既定 frontal はユーザー自身の GUI には効かない (localStorage が勝つ、要手動切替)。 --- ## 2026-09-10 — rev23: 撮り直した画面が一覧に出ない (持ち越し ② の回収) 前日の持ち越し ② に着手。ユーザーの選択で、費用のかからない側から入った (①は live の LLM 費用、③は目視、④は操作なので、コードで動かせるのはこれだけだった)。 **調べて分かったのは、これがバグではなく契約違反だったこと。** `data_contract.yaml` の `RunSnapshots.add.no_dedup` は「撮り直しは同じパスで中身が変わるので、パスでの重複排除は 新しい画像を拒むことになる」と名指しで禁じている。backend はそれを守っていて同じパスを 2 度受ける。 一方 `snapshots.ts::snapshotChoices` は `inputPaths.filter((p) => !inRun.has(p))` で パス一致を落としていた — **契約を書いた rev20 自身が、その 1 行下で禁止を実装していた。** **さらに調べると、rev20 は症状を見つけてもいた。** spec に `これは未解決` と書いてある。 直らなかったのは、そこに「run が自己完結する以上そうなる」という**誤った必然性**を添えたから。 自己完結 (rev10) が強制するのは焼き直しが写しを読むことだけで、一覧の重複排除の鍵が パスであることは強制しない。迂回策 (別名で足す) の併記と合わせて、**開いている問いが 閉じた問いに化けていた**。これが今日いちばん持ち出す価値のある観察 (failures #20 の一般化 2)。 **処方**: 重複排除の鍵をパスから中身へ。`stale_run_snapshots` (backend) が「今の元ファイルの バイト列が、同じパスで写したどの写しとも一致しない」ものを返し、`snapshotChoices` は そのパスだけ重複排除から外す。run の写しは古いまま残す (rev10 は崩さない)。 一覧は同じ名前が 2 行並び、下が「撮り直し」。サムネイルのキャッシュも捨てて読み直す (パスをキーにしている以上、同じ欠陥の表示側の面)。 **測り方の観察**: backend の PoC 5 本のうち、実装前のスタブで **Red になったのは 1 本だけ**。 残り 4 本 (元が消えた / 写しが無い / 2 度足した後) はスタブでも通るので、**その 4 本だけでは 今回のバグを検出できない**。長さで弾く近道を通らない「同じ長さで中身が違う」1 本を別に足し、 バイト照合の行を消すと落ちることを確認した。 **足場**: crates 152 / vitest 30→32 / backend 10→15 green・両ワークスペース clippy clean。 **接地の限界**: **画面は見ていない。** 2 行並ぶ一覧が実際に読めるか、「撮り直し」の肩書きで 出どころが分かるかは未目視 (rev16〜21 と同じ状態)。 **この日の入力 (デザイン)**: ユーザーが X の調査を持ち込んだ。要点は「おしゃれ」は褒め言葉ではなく **スロップの合図**になっており、紫/ピンクグラデ・キラキラ・大味カード・強シャドウ・既定の glassmorphism が「AI っぽい」として忌避されていること。実務側の合意は **「形容詞で指示するな。見本とコードを渡せ」**。つまり次の主題 (UI を簡単にする) で待つべきものは 方向性ではなく**見本かトークン表**で、こちらで先に作り込むと既定値に落ちる。着手条件は変えない。 --- ## 2026-09-11 — rev24: mood にも面を足せるように (自由度) ユーザーの問い「1 枚目がかならずはめ込みなしというのは仕様ですか?」から。 **まず事実確認**: 仕様ではない。検査に位置の規則は無く、あるのは `NoProductCut` (product が 1 つ以上) という下限だけ。そうなるのは `scene_prompt` の一文 (`use mood only for the opening or a transition`) の誘導で、これは 2026-09-08 の live で**7 シーン全部が情景になり製品が 1 枚も 映らなかった**ことの回収として入れたもの。1 枚目を mood にすること自体は狙っていない。 **実測: 手元の promo.json 10 個すべてで 1 枚目が mood、mood はちょうど 1 つ** (AppPromoVideo 5 / Fuseforks 4 / Lorekeel 1)。例外なし — ユーザーの体感どおりだった。 **ユーザーの判断**: 1 枚目が mood 既定なのは構わないが、**足せないのが自由度が低い**。 **処方**: `cut_kind` は書き換えず、`PlateOverride.snapshot_index` を mood にも効かせる。 種別を書き換えると `image_prompt` が背景の記述でなくなり、参照画像を作り直したときに mood の絵を失うため。貼るかどうかの判定は `pipeline::reference::plate_snapshot_index` に **1 箇所へ寄せた** — それまで `reburn_caption` と `plate_preview` が別々に同じ式を持っており、 #19 (枠が嘘をつく) の再発条件が揃っていた。 UI ははめ込みの節をどのシーンにも出し、mood の既定を「はめ込みなし (絵のまま)」にした。 つまみは面がある時だけ。**編集ボタンはどのシーンでも押せるようにした** — 以前は「文も面も無い」と 押せず、mood に面を足す経路がそこで塞がっていた。 **足場**: crates 152→154 / vitest 32 / backend 15 green・両ワークスペース clippy clean。 **接地の限界**: 画面は見ていない。mood の素材は背景ではなく絵そのものなので、空きの無い場所に 面を置くと重なる。**黙って綺麗にはならない**前提で、その手触りは未確認。 **この日もう 1 つ**: ユーザーが GUI のスクリーンショットを送ってきた。**私が画面を見たのは初めて**で、 「どこが複雑かの判断材料を持っていない」という申告がここで外れた。観察 3 点を材料として残す — ①進捗ペインは常設の 1 列で、走らせるまで「まだ何も起きていません」だけを表示している ②1 シーンあたりの縦の場所代が高い (種別 + 尺 + ショット + コピー + 3 つの折り畳み + 画像 + ボタン) ③状態とそれに依存する操作が別のペインに離れている (左下の `画像: off` と、中央上端の `参照画像を生成`)。 ③は「おしゃれ」と無関係の構造の問題なので、見本が来なくても直せる。 **同日、目視 (ユーザー)**: rev23 と rev24 を GUI で確認済み (スクリーンショット 2 枚)。 rev23 は `4 枚目 — snapshot_04.png` の下に `撮り直し — snapshot_04.png` が並び、rev24 は mood の シーン 1 で `2 枚目` を選ぶと予定位置の枠が出た。**シーン編集ダイアログを見たのはこれが初めて。** UI 主題の材料として観察 2 点 — ①スナップショットの一覧が**ファイル名だけ**で、`clip_1788972676608.png` の ような名前からは画面が分からない (7 行を超えると名前で選ぶのは当てずっぽう) ②右列で**説明文がつまみを画面外へ押し出している** (撮り直しと mood の説明の後に `左右の傾き` が 見え始めて切れている)。どちらも見本が無くても直せる構造の問題。 --- ## 2026-09-11 — rev25: run に実際のモデルを残す ユーザーの問い「モデル名は短縮名 (`opus` / `sonnet`) ではなく正式名で入れるべきか」から。 **事実**: CLI の `--model` はどちらも受ける (エイリアスは系列の最新版に追従、正式名は固定)。アプリは 入力をそのまま渡す。**調べて分かったのは、run がモデルをどこにも残していなかったこと** — Fuseforks の promo.json に `model` の文字列 0 件。エイリアスで新しい版が出ると、開いている判断 1 の比較の途中で 入れ替わっても分からない。回答は「再現性のため正式名」、そのうえで**入力の書き方に依らず実名を残す**修正を入れた。 **処方**: stream-json の init 行にはトップレベルの `model` があり、エイリアスでも解決後の名前が入る (fixture で確認)。init → `StreamFold` → `RunOk` → `StageReport.models` → `RunStats.models` と運ぶ。 `None` = 記録なし / `Some([])` = CLI が名乗らなかった、を区別する。ついでに CLI と GUI が別々に手組み していた `RunStats` の組み立てを `pipeline::stages::run_stats` 1 箇所へ寄せた (#18 の再発条件)。 **PoC**: 5 本。Red は層ごとのスタブで 3 回観測。Red にならなかった 2 本 (後方互換の見張り / 新設関数の 全項目) はそう申告した。**足場**: crates 154→159 / vitest 32 / backend 15 green・両 clippy clean。 **検証での自分の失敗 (記録)**: 検証 3 本を並べて走らせた際、backend のコマンドの `cd app/src-tauri` が シェルの作業ディレクトリとして残り、ルート workspace の検証がその場所で走って「15」(backend の本数) を 返した。frontend は `cd app` が見つからず失敗。**数字は出たが別物を数えていた** — 以後、並べる検証は 絶対パスで cd する。2026-09-08 の台帳分裂 (cwd 起因) と同じ機序。 **接地の限界**: live は未実施。実物の promo.json で `models` が埋まるのは次の live から。 --- ## 2026-09-11 — rev26: 確認はアプリ内のメッセージボックスで rev25 の検証中にユーザーから (スクリーンショットつき)「ブラウザの confirm や message を使うとローカルの URL が出てしまうので、Web モーダルでメッセージボックスを作ってください」。シーン編集を未適用のまま閉じると 見出しに `localhost:1421 の内容` と出ていた。rev25 を先にコミットしてから (push はしていない) 着手した。 **使っていた箇所は 2 つだけ** (シーン編集の未適用確認 / run のフォルダ削除)。`alert` / `prompt` は無し。 `dialog.ts` の `ask` (一度に 1 件、待ち行列つき) と `MessageBox.vue` (App.vue に 1 つ) に置き換えた。 Esc と背景クリックは否定側、削除は最初の焦点を「キャンセル」に、keydown は capture で止めて下のダイアログの Esc と二重に反応しないようにした。`noBrowserDialogs.test.ts` が標準ダイアログの呼び出しを落とす網。 **測り方の失敗 2 件 (記録)**: ①網の初版が画面の文言「video prompt (…)」に誤検出した。②網の初版は `node:fs` で読んでいて、**vitest は通ったのに build の型検査で落ちた** (vue-tsc はテストも見る、Node の型定義が無い)。 `import.meta.glob` に書き直し、ソースに `confirm(` を一時的に差し込んで網が本当に落ちることを確かめてから戻した。 **「テストが通った」は「build が通る」を含まない** — このプロジェクトの足場確認は vitest と build の両方。 **足場**: crates 159 / vitest 32→38 / backend 15 green・build green。 **接地の限界**: DOM の無い node 環境なので、Esc が確認だけを閉じるか・焦点の閉じ込めと戻し・重なり順は未確認。 **同日、目視 (ユーザー)**: rev26 は GUI で OK (確認手順 4 点を提示、結果 OK の報告)。rev26 をコミットし、rev25 と合わせて push。 --- ## 2026-09-11 — rev27 (試行): 文字を少し大きく・列の枠を外す ユーザー (スクリーンショットつき)「文字を少しだけ大きくして、入力、結果、進捗の枠をなくしてみたい」。 **次の主題 (UI を簡単にする) で、ユーザーが具体的な方向を出した最初の手。** **文字**: px の直書きが 37 か所 (10〜18px) に散らばっていて、基準 (body 13px) だけ上げてもラベルや補足 (11〜12px 固定) は変わらない。`main.css` に `--fs-xs〜--fs-xl` のトークンを置いて全箇所から参照させ、 全段 +1px。次の調整は 1 か所で済む。**枠**: `.panel` はダイアログ 4 種も使うので共通の定義は変えず、 App.vue で 3 つの列の直下だけを平らにした。 **PoC なし** — 見た目の変更で、DOM の無い vitest では Red を観測できない (申告)。px の直書きが残っていないことを grep で、vitest 38 / build green を確認。**採否はユーザーの目視待ち**。枠が無くて境目が読みにくければ、 枠線だけ残す案と細い区切り線の案がある。 --- ## 2026-09-11 — rev28 (試行): 書体を IBM Plex Sans JP の Bold に・文字 1.5 倍 ユーザー「文字をフォントを IBMPlexSansJP-Bold にして 1.5 倍くらいにしてみて」。rev27 (試行) の上に重ねた。 **先に確かめたこと**: Plex はこの機体にユーザーごとのフォントとして 8 ウェイト入っている (システム全体には無い)。 CSS で名前を指定しても見つからなければ黙って別の書体になるので、書く前に確認した。 **入れたこと**: 書体・倍率・太さをすべてトークンに (`--font-ui` / `--fs-scale: 1.5` / `--fw-*`)。 本文だけ Bold にすると SemiBold (600) の見出しやボタンが本文より細く見えて逆転するので、太さも段ごとに引き上げた。 **PoC なし** (見た目、Red 未観測)。直書きが意図した例外 3 つを除いて残っていないことを grep、vitest 38 / build green。 **接地の限界**: WebView2 がユーザーごとのフォントを拾えるか未確認 / 幅と高さは px のままなので詰まる箇所が出うる / 公開前に採用するならフォントの同梱を別に判断する。 --- ## 2026-09-11 — rev29 (試行): 見出しと項目の大きさを分ける・説明文とログを Medium に ユーザー「見出しの大きさはこれくらい、内部の項目はちょっと小さく」。作業中に「進捗のログも medium で普通サイズ」 「説明文は bold でなく medium で小さめ」が続いた。 **大きさ**: 倍率 1 本 (`--fs-scale`) が全段に掛かっていて、見出しと項目が同じ段を共有していたので、 下げると見出しも縮む。見出し用の倍率 (1.5) と段 (`--fs-h-*`) を分け、見出し 6 か所を切り替え、項目は 1.25 倍へ。 **説明文**: `.muted` を Medium・小さめに。ユーザーの例が `note` 無しの `muted` だったので `.muted` 全体 (47 か所) に効かせた — 表の列やラベルなど説明文以外も巻き込むことはユーザーに伝えた。**ログ**: Consolas には Medium が無いので等幅をやめ、 UI の書体の Medium・本文の大きさ・等幅数字に。 **PoC なし** (見た目、Red 未観測)。参照箇所の grep と vitest 38 / build green を確認。採否はユーザーの目視待ち。 **同日、続き**: ユーザー「ウィンドウタイトルバーのアプリタイトルのフォントと大きさは変えなくていい」。 `.brand` を試行中の変数から外し、試行前の値 (Segoe UI 系 / 12px / 700) を固定値の変数 `--*-chrome` で参照させた。 見出し用の段は 5 か所に。rev29 の台帳 (契約の headings・spec の 118) からタイトルバーを外し、spec に 121 を足した。 vitest 38 / build green。 **同日、一時コミット (ユーザー指示)**: rev27〜29 を**試行のまま**一時コミットした (push はしていない)。採否は未決。 未決の一覧は CLAUDE.md「次の主題」の下 — 採否 / Plex の同梱 / WebView2 がユーザーごとのフォントを拾っているか / `.muted` に巻き込まれた表の列・ラベル / ログの medium の解釈 / 「実行中」chip の大きさ。 コードは直前の検証 (vitest 38 / build green) から変えていない。Rust は rev25 から不変 (crates 159 / backend 15)。 --- ## 2026-09-11 — rev30: デザインの修正と UI の多言語化 (ユーザーの手) を検めて回収 セッション再開 (Resume Key `APPPROMO-UI-TYPE-TRIAL-20260911`) の直後に、ユーザーから「デザインを修正し、UI の多言語化をこちらで おこなっておきました」。未コミットの差分 (変更 13 ファイル + 新規 `Icon.vue` / `i18n.ts` / `i18n.test.ts`) として受け取った。 **検めた結果** (差分のままで vitest 44 / build green): ①消えたトークン `--fs-h-xl` を ScenePanel が参照している ②Google Fonts の読み込みを CSP が通さない ③多言語化が一部だけ (英語を選んでも日本語が残る) ④キーの整合テストが件数の比較だけ。 ユーザー「提案の順で 1 から」。フォントは Inter と Noto の両方を同梱 (ユーザー判断)。辞書を読み直す途中で ⑤`t()` が差し込む値を置換パターンとして解釈する欠陥も見つけた。 **着地**: spec 01 rev30 (122〜127)。日本語の文言は変えていない (断片 275 個を機械で突き合わせた)。 **測り方の失敗 (記録)**: CSS トークンの網の初版は、vitest が CSS を空の文字列にするので全トークンを未定義と報告した。 自己テストは「main.css を拾っている」(パス) しか見ておらず、中身が空でも通っていた — **ファイルを拾ったことは、中身を読んだことを含まない**。 次に `css.include` で通そうとして 1 度外した (ID にクエリ `?raw` が付き、`$` で終わる正規表現が一致しない)。 この仮説を棄却してから vitest の実装を読み、照合の仕方を確かめて直した。未使用キーの数え直しでも、走査スクリプトが辞書の古い書き方を前提にしていて 「0 個」と出た (書き直した辞書を読めていなかった)。 **足場**: vitest 38 → 60 / build green。Rust は不変。 **接地の限界**: 画面は見ていない (書体・3 言語の溢れ) / en・zh-CN の訳は未査読 / backend 由来のログは日本語のまま / 既存の文言の不具合 2 点 (`**` がそのまま出る / 「既定は OFF」が実際と違う) は、文言を変えない方針なので残した / ユーザーが先に作った未使用キー 17 個は消していない。 --- ## 2026-09-11 — rev31: 履歴をダイアログから全画面へ rev30 のコミット (`4400506`) の後に、ユーザー「履歴のダイアログについてですが、これはダイアログではなく全画面に変えてください。 アイテムから開くを押すと画面を閉じてメイン画面に開いた画面で戻るようにする」。 **既定で選んだこと** (ユーザーには確認せず、報告する): タイトルバーは残して、その下を入れ替える / メインは `v-show` にして戻った時に状態を残す / 「開く」は読めた時だけ戻り、失敗したら残って理由を出す (履歴画面ではエラーの出る場所が見えない) / Esc では戻らない / ファイル名は `RunsScreen.vue`。 **PoC**: `openRun` が成否を返す (`store.test.ts`、Tauri の invoke をモック)。Red 2 本 → Green。画面の切り替えは DOM が無いので未確認。 **足場**: vitest 60 → 62 / build green。Rust は不変。 --- ## 2026-09-12 — rev32: 比較中は比較対象を表の先頭に (rev31 で表が潰れていた) ユーザー (スクリーンショットつき)「比較のときに上の 2 つだけ表示されます。よってリストの下の方を比較しようとすると戻るを押さないと戻る術がありません。 なので比較のときは比較対象のふたつを上に表示しましょう」。 **真因は rev31 の私の不具合**: 表の包みの `overflow-x: auto` で、縦の flex の中の表が画像に場所を譲って潰れていた (failures.md 末尾)。 ご提案の「先頭に出す」に加えて、潰れ方を偶然に任せないようにした (表は縮ませず比較中だけ 30vh、画像の側を縮める)。 **初めて画面を測った**: vite の dev サーバーをブラウザのペインで開き、Pinia のストアにダミーの履歴 10 行と比較画像を入れて、 表・行・画像の位置を数値で取った。直す前 (見えている 156px / 中身 622px、選んだ 8 行目が見えない) を再現してから直し、 1920x1032 と 1280x720 で比較対象の 2 行が見えることを確かめた。途中で、画像の最小を区画に持たせたために 1280x720 で画像が 139px まで潰れているのを見つけ、 画像の枠の最小 (260px) に改めた。**スクリーンショットはペインの描画待ちで時間切れが続き、見た目は数値でしか見ていない。** **足場**: vitest 62 → 65 / build green。rev31・32 は未コミット。 --- ## 2026-09-12 — rev33: チェックボックスを大きくしない / 表のセルを flex にしない ユーザー (スクリーンショット 2 枚)「選択されるとチェックボックスを大きくする必要はないかな」。 **真因は 2 つ**: ①rev30 のデザイン修正の全体の `input` 規則 (幅 100% / 最小の高さ 44px) がチェックボックスにも効き、列の幅に引き伸ばされていた (比較中は A / B の印で列が広がるので大きくなる) ②rev32 で私がチェックの列の td を flex にしていた (操作ボタンの列は以前から) ので、同じ行のセルがずれていた。 スクリーンショットの「先頭 2 行の色付けが段になる」は②。 **直す前を測ってから直した**: 28x44 / 76x44、セルの上端 2 通り → 直した後は全行 16x16、上端 1 通り、行の高さ 65 → 40px。 設定ダイアログのチェックボックスも 13x44 → 16x16 (直書きの `width: auto` を外した)。 **測り方の失敗 (記録)**: 最初の測り直しが直す前と同じ数値だった。ユーザーの `tauri dev` が止まっていて、ページが古いコードのまま動いていた (failures.md 末尾)。自分で vite を起動して測り直した。**測る前に、今のコードが届いているかを確かめる。** 自分で起動した vite は、測り終えてから止めた (port 1421 を空けるため)。 **足場**: vitest 65 / build green。rev31〜33 は未コミット。 **同日、一時コミット (ユーザー指示)**: rev31〜33 を一時コミットした (push はしていない)。続けて data_contract.yaml を YAML として読めるように直す (以前から 87 行目で解析に失敗していた。rev30 以降、触ったブロックだけを切り出して検証していた)。 --- ## 2026-09-12 — data_contract.yaml を YAML として読めるように直す 一時コミット (`51e7645`) の後、ユーザー「data_contract.yaml の yaml をなおしましょう」。 **壊れていた箇所** (29 ブロック中 5 つ。PyYAML で 1 ブロックずつ読んで洗い出した): ①CliOutcome / CliError — Rust の列挙の擬似記法 (`Ok { text: String, ... }` / `Cancelled`) が、キーの無い行として並んでいた。 ②ClaudeStreamLine / Scene — 値の中の `|` や `Option<{ yaw_degrees: f32, ... }>` が YAML の構文と衝突していた。 ③CaptionLayout — **中身ではなく位置の破損**。`ImageGenConfig.caption` の続き (`default_font` など 5 キー) と `ImageGenConfig` の続き (`http` など 4 キー) が、 rev11 (`9375135`) で `PlateOverride` をその途中に挿し込んだ時に親から切り離され、後から挿し込まれた `RunSnapshots` / `CaptionLayout` の下に残っていた。 git で rev11 の 1 つ前の版を見て、`per_scene.layout_for` の直後に `default_font` が続いていたことを確かめた。 加えて、Scene に `video_prompt` のキーが 2 回あった (PyYAML は黙って後勝ちにする)。 **直し方**: 擬似記法は `Variant: "{ ... }"` の形で引用符の中へ (情報は変えない)。迷子の 9 キーは元の位置へ移した (行頭の目印で切り出すスクリプトで、長い行を手で写さない)。 重複した `video_prompt` は 1 つにし、2 つ目の説明は同じ文言のまま続きのコメント行にした。 **検証**: 全体の解析 OK・重複キーなし (29 ブロック)。直す前の 513 行と、引用符・コロン・空白の差を除いて突き合わせ、差は意図した 3 行だけ。 網 `scripts/check_data_contract.py` (重複キーも拾う) を足し、直す前の版で NG (87 行目) / 今の版で OK / 重複キーの見本で NG になることを確かめた (素の PyYAML は同じ見本を黙って通した)。 **コミット** (ユーザー指示、この修正と台帳の記録を 1 本に。push はしていない)。 --- ## 2026-09-12 — アプリのアイコン ユーザーがリポジトリの直下に `icon.png` (800x800、カチンコ) を置いた。「AppPromoVideo のために作ったアイコン。アイコンを作ったら消しましょう」。 `npx tauri icon ../icon.png` で `app/src-tauri/icons` の 50 ファイル (Windows / macOS / Android / iOS) を作り直した。 ファイル名はひな形と同じなので tauri.conf.json は不変で、新規ファイルは 0 (全部上書き)。生成した 128x128 を目で見て確認した。 元の `icon.png` は git で追跡していないので、完全削除ではなく**ごみ箱へ移した** (戻せる状態にしておく)。 **接地の限界**: 元画像が 800x800 で、Tauri の推奨 (1024) より小さい。`icon.icns` の最大サイズだけは拡大なので、macOS の大きい表示でぼやけうる。 反映は Tauri の起動し直し / ビルドから。 `.claude/` (Claude Code の手元の設定、launch.json など) は `.gitignore` に入れた (ユーザー判断)。 --- ## 2026-09-12 — rev34: 設定の入り切りをスイッチに ユーザー (スイッチの見本の画像つき)「設定画面のチェックボックスですがチェックボックスではなく、スイッチにして下さい」。 **作ったもの**: `components/Switch.vue`。中身は `input[type="checkbox"]` のままで、枠は input 自身、つまみは兄弟の ``。 設定の 4 か所が使う。履歴の比較のチェックは表の複数選択なのでチェックボックスのまま。 **初版から変えた点**: つまみを `::after` (疑似要素) で描いていたが、`` に疑似要素が描かれるかはブラウザ次第で、 **描かれたことを外から確かめる手段が無い** (位置を測る API が無い / この環境ではスクリーンショットが撮れない)。実体のある要素にして、位置を数値で測れるようにした。 **実測** (遷移を 0 秒にしてから): 切 = 枠 38x22・rgba(154,146,172,0.3)・つまみ 14x14・左 4 / 右 20px、 入 = rgb(236,122,56) (`--accent`)・左 20 / 右 4px。押すと値が false → true → false と動く。vitest 65 / build green。 **測り方の失敗 2 件 (記録)**: ①「入れても色が変わらない」と出たが、ペインが描画されていない状態では**遷移が進まず**、computed style が時刻 0 の値を返していた (failures.md 末尾)。②次の測定ではページが古い版のままだった — rev33 と同じ形で、サーバーが止まってページが取り残されていた。 rev33 で入れた「先に今のコードが届いているかを確かめる」手順のおかげで 1 回で気づけた。 **未コミット。** ON の色は見本 (青緑) ではなくアクセント (オレンジ) にした — 配色にその色が無いため。色はユーザーの判断待ち。 --- ## 2026-09-12 — rev35: 設定も全画面に (3 列・スクロールなし) ユーザー「設定画面もダイアログではなく 1 画面にしたい。構成なども考えて、スクロールなしで収まるようにしてください」。 最初の版のスクリーンショットを見て → 「段が崩れないように調整して下さい」。 **構成**: `SettingsDialog.vue` → `SettingsScreen.vue` (git mv)。3 列 = LLM CLI / 画像生成 / 面と見出し。タブは廃止。 長い説明・認証の診断・ComfyUI の詳細は `
` で畳み、文言は残した。画面自体はスクロールせず、収まらない時は列の中だけが動く。 **段の崩れ**: ラベルが 1〜3 行に折り返すため、`.row` (flex の中央揃え) では入力欄の位置がずれていた。`.grid-row` (grid + align-items: end) で下端を揃え、 プレビューのボタンにも label と同じ下余白 4px を持たせた。実測で 4 本すべて下端が 1 通りになった (直す前は見出しの行が 2 通り)。 **測り方の失敗 2 件**: ①最初の「収まった」は 1600x1017 での測定で、ユーザーの窓 (1288x842) より 175px 高かった (failures.md 末尾)。 ②テンプレートが届いていないページで測り `.grid-row` 0 本と出た — `tauri dev` が止まっていた (rev33 と同じ)。届いているかの確認があったので誤った結論には至らなかった。 **足場**: vitest 65 / build green。**同日コミット** (ユーザー指示、rev34 と 1 本に)。 --- ## 2026-09-12 — rev36: 無い記録を描かない / タイトルバーのアイコンはトグル ユーザー (GUI の目視から 2 件)「履歴から run を開いた直後の結果ペインに `Passed on attempt 0` と `0.000 USD` が出ています。修正お願いします」 「ウィンドウタイトルバーの呼び出しのアイコンを、開いているときにもう一度クリックすると戻ると同じ動きになるようにしてください」。 **1 件目の真因は rev31 の私のコード**: `openRun` が実行時の統計を 0 で埋めており、結果ペインがその 0 を描いていた。 正本 (`promo.json` の `run_stats`) には本当の回数と費用があったので、出所を正本に変え、記録が無い run では chip を出さないようにした (failures.md 末尾)。 **2 件目**: `views.ts::nextView` (純関数) でトグルにし、開いている画面のアイコンをアクセント色にした。設定から離れる時は保存する。 **テストの値は実物から取った**: `runs/20260912-035429/promo.json` の `plan_attempts 3` / `cost_usd 1.6736855` / `violation_kinds [product_backdrop_draws_screen]` と、記録を持たない 2026-09-08 の run。 **足場**: vitest 65 → 72 / build green。ブラウザで 5 通りの操作と 2 通りの run を実測。Tauri の GUI では未目視。 --- ## 2026-09-12 — rev37: プロンプトを書き換えられるように (鉛筆で編集モード) ユーザー「motion や video prompt は書き換えが可能のほうがよい。ただそのまますぐに書き換えられるのではなく、鉛筆アイコンを押して、 シーンの編集モードにしてから書き換えられるようにしたい」。 **契約を先に書いた** (`ScenePromptEdit`)。編集モードを挟む理由 / 空は拒む / promo.json と scenes.md を揃える / 「最初の文」は持たない。 そのうえで promo_core の純関数 → backend の command → frontend の順に Red を取ってから実装した。 **層ごとの PoC**: promo_core 3 本 (trim と拒否、拒否時は plan を触らない) / backend 2 本 (promo.json と scenes.md の両方が変わる、拒否時は何も書かない。 **scenes.md の書き込みを外すと落ちることまで確認**) / frontend 3 本 (成功で backend の promo に差し替え、失敗で false)。 **ブラウザ実測**: 鉛筆 → 入力欄 2 つ、保存に失敗すると編集モードのまま、破棄で元に戻る、隣のシーンは編集モードに入らない。 **足場**: crates 159 → 162 / backend 15 → 17 / vitest 72 → 75 / build green。**GUI からの保存は未実施** (backend のテストで固定した経路)。 **GUI 目視 OK** (ユーザー、2026-09-12。英語表示のスクリーンショットで確認 — 3 列とも溢れず、2 列目のスクロールバーも消えた)。スイッチの色はアクセントで確定 (「見本は形のサンプル」)。 ## 2026-09-12 (rev38) — 落ちた run の生ログを残す / 未使用の i18n キーを撤去 ユーザー「途中で止まってしまった」。live の解析が 18:48:10 開始 → 18:50:14 に `CLI の出力の形が想定と違います (result 行が無い (途中で終了)): - **製品分析JSONの作成**: …出力しました。` で落ちた。 **切り分けで除外できたもの**: タイムアウト (それなら `CliError::Timeout`。`Shape` は子が自分で stdout/stderr を閉じた経路。既定 600 秒に対し 124 秒) / 認証 (`CliError::Auth` に分類される) / `--max-turns` が消えた説 (2.1.263 の `--help` には載っていないが受理される。 未知フラグは `error: unknown option` で即死することを対照実験で確認した)。 残ったのは「**result 行を出さずに終了し、stream-json の規約に反して素の散文が stdout に出ていた**」という事実だけ。 **そこで止まった理由が、そのまま欠陥だった。** `runner::run` は受信した行をメモリにしか持たず、畳むのに失敗すると 300 字だけ載せて捨てる。 run ディレクトリは export 時に作られるので、解析で落ちた run は痕跡ゼロ。おまけに子の終了コードは取得しておきながら claude では見ていなかった。 → 契約 `CliRawLog` を先に凍結し、`/cli-logs/-.jsonl` へ**行が届くたびに**書くようにした (fail-open、最新 20 本、成功 run も残す)。 `RunFailed.log_path` に載せて Display にも出すので、進捗ログにパスが出る。偽 CLI に `stream-truncated` モードを足して今回の形を再現し、 3 本の PoC を Red → Green。**3 か所をそれぞれ壊すと 3 本とも落ちることまで確認**した。 **ついでに見つけたもの** (このアプリとは無関係): ユーザーの global な SessionStart hook `~/.claude/hooks/toolcall-leak-clean.js` が 203 行目で SyntaxError を起こし、claude の起動のたびに落ちている (fail-open なので実行は止まらないが、掃除は一度も走っていない)。 **i18n の整理**: 未使用キーを数える網を書いて 12 個を撤去したが、台帳には「13 個」とあった。 網の初版が `blob.includes(key)` の**部分一致**で、`runs.delete` が `runs.deleteTitle` の陰に隠れていた。 引用符つきの完全一致に締めて 13 個目が出た。**自分の測定と既存の記録が 1 個ずれたら、先に測定を疑う。** 13 個はすべて改名の取り残しで、置き換え先が現役だった (`runs.empty`→`runs.none` など)。3 言語から同時に消して 270 → 257 キー。 **生ログの初実測 (19:24)**: ユーザーが再実行すると**別の壊れ方**が出た。設定「OAuth ログインを使う」を ON にした run で、 起動と同じ秒に終了コード 2、stderr に `Error: -p took "--output-format" as its prompt, so the intended prompt was left as an argument and ignored.`。 生ログのパスはエラーに出た (機構は効いた)。しかし**手元で再現しない** — 同じ実体 (2.1.263、Sep 8 から更新なし) に `-p --output-format …` を `--max-turns` / `--model` / `--add-dir` / `--allowedTools` / `--json-schema` と組み合わせ、 bash と PowerShell の両方で試したが全部通る。`-p` を末尾に移しても同じ。位置引数を足してもエラーにならず、単にそれが prompt になる。 → **何が返ったか (生ログ) だけでは足りず、何を起動したかが要る。** 兄弟ファイル `.invocation.json` と進捗ログの「起動: …」を足した (153)。 疑いは設定「追加の引数」の 1 語だが**未確認の仮説**。 この検証で**位置引数のテスト 2 本が実際に API を呼んでしまった** (プロンプト `stray`)。 失敗させるつもりの引数が失敗しなかった — 「安全に試せる」と思った手が課金経路だった。 **足場**: crates 162 → 167 / backend 17 / vitest 75 → 78 / build green / 両ワークスペース clippy clean。 **接地の限界**: **障害は 2 件とも直っていない。** この日したのは「次に落ちた時に読めるようにした」ことだけ。 19:24 の生ログは実機に出たが、**中身はまだ読んでいない** (ユーザーの手元にある)。 ## 2026-09-12 (rev39) — agy 対応: 封筒 + 見張り + 隔離の限界の明記 **障害 2 件の真因が 1 つに畳まれた。** rev38 で足した `.invocation.json` と進捗ログの「起動: …」が `agy -p --output-format …` と出し、**起動していたのが `claude` ではなく `agy`** だと分かった。 agy の `-p` は値 (prompt) を取るので `--output-format` を本文として飲む。18:48 の 2 分の run も同じ並びで、 更新前の agy が黙って受けた結果、出力形式が既定の `text` のまま = 封筒が 1 行も無く素の散文だけが出ていた。 **ユーザーの最初の報告は「agy で途中で止まってしまった」だった。** 1 語目に実行ファイル名が書いてあったのに、 私はそれを打ち間違いと読み、以後の検証をすべて `claude` の前提で回した。確認は `where agy` の 1 行で済んだ。 **実測してから設計した** (fixtures 3 本)。read (`list_dir` / `view_file`) は無条件に通り **cwd の外も読む**、 `write_to_file` も**無条件に通ってファイルが実際に作られ**、止まるのは `run_command` だけ。 `--mode plan` も `--sandbox` もツールの面 (57 個) を変えない。**argv で絞る手は無い。** ユーザーは選択肢 1 (封筒 + 見張り + 契約の限界明記 + UI 警告) を選び、契約を kind ごとに割った (`IsolationGuarantee`: claude = 構造で保証 / agy = 検出のみ)。 **ユーザー案に 2 点反対して変えた**: ①JSON でない行を `filter_map` で捨てる案 → 18:48 で唯一の手掛かりだった行であり、 今回も `EmptySuccess` の理由はそこにしか無いので**保持**に変更 ②deny リスト → 57 個の数え漏らしが穴になるので **allow リスト**に反転。 **網が空振りしていた**: 「読み取りでは止まらない」テストが `all()` で書かれており、見張りを無効化すると `stopped` が空になって通り続けた。件数の固定を足して塞いだ。同日の i18n の部分一致と同じ形 (不在の証明を書くときは、先に存在の証明を書く)。 **足場**: crates 167 → 175 / backend 17 / vitest 78 → 81 / build green / 両ワークスペース clippy clean。 **接地の限界**: **agy で通しを走らせていない。** fixture は短い run 3 本で、リポジトリ解析のような 長い run の封筒は未観測。`finish` を allow リストに入れたのは推測 (tool event としては未観測)。 `--add-dir` が agy の読み取りを縛るかも未確認。UI のバナーは Tauri の GUI で未目視。 ## 2026-09-12 (rev40) — 種類と実行ファイルの食い違いを画面で止める rev39 を積んで再実行してもらったが、argv は claude のままだった。ビルドは rev39 を含んでいた (exe 20:14:41 > コミット 20:13:32) ので、原因は**設定の「種類」が claude のまま**だったこと。 実行ファイル欄だけ `agy` にでき、誰も咎めない構造だった — **今日の障害 2 件はすべてここから出ている。** 設定画面のチップも `claude 1.2.2` と、ラベルは種類・版は実体から取った嘘を出していた。 `kind_mismatches_version` を純関数で置き、`--version` の実測の名乗り (claude `2.1.263 (Claude Code)` / agy `1.2.2`) に接地させた。**名乗りが空なら何も言わない** — 検査が失敗しただけで食い違いの証拠は無い (rev36「無い記録を描かない」と同じ規律)。aider / custom は名乗りの形を知らないので判定の外。 警告は出すが**実行は止めない** (判断はユーザーのもの)。 **自分で既知の不具合を踏んだ**: 文言に `**強調**` と書いた。`Rich.vue` は `` / `` しか解さないので そのまま画面に出る (未決 ⑥ と同じ形)。網を書いて塞いだ。 **vitest は通ったが build の型検査が落ちた** — `store.ts` の失敗時フォールバックに新しい欄が無かった。 「vitest と build はセット」の実演がまた 1 件。 **足場**: crates 175 → 176 / backend 17 / vitest 81 → 83 / build green / 両ワークスペース clippy clean。 **接地の限界**: 判定は**名乗り 2 種の実測**にしか接地していない。claude が名乗りの文言を変えたら誤検出する。 aider の名乗りは未確認。GUI は未目視。 ## 2026-09-12 (rev41) — 食い違ったまま走らせない rev40 を入れても同じ失敗が 3 回続いた。配線を端から端まで検めたが**アプリ側に欠陥は無く** (選択肢は動いているビルドに届いており、`toBackendCli` は `kind` をそのまま通し、run は毎回ストアを読み直す)、 run の時点で種類が `claude` のままだった。つまり**設定が変えられていない**。 **これは私の設計の誤り。** 警告を設定画面にだけ置いたが、実行はメイン画面からするので誰も通らない。 検出は働いていたのに出口が人の通り道に無かった。ユーザー決定で**止める**側に倒した (食い違ったまま走らせると必ず数秒で失敗するので、止めても失うものが無い)。 run の開始時 — 認証の行の直後・brief を読む前 — に `--version` を引き、食い違えば 1 行出して `Err`。 名乗りが取れない時は黙って進む (取れないことは食い違いの証拠ではない)。 `--version` を引く経路は `cli_version` 1 つに寄せた。 **足場**: crates 176 / backend 17 → 19 / vitest 83 / build green / 両ワークスペース clippy clean。 **接地の限界**: **止める配線そのものは未テスト** (Tauri のコマンドを動かさないと確かめられず、Red を観測していない)。 判定と文言は純関数の PoC で固定し、文言から直し方を消すと落ちることは確認した。GUI 未目視。 ## 2026-09-12 (rev42) — agy の初 live で出た 2 つ **agy で通しが動いた。解析は成功** (`structured_output` を受信、94.3 秒)。`event` / `step_update` / `result` の読みも、 進捗ログへのツール表示も実機で確認できた。構成タスクで 2 件出た。 **①見張りが実際に止めた。** 構成タスクが `run_command (Get-ChildItem …snapshots)` に手を伸ばした瞬間に木ごと停止 (生ログの最終行が `ACTIVE run_command`、何も実行されていない)。**なぜシェルに逃げたか**まで分かった — スナップショットは app_data にあり `--add-dir` の外で、`find_by_name` / `grep_search` が空振りしていた。 **逃げ道を塞ぐのではなく逃げる理由を消す**方針で、置き場を `--add-dir` に足した。見張りは厳格なまま (緩めると「確率の保証」に戻る)。あわせて**agy の `--add-dir` は読み取りを縛らない**ことが確定した — 同じ run の解析タスクは `--add-dir` の外のスナップショットを `view_file` で普通に読めていた。 **②こちらが嘘をついた。** `解析 完了: 0.000 USD`。agy は費用を返さないのに `unwrap_or(0.0)` で潰していた。 rev36 で「無いものを 0 で埋めない」を直したのは画面で、**同じ潰しが上流の畳み込みに残っていた**。 `StageReport` / `RunStats` / `RunListItem` / frontend の型を `Option` にし、合計は 「**片方でも不明なら不明**」に統一した (分かっている分だけ足すと別種の嘘になる)。 **手順の失敗**: 検出力の確認のあと `git checkout -- src/runs.ts` で戻したら、**rev42 の変更ごと消えた**。 未コミットの作業があるファイルに checkout を当てた。壊す→測る→戻す、は checkout ではなく逆編集でやる。 **足場**: crates 176 → 179 / backend 19 / vitest 83 → 86 / build green / 両ワークスペース clippy clean。 **接地の限界**: **`--add-dir` の追加が効くかは未検証** (次の live でしか分からない)。GUI 未目視。 ## 2026-09-12 (live) — agy で通しが成功 ユーザー実機、21:13。**解析 → 構成 → 参照画像 → 合成 → 見出しの焼き込み**が agy 1.2.2 で最後まで通った (`runs/20260912-121310`、scene 01〜04)。スクリーンショットで確認。 - **構成は 1 回目で通過** = rev42 の `--add-dir` 追加が効き、`run_command` への逃げは起きなかった。 見張りは厳格なままで通った — **逃げ道を塞ぐのではなく逃げる理由を消す**という方針が実機で裏付けられた。 - **費用の chip が出ていない。** rev42 で `Option` にした結果で、`0.000 USD` という嘘は消えた。 - 参照画像は 4 枚のうち先頭 3 枚 (default) を送り、canvas を 1344x768 → 2824x1614 に拡げ、 見出しを `NotoSansCJKjp-Black.otf` で焼いた。 これで **CLI の選択肢が 2 つになった** — claude (構造で隔離を保証) と agy (検出のみ、警告つき)。 **まだ見ていないもの**: 設定画面 (rev35・rev40 の警告バナー)、鉛筆からのプロンプト保存 (rev37)、 履歴の全画面 (rev31〜33)。今回のスクリーンショットはメイン画面だけ。 ## 2026-09-12 (rev43) — シーンの並び替え / 複製 / 削除 ユーザー要望。着手前に**何が `scene_id` に紐づいているか**を数えたのが設計の分かれ目だった — 3 つの Map (`caption_overrides` / `plate_overrides` / `original_copy`) と 2 か所のファイル名 (`run` 直下と `base/`)。 検査が `scene_id` = 位置 + 1 を強制するので並べ替えと削除は必ず振り直しを伴い、**素朴に振り直すと 人の手編集が別のシーンに付く**。しかも失敗のシグナルは出ない。 選択肢を 2 つ出して**ユーザーが「振り直しを全経路に伝播」を選んだ** (もう一方は `scene_id` を不変にして 並びを別に持つ案。`scene_id` = 表示順に依存した箇所が多すぎて、読み替え漏れのほうが怖い)。 危険は `SceneRemap` (旧 → 新) **1 つ**に集めた。追加は複製 (空欄を検査が弾くので白紙は作れない)、 尺は直さずずれを出すだけ — どちらもユーザー決定。 **ファイルの付け替えは一時名を必ず経由する。** 1↔2 の入れ替えを直接 rename すると片方を潰す。 **網の穴を 1 つ塞いだ**: 削除のテストが 2 番目を消していたが、**孤児が出るのは末尾を消した時だけ**だった (手前を消すと後ろの rename が上書きしていく)。末尾削除を足したら、削除の実装を外してもそちらだけが落ちた。 同日の i18n の部分一致・見張りの空振り `all()` と同じ family — **どれも「無い」を測る側の失敗**。 **足場**: crates 179 → 188 / backend 19 → 25 / vitest 86 → 90 / build green / 両ワークスペース clippy clean。 **接地の限界**: **GUI 未目視。** 実ファイルでの付け替えは backend のテストで固定したが、画面から押した時の 見え方は確認していない。参照画像の走査は 1 シーン 8 枚まで (`MAX_REF_PER_SCENE`)。 ## 2026-09-12 (rev44) — 初回起動のナビゲーション ユーザー要望。「Lorekeel と同じ。ただ細かく実行させながらではなく**順番を教えるだけでいい**」。 歩と文言もユーザー指定 (入力 → 設定 → 解析 → 出力から動画生成 AI へ)。 **移植元を先に読んだ** (掟)。Lorekeel の `tour.ts` は DOM に依らない判断だけの純粋な芯で、 `spotlightBox` / `placeCard` は向こうで実機に当てて詰めた判断 (カードは 下 → 上 → 右 → 左 の順に 収まる側へ、三角は寄せても対象の中心を指し続ける)。**そのまま写した** — こちらで作り直す理由が無い。 変えたのは歩と対象、「初回」の見分け方、そして `spotlightUnion` (複数の要素を束ねて照らす)。 1 歩目が「実行ボタン**以外**をまとめて」なので、**包む div を足さずに**先頭と末尾へ印を付けて束ねた (包むと余白の出方が変わる)。見た目は Tailwind ではなくこちらのトークンで組み直した。 **網が 2 つ仕事をした**: `cssTokens.test.ts` が `var(--arrow)` (行内スタイルで注入するので CSS に定義が無い) を捕まえ、`i18nUnusedKeys.test.ts` が動的キー `t(`tour.${step}.title`)` を捕まえた。 後者は字面に直した — 組み立てると型がキーを検めず、網からも見えなくなる。 **私の誤読**: 印を 1 つ外して検出力を試し「落ちなかった」と読んだが、**外すコマンドがシェルの エスケープで効いておらず、ファイルは無傷だった**。同日に網の空振りを 3 件見つけた直後で、 「またか」という予断が確認を飛ばさせた。ファイルの件数を数えてからやり直すと、ちゃんと落ちた。 **足場**: crates 188 / backend 25 / vitest 90 → 110 / build green / 両ワークスペース clippy clean。 **接地の限界**: **GUI 未目視。** `placeCard` は Lorekeel の実機調整済みだが、こちらの窓と 3 ペインの 配置では未検証 — 特に 1 歩目 (縦長の入力ペイン) はカードが右に出るはずで、計算でしか確かめていない。 ## 2026-09-12 (rev45) — 案内をもう一度見る口 rev44 を入れたのに出てこない、とユーザー。推測せず `runs.json` を数えたら **7 件**あり、 `shouldShowTour` が「使った痕跡あり」で弾いていた。しかも弾いた時に印を立て直すので、 `apppromo.tourDone` を消して再読み込みしても同じ結果になる。 判定は設計どおりだが、**一度でも使った環境では二度と見られない**。作った本人も見られない。 設定画面に「使い方を見る」を置き、押したらメイン画面へ戻してから出すようにした (対象の要素はメイン画面に居るので、設定画面のままでは照らせない)。 **「初回だけ」は「二度と見られない」と同義** — 出す条件を絞るほど出し直す口が要る、を failures に残した。 検出力の検査は **外した件数を数えてから**測った (rev44 で同じ検査を誤読したため)。 **足場**: vitest 110 → 112 / build green。 ## 2026-09-12 (rev46) — ブランドの標を 1 か所に ユーザー指摘「Outcasts の色とフォントと効果は合わせて欲しい」。案内の幕を橙のアクセント色で書いていたが、 タイトルバーは紫 + 二重のグローだった。**タイトルバーを見ずに「目立たせる」と決めた**のが原因。 見た目を 2 か所に書けば必ずずれるので、`.brand-line` / `.outcasts` を `main.css` に置いて両方が使う形にし、 タイトルバー側の自前定義は撤去した。色は `--brand-mark` を oklch の**成分**で持ち、 `oklch(var(--brand-mark) / 0.8)` で元の描画をそのまま再現する (`color-mix` に依存しない)。 **ブラウザで実効値を突合してから報告した** (掟)。色・text-shadow・書体・太さ・字間 (em) が一致、 大きさだけ 14px 対 23.8px。あわせて **rev44 の未検証が 1 つ閉じた** — 1 歩目のスポットライトは 入力欄の先頭から設定の行までを覆い、**実行ボタンは枠の外**、カードは右に出て画面内に収まる。 **足場**: vitest 112 / build green。**接地の限界**: 測ったのはブラウザ (1280x720) で、Tauri の WebView では未目視。 ## 2026-09-12 (rev47) — 配布ビルドの締め ユーザー要望「リリースビルドで右クリックで開発ツールを開けないように、ラベルも選択できないように、F5 も」。 **先に数えたら 3 つのうち 2 つは既に入っていた** — 右クリックと F5 / Ctrl+R の抑止は Kataribe から 移植済みで `main.ts` にあった。さらに `Cargo.toml` の tauri は `features = []` なので、 **release には開発ツールがそもそもコンパイルされない**。新しく要るのは文字の選択だけだった。 配布ビルドでだけ `` に印を立て、CSS が `:root[data-locked]` で切る形にした。 **免除を先に決めた** — 入力欄・プロンプト本文・エラー文・**進捗ログ**。 ログを塞ぐと「ログを貼って報告する」導線ごと潰れる。この会話でも一日中ログを貼ってもらっている。 締めの目的はアプリらしくすることであって、報告を難しくすることではない。 この機構には今まで 1 本もテストが無く、dev まで巻き込む書き換えが静かに通る状態だったので網を足した。 **測り方でまた 1 つ**: 最初の測定は `auto` のままで「効いていない」と見えたが、**CSS の HMR が届く前**だった。 rev33 で自分が書いて CLAUDE.md にも載せた「測る前に届いているかを確かめる」を飛ばしている。 同日 rev44 の誤読も同じ機序。**手順を書くことと、手順を通ることは別。** **足場**: vitest 112 → 118 / build green。 **接地の限界**: **配布ビルドでは未確認** — 規則の効き目だけを手で印を立てて測った。 ## 2026-09-13 — 公開と v0.1.0 public にした。公開前に追跡ファイルを洗ったら**個人情報が入っていた** — Windows のアカウント名が fixture 3 本と specs に、さらに `agy_empty_success.jsonl` は**別のツール構成まで**晒していた (`.gemini/.../memoria/skills/...`)。agy が cwd の外を読んだ実測なので、**読まれた側のパスが封筒に載った**。 私が作った fixture で、実データをそのまま焼いたことの代償。形だけ保って名前を中立にした。 `.github/workflows/build.yml` は Lorekeel / Fuseforks からの移植。**初回から 3 OS とも green**で、 Release に installer 7 点が揃った。通ったのは移植元が実運用で詰めたからで、こちらの手柄ではない。 **配布物の性質を台帳に残した** — macOS は未署名・未公証、かつ **aarch64 のみ** (Intel Mac は対象外)。 どちらも後から「なぜ動かない」と聞かれる種類の事実で、**出荷物の性質は出荷時にしか正確に書けない**。 **次の主題は Mac の Homebrew 対応。** cask は検疫付きで配るので、未署名のままでは Gatekeeper に止まる — 署名と公証が先。Lorekeel / Fuseforks が同じ道を通っている。 ## 2026-09-13 (昼) — Apple の秘密と公証の資格情報の検証 ユーザーの問い「Apple での登録はどうすればいいか」。答えは**登録済み** — Fuseforks の v0.1.9 で取った Developer ID (Team `6XU323VJZN`) が `~/.apple-signing/` に残っており、証明書はアカウント単位なので流用できる。 足りなかったのはリポジトリの secret だけ (Fuseforks には 5 つ、こちらは 0)。ユーザーが手元の材料から 5 つを入れた。 `verify-notary.yml` を Fuseforks から移植し、手動実行 1 回で通過 (APPLE_PASSWORD 19 文字、`notarytool history` が Accepted の履歴を返した)。**証明書の取り込みは実ビルドでしか試せない**ので、署名の成否は次のタグで分かる。 **観察**: 三点測量の 1 点目 (Fuseforks の CLAUDE.md) で「登録済み」が分かった。手順を一から答えていたら、 既に持っている証明書を二重に発行させるところだった。**「どうすれば」の問いには、まず「もう持っていないか」を測る。** ## 2026-09-13 (午後) — v0.1.1、署名と公証が通った ユーザーの「CI」で私がタグを打つ段取りにしたが、ユーザーが先に `v0.1.1-` (末尾ハイフン) を push しており、 3 OS とも build で落ちた (`version must be a semver string`)。Extract の段で検めていなかったので、 cargo test を終えた後 10 分以上たってからの失敗だった。**検査は入口に置く** — 落ちるなら数秒で、原因が分かる文言で。 semver の検査を足し (手元の bash で Red/Green)、誤タグを消して `v0.1.1` を打ち直した。 2 回目は 3 OS green。macOS のログで署名 → 公証 Accepted → staple → dmg 署名を確認。**証明書の取り込みはこの実ビルドが初検証**。 Release `v0.1.1` は draft で 7 点。アプリのコードは変わっていない (版番号と workflow だけ)。 **次**: Homebrew の tap に cask を足す (`betyourluck/homebrew-tap`、`verify-cask.yml` の `spctl` を本命に)。 ## 2026-09-13 (午後・続) — Homebrew tap に載った ユーザーが `v0.1.1` を publish。tap に cask を足し、`verify-cask.yml` を実機ランナーで回して `spctl -a -vv` の `accepted / source=Notarized Developer ID` まで確認した (初回で通過)。 これで **次の主題「Mac の Homebrew 対応」は閉じた** — `brew install --cask betyourluck/tap/apppromovideo`。 朝の問い「Apple での登録はどうすればいいか」から、登録済みの発見 → secret 5 つ → 公証の資格情報の検証 → v0.1.1 の 署名ビルド (誤タグで 1 回落ちた) → publish → cask → Gatekeeper の受け入れ確認まで、1 日で通した。 通ったのは Fuseforks が同じ道を先に歩いて台帳に残していたからで、こちらで新しく解いた問題は semver 検査の 1 つだけ。 ## 2026-09-13 (夕) — v0.1.0 の Release を削除、Upload artifacts を撤去 どちらもユーザー裁定。`v0.1.0` は未署名版なので draft のまま消した (タグは残す)。`Upload artifacts` は Release Assets の 7 日限りの複製で、Fuseforks が private 時代に 500 MB の枠超えでビルドを落とした型 — このリポジトリは public で枠の実害は無いが、 同じ形は同じ理由で置かない。撤去した名前で台帳を grep し、spec 01 の「残り」1 行を回収した。 ## 2026-09-13 (夕・続) — winget を提出 ユーザー指示で `Outcasts.AppPromoVideo` 0.1.1 を winget-pkgs へ New-Package 提出 (PR #434054)。 Fuseforks / Lorekeel の台帳をそのまま継承 (MSI のみ・`InstallerLocale` 無し・`Scope: machine`)。 `ProductCode` は MSI を落として COM で読み、SHA256 は digest と突合。`winget validate` 通過 → `wingetcreate submit --token`。 マージまでは README に書かない。 ## 2026-09-13 (夜) — rev48: 見張りが止めた理由を進捗に出す ユーザーが配布ビルドで agy を試し、`view_file` の直後に見張り (`run_command`) で止まった。生ログを読むと `view_file` は `state: ERROR` — 同日に入った Gemini プラグインの PreToolUse hook が引用符ごとのパスで node を呼び、全ツールが失敗していた。 見張りは設計どおり。見えていなかったのは 1 手目の失敗で、`ERROR` の tool 行を進捗に出していなかった (rev48 で修正、fixture で固定)。 **止める機構の隣に、止める前に何が起きたかを置く。** agy 側の処方はプラグインの無効化で、ユーザーに提示。 台帳の書き込みで cwd が `app/src-tauri` に残る事故を**また**踏んだ (並列の backend 検証)。書けていないことを git status で見てから絶対パスで書き直した。 ## 2026-09-13 (夜・続) — rev49: 実行中を動かして見せる ユーザー (配布ビルドのスクリーンショット)「実行中は画面が固まっているように見える」。回転は定義自体が無く、中央ペインは実行中も案内のままだった。 `runPhase.ts` (段 / 経過時間の純関数、9 本 Red→Green) + `.spin` + 中央の実行中ブロック。ユーザーの `tauri dev` の vite にブラウザから接続し、 ストアに running と進捗を注入して実測 — 最初はペインが hidden で `currentTime` が 0 のまま (rev34 の同型)、前面に出して測り直したら進んだ。 狭い幅で段の名前が折れたので nowrap + 段ごとの折り返しに。vitest 118 → 127。**ユーザーが Tauri の実画面で目視 OK** (23:46、1920 幅・画像 off で 3 段)。 ## 2026-09-14 — rev50: 費用の chip に「LLM」の印 前日のユーザーの疑問「画像を生成していないのに費用?」の回収。chip は数字と USD だけで、中身が解析 + 構成の LLM 費用だと書いていなかった (画像生成は元から数えていない — 契約の `RunStats.cost_usd` の注記で確認)。`runHeaderStats` が `LLM … USD` とホバーの説明を組み立てる形にし、 `runs.test.ts` 1 本で Red → Green (vitest 127 → 128)。ユーザーの vite にブラウザから接続し、変更が届いていることを先に確かめてから、 ストアに注入して chip の文字と title を読んだ。履歴の表の列「費用」も同じ中身だが、依頼の範囲外として据え置き。 足場の確認で 3 本を並べた際、cwd が `app/src-tauri` に残った (4 回目)。今回は環境の通知で読み違える前に気づいた。 `cd /abs && …` はそのコマンドだけを守り、次のコマンドには残る — 並べる全部に `cd` を書く。 ## 2026-09-14 (続) — rev51: リポジトリ欄のプレースホルダー / agy の再試行 ユーザー指示で、リポジトリ欄の入力例を `D:\Github\my-app` から「ここにパスを入力して下さい」へ (en / zh-CN も案内文に)。 文言のみの変更で、テストは足していない。 同日、ユーザーが agy の PreToolUse hook (Gemini プラグイン) を無効にした後の再試行が通ったことを確認 — rev48 の診断 (原因はアプリ外) の裏付け。 ## 2026-09-14 (続) — rev52: agy には Anthropic の鍵を渡さない ユーザー (設定画面のスクリーンショット)「OAuth ログインを使うが agy でも同じ文章で、agy に ANTHROPIC_API_KEY を送ると誤認させそう」。 読むと誤認ではなく挙動として成り立っていた — 子 CLI は `ANTHROPIC_*` を引き継ぎ、外すかどうかは種類を問わずスイッチ次第だった。 選択肢 3 つを出し、ユーザーは「agy ではスイッチを隠して常に外す」+「診断とログの語も直す」を選んだ。 `env_remove_for` / `auth_log_lines` / `usesAnthropicAuth` の 3 つを、今の挙動を写したスタブで Red にしてから実装 (各 2 本、agy 以外の本は Red の時点で通過)。 並べた検証のうち vitest の 1 本だけ `cd` を書き忘れて何も出力されず、`cd` を付けて取り直した — 同日に台帳へ足した処方を、その場で自分が破った。 スイッチの代わりに出した「agy には Anthropic の鍵 (…) を渡しません」の 1 行は、ユーザー判断「わざわざ出す必要はない」で撤去 (キーも 3 言語から削除)。進捗ログの agy の 1 行は残した。 続けて、その進捗ログの 1 行も撤去 (ユーザー判断「agy なのにわざわざ Anthropic の話を出すのは不自然」)。agy の run では認証の行を 1 行も出さない。テストを「0 行」に書き換えて Red → Green。 コミット `aeb3484` の後、`i18n.ts` の差分が入力例 3 行にしては多い (6 追加 / 9 削除) ことに気づいて中身を見たら、辞書から 1 行を消す置換で **直前の改行まで消していた** — 3 言語とも `settings.oauthOnly` の値と `settings.extraArgs` が同じ行に並んでいた。カンマで区切られるので 型検査もテストも通り、網には掛からない。2 行に戻し、rev50 時点との差分が入力例の 3 行だけになったことを確かめて追いのコミットにした。 **行を消す置換は、置換後の前後の行を見る。差分の行数が変更の大きさと合わない時は中身を読む。** ## 2026-09-14 (続) — rev53: aider / custom も Anthropic の表示を出さない ユーザー「aider やカスタムでも同様にして欲しい」。agy と完全に同じ (鍵を常に外す) にすると aider が壊れる — aider は `ANTHROPIC_API_KEY` を 環境変数から読む (公式文書を WebFetch で確認)。回避の `--anthropic-api-key` は鍵を `.invocation.json` に残す。選択肢 3 つを出し、ユーザーは 「表示だけ隠して鍵は引き継ぐ」を選んだ。スイッチは claude だけになり、aider / custom では保存値も効かせない (見えない設定で挙動を変えない)。 3 層とも新しい期待を先に書いて Red → Green。ブラウザで 4 種類の設定画面を読んだ (claude だけスイッチと診断 5 行)。 vitest を並べた検証で**また `cd` を書かず**に何も出力されず、取り直した (同日 2 回目)。処方を知っていても、並べる時に 1 本だけ書き落とす。 ## 2026-09-14 (続) — v0.1.2 のタグ ユーザーが `v0.1.2` を push (`1535af0` = rev53)。CI は 3 OS とも green、ログで署名 → 公証 Accepted → staple を確認、Release は draft で 7 点。 リポジトリの版番号は 0.1.1 のままタグが打たれたが、`build.yml` の `Sync app version to tag` がビルド時に `tauri.conf.json` をタグへ揃えるので 配布物は 0.1.2 の名前で出ている。main 側はユーザー指示で後から 3 か所 + `Cargo.lock` を 0.1.2 に揃えた (タグは動かさない)。 Release の説明に「macOS 版は Apple Silicon (aarch64) のみ」を足した (ユーザー承認)。最初は `$TMPDIR` が空で notes ファイルを作れず、scratchpad の絶対パスで作り直した。 v0.1.1 の説明も読んだ — aarch64 の注記は無く、テンプレートの作者向けメモ (Customization Notes / アレンジのポイント) と「(v0.1.0)」の表記が残っている。書き換えは未承認なので触っていない。 リリースノートを英日で書いて draft に反映した (rev48〜53)。反映前に実装と突き合わせて 2 点直した — 進捗ログは UI が英語でも日本語で出るので 英語版にも `ツール失敗: …` と実物の文字を書く / 参照画像の段は画像生成 ON の時だけ。ユーザーが publish (05:24 UTC)。 publish 後に dmg の digest が draft の時と同じことと URL が 200 を返すことを確かめ、tap の cask を 0.1.2 に (`3de4ddd`)。verify-cask を実機で起動。 結果 (run 34809584095): インストール → 署名 → staple → `spctl` が `accepted / source=Notarized Developer ID` まで success。入ったのは 0.1.2 の dmg (ログの URL で確認)。 `brew audit` は「exception while auditing: GitHub API rate limit exceeded」で中断 — runner の未認証 API の上限で、cask の指摘は出ていない。 step は continue-on-error なので job は success と出る。**success の表示と、検査が実際に走ったかは別** — informational の step は中身を読む。 v0.1.1 の Release 説明を 3 か所直した (ユーザー承認): 英語冒頭の `(v0.1.0)` → `(v0.1.1)` / テンプレートの作者向けメモ「Customization Notes」「アレンジのポイント」を削除。 日本語冒頭とメモ内の版番号はユーザーが先に直していた。置換は各 1 回だけ当たることを検めてから反映し、元の本文は手元に控えた。反映後に 3 語とも 0 件。 ## 2026-09-14 (続) — 未目視の 8 項目をユーザーが確認 Tauri の実画面で未目視だった rev43・45〜47・50〜53 を、すぐ確認できる順に 8 項目のリストにして渡し、ユーザーが全部確認した (①入力例 ②設定の認証を 4 種で切替 ③使い方を見る ④ブランドの標 ⑤右クリック・F5・選択 ⑥費用の表示 ⑦シーンの並び替え・複製・削除 ⑧実 run の進捗ログ)。 ⑤は配布ビルドでしか効かないので、確認は配布ビルドで行われた。⑧で agy の run から認証の行が消えていることも見えている。 残る未目視は rev44 の案内の各歩の中身と、rev36・37 の GUI からのプロンプト保存。aider の実機 (鍵が読まれるか) は未確認のまま。 続けて rev44 もユーザーが確認: 4 歩とも、窓を最大化しても小さくしても位置に問題なし、ライトテーマでも問題なし。残る未目視は rev36・37 のプロンプト保存だけ。 続けてユーザー「プロンプトは保存されています」。台帳に「2 ファイルが揃う」と書くため手元でも確かめた — runs.json の 13 件の更新時刻を並べると、 今日の 2 件 (`20260914-071122` / `-022934`) で作成の後に promo.json と scenes.md が同じ秒に書き直されていた。シーン編集 (rev43) も同じ 2 つを書くので時刻だけでは区別できず、 新しい方の中身を突き合わせて 5 シーンの motion_prompt / video_prompt がすべて scenes.md に載っていることを確認した。 途中、runs.json を 1 本目のスクリプトでは `runs` キーの下のリストとして読めていたのに、2 本目で同じ剥がしを書き落として落ちた。 9/12 と 9/9 の古い 2 件は promo.json だけが後から更新されている — 焼き直し (見出し・はめ込み) は promo.json だけを書くのかもしれないが、未確認。 ## 2026-09-14 (続) — rev54: scenes.md をいつも揃える / 表の番号 上の「未確認」をユーザーの指示で調べた。理由は設計どおり — 焼き直しは上書きを毎回 promo.json に書くが scenes.md はコピー文を変えた時だけ、 スナップショットの追加は promo.json だけ。古い 2 件の中身は scenes.md と一致していた。**ただし表の snapshot 番号がずれていた** — plan の番号を書いていて、はめ込みで選び直した番号を見ていない (実データで 4 シーン)。`scenes_markdown` が上書きを受け取らない作りなので、 書く回数を増やしても直らない。ユーザー「両方とも直して」→ `plate_snapshot_index` を promo_core へ移して表に使い、 promo.json を書く全経路を `write_run_files` (2 ファイルを揃えて書く) に寄せた。形だけ変える段を先に緑で通してから Red を取った。 backend の Red を 1 回 `cd` 無しで走らせて root で 0 本選ばれ、何も観測できていなかった — 取り直して 2 本とも「scenes.md が書かれていない」を見た。 結果ペインのシーンの chip も plan の番号を出していて同じずれがありうるが、今回は触っていない。 ## 2026-09-14 (続) — rev55: 結果ペインの snap 表示 / 番号は 1 始まり 上の chip をユーザーの指示で調べた。同じずれ (plan の番号で上書きを見ない) に加え、**数え方が混ざっていた** — chip と scenes.md は 0 始まり、 編集ダイアログとファイル名 `snapshot_01` は 1 始まり。実データの scene 3 は chip が `snap 0`、ダイアログは 3 枚目、実際に貼ったのも 3 枚目。 直し方を 2 案出し、ユーザーは「画面側に同じ規則の関数 + 1 始まりに統一」を選んだ (backend が番号を返す案は promo の型が変わるので後続)。 `plate.ts` に `plateSnapshotIndex` / `snapshotChipLabel` を置き、Rust と同じ 8 ケースをテストに写した。chip と `CaptionEditor` の `hasPlate` が同じ関数を通る。 scenes.md の表も `i + 1` に (rev54 で入れた書き方を当日中に変えた)。TS と Rust の両方で Red → Green、ブラウザで 4 通りの chip を読んだ。 ユーザーが Tauri の実画面で 2 回確認した — 1 回目は上書きの無い run (`product · snap 1` / `mood`)、2 回目は面を足した mood のある run (`mood · snap 1` / `mood · snap 3`)。 ## 2026-09-14 (続) — Qiita 記事の下書き / README の食い違いを直す ユーザーの依頼で Qiita 記事の下書きを `docs/qiita_apppromovideo.md` に書いた (ザリ・ロブステル名義、Fuseforks / Lorekeel の 2 記事を `https://qiita.com//items/.md` で原文取得して構成と語り口を合わせた)。スクショはユーザーが撮るので `📸 TODO` の置き場所だけ置いた。 中身は README ではなくコードと台帳から取った — README を読んだら台帳と食い違っていたため。書く前に根拠のない一文 (「MiniMax なら 16:9 がおすすめ」) を削った。 続けてユーザーの指示で README 日英を直した: ①Veo / Sora を消す (rev3 でコピー先から外したのに README に残っていた。実物のコピー先 MiniMax / 汎用 t2v に置換) ②画像生成プロバイダの表 — ComfyUI と OpenAI を「✅ 実機検証済み」としていたが、実機の通しは Gemini だけ (spec 01 Phase C)。接続先も実装 (`generateContent` / `images/generations`・`images/edits` / upload → `/prompt` → `/history` → `/view`) に合わせた ③出力フォルダの構成例 — `images/ref_001.png` / `repobrief.txt` は実在しない。実物 (今日の run と契約 `ExportPackage.layout`) の `promo.json` / `scenes.md` / `scene_NN_ref_MM.png` / `base/` / `snapshots/` に ④動作要件の LLM CLI に `agy` / カスタムを足し、`agy` の隔離の弱さを一言添えた。README は会話の外で作られた文書で、機能の追加に追従していなかった。 ## 2026-09-14 (続) — rev56: 配布ビルドのコンソールの窓 ユーザー (v0.1.2 の exe、スクリーンショット)「release で exe を実行するとコンソールのウィンドウが出る。そういうものか」。窓のタイトルは「claude」で空。 真因は `windows_subsystem = "windows"` が配布ビルドにだけ効くことと、子プロセスの起動に `CREATE_NO_WINDOW` が無かったこと — dev ではアプリが コンソールを持つので出ず、配布物でしか現れない不具合を dev だけで検証していた。Fuseforks の MCP 起動に同じ処方があり、それに揃えた。 起動を `cli_runner::no_window` に一本化し、`Command::new(` を直に書いた行を数える網で Red (10 か所) → Green。起動フラグは読み戻せないので、 窓が出ないことは次の配布物でユーザーが確かめる (ユーザー判断)。 ## 2026-10-02 — 後回しの 2 件を閉じる (snapshot 番号の式 / frontal の費用) 18 日ぶりの再開。足場 crates 197 / backend 30 / vitest 135 + build green。前回からの変化はユーザーの README 動画差し替え 2 本だけ。 ユーザー「後回しにしているものは片付けられるか」→ 3 件を調べた。 ①**snapshot 番号の式を 1 つにする件**: TS の `plateSnapshotIndex` は結果ペインの chip と編集ダイアログの `hasPlate` の 2 か所で使う。 chip は backend の 7 経路の戻り値に番号を添えれば置き換えられるが、`hasPlate` は**適用前の選び直し**を見てつまみの出入りをその場で決めるので、 backend に寄せると往復が挟まる。寄せても式は残り、減るのは使う場所 1 つ。→ ユーザー「今のまま閉じる」。`plate.ts` の注釈を書き換えた。 ②**aider の実機**: aider は未導入で、確かめるには本物の鍵で走らせる必要がある。私だけでは閉じられない (持ち越し)。 ③**frontal の費用**: 前回まで「live 記録 0 件」と書いていたが、`runs.json` を読むと 9/11 以降の run に `run_stats` が溜まっていた (15 本、費用あり 13 本。agy の 2 本は費用なし)。 | mode | 本数 | 費用 平均 (範囲) | 構成の試行 | |---|---|---|---| | frontal (AppPromoVideo、9/12〜14) | 8 | 1.283 USD (0.911〜1.674) | 2,3,2,2,2,1,1,3 (平均 2.0) | | perspective (Fuseforks、9/9〜11) | 5 | 1.394 USD (1.007〜1.838) | 1,1 (古い 3 本は記録なし) | 1 回で通った run どうしでも 0.911 / 1.316 (frontal)、1.007 / 1.252 (perspective) とばらつき、2026-09-08 の約 1.5 倍 (3 本) はこの幅と区別できない。 frontal の違反種別 (全試行の合算): `product_backdrop_draws_screen` 4 本 (mode に依らない検査) / `product_backdrop_angled` 2 本 (frontal 固有、単独は `20260914-071122` の 1 本) / `image_prompt_mentions_input` 1 本。**対照実験ではない** — リポジトリとモデル (`claude-opus-5` / `[1m]`) が mode と重なっている。 既定 frontal は費用ではなく動画の落ち着きで決めたので、測り直しても判断は変わらない。→ ユーザー「台帳で閉じる」。 **反例・私の誤り**: 前回の台帳と索引記憶は「live 記録 0 件」を 9/9 から書き写し続けていた。記録はその 2 日後から溜まっていたのに、 数える手段 (rev15) を作った後で**数えに行かなかった**。持ち越しの理由に「材料が無い」と書いたら、書いた本人が材料の有無を見直す日付を持たない。 集計の途中、python の文字列のバックスラッシュがシェル経由で潰れて 1 回落ちた (2026-09-13 と同じ機序。`os.path.normpath` で回避)。 ## 2026-10-03 — rev57: はめ込みの大きさ (等倍に目盛り / 越えた分は拡大) ユーザー「はめ込み画像は一定以上は大きくできない。0.70 以上拡大できない理由を」。調べると設計どおりの頭打ちだった — 合成の縮尺は `min(枠 / スクショ, 1.0)` で等倍より大きくしない (rev6)、canvas は生成時に既定の枠でスクショが等倍になる大きさにしてある。 ユーザーの今日の run `20261002-133028` で canvas 2824×1614 / スクショ 1920×1032 → 0.680 から上は変わらない (4 枚目 963×320 は 0.341)。 ただしつまみが嘘をついていた (0.95 まで動く / 既定は 0.78 の決め打ち)。直し方を 2 案 (正直にする / 拡大も許す) 出し、ユーザーは「組み合わせて」。 形だけの段を緑で通してから Red (compose 2・pipeline 1・TS 4) → Green。promo_core の 1 本は形の段で通ってしまい Red を観測していない。 backend の 1 本は配線の後に書いたので、配線を壊して Red を見てから戻した。ブラウザのペイン (ユーザーの vite) で `invoke` を差し替えて表示と送る値を測った。 スクリーンショットはペインの描画が間に合わず (タイムアウト・切れた絵) 撮れなかった — 数値で確かめたので打ち切った。 **反例・私の誤り**: #19 で傾きに「つまみは実効値を指す」を当てたとき、同じダイアログの大きさのつまみには同じ問いを当てていなかった (既定 0.78 の決め打ちは rev11 から残っていた)。処方を当てる範囲を、報告された 1 本ではなく同じ入力の仲間まで広げる。 ユーザーが Tauri の実画面で確認した (「確認できました」)。 ## 2026-10-03 (続) — rev58: motion_prompt を MiniMax H3 の公式の作法に ユーザーが PV 品質を上げるプロンプトの調べもの (要約) を持ち込んだ。エージェントに一次資料を当たらせ、要の 2 点 (i2v の API 文書・H3 の公式ガイド) と 公式スキル 2 本は自分で原文を読んだ。要約の「MiniMax H3」は実在 (2026-07 末)。Hailuo 02 / 2.3 は角括弧のカメラ指示、H3 は文の中で種類 + 振れ幅 + 速さ。 要約の「i2v は動きだけ書く」は H3 の公式ガイドと逆 (最初の 1 コマを押さえてから動き) で、こちらの rev3 の規則も同じく逆だった。 ユーザー「H3。最新版に合わせたい / 公式の推奨を先に」→ 規則の書き換え・画面を固定する 1 文 (Rust が足す)・schema の説明文の Veo / Sora を外す。 形の段 → Red 4 → Green。crates 203。持ち越し: 見出しの引用 / 否定形 / **H3 の尺 4〜15 秒と 1 シーン 3 秒の食い違い** (ユーザー判断)。 **反例・私の誤り**: バックスラッシュを含む Rust の文字列をシェル経由の python で書こうとして構文エラー (2026-09-13 に台帳へ書いた罠の再発)。 スクリプトは丸ごと失敗して何も書かれなかったので被害は無く、Edit ツールで書き直した。**バックスラッシュを含む置換は最初から Edit / Write で。** ## 2026-10-03 (続) — rev59: 1 シーンの下限を 4 秒に H3 の尺 (4〜15 秒) と検査の下限 (3 秒) の食い違いに、ユーザー「A で」(今の構成のまま下限だけ合わせる)。`DURATION_MIN` 3 → 4、 指示文に「15 秒は 3 シーン」まで書いた。Red 2 → Green、crates 204。既存の run は変わらない (検査は構成の段だけ)。 ## 2026-10-03 (続) — rev60: ドロップで 4 枚並ぶ ユーザー「画像をドロップすると 4 重になる原因を調べて」。リスナーは 1 か所。Tauri / wry のソースを読み、ドロップ 1 回で送るのは 1 回と確かめた。 使い捨てのテストで「同時 4 回 → 4 枚 / 順番 → 1 枚」を測り、増幅器 (await 前だけの重複判定) と源 (非同期登録の外し損ね) の掛け算と結論した。 源の引き金 (HMR か) はユーザーに条件を聞いたが、ユーザーは両方を塞ぐ判断。Red 3 → Green、vitest 144。`store.listenProgress` に同じ形が残る (候補)。 **私の誤り**: 使い捨てテストの `console.log` が vitest で出ず、1 回空振りした (アサーションの差分で値を出して測り直した)。 ユーザーが Tauri の実画面で確認した (「問題ありませんでした」)。 ## 2026-10-03 (続) — rev61: 進捗ログのリスナーの競合 rev60 で残した同じ形を、ユーザーの指示で直した。登録中の印 (`listening`) を await の前に立て、失敗したら下ろす。Red 1 → Green、vitest 146。 実画面で二重になった観測は無い (呼ぶのは App.vue の 1 か所) — 形の予防。