v0.35.0
[0.35.0] - 2026-09-12
Highlight: スマホ・弱電波での操作性をまとめて立て直したリリース。通信が数秒切れただけで worktree 詳細画面が全画面エラーに落ちてポーリングごと止まる、通信断の chunk 読み込み失敗を「サーバ更新」と誤判定して入力中の内容ごと自動リロードする、WebSocket に heartbeat が無く half-open 接続のまま送信が黙って消える——という 3 つの「固まる」経路を塞ぎ、スマホに接続状態を表示する
useConnectivityを新設した。あわせてファイル面を広げ、2MB を超えるテキストファイルが 413 で閲覧すらできなかったのを読み取り専用で開けるようにしたうえで.txtを編集可能にし、スマホからも.md以外を編集できるようにした。実測では heartbeat が 30 秒間隔で届き、pong を返さない接続はサーバが 52 秒で切断、クライアントの half-open 検知は 75.1 秒、オンライン復帰からの再接続は 5ms である。
Added
- feat(mobile): スマホのファイル画面で
.txt/.yaml/.ymlも編集・保存できるようにした (#2507): モバイルの全画面ファイル面(FileViewerの unified screen)に入る条件がcontent.extension === 'md'決め打ちで、PC ファイルパネルがisEditableExtension()でエディタに回している.txt/.yaml/.ymlがスマホからは閲覧専用のままだった(#2506 で.txtを編集可能にしても、この分岐を広げない限りスマホには届かない)ため、判定をisEditableExtension('.' + content.extension)に置き換え、実態に合わせてisUnifiedMarkdown→isUnifiedEditable、markdownMode→screenModeにリネームした。プレビューを持つのは.mdだけなので閲覧/編集の切替 UI(markdown-file-mode-switch)は.mdのときだけ描画し、他の拡張子は編集モード直行(ファイル内検索の間だけ、行アンカーが要る行番号つきソース表示に切り替わる)。全画面表示・閉じる導線・未保存時の確認(requestClose())・アクションシートは拡張子によらず従来どおり動く。.html/.htmは判断のうえ既存のHtmlPreviewMobile(Source/Preview タブ+サンドボックス信頼度セレクタを持つ閲覧面)に据え置き、スマホでの HTML 編集は PC のHtmlPreviewに委ねる(切替 UI の二重ネストとレンダリング済み HTML を見る手段の喪失を避けるため)。#2505 のreadOnlyが真のファイルはエディタにも保存導線にも入れず、理由(readOnlyReason.message)を警告バナーとしてモーダル/全画面の両方に表示する - feat(files):
.txtをブラウザ上で編集・保存できるようにした (#2506):EDITABLE_EXTENSIONS(src/config/editable-extensions.ts)に.txtを追加し、EXTENSION_VALIDATORSに 2MB 上限(TEXT_MAX_SIZE_BYTES)のエントリを足した。この 1 行で PUT の書き込み許可(isEditableFile())・PC ファイルパネルのエディタ分岐(FilePanelContent)・新規ファイルダイアログの拡張子候補(NewFileDialog)の 3 つが同時に.txtを受け入れる。.md以外はMarkdownEditorが text モード(プレビュー無し)で開くため、.txtはプレーンテキストエディタになる。2MB を超える.txtは #2505 の経路に乗り、本文を 200 で返したうえで読み取り専用(readOnly: true)として仮想化ビューアで描画されるので、大きなログやダンプが textarea へ一括描画される崖には落ちない。.env.txtのような機微パスはfindSensitivePathSegment()が全 HTTP メソッドで拒否したまま(#2014)で、NULL バイト検出もvalidateContent()の共通処理として効く。あわせてMarkdownWithSearchがファイル内検索中のCodeViewerにextension="md"を固定で渡していたのをcontent.extensionに直し、.txt/.yamlが Markdown としてハイライトされる崩れを解消した - feat(chat): 圏外で送ったメッセージを「送信待ち」として保持し、サーバ到達を確認できた時点で1回だけ自動再送する (#2503): 通信が測定上落ちている間は送信失敗を
errorにせず 30 秒タイムアウトも止め、復帰時は再取得でサーバ側に届いていないことを確かめてから再送する(届いていた・送信中・応答済みのものは送らない)。自動再送は1メッセージ1回で、失敗すれば従来どおり手動の再試行/破棄に委ねる。PC 分割(TerminalSplitPaneContent)とスマホのチャット面(MobileTerminalTab)の両方に配線。 既知の問題: 実機受入テストで、オンライン復帰時の自動再送が発火しないことを確認している(#2535)。オフライン中に失敗表示にしない・タイムアウトで error に落とさない部分は期待どおり動作する。 - feat(files): サイズ上限を超えたテキストファイルを読み取り専用で開けるようにした (#2505):
GET /api/worktrees/:id/files/:pathは編集可能拡張子が上限(.html/.htmは 5MB、その他は 2MB)を超えると 413FILE_TOO_LARGEを返し閲覧すらできなかったが、本文を 200 で返したうえでreadOnly: trueと理由(readOnlyReason:code/message/limitBytes/sizeBytes)を添える形に変えた。上限は「読めない」ではなく「保存できない」の意味になり、PUT 側の上限拒否(validateContent()のmaxFileSize)は従来どおり。FilePanelContentは編集分岐(HTML プレビュー・Markdown/テキストエディタ)に入る前にreadOnlyを見て仮想化ビューアCodeViewerに回すため、数万行のファイルが textarea へ一括描画されてスマホがフリーズする経路も塞がる。画面には理由が警告バナーとして表示される - feat(mobile): スマホに接続状態(オフライン/再接続中)を表示 (#2501):
useConnectivityフックがnavigator.onLine・共有WebSocketのstatus・サーバ到達性プローブの3信号を1つの判定にまとめ(onLine === trueは「オンラインの確定」には使わず、キャプティブポータル等で「オンライン」と誤表示しない)、モバイルシェル先頭にMobileConnectionBanner(flex列内の細いバー。composer やGlobalMobileNavを覆わない)を、デスクトップは既存ConnectionStatusIndicatorを同フックへ載せ替えて表示する。接続中は何も表示しない
Fixed
- fix(detection): Command Code の折り返し説明つき AskUserQuestion を multiple_choice として読み、status/履歴・通知/respond/Auto-Yes が同じ質問と選択肢を扱えるようにする (#2522): #2521 が認識だけして手動操作 fallback を出していた footer なしの質問画面を、新設の専用リーダ
src/lib/detection/tools/command-code/dialog.tsが読むようにした。罫線・タブ帯・厳密連番・単一❯の領域判定は #2521 のreadCommandCodeQuestionRegionをそのまま再利用し、オプション本体は共有のfindNumberedOptionBlockに渡して画面で直上の option へ説明行を空白 1 個で畳み込む(インデント 0/1/2・最終 option の下も同様)。質問はタブ行の直後から最初の option の直前までで、?/?は必須にしない — 罫線・タブ帯・上の TODOS 行が混入しない。Type something...は表示上の選択肢として残したうえでrequiresTextInput:trueにし(汎用TEXT_INPUT_PATTERNSは変更していない)、Auto-Yes の既定回答も手動の数字回答も拒否する。罫線が消える経路が核心で、detectPromptWithOptions・Auto-Yes のcaptureAndCleanOutput・/prompt-responseはいずれもstripBoxDrawingを通すため領域が常に null になっていた(stripBoxDrawingは 200 桁の U+2500 行をまさに消す)。そこで 1 tick の同じ capture から raw と clean の両方を保持して伝搬する配線に変更した(capturePollerFrame()新設、detectPromptOnCleanFrame(clean, tool, precomputedLines, rawFrame)に raw 引数追加、precomputedLinesは従来どおり cleanOutput の split のまま)。送信は既存submitMode:'answer_only'(数字で即選択される質問に遅延 Enter を足すと次画面まで確定してしまうため)。読めない質問画面(欠番・重複・上限超過・最終 option 直下の説明過多・複数選択チェックボックス)は #2521 と同じwaiting / command_code_selection_list / hasActivePrompt:falseの手動操作 fallback へ落とし、promptData も自動回答も generic parser の部分リストも生成しない。手動/prompt-responseは HTTP 200・success:false・unsupported_dialog_layoutでキー送信 0 回、質問が消えていれば従来のprompt_no_longer_active。COMMAND_CODE_VERIFIED_AGAINSTは据え置き(根拠は報告された 1.53.0 の匿名化 capture と 1.53.1 パッケージの静的確認のみで、実機 probe は未実施)。 - fix(api): クライアントの fetch にタイムアウト・リトライ・オフライン即時失敗を入れた (#2499):
fetchApi()/ 新設fetchApiResponse()が GET 10秒・送信系 30秒・ポーリング 8秒で打ち切り、AbortErrorをkind(timeout/network/offline/aborted/http)つきのApiErrorに正規化する。冪等 GET のみ jitter 付き指数バックオフで最大2回リトライし(総予算20秒で打ち切り)、POSTなどの送信系はリトライ対象外なので二重送信しない。navigator.onLine === falseのときは fetch を出さず即失敗。詳細画面・ファイル取得・ターミナル出力の各ポーリングと FilePanelContent のファイル読み込みを共通ラッパへ寄せ、各リクエストの成否を #2501 のreportServerReachability()へ報告して接続バナーの精度を上げた。タイムアウト値とリトライ定数はsrc/config/api-timeout-config.ts。 - fix(ui): 通信断による ChunkLoadError を「サーバ更新」と誤判定して自動リロードするのをやめた (#2500):
ChunkLoadErrorをnavigator.onLineでnetwork/buildに分類し(classifyChunkError())、オフライン時は自動リロードせず「接続が切れています」を表示したうえで、#2501 のsubscribeServerReachability()/probeServerReachable()を使ってサーバ復帰時にユーザー操作なしでreset()再試行する。オンライン時の本物の版ズレは従来どおりCHUNK_RELOAD_GUARD_MSつきで1回だけ自動リロードする - fix(detection): Command Code の折り返し説明付き AskUserQuestion が
readyと誤判定され、チャットに操作カードが出ずwaitが偽完了する問題を修正 (#2521):AskUserQuestionはフッタ(ヒントバー)を描かないため/usage・/modelの既存 2 分岐はどちらも一致せず、さらに選択肢 1 の説明が折り返されると継続行が空白 1 個で始まる(answer).)ため共有detectPromptはそこでスキャンを止めて選択肢 1 に到達しない。結果フレームは step 3 の汎用 composer チェックへ落ち、COMMAND_CODE_PROMPT_PATTERN(=^❯(\s*$|\s+\S)) がダイアログ自身のカーソル行❯ 1. Prepare worktrees + dispatch (Recommended)に一致してready/input_promptを publish していた(2026-09-12 実測・Command Code 1.53.0 と報告されたセッション・200x1000 の pane で選択画面が約 12.1 分間未検出)。そこで browser-safe leaf のselection-shape.tsに領域判定readCommandCodeQuestionRegion()を追加し、「最後の適格な罫線(純粋 U+2500・40 桁以上)を上端」「その直下がタブ列● Dispatch | ◯ Review(固有名ではなく選択/未選択マーカーと区切りの構造で読む)」「空でない質問本文」「1.からの厳密な連番 2 個以上」「領域内に❯が 1 個だけかつ選択肢行の上」「検索/filter 入力・/modelfooter・dismiss-only footer・他 turn の生成/完了 UI を含まない」の全成立でのみ陽性とした。status(waiting/high/command_code_selection_list/hasActivePrompt:false/evidence:positive)・extractCommandCodeSelectionListFrame()の crop・ChatSurfaceの数字キー抑止が同一の読み取りを共有するので、カードの行・待機判定・ボタンが食い違わない。commandmate waitは既存の選択リスト経路で exit 10 /type=selection_list/options=[]、--on-prompt humanは応答待ちを継続する。汎用prompt-detect-multiple-choice.tsの条件は緩めていないし、readSelectionListShape/shouldOfferOptionNumbersの全 CLI 共通規則も不変(claude/modelのs扱い、codex picker の 7 個の数字ボタンは維持)。promptDataの生成・respond/ Auto-Yes の復旧・COMMAND_CODE_VERIFIED_AGAINSTは本件の対象外(#2522)で、この画面の数字キーは「押せる」と実測できていないためカードには出さない。fixture はtests/fixtures/command-code-askuserquestion-2521/(実キャプチャ由来の匿名化 200x1000 版+合成の最小例+採取日・geometry・加工箇所の README)。リポジトリの全.txtを走査するdialog-frame-2326.test.tsの等値ピンは、この 2 件だけが増えることを毎回再導出する。 - fix(ws): WebSocket に heartbeat を入れ、half-open 接続を検知して再接続する (#2502): サーバが 30 秒ごとに ping と application-level heartbeat フレームを送り、pong の無い接続を次の周期で
terminate()してルーム・ターミナル購読ごと解放するようにした。クライアントは受信の途絶(75 秒)で half-open と判定して自らclose()しstatusをdisconnectedに落とすため、ターミナル以外の面(サイドバー・履歴・版不一致検知)も通常のポーリング間隔へフォールバックする。あわせて再接続バックオフに ±20% の jitter を入れてサーバ再起動時の同時殺到を防ぎ、onlineイベントで即時再接続するようにした(従来は最大 30 秒待ち)。間隔・タイムアウト・jitter 比・close code はsrc/config/websocket-config.tsに集約。 - fix(ui): worktree詳細画面がポーリング1回の通信失敗で全画面エラーに落ちる問題を修正 (#2498):
fetchWorktree()の失敗を初回ロード失敗(従来どおりErrorDisplay+再試行ボタン)とポーリング失敗(画面を保持したまま連続3回で控えめな再接続バナー)に分離し、ポーリング effect のif (loading || error) returnからerrorを外してエラー中も回り続けるようにした。初回ロード失敗にはuseWorktreesCacheのINITIAL_LOAD_RETRY_DELAYS_MS相当のバックオフラダー(2s/5s/10s)を移植し、成功1回で全ての失敗判定を解除する。あわせてdetectAuthRedirect()/detectNonJsonBody()を通し、セッション期限切れ(401 //loginリダイレクト)をUnexpected token '<'ではなく再ログイン導線として表示するようにした。電波が途切れても画面とcomposerの下書きが失われず、復帰後はユーザー操作なしにポーリングが再開する。