変更履歴
GitHubすべての注目すべき変更をリリースごとに記録しています。同じファイルがリポジトリに収録されており、19言語すべてに翻訳されています。
[1.57.2] — 2026-05-19
fix(config): /#/config「validation failed」の真の原因 — SPA が注入する lang フィールド。 public/js/api.js は すべての JSON POST ボディに lang を自動付与します(LLM ルートが UI ロケールを拾うため)。/api/config は LLM ルートではなく lang は設定キーでもないため、validateConfig の(正しく、セキュリティ上重要な)未知キー拒否が 毎回の保存 に 400 を返していました:validation failed — lang: not a known config key。ブラウザ限定の症状で、curl/インプロセスの再現は lang を送らないため v1.57.0/.1 はメッセージを改善しても原因は残っていました。設定ルートは検証前にトランスポート用の lang を除去するように。KNOWN_KEYS 書き込みフィルタは依然として真に未知のキーを破棄 — インジェクション対策は不変。実際の保存ボタンを押す新しい Playwright フォーム巡回で発見。テスト: 新規 tests/playwright-forms.mjs(26、npm run test:e2e:browser に組込)で全フォームを巡回、config-endpoint にブラウザ等価ケース。879 → 881 ユニット、Playwright 32 → 58。詳細は CHANGELOG.md。
[1.57.1] — 2026-05-19
fix(ux): すべての API エラーが「何が失敗し、どこで、なぜ」を表示。入力エラー文も最大限に説明的に。 サーバは以前から { error, details: ["フィールド: 理由", …] } を返していましたが、各フォームはトップ行(「validation failed」)しか表示せず、/#/config(および全画面)でどのフィールドが不正か分かりませんでした。api.js がフィールド単位の details をメッセージにサイト全体で畳み込み(1 箇所の修正で全フォームが恩恵)、リクエスト文脈 (メソッド /パス · HTTP NNN)(どこで)を付与、非 JSON レスポンスは生本文の断片を表示、ネットワークエラーにもメソッド+パスを付与。err.details も公開。validateConfig のメッセージは最大限に説明的(何が悪く、どう直すか)に。シークレットキーは入力値を一切エコーしません(文字数のみ)—実キーの打ち間違いがトースト/ログに漏れません。PORT の範囲も実際に検証(99999 は拒否)。/#/config の PORT/HOST は実デフォルト(4317 / 127.0.0.1)を初期表示。エラートーストは長め(9–20 秒)に表示し、折り返し/スクロールして見切れません。テスト: 新規 tests/config-validation-detail.test.mjs(12)、874 → 879。詳細は CHANGELOG.md。
[1.57.0] — 2026-05-19
feat(provider): OpenRouter を 5 番目のヘッドレス・ライブ評価プロバイダとして追加 + fix(config): どの API キーを保存しても「validation failed」になる不具合を修正。 貼り付けたキーは末尾の改行や空白を伴うことが多く(OS のクリップボード、プロバイダコンソールの「コピー」ボタン)、1.57 以前は すべての プロバイダで改行ガードに引っかかり、さらに $ 終端アンカーの ANTHROPIC_API_KEY 正規表現が本物の Anthropic キーを誤って弾いていました。validateConfig は検証の 前 に各値を正規化(トリム)し、ルートはトリム後の値を保存(ランタイムで認証成功、\n による .env 破損なし)、Anthropic チェックは堅牢な sk-ant- プレフィックス + 長さに変更(共通の isUsableKey() ≥ 20 文字が本当の「実在キーか」判定)。内部改行は引き続き拒否(.env インジェクション対策)。OpenRouter が一級プロバイダに:/#/config の OPENROUTER_API_KEY 一つで 300 以上のモデルにアクセス。auto 順序の 最後(Anthropic → Gemini → OpenAI → Qwen → OpenRouter)なので既存設定が黙ってリルートされることはなく、LLM_PROVIDER=openrouter で固定可能。OpenAI/Qwen と同じ _tailProvider() 経路で /api/evaluate・/api/deep・/api/mode/:slug に接続、/api/status/providers と Health ダッシュボードに表示。OpenAI 互換クライアント(新規依存なし — 直接 fetch、AbortController タイムアウト、キーは記録しない)で推奨の HTTP-Referer/X-Title ヘッダ付き。モデルのドロップダウンはライブ:OPENROUTER_MODEL は GET /api/openrouter/models(OpenRouter 公開カタログのサーバ側プロキシ — CSP connect-src 'self' を維持)から取得し、取得失敗時は厳選フォールバック、10 分間のメモリキャッシュ。8 ロケールに新 i18n キー(config.openrouter*)。テスト: 新規 tests/openrouter-route.test.mjs・tests/openrouter-model-selector.test.mjs、env-config/openai/provider-selector を拡張。831 → 855。詳細は CHANGELOG.md。
[1.56.4] — 2026-05-19
feat(ui): UX-N2 — グローバル検索入力にプラットフォーム対応の可視 ⌘K / Ctrl K ヒント。 Cmd/Ctrl+K(検索にフォーカス)ショートカットは aria-label/ソースにしか無く、晴眼ユーザーは発見できずアプリが実際より遅く感じられた。控えめな <kbd class="kbd-shortcut"> が検索ピル末尾に表示され、起動時にプラットフォーム判定(navigator.platform/userAgent)で data-mac/data-other から埋められる:macOS/iOS は ⌘K、その他は Ctrl K。aria-hidden="true"(既存の aria-label が AT に伝えるため二重通知を避ける)かつ pointer-events:none(装飾)。既存の Cmd/Ctrl+K キーバインドは不変。新しい i18n キーなし(グリフは共通);バッジは既存 .searchbar の flex 子(ラッパー/絶対配置不要 — input は既に flex:1)。テスト: 新しい CI 隔離のソース静的スイート tests/cmdk-hint-visible.test.mjs(5):.searchbar 内に <kbd class="kbd-shortcut">;aria-hidden="true" + data-mac/data-other 両方;app.js が navigator 判定で埋める;(e.ctrlKey||e.metaKey)&&e.key==='k' → search.focus() バインド健在(リグレッションガード);app.css が .kbd-shortcut を整形し display:none にしない。826 → 831。feat(ui) · test: tests/cmdk-hint-visible.test.mjs。詳細は CHANGELOG.md。
[1.56.3] — 2026-05-19
fix(reliability): プロバイダーのキー検出が空文字だけでなくプレースホルダー / 短すぎる値も拒否。 親 .env のプレースホルダー GEMINI_API_KEY が「✓ set」と表示され、かつ有効な ANTHROPIC_API_KEY を差し置いてアクティブプロバイダーに選ばれていた。effectiveEnv() は undefined/'' しか弾かないため、10 文字のゴミが本物のキー扱いに:オンボーディングバナーが GEMINI ✓ set、GET /api/status/providers が activeProvider: "gemini" を返し、有効な 108 文字の Anthropic キーを無視して死んだキーで全ライブ ⚡ 評価が無言で失敗していた。新しい純粋関数 isUsableKey()(env-config.mjs)はシークレットを「設定済み」とみなす条件を ≥ 20 文字(どの対応プロバイダーのキーもこれより短くない — Gemini AIza… ≈ 39、Anthropic sk-ant-… ≈ 100+、OpenAI ≥ 40、Qwen ≈ 35)かつ既知のプレースホルダー(your_*_here、changeme、placeholder、<…>、同一文字の繰り返し…)でないこと、に限定。hasAnthropicKey()/hasGeminiKey()(anthropic.mjs)、hasOpenAIKey()/hasQwenKey()(openai.mjs)、GET /api/health の GEMINI_API_KEY/ANTHROPIC_API_KEY 行(生 process.env から同じ effective+plausible ビューへ移行)に一律適用 — health ページ・プロバイダーエンドポイント・OR ルーターが常に一致。selectActiveProvider() は不変(正しい keysConfigured を受け取るだけ)。テスト: 新しい CI 隔離スイート tests/key-detection-rejects-placeholder.test.mjs(5):isUsableKey 単体 + in-process createApp() で報告シナリオを再現(一時 .env に 10 文字 GEMINI_API_KEY + 実 ANTHROPIC_API_KEY)— gemini は keysConfigured になく、activeProvider === "anthropic"、/api/health 行も一致。既存の effective-env 階層テスト 4 件は短すぎるスタブを現実的な長さに延長(契約は不変)。821 → 826。fix(reliability) · test: tests/key-detection-rejects-placeholder.test.mjs。詳細は CHANGELOG.md。
[1.56.2] — 2026-05-19
feat(a11y): UX-N1 — ルートごとのロケール対応 document.title(マルチタブの識別 + スクリーンリーダーのページ変更通知)。 修正前は 24 ルートすべてが index.html の静的 <title>(「career-ops — command center」)のままで、タブ名が同一・ブックマークが汎用・「ページが変わりました」通知も毎回同じだった。public/js/router.js の focusNewView() がビュー自身のローカライズ済み <h1 class="page-title"> からタイトルを導出 — 「ビュー — career-ops」— するため、自動翻訳(新しい i18n キー不要)かつルートごとに一意。初回ペイントの guard より前に設定するので最初のタブにもタイトルが付く(v1.56.0 UX-12 の tabindex 設定と同じ順序)。見出しが無いビューは career-ops — command center にフォールバック。テスト: 新しい CI 隔離のソース静的スイート tests/document-title-per-route.test.mjs(4):focusNewView が document.title を代入;タイトルは <h1> 由来(ルートごと + ローカライズ、単一リテラルでない);代入は !firstPaintDone より前;製品デフォルトあり。817 → 821。feat(a11y) · test: tests/document-title-per-route.test.mjs。詳細は CHANGELOG.md。
[1.56.1] — 2026-05-19
fix(a11y): ルーター管理の tabindex="-1" 見出しフォーカスで出ていた偽のブランドフォーカスリングを抑制。 public/js/router.js は各クライアント遷移で遷移先ビューの見出しに tabindex="-1" を付与し .focus() する(スクリーンリーダーが新ページを読み上げるため)。tabindex="-1" 要素はキーボードで到達不能だが、Chromium の :focus-visible ヒューリスティックが依然グローバルのブランドリング(*:focus-visible { outline: 2px solid var(--rausch) })を描き、遷移ごとに ページ見出しの周りに赤い矩形(例:#/dashboard の「Command Center」)を表示 — それが images/dashboard-*.png のヒーロー画像にも焼き込まれていた。修正は 1 つのスコープ付きルール [tabindex="-1"]:focus, [tabindex="-1"]:focus-visible { outline: none }(WAI-ARIA APG の管理フォーカス・パターン)。実際のキーボードフォーカスはグローバル *:focus-visible リングを維持(WCAG 2.4.7 維持);skip-link のリングは無影響(<a> であり tabindex="-1" ではなく、より高い詳細度)。8 つの images/dashboard-*.png を再生成 — 赤い枠は消えた。テスト: 新しい CI 隔離のソース静的スイート tests/managed-focus-no-ring.test.mjs(4):グローバル *:focus-visible リングが定義されたまま(WCAG 2.4.7 非後退);[tabindex="-1"]:focus,:focus-visible ⇒ outline:none;抑制ルールはグローバルの後(カスケード安全);修正はスコープ付き(全体 *:focus{outline:none} なし)。tests/dashboard-initial-focus.test.mjs と対。813 → 817。fix(a11y) · test: tests/managed-focus-no-ring.test.mjs。詳細は CHANGELOG.md。
[1.56.0] — 2026-05-19
feat(ux): LOW 仕上げバンドル — UX-9 / UX-10 / UX-11 / UX-12(1 つのグループ化マイナーリリース)。 UX-9 #/cv:ページタイトルを控えめな .cv-breadcrumb チップに降格し、うるさいサブタイトルは <h1> の title ツールチップへ移動 — ユーザーの CV(プレビューの名前)が視覚的優先権を持つ。F-V54-A 不変は維持 — 依然 正確に 1 つの <h1>、依然 .page-title。UX-10 新しい共有ヘルパー UI.providerCostHint(t) を #/auto・#/evaluate・#/deep・各 #/<mode> の ⚡ ライブ実行の隣に表示;GET /api/status/providers(v1.55.3)を再利用:キーありで 「推定コスト: OpenAI gpt-5-codex · ~$0.04/eval」(桁レベル、“~”)、キーなしで ⚡ は手動プロンプトをコピー(API コストなし)と明示;fail-soft。UX-11 #/help:TOC フィルタが ちょうど 1 つ のセクションに絞られたら 300ms アイドル後にそこへスクロール(デバウンス;0 や 2 以上では発火しない)。UX-12 #/dashboard:初回ペイントで <h1> をフォーカス可能(tabindex="-1")にし、#content は aria-live="polite" を維持(起動時に読み上げ)— フォーカスは 奪わない(skip-link との競合回避、v1.41.0 の決定)。新 i18n キー cost.estimate、cost.manual ×8;新しい .cv-breadcrumb/.cost-hint CSS。テスト: 4 つの新しいソース静的 CI 隔離スイート(cv-breadcrumb 3、run-cost-line 4、help-toc-autoscroll 4、dashboard-initial-focus 3);既存 cv-single-h1/help-nav-a11y ロック更新(不変保持)。800 → 813。4 つのライブ Playwright プローブ、コンソールエラー 0。feat(ux) · 4 test suites。詳細は CHANGELOG.md。
[1.55.8] — 2026-05-19
feat(tracker): サーバーサイド・ページネーション + クリック可能なファネルチップ(UX-8)。 サーバー: GET /api/tracker に任意の ?page / ?pageSize / ?status クエリパラメータを追加。無しなら応答は従来の { rows: [...] } とバイト単位で同一(既存の呼び出し元/テストは無影響)。有りなら { rows: slice, total, page, pageSize, funnel } を返す — pageSize は [1,500]、page は ≥1 にクランプ、status は rows+total をフィルタ、funnel は全履歴のステータス→件数内訳(ページ/フィルタに依存しないのでチップが常に正確)。#/tracker: 上部に新しいクリック可能なファネルチップバー — 「すべてのステータス · N · Applied · N · Interview · N …」(順序 Applied → Responded → Interview → Offer → Rejected → Discarded → Evaluated → SKIP)。チップのクリックで Status フィルタを設定(アクティブなチップの再クリックで解除);アクティブチップは aria-pressed + ハイライト。8 ロケールの新 i18n キー track.funnelAria;新しい .tracker-funnel/.tracker-chip/.tracker-chip--active CSS。test: tests/tracker-server-paged.test.mjs(新規、7 ケース、CI 隔離、エフェメラルポートのインプロセス Express + 一時 CAREER_OPS_ROOT applications.md — CLAUDE.md #2/#8):back-compat(パラメータ無しで厳密に {rows});?page&pageSize スライス + total/page/pageSize/funnel 合計 N;最後の部分ページで重複なし;範囲外ページ ⇒ 空 rows + 有効 total;?status= が total/rows をフィルタしつつ funnel は全履歴;pageSize キャップ;+ チップバーのソース静的ロック。793 → 800。feat(tracker) · test: tests/tracker-server-paged.test.mjs。詳細は CHANGELOG.md。
[1.55.7] — 2026-05-19
feat(pipeline): >1000 行で vanilla-JS の行仮想化(UX-7)。 #/pipeline は全行を描画(filtered.forEach(list.appendChild(urlRow)))していた — スキャンはキューを数千の URL で満たすため、数千の行ノード(各々 flex div + <a> + ボタン 2 個)がフィルターのキー入力ごとに同期構築され、DOM とアクセシビリティツリーを溢れさせた。新しい vanilla-JS 仮想化(react-window 相当、依存なし):VIRTUALIZE_THRESHOLD = 1000 超で #/pipeline は固定高(70vh)のスクロールビューポートになり、縮まないスペーサー(flex:0 0 auto、height = 行数 × 56px)がリスト全体の実スクロールバーを保ち、rAF スロットルのスクロールリスナーがビューポート ± 5 行バッファのみ描画(一度に N ではなく ~16–19 ノード)。閾値以下では元の単純描画をバイト単位で維持するため、典型的なパイプラインと既存テスト/e2e は影響なし。各仮想化行は URL で曖昧性を解消した ▶/✕ aria-label を維持(F-V54-B 回帰ロック)。ウィンドウ計算は純粋な computeWindow() ヘルパー。test: tests/pipeline-virtualize.test.mjs(新規、5 ケース、CI 隔離、ソース静的):~1000 の数値閾値;≤閾値分岐は forEach→appendChild 維持;>閾値分岐は rAF スクロールリスナー + スペーサーで slice(start,end) 描画;computeWindow() が [0,total] ± バッファでクランプ;行が ▶/✕ aria-label 維持。788 → 793。ライブ Playwright プローブ(1200-URL フィクスチャ):scrollHeight≈67248、DOM に ~16–19 ノードのみ、ウィンドウがスクロールを端から端まで追従、コンソールエラー 0。feat(pipeline) · test: tests/pipeline-virtualize.test.mjs。詳細は CHANGELOG.md。
[1.55.6] — 2026-05-19
feat(scan): 副次フィルターを「詳細フィルター」ディスクロージャの背後へ整理(UX-4)。 #/scan は全フィルター — フリーテキスト、リモート/ハイブリッド/オンサイト、スコープ、ソース、スキャン後の stack/level/dynamic ファセットチップ — を同じ重みで積み上げ、コントロールの壁だった。今や日常フィルターは可視のまま(フリーテキスト + リモート/ハイブリッド/オンサイト;🌐 スキャンボタンは既にコントロールカードに別置)、副次は <details class="scan-advanced"><summary>詳細フィルター</summary> の背後に折り畳み:スコープ + ソースのセレクト、そして別途ファセットチップ群(新しい結果集合がチップの壁ではなくテーブルで始まり、チップ行が 1 つ以上ある時のみ描画)。8 ロケールの新 i18n キー scan.advancedFilters;新しい .scan-advanced サマリースタイル(控えめな ⚙ アフォーダンス、マーカーなし、開時に太字)。test: tests/scan-advanced-disclosure.test.mjs(新規、6 ケース、CI 隔離、ソース静的):.scan-advanced フック + scan.advancedFilters ラベルの <details>/<summary> が存在;フリーテキスト + リモートは可視のまま;スコープ + ソースはディスクロージャ内;chipsContainer が <details>;.scan-advanced summary がスタイル付き;scan.advancedFilters ×8。782 → 788。feat(scan) · test: tests/scan-advanced-disclosure.test.mjs。詳細は CHANGELOG.md。
[1.55.5] — 2026-05-19
feat(dashboard): 2 つの P0 CTA + 焦点となる最近のアクティビティヒントのヒーロー(UX-3)。 #/dashboard は同じ重みの約 30 ノードで開き、「次に何をすべきか」が不明瞭だった。新しい .dash-hero ブロックがページヘッダー直下に配置:2 つの P0 ジャーニー — ✨ URL を自動パイプライン、🌐 今すぐスキャン — を大きな .btn-hero ボタンに昇格し、単一の 焦点となる最近のアクティビティヒント(「最新の評価: <スコア> — <タイトル>」、レポートへリンク;コールドスタートでは dash.heroNoEval の案内空状態)が、再訪ユーザーには中断地点を、新規ユーザーには重要な唯一のアクションを示す。2 つの主要ボタンはヘッダーから削除(副次的な「📋 パイプラインを開く」のみ残置)しアクションの重複を回避。ステータスバケットは目立つ .badge から控えめな .dash-chip ピルへ降格。8 ロケールの新 i18n キー dash.lastEval、dash.heroNoEval;新しい .dash-hero/.btn-hero/.dash-chip CSS。test: tests/dashboard-hero.test.mjs(新規、5 ケース、CI 隔離、ソース静的):.dash-hero が存在し Quick-actions グリッドに先行;両 P0 CTA が /auto+/scan ルートの .btn-hero;焦点 dash.lastEval + 空状態 dash.heroNoEval;バケットが .dash-chip;CSS 存在;dash.lastEval+dash.heroNoEval ×8。777 → 782。feat(dashboard) · test: tests/dashboard-hero.test.mjs。詳細は CHANGELOG.md。
[1.55.4] — 2026-05-19
feat(ux): Run の隣に正直な auto-pipeline ETA + スキャン中の目立つ Stop(UX-6)。 #/auto:新しい .auto-eta ヒント — “⏱ 約1〜2分”(キー auto.eta、title は auto.etaTitle)— が Run ボタンの隣に表示され、ワンクリックの約束が所要時間について コミット前に 正直になる;文言は career-ops.org/docs(「URL を貼る → 1〜2 分で完全レポート」)と一致。#/scan:数分のクロールが実行中(aria-busy)の間、Stop を低コントラストのゴーストボタンから目立つ破壊的ボタンへ昇格(新しい .btn-danger — 塗りつぶし、高コントラストの白文字 on コーラル、太さ 600)。setScanRunning(running) が scan-stop-btn を btn-danger(実行中)と btn-ghost(アイドル、どのみち非表示)で切り替え、負荷時でもユーザーが Stop を見つけ信頼できるようにする。8 ロケールの新 i18n キー auto.eta、auto.etaTitle;新しい .btn-danger/.auto-eta CSS。test: tests/auto-eta-stop.test.mjs(新規、4 ケース、CI 隔離、ソース静的):#/auto が runBtn の隣に .auto-eta クラスで t('auto.eta') を描画;auto.eta ×8;setScanRunning(running) が Stop を btn-danger に昇格;.btn-danger が高コントラストの白文字で存在。773 → 777。feat(ux) · test: tests/auto-eta-stop.test.mjs。詳細は CHANGELOG.md。
[1.55.3] — 2026-05-19
feat(onboarding): 画面上の 4 プロバイダ OR ステータス — コールドスタート・バナー + アクティブ・プロバイダ・チップ(UX-2、HIGH)。 新しい読み取り専用エンドポイント GET /api/status/providers → { activeProvider, activeModel, keysConfigured }。keysConfigured は llm.mjs のゲートと同じ実効 env ビュー(process.env ∨ 親 .env);activeProvider は OR ルーターが実際に選ぶもの — env-config.mjs の新しい純粋ヘルパー selectActiveProvider() が providerOrder() を走査(キーのない LLM_PROVIDER ピンは null)。秘密は返さない — プロバイダ名 + モデル id のみ。SPA シェルがグローバルなオンボーディング領域(#onboarding-banner、app.js が描画、CSP 安全な DOM のみ)を表示:0 キー → 赤いバナー + #/config?tab=api-keys への CTA;≥1 キー → 控えめなチップ にアクティブなプロバイダ+モデル。看板の差別化要因(「Anthropic / Gemini / OpenAI / Qwen のいずれかが自動順で動く」)を試行錯誤ではなく画面上で発見可能にする。8 ロケールの onboarding.* i18n キー;新しい .onboarding-warn/.onboarding-ok CSS。test: tests/onboarding-key-banner.test.mjs(新規、9 ケース、CI 隔離):selectActiveProvider の意味論;GET /api/status/providers のインプロセス(エフェメラルポート + 一時 CAREER_OPS_ROOT .env で実際の親キーを決して読まない — CLAUDE.md #2/#8);静的 SPA 配線 + onboarding.* ×8 カバレッジ。764 → 773。feat(onboarding) · test: tests/onboarding-key-banner.test.mjs。詳細は CHANGELOG.md。
[1.55.2] — 2026-05-18
fix(cv): #/cv マークダウンエディタに記述的で自己完結したアクセシブル名を付与(F-V55-H / UX-5)。 #/cv の主エディタ <textarea id="cv-editor"> は今や新キー cv.editorAria 経由で記述的な aria-label — “CV マークダウンエディタ — マークダウン形式のプロフェッショナル履歴書” — を持ち、可視の「Markdown」セクション見出しから継いでいた簡素な名前を置き換える。注:F-V55-H の症状(aria-label/labels のみ検査)と異なり、このフィールドは名前が無かったわけではない — v1.47.0(WS2 #16)が既に aria-labelledby → <h3 id="cv-md-heading">Markdown</h3> で結び付けており、スクリーンリーダーは「Markdown、編集、複数行」と読み上げていた。v1.55.2 はその簡素な「Markdown」を自己完結ラベルへ改善する。冗長な aria-labelledby は削除(残れば死んだマークアップ — ARIA 優先順位で aria-label が勝つ);可視の <h3>Markdown</h3> は晴眼ユーザー向けに残る。WCAG 1.3.1 + 4.1.2;v1.54.5 の batch-tsv 修正(F-V54-C)と並行。test: tests/cv-editor-a11y.test.mjs(新規、3 ケース、CI 隔離、auto-stepper-prerender.test.mjs 流のソース静的):#cv-editor は空でないフォールバックと共に t('cv.editorAria', …) で自己命名;cv.editorAria は 8 ロケール全てに存在し空でない;要素に冗長な aria-labelledby なし。761 → 764。fix(cv) · test: tests/cv-editor-a11y.test.mjs。詳細は CHANGELOG.md。
[1.55.1] — 2026-05-18
fix(auto): #/auto マウント時に 5 段階パイプラインのステッパーを事前レンダリング(F-V55-E / UX-1、シニア観察 S-4 再オープン)。 #/auto は今や、文書化された 5 段階の概要 — 検証 → 取得 → 評価 → レポート保存 → トラッカー追加 — を画面マウントの瞬間に表示する。以前は最初の SSE イベントまで空白だった。従来は <ol class="auto-stepper"> が display:none で生成され、renderStepper() は setStep() / run() からのみ到達したため、コールドスタートのユーザーは Run をクリックする前にドキュメントが約束するパイプラインを決して見られなかった。ステッパーは今やマウント時に 5 段階すべてが pending 状態で可視となり、aria-label(auto.stepperAria)を持つため支援技術がその領域を読み上げる。F-V55-E(a11y/静的保証レンズ)と UX-1(約束忠実度レンズ)をクローズ — 同一修正、両レンズ。test: tests/auto-stepper-prerender.test.mjs(新規、4 ケース、CI 隔離、router.test.mjs 流のソース静的):STEPS 配列は正確に 5 つの正準段階が順序どおり;stepperEl はマウント時に display:none ではなく auto.stepperAria を持つ;マウントスコープの renderStepper() 呼び出しが function setStep( に先行する;auto.stepperAria は 8 ロケール全てに存在。757 → 761。fix(auto) · test: tests/auto-stepper-prerender.test.mjs。詳細は CHANGELOG.md。
[1.55.0] — 2026-05-18
feat(llm): ヘッドレス・ライブ評価が “OR” で動作 — Anthropic | Gemini | OpenAI | Qwen、どのキーが設定されているかで自動選択。 ユーザー要望により、web-ui の ⚡ ライブ評価は Anthropic/Gemini だけでなく設定されているどの API キーでも動作するようになった。LLM_PROVIDER に openai と qwen を追加;auto(既定)はキーが存在する最初のプロバイダを使い、Anthropic → Gemini → OpenAI → Qwen の順で優先する。明示値は 1 つを固定する;キーなしで強制されたプロバイダは依然として手動プロンプト経路へフォールバックする。新しい server/lib/openai.mjs — 依存ゼロの OpenAI 互換 Chat Completions クライアント(anthropic.mjs と同じ安全な直接 HTTPS パターン:AbortController タイムアウト、キーは決してログ出力しない、effectiveEnv() キー解決により親 .env のキーが再起動なしで動作)。1 つのコア(runOpenAICompatible)が runOpenAI(api.openai.com)と runQwen(Alibaba DashScope の OpenAI 互換モード;中国本土ホストは raw .env で QWEN_BASE_URL によりエンドポイントを上書き)を支える。SDK なし、任意の CLI 実行なし — 親プロジェクトは CLI 非依存のまま(Claude Code · Codex · Gemini · OpenCode · Qwen · Copilot · Kimi);これはヘッドレスな API キー経路のみを拡張する。OpenAI/Qwen のテールは全評価サーフェスに配線済み:/api/evaluate、/api/deep、/api/mode/:slug、/api/auto-pipeline SSE — auto の優先を保持するため Anthropic(インライン)+ Gemini(サブプロセス)分岐の後で参照され、Anthropic が使うのと同じバンドル済みコンテキストのインライン化を行う。env-config.mjs:QWEN_API_KEY(秘匿)+ QWEN_MODEL(非秘匿)を KNOWN_KEYS/KEY_GROUPS.core に追加;LLM_PROVIDERS と providerOrder() を拡張;OPENAI_API_KEY は今や第一級のヘッドレス・プロバイダ・キー(以前は保存のみ)。#/config API キータブ:LLM_PROVIDER セレクトに openai/qwen を追加;新しい QWEN_API_KEY + QWEN_MODEL フィールド(キュレートされた qwen-max/qwen-plus/qwen-turbo/qwen2.5-* リスト);タブ先頭の新しいノートが CLI 非依存の親 vs web-ui のヘッドレス評価と OR 順を説明する。8 ロケール全体に新しい i18n キー。test: tests/openai.test.mjs(新規、9 ケース、CI 隔離):OpenAI/Qwen 成功 + ブロック配列コンテンツ、Bearer 認証、既定および QWEN_BASE_URL 上書きエンドポイント、4xx/5xx/不正形式、max_tokens クランプ、タイムアウト、effectiveEnv キー検出、キー無漏洩カナリア。tests/provider-selector.test.mjs は v1.55.0 の providerOrder/LLM_PROVIDERS/SECRET サーフェス + OpenAI/Qwen テール配線向けに更新。748 → 757。feat(llm) · test: tests/openai.test.mjs · test: tests/provider-selector.test.mjs。詳細は CHANGELOG.md。
[1.54.10] — 2026-05-18
fix(auto-pipeline): SSE クライアント切断の衛生 — 不安定な Playwright e2e ジョブを潰す。 Playwright e2e ジョブが断続的に赤になっていた(個別テスト 32/32 通過するが not ok 2 - tests/playwright-smoke.mjs):#/auto SSE ストリームが進行中にページを閉じると、サーバーの次の res.write() が EPIPE/"aborted" で拒否され、—レスポンスに 'error' リスナがないため— Node がそれを uncaughtException に昇格させ、node:test が “asynchronous activity after the test ended” として報告した。auto-pipeline.mjs の openSse() は今や no-op の res.on('error') を登録し、send() を res.writableEnded || res.destroyed で保護する(try/catch で包む)— 消えたクライアントは例外ではなく想定内である。これは単なるテスト修正ではなく、正しい本番 SSE 衛生である。tests/playwright-smoke.mjs:Cmd+K テストは実際の外向き URL(https://example.com/jobs/123)を使っていたがモーダル出現を待つだけだったため、closePage() がテスト終了後にサーバーの進行中の safeGet() を中断していた。今やパイプラインが終端状態に達するまで待つ(閉じる前に fetch が正常に解決するように)。共有 closePage() ヘルパ(window.stop() してから閉じる)と after フックの server.closeAllConnections() は多層防御として残る。検証:連続 8/8 グリーン実行(6× node --test + 2× browser-smoke)、以前は ~2 回に 1 回赤。tests/auto-pipeline.test.mjs +1 静的ケースで openSse 切断衛生コントラクトを固定(res.on('error') リスナ + writableEnded||destroyed ガード + try で包んだ書き込み)。747 → 748。fix(auto-pipeline) · test: tests/auto-pipeline.test.mjs。詳細は CHANGELOG.md。
[1.54.9] — 2026-05-18
fix(llm): リクエスト時に親 .env の LLM キーを尊重する — 古い/無効なプロバイダへの誤ルーティングを停止。 ANTHROPIC_API_KEY が構成済みプロバイダであっても、ライブ評価が “Gemini API error: API key not valid” で失敗することがあった。根本原因: hasAnthropicKey() / hasGeminiKey()(および runAnthropic のキー/モデル検索)が起動時の process.env スナップショットのみを読んでいた。サーバー起動後に Anthropic キーが親 .env に追加されても、実行中プロセスはそれを決して見ず → Anthropic 検出が false になり、評価は process.env に実際にあった古いキー(しばしば古く無効な GEMINI_API_KEY)へフォールバックした。Gemini 実行パス(親 Node サブプロセス)は既に親のライブ .env を読んでいたため、両プロバイダがキーを一貫しない形で解決していた。env-config.mjs の新しい effectiveEnv(key, envFilePath): 空でない process.env 値が優先(シェル export と POST /api/config のライブ適用をカバー)、そうでなければ現在の親 .env ファイルを参照する。anthropic.mjs は今や ANTHROPIC_API_KEY、ANTHROPIC_MODEL、Gemini キーチェックをこれ経由で解決するため、親 .env に設定されたキーがサーバー再起動なしで尊重され、キー検出がリクエストが実際に送るキーと常に一致する。プロバイダ順序は不変(auto → Anthropic-次に-Gemini);これは検出のみを修正する。キーは決してログ出力も返却もされない(REVIEW-B4 無漏洩テストは引き続き通過)。tests/anthropic.test.mjs は CI 隔離向けに書き直し(temp CAREER_OPS_ROOT、動的 import)、正確なバグを再現する 2 つの新ケースを含む(キーが親 .env のみ → 検出される;process.env が未設定のとき runAnthropic が親 .env のキー + モデルを送信)。tests/env-config.test.mjs +3 effectiveEnv ケース(process.env の優先、空文字列を未設定として扱う .env フォールバック、ファイル欠如 / キー欠如 / パスなし → undefined)— 新分岐の 100%。742 → 747。fix(llm) · test: tests/anthropic.test.mjs · test: tests/env-config.test.mjs。詳細は CHANGELOG.md。
[1.54.8] — 2026-05-18
feat(config): Modes フィールドフォームが(空/スタブのファイルでも)常に career-ops.org のフィールドガイダンス付きで正規スキーマをレンダリングする。 v1.54.3 の Modes フィールドフォームは既存の ## セクションのフィールドのみをレンダリングしていた — そのため新規・空・スキーマ非準拠の modes/_profile.md(例:よくある 1 行スタブ)では “No ## sections found — use the raw editor below.” にフォールバックし、ユーザーはフィールドを得られなかった。ユーザー要望(“разбей по полям … описание полей возьми из career-ops.org/docs”)により、フォームは今や常に文書化された順序で 5 つの正規フィールドをレンダリングする(Target Roles, Adaptive Framing, Exit Narrative, Comp Targets, Location Policy)。ファイルにあれば事前入力され、なければ空だが編集可能 — そのため真新しいプロファイルもフォームだけで完全に記入できる。各フィールドは career-ops.org 正規 Quick Start §Step-5 由来の説明(Target Roles / Adaptive Framing / Exit Narrative / Comp Targets / Location Policy に何を入れるか)を表示し、スクリーンリーダー向けに aria-describedby で配線される。見出しバリアント許容:テンプレートの ## Your Target Roles(等)は ## Target Roles と同じ正規フィールドにマップされるため、テンプレートもサーバースキャフォールド慣習もフォームを壊さない。collect() は今やタグ付きペイロードである:レンダリングされた見出しがファイルの既存の見出しと正確に一致するときの非破壊的な { sections } マージ(前文 + 触れていない + カスタムセクションがバイト安定で残る)、またはファイルにスキーマが欠けていたときにスキーマ準拠ドキュメントをブートストラップ/正規化する { markdown } 全ファイル再構築。再構築パスは config.js で確認ゲート付き(親ファイルを置き換える — WS2 #4 破壊的保存不変条件)、既存の前文(または文書化されたデフォルト)を保持し、非正規セクションを verbatim で維持する。8 ロケール全体に 6 つの新 i18n キー(config.modesDescTargetRoles … config.modesDescLocationPolicy + config.modesFormRebuildBody)。tests/modes-form.test.mjs は v1.54.8 契約向けに書き直し:スキーマ + 正規順序、config.js のペイロード/確認配線、8 ロケールでの各フィールドの文書由来説明の存在、canonicalKey “Your X” 許容、リストのラウンドトリップ安定性、ブートストラップ常時レンダリング保証、そしてデータ安全性を備えたタグ付き sections-vs-markdown の collect()。実際の親スタブファイルに対してライブ検証済み(5 フィールド + 説明が表示、コンソールエラー 0)と隔離スタブフィクスチャ(記入 → 確認ゲート保存 → 5 つの正規セクションすべて永続化)。feat(config) · test: tests/modes-form.test.mjs。詳細は CHANGELOG.md。
[1.54.7] — 2026-05-18
fix: W-001 — コード/スタイルのアセット + SPA シェルが Cache-Control: no-store で配信されていた(デプロイ衛生)。 SPA は api.js / router.js / 各ビューをバージョンクエリ文字列なしの素の <script src> でロードし、ビルドステップがない(コンテンツハッシュなし)ため、デプロイ後にブラウザがキャッシュされた古いバンドルを数時間配信し続けることがあった → クエリ文字列ルートで stale-cache 404(v1.29.2 回帰中にライブ観測;回帰ラン W-001)。server/index.mjs は今や express.static の setHeaders フック経由で .js / .mjs / .css / .html に Cache-Control: no-store を設定し、SPA シェルの catch-all(sendFile を使い setHeaders を回避する)にも明示的に設定するため、ブラウザはルーティングを駆動するコードを常に再検証する。非コードの静的アセットは express.static のデフォルトキャッシングを維持する。セキュリティヘッダー(CSP / nosniff / frame-deny / referrer-policy)は不変 — 既存の security-headers スイート(8 ケース)が新テストと並んでグリーンで実行され検証済み。テストファイル tests/asset-cache-control.test.mjs を +1 — 4 ケース(JS アセット no-store、CSS no-store、静的 index.html no-store、SPA catch-all のディープルートシェル no-store)、隔離された CAREER_OPS_ROOT に対して実アプリをブート。さらに tests/playwright-smoke.mjs のフレーキーな teardown 修正(別の test(e2e) コミット):auto-pipeline の SSE スモークテストが今や finally でリーダーをキャンセル + fetch を中断し、after フックが残存ソケットを強制クローズして、v1.54.6 の Playwright e2e ジョブを赤くしていた teardown 後の “Error: aborted” を排除。738 → 742。fix · test: tests/asset-cache-control.test.mjs。詳細は CHANGELOG.md。
[1.54.6] — 2026-05-18
fix(a11y): S-7 — #/help の back-to-top ボタンが正規のセレクタークラス back-to-top を持つ。 #/help のフローティング back-to-top ボタンは正しく動作していたが(ライブ検証済み)、そのクラスリスト(btn btn-primary help-back-top)は spec §2 #28 テストが狙う .back-to-top セレクター規約の外にあった — より厳格なセレクターならフレーキーだったはず(回帰ラン S-7、“イージーウィン”)。ボタンは今や正規の back-to-top クラスも持つ。純粋に加算的で CSS no-op:help-back-top(既存の CSS フック)は変わらず、back-to-top には CSS ルールがない — 安定したテスト/自動化ハンドルにすぎない。ライブ検証済み:document.querySelector('.back-to-top') がボタンを解決、aria-label 維持、コンソールエラー 0。tests/help-nav-a11y.test.mjs の既存 #12 ケースを、back-to-top ボタンのクラスリストが正規 back-to-top セレクターを含むというアサーションで拡張(新ファイルなし)。fix(a11y) · test: tests/help-nav-a11y.test.mjs。詳細は CHANGELOG.md。
[1.54.5] — 2026-05-18
fix(a11y): F-V54-C — #/batch の TSV エディターにアクセシブルな名前がある。 #/batch の TSV <textarea> は aria-describedby 経由で配線されたヒントはあったがアクセシブルな名前がなかった — <label htmlFor> なし、aria-label/aria-labelledby なし(回帰ラン F-V54-C; WCAG 1.3.1 Info & Relationships / 4.1.2 Name, Role, Value)。aria-describedby は名前ではなく説明を供給するため、スクリーンリーダーはラベルのない “edit text” を読み上げた。textarea は今や新しい i18n キー batch.tsvAria 経由の aria-label を持ち、すでに *Aria キーを使う兄弟のラン制御入力と一貫する;既存の describedby ヒントは保持される。ライブ検証済み:aria-label 存在 + ローカライズ、aria-describedby 維持、コンソールエラー 0。新しい i18n キー batch.tsvAria を 8 ロケール全体に追加。テストファイル tests/batch-tsv-accessible-name.test.mjs(2 ケース:batch-tsv ブロックが describedby ヒントを保ちつつ t(batch.tsvAria) 経由の aria-label を持つ;batch.tsvAria が 8 ロケールで定義)を +1;736 → 738。fix(a11y) · test: tests/batch-tsv-accessible-name.test.mjs。詳細は CHANGELOG.md。
[1.54.4] — 2026-05-18
fix(a11y): F-V54-B — #/pipeline の行アクションボタンにアクセシブルな名前がある。 #/pipeline の行ごとの ▶(評価)および ✕(削除)ボタンは title 属性のみのアイコン専用だった(回帰ラン F-V54-B; WCAG 4.1.2 Name, Role, Value)。title は信頼できるアクセシブルな名前ではないため、スクリーンリーダーユーザーは区別のつかない “button” の長い連なりを聞き、削除がどの行に当たるか判別できなかった。両ボタンは今や、新しい shortUrl() ヘルパー経由のコンパクトな URL(host + …/ + 末尾 2 つのパスセグメント;パース不能な入力には末尾スライスのフォールバック)で曖昧さを解消した明示的な aria-label を持ち、a11y ツリーは例えば “Delete: hh.ru/…/vacancy/12345” と読み上げる。新しい i18n キーなし — common.delete / pipe.evaluateBtn + URL を再利用。ライブ検証済み:1385 行、各ボタン名が行ごとに一意、コンソールエラー 0。テストファイル tests/pipeline-row-action-names.test.mjs(4 ケース:両ボタンが shortUrl(url) で配線 + ちょうど 2 つのそのラベル、shortUrl が使用前に宣言、同一ホスト別求人の URL は collapse しない、ベアホスト / パース不能 / 空のフォールバック)を +1;732 → 736。fix(a11y) · test: tests/pipeline-row-action-names.test.mjs。詳細は CHANGELOG.md。
[1.54.3] — 2026-05-18
feat(config): #/config の “Modes” タブ向け構造化フィールドフォーム(もう生の markdown ではない)。 “Modes” タブは modes/_profile.md を ## セクションごとに 1 つの生の <textarea> として編集していた(v1.36.0 のセクション単位の粒度)。ユーザー要望により、今や文書化されたスキーマから派生した構造化フィールドフォームをレンダリングする(career-ops.org Quick Start §Step-5): Target Roles / Adaptive Framing / Comp Targets → 追加/削除可能な反復ラベル付きライン入力(フィールドごとにロール/アングル/comp 1 行、+ Add line / 行ごとに aria-label 付き ✕); Exit Narrative / Location Policy → 単一のラベル付き散文 <textarea>。各フィールドは i18n セクション名を持つ <label htmlFor> で紐付けられた実コントロール。新しい public/js/lib/modes-form.js(window.ModesForm)が parse → render → collect() のロジックを保持する;これは既存の PUT /api/modes/_profile { sections } マージ経路へ供給されるため、前文、順序、およびフォームが触れないすべてのセクションがバイト安定で残る(マージ-非置換、サーバー強制)。データ安全性: 本文が純粋な箇条書きでない正規リストセクション(ユーザーがそこに散文を入れた)および非正規 ## セクションは、説明ノート付きのラベル付きそのままの <textarea> にフォールバックする — 任意のコンテンツはそのまま round-trip し、決して暗黙に再構成されたり失われたりしない。Round-trip 安定性を実証:serialise(parse(body)) が同一に再パースされる。全ファイル生 markdown エディターは、セクションの追加/削除と前文編集のための確認ゲート付き Advanced ディスクロージャーとして残る(WS2 #4 破壊的保存ゲートは変更なし)。8 ロケール全体で新しい i18n キー 10 個(config.modesTargetRoles … config.modesUnknownNote)。テストファイル tests/modes-form.test.mjs(7 ケース)を +1;725 → 732。隔離された CAREER_OPS_ROOT フィクスチャに対してライブ検証済み:正規セクション 5 個がフィールドとしてレンダリング + カスタムセクション 1 個がラベル付きフォールバックとして、編集して保存の round-trip が前文 + カスタムセクションを保持、コンソールエラー 0。feat(config) · test: tests/modes-form.test.mjs。詳細は CHANGELOG.md。
[1.54.2] — 2026-05-18
feat(config): #/config の OpenAI / Codex モデルセレクター。 #/config には OpenAI / Codex モデルを選ぶ手段がなかった — OPENAI_API_KEY は親のマルチ CLI(Codex / OpenCode)フロー用に既に公開されていたにもかかわらず、ドロップダウンは ANTHROPIC_MODEL と GEMINI_MODEL にしかなかった。今や OPENAI_MODEL はファーストクラスの環境キー:env-config.mjs の KNOWN_KEYS(OPENAI_API_KEY の直後に並べる)と core キーグループに追加し、SECRET_KEYS には意図的に含めない — 資格情報ではなくモデル id なので、決してマスクされない。config.js はキュレートされた OPENAI_MODELS リスト(デフォルト gpt-5-codex、続いて gpt-5 / gpt-5-mini / gpt-4.1 / o4-mini / o3)と、OpenAI キーの直後にレンダリングされる OPENAI_MODEL <select> フィールドを追加し、Anthropic/Gemini のモデルフィールドを正確に踏襲する。8 ロケール全体で新しい i18n キー config.openaiModel + config.openaiModelHint。テストファイル tests/openai-model-selector.test.mjs(4 ケース)を +1;721 → 725。ライブ検証済み:#/config → 6 オプションの OPENAI_MODEL select、デフォルト gpt-5-codex、ラベル紐付け済み、コンソールエラー 0。feat(config) · test: tests/openai-model-selector.test.mjs。詳細は CHANGELOG.md。
[1.54.1] — 2026-05-18
fix(a11y): F-V54-A — #/cv を単一の <h1> に。 CV markdown 自身の # Name が、ページタイトルの <h1>CV</h1> の隣に 2 つ目 のトップレベル <h1> としてレンダリングされていた(回帰実行 F-V54-A;WCAG 1.3.1 情報と関係性 / 2.4.6 見出し)。cv.js は今や CV プレビューの全注入ポイント(初期レンダー、ファイルインポート時の更新、エディタのライブ同期)を、見出しを 1 レベル下げる(h1→h2 … h6→role="heading" aria-level="7")スコープ付きの cvMd() を通すようになり、ページは正確に 1 つの <h1> を保つ。cv.js に意図的にスコープ — UI.md は help/reports/deep/evaluate で共有され、それぞれが見出しを独自に管理するため。テストファイル tests/cv-single-h1.test.mjs(4 ケース)を +1;717 → 721。ライブ検証済み:#/cv → <h1> 1 個、ユーザーの # Name は今や <h2>、コンソールエラー 0。fix(a11y): F-V54-A · test: tests/cv-single-h1.test.mjs。詳細は CHANGELOG.md。
[1.54.0] — 2026-05-18
WS10 — canonical-docs 再検証 + help バンドルの H3 パリティ(最終の収束リリース)。 CHANGELOG/構造の CI ゲートは H2 しか検査していなかったため、docs/help/en.md は静かに 70 個の H3 サブセクションへ漂流した一方、7 つのローカライズ済みバンドルは 68 のままだった — ギャップは §17(「Reference adapters」テーブル + 「Common pitfalls」リスト、英語のみ)。両方が今や 7 言語すべてに翻訳され(アダプタのファイル名 / リンク / 識別子はバイト同一に保持);8 バンドルすべてが今 17 H2 / 70 H3。help-ru-config-section.test.mjs の新しい H3 パリティゲートがこれをロックする(716 → 717)。canonical-docs-coverage.test.mjs 7/7 が、help が依然として career-ops.org/docs の 5 ガイドすべてを反映していることを確認;WS2 の UX 監査(v1.41→v1.52 の 40 件)が各画面を docs と照合 — 乖離なし。docs/sdd/CONVENTIONS.md を v1.54.0 に更新(テスト合計、H3 パリティゲート、ファイルサイズの外れ値、新しいアクセシビリティ規約セクション)。WS0–WS10 完了;残るは WS11 のみ。fix(docs): WS10 canonical re-validation + H3 parity · test(help): H3-parity gate。詳細は CHANGELOG.md。
[1.53.0] — 2026-05-18
WS9 — シェル表面のテストピラミッド(最後の未テスト層)。 4 つの bin/*.sh スクリプトと .githooks/pre-commit フックはカバレッジがゼロだった;新しい tests/sh-files.test.mjs が 10 件を追加し、bash -n/sh -n 構文、shebang + 実行ビット、そして他のワークストリームが依存する振る舞いの契約を固定する:career-ops-ui.sh — help は 0 で終了し shell-source の漏れがない(v1.40.0 リグレッションガード)、未知の verb は 2 で終了、usage() は heredoc;start.sh — NO_OPEN を尊重、Node ≥ 18 を要求、ブラウザの前面化を scripts/open-dashboard.mjs に委譲(v1.43.0 ガード);setup.sh — strict モード、SKIP_START、両リポジトリを clone;run_all.sh — --quick/--no-e2e のパースと 4 スイート;.githooks/pre-commit は WS7 のレビュアを exec し、いかなるシェルファイルも git --no-verify を呼ばない(CLAUDE.md ハードルール #7 ガード);install-hooks.mjs が core.hooksPath を配線する。docs/architecture/TESTING.md — ピラミッド図にシェル表面のベース層を追加 + v1.53.0 の合計ノート(716 node --test ケース / 90 ファイル + 4 E2E サーフェス)。706 → 716。詳細は CHANGELOG.md。
[1.52.0] — 2026-05-18
WS2 LOWs #33–#40 — バッチ磨き上げ一掃(UX 監査キューを締める)。 低重大度の 8 件。fix(a11y/i18n): WS2 LOW batch — #33:#/dashboard — ヘッダーの 3 つの CTA が不揃いだった(2 つだけ先頭アイコンあり);「Open Pipeline」が今や 📋 を持ち、3 つすべてが揃う。#34:#/profile — アーキタイプの fit/level が曖昧な 2 つのチップとして描画されていた;今は接頭辞付き(Fit: / Level:)で対応する aria-label を持つ。#35:#/health — Run-doctor / verify の toast が doctor.mjs の生文字列を表示していた;今は i18n キー化。#36:#/health — チェック結果はフラットな <div> の連なりだった;今は role=list の <ul>/<li> で、ステータスバッジは aria-label="<check>: <status>" を持つ。#37:#/reports — レポートカードはマウス専用の <div onClick> だった;今は role=link + tabindex + Enter/Space ハンドラ + aria-label。#38:#/activity — ページネータのコメントは「200」と言うがコードは 500 を要求していた;CAP 定数へ整合させ、500 上限が古い履歴を切り詰めると role=note の通知が現れる。#39:#/batch — プレースホルダ文言が英語ハードコードだった一方で aria-label は localized だった;4 つが今は i18n キー化。#40:モードページは非同期プローブ後に主ボタンを黙ってラベル変更していた;今は丁寧な role=status 領域がそれをアナウンスする。新規 i18n キー 10 件 × 8 ロケール({n} 保持);テスト +9:test: tests/low-sweep.test.mjs。697 → 706。WS2 の UX 監査キュー(v1.41→v1.52 の #1–#40)を締める;次は WS9 → WS10 → WS11。詳細は CHANGELOG.md。
[1.51.0] — 2026-05-18
WS2 #13 + #14 + #18 + #19 + #20 — #/auto と #/evaluate の feedback/i18n 一掃。 UX 監査の 5 件の指摘。fix(a11y/ux): auto+evaluate — busy state, actionable HTTP errors, clipboard fallback, aria-live result, spinner-guarded submit — #13:#/auto の Run ボタンは今や単に無効化されるのではなくビジー状態(is-loading + aria-busy +「Running…」)を表示する。#14:失敗した HTTP リクエストは今やステップ上の操作可能な i18n メッセージと toast({n} 付きの auto.httpFail)を提示する(以前は素っ気ない「HTTP 500」だった)。#18:手動モードの「Copy prompt」は今や非同期 Clipboard API を execCommand フォールバック付きで使い、偽の「Copied」ではなく実際の失敗を toast する。#19:evaluate の結果コンテナは今や role=status aria-live=polite であり、長い LLM 呼び出しがスクリーンリーダーへアナウンスされる。#20:Evaluate ボタンは UI.withSpinner でラップされた(以前は素の onClick: run で重複送信を許していた)。新規 i18n キー 3 件 × 8 ロケール;テスト +6:691 → 697。さらにテストのみの修正(コミット 7f8e250):e2e pipeline-delete のティアダウンが v1.48 以前のネイティブ confirm 経路上にあった;API DELETE へ切り替え(fix(test): … — CI の Playwright-e2e が赤だった;製品の回帰ではない)。詳細は CHANGELOG.md。
[1.50.0] — 2026-05-18
WS2 #12 + #27 + #28 — help ナビゲーションのアクセシビリティ。 17 セクション・90+ 見出しのガイドにおける #/help の UX 監査の 3 件の指摘を help.js で修正。fix(a11y): help — single h1, labelled+filterable TOC, focus-on-anchor, back-to-top — #28:ドキュメントの markdown が独自の # Title で始まり、すでにヘッダーが正規の h1 を供給しているページに 2 つ目の <h1> を生んでいた;今は記事のすべての <h1> が除去され、h1 はちょうど 1 つで、階層は <h2> セクションから綺麗に始まる。#27:TOC の <nav> は名前の無いランドマークだった(ページ上にラベルの無い <nav> が 2 つ);今は aria-label(help.toc)を持ち、TOC エントリをクリックするとビューポートのスクロールだけでなくフォーカスがセクション見出しへ移る(tabindex=-1 + focus())。#12:長いドキュメント内で何かを見つける手段が無かった;TOC の上の type=search フィルタが見出しテキストでエントリをライブに絞り込み、スクロール後に aria-label 付きのフローティング「Back to top」ボタンが現れ、先頭に戻りページの <h1> にフォーカスを戻す;その scroll リスナーは #/help から離れる hashchange で除去される。新規 i18n キー 2 件 × 8 ロケール — help.tocFilter、help.backToTop;テスト +6:test: tests/help-nav-a11y.test.mjs。685 → 691。詳細は CHANGELOG.md。
[1.49.0] — 2026-05-18
WS2 #10 + #11 + #25 + #26 — tracker テーブルのアクセシビリティとソート。 #/tracker における UX 監査の 4 件の指摘を tracker.js で修正。fix(a11y): tracker headers, sortable table, localized fix labels, empty state — #10:アクション列のヘッダーは空文字列で、行ごとの Report ボタンには文脈が無かった;今はすべての <th> が scope=col を持ち、アクションヘッダーと Score/PDF ヘッダーは i18n キー化され(空またはハードコードされた英語だった)、Report ボタンは会社名付きの aria-label(<report> — <company>)を得る。#11:ソート手段の無い tracker;Date / Score / Status ヘッダーは今や <th> 内のキーボード操作可能なソートボタンで aria-sort(none/ascending/descending)を持つ;sorted() コンパレータ(score は数値、date/status はロケール比較)がページネーションの前に走り、クリックで方向をトグルしページャをリセットする。#25:track.normalize/dedup/merge は最もリスクの高い破壊的コントロールであるにもかかわらず 8 ロケールすべてで同一の英語だった(data/applications.md をその場で書き換える)— 今は適切にローカライズされ、さらに title ツールチップを追加。#26:ゼロ行の初回実行が過剰フィルタのリストと同じ「no match」メッセージを表示していた;rows.length === 0 は今や独立した空状態(タイトル + 本文 +「Open pipeline」CTA)をレンダリングする。新規 i18n キー 7 件 × 8 ロケール + 3 件を再ローカライズ;テスト +6:test: tests/tracker-a11y-sort.test.mjs。677 → 683。詳細は CHANGELOG.md。
[1.48.0] — 2026-05-18
WS2 #8 + #22 — pipeline:フォーカストラップされた確認 + プレビューのアクセシビリティ。 #/pipeline における UX 監査の 2 件の指摘を pipeline.js で修正。fix(a11y): pipeline UI.confirm() + live preview region — #8:#/pipeline の 3 つのアクションはネイティブ confirm() を使用していた(フォーカストラップされない):プレビューペインの Delete、行ごとの ✕ 削除、「Evaluate first」。いずれもフォーカストラップされる UI.confirm()(v1.44.0 のインフラ)を経由するようになった — 2 つの削除は danger:true(Cancel がデフォルト)、「Evaluate first」は danger:false;pipeline.js にネイティブ confirm() はもう残っていない。#22:previewPane にはライブロールが無く、fetch 失敗が previewBody に詰め込まれて誤解を招く <pre>「preview」としてレンダリングされていた;今は aria-label 付きの role=region aria-live=polite となり、失敗時は別の previewError を設定して独立した role=alert ブロックとしてレンダリングする((再)選択時およびアクティブ行の削除時にクリア)。新規 i18n キー 4 件 × 8 ロケール;テスト +5:test: tests/pipeline-confirm-preview.test.mjs。672 → 677。詳細は CHANGELOG.md。
[1.47.0] — 2026-05-18
WS2 #7 + #30 + #31 + #16 — 未バインドラベルのアクセシビリティ一掃。 フォームコントロールにプログラム的ラベルが無かった UX 監査の 4 件の指摘(WCAG 1.3.1 / 3.3.2 / 4.1.2)を、すべてバインド。fix(a11y): bind every swept form control to an accessible name — #7 scan.js:dry-run チェックボックスと company-select ドロップダウンには for の無いラベルがあった;既存の id に合わせて htmlFor を追加。#30 deep.js:company / role 入力に未バインドのラベルがあった;id + htmlFor を追加(deep-company、deep-role)。#31 apply.js:url / jd に未バインドのラベルがあった;id + htmlFor を追加(apply-url、apply-jd)。#16 cv.js:主要な markdown <textarea> にアクセシブル名が無かった;表示されている「Markdown」見出しへ aria-labelledby でバインド — スクリーンリーダー名と画面上の見出しが一致、新規 i18n キーなし。batch.js / mode-page.js で既に標準の明示的 label[for]↔control[id] パターンを使用;新規 i18n キーなし;挙動の変更ゼロ。テスト +5:test: 667 → 672。詳細は CHANGELOG.md。
[1.46.0] — 2026-05-18
WS2 #5 + #6 + #21 + #24 — scan SSE のアクセシビリティ。 #/scan における UX 監査の 4 件の指摘を scan.js で修正。fix(a11y): scan SSE — live-log region, Stop, run-state, error banner — #5:ストリーミングコンソールは今や role=log aria-live=polite(+ aria-label、tabindex=0、キーボードでスクロール可能)で、別の視覚的に隠された assertive な role=status 領域がターミナルイベント(完了 / 失敗 / 停止)をアナウンスする。#6:Stop ボタンが進行中の EventSource を閉じ(es.close())、結果ポーリングをキャンセルし、状態をリセットする。Stop は scan 実行中のみ表示。#21:scan 実行中は Scan ボタンを無効化 + aria-busy を付与し、Stop を表示する。両方のストリームパス(単一フェーズの streamTo と多フェーズの runScanAll — 後者は終端 done、final !== false でのみ実行を終える)で適用。#24:SSE 障害はもはや 3.5 秒のトーストだけではなく、永続的な role=alert バナーがエラーを再試行アクション付きで表示(直近の実行関数を再呼び出し)し、次回の実行でクリアされる。i18n キー 8 件を新規追加 × 8 ロケール;テスト +7:test: tests/scan-sse-a11y.test.mjs。660 → 667。詳細は CHANGELOG.md。
[1.45.0] — 2026-05-18
WS2 #3 — #/config タブ:完全な WAI-ARIA Tabs パターン。 #/config の 3 つのタブ(API keys / Profile / Modes)はクリックのみで起動する素の <button class="tab-btn"> で、role も aria-selected もキーボードモデルもなかった(UX-audit HIGH #3、WCAG 4.1.2 / 2.1.1)。fix(a11y): config.js tabs implement role=tablist/tab/tabpanel — 今は aria-label 付きの role=tablist コンテナ;各タブは role=tab + id + aria-controls + aria-selected(activate() 内で同期)+ ローテーション tabindex(アクティブ 0 / その他 -1);パネルは role=tabpanel + tabindex=0 + アクティブタブを追う aria-labelledby。完全なキーボードナビ:←/→/↑/↓(ラップ)+ Home/End がフォーカス移動と起動の両方を行う。レガシーの .tab-btn.is-active CSS フックは保持。i18n キー +1 × 8 ロケール(config.tablistLabel);テスト +7:test: tests/config-tabs-aria.test.mjs。さらにテストのみの修正:fix(test): retarget 2 stale auto-pipeline smoke tests — v1.34 以前の Playwright-e2e smoke テスト 2 件が、ダッシュボードの「Auto-pipeline」ボタンが v1.34.0 で開かなくなった一時モーダル(→ Router.go('/auto'))をアサートしており、別の Playwright-e2e CI ジョブで赤のままだった。#/auto 画面に再ターゲット。653 → 660。詳細は CHANGELOG.md。
[1.44.0] — 2026-05-18
WS2 #4 + #9 — 親プロジェクトファイルの破壊的上書き前にフォーカストラップ付き確認。 UX 監査の HIGH が 2 件、いずれもデータ消失: (#4) config.js の saveProfileRaw/saveModesRaw は親の config/profile.yml / _profile.md を確認なしでまるごと置換していた; (#9) tracker.js の Normalize/Dedup/Merge は親の data/applications.md を確認なしでその場で書き換えていた。fix(a11y/safety): UI.confirm() gate before whole-file parent overwrites — public/js/api.js に新しい UI.confirm() を追加。既存の WAI-ARIA モーダル基盤を再利用したフォーカストラップ付きダイアログで(_onClose フックにより Esc / backdrop / × / Cancel のすべての解除経路が false を解決;フォーカスは既定で Cancel;Promise<boolean> を返す;ネイティブ confirm() ではない)、3 つの破壊的呼び出しはすべて書き込み前にゲートされる。新規 i18n キー 8 件 × 8 ロケール({op} プレースホルダは逐語的に保持);テスト +8:test: tests/confirm-gate.test.mjs、644 → 652。詳細は CHANGELOG.md。
[1.43.0] — 2026-05-18
ユーザー要望 — career-ops-ui open + autostart によるブラウザ前面化。 setup/run の後、ブラウザが既に起動しているとむき出しの open/xdg-open ではダッシュボードのタブが背面に残り、ユーザーが探す羽目になっていた。feat(cli): career-ops-ui open — open AND raise the dashboard tab — 新しい scripts/open-dashboard.mjs が HOST/PORT から URL を構築し(0.0.0.0 バインドを loopback に書き換え)、必要なら /api/health を待ち、既定ブラウザを開いてから強制的に前面化する — macOS は osascript で起動中の Chrome/Brave/Edge/Safari/Arc/Firefox のいずれかをアクティブ化、Linux は xdg-open+wmctrl、Windows は start。career-ops-ui open 動詞として公開(エイリアス dash、focus)。bin/start.sh の autostart はこれに委譲し、タブが自動的に前面化される;NO_OPEN=1 は headless/CI 起動で auto-open を無効化する。README ×8 + help §1 ×8 を更新;テスト +8:test: tests/open-dashboard.test.mjs、636 → 644。詳細は CHANGELOG.md。
[1.42.0] — 2026-05-18
WS2 修正 #2 — デッドルート #/portals → config ディープリンク。 #/portals は未登録ルートで 404 ビューを描画していたが、ポータルソース管理用にブックマーク/手入力されうる妥当な URL だった(UX 監査 HIGH #2)。fix(router): #/portals 404 → alias to config + Regional-sources deep-link — router.js の ALIASES に portals: 'config' を追加(settings→profile と同じブックマーク安定化パターン)、これで config ビューに解決され config ナビ項目がアクティブになる。Regional-sources グループが存在する場合、ビュー(config.js)が #/portals ハッシュを検出し、その <details> グループを強制展開・スクロール表示し、その summary にフォーカスを移動(既定の h1 フォーカスを上書き)、ユーザーはポータルソース操作部にちょうど着地する;エイリアス単独で空の地域グループを描画することはない。help-bundle §5 × 8 にショートカット注記を追加;router テスト +1:test(router): portals→config alias guarantee を router.test.mjs に追加、635 → 636。詳細は CHANGELOG.md。
[1.41.0] — 2026-05-18
WS2 — シニア UX/ユーザビリティ監査 + 横断的フォーカス管理修正。 10 年以上のヒューリスティック監査(Nielsen × WCAG 2.2 AA × プロジェクト規約)で全 17 ルートを精査し、重大度順の 40 件キューを生成(.planning/.../UX-AUDIT.md);HIGH→MEDIUM→LOW をリリースごとに 1 修正ずつ出荷。本リリースは横断的 HIGH の第 1 位に着地。修正: fix(a11y): move focus to the new view on every route change — router.js render() は hashchange ごとに #content を差し替えるがフォーカスを移動しなかったため、キーボード/スクリーンリーダー利用者が破棄されたノードに取り残され位置を失っていた(WCAG 2.4.3 Focus Order / 4.1.3 Status Messages — 横断的で全 17 画面に影響);新しい focusNewView(content) が新ビュー先頭の h1/.page-title にフォーカス(簡潔な SR アナウンス + 正しいフォーカス順)、必要なら見出しをフォーカス可能化(tabindex=-1)し #content にフォールバック;skip-link と競合しないよう初回描画はスキップ;成功・エラー両レンダパスに配線;ライブ検証済み:遷移後 document.activeElement は新ビューの H1.page-title。テスト: test(router): focus-management static guarantees — router.test.mjs に 4 ケース(ヘルパ定義、見出しターゲット + content フォールバック、初回描画スキップガード、≥2 呼び出し箇所);631 → 635。詳細は CHANGELOG.md。
[1.40.0] — 2026-05-18
WS8.3 — docs 実態化スイープ + career-ops-ui help 修正 + askSecret 強化。 修正: fix(cli): career-ops-ui help no longer leaks shell source — ディスパッチャはヘッダコメントを sed -n '2,12p' で出力していたが、12 行目(set -euo pipefail)はコメントではなくコードのため、career-ops-ui help(および未知動詞の使用法テキスト)が余分な set -euo pipefail 行で終わっていた;help と *) の両ケースを 2,11p(コメントブロック)に絞り込み;help は exit 0、未知動詞は exit 2 — 検証済み。fix(cli): scripts/init.mjs key entry never echoes — v1.39.0 のフォローアップで装飾的な readline 上書きマスクを実際の raw モードリーダに置換: setRawMode(true) + バッファ行により、入力/貼り付けされたキーのバイトが端末に一切届かない(scrollback / tmux / 画面共有への漏洩なし);完全な VT エスケープ FSM が CSI/SS3/OSC/DCS/SOS/PM/APC の全シーケンスを消費し、矢印・ファンクションキーが秘密を破損しない;stdin は依存性注入されるため、非 TTY フォールバックはグローバルを触らず単体テスト;AI レビュー LGTM までクリーンに反復。ドキュメント: README ×8 — 旧「ワンコマンドインストール」セクションを目立つ 「ワンコマンドで起動と初期化」 セクションに置換(curl ワンライナーに加え明示的な career-ops-ui CLI チェーン: clone → npm link → setup → init → doctor → run → help、プロバイダウィザード説明、CI 形式 --provider --anthropic-key --yes、LLM_PROVIDER ノート);8 つの README バッジを v1.22–v1.24 / tests-461–474 から v1.40.0 / tests-631 に実態化(e2e バッジは捏造カウント回避のため非数値化);help-bundle ×8 §1 — クイックスタート手引きの先頭(「A. Setup」の前)に「ワンコマンド起動 & init」コールアウトを 8 ロケール全てに追加;H2 セクションのパリティ維持(各 17 — CI ゲート緑)。テスト: test(init): non-TTY askSecret fallback — provider-selector.test.mjs に DI-stdin ケースを追加し、askSecret が非 TTY で共有グローバルを変更せず素の ask() に委譲(trim パリティ)することを検証;629 → 631。詳細は CHANGELOG.md。
[1.39.0] — 2026-05-18
WS8.2 — LLM プロバイダ選択 + OpenAI/Codex キー + 対話型 init ウィザード。 env-config に LLM_PROVIDER(auto|claude|gemini)+OPENAI_API_KEY(秘匿)。llm.mjs の 6 ゲートが _provGate() 経由で providerOrder() を参照;auto は挙動不変。#/config に select+フィールド。scripts/init.mjs は実ウィザード(検証済みパスで parent .env 書込)。7 テスト。622 → 629。README ×8/正規ドキュメント fold = WS8.3/WS10。詳細は CHANGELOG.md。
[1.38.0] — 2026-05-17
WS8.1 — 統合 CLI ディスパッチャ + doctor 動詞。 bin/career-ops-ui.sh が setup/run/doctor/init/help をルーティング。scripts/doctor.mjs は /api/health エンジンをそのまま再利用(createApp インプロセス → ターミナルレポート);必須チェック全通過時のみ exit 0。docs/sdd + help §1 ×8。6 テスト。616 → 622。README ×8 = WS8.3。詳細は CHANGELOG.md。
[1.37.0] — 2026-05-17
WS7 — git ワークフローの pre-commit AI レビュー。 決定論フロア(fail-HARD):staged .env/シークレット、diff 内キーパターン、staged ビューの .also(、node --check 失敗をブロック。AI レイヤ(fail-SOFT):CLI があり AI_REVIEW != off なら claude -p。.githooks/pre-commit + prepare で core.hooksPath を配線。--no-verify 禁止。docs/sdd。6 テスト。610 → 616。詳細は CHANGELOG.md。
[1.36.0] — 2026-05-17
WS6.3 — Modes タブ:生の塊 → セクション別エディタ。WS6 完了。 modes/_profile.md を ## セクション単位で編集(見出しごとに折りたたみ textarea)。サーバ splitProfileSections はバイト厳密;PUT { sections } は指定セクションのみマージ — プリアンブル・他セクション・順序をバイト単位で保持。未知見出し → 400。raw 経路は不変。i18n 5 キー ×8。help §2 ×8。新規テスト 6。604 → 610。WS6 完了。詳細は CHANGELOG.md。
[1.35.0] — 2026-05-17
WS6.4 — Profile 配列エディタ + WS6.2 API-keys 監査。 PUT /api/profile が { arrays } を受理({ fields } と併用可):Target roles/Superpowers(リスト)、Archetypes(name/level/fit)、Proof points(name/url/hero-metric)。同一 merge-not-replace;空行破棄;空リストはキー削除。#/config に add/remove エディタ 4 つ。i18n 6 キー ×8。監査:KNOWN_KEYS ≡ FIELDS、ギャップなし。新規テスト 7。597 → 604。詳細は CHANGELOG.md。
[1.34.0] — 2026-05-17
WS5 — ワンクリック Auto-pipeline 画面(#/auto)。 モーダルを専用・リンク可能ページへ昇格。ワンクリックで検証→取得→評価→レポート保存→トラッカー(SSE)。アクセシブルなステッパー、ディープリンク、キーなし手動モード、#/auto?url=…&go=1 リンク可。サイドバー項目;ダッシュボード ✨ ボタンはここへ。i18n 14 キー ×8。help §1 ×8 + README ×8。新規テスト 8。589 → 597。詳細は CHANGELOG.md。
[1.33.0] — 2026-05-17
WS4 — career-ops 1.8.0 パリティ監査 + location_filter。 親 scan.mjs に location_filter(#570)追加;web-ui の in-process スキャナは委譲しないため流れず。新 server/lib/location-filter.mjs がセマンティクスを忠実に複製、両スキャナに配線。help §5 ×8。新規テスト 8。581 → 589。詳細は CHANGELOG.md。
[1.32.0] — 2026-05-17
#/config Profile タブ — 生 YAML 塊 → 項目別フォーム (WS1)。 折りたたみ 3 セクション(候補者/ナラティブ/報酬)、14 スカラーパス。項目保存は config/profile.yml に マージ — アーキタイプ・プルーフポイント・独自キーをそのまま保持。Advanced に raw-YAML エスケープハッチ維持(コメント保持)。i18n 23 キー ×8。新規テスト 7。574 → 581。詳細は CHANGELOG.md。
[1.31.0] — 2026-05-17
career-ops 1.8.0 同期 — #/batch が --model + --start-from を公開。 親プロジェクトが 1.7.1 → 1.8.0 に。batch-runner.sh に --model NAME(#504)+ --start-from N 追加。web-ui は #/batch で公開(モデル・開始 # 入力)+ サーバ側 defense-in-depth 検証。i18n ×8。新規テスト 7。567 → 574。詳細は CHANGELOG.md。
[1.30.0] — 2026-05-14
#/scan 結果ページネータ — v1.12 の「最初の 200 件を表示 / 合計 N」トランケーションを置き換え。
v1.30 以前は、スキャン結果テーブルがフィルタ後の最初の 200 行で切り詰められ、「Showing first 200 of N」のフッターが表示されていました。201..N 行は UI から到達不能。v1.30.0 はキャップを UI.paginate に置換します(#/tracker / #/reports / #/activity と同じヘルパー)。PAGE_SIZE = 200 で従来の視覚密度を維持;ブースト先頭の並びがページ間で安定(全体をソート後にページ分割);フィルタ変更時にページ 1 に自動リセット。未使用の scan.shownTop i18n キーを削除(8 ロケール)。tests/scan-paginator.test.mjs に 9 件の新規ケース(静的カナリア 7 件 + 6 つの境界ケースを持つ純ロジックテーブル 1 件 + サマリ計算 1 件)。558 → 567 unit + acceptance(+9)。完全な詳細は CHANGELOG.md を参照。
[1.29.2] — 2026-05-14
ホットフィックス:🌐 Scan を source=both で実行しても EN フェーズのみ動作。RU フェーズが静かに破棄されていました。
SSE クライアント(public/js/api.js:156)が最初の done イベントで EventSource をクローズしていましたが、サーバは source=both でフェーズごとに 1 つずつ送出します。RU フェーズは開始直後にキャンセルされていました。修正:サーバは各 done に final: true|false を付与し、クライアントは final !== false の場合のみクローズします。後方互換 — final を設定しない単一フェーズの送出側は従来通りクローズ。547 → 558 unit + acceptance(+11 新規)。完全な詳細は CHANGELOG.md を参照。
[1.29.1] — 2026-05-14
5 つの RU ポータルを構成するための詳細なユーザガイドを help-bundle §5 の 8 ロケール全てに追加。
§5(Portals & sources)内に新規 ### サブセクション「ロシア系ポータルの構成 — 詳細セットアップガイド」を追加:認証・地域制限を含む 5 ソースのインベントリ表、portals.yml の場所と編集手順、5 ソースの YAML 例、negative リストの衝突と修正例、1 ソースを一時無効化する方法、🌐 Scan + SSE ログによる検証方法。§17(v1.29.0 で出荷)は開発者向けフロー、§5 v1.29.1 はエンドユーザ向けフロー。540 → 547 unit + acceptance(+7 新規)。完全な詳細は CHANGELOG.md を参照。
[1.29.0] — 2026-05-14
ロシア系ポータルスキャナが 2 → 5 ソースに拡張。レジストリ + 動的ドロップダウン。新 §17「新規ポータルの追加方法」。
- 3 つの新 RU アダプタ:
Trudvsem(政府オープンデータ API、認証なし、ジオゲートなし)、GetMatchおよびGeekJob(防御的な HTML スクレイプ — パース失敗時は[]、健全な 200 で throw しない)。 - ソースレジストリ
server/lib/sources/registry.mjs— ディスパッチャ + エンドポイント + ドロップダウンが消費する単一情報源。v1.29 以前は 3 箇所にハードコードされていました。 - 新エンドポイント
GET /api/scan/sources(Cache-Control: max-age=60) — SPA は#/scanマウント時にソースフィルタのドロップダウンを動的に再構築。 - 新規 §17 8 ロケール全てに「新しい求人ポータルソースを追加する方法」(アダプタテンプレート、レジストリエントリ、ディスパッチャ、モックテスト、
portals.yml)。 russian_portals.sourcesのデフォルトが["hh", "habr"]から 5 ソースに変更。portals.ymlでsources:を明示的に列挙している場合は、新規 3 つを手動で追加してください。- テスト: 520 → 540(+20)。完全な詳細は
CHANGELOG.mdを参照。
[1.28.1] — 2026-05-14
ホットフィックス: ?query 付きハッシュでの router 404。health から HH_USER_AGENT 行を削除。
v1.28.1 以前は Router.go('/evaluate?url=…') が最初の split('/') セグメントとして "evaluate?url=…" リテラルを生成し、登録済みルートに一致せず __not_found__(404)に落ちていました。1 行修正: 名前分割の前に hash.split('?')[0]。報告された 2 つのクリックを両方カバーします:#/pipeline → ▶ と「App settings → Modes」。オプションの HH_USER_AGENT 行は /api/health から削除(ロシア外 403 ゲートのヒントは help-bundle §16 に残り、スキャン時に stderr でも依然として出力される)。515 → 520 unit + acceptance(+5 新規)。完全な詳細は CHANGELOG.md を参照。
[1.28.0] — 2026-05-14
ドキュメント整合 + #/batch の --max-retries N コントロール。qa/QA-PROMPT-docs-vs-app.md から提起された 2 件のオープン issue を解消します。
- Issue #2 —
#/batchに数値入力「Max retries」(1–10) を追加。「Retry failed」がチェックされた場合のみ有効化。サーバ側でparseIntと 1≤N≤10 のレンジ検証を行い、範囲外は静かに破棄、--retry-failedなしで--max-retriesは付与されません。tests/batch-max-retries.test.mjsの 7 ケース。新規 i18n キー 2 個 × 8 ロケール。 - Issue #1 — 8 個の help-bundle と 8 個の README の AI CLI リストを career-ops.org/docs の正典(Claude Code・Codex・OpenCode・Qwen CLI)に揃え、ローカライズされた一文を追記:「同じスラッシュコマンド・サーフェスで他の Claude 互換 CLI も動作します」。README の「Multi-CLI」項目(web-ui 自身の shim ファイル説明)はそのまま(別 surface)。
tests/canonical-docs-coverage.test.mjsに新規カナリア 2 個。 - 506 → 515 unit + acceptance(+9 新規)。Playwright 32/32 変更なし。完全な詳細は
CHANGELOG.mdを参照。
[1.27.0] — 2026-05-14
外観 + アクセシビリティ調整: サイドバーの #/dashboard エントリ重複排除。
サイドバーで、ブランドロゴ(<a class="logo" href="#/dashboard">)と最初のナビ項目が同一ルートを指していました。スクリーンリーダーは「Dashboard」を二回読み上げ、キーボード操作で無意味なタブストップが発生していました。ブランドブロックは通常の <div class="logo"> になり、ナビ項目のみが #/dashboard への唯一のリンクとなります。506 / 506 unit + 32 / 32 Playwright — 変更なし。詳細は CHANGELOG.md を参照。
[1.26.1] — 2026-05-14
ホットフィックス WCAG 2.5.5 — .btn 最小高さ 44 px の復元.
v1.26.0 で .btn の min-height: 44px 宣言が失われ、ヘッダーボタンが 39-41 px で描画されていました (WCAG 2.5.5 違反)。v1.26.1 で 44 px のフロアと flex-shrink: 0 + line-height: 1.2 を復元。502 → 506 unit、Playwright 32/32 変更なし。詳細は CHANGELOG.md を参照。
[1.26.0] — 2026-05-14
テストピラミッド + 行カバレッジ ≥ 93 %.
v1.25 バックログに従い 4 階層テストピラミッド (unit → functional → acceptance → e2e) を導入。v1.25 で最大だったカバレッジギャップを埋める 22 件の新テスト (jds.mjs 61.64 % → 100 %、auto-pipeline 拒否経路)。マルチエンドポイントのユーザージャーニーテスト用に tests/acceptance/ ディレクトリを新設。480 → 502 unit + acceptance、Playwright 32/32 変更なし。詳細は CHANGELOG.md と docs/architecture/TESTING.md を参照。
[1.25.0] — 2026-05-14
自動パイプラインの手動ショートサーキット + ダッシュボードのコスメティック修正 + CHANGELOG パリティのバックフィル。 G-014 (自動パイプラインが mode: 'manual' を無視していた問題)、G-012 (CHANGELOG パリティのドリフト — 6 ロケールが 2 リリース遅れていた)、およびダッシュボードの ✨ ✨ 二重グリフのコスメティック問題をクローズします。G-003 (README.cn.md のリネーム) は実質的に既にクローズ済み — リポジトリには README.zh-CN.md しか存在しません。G-005 (A-G → A-F レポートブロックの再整列) は親プロジェクトとの協調コミットが必要で、引き続き延期となります。
🛡️ G-014 — 自動パイプライン mode: 'manual' ショートサーキット
fix(auto-pipeline): G-014 — honour mode:'manual' short-circuit(server/lib/routes/auto-pipeline.mjs:158-195) — v1.25 以前は、このルートは常に LLM を呼び出していました。mode: 'manual'を渡す (v1.10.2 以降の/api/evaluateのミラー) は黙って無視され、リクエストは Anthropic で 1〜3 分ハングしていました。新ハンドラの動作は以下の通りです:- 後方互換のため
modeとevalModeの両方を受け付けます。いずれかが'manual'であればショートサーキットが発動します。 - 5 ステージ全ての SSE を
status: 'done'/status: 'skipped'で送出します。fetch なし、LLM 呼び出しなし、リクエストあたり $0.05 のコストなし。 doneペイロードは{ mode: 'manual', prompt: <buildEvaluationPrompt scaffold>, message }を含み、SPA は既存の/api/evaluate手動プロンプトカードと同様にレンダリングできます。
- 後方互換のため
HOST=0.0.0.0における DoS リスクをクローズ: 以前はllmRateLimitが 10 req/60s/IP で上限を設けていたとしても、攻撃者 10 名 × 各 10 リクエスト = 1 分あたり $50 の Anthropic 課金が発生していました。ショートサーキットは実呼び出しのレート制限カウントが減る前に発火します。- テスト —
tests/auto-pipeline-manual-mode.test.mjs: 3 件のテストで以下を確認します — (1)mode: 'manual'が 5 ステップキー全てを伴って 2 秒以内に返ること、(2)ANTHROPIC_API_KEYがセットされていてもショートサーキットが発火すること (元の症状)、(3) レガシーなevalMode: 'manual'呼び出し元が引き続き動作すること。
📝 G-012 — CHANGELOG パリティのバックフィル (6 ロケール × 欠落 2 リリース)
docs(changelog): backfill v1.23.0, v1.24.0, v1.24.1, v1.25.0 in 6 lagging locales— v1.25 以前は EN のみが v1.23-v1.24 を持ち、RU は 1 リリース遅れ、その他 6 ロケールは 2 リリース遅れでした。v1.25 では並列翻訳エージェント (v1.23 と同じパターン) をディスパッチし、4 エントリ全てをCHANGELOG.{es,pt-BR,ko-KR,ja,zh-CN,zh-TW}.mdに投入します。RU は v1.24.0 + v1.24.1 + v1.25.0 を受け取ります (v1.23.0 は v1.23 サイクルで既に翻訳済み)。feat(ci): scripts/check-changelog-parity.mjs gate— いずれかのロケール CHANGELOG の最新エントリが EN 正本より古い場合、ビルドを失敗させます。npm run test:ciに組み込み済み。既存の G-012 ドリフトは、EN の境界を越えた瞬間に自身を検出できていたはずです。
✨ コスメティック — ダッシュボードの二重グリフ重複排除
fix(dashboard): dedup ✨ glyph in auto-pipeline button label(public/js/lib/i18n-dict.js:219) —dash.autoPipelineは全ロケール文字列の先頭に✨を含んでおり、加えてpublic/js/views/dashboard.js:58がビュー側でさらに✨を前置していました。結果、ボタンは✨ ✨ Auto-pipeline …とレンダリングされていました。v1.25 では各ロケール DICT エントリの先頭グリフを削除し、ビューの前置を唯一のソースとします。同じ監査パスで残りの i18n バンドルもスイープしましたが、他に二重グリフのパターンは見つかりませんでした。
🚫 将来のリリースへ延期
- G-005 — 正規 career-ops.org/docs に従ったレポートブロック A-G → A-F — 親プロジェクト
santifer/career-opsの協調コミットが必要 (modes/oferta.mdを書き換えて A=Role、B=CV-match、C=Strategy、D=Comp、E=Personalization、F=STAR を出力し、C-Risks / G-Legitimacy を独立ブロックから外す)。v1.25.0 は web-ui 側を新スキーマ受け入れ可能な状態で出荷します (reports.jsは v1.13 以降、任意のブロック文字を受け付けます)。親と子を一緒に着地できる次のリリースウィンドウまでトラックします。 - G-003 —
README.cn.md→README.zh-CN.mdリネーム — v1.25 準備時に検証済み: リポジトリには既にREADME.zh-CN.mdのみが存在し (worktree 配下に孤児のREADME.cn.mdは皆無)。G-003 の指摘は陳腐化していました。
🧪 テスト
- 477 → 480 ユニットテスト (+3 は PR-B の
auto-pipeline-manual-mode.test.mjsより)。 - 32/32 Playwright は変更なし。
npm run test:ciはnpm test+check-no-also-leftovers.mjs+check-changelog-parity.mjsを実行するようになりました。
検証
$ npm run test:ci
# 480 / 480
# ✓ no .also( leftovers in views/
# ✓ CHANGELOG parity: all 8 locales at v1.25.0
# G-014 — manual mode returns < 2 s even with ANTHROPIC_API_KEY set:
$ ANTHROPIC_API_KEY=sk-ant-test PORT=4317 npm start &
$ sleep 3
$ time curl -sS -X POST -H 'Content-Type: application/json' \
-d '{"url":"https://job-boards.greenhouse.io/anthropic/jobs/x","mode":"manual"}' \
http://127.0.0.1:4317/api/auto-pipeline | head -20
# real 0m0.1xx s (was 1-3 min)
# event: start … event: step (×5) … event: done {"mode":"manual","prompt":"…"}
# G-012 — every locale CHANGELOG carries the v1.25.0 entry:
$ grep -c '^## \[1.25.0\]' CHANGELOG*.md
# 8 files, each → 1
# Cosmetic — dashboard glyph:
$ grep "dash.autoPipeline" public/js/lib/i18n-dict.js
# No leading ✨ in any locale value (view supplies the single glyph)
破壊的変更
なし。mode: 'manual' はオプトインで、レガシーな evalMode: 'manual' 呼び出し元は変更なしで引き続き動作します。
スコープ外 (v1.26+)
| 項目 | 備考 |
|---|---|
| G-005 — A-F レポートブロックの再整列 | 親プロジェクトの協調コミットが必要 (santifer/career-ops が modes/oferta.md を書き換え)。 |
| QA シナリオ 31 の ビジュアル サブテストのライブ実行 | ブラウザ駆動エージェント (Claude Cowork) が必要。Playwright スモークで部分的にカバー。 |
i18n-dict.js の 400-LOC 目標超過 |
翻訳フィクスチャ — ポリシーにより除外。バンドラを使わずに分割すると HTTP リクエストが増えるため不採用。 |
[1.24.1] — 2026-05-14
ホットフィックス: 8 ロケール全てで #/config がクラッシュ (G-015)。
🚑 重大ホットフィックス
fix(config): G-015 — replace removed Element.prototype.also call in config.js(public/js/views/config.js:371) — v1.22.0 の N-2 でElement.prototype.alsoのグローバルモンキーパッチを撤去し、cv.jsをフリーステートメントパターンへ移行しましたが、config.jsの移行が漏れていました。結果、#/configは全ロケールで初回呼び出し時にc(...).also is not a functionを伴ってクラッシュしていました。v1.24.1 ではcv.js:188-201と同じ移行パターンを適用 — ツリーをconst root = c(...)に抽出し、起動ブロックを単体で実行した後return root;を返します。
🛡️ CI ゲート
feat(ci): scripts/check-no-also-leftovers.mjs sweep—public/js/views/配下の全ファイルを走査し、いずれかの.also(呼び出しサイト (コメント内参照は許可) があればビルドを失敗させます。新しいnpm run test:ciスクリプトに組み込み済み。将来モンキーパッチ撤去がリバートされても、同じリグレッションが黙って再導入されることはありません。
🧪 テスト
test: tests/config-view-syntax.test.mjs— 3 つのガード:node:vm.Scriptでconfig.jsをパース (Playwright なしでも構文レベルのリグレッションを捕捉)- コメント外に
.also(が残っていないことを表明 const root = c(...)/return root;の移行アンカーが存在することを表明
- 474 → 477 ユニットテスト (+3) + 32/32 Playwright は変更なし。
検証
$ npm run test:ci
# 477 / 477
# ✓ no .also( leftovers in views/
# Browser smoke:
$ open http://127.0.0.1:4317/#/config
# → renders normally, no "is not a function" card. Every locale equivalent.
スコープ外 (v1.25 に延期)
- G-014, G-012, G-005, G-003 — バンドルは下方の v1.25.0 エントリを参照してください。
career-ops-ui v1.267.1 · page 8/10 · mirror of the project changelog for cvstart.org