Repository navigation
v0.39.1
[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)を導入して.sh21 本を検査対象にし、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での tmuxrequire()陰性対照・リポジトリ全体を 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にignorePatterns7 件(.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/**+**/*.cjsoverride のfilesに足して恒久除外にした。実債務であるpublic/sw.js5 件 /website/main.js1 件(#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-vars3 件 /no-explicit-any3 件 /no-this-alias1 件、計 7 件 / 5 ファイル)を解消し、override のwarn3 行を削除して継承値の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-any58 /rules-of-hooks9 /no-require-imports6 /prefer-const2 /no-this-alias1、25 ファイル)を解消し、override のwarn6 行をすべて削除して継承値の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「設定」→「一般」/ enSettings→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 下端とナビ上端の一致を固定。