Skip to content

v0.39.1

Choose a tag to compare

@Kewton Kewton released this 20 Sep 05:49
· 24 commits to main since this release
c8cae3b

[0.39.1] - 2026-09-20

Highlight: 静的検査の穴を塞いだリリースです。npm run lint はこれまでディレクトリの明示列挙で、足し忘れるたびに穴が開いていました(tests/ 1,849 ファイル → scripts/ 45 + bin/ → ルート直下・public/・website/ と 3 回)。列挙をやめて eslint . + ignorePatterns に反転し、除外してよいのは「git 管理下のファイルを 1 件も含まないビルド成果物」だけという根拠でガードに固定しました。あわせて shellcheck(0.11.0 を CI で pin)を導入して .sh 21 本を検査対象にし、CI の Type Check ジョブに残っていた continue-on-error: true を外しています。結果としてリポジトリ全体が npm run lint -- --max-warnings 0 / npm run lint:sh / npx tsc --noEmit すべて exit 0 になり、job レベルで結果を握り潰すジョブは 0 件になりました。PC 向けの設定モーダル(#2706〜#2709)と、実ブラウザでしか観測できなかったモーダルの外側クリック欠陥の再発防止 e2e(#2715 / #2716)も含みます。

Added

  • chore(lint): scripts/ の shell スクリプト 18 本に shellcheck を導入 (#2734): npm run lint は ESLint なので .sh を 1 本も読まず、package.json / .github/workflows/ / .commandmate/verify.yaml のどこにも shellcheck が無かったため、サーバーの起動・停止(stop.sh / stop-server.sh / start.sh)と .env 読み込み(load-env.sh)が完全に無検査だった。npm run lint:sh(shellcheck -x -P scripts --severity=style --exclude=SC2086,SC2317,SC2116 $(find scripts -name '*.sh'))を追加し、CI の Lint ジョブに npm run lint:sh を走らせる専用ステップを 1 つ足した(npm run lint に && で連結すると shellcheck の無い環境で .commandmate/verify.yaml の lint ゲートごと落ちるため独立スクリプトにしてある)。対象指定を scripts/*.sh ではなく $(find scripts -name '*.sh') にしているのは、直下の glob では scripts/lib/port-pids.sh(stop.sh / status.sh が source する -sTCP:LISTEN の PID 判定、#2473)を含むサブディレクトリの 3 本を取りこぼすため。-x -P scripts は source 先を追わせる設定で SC1091 の 12 件が消えるが抑止ではない。唯一の warning だった scripts/load-env.sh:67 の SC2163(export "$line")は理由コメント 1 行 + # shellcheck disable=SC2163 の 2 行で抑止した(ShellCheck は # shellcheck <key>=<value> しか解釈しないので ESLint の -- 理由 記法を同じ行に書くと SC1073 / SC1072 で落ちる。ロジックは 1 文字も変更していない)。残る note 26 件(SC2086 23 / SC2317 2 / SC2116 1)は --severity=warning で丸ごと黙らせるのではなく 3 コードを名指しで除外して将来の info / style の指摘は拾えるようにし、修正自体は別 Issue に回した(SC2086 の $PIDS は単語分割されて個別の PID 引数になるのが意図であり、クォートすると停止処理が壊れるため本番の停止経路を bash 配列へ書き換える作業になる)。CI ステップは shellcheck を install せず command -v shellcheck || exit 1 で fail-closed にしている(self-hosted 実機は 0.11.0 同梱、fork PR が落ちる ubuntu-latest イメージも同梱済み)。新規ガード tests/unit/guards/lint-sh-scope.test.ts が shellcheck を実行せずに設定側を固定する(-x / -P scripts / --severity=style を含み --severity=warning を含まないこと、--exclude 集合が 3 コードと完全一致、scripts/*.sh を含まず find scripts -name '*.sh' の形であること、scripts.lint に shellcheck が入らないこと、CI の当該ステップと job に continue-on-error が無いこと、そして「サブディレクトリに .sh が実在する」陽性対照)。

  • chore(lint): lint 対象に tests/ を追加する土台を作る (#2719): npm run lint を eslint src tests --ext .js,.jsx,.ts,.tsx に広げ、これまで一度も ESLint がかかっていなかった tests/ を対象に含めた。あわせて .eslintrc.json の overrides に tests/** エントリを追加し、テストに当てるのが誤りである src 専用ポリシー3つ(i18n 文言直書き検出 #1271 / tmux ゲートウェイ強制 #1922 / Next のページ規約)を恒久的に off、実債務6ルール(no-unused-vars / no-explicit-any / rules-of-hooks / no-require-imports / no-this-alias / prefer-const)を段階解消のため一時的に warn にした(npm run lint は exit 0 のまま)。CI の Lint ジョブから continue-on-error: true を外し、lint の結果が実際に PR を止めるようにした。分類は tests/unit/guards/lint-tests-scope.test.ts で固定し、warn エントリの削除は当該ルールの指摘が tests/ から実際に消えている場合のみ通るようにした。

  • test(e2e): モーダルの外側クリックを実ブラウザで固定する再発防止テストを追加 (#2716): #2715 が直した「背景クリックでモーダルが閉じない」欠陥は、jsdom がヒットテストを行わないため単体テスト 12 ケースをすり抜けて 7 か月以上残っていた。tests/e2e/modal-outside-click-2715.spec.ts を追加し、実ブラウザで「パネル外の座標を document.elementFromPoint() が返す要素」と「その座標を page.mouse.click() で実際に押したときモーダルが閉じるか」を測る(外側クリックで閉じる/パネル内クリックで閉じない/内→外のドラッグで閉じない/外→内のドラッグで閉じない/Escape と閉じるボタンは閉じる、の 5 ケース)。「閉じない」側の 3 本は退場アニメーションの窓(EXIT_ANIMATION_DURATION_MS)を越えてから [role="dialog"] の存在と data-state="open" を測る(操作直後に toHaveCount(1) を置くと、閉じ始めていても最初のポーリングで 1 を返して assert が即成功し、変異注入で 5 本とも緑になることを実測した)。製品コードの変更は無し。

  • feat(settings): PC 用の設定モーダルを追加 (#2708): アプリ全体で 1 つだけの設定モーダル(src/components/settings/SettingsDialog.tsx)と、その開閉状態を持つ SettingsDialogContext を追加。モーダルは #2707 で個別 export したセクション部品を、左の縦カテゴリ一覧(一般 / 通知 / External Apps / About)+右のペインに並べ替えて描く(/more の 5 セクション積み上げ=SettingsPanel はページ専用のまま)。SettingsDialogProvider は AppProviders の ViewTransitionsProvider の外に置き、ページ遷移で開閉状態が落ちないようにした。ui/Modal は閉じている間 null を返し、Radix Tabs は非選択カテゴリを unmount するため、通知・外部アプリ・既定エージェントの fetch はモーダルを開いてそのカテゴリに入るまで走らない。useSettingsDialog() は Provider の外でも throw せず「開かないモーダル」を返す。辞書は common.settings.categoriesLabel(左カテゴリの tablist の aria-label)を ja/en に 1 キー追加。このIssue単体では開く入口が無く画面に変化は出ない(既存の設定ボタンとの接続は #2709)。

  • feat(sidebar): サイドバー下部に設定ボタンを追加 (#2706): サイドバー(モバイルではドロワー)のフッターに、言語セレクトの左へ設定画面(/more)への導線ボタン(TransitionLink)を追加。/worktrees/* 画面で GlobalMobileNav が非表示になり設定へ遷移できなくなる問題を解消。

Changed

  • ci(workflows): type-check ジョブの continue-on-error を外す (#2749): .github/workflows/ci-pr.yml の type-check ジョブから不要な continue-on-error: true を削除し、型エラー発生時に CI が正しく停止するようにした。tests/unit/guards/lint-tests-scope.test.ts で type-check ジョブが continue-on-error を持たないことを検証するガードを追加。

  • chore(lint): lint:sh の対象に tests/scripts と .claude/lib を追加 (#2739): package.json の lint:sh の find 対象に tests/scripts と .claude/lib を追加し、tests/scripts/test-rotate-logs.sh の SC1091 を source=/dev/null で解消し未使用関数 create_file_bytes を削除した。tests/unit/guards/lint-sh-scope.test.ts に find ルート集合と未検査 shell スクリプト除外リスト(26本)の完全一致ガードを追加。

  • chore(lint): server.ts の tmux 動的 import 1 件を選択的な例外として確定する (#2738): #2736 が npm run lint を eslint . に反転して初めて見えた server.ts:803 の await import('./src/lib/tmux/read-mode')(#1922 §4 D4 / DR4-005 の ImportExpression セレクタに当たる唯一の 1 件)を、#2736 が置いた暫定の "warn" からルート配列の ImportExpression セレクタ 1 つだけを除いた選択的な再宣言(severity は error)に置き換えた。"off" にすると i18n 文言直書き検出(#1271)と tmux の require() 検出まで道連れになるため採らず、静的 import 化(hoist)も採らない(no-restricted-imports の allowlist への追加になり同じ §4 D4 が禁じているうえ、#1623 の bootstrap 制約——server.ts の top-level 静的 import は tsx server.ts 下で Next の AsyncLocalStorage ブートストラップを乱し最初に middleware をコンパイルするリクエストが落ちる——を server.ts / src/lib/tmux/read-mode.ts / docs/design/1623-tmux-reading-mode.md の 3 箇所が明文化している)。server.ts は 1 行も変更していない。tests/unit/guards/tmux-import-allowlist.test.ts に DYNAMIC_TMUX_IMPORT_EXEMPT(追加禁止・削除のみ許可)を置き、override を構造で特定して(no-restricted-syntax を配列で再宣言し ImportExpression セレクタを持たないもの)ファイル集合の完全一致・ルート配列マイナス 1 セレクタとの deep-equal・server.ts 以外での陽性対照・server.ts での tmux require() 陰性対照・リポジトリ全体を ESLint API で走査して ImportExpression 由来の指摘が server.ts 以外に無いことの 5 点を固定した。docs/design/multi-agent-state-architecture.md §4 D4 の DR4-005 段落直後に、唯一の例外であること・理由 2 つ・pin の場所・追加禁止/削除のみ許可を追記した。npm run lint は 0 errors / 0 warnings(--max-warnings 0 で exit 0)になった。

  • chore(lint): public/sw.js と website/main.js の実債務 6 件を解消 (#2737): #2736 が eslint . へのスコープ反転時に public/sw.js と website/main.js の override で warn に落としていた実債務 6 件(public/sw.js の未使用 catch (err) 4 箇所を catch に、.catch(function (err)) 1 箇所を _err に修正し、website/main.js の式文三項演算子 1 箇所を if に修正)を解消した。.eslintrc.json から両ファイルの override エントリを削除してルールを継承値の error に戻し、tests/unit/guards/lint-repo-scope.test.ts に overrides に両ファイルが含まれないことを確認する assert を追加した。

  • chore(lint): lint 対象をディレクトリ列挙から eslint . に反転する (#2736): npm run lint を eslint . --ext .js,.jsx,.ts,.tsx,.mjs,.cjs にし、src tests scripts bin の列挙をやめた(同じ穴が #2719 / #2732 / 本Issue と 3 回開いたのは個々の抜けではなく「対象を人が覚えて足す」方式そのものが原因のため、既定で全体を見て除外だけを明示する形へ反転した)。これでこれまで一度も ESLint がかかっていなかったリポジトリ直下(next.config.js / playwright.config.ts / postcss.config.js / server.ts / vitest.config.ts)・public/sw.js / website/main.js も対象に入る。ESLint 8 は .gitignore を読まないので .eslintrc.json に ignorePatterns 7 件(.next/ / coverage/ / dev-reports/ / dist/ / next-env.d.ts / skills/backups/ / workspace/)を追加した。7 件はいずれも .gitignore 済みかつ git 管理下のファイル 0 件のビルド成果物・run の残骸であり、それが除外してよい唯一の根拠である(node_modules/ は ESLint 既定、.claude/ .agents/ は ESLint 8 のドットディレクトリ既定無視なのでどちらも書いていない)。next.config.js は module.exports の CommonJS 設定ファイルで require() が正しい綴りなので #2732 の bin/** + **/*.cjs override の files に足して恒久除外にした。実債務である public/sw.js 5 件 / website/main.js 1 件(#2737 が解消)と server.ts:803 の tmux 動的 import 1 件(#2738 が扱う。静的 import にすると Next の AsyncLocalStorage ブートストラップが壊れると同ファイルのコメントが明記しているため本Issueでは直さない)は override で一時的に warn に落とし、npm run lint は exit 0(0 errors, 7 warnings)のまま切り替えた。再発防止に tests/unit/guards/lint-repo-scope.test.ts を新設し、lint が eslint . で始まりディレクトリ列挙を含まないこと・ignorePatterns の各要素が実際に gitignore 済みかつ追跡ファイル 0 件であること・ESLint#isPathIgnored() が真を返す追跡ファイルの集合が既知の 10 パス(.claude/skills/demo-video/scripts/*.ts と .agents/ のそのミラー)と完全一致することを固定した(ignorePatterns だけを見るガードでは、将来 .foo/tool.ts が置かれたときドットディレクトリ既定無視で無音のまま未検査になるのを検出できないため)。既存ガード 2 本(lint-tests-scope.test.ts / lint-scripts-scope.test.ts)は assert の意図を変えずに期待値だけ eslint . と ["bin/**", "**/*.cjs", "next.config.js"] へ更新した。

  • chore(lint): scripts/ の残り 7 件を解消し 3 ルールを error に戻す (#2733): #2732 が scripts/** override で warn に落とした実債務 3 ルール(no-unused-vars 3 件 / no-explicit-any 3 件 / no-this-alias 1 件、計 7 件 / 5 ファイル)を解消し、override の warn 3 行を削除して継承値の error に戻した。scripts/measure-tile-polling/index.mjs の未使用 BASE_URL 宣言を削除し(PORT は保持)、scripts/migrate-cli-tool-id.ts の未使用 import 2 行(Database と getWorktreeById)を削除、scripts/migrate-prompt-support.ts の catch (rollbackError) を catch に変更、scripts/init-and-migrate.ts と scripts/migrate-prompt-support.ts の catch (error: any) を unknown に変更して error instanceof Error ? error.message : String(error) で安全に取得した。scripts/spike/lib/node-sqlite-adapter.mjs は this 転送の破壊を防ぐため function 式を維持し理由付きの eslint-disable-next-line @typescript-eslint/no-this-alias を付与した。ガード tests/unit/guards/lint-scripts-scope.test.ts に scripts/** override の rules に "warn" が 0 個であることを assert する検証を追加した。

  • chore(lint): lint 対象に scripts/ と bin/ を追加する土台を作る (#2732): npm run lint を eslint src tests scripts bin --ext .js,.jsx,.ts,.tsx,.mjs,.cjs に広げ、これまで一度も ESLint がかかっていなかった scripts/(45 ファイル。CI から呼ばれる changelog-fragments.mjs / run-related-unit-tests.mjs、DB マイグレーション、起動系を含む)と bin/ を対象に含めた(--ext に .mjs,.cjs を足さないと scripts/ の 12 ファイルが漏れる)。40 件の指摘のうち 32 件はテストに当てるのが誤りである src 専用ポリシーの誤検出だったため、.eslintrc.json の overrides に 3 エントリを追加: scripts/** では no-restricted-syntax を off にせず再宣言し、3 つの selector のうち i18n 文言直書き検出(#1271)の 1 つだけを落として tmux の動的 import() / require() 検出(#1922 §4 D4 / DR4-005)2 つは error のまま逐語コピーした。tmux ゲートウェイ強制(no-restricted-imports)の免除は §4 D4 の「ソート済みパス列挙・追加禁止」に従い scripts/canary/session.ts と scripts/legacy-tmux-probe/probe.ts の 2 ファイルだけを列挙(ディレクトリ単位の off は禁止)、実債務 3 ルール(no-unused-vars / no-explicit-any / no-this-alias)は段階解消のため一時的に warn、bin/** と **/*.cjs の no-require-imports は CommonJS が正しい綴りであるため恒久的に off とした(npm run lint は exit 0、0 errors / 7 warnings)。分類は新規ガード tests/unit/guards/lint-scripts-scope.test.ts で固定し、scripts/ の免除列は tests/unit/guards/tmux-import-allowlist.test.ts の SCRIPTS_EXEMPT に追加して完全一致・陽性対照で pin した。

  • chore(lint): tests/ の no-unused-vars を解消(その他の tests/unit ・ tests/helpers) (#2724): tests/ 配下の no-unused-vars 警告解消バッチ(その他の tests/unit ・ tests/helpers 担当分、40ファイル57件)において、未使用の import、変数、引数、型定義を整理・削除・リネームした。

  • chore(lint): tests/ の no-unused-vars を解消(tests/unit/components ・ tests/unit/lib) (#2723): tests/ の段階的 lint 解消の一環として、tests/unit/components および tests/unit/lib 配下の 32 ファイル・40 件の未使用変数・インポートを整理した。

  • chore(lint): tests/ の残り 76 件を解消し 6 ルールを error に戻す (#2721): #2719 が tests/** override で warn に落とした 6 ルールのうち、no-unused-vars 以外の残り 76 件(no-explicit-any 58 / rules-of-hooks 9 / no-require-imports 6 / prefer-const 2 / no-this-alias 1、25 ファイル)を解消し、override の warn 6 行をすべて削除して継承値の error に戻した。rules-of-hooks は hook ではないのに use* と名乗る test helper を ESLint が React Hook と誤認していたものなので改名した(useIsolatedAgentHooksDir → registerIsolatedAgentHooksDir / useIsolatedHooksStateDir → registerIsolatedHooksStateDir / 4 ファイルがそれぞれ自前定義していたローカル関数 useLiveSessionState → wireLiveSessionState)。no-require-imports は 3 ファイルとも標準モジュールだったので静的 import に置き換え、no-explicit-any は具体型か unknown で置き換え(exec の promisify 経路のように exec のどのオーバーロードとも一致しない double だけ as unknown as typeof exec を残した)、構造上 const にできない let と mock の this 登録の 2 箇所だけ理由つき eslint-disable を付けた。ガード tests/unit/guards/lint-tests-scope.test.ts は override の rules に "warn" が 0 個であることを新たに assert し、昇格済みルールの再実測を 1 ルール 1 ESLint インスタンスから全ルール 1 インスタンス 1 走査へまとめた(6 ルール昇格時の実測 44.0 秒 → 8.2 秒)。再実測はルールの severity だけを error へ上げてオプション(no-unused-vars の ^_ 無視など)はルート設定のものを引き継ぐ。STAGED_WARN の 6 要素は減らしていない(減らすとそのルールが再実測の対象から外れるため)。

  • chore(lint): tests/ の no-unused-vars を解消(tests/integration ・ tests/e2e) (#2720): tests/integration および tests/e2e 配下の 15 ファイルで発生していた未使用変数・import・引数(21 件)を整理し、@typescript-eslint/no-unused-vars を解消。

  • feat(settings): PC の設定ボタンを設定モーダル起動に切り替え (#2709): PC にある 3 つの設定入口(ヘッダーのナビ「設定」・ブランチ画面 ActivityBar 最下部の歯車メニューの「設定」・サイドバーのフッターの歯車)が、/more への全画面遷移ではなく #2708 の設定モーダル(useSettingsDialog().open())を開くようにした。スマホ(useIsMobile())と修飾クリック(⌘/Ctrl/Shift/Alt/中クリック)は従来どおり /more のページへ落ちるので、<a href="/more"> であること・aria-current="page" の付き方・ブックマーク・コマンドパレット・スマホ下部ナビはいずれも変わらない。ActivityBar は onSelect で ref のフラグを立てるだけにして、モーダルを開くのは DropdownMenuContent の onCloseAutoFocus から queueMicrotask(openSettings) で行う(ここで event.preventDefault() を呼ぶと Radix の歯車へのフォーカス復帰が composeEventHandlers によって丸ごと飛び、モーダルの useFocusTrap が opener を <body> として記録して閉じても歯車へフォーカスが戻らなくなるため、止めずに順序だけを決める)。辞書の追加・変更は無し。

  • refactor(settings): /more の中身を SettingsPanel に切り出し (#2707): src/app/more/page.tsx に直書きされていた 5 セクションを src/components/settings/SettingsPanel.tsx へ移し、ページが持つのは <h1> と説明文とページ余白だけにした(PC の設定モーダル #2708 が同じ中身を描けるようにするため)。各セクションは SettingsQuickLinksSection / SettingsGeneralSection / SettingsNotificationsSection / SettingsExternalAppsSection / SettingsAboutSection として個別 export し、モーダルがカテゴリ単位で 1 つずつマウントできる形にした(SettingsQuickLinksSection の onNavigate は遷移前にモーダルを閉じるためのもので、ページは渡さず従来どおりの挙動)。英語べた書きだったセクション見出し(Quick Links / External Apps / About)を common.settings.sections.* の 5 キーへ移送。レイアウト・DOM 構造・data-testid・カード本文は変更なし。文言変更は 1 か所だけで、「設定」セクションの <h2> が ja「設定」→「一般」/ en Settings → General(<h1>「設定」と同じ語が直下の <h2> に重複していたのを解消)。

Fixed

  • fix(ui): モーダルの背景クリックで閉じない問題を修正 (#2715): 背景クリックで閉じる処理をbackdropからパネルのラッパー(data-testid="modal-backdrop-surface")へ移し、mousedownとmouseupの両方がラッパー自身で起きた時のみ閉じるよう修正。

  • fix(css): モバイル <main> の余白が下部ナビ境界線 1px ぶん足りずコンテンツが重なる問題を修正 (#2700): .pb-nav-safe の計算式にナビ上境界線(1px)を足し calc(3.5rem + 1px + env(safe-area-inset-bottom, 0px)) に更新。inset の有無にかかわらず <main> の content box 下端とナビ上端の一致を固定。