更新日誌

GitHub

每一項重大變更,逐版記錄。同一份檔案隨儲存庫發布,並已翻譯為全部 19 種語言。

[1.248.8] — 2026-10-11

父專案對齊:fork 和 web-ui 同步上游的 115 個提交 — 新的 PageUp 來源、追蹤器 URL 儲存格解析為 markdown 連結、Assessment 狀態 — 加上 ta/ja 語言工作:循環的報告會被切斷並命名,標題骨架保持 A–G 穩定,文字佔比只按散文計算。

新增

修復

  • 新 PageUp 來源(EN 轉接器 105 / 來源 110);追蹤器 URL 儲存格按 markdown 連結解析(href 是去重鍵);Assessment 狀態經 states.yml 到達。
  • 循環報告被切斷並命名(report looping (block B repeated)),非英語標題骨架 ## A) … ## G)。
  • 文字佔比只按散文(表格、SCORE_SUMMARY、程式碼、URL 除外)— 帶拉丁表格的日語報告不再抖動。
  • /api/evaluate 加入 report not in <locale> 警告,remote-qa 把代碼印成一欄。

-

[1.248.7] — 2026-10-11

報告頁首的角色現在讀取父專案的真實約定 — # Evaluation: {Company} — {Role} — 而非假定 # <角色> — <公司>,追蹤器不再把 «Evaluation: <公司>» 當作角色;語言錯誤的報告會警告而非靜默保存;H1 之前的模型閒聊會從保存的報告中剔除。

修復

  • 先剝掉本地化標籤(Evaluation:、Оценка вакансии: 等 16 種變體);標籤後 Company — Role → 角色取右側;無標籤 Role — Company → 左側;指名公司的部分說了算;角色絕不等於公司或標籤詞 — 後備是清理過的帖子行。
  • cleanup-plan 累加行的 N(— N dirty-URL line(s)),不再數檔案 — 含 5 條髒 URL 的檔案此前報 1 而非 5。
  • 語言錯誤的報告會警告:提示詞以語言指令開頭並收尾(整個報告含表格),管線校驗所存報告的語言 — 漂移以 report not in <locale> 警告浮出。
  • H1 前無模型閒聊 — 報告從標題行開始。

備註

  • 未移植、未改動(已在 v1.248.6 回歸中驗證):embed-抓取、帖子行結構(«Компания : X»)、«## Rejected» 去重、fetch 前拒絕網域根、recon/link-check 的誠實性、cleanup 模式。語言校驗是警告 — (內容 A–G + 分數已驗證的)報告仍會保存;維運者看到漂移後可重新評估。

[1.248.6] — 2026-10-10

回歸後對 Telegram 鏈路的打磨:真實帖子保留行結構(«Компания : X» 標籤和角色行在其真實所在處被找到),追蹤器角色來自報告頁首而非原始帖子行,維運面(recon、cleanup-plan、link-check)報告它們所聲稱的內容。

修復

  • t.me 帖子不再黏成一行:去標籤前 <br> 變為換行,數字 HTML 實體被解碼;公司標籤模式接受冒號前帶空格的形式。
  • 追蹤器角色來自報告頁首(H1 # <角色> — <公司>);後備行會清理 — 無井號標籤、無 emoji、≤80 字元。
  • ## Rejected 不再重複:分區只建立一次,每次拒絕在其內追加自己的行。
  • 網域根在請求前被拒絕 — 垃圾 URL 不再花費任何下載。
  • Recon 計算契約訊號而非斷言(檢查已部署程式碼中的 markPipelineRejected);marks 計數不再印出兩次 0。
  • cleanup-plan 按類別報告數字(待移動的 t-role、dirty-URL 行、test-row 行),替代把自身成功行也計數的單一 grep -c。
  • link-check 把 github.com 5xx 視為未驗證 — 計入機器人牆之後,不算壞鏈。

備註

  • 未移植、未改動(已在 v1.248.5 回歸中驗證):embed-抓取本身、pipeline 跳過契約、_setLookup 離線測試、uk 序數接受、角色過濾的詞邊界、cleanup-apply 模式。cleanup-plan 的分類計數讀取清理腳本自身的輸出標籤 — 重新命名這些標籤會悄悄破壞計數。

[1.248.5] — 2026-10-10

回歸後加固:t.me 單帖條目現在會抓取帖子文字本身(embed 形式),被拒絕的條目在伺服器端退出評估計時器的候選池,烏克蘭語報告的原生區塊編號被接受 — 此外,一個偷偷存取真實網路的測試從此永久離線。

修復

  • t.me 條目終於能被評估:單帖頁面在純 HTML 中只提供頻道頁首(約 0.2 KB — 只有 «View in Telegram»),模型收到空描述,每條 Telegram 條目都在評估時死掉。抓取現在把 t.me/<頻道>/<id>(以及 t.me/s/… 訂閱形式)重寫為 embed 形式(?embed=1&mode=tme)— 同一主機,SSRF 驗證不變 — 並從 .tgme_widget_message_text 讀取帖子(後備:og:description)。沒有元件文字 → 在 fetch 步驟拒絕(telegram post has no text,rejected: true),不浪費任何 LLM token。
  • 評估計時器不再為被拒絕的條目重複付費(伺服器端契約):被拒絕的條目現在在 data/pipeline.md 中標記 — 它的行離開 URL 圍欄,進入帶有簡短原因的 ## Rejected 部分(冪等,永不重複)。計時器從圍欄中選取裸 URL,因此被標記的條目自然被跳過 — 不依賴計時器腳本的內部實作。Recon 印出 pipeline rejected marks: N + honours rejected: yes (server-side),而不是 grep 計時器腳本。
  • 烏克蘭語報告再次通過 A–G 驗證:uk 模型按自己字母表的順序給區塊編號(А Б В Г Д Є Ж),而相似字形通道只統計了 А/В/Е — 正好是區塊 C、D、F、G 在生產中缺失(2/2 重現)。當標題按順序出現時,驗證器現在接受烏克蘭語序數序列(與 ar 的 abjad 和 hi 的天城文相同的紀律),並且每個 locale 的評估提示現在明確要求拉丁 A–G。
  • 角色關鍵字門檻按詞而非子字串觸發:linkedin.com/ 和 x.com/ 根路徑通過了門檻,因為 lead/it 藏在頁面無關的介面文字中。關鍵字現在按 Unicode 詞邊界匹配,沒有路徑的網域根在 LLM 呼叫前被拒絕。
  • 單元測試不再需要真實網路:no-hints 測試(tests/auto-pipeline-jd-gate.test.mjs)替換了 HTTP 傳輸,但仍執行真實的 DNS 查詢 — 只因 runner 有網路連線才在 CI 中變綠。safe-fetch 增加了 _setLookup() 掛鉤(測試中可替換解析器本身;生產程式碼從不覆蓋它),測試離線解析。

新增

  • deploy.yml 中的 cleanup-plan / cleanup-apply 工作流模式:生產環境 QA 垃圾清理(post-qa-cleanup)在伺服器端執行,無需從筆記型電腦 SSH。日誌只包含數字 — 沒有檔案名或行預覽,因為日誌是公開的,而檔案名包含公司名稱。Verify 步驟對清理模式跳過。

備註

  • 未移植:父專案的詞邊界位置編譯器和 USPS 州表(自 v1.248.1 起已記錄)。被拒絕條目的跳過契約位於伺服器端(data/pipeline.md 中的標記),而不是計時器腳本 — 該腳本在儲存庫之外,有意不做修改。

[1.248.4] — 2026-10-10

回歸輪續篇:Telegram 條目的角色解析鏈——帶真實職缺的西里爾貼文現在產出有名有姓的報告,而不是被拒的 unknown-role 檔案——且 LLM 呼叫只為值得評估的條目觸發。

修復

  • 角色鏈對西里爾貼文生效:關鍵字清單加入 разработчик/программист/инженер/аналитик/менеджер/руководитель/тимлид/лид/дизайнер/тестировщик/QA/архитектор/специалист/директор/DevOps/SRE(舊清單只有英文:40 篇真實貼文中 0 篇得到角色),查詢報告頭(parseReportHeader——模型把角色寫在 H1),貼文首行有意義時作為最後手段。
  • 身分閘移至 LLM 呼叫之前:既無公司也無角色線索的條目在 fetch 步驟即失敗(rejected: true)——不再為注定被拒的評估燒 token。
  • t.me/s/<channel>/<id> 不再把公司命名為「S」——跳過預覽段;兩種連結形式都解析到頻道。
  • Telegram 貼文的「Компания: X」自標籤被尊重(即時通訊主機的句中「…в компании X」亦然)。

新增

  • auto-pipeline 失敗在 SSE 錯誤中帶 rejected: true——伺服器端評估計時器用該旗標跳過 pipeline 條目,而不是每 2 小時重試。

備註

  • 未移植:父級的詞邊界位置編譯器與 USPS 表(自 v1.248.1 起記錄在案)。
  • README 的「Translations」區塊現在從單一正典清單生成,CI 增加對等測試(每檔案 17 個連結,自身永不連結)——ar/pl/uk 獲得區塊;|/· 分隔符漂移已消除。

[1.248.3] — 2026-10-10

回歸輪續篇:評估計時器在任何情況下都無法把無名 Telegram 垃圾存為報告,QA 清理不再刪除真實 Telegram 職缺,地圖署名重新可見,儀表板晶片對比 ≥ 4.5:1。

修復

  • auto-pipeline 在儲存前驗證:沒有 0–5 分數或缺 A–G 區塊的報告在 evaluate 步驟終止執行(SSE 載荷帶 rejected: true)——不寫報告檔案、不加 tracker 列。通過 v1.248.2 長度閘的 t.me 條目在那版之後又產生兩個垃圾;現在無名條目以 company/role not identifiable 拒絕。
  • guessCompanyRole 不再從即時通訊主機捏造公司——t.me、telegram.me、vk.com、linkedin.com 等在 EMPTY_DOMAINS:Telegram 貼文回退到頻道名,無名條目被拒絕而非存為 unknown-role。
  • post-qa-cleanup 先讀標頭分數:auto-pipeline 儲存前會剝除 SCORE_SUMMARY 區塊,因此舊的僅-summary 檢查把每份已存的 Telegram 職缺判為垃圾——生產 41 份中 8 份標頭有分數,--apply 本會刪除它們。標頭分數(經 parseReportHeader)現在是主信號;區塊作為後備。dry-run 逐檔案列印原因。
  • #/map 的 Leaflet 署名可見——docs FAB 曾遮住它(違反 OSM 授權);角落抬高 96px。
  • 儀表板晶片 ≥ 4.5:1——dash-chip 標籤與 --manual 變體改用 --foggy-strong(先前 4.31:1)。

新增

  • auto-pipeline 失敗在 SSE 錯誤中帶 rejected: true——伺服器端評估計時器用該旗標跳過 pipeline 條目,而非每 2 小時重試。

備註

  • ar/ja 漂移在重跑中未再現(見 v1.248.2)。
  • 生產清理仍等待使用者明確「是」(CAR-61c);本次修復後清理已無法刪除真實 Telegram 職缺。

[1.248.2] — 2026-10-10

回歸輪:評估計時器不再把 pipeline 垃圾變成「Last evaluation」,SCORE 解析扛得住真實格式,追蹤器終於說你的語言。

修復

  • 空 JD 報告不再被寫入。 auto-pipeline 的 JD 閘在 LLM 呼叫前從 50 提到 200 字元——佔位頁(example.com/qa-v167-…)以 SSE 錯誤 + 主控台警告結束,而不是被儀表板顯示為「Last evaluation」的 …-t-role-<ts>.md 報告(生產環境已有 42 個)。
  • SCORE_SUMMARY 解析扛得住真實格式:「4,2/5」「4.2 / 5」「4.2」「SCORE :」全部有效;逗號後 1–2 位是小數,3 位是千分位(舊程式把「4,200」讀成 4.2 並通過 0–5 閘)。失敗只記錄 SCORE: 行——絕不記錄報告內容(CI 日誌公開)。

新增

  • scripts/post-qa-cleanup.mjs:預設 dry-run,--apply 帶自動備份(*.bak-<ts>,報告移入 qa/cleanup-backup-<ts>/):tracker 的 ZZ-QA-TEST 列、無有效 SCORE 的 *-t-role-* 報告、pipeline 的 example.com 條目。

備註

  • 回歸輪的 ar/ja 漂移在重跑中未再現(兩者 lang ✓ + A–G ✓)——language-check 的 ta 條目與強化的 SCORE 解析作為縱深防禦發布。
  • 生產清理等待用戶明確「是」:先 dry-run 計劃,後 --apply。

[1.248.1] — 2026-10-09

location_filter 獲得父級的 always_allow 與 block_hard 層級(CAR-60)——多國位置字串挺過兄弟封鎖清單,而明確硬封鎖不再能被救回。

修復

  • always_allow 勝過 block:在 block: [romania, poland, croatia] 與 always_allow: [united kingdom] 下,workable 的合併儲存格「London, United Kingdom · Romania · Poland · Croatia」通過——家鄉地區是選項(workable #4806 的消費者案例,現為父級形式)。
  • block_hard 勝過 always_allow:絕不允許被字串他處的 always_allow 命中救回的國家級詞彙。
  • strict: true 現在將 block_hard 計為限制性層級;docs/help 的 location_filter 小節以 ×18 記錄兩個層級。

備註

  • 本次刻意未移植:父級的詞邊界位置關鍵字編譯器與 USPS 州名表(web-ui 保持子字串匹配;該表服務於以美國為中心的設定——如需請歸檔至 CAR-60)。workable 消費者測試的過渡 allow-list 形式作為第二判定保留,與新的父級形式並列。

[1.248.0] — 2026-10-09

父級對齊:移植五個上游 provider/liveness 修復——遷移了 API 的招聘版繼續運作,獨立一行的關閉橫幅不再被漏讀。

修復

  • Gupy 跟隨 API 遷至 portal.gupy.io,並發送 API 真正讀取的過濾參數名(workplaceType/type)——舊的複數名稱被靜默忽略;country 不再發送(配置的國家會以條目名繪製警告)。機密雇主按精確標籤匹配丟棄(「Confidencial Seguros」存活),max_pages 上限 100——API 在 offset 10,000 之後回傳 400,截斷警告現在建議收窄關鍵字。
  • Workable 保留多國職缺的所有國家:同一職缺 URL 下的兄弟條目摺疊為一行(「London, United Kingdom · Romania · Poland · Croatia」),不再由第一個國家獲勝。
  • Liveness:獨立一行的橫幅不屬於上一行——職缺下方的「Sign in if you want to apply」/「Get notified when this position is filled」被讀作它本來的關閉子句,而跨行的真正「until filled」仍讀為活躍。
  • Radancy 讀取舊版卡片標記中的兄弟 job-location 元素(Barclays 家族佈局把地點放在 </a> 之後)——沒有自己地點的列保持為空,而非借用鄰居的。
  • SmartRecruiters 後備職缺連結保留配置的 slug——缺少/不可信 ref 的職缺從租戶 slug 合成公開 URL,絕不用顯示名稱。

新增

  • 記錄在案的 no-port:上游 _http 代理 fetchImpl 修復(web-ui 沒有代理分支——移植會使 DNS-rebinding 守衛的身分檢查失效,並加入本儲存庫刻意避開的 undici 依賴);上游 verify-ats(+226 行)與 cv-facts(web-ui 實作自己的 ATS 評分與 CV 解析);父級儀表板 Go TUI 與 CLI 腳本;Teamtailor 的 import 重排(表面性)。

備註

  • 已知缺口列為後續:父級 location 過濾器的 always_allow 層級(workable 的消費者測試靠它挺過 UK 封鎖)在 web-ui 中不存在;移植以 allow-list 測試表達了相同的消費者結論。已立案為單獨變更。

[1.247.0] — 2026-10-09

CAR-49…58 設計巡檢的收尾完成——剩餘所有次要與打磨項,加上上一版刻意延後的深色地圖。

修復

  • 地圖渲染真正的深色主題。 縮放/圖層/歸屬已在 v1.246.0 變暗;現在圖磚也跟隨:深色模式繪製 CSS 反轉的 OSM 圖層(無需供應商金鑰),並可透過 MAP_TILE_DARK_URL 使用帶金鑰的 Carto-dark 預設(其主機已加入 CSP img-src 許可)。切換主題時圖層即時重掛。
  • 黃色提示條獲得深色變體(config、batch、hero 警告)——使用 v1.246.0 的代幣機制;順帶捕獲一處回歸:.callout a 蓋過 .btn-primary,現已限定 :not(.btn)。
  • Apply 資訊卡 URL 不再於詞中截斷。
  • CV 標頭按鈕統一圖示體系(文字;📁/📄/💾 混用移除)。
  • Tracker 狀態分頁大小寫統一——句首大寫,並按語言適配(CJK/ar/hi/ta 無性數變化)。
  • 頂欄圖示統一為描邊 SVG 套裝——🔔/🌙/🩺 混用移除;主題按鈕的日/月由 CSS 繪製。
  • 儀表板 Pipeline 卡片移除多餘粉色高亮(圓環僅 hover/focus)。
  • 刪除已存搜尋呈現破壞性樣式(.btn-danger,而非中性幽靈按鈕)。
  • Pipeline 預覽擁有真正的空狀態(標題 + 提示 + CTA,tracker 模式)。
  • 時間/成本元資訊統一:UI.pageMeta 在視圖標題下以一行繪製 ⏱ + 成本(evaluate/deep/auto/mode-page/orientation)。
  • Auto-pipeline 去除 ✨——全應用唯一帶裝飾的 h1 回歸共同的左緣。
  • Batch 文件 URL 為內嵌連結。
  • interview-digest / orientation 擁有設計過的空狀態(標題 + 提示 + CTA)。
  • 無計畫時 career-plan 匯出停用,操作收進一行。
  • stats:移除重複的「Market report」標題,地區佔位符可完整顯示(×18 縮短文案,固定寬度)。
  • Config 不再洩漏絕對檔案路徑——伺服器端 displayPath() 顯示 ~/… 或裸檔名。
  • LLM_PROVIDER 說明拆成三短行(×18 + pre-line),不再是 150 字元的等寬牆。
  • Profile EMAIL 卡片地址保持單行(省略號;僅 ≤480px 才換行)。
  • Health 文案不再詞中斷行(「Run buttons」完整;overflow-wrap anywhere)。
  • Help 內嵌代碼在 RTL 下不破碎(LTR 隔離、nowrap、可捲動)。
  • Hero「Live evals」膠囊在 ru/ar 下不再碰撞(flex-wrap、不可收縮圖示)。

新增

  • 深色圖磚預設(MAP_TILE_DARK_URL + attribution)已在程式碼內文件化;CSP img-src 的擴充涵蓋其 Carto 主機。

備註

  • 深色圖磚預設為 OSM 的 CSS 反轉——無金鑰的 Carto 已把底圖關進 API 金鑰之後(經請求驗證),帶金鑰預設為可選而非預設。

[1.246.0] — 2026-10-09

CAR-48 資深設計巡檢的十大發現全部修復——含 1 個阻塞級與全部 7 個重要級。

修復

  • Apply 深色模式:資訊卡連結重新可見。 卡片在深色下仍保持淺色表面,「Need Playwright? See …」連結對比僅 ≈1.0:1。主題感知的 .callout--info(surface/border/text/link 代幣)把深色下的連結提升到 ≈7.3:1。
  • RTL 不再翻轉數字組合。 分數膠囊(「1.5 / 5」)、報告檔位(「≥ 4.5」「< 3.5」)與儀表板的「/ 5.0」獲得方向隔離(LTR span);ar 詞典在散文更自然處把數字區間改寫為文字。
  • CV Markdown 保持 LTR。 阿拉伯語下 textarea 與預覽以 dir="ltr" 渲染,周邊框架仍為 RTL。
  • 報告 DATE 欄不再折斷 ISO 日期(nowrap + 表格數字)。
  • 側邊欄止於用量 HUD 之上(捲動埠保留 + 淡出)——最後一個導航項不再滾到面板下方;RTL 已鏡像處理。
  • 行動版 scan @390:「Save search」完整可見——控制列換行並壓縮,不再於視窗邊緣被裁切。
  • 地圖控制項跟隨深色主題(縮放、圖層、歸屬)。圖磚刻意維持不變:圖磚來源由伺服器端 CSP img-src 固定。
  • 聊天 FAB 不再遮蔽互動內容——頁面捲動內距為其留白(Portals 的 Disable 按鈕、Reports 的分數膠囊、two-pager 輸入框、Leaflet 歸屬行)。
  • 活動日誌說介面語言:22 個動作 slug ×18 語言本地化(未知者回退到原始 id),篩選晶片已翻譯;TARGET 欄保留——由 auto-pipeline 填充。
  • Assessments 表單擁有真實標籤(句首大寫、htmlFor/id 綁定),不再只有佔位符。

備註

  • 本版本未包含:深色地圖圖磚(需擴充 CSP img-src——屬伺服器端變更,刻意未在客戶端實施);黃色提示條(config、batch)的深色變體——callout 代幣機制已為後續就緒。

[1.245.0] — 2026-10-09

坦米爾語(தமிழ்)以第 18 個介面語言加入——這是 17 語言浪潮後的第一個新語言,鍵值完全對齊。

新增

  • 坦米爾語(ta):1449/1449 個鍵值,與 en 完全對齊,無佔位符遺漏,沿用 hi 風格的印度系技術慣例(供應商、模型名稱、路徑與指令保留拉丁字母)。detect() 接受 ta、ta-IN、ta-LK;坦米爾語為 LTR(由測試鎖定)。
  • 完整的 docs/help/ta.md(33 個 H2 / 125 個 H3,來源計數與線上註冊表核對,Hermes 金絲雀錨點,逐字 YAML),並附 README.ta.md 與 CHANGELOG.ta.md 鏡像。
  • 列舉關卡 17 → 18:LANGS、detect 探針、i18n 稽核、更新日誌對齊、RTL 守衛、CI langs——同時補上先前 CI 清單缺席的 hi。

備註

  • cvstart.org 落地頁暫維持 17 個語言——翻譯落地頁是獨立任務;網站內容同步已發布 docs/help/ta.md 與 ta 更新日誌。
  • report-header.js 的 REPORT_LABELS 維持 17——坦米爾語報告解析將隨父儲存庫的坦米爾語報告生成一併到來。

[1.244.2] — 2026-10-08

掃描頁面打磨:啟動器、狀態列、轉發面板與篩選器重構為一致的外觀——結果表格不再會被看板資料撐開。

修復

  • 薪資儲存格只顯示金額區間。 有些看板把整段福利說明塞進薪資欄位——可見儲存格現在只顯示金額部分,說明文字移入提示框(此前:6 行高的儲存格撐開每一列)。
  • 空標題的列顯示公司名(再退化為中性破折號),而不是巨大的空白儲存格。
  • 資歷徽章在固定寬度欄中不再換行。
  • 無評分的列顯示淡化的「◎ —」,提示框說明兩頁紙比對未找到匹配關鍵字(此前:空白,在有評分的列旁邊顯得像壞掉)。

新增

  • 外觀重構:啟動卡片(對齊的控件列、主導按鈕)、帶狀態點的終端機狀態列(閒置/執行中/完成/錯誤)支援 reduced-motion、轉發面板有高度上限與固定表頭(1,560 個集群曾渲染出 155,000 px 的面板)、篩選器塊為均勻響應式網格並對齊頁腳。

說明

  • #/scan 的其餘部分與 v1.244.1 完全相同——僅變更了外觀拋光與上述四個修復。測試 5045 → 5068 unit。

[1.244.1] — 2026-10-08

修復

  • Scan icons announce real values. The boost/fit/score icons’ accessible names leaked the raw dict templates (“Fit: {band}”, “Boosted by {by}”) — {band}/{by}/{score} are now substituted (band word, booster keyword, numeric score). [HIGH a11y, caught live in the v1.244.0 regression round]
  • Title pipes split correctly. A 4-segment title like «C++ | Rust | Go Developer | Onsite» no longer lifts “Go Developer” out as a “country” — the split demands the segment resolves to a real country (conservative Countries.detectCountry).
  • Meta dates render for every row. daysSince accepted date-only ISO, but the API serves full timestamps for ~83% of dated rows — those rows showed no date segment.

說明

  • #/scan 的其餘部分與 v1.244.0 完全相同——僅變更了上述三個修復。
  • 刻意不做:URL 雜湊篩選狀態、鍵盤列導覽與 5 lakhs INR 解析(皆為既有後續)。

[1.244.0] — 2026-10-08

掃描結果讀起來像一份清單,而不是一面牆:一個職缺=兩行,訊號化為圖示,700 筆的掃描變成分頁表格,而不是 37,000 px 的長頁面。

修復

  • 列的結構。 每個職缺都渲染為一個有邊界的兩行列:第 1 行是職稱,第 2 行是 company · location · source · date · work-type。提升徽章、職稱契合度色帶與契合度得分改為圖示,並附上在地化的無障礙名稱與工具提示(螢幕閱讀器讀出的是 «Соответствие: сильное» / “Fit: strong”,而不是一整面文字牆);信任與搬遷徽章也併入同一側欄。級別與薪資仍保留為僅桌面版顯示的輔助欄位。
  • 職稱清理。 結尾的 | Germany | Remote 片段會從職稱中拆出、併入 meta 行——具 Unicode 感知,僅當結尾是真正的工作型態標記、且中間片段呈地名形狀時才拆分(像 “C++ | Rust | Go Developer” 這種正當的豎線永不拆分)。
  • 不再橫向蔓延。 固定表格版面、min-width: 0、單行職稱/meta 以省略號截斷、完整文字放進工具提示:在 1440 px 與 390 px 寬度下,頁面零溢出、表格內部零捲動(舊版頁面在手機寬度會溢出 675 px)。
  • 分頁。 結果每頁渲染 50 筆(可選 25/50/100/200)——700 筆的掃描現在高度為 ~4,600 px,而不是 ~37,000 px,DOM 節點約 ~1,000 個,而不是 5,500+ 個。

新增

  • 版面契約測試:兩行列結構、圖示的無障礙名稱(en + ru)、分頁的節點數預算,以及職稱契合度的數量/順序守衛,都在瀏覽器測試套件中執行——這次重新設計不可能再無聲地回歸。

說明

  • 單元測試 5036 → 5045(瀏覽器套件新增了版面契約),覆蓋率平均 98.14 % 行 / 89.34 % 分支(下限 96/86)。
  • 掃描器找到的內容、評分公式與 last-scan.json 都未更動——職稱契合度仍只是一項註記。
  • 本版未包含:URL-hash 篩選狀態與鍵盤列導航(後續跟進);5 lakhs INR 的解析(既有問題)。

[1.243.2] — 2026-10-08

修復

  • 可部署版本切為 v1.243.2。 v1.243.1 的 git 標籤在發佈後被重新指向,伺服器的 git fetch --tags 正確拒絕覆蓋已有標籤——因此 v1.243.1 從未能部署。此標籤攜帶完全相同的程式碼樹(基於 v1.243.0 的彈窗 window-監聽修復)加此次版本號提升。沒有其他變更;已發佈的 npm 1.243.1 壓縮檔已包含該修復。

說明

  • 應用程式碼與 v1.243.1 完全相同——只是部署用的標籤是新的(伺服器的 git fetch --tags 拒絕了重新指向的 v1.243.1)。
  • 刻意不做:強制更新伺服器端標籤(已發佈的標籤永不移動)。

[1.243.1] — 2026-10-08

修復

  • 「記錄結果」對話框現在真的會在導航時關閉。 v1.243.0 的守衛在 document 上監聽 hashchange,而該事件從不在那裡觸發——修復以死代碼的形式上線,對話框仍會擋住下一個視圖(v1.243.1 回歸輪在 正式環境 現場發現)。守衛改為監聽 window;測試也向那裡派發事件,並針對壞代碼驗證過紅色。

[1.243.0] — 2026-10-08

瀏覽器層守護你的工作成果,也說你的語言:未儲存的編輯不再遺失、失敗不再無聲無息、翻譯後的介面裡不再悄悄出現英文、對比處處達到 AA 等級。

修復

  • 未儲存的編輯可在重新渲染後留存。 CV 編輯器會保留 dirty buffer,並在每次渲染時還原——取消、切換語言或前往 CV Studio 時,現在會先提示而不是直接捨棄;職涯計畫(career plan)也以同樣方式保留其產生的文字。Config/CV Studio/career-plan/memory/two-pager 載入失敗時會顯示錯誤並停用「儲存」,而不是悄悄以空白內容覆寫檔案。
  • 「記錄結果」(Record outcome)對話框不再跨越導航存活。 在 #/tracker 上開啟它再點擊另一個視圖,它會擋住整個頁面(在 v1.242.1 的 two-stand 回歸測試中發現)——現在切換路由時會關閉;它的 scan-view 兄弟視圖同樣會在導航時停止 SSE 串流並解除事件監聽器。
  • 薪資與關鍵字解析能讀懂每一種文字。 關鍵字標籤改用 Unicode 詞邊界(西里爾文、c++、.net 現在能正確篩選,而不是被篩成 0 筆);單一共用的薪資解析器修正了 $182.9K - $240K、от 100 000 руб、100 тыс. руб.、50 000 Kč(過去被讀成 5,000 萬美元)與區間語義($100-150K);必備條件「Germany or Netherlands」不再同時被算作符合又違反。
  • 串流只收斂一次。 SSE 連線中斷時,錯誤會被送達兩次,並可能讓 Generate 按鈕永久停用;每一種終態(done/error/close/throw,包括子程序被終止)都只走一條路徑收斂,並重新啟用按鈕。auto-pipeline 會送出最後一個緩衝的幀,而不是將它丟棄。
  • 小路徑上的隱私與正確性。 錯誤回報(bug-report)的內文不再攜帶被評估職缺的 URL 或報告 slug;CV 隱私遮罩會處理裸露的 linkedin.com/in/… 連結與全大寫姓名,並不再破壞 EU 日期格式;國家判定採用最長匹配,當同時出現兩個國家名時拒絕猜測(「Cambridge, MA」不再被判成英國);檔案下載會保留西里爾文檔名。
  • 路由與無障礙(a11y)。 skip link 不再 404 到 ontent;作用中的導航項目帶有 aria-current="page";單一 <main>/單一 <nav>;Escape 會關閉行動版抽屜(關閉時也真正離開 tab 順序);scroll-padding-top 讓焦點在黏性列之後仍然可見;黏性橫幅不再蓋住頂欄。
  • 兩種主題的對比都達到 AA——深色 toast 過去是白底白字(1.10:1):toast、按鈕、橫幅以及 --rausch 上的連結,現在都透過主題無關的 token 解析,並由計算式的 WCAG 契約測試(≥ 4.5:1)護航。
  • 本輪的其他一切:evaluate 會渲染截斷警告;memory/config/two-pager 拒絕在載入失敗的檔案上執行「儲存」;scan 不再遺棄它的 SSE;掃描結果改由伺服器註冊表分類 API 型職缺板,而不是依賴已漂移的正規表示式;追蹤表的「Proceed with Caution」是警告,而不是 badge-red;統計會彙整在地化的狀態;長條圖在 RTL 下閱讀正確;docs FAB 不再遮住手機上的內容;GET /app.js 回應 404,而不是回傳 index 外殼;GET /api/runners 會列出其動作。

新增

  • 丹麥語加入自動偵測——da-DK 的瀏覽器現在會取得丹麥語介面(字典本就齊全,缺的是偵測);zh-Hant* 會對應到繁體中文。cv-diagnostics——履歷檢查清單——已完整在地化(28 個 diag.* 鍵 × 17 種語言)。新增 26 個 field-spec 標籤;移除 31 個失效的鍵;供應商清單不再宣稱 Hermes「位於 auto 順序的最後」;docs-fab/選單的 aria-label 也已在地化。

說明

  • 單元測試 4812 → 5036,瀏覽器測試 118 + 新增的導航/對比契約,覆蓋率平均 行 98.15 % / 分支 89.34 %(下限 96/86)。
  • 本版本未包含:#/scan 的重新設計(Phase 4 —— v1.244.0,規格已起草);專屬的 export.docxFailed 字典鍵(失敗時沿用 common.error);5 lakhs INR 仍被解析為 5(既有問題)。

[1.242.1] — 2026-10-08

修復

  • 發佈歸檔再次不含 node_modules 符號連結。 v1.242.0 的 git 標籤帶有根目錄的 node_modules 符號連結與 site/node_modules(一次全域 git add -A 讓它們繞過了帶尾部斜線的 ignore 模式),導致伺服器部署的 tar 解包失敗——部署安全回滾,正式環境停留在 1.241.1。兩個符號連結均已取消追蹤,ignore 模式已加寬;npm 壓縮包從未受影響(打包時 npm 會排除 node_modules)。

說明

  • 應用程式碼與 v1.242.0 完全相同——僅打包修復。來源正確性的內容隨本次部署上線(v1.242.0 從未抵達正式環境:其伺服器部署因 tar 失敗而回滾)。
  • 刻意不做:重新打 v1.242.0 標籤(npm 已發佈 1.242.0;登錄庫的壓縮包是乾淨的——打包時 npm 會排除 node_modules)。

[1.242.0] — 2026-10-07

掃描器的資料層重新值得信賴:失效的來源恢復運作,格式錯誤的回應不再能冒充空的職缺板,惡意的職缺板 URL 也不再能把掃描重新導向。

修復

  • 失效的來源恢復運作。 justjoin(API 回應的外層結構改為 {data, meta.next.cursor})透過游標走訪讀取完整目錄,並帶入新的薪資欄位;nofluffjobs 在沒有 salaryCurrency 時會回應 400——因此現在會送出父專案要求的必帶查詢參數,並讓結果分頁(pageTo)。himalayas 與 jobicy 過去在 10 萬多筆職缺中只能看到最新的約 20 筆——兩者現在都會走訪完整目錄(offset / 游標分頁,並設有頁數上限)。
  • 結構錯誤的 200 回應會在第 1 頁就拋出例外。 約 40 個來源過去把格式錯誤或挑戰頁的回應讀成「0 筆職缺」,掃描也悄悄地「成功」了。新的 server/lib/sources/_shape.mjs 防護(requireArray / requireContainer / requireObject)讓每個來源家族的失敗都大聲顯現;後續頁面失敗時,已收集的頁面會保留;分頁以原始頁面長度作為停止依據,而不是篩選後的數量。
  • SSRF:子字串式的廠商檢查改為解析後的精確主機釘選。 lever(clever.com 內含 lever.co)、greenhouse(5 個主機 + 舊版 boards[.eu].greenhouse.io 路徑別名主機)、ashby、smartrecruiters、workable、gem REST、ibm、arbeitsagentur、hecklerkoch(僅 www)、workingnomads(http://169.254.169.254 過去能穿過 DNS 防護被存取)、remoteok、remotive、rss、successfactors。全面使用 redirect:'error';職缺 URL 必須是釘選主機上的 https:(javascript: / data: / 非釘選主機的連結一律丟棄)。
  • 單一個設定錯誤的 portals 項目不再能中止整個掃描。 resolveAdapter 會接住轉接器拋出的例外,detectApi 逐公司包裝,拋例外的轉接器回傳 null;comeet 的擷取錯誤在進入日誌或隔離區記錄之前,會先遮蔽 ?token=。
  • 分頁與新鮮度。 workday 對超過 100 筆職缺的租戶會持續走訪(offset 分頁,MAX_PAGES);telegram 保留最新的貼文並以 ?before= 分頁,而不是悄悄保留最舊的;tencent 在缺少 Count 時也會繼續走訪;taleo 能解析法文表頭的表格;avature 以原始頁面長度停止;UTC 日期戳記(amazon 在 UTC 以東的差一天錯誤、torre、workingnomads、trudvsem);HTML 實體會被解碼(geekjob、getmatch、hh);mycareersfuture 產出完整的 12 欄位職缺結構。
  • 遠端判定收緊。 arbeitsagentur、vdab、rippling、teamtailor:合約百分比(「Verpleegkundige (100%)」、bundesweit)與「distributed」一詞(「Distributed Systems Engineer」)不再把現場職缺標記為遠端。

新增

  • tests/adapters-pin-coverage.test.mjs 補齊這些主機釘選所引入的分支覆蓋率。

說明

  • 測試數 4419 → 4812,瀏覽器測試 118。覆蓋率平均 行 98.15 % / 分支 89.36 %(閘門下限 96 / 86)。
  • 本版本未包含:同一輪審查中的用戶端發現(Phase 3 —— views、libs、CSS/a11y、i18n);sources/habr.mjs 仍缺少專屬的測試檔(棘輪豁免)。

[1.241.1] — 2026-10-07

即時評估在每個語系都能端到端運作,職缺地圖能標示含多個地點的長篇職缺,而且部署不再中斷正在執行的掃描。

修復

  • 評估能看到完整的方法。 內嵌履歷與個人檔案之後,上下文預算(144 KB)仍會截掉 modes/oferta.md 的結尾,因此每次即時評估都缺少最後幾個區塊。預算現在是 192 KB,提示詞軟上限為 256 KB,並統一定義於 llm-dispatch.mjs。
  • 報告使用所選語言。 語言指示現在除了結尾,也放在提示詞開頭;一般詞彙(職位、團隊、設計……)會以報告的語言書寫,只有名稱、技術與逐字引文保留英文。修正了印地語與日語報告大多是英文的問題。
  • 緩慢的評估不再以 HTTP 502 結束。 評估逾時為 300 秒(原本是服務商預設的 180 秒)。
  • 印地語區塊字母。 以天城文書寫 A–G 字母的報告(## ए)、## बी) … ## जी))現在能被辨識,SCORE: 也接受本地數字(३.८、٣٫٨、3.8)與小數逗號。
  • 職缺地圖:長地點。 像「Berlin Office · Berlin · Germany · Bosnia · …」這樣的儲存格(300 多個字元)過去會以 HTTP 400 被拒絕,該職缺也就沒有標記。現在改用第一個真實地點(以 ·、;、|、/、換行分隔;「Remote …」片段、「(HQ)」與「Office」會被略過);只有超過 2000 個字元的查詢才會被拒絕。
  • 部署與每小時掃描。 部署會暫停掃描與自動評估的計時器,等待執行中的作業完成(最長 15 分鐘),重新啟動後一律恢復計時器——包含回滾的情況。

新增

  • cvstart.org 上的職缺地圖,涵蓋全部 17 種語言,英文與俄文 README 也一併加入。
  • 所有語言的地圖截圖:每種語言一張(scripts/capture-map-screenshots.mjs),收錄於全部 17 個 README、cvstart.org 以及說明中新增的小節「如何使用地圖」(§33,×17)。

說明

  • 測試數 4404 → 4419,瀏覽器測試 118。遠端 QA(所有路由、網站、連結,以及 17 個語系的即時 LLM)在正式環境全數通過。
  • 本版本未包含:串流評估——300 秒逾時仍是硬性上限(較慢的本機模型仍可能觸及),Hermes 橋接仍使用非串流呼叫。

[1.241.0] — 2026-10-07

來自 28 個代理程式程式碼審查(約 190 項已驗證的發現)的強化版本:安全性、程序當機與資料遺失這幾類問題均已封堵,另有由 @bullitt186 貢獻的職缺地圖。

新增

  • 職缺地圖(#/map) —— 把來自 pipeline、最近一次掃描與追蹤表的職缺顯示在 OpenStreetMap 地圖上,透過 Nominatim 進行地理編碼,附帶可識別身分的 User-Agent、每秒 1 次請求的佇列、退避機制與磁碟快取。Leaflet 由本伺服器提供(不使用 CDN);CSP 只在 img-src 中新增圖磚來源。由 @bullitt186 貢獻(#381)。
  • 請求防護 —— 伺服器會拒絕它未提供服務的 Host(DNS 重新綁定),並拒絕跨站寫入與帶有副作用的 GET(/api/stream/*、/api/run/*、/api/geocode)。轉送公開網域名稱的反向代理需把它列入 ALLOWED_HOSTS;部署工作流程會依公開網址寫入該 systemd drop-in。
  • 覆蓋率閘門 —— npm run test:coverage:gate(scripts/coverage-report.mjs)會印出每個檔案的行/分支覆蓋率,並在低於逐步上調的基準線時失敗。

修復

  • 格式錯誤的請求不再讓伺服器停止。 Express 4 會忽略被拒絕的非同步處理常式,因此 /api/deep、/api/auto-pipeline、/api/career-plan、/api/tracker 中型別錯誤的欄位,或 /api/stats 中的 NUL 位元組,都會讓程序結束;現在這些情況會回傳 JSON 400,其他任何被拒絕的情況則變成 500。
  • 被中止或失敗的掃描不再清空上一次的結果。 掃描被停止或所有來源都失敗時,會保留 last-scan.json,而單一公司的掃描會合併而不是取代。一個設定錯誤的 portals.yml 項目不再讓整個掃描中止。
  • 評估能看到完整的方法。 模式檔案過去在 16 KB 處被截斷,因此 oferta.md(92 KB)遺失了 C–G 區塊;現在每個檔案的上限為:模式 128 KB、履歷 64 KB,且截斷會被回報。空白或被截斷的回答會失敗,而不是寫出空的報告。
  • SSRF: 重新導向的每一跳都會重新檢查是否指向私有目標,每一筆 DNS 回應都會被檢查,掃描期限涵蓋回應本文,*_BASE_URL 金鑰只會送往該廠商的主機(Hermes/Ollama:迴路位址或區域網路),pandoc 以 --sandbox 執行。
  • 事件迴圈停滯: 兩個二次方複雜度的正規表示式(HTML 剝除、markdown 淨化)已改為線性掃描器。
  • 資料遺失: 刪除 pipeline 的一列時,其他列的薪資資訊被保留下來;URL 中的 $& 會損毀 pipeline.md;模擬面試、人脈經營與深度研究的儲存會覆蓋先前的檔案;面試歷史保留的是前 40 輪,而不是最後 40 輪。
  • LLM 層: auto-pipeline 與深度研究現在會遵循 LLM_PROVIDER,並透過共用的分派機制支援所有服務商,記錄用量並回報截斷;Gemini 的 evaluate/test 不再儲存報告;OpenAI 使用 max_completion_tokens,僅限 Codex 的 OPENAI_MODEL 會以 gpt-5 送出;職缺描述會被圍起來視為不受信任的資料。
  • 報告、深度與內嵌 PDF 會傳入 --skip-fact-check(履歷事實查核會擋下任何含有數字的報告);個人檔案的頁面格式不再被覆寫。
  • 掃描器: 隔離區改以 URL 為鍵(修正了 URL 的重試),單次執行內的去重改以正規化 URL 為準,空白的清單項目不再比對到所有內容,寫入為原子操作,skipped_* 歷史列不再算作「已見過」。
  • 程序控制: SIGKILL 的升級機制過去從未觸發;批次執行在中斷連線時會終止其整個程序群組。
  • CI 與發布: CI 現在會執行 changelog 一致性、工作流程與 i18n 閘門;工作流程權限採最小權限原則;正式環境的機密只存在於 production 環境之下;npm 套件現在會附帶 docs/help/。
  • 測試: 現在沒有任何測試會碰到網路或真正的父專案(明確指定的 CAREER_OPS_ROOT 一律會被採用);複製來的演算法已換成正式環境的程式碼。

說明

  • 測試數 4056 → 4404,瀏覽器測試 118。伺服器覆蓋率基準線:行 96.7 %,分支 86.3 %(scripts/coverage-baseline.json)。
  • 規格現在集中放在同一個資料夾 docs/sdd/specs/;計畫書為 docs/sdd/PLAN.md,不變式→閘門對照表為 docs/sdd/TRACEABILITY.md。
  • 維運注意事項: 在反向代理後方請設定 ALLOWED_HOSTS=<公開主機>;位於公開主機上的 Ollama/Hermes 基底 URL 現在會被拒絕。

[1.240.0] — 2026-10-06

父專案對齊 —— career-ops main @ 62905981(VERSION 為 1.35.0,自 b39931e 以來共 177 個上游提交)。六項新來源(103 → 109,104 EN + 5 RU)、鏡射進八個既有服務商的修復,以及由 @bullitt186 貢獻的檢查清單式 pipeline 格式。

新增

  • 六項新來源 —— 103 → 109(104 EN + 5 RU),98 → 104 個 EN 轉接器。
    • ADP Workforce Now(workforcenow.adp.com,帶 cid + ccId)—— $skip 分頁依實際回傳的列數推進;若回應既沒有 jobRequisitions 也沒有 meta,會拋出例外,而不是被當成空的職缺板;明細頁擷取為選擇性加入(adpWorkforcenow.fetchDetails,預設 25,上限 100)。
    • Gupy(employability-portal.gupy.io)—— 全站關鍵字掃描,最新優先,並依 since_days 提前停止;workplaceType 以單數的 pt-BR 字串讀取,因此混合辦公職缺不會遺失;保密的職涯頁面會被捨棄。
    • JazzHR(*.applytojob.com)—— 卡片解析器帶有純錨點連結的後備方案,因此頁面部分改版後仍能產出職缺;頁面上有職缺連結卻解析出零張卡片時會拋出例外,真正空的職缺板則回傳 []。
    • Startup Jobs(startup.jobs/feeds/jobs)—— RSS,單次請求;格式錯誤、HTML 或被截斷的回應本文會拋出例外,而不是回傳 [](父專案合併後審查的修復,08fe5d06);在比對項目之前會先遮蔽 CDATA 與註解。
    • Taleo(<tenant>.taleo.net,tre.taleo.net 會被拒絕)—— 先對外殼頁面發出 GET,再對 searchjobs 發出 POST;外殼頁面沒有入口 id 時,會視為私有職缺板而拋出例外;欄位依表頭比對。
    • UKG Pro / UltiPro(recruiting[N].ultipro.{com,ca})—— Top=50 + Skip,絕不採信 totalCount 作為停止條件,頁數上限 100(限制在 1500 以內),選擇性加入的明細頁以能辨識字串的大括號走訪法解析。

修復

  • 以父專案的檢查清單格式讀寫 pipeline —— 上游 career-ops 會把 data/pipeline.md 寫成 ## Pending / ## Processed 區段,內含 - [ ] url | … 列;UI 過去把它讀成 0 筆待處理,寫入時還會在檔案末端追加一個圍欄區塊,而父專案從不看那裡。parsePipeline、addPipelineUrl 與 removePipelineUrl 現在讀寫的都是這種格式(也會對已處理的列去重;薪資以帶標籤的 note: comp … 片段表示);圍欄式格式維持不變。由 @bullitt186 貢獻(#380)。
  • 國家併入所在地 —— Ashby(主要的 address.postalAddress)、Breezy(country.name)與 Recruitee(扁平的 location)會附加國家,除非名稱中已將該國家作為完整單字包含,因此主要所在地為英國、次要為美國的遠端職缺,不會再被所在地篩選器丟掉。
  • MokaHR —— 接受 hire-r1.mokahr.com 區域主機(允許清單恰好是 app.mokahr.com + hire-r1.mokahr.com),API POST 與職缺連結使用該職缺板自己的主機,回應本文中的 success:false 現在會拋出例外,而不是被當成空的職缺板。
  • Avature —— 可解析 article--jobs 卡片(標題取自標頭錨點,所在地取自 icon-address span)。
  • SAP SuccessFactors —— /go/<Category>/<id>[/<offset>]/ 形式的已儲存搜尋網址現在會解析到租戶根路徑;先前它會請求 …/tile-search-results/,該路徑回應 200 卻沒有任何磚塊。
  • Deutsche Bahn —— db.jobs 會把含有它無法呈現之職缺的任何結果頁整頁清空;現在的走訪改以 pubExternalDate_tdt 排序,每頁 1000 筆(最多 5 頁),以原始命中錨點數作為停止依據,而且遇到缺少結果標頭或未能解析的職缺連結時會拋出例外,而不是回傳一個偏短的職缺板。
  • HigherEdJobs —— 以 HTTP 200 回應的 Incapsula 挑戰頁面,過去會被解析成零筆項目並被當成空的職缺板;現在會拋出例外。
  • 有一項測試在開發者機器上讀到了真正的父專案 —— tests/reports-list-cache.test.mjs 把 CAREER_OPS_ROOT 設為沒有 cv.md/portals.yml 的暫存目錄,而 paths.mjs 會忽略這種目錄,因此在本機上它統計到的是父專案的報告(6 份,而不是 3 份),而旁邊沒有父專案的 CI 則通過。

說明

  • 測試數 3782 → 4056。新增測試套件:sources-adp-workforcenow、sources-gupy、sources-jazzhr、sources-startup-jobs、sources-taleo、sources-ultipro、sources-location-country-fold、sources-deutschebahn-blanked-page、sources-higheredjobs-challenge-page,另有來自 #380 的 parsers-pipeline-checklist。
  • 父專案: 本分支已追上上游 main(落後 144 → 0 個提交),四項 ADR-0002 的分歧均確認完好;上游對西里爾字母 LOCATIONISH_RE 的還原沒有帶進來。
  • 已在 v1.239.0 發布,不再重複: AppliTrack、NEOGOV、Red Rover、SchoolSpring、OCC Mundial、Greenhouse 嵌入式職缺板、Workday myworkdaysite.com。

[1.239.4] — 2026-09-29

針對 v1.239.3 線上回歸測試的修補。

修復

  • 韓文與繁體中文評估回報缺少評分摘要。 這些報告長達 1 萬至 1.1 萬字,而摘要位於最後,因此超出了 8192 個輸出 token 的上限。評估(以及自動流程)現在允許 16384 個 token;若供應商仍截斷回答,第一則警告會直接說明,而不是列出缺少的區塊。
  • 線上檢查把一段簡短的德文回答判為非德文(德文停用詞 3 個,英文 0 個)。列表式回答的停用詞可能很少;在完全沒有英文停用詞時,現在 3 個即可。
  • 測試 3778 → 3782。

說明

  • 深度研究與模式路由維持 8192 token 上限:線上執行中它們的回答沒有達到該上限。

[1.239.3] — 2026-09-29

針對 v1.239.2 線上回歸測試的修補。

修復

  • 在 v1.239.2 修正日文與印地文之後,阿拉伯文評估仍未通過 A–G 檢查。 一份遮蔽後的標題骨架(日誌中不含報告正文)揭示了原因:無論提示詞如何要求,阿拉伯文報告都會依阿布賈德順序以阿拉伯字母書寫區塊字母。現在當 أ ب ج د هـ و ز 單獨出現在標題開頭附近時,檢查會將其計為 A–G,與已接受的西里爾形近字母相同;阿拉伯文單字中的同一字母不計。
  • 線上檢查在 A–G 檢查失敗時會輸出這份遮蔽後的骨架,便於從日誌診斷下一例。
  • 測試 3776 → 3778。

說明

  • v1.239.2 的說明稱檢查不會放寬;對阿拉伯文現在放寬了,因為三次線上執行顯示僅靠提示詞無法保住拉丁字母。

[1.239.2] — 2026-09-29

針對 v1.239.1 線上回歸測試的修補。

修復

  • 阿拉伯文評估翻譯了區塊字母,日文評估遺漏了評分摘要。 v1.239.1 只在提示詞最後一行重複語言。該行現在也說明每個標題中的拉丁字母 A–G 以及 ---SCORE_SUMMARY--- 區塊(標記與鍵為英文)不得翻譯,並要求使用該語言自己的文字。
  • 測試 3775 → 3776。

說明

  • 報告檢查沒有放寬為接受翻譯後的字母或標記:格式的穩定由提示詞維持。

[1.239.1] — 2026-09-29

針對 v1.239.0 線上回歸測試的修補。

修復

  • 日文與烏克蘭文評估幾乎全是英文。 語言指令位於提示詞開頭,其後是數十 KB 的英文專案檔案與職缺描述。現在提示詞會在最後一行再次指定語言。
  • 伺服器已在執行另一個掃描時,線上檢查會把掃描回報為失敗(部署後立即重試的每小時掃描)。伺服器拒絕第二個掃描是正確行為;檢查現在會等待並重試,最長 30 分鐘。
  • 測試 3774 → 3775。

說明

  • 伺服器的單次掃描限制沒有放寬:第二個掃描仍會以 SCAN_BUSY 拒絕。只有檢查有所變更。

[1.239.0] — 2026-09-29

與 career-ops @ b39931e 同步:五個新來源與三項移植的修正。

新增

  • 五個新來源(98 → 103;英文轉接器 93 → 98): 針對學校與公部門招募的 AppliTrack(applitrack.com/<學區>)、NEOGOV(SchoolJobs / GovernmentJobs)、Red Rover(jobs.redroverk12.com/org/<id>)與 SchoolSpring(<學區>.schoolspring.com),以及墨西哥最大的求職網站 OCC Mundial(帶搜尋關鍵字的明確 provider: occ)。每個來源都固定在自己的主機,只用 HTTPS,並拒絕重新導向。
  • 測試 3500 → 3774。

修復

  • Greenhouse 嵌入式職缺板(job-boards.greenhouse.io/embed/job_board?for=<職缺板>)會指向名為 “embed” 的職缺板而失敗;現在職缺板取自 ?for=,且只在真正的 Greenhouse 主機上生效。
  • myworkdaysite.com 上的 Workday 職缺板(/recruiting/<tenant>/<site>)無法辨識;現在可以掃描,職缺連結也指向同一主機。
  • 含括號的報告檔名(042-acme-(berlin)-….md)在追蹤表中會在第一個 ) 被截斷;現在會保留完整名稱。

說明

  • 僅限 CLI 的上游變更未移植 — PDF 與 CV 範本、scaffolder、批次執行器、career-profile 匯入、ATS 關鍵字涵蓋、面試模式翻譯、CLI 主機 pi:web-ui 不會執行它們。
  • 要從追蹤表開啟這類報告,報告路由還需要接受檔名中的括號;本版本未修改該路由。

[1.238.3] — 2026-09-27

掃描期間頁面仍能回應,遠端 QA 日誌也不再洩漏正式環境主機。

修復

  • 儀表板與報告載入需要數秒,掃描期間還會逾時。 /api/reports 與 /api/dashboard 每次請求都會讀取並解析所有報告——在正式環境伺服器上約 3.7 秒,期間其他請求全部被阻塞;掃描進行時,頁面載入會觸發 30 秒逾時。現在解析後的報告標頭會依檔案快取,檔案變動時才重新整理,因此列出報告只需對每份報告檢查一次檔案(500 份報告:本機 430 ms → 3 ms)。
  • 遠端 QA 的發現可能把正式環境主機名稱的一部分印到公開日誌中。 GitHub 只會遮蔽完整出現的密鑰,而截斷為 180 個字元的錯誤訊息剛好在主機名稱中間結束。現在發現與整份報告都會在截斷前先遮蔽。
  • 每一份 AI 評估都被標記為格式錯誤。 提示詞從未要求報告檢查所需的 ---SCORE_SUMMARY--- 區塊(上游專案的評估腳本會要求),而翻譯後的報告連 “Block” 一詞也翻譯了,導致檢查找不到各個部分。現在提示詞會要求摘要區塊並在每個標題中保留區塊字母,檢查也接受字母前的譯詞,摘要區塊會在報告顯示或儲存之前移除。

新增

  • 測試 3488 → 3500。

說明

  • 掃描器仍在伺服器行程內部執行;本版本移除了另一個主要阻塞點,但不會把掃描移到 worker 中。

[1.238.2] — 2026-09-27

修正正式環境伺服器在每小時掃描期間仍會出現的 502。

修復

  • 掃描期間頁面與資源回傳 502。 Node 在 5 秒後關閉閒置的 keep-alive 連線,而 Caddy 會重複使用閒置連線長達 2 分鐘。行程內掃描讓伺服器忙碌時,Node 關閉了 Caddy 剛送出請求的 socket,Caddy 因而回傳 502 connection reset by peer(一次掃描中 62 次,伺服器本身正常)。現在伺服器會將閒置連線保留 125 秒——比代理更久——因此一律由代理先關閉。

新增

  • deploy.yml → verify 會顯示代理回傳 502 的原因(錯誤類型,位址已遮蔽)、自上次重啟以來每分鐘的 502 數,以及每小時掃描的結果。
  • release.yml 手動執行時可建立缺少的標籤,因此在無法推送標籤的環境中也能發佈版本。
  • 測試 3486 → 3488。

說明

  • 掃描仍在伺服器行程內部執行,可能讓伺服器忙碌數秒;將其移到 worker 不屬於本修補。

[1.238.1] — 2026-09-27

正式環境伺服器首次線上回歸後的穩定性修補:同一時間只執行一個掃描,並修正網站問題。

修復

  • 兩個掃描同時執行會讓伺服器當機。 每個 GET /api/stream/scan 都在伺服器行程內毫無保護地執行完整掃描器;一次從介面啟動的掃描與每小時計時器重疊,行程因 JavaScript heap out of memory 結束(兩次,期間出現 502 錯誤)。現在同一時間只執行一個掃描:第二個請求會收到代碼為 SCAN_BUSY 的 SSE error,不會啟動任何掃描。部署工作流程也會提高伺服器的記憶體上限(heap_mb,預設 448 MB)。
  • cvstart.org 的 /help 與 /changelog 在手機上會橫向捲動(ja/zh 的 /changelog 在桌機上也會):長程式碼與目錄現在會自動換行。
  • 網站地圖的語言替代項中缺少印地語;網站的 i18n 檢查現在會把網站地圖與語言登錄表比對。
  • 說明 §19 要求把鍵加入「全部 8 個」語言檔 — 實際有 17 個。

新增

  • 遠端回歸測試(.github/workflows/remote-qa.yml):已部署應用的每個路由 × 每種語言(唯讀),cvstart.org 的每個頁面 × 語言 × 手機/桌機,README 與 wiki 中的每個連結,並可視需要執行一次真實掃描以及每種語言的即時 AI 呼叫。
  • 伺服器部署工作流程(.github/workflows/deploy.yml):recon、deploy(含逐位元組精確回復)與 verify(冒煙測試與穩定性報告)。
  • 測試 3481 → 3486。

說明

  • 本版本沒有降低掃描器本身的記憶體用量:保護機制阻止兩個掃描同時執行,drop-in 提高了上限。對真實的 98 來源掃描做效能剖析需要正式環境網路。
  • 每小時的 career-ops-scan 計時器沒有變動;recon 現在會顯示它執行的指令。

[1.238.0] — 2026-09-27

父專案對齊 — career-ops main @ 993085ce(VERSION 為 1.34.0,自 de2224a9 以來共 182 個提交)。四項新來源、十項鏡射的服務商修復、一個橫跨整個掃描器的 SSRF 破口被堵上,以及掃描結果新增一項免費的標題吻合度提示。

新增

  • 四項新來源 —— 94 → 98(93 EN + 5 RU),89 → 93 個 EN 配接器。
    • Eploy(provider: eploy)—— 在任意品牌化主機上讀取租戶的 /live-jobs.xml 網站地圖(僅限 HTTPS;IP 字面值、localhost、*.local、*.internal 與單標籤主機一律拒絕)。非網站地圖的回應(登入頁面)會拋出例外,而不是被當成零筆職缺放行。明細頁擷取為選擇性加入(eploy: { fetchDetails, detailLimit })。
    • HiringRoom(*.hiringroom.com)—— 優先讀取 JSON-LD 的 /portal/jobs,以舊版 /jobs 卡片作為後備;酬載中 http:// 形式的職缺網址會重新組成 HTTPS。
    • PeopleSoft Candidate Gateway —— 透過任意品牌化主機上的 /psc/{site}/EMPLOYEE/HRMS/c/HRS_HRAM_FL.HRS_CG_SEARCH_FL.GBL 路徑偵測。它會自行跟隨 PeopleSoft 自身的同源工作階段重新導向(最多 10 跳,每一跳都重新釘選回租戶來源並比對私有位址),重播「載入更多」表單,並以標記部分走訪(peoplesoftIncomplete)取代回報為已完成。
    • PrevueAPS(*.prevueaps.ca)—— 先從職缺頁面解析出網站 id,再讀取 JSON API。
  • #/scan 結果列新增標題吻合度提示。 每筆結果都帶有一個粗略的strong / related / weak 吻合度標籤,用來比對職缺標題與 config/profile.yml 中的目標職務(target_roles.primary 加上各原型名稱)。這只是一次免費的關鍵字估算:它從不過濾、重新排序或改變筆數,提示文字也如此說明。計算發生在結果被提供的當下,因此修改設定檔會反映在下一次重新整理。已翻譯為全部 17 個語系。對應父專案 #3261 的鏡射。

修復

  • 安全性 —— DNS 重新綁定防護在真實掃描中從未執行過。 http-json.mjs 只有在拿到裸的全域 fetch 時才會檢查解析出的位址,但兩個掃描器都會注入 makeTimeoutFetch(),於是每一次掃描都跳過了這項檢查。一個指向解析為 10.x 或 127.0.0.1 的主機名稱的來源,就這樣被直接擷取。這項防護現在移到 makeTimeoutFetch 內部執行,並與掃描逾時競速,避免卡住的解析器讓整個掃描停滯。發現於本次發布的審查過程。
  • 安全性 —— 內嵌 IPv4 位址的 IPv6 位址先前未被視為私有位址。 http://[::ffff:127.0.0.1]/(URL 解析器會將其改寫為 [::ffff:7f00:1])能通過 isValidJobUrl。::ffff:a.b.c.d / ::a.b.c.d 的點分形式與十六進位形式,現在都會依其內嵌位址分類。對應父專案的 _ip-guard.mjs。
  • Rippling 遷移到 v2 看板 API(ats.rippling.com/api/v2/board/<slug>/jobs),具備分頁(預設 10 頁,上限 50 頁)、對職缺連結的主機鎖定,以及 slug 驗證。貼上一個 API 網址現在會得到真正的 slug,而不是 api。
  • Recruitee —— 多地點職缺會列出每一個地點(A · B),(Sample) 示範職缺與無標題的列會被捨棄,只要兩個網址中任一有效,該筆職缺就會被保留。
  • Pinpoint —— 捨棄作為種子資料的示範職缺(重複的示範影片)。
  • Telegram 頻道 —— 含有多個 | 的第一行不再被讀作「標題 | 公司」,並能辨識更多職務相關字詞。此分支自有、支援西里爾字母的 LOCATIONISH_RE 未作變動(ADR-0002)。
  • Workday —— 第一頁出現 422/401/403 時只嘗試一次;Workday 的維護頁面或故障重新導向會將該看板判定為已失效(strict 模式下會被隔離)。CXS 與明細請求現在都會拒絕跟隨重新導向。
  • BambooHR —— 被重新導向的 /careers/list 會透過公開的內嵌 feed 進行判別:空白代表一個沒有職缺的存活看板,非空白則代表看板已失效。
  • Heckler & Koch / Rheinmetall —— 裸網域 heckler-koch.com / rheinmetall.com 會被改寫為 www. 開頭,讓不跟隨重新導向的傳輸層依然能夠到達。Ashby 拒絕跟隨重新導向。beesite 使用可取消的 delay() 來控制翻頁節奏。
  • 標題篩選器在兩側都會折疊重音符號(EN 與 RU 兩個掃描器),因此 Développeur 能被 developpeur 這個否定詞捕捉到。
  • 冷卻期的公司名稱比對現在能理解非拉丁文名稱與法人組織形式(中日韓/韓語標記、土耳其語帶點的 İ)。CJK 名稱過去會被壓縮成一個空鍵,永遠無法比對成功。
  • location_filter.strict: true 在設定了允許清單或封鎖清單時,遇到空白的所在地會採取失敗即拒絕(fail closed)。
  • Liveness —— 為 Greenhouse 內嵌(gh_jid)、Arbeitsagentur 與 We Work Remotely 新增公開 API 檢查;SmartRecruiters 現在依據職缺的 active 旗標判定。
  • PeopleSoft —— 相對路徑的「載入更多」表單動作,現在是相對於搜尋頁面本身解析,而不是相對於來源的根路徑。

說明

  • 測試數 3210 → 3481。新增測試套件:sources-eploy、sources-hiringroom、sources-peoplesoft、sources-prevueaps、title-fit、fetch-timeout-dns-guard、cooldown-company-match-unicode、liveness-api-more-rungs、ru-scanner-title-accent-fold。
  • 未移植的部分,附上原因:所在地篩選器的 Unicode 詞語邊界(web-ui 的篩選器以子字串比對為基礎,因此並不存在需要修復的邊界——完整移植父專案的所在地篩選器留作未來工作);同標題職缺去重(web-ui 僅以網址去重);聚合器轉發警告、黑名單網域範圍、未驗證的零結果紀錄,以及 scan-runs.tsv 遷移(web-ui 完全沒有這些介面);Workday 的分面拆分與截斷標籤(web-ui 對每個看板只擷取一頁);BambooHR 的 enrichDate(沒有日期強化的掛鉤);父專案中橫跨約 20 個服務商的 _http.mjs sleep 重構(web-ui 已經在使用共用的可取消 delay);updater、doctor、PDF、tracker/merge-tracker、validate-profile、discover-new-companies、web/ 這個 Next.js 應用程式、GitHub 機器人——它們要嘛是純 CLI,要嘛屬於轉送對象。
  • 刻意與父專案維持不同之處:Heckler & Koch / Rheinmetall 依舊僅使用 HTTPS(不做 http→https 升級);一般掃描中遇到失效的 Workday 看板,依舊回傳 [] 而不是讓整個掃描失敗。

[1.237.1] — 2026-09-22

針對 v1.237.0 的一次外部 QA 複查修補。 Jobstreet 的修復已首次從外部獲得證實:在 AU、NZ、HK 與 MY 主機上,舊有的 /id/job/<id> 形式會回傳一個乾淨的 404,而新的 /job/<id> 能到達一個真實的、受 Cloudflare 保護的路由;在 id.jobstreet.com 上情況則正好相反。遺失的連結確實存在。發現了兩項缺陷,皆出在 v1.237.0 自身新增的內容裡。

修復

書籍連結 ——文案翻譯了,目標網址卻沒有。 全部 17 個 SPA 語系與全部 17 個 cvstart.org 語系,都把頁尾指向 Agentic Coding Design Patterns 的連結指向英文版,而唯一被翻譯的文案——俄語 «Паттерны агентного кодинга»——承諾的卻是一本俄語版的書。新增的 data-i18n-href 套用器(與 data-i18n-title 同形,僅限絕對的 https:// 值)與 footer.patternsUrl 鍵,現在會把 ru 導向俄語版,其餘每個語系都導向英文版。回歸測試從兩個方向斷言這個不變式:翻譯過的標題意味著在地化的網址。 SEEK ——正在遷移主機,而傳輸層拒絕跟隨重新導向。 www.seek.com.au → au.seek.com、www.seek.co.nz → nz.seek.com:明細頁面如今已經回傳 301,這兩組主機目前也都以 200 提供 v5 搜尋 API。由於 http-json.mjs 設定了 redirect:'error'(一項值得保留的 SSRF 防護),一旦 API 路徑開始對舊主機上的 AU/NZ 項目發出重新導向,擷取就會失敗——而由於允許清單裡此前只有 www. 開頭的主機,根本沒有人能完成遷移。au.seek.com 與 nz.seek.com 現已加入 ALLOWED_JOBSTREET_HOSTS;舊主機仍保留,因此既有設定與已產生的連結照常可用。仿冒主機(au.seek.com.evil.com、au-seek.com)依然會被拒絕。 Wiki: Home 文件地圖中的 Agentic Practices 那一列,是十二列裡唯一沒有在 [[…|…]] 內轉義 | 的一列,導致 GitHub 截斷了該儲存格——連結失效,描述被隱藏。只差一個字元。

說明

來源數量維持 94 個不變(89 EN + 5 RU),89 個配接器。測試 3201 → 3210(+5 頁尾連結語系,+4 SEEK 主機)。 無法從外部關閉,只能記錄而非斷言:Oracle Cloud(Akamai 擋下了兩次 REST 探測)、Personio 的租戶釘選與 appendWorkType(需要在 portals.yml 新增一筆,而 UI 裡沒有其中一項的刪除功能),以及 liveness 的字串案例(沒有可控頁面;40 次存活探測得到 12 次存活、28 次不確定、0 次誤判到期)。 Help ×17 現在同時列出 AU-Main 與 NZ-Main 的兩種主機形式。標題結構維持不變:32 個 H2 / 122 個 H3。

[1.237.0] — 2026-09-21

父專案對齊 — career-ops main @ 93c4302b(VERSION 為 1.33.0,取自 career-ops-hq/main 的 75 個提交)。這次沒有新增來源:四項鏡射修復,其中兩項曾悄悄讓本專案遺失職缺。

修復

Jobstreet / SEEK ——每一則澳洲、紐西蘭、新加坡、馬來西亞與香港的職缺都連到一個死頁面。 parseJobstreetItem 無條件把明細網址組成 ${baseUrl}/id/job/${id}。/id/ 這個片段並非路徑本身——它是印尼語系前綴,只存在於印尼主機上。其餘每一個 SEEK 平台主機在存取 /id/job/<id> 時都回傳 404。web-ui 先前已把 www.seek.com.au、www.seek.co.nz、sg.jobstreet.com、my.jobstreet.com 與 hk.jobsdb.com 加進 ALLOWED_JOBSTREET_HOSTS,於是掃描順利回傳了這些職缺——每一則都指向一個 404 頁面。路徑現在改依主機決定:ID_LOCALE_HOSTS(id.jobstreet.com、www.jobstreet.co.id、jobstreet.co.id)維持 /id/job/,其餘一律改用 /job/,一個無法解析的來源網址則會落到共用路徑,而不是拋出例外。我們自己的兩個測試先前把這個錯誤寫進了斷言——分別斷言 hk.jobsdb.com/id/job/… 與 www.jobstreet.com/id/job/…——現已修正。 Oracle Cloud ——名單中途出現的一個短頁面提前結束了走訪,遺漏了看板尾端的職缺。 停止條件原為 listLen === 0 || listLen < PAGE_SIZE,但 ORC 會在名單中途回傳比上限更少的筆數,因為某些資料列會在伺服器端被篩掉。對 American Express 的上游實測:TotalJobsCount 為 454,分頁為 200 / 199 / 54——199 那一頁被判定為結尾,於是最後 54 筆職缺,佔看板 12%,從未被抓取。現在只有空白頁面才會結束走訪;只要租戶回報總數,分頁就會持續,直到 offset + PAGE_SIZE >= total;唯有沒有總數可供核對的租戶,才會在短頁面上結束。這符合更廣泛的慣例,即 API「即使尚未到達集合結尾,也可能回傳比要求數量更少的結果」(Google AIP-158)。hasMore 依然刻意被忽略——有些租戶在一個 7000 筆職缺的看板中,每一頁都回傳 hasMore:false。既然短頁面不再結束走訪,MAX_PAGES 現在是唯一能防範租戶回報虛假總數的防線,因此它有專屬測試涵蓋。 Liveness ——「本職缺已關閉」未被辨識,而一次誤判的到期是永久性的。 原本的樣式是 /this job (listing )?is closed/i,漏掉了同樣常見的 role 與 position 說法;上游在某個看板上實測,111 筆中有 111 筆不確定的職缺使用「role」這個字。現已放寬為 /this (?:job|role|position)(?: listing)? is closed\b(?!-)/i。結尾的 \b(?!-) 是一個複合形容詞防護:\b 單獨使用會排除 closedown,但仍會誤判真實文案,例如「This role is closed-loop control of the platform」,而擋下它的正是這個向前否定式。 Liveness ——job expired 現在歸入一個無法勝過可見「Apply」按鈕的軟性分級。 HARD_EXPIRED_PATTERNS 會在申請按鈕檢測之前被檢查,因此凡是放在那裡的樣式都會勝過一則仍在刊登中的職缺。/\bjob expired\b/i 在四種真實的刊登頁面上會誤判——一個「類似職缺」輪播項目、一個「隱藏已到期職缺」篩選標籤、一個頁尾常見問答,以及上述的 closed-loop 文案——因為分類器接收的是整個頁面的文字。它現在移到 SOFT_EXPIRED_PATTERNS,在申請按鈕檢測之後、刊登頁面判斷法之前被檢查,回傳 expired_body_soft;一個沒有任何申請按鈕的純「JOB EXPIRED」橫幅仍會判定為到期。這個方向之所以重要,是因為一次誤判的 expired 會被寫入掃描歷史,標記為 skipped_expired,接著會在往後每一次掃描中,把一則真實的職缺從去重結果中永久濾除,除非設定了 scan_history.recheck_after_days。

新增

Personio ——明確指定 personio: <slug> 可釘選租戶。 許多公司會把自己的 Personio 看板以 iframe 嵌入品牌化的徵才頁面,因此 careers_url 指向公司自己的網域,而動態消息實際位於 <slug>.jobs.personio.de;若不釘選,這些看板就什麼都解析不到。slug 受限於一個具錨定的許可清單(/^[a-z0-9][a-z0-9-]{0,62}$/i),組出的網址仍會經過既有的 new URL() 解析、HTTPS 檢查與 PERSONIO_HOST_RE 主機把關——這個釘選機制絕不會變成通往其他主機的途徑。一個帶有 .、/、@、:、..、空白字元、開頭為 -,或超過 63 個字元 DNS 標籤上限的 slug,會回退到今日的 api/careers_url 行為。與父專案不同,web-ui 的主機正規表示式區分大小寫,因此釘選值會先經過 new URL() 正規化——AcmeGroup 會解析為 acmegroup.jobs.personio.de,而不會組出一個連自家許可清單都會拒絕的主機名稱。 Jobstreet ——選擇性加入的 appendWorkType 會在標題後方附加該則職缺的工作型態,例如 Strategy Consultant [Part time],讓標題篩選與初步分類在掃描當下就能看到聘用型態。預設為關閉;workTypes 缺漏或為空皆為無動作。

說明

來源與配接器數量維持不變:94 個來源(89 EN + 5 RU),89 個 EN 配接器。本次版本異動的只有測試數量,3164 → 3201(+13 jobstreet、+7 Oracle Cloud、+9 liveness、+8 Personio)。 父專案刪除了 providers/telegram.mjs,整併為 telegram-channel。web-ui 把兩者都註冊為使用者可見的來源,因此不跟進這項刪除:跟進會讓登記表從 94 降到 93,並悄悄弄壞任何設定了 telegram 的使用者。僅在日後成為一項刻意且公告周知的移除措施時,才重新考慮。 jd-skill-gap.mjs 新增了一種未加冠詞的「what we are looking for」標題形式,以及一個粗體標記剝除機制。web-ui 是轉送該指令碼,而非鏡射它,因此這項修復會隨已部署的父專案一併送達,本專案不需任何變動。 未移植項目,並各自附上理由:lib/latex-escape.mjs、browser-extract.mjs、openai-eval.mjs、update-system.mjs 與該產生器(web-ui 既不鏡射、也不以 shell 呼叫它們);Go 語言的儀表板 TUI 與 web/ 設定表單(屬於另一套介面);doctor.mjs 與 analyze-patterns.mjs(唯讀轉送,其容錯失效機制會吸收這項變動)。 拉取之後重新驗證過的 fork 差異:providers/telegram-channel.mjs 中的西里爾字母 \p{L} 修正,已對照合併前的副本逐位元組核實無誤。

[1.236.0] — 2026-09-18

父專案對齊 — career-ops main @ 6a9c84c(VERSION 為 1.33.0,128 個提交)。兩個新來源,以及三項鏡射修復——其中兩項曾悄悄讓本專案遺失職缺。

新增

兩個掃描器來源,92 → 94(89 EN + 5 RU)。 Python.org Jobs(pythonorg)讀取該看板的公開動態消息;雇主歸屬為必要欄位,因此標題中未列出雇主名稱的項目會直接捨棄,而不會回填成看板項目本身的名稱。Generalist World(generalist-world)擷取該看板的卡片,一旦遇到無法解析的版面標記就會大聲失敗——卡片版面若有變動會直接拋出例外,而不是被讀成一個空看板,而後者正是讓一個擷取型來源變得不可信的失敗模式。兩者皆出現在 #/scan 的 Source 篩選器中,皆為主機固定(錨定正規表示式、僅限 HTTPS),並皆由端到端的假傳輸層測試套件驅動(分別為 43 與 36 個案例)。

修復

一個在多個城市都有職缺開放的 Workday 職位,被地點篩選器捨棄。 Workday 的清單端點對多地點職缺回傳的是一個數量,而其他每則職缺帶的都是一個地點:"53 Locations"。這不是一個地點,而 buildLocationFilter 是以不分大小寫的子字串比對——於是一個在 Austin 以及另外 52 個城市都開放的職位,不符合任何 allow: [austin] 項目而遭捨棄,而同一個職位若只單獨列出一個地點則會通過。對三個租戶的上游實測:291 筆職缺中有 53 筆是這種佔位文字,所以這是常見情況,而非邊緣案例。職缺真正的地點只存在於 CXS 的明細文件之中,因此每個佔位文字現在都要多花一次 GET 請求去查詢,並設有上限,每筆最多 200 次——超過上限時,佔位文字會維持可見,而不是被假造出來,因為一則尚未解析的職缺絕不能被讀成已解析。若明細查詢失敗或本身沒有地點,職缺會維持清單端點原本回傳的樣子:一個可見的 "53 Locations" 誠實地反映了無法解析的事實,而一個被清空的地點欄位卻會悄悄通過所有篩選。同一次 GET 請求也帶有 startDate,因此一則已豐富化的職缺會有精確的日期,而非清單端點的相對敘述文字("Posted 30+ Days Ago" → undefined);任何不是單純 YYYY-MM-DD 格式的值都會原樣保留,而不會交給 Date.parse 處理,因為它在非 ISO 格式下的回退行為屬於實作定義。 標題中標明的層級,不再讓兩則職缺申請被合併成同一次重貼。 "Insurance Specialist II" 是雇主自己的聲明:這則職缺申請並非另一則的同胞——在同一個基礎職稱之下,它有不同的薪資級距、職責範圍與申請編號。分詞器原本看不見這一點:w.length > 3 會把所有到 VIII 為止的羅馬數字,以及所有個位數字都捨棄,於是 "Insurance Specialist" 與 "Insurance Specialist II" 分詞結果完全相同,Jaccard 相似度算出滿分,重貼偵測器便把第二則標記為第一則的重新刊登。web-ui 的 role-matcher.mjs 原本完全沒有層級處理機制,因此這次直接落地了父專案的整套機制:層級的擷取會把羅馬數字與阿拉伯數字摺疊為同一個數字("Nurse II" 與 "Nurse 2" 是同一個聲明——而這一對先前恰好被誤判為不同),兩邊標明的層級必須一致,而僅有一邊標明層級時,則視為鬆散改寫,除非兩個標題各自還帶有對方沒有的詞——這代表兩處分歧,而不只是裝飾性差異。 一個已釘選篩選條件的 Avature 看板,實際上仍會走遍整個全域看板。 Avature 的搜尋介面會產生不透明的分面(facet)參數(?42386=[812132] 即為一個國家篩選器),而在 portals.yml 中釘選了一個縮小範圍看板的操作者,理應預期掃描會走那個範圍。resolveSearch 把網址重建為 ${origin}${path},卻直接把查詢字串捨棄——於是每一次請求,包括第一次在內,都走遍了整個租戶看板:更多頁面、更多請求,以及操作者刻意篩掉的職缺。分面參數現在會隨每一次分頁請求一併送出,並讓我們自己的 jobOffset 最後套用,如此一來,一個恰巧同名的雜散分面參數就絕不可能挾持分頁。

安全

devalue 5.8.1 → 5.9.2(透過 astro 間接引入),解決了唯一一個未結的 Dependabot 告警——一個由格式錯誤輸入觸發的阻斷服務漏洞。僅限網站端相依套件;執行期程式碼未變動。

說明

未移植項目,並各自附上理由。 父專案把 lever 的逾時從共用的 10 秒預設值調高為 30 秒,把 manfred 調高為 25 秒——這是一個不移植項目:web-ui 的掃描傳輸層本來就允許 60 秒(DEFAULT_SCAN_TIMEOUT_MS),因此這兩個看板在這裡本來就比那邊修復後給予的餘裕還要更寬鬆。iCIMS 重複的 careers- 前綴問題在這裡也不可能發生:web-ui 直接從 api/careers_url 取得租戶來源(origin),從不會從租戶名稱組出主機名稱;它新增的備援主機走訪機制,是為了父專案較寬鬆的設定而設計的功能,而不是缺陷修復。deutschebahn 與 manfred 新增了暫時性中止的重試機制,而 web-ui 的 60 秒上限讓這一點顯得沒那麼迫切——因此只記錄而不移植,讓未來若出現長時間走訪逾時問題時,有一個已知的第一線索可查。128 個提交差異中其餘的一切都屬於 CLI、驗證器、儀表板或 web/(父專案自己的前端):liveness-browser、check-jd-archive、verify-ats/verify-portals/verify-cv-facts、update-system、set-status、audit-portals、discover-ats、scaffolder、test-all、外掛程式清單檔案。 拉取之後重新驗證過的 fork 差異:providers/telegram-channel.mjs 中的西里爾字母 \p{L} 修正、fork 自身的 providers/telegram.mjs、web/src/lib/clis.ts 中的 hermes。上游 career-ops-hq/main 已完全合併(0 個提交缺漏)。3066 → 3164 項測試;說明文件現於全部 17 個語系套件中皆載明 94 個配接器(89 EN + 5 RU),標題結構維持不變,為 32 H2 / 122 H3。

[1.235.0] — 2026-09-16

父專案對齊 — career-ops main @ 68e6b94(VERSION 為 1.33.0,42 個提交)。兩項鏡射修復;其中一項曾悄悄讓本專案遺失 MokaHR 職缺。

修復

同一個 MokaHR 職缺看板上的第二筆職缺會憑空消失,而不是被加入。 url-key.mjs 會把職缺網址化簡為一個比對用的鍵,而它先前會無條件捨棄網址片段——這在多數情況下合理,因為幾乎每個片段都只是呈現用途(#apply、#section-2)。MokaHR 卻是我們自家登記表裡的例外:其職缺網址形如 …/social-recruitment/{tenant}/{id}#/job/{n},同一租戶下的每則職缺共用同一路徑,職缺 id 只存在於片段之中。於是它們全都被正規化成同一把鍵。修復前實測:把兩筆不同的 MokaHR 職缺貼進管線,最後只會留下一行——沒有錯誤、沒有警告,毫無異狀可察;掃描器的新鮮度過濾器(en-scanner.mjs)讀的是同一把鍵,所以只要該租戶底下曾有任何一筆職缺進了掃描歷史,其餘的都會被當成「已經看過」而捨棄。normalizeUrl 現在會在捨棄片段之前,先把可辨識的 #/job/{id} 或 #/jobs/{id} 路由提升為一個僅供比對用的查詢鍵——MokaHR 沿用既有的 mokahr_job_id 名稱,讓先前已寫入的鍵仍可比對,其他任何主機則會得到一個內部用的 _career_ops_fragment_job_id。這個被提升的 id 是附加,而非覆寫:一個網址若已帶有該參數,代表的是另一筆職缺的 id,直接覆寫就會把這把鍵原本要區隔開的兩樣東西合而為一。所有無法辨識的片段仍會照舊被摺疊,這一半原本就是對的。 accept-encoding 現在會在每一次來源請求中固定送出。 若不設定,Node 會協商其 undici 版本所提供的任何選項;從 Node ≥ 23 起,這包含了 zstd,而 amazon.jobs 回傳的 zstd 回應會恰好在第 1024 個位元組被截斷,狀態碼卻仍是 200——於是這個失敗看起來像 Unterminated string in JSON at position 1024,彷彿是 API 格式錯誤,而不是傳輸層的問題。現在每個請求都會送出 gzip, deflate, br。在我們的執行環境上,這是預防性措施,而非修補:Node 18/20/22——CI 矩陣與伺服器(22.22.1)——送出的是 gzip, deflate,這一點經過直接驗證而非假設,所以這裡本來就沒有被截斷過;今天真正改變的是現在會提供 br,這些版本預設不會要求它,但能正確解碼。這個預設值是以不分大小寫的方式套用,而這正是值得留意之處:標頭名稱本就不分大小寫,fetch 又會把兩個同名項目合併成一個以逗號分隔的值,因此若呼叫方傳入 Accept-Encoding: identity,原本會變成送出 gzip, deflate, br, identity。

說明

在 fetchImpl 邊界上,標頭維持為純物件,而父專案在此處交給 fetch 的是一個 Headers 實例。這是刻意為之的分歧,並非疏漏:web-ui 的 fetch 輔助函式接受可注入的 fetchImpl,而 45 個來源測試套件會從擷取到的內容讀取 opts.headers['User-Agent']——Headers 實例不支援中括號存取,所以忠實移植的是行為(不分大小寫覆寫、呼叫方優先),而非實作本身。呼叫方的物件只會被複製,絕不會被就地修改,因為有多個來源共用同一個標頭常數。父專案那項配套修復——以不分大小寫的方式套用預設 user-agent——則是不移植:web-ui 完全不會注入預設 UA,每個來源都會設定其看板要求的那一個,因此呼叫方的 User-Agent 根本沒有預設值可供衝突。 兩個新測試套件與三個新案例釘住這一對修復:tests/http-accept-encoding.test.mjs(釘住確切的固定值、四個 fetch 輔助函式各自的行為,以及呼叫方在三種大小寫拼法下的覆寫——斷言只會留下單一一個 accept-encoding 鍵,而合併錯誤原本會產生兩個)以及 tests/parsers-pipeline-hash-route.test.mjs,它驗證的是使用者看得見的介面,而非那把鍵本身:兩筆 MokaHR 職缺都必須進到管線裡,同一筆職缺以四種拼法重貼一次仍必須摺疊成一行,而一個裝飾性片段不得被誤判為身分識別。所有案例移植前都已確認會失敗。 該固定位於傳輸層,而不僅僅在這些輔助函式中:92 個來源中有 25 個直接呼叫注入的 fetchImpl,根本不經過它們,因此由 makeTimeoutFetch——兩個掃描器實際注入的包裝器——來套用它,這是所有來源共享的唯一通過點。(父專案不需要等價物:providers/_http.mjs 是它唯一的傳輸層。)現在有一道結構性防護:若新來源直接呼叫 fetch 而非使用交給它的傳輸層,CI 就會失敗;另有一個行為案例,讓 25 個之一的 greenhouse 經由該包裝器執行,證明標頭確實送到了線上。 未移植,理由如下:liveness-browser.mjs(有頭 Playwright 重用防護——web-ui 不會驅動有頭瀏覽器)、check-jd-archive.mjs(web-ui 沒有對應的轉送)、test-all.mjs(父專案自己的測試線束;fork 自身的 vpFixtureEnv 差異維持不變)、web/(父專案自己的前端,不在鏡射範圍內)、外掛程式清單檔案與 SIGNATURES.md(僅存在於儲存庫層級)。batch/batch-runner.sh(在 sed 插值前先跳脫 & 符號)與 jd-skill-gap.mjs(依資料根目錄解析 cv.md)屬於唯讀轉送——web-ui 是以 shell 呼叫父專案的副本,因此這兩者在本專案都不需要改動程式碼,而 jd-skill-gap 這項還讓 /api/jds/:id/skill-gap 的轉送更加正確:cv.md 現在會依資料根目錄解析,而不是依處理程序被啟動時所在的任意目錄。拉取之後重新驗證過的 fork 差異:providers/telegram-channel.mjs 中的西里爾字母 \p{L} 修正、fork 自身的 providers/telegram.mjs、web/src/lib/clis.ts 中的 hermes。來源數維持在 92 個不變(87 EN + 5 RU),因此說明文件未受影響。3042 → 3066 項測試。

[1.234.0] — 2026-09-15

父專案對齊 — career-ops main @ 56cce8f(VERSION 仍為 1.32.0,距 1.32.0 已有 58 個提交)。同一項修復鏡射至 15 個來源,並加上防護以杜絕重演。

修復

單一格式錯誤的職缺 id 不再清空整頁。 encodeURIComponent 在遇到孤立的 UTF-16 代理項時會拋出 URIError,而 JSON 負載就可能攜帶這種代理項——JSON.parse('"\\uD800"') 會把它原樣保留。十五個來源都在解析迴圈內部,以主機端提供的 id 或 slug 組出每則職缺的 URL,於是一筆錯誤的職缺就會中止整個迴圈,連帶丟失該頁上的所有職缺;由於例外在逐公司的 catch 取得任何回報之前就已經展開,這甚至不會被記錄成一次來源失敗。父專案在 providers/_safe-url.mjs 中修復了這個問題(safeEncodeURIComponent 在遇到 URIError 時回傳 null,其餘例外照樣拋出),並讓每個迴圈的 id 都經過它處理,遇到 null 只丟棄那一筆職缺。該輔助函式現已成為 server/lib/sources/_safe-url.mjs,同樣的修改套用到十三個被鏡射的來源——alibaba、arbeitsagentur、bamboohr、feishu-jobs、garena、jibeapply、manfred、meituan、mokahr、phenom、thehub、tkms、vdab——以及兩個僅 web-ui 獨有、由新防護在第一次執行時揪出的來源:jobstreet 與 trudvsem,它們有著相同的迴圈結構與相同的潛伏拋出點。範圍是刻意收窄的:只有來自 API 的 id 才會經過這個輔助函式。由設定衍生的值維持嚴格編碼、出錯就直接拋出——garena 的 office(一個 portals.yml 欄位片段)仍走會拋出例外的 urlSegment,而它的 API id 則改用會回傳 null 的 idUrlSegment;csod 的 corpName 與 4dayweek 經 SLUG_RE 驗證過的 slug,則以具名方式列入白名單並附上理由。 rheinmetall 的標題備援機制可能因格式錯誤的 href 而拋出例外。 當卡片沒有標題時,標題會透過 decodeURIComponent 從 URL slug 重建而成,而它在抓取到的 href 含有錯誤的百分比序列時會拋出例外——這是同一種「一列壞資料拖垮整頁」的結構,只是發生在解碼這一側。現在它會採防禦性解碼,並在失敗時回退到原始 slug。 來源註冊表現在會跳過以 _ 開頭的檔案。 自動探索機制原本會匯入 server/lib/sources/ 底下除自身之外的每一個 .mjs 檔案,並對任何沒有 export const meta 的檔案發出警告——於是這個輔助檔案在啟動時就觸發了警告。現在採用父專案的慣例:開頭底線代表「這不是一個來源」;這類檔案既不會被匯入,也不會被警告。一個沒有宣告、也沒有 meta 的檔案仍會觸發警告,而探索測試現在同時斷言這兩種情況(已確認在舊過濾邏輯下會失敗)。 網站建置腳本 sync-assets.mjs 以同樣的規則計算磁碟上的配接器檔案,因此該輔助模組不再觸發其靜默遺失防護。

說明

tests/sources-url-encoding-surrogate.test.mjs 鏡射父專案測試套件的三個部分:輔助函式的契約(成對的代理項——也就是一般的表情符號——仍會正常編碼;若值自身的 toString 拋出例外,那是呼叫方的錯誤,例外會照樣傳播)、全部十五個來源各自的行為(一個錯誤 id 加一個乾淨 id → 不拋出例外、保留乾淨的、丟棄錯誤的,每個都透過該來源匯出的純解析函式驅動,thehub 則透過其擷取器搭配假傳輸層驅動),以及一道來源層級的防護:每個已轉換的檔案都必須匯入該輔助函式並且呼叫它,任何檔案都不能只引用卻不匯入,未經審查的檔案也不得以裸露的 encodeURIComponent 組出職缺 URL——一個沿用舊結構的新來源會讓 CI 失敗,並附上出錯的行號。全部十五個來源層級的案例在移植前都已確認會失敗。 父專案差異中的其餘部分皆未移植,理由如下:batch/batch-runner.sh --cli 是僅供 CLI 使用的旗標(web-ui 不會以 shell 呼叫批次執行器);invite-match.mjs 現在改從程式碼根目錄解析 set-status.mjs——web-ui 不會轉送它;company-funded.mjs 內部同樣套用了 safe-url 的改動,但 /api/company-funded 解析的 --json 輸出結構未變;test-all.mjs 只是搬動了逾時預算(fork 自身的 vpFixtureEnv 差異維持不變);web/ 是父專案自己的前端,不在鏡射範圍內;AGENTS.md/SIGNATURES.md 僅存在於儲存庫層級。拉取之後重新驗證過的 fork 差異:providers/telegram-channel.mjs 中的西里爾字母 \p{L} 修正、fork 自身的 providers/telegram.mjs、web/src/lib/clis.ts 中的 hermes。來源數維持在 92 個不變(87 EN + 5 RU),因此說明文件未受影響。3022 → 3042 項測試。

[1.233.2] — 2026-09-10

修復 — 兩條高危 CodeQL js/remote-property-injection 告警。

修復

把儲存值寫入 process.env 的迴圈原本遍歷由請求體建立的 Object.entries(safe),內部帶著針對原型鍵的顯式防護。程式碼本身安全,因為 safe 僅從 KNOWN_KEYS 拷貝,但分析器無法跨兩個迴圈追蹤該約束。現在直接遍歷常量陣列,屬性名可證明是模組級字面量。行為完全相同。

說明

釘住舊防護的測試是對源碼文字的 grep。已換成結構檢查與行為檢查,後者以原始 JSON 發送 __proto__——物件字面量會設定原型而非建立屬性。3021 → 3022 項測試。

[1.233.1] — 2026-09-10

修復 — 指南從未描述 v1.233.0 新增的控制項,而同一節仍留著 200 個版本前就不再成立的說明。

修復

DOC-1 — 每個下拉框現以「使用預設值(…)」開頭,表達介面此前無法表示的狀態:該金鑰未設定。它與從清單選中同一個值的差別既看不見又要緊。已在既有的 ### Behavior 清單補入三條 ×17。 DOC-2 — 表格中有一行沒有指明任何金鑰:Key 欄位是 (server uses default UA),描述 v1.19.0 已移除的 HH_USER_AGENT,並要讀者去 dev.hh.ru 註冊——自 v1.65.0 起毫無用處。三處陳舊說明已清除;排障行是改寫而非刪除,因為自 2026 年 7 月起 hh.ru 對非俄羅斯 IP 回傳 HTTP 451。 金鑰表只列出 45 個中的 6 個,標題卻承諾登記表。一句話 ×17 聲明它是精選並指向 #/config。

說明

tests/help-recognized-keys.test.mjs 將金鑰表第一列與實時 KNOWN_KEYS 比對。3018 → 3021 項測試;說明仍是 32 H2 / 122 H3。

[1.233.0] — 2026-09-10

修復 — #/config 儲存路徑上的三處發現;CONFIG-3 由 v1.232.1 的修復本身引起。

修復

CONFIG-3 — 與預置值恰好相同的有意選擇被當作「未碰過」丟棄。initial 混淆兩種來源:來自 .env 的預置與來自 defaultValue 的預置,後者的相同是歧義的。現在在 initial 旁記錄 seededFromFile。 刪除鍵會顯示「· 0」,因為伺服器只回傳寫入的內容。現在還會回傳 removed,提示兩者相加。 下拉框能設不能清:18 個中沒有一個有空選項。現在每個以**「使用預設值(…)」**開頭。新增一個 i18n 鍵 ×17。

說明

單元基線仍為 3018;瀏覽器 111 → 116。

[1.232.1] — 2026-09-10

修復 — #/config 的儲存會寫入使用者從未碰過的欄位。

修復

CONFIG-2. v1.57.1 會為未設定的控制項填入其 defaultValue,讓欄位顯示伺服器真正使用的值;而 save() 只對機密欄位查詢 dirty,其餘一律照送。於是為顯示而放的值被當作使用者的選擇送出:改動一個下拉選單就送出 28 個鍵並釘死 18 個從未開啟的欄位。變的是每項設定的狀態:從「未設定,跟隨專案預設」變成「釘死在 .env」。

說明

與 cfg.values 比較並不夠:未設定的 select 顯示自己的 defaultValue,而儲存值是 ''——請求體只從 27 個鍵降到 19 個。現在改為與每個控制項初始化時的值比較。提示中的鍵數也回來了。105 → 111 個瀏覽器測試。

[1.232.0] — 2026-09-09

修復 — 瀏覽器 QA 發現的兩處缺陷,均非來自 v1.231.5。

修復

PROFILE-1 — #/profile 會讓整頁橫向捲動。 欄位值是僅帶內聯字型樣式的匿名 <div>,計算後的 overflow-wrap 仍為 normal。LinkedIn 的 URL 不給 CSS 任何合法斷點,於是元素的 min-content 寬度就是整條字串;格線項目預設 min-width: auto,拒絕收縮,卡片便撐破軌道、行、主欄直至文件(1280 px 下實測 23 px 橫向捲動)。已用共享類別 .card-value 加 overflow-wrap: anywhere 修復,並以 .card-row > * { min-width: 0 } 兜底。是 anywhere 而非 break-word:只有 anywhere 計入 min-content。 CONFIG-1 — POST /api/config 對 select 欄位來者不拒。 {"LLM_PROVIDER":"not-a-provider"} 回傳 200 並寫入 .env;此後一切照常,因為解析器悄悄退回備選。持有這 19 個值的 LLM_PROVIDERS 一直由 env-config.mjs 匯出,只是校驗器從未查閱。

說明

只校驗 LLM_PROVIDER:模型清單的真實域屬於廠商,會在我們兩次發布之間變動。已儲存的值予以豁免,因為表單每次儲存都會重送全部非機密欄位。另補上 OLLAMA_API_KEY 描述符。3013 → 3018 項測試。

[1.231.5] — 2026-09-09

安全 — 關閉七項公告,其中一項為嚴重的遠端程式碼執行。 未改動任何原始碼檔案。

安全

自 Dependabot 併入: multer 2.2.0 → 2.3.0(高危 — 中止時檔案描述子洩漏導致阻斷服務),以及站台的 svgo 4.0.2 → 4.1.0(高危與中危 — removeScripts 放過了可執行連結)。 隨後由這兩次併入觸發的重新掃描又暴露五項。 受影響區間均已被現有 ^ 涵蓋,因此只需升級:站台 astro 7.1.0 → 7.3.2 ——嚴重,經 AVIF 影像最佳化的遠端程式碼執行(7.2.8 修復),並附帶授權繞過(7.2.4);sharp 0.35.3 → 0.35.4 ——高危,libheif 漏洞;兩份清單中的 js-yaml 4.3.1 → 4.3.2 ——高危。

說明

此處「僅相依」並不等於「無風險」。js-yaml 解析應用讀取的每一份設定,multer 接收每一次上傳,而 Astro 跨了一個次版本。已驗證:test:ci 3013/3013、瀏覽器檢查 4/4,以及完整重建站台後仍是同樣的 86 個頁面與全部 17 份鏡像。npm audit:兩份清單皆0 個漏洞。

[1.231.4] — 2026-09-07

修復 — v1.231.3 修的是點擊 Doctor 之後的殘局,不是當時。行動端與桌面端再次回報。

修復

請求進行期間,withSpinner 仍在改寫按鈕。 v1.231.3 讓它快照子節點並在 finally 裡還原,節點確實回來了——但忙碌提示依舊執行 replaceChildren,內容是 '⏳ ' + original。於是在 doctor.mjs 執行的整段時間裡,36 px 的方塊裝著 ⏳ 🩺Doctor 文字,脹成壓壞整列的寬膠囊;桌面端則擠成一團 ⏳🩺Doctor。擁有元素子節點的按鈕,現在完全不再被改寫。 它的提示交給 .is-loading 類,該類如今在 CSS 裡把圖示換成沙漏,因此尺寸不可能變化:320 px 下靜止 36 px、進行中 36 px、結束後 36 px。

說明

教訓在斷言,不在程式碼。v1.231.3 的閘門等待 aria-busy 清除才測量——按其構造,只能看到 finally 已經修好的狀態。新的瀏覽器檢查自己把 promise 攥在手裡,在飛行途中取樣。3012 → 3013 項。

[1.231.3] — 2026-09-07

修復 — 點擊 Doctor 會把行動版頂欄撐壞。v1.231.2 發布當天由手機端回報。

修復

UI.withSpinner 壓平了它正在裝飾的那個按鈕的子節點。 它的忙碌提示走的是 textContent:儲存 button.textContent,寫入 '⏳ ' + original,再寫回去。而對 textContent 賦值會把所有子節點替換成一個文字節點——偏偏 v1.231.2 剛剛把頂欄的每個操作拆成 .btn-ico 與 .btn-label,並在 max-width: 900px 下隱藏標籤。於是第一次點擊 Doctor 就永久摧毀了這兩個 <span>:行動版規則再無可匹配之物,標籤以裸文字回歸,36 px 方塊膨脹成壓住主題切換的 🩺Doctor 膠囊。它不會自癒——只有重新整理頁面才能恢復標記。修復改為快照子節點並以 replaceChildren 還原;純文字按鈕,也就是 24 處呼叫中的絕大多數,行為與此前完全一致。

  • FIND-3 — 在 320 px 的手機上,搜尋框被壓縮到 8 px。 把頂欄併成一列後,searchbar 成了唯一可伸縮的元素,而上個版本的變更日誌自己就寫著——「可以縮到什麼都不剩」。它真的縮到什麼都不剩:320 px 下輸入框 8 px,340 px 下 28 px,21 個字元的預留文字只露出一個。可點可輸入,但已經不像一個輸入框。min-width 救不了:320 px 的列裡有 32 px 內距、40 px 選單和 162 px 操作,拿不出 120 px。於是在 420 px 以下,它藏到放大鏡後面,輕點即展開佔滿整條列:320 px 下 182 px,360 px 下 222 px,390 px 下 252 px,任何寬度都不溢位。展開按鈕保持唯一的可存取名稱,狀態由 aria-expanded 傳達;🔍/✕ 的切換是純 CSS。420 px 以上一切照舊。由針對線上建置的瀏覽器迴歸發現。

說明

這與 v1.231.2 記下的 applyI18n() 陷阱同屬一類缺陷——把 textContent 賦給一個如今擁有元素子節點的元素——只是經由另一個呼叫方觸發。教訓可以推廣:一旦把某元素的內容拆進子 <span>,該元素上每一個既有的 textContent 寫入方都成了潛伏的缺陷。 雙重鎖定:在 node:vm 中以假按鈕跑真實的 withSpinner,以及在 320 px 的真實瀏覽器裡點擊 Doctor。兩者都已確認在舊程式碼上失敗。3009 → 3012 項測試。


career-ops-ui v1.267.1 · page 2/9 · mirror of the project changelog for cvstart.org