Ctrl K
ComunidadGuías…

CHANGELOG.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# 变更记录

所有值得关注的项目变化都会记录在此。格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),版本号遵循 [Semantic Versioning](https://semver.org/lang/zh-CN/)。

## Unreleased

## 2.1.0 - 2026-11-10

### Added

- **实施批次(2026-11-10):问题清单 24 条落地(1–13、15–24 共 23 条实施,第 14 条仅存档)。** 无新增依赖;数据库迁移两条(`0009`、`0010`,head `0010_external_resume`);真实数据集与模型链路均做过实机验证。分域摘要:
  - **语义检索(第 1、16 条)**:远程服务由「单份扁平配置 + 6 张厂商卡」改为**命名配置列表**(新建/编辑/复制/删除、重名 409、上限 20),存储走 `data/global_settings.json` 顶层 `embedding_profiles`(旧 `embedding` 段保留为当前生效镜像,**未升 GLOBAL_SETTINGS_VERSION**);支持**本地 ↔ 远程一键切换**(ready 态切换只改 `active_profile_id`,失败不污染当前生效项);空密钥只沿用同一条 profile、绝不跨 profile 继承;「清除密钥」语义收窄为只清当前生效项并保留命名配置。API 新增 `POST /api/settings/embedding/profiles*`,`GET /status` 增加 `profiles` / `active_profile_id` / `local_profile_id`(向后兼容新增)。
  - **语音转写(第 2 条)**:改为**命名配置列表 + 使用/复制/删除**,「使用」不探测;配置从「每个数据集一份」改为**全局一份**(`global_settings.json` 顶层 `asr_profiles`),首次打开设置页时把当前数据集的旧 `asr_config` 幂等迁移成一条「默认配置」;旧配置行保留作回滚面包屑(其它数据集旧配置留在原库但不再生效)。新增 6 个 `/api/settings/asr/profiles*` 接口,旧接口保留。**安全修复**:备份勾选「不包含 API Key」时,此前仍会把 `app_setting.asr_api_key` 与 `app_setting.search_deepseek_api_key` 带进包(macOS/Linux 上是明文);现已收集并剥离,并补反证测试。
  - **联网搜索(第 3、4、5 条)**:搜索来源/地址/Key 改为**全局一份配置**(`global_settings.json` 顶层 `web_search`,旧 `app_setting.search_config` 懒迁移一次、旧行保留不删、不随数据集备份恢复);来源卡网格改 `alignItems: start`、卡片去掉 `height:100%`,DeepSeek 计费/隐私提示移到网格下方全宽常驻 Alert(卡内不再留白);「每次最多返回的结果条数」上限 **15 → 50**、语义改为「最多返回」,`web_search` 助手工具新增可选 `limit`(模型可指定更少),「通用参数」由多选改单选 Radio,提示写明「实际条数受来源限制(Bing 最多 8 条)」。
  - **示例数据(第 6 条)**:欢迎向导「载入示例数据体验」改为载入**随包发布的完整演示数据集**(内置 `backend/app/assets/sample/sample_dataset.zip`,由 `scripts/seed_test_dataset.py` 生成并 `--check` 自检):14 岗位 / 6 简历 / 52 资料 / 8 资料箱 / 8 知识库 / 4 面经 / 10 台账 / 12 进度 / 60 网申资料 / 10 附件;再次点击复用同一份(`reused:true`),旧示例保留不删、主数据零字节影响。
  - **超额页预览(第 7 条)**:简历草稿预览超出「最大篇幅」的页现在**照常显示正文**并保留红框与「超出上限,不计入导出」标注(导出请求新增 `include_overflow_pages`,仅 `preview=true && format=html` 跳过截断,`render_config_fingerprint` 同批计入防缓存串号);普通导出 / PDF / Word / HTML 仍按上限截断。
  - **面试设定(第 8 条)**:「面试官设定」由网格改纵向堆叠 + 字段 4 等列(宽屏填满整行、`@container` 768px 两列、440px 单列),右侧不再出现空列;轮数说明移出格子放字段下方全宽,点「指定轮数」只增高输入框那一档;数据/校验/提交协议未变。
  - **并发生成(第 9 条)**:支持后台同时生成多份简历——API 由「任何活动任务都 409」改为**同岗位去重**;Runner 改为「管理器 + 每任务 worker」,`RESUME_GENERATE_MAX_CONCURRENT`(1–8,默认 4)限制同时运行、`RESUME_GENERATE_MAX_PENDING`(默认 50)超出排队、有空位自动开始、排满才 429;取消只影响选中的那一份;进度提交粒度 200 → 400 字缓解 SQLite 单写争用;前端弹窗关闭即回配置态(后台继续),任务条支持多条滚动展示。
  - **思考参数(第 10、11 条)**:修复 Responses 链路「设置页写着思考关闭、实际一直在思考」——关闭时显式发 `reasoning: {"effort": "none"}`(DeepSeek `/responses` 实测唯一有效写法;`thinking:{type:disabled}` 在该端点无效);实测同一岗位生成 **148.6s → 27.5s(−81.5%)、输出 tokens 25,149 → 6,118(−75.7%)**。思考规则表拆为 `thinking_vendor_table / thinking_vendors / thinking_vendor_rules_{cn,global}` 四模块(30 条规则覆盖 22 个预设、按 chat/responses 区分关闭形态);强制思考型号(GPT-6 Astra / GPT-6.1 Sol / GLM-5.3 / Kimi K3 / MiniMax M3.1-Flash)一个关闭参数都不发、设置页置灰并说明原因;前端协议矩阵 4 处修正(豆包/xAI 的 Anthropic 入口、Spark/MiniMax 域名)+ 预设 `config.ts` 同步,表尾补思考开关说明。
  - **首页与求职进度(第 12.1、12.2、13 条)**:首页任务卡 `.home-tasks-card` 标题留白与副标题下留白相等(不再贴顶);求职进度页顶部统计改为**指标带 + 一条 100% 堆叠构成条**(五段按「进行中总数」`flex:{count} 0 0` 切分、图例最大余数法百分比合计 100、0 段不画、图例可点筛选),记录列表**去卡片化**(容器 + 分隔线行、`.is-active` 只留一条竖条、状态改圆点、元信息压一行、每 10 行细分隔),`TrackerPage.tsx` 467 → 377 行。
  - **采集筛选(第 15 条)**:投递台「自动采集」筛选项按站点真实能力区分——多选 `experience/degree/industry/scale/payType/partTime/stage`、单选 `jobType/salary`;兼职(`jobType=1903`)时「薪资待遇」整组消失、换结算方式 + 兼职时间(反向不成立);`GROUP_SPECS` 新增 `multi` / `visible_when` 由后端随选项下发,前端不写死;切换求职类型时前后端都清残留值。
  - **求职统计(第 17 条)**:时间线空态改「还没有状态变化记录,改一次状态就会开始累积」(**不伪造事件**);补「进行中不含 Offer」说明;区间选择器归位到趋势图卡头;新增渠道转化率(招聘平台 7 / 内推 2 / 官网 1 / 猎头 1 / 其他 1)、投递→当前阶段耗时中位数、同公司多投与止损清单、简历版本效果(带样本量/小样本徽标、不做归因)、周投递目标(`PUT /api/analytics/weekly-goal`)、生成成本(只报 token 与耗时,不报金额);停滞口径改为「最后一次状态变化(事件 > status_date > updated_at)」,`/api/stats` 与首页提示同批对齐。
  - **桌面版(第 18、19 条)**:窗口 × 改为「最小化到托盘 / 退出应用」二选一,可勾「记住我的选择」(存 `userData/close-behavior.json`,删掉恢复询问;记住退出有二次确认;托盘提供「关闭窗口时询问」);桌面版隐藏页内「退出」(浏览器发行版保留);新增**自绘 32px 标题栏** + 文件/编辑/视图/窗口/帮助/关于六菜单(无字母跳转;Alt→方向键→Enter 键盘可达;菜单/下拉全部 `no-drag`;Win11 贴靠与双击最大化;深浅色覆盖层按钮颜色跟随;净增 0px)。
  - **事实台账(第 20 条)**:`claim_record` 新增 `entry_ids`(JSON,存 24 位 `entry_id`)与 `entry_ids_auto`(迁移 `0009`),一次性保守回填(按主体名去空白双向包含;技能/语言分区不参与;学历同校用专业名消歧);生成判据改为 `entry_ids ∩ 快照` 或 `sources[].location` 命中;表单新增「关联资料条目」多选、「证据来源」只做定位、列表对未关联条目标「不会进入生成」、对自动关联标「已自动关联,请核对」、页面读数拆「全量 M 条已确认 / 其中 K 条已关联资料」;提示词把「基线没提到的一律不得出现」限定到台账覆盖的条目并渲染覆盖清单,`blocked_wording` 独立于基线渲染;「从资料生成」支持从资料库选条目预填。真实数据集 **10/10 已确认全部回填成功**,页面口径 == 生成口径。
  - **外部简历(第 21 条)**:简历中心新增「外部简历」区块(只读快照),新表 `external_resume`(迁移 `0010`,引用 `user_file` + 提取文本 + 软删);上传 pdf/docx/txt/md 时本地提取文本(扫描件不识别视觉、返回 warning 并标注「未提取到文字」);提供原件预览、文本、条目化文本检查(占位符/量化/联系方式/长度/弱表述/有 JD 才出关键词覆盖,**无总分**)、软删进回收站可恢复(不删物理文件)、「用于面试」跳转并预填面试材料;与 `resume_record` 完全隔离,不进制作台/导出。备份表清单 76 → 77、回收站 23 → 24 类(含外部简历)。
  - **资料箱附件(第 23+24 条)**:附件支持图片 / PDF / Word(.docx) / txt / md / json / csv 上传并**保存原件**(`material.files` 增 `file_id`,引用 `user_file`),新增 `POST /api/materials/attachments`;附件行提供「预览 / 复制 / 另存为」(图片复制写真实剪贴板 `image/png`、PDF 复制文本、另存为下载原件);历史无原件附件只显示「复制」;扫描件 PDF 不识别只给 warning;「添加图片 / 添加文本文件」两个按钮合并为唯一「添加附件」(`Upload` 与 `FileDropZone` 共用同一 `accept` 常量),`.doc` 选文件即拒并提示另存为 `.docx`,多选时上限检查按串行队列累加;删除「PDF/Word 请把文字粘贴到正文,程序不保存原始文件」等已失效文案。

### Changed

- **第二轮复核与补完(2026-11-10)的口径与交互修正:** 资料箱附件上传的请求体上限**单独放宽到 12 MB**(新增配置 `MAX_ATTACHMENT_UPLOAD_MB`,默认 12;端点自身仍按 10 MB 校验单个文件)——此前的全局 8 MB 默认会让 8–10 MB 的 PDF/Word 在进入端点前就被中间件以「请求体过大,最大允许 8 MB」拦下,界面写着支持却没真正可用;只放宽 `POST /api/materials/attachments`,其余接口(含同前缀的 `/api/materials`)仍受全局上限。语义检索的命名配置卡不再「点整卡打开编辑弹窗」(卡根去掉 `role="button"` / `tabIndex`),键盘等价入口是「⋮ → 编辑」,鼠标仍可整卡点击。求职助手的两处能力描述纠正为事实:渠道字段存在(`channel_stats` 可答各渠道投递/面试/Offer 数与转化率,与「内推」模块是两套口径、不可相加)、进度记录可显式关联简历(`resume_effects` 带样本量),不再回答「本库没有渠道字段」「关联入口还没有」。README 功能表补「外部简历」「附件多格式」两行(中英各 82 项)并登记进助手能力地图。
- **实施批次(2026-11-10)的行为变化(需要用户知道的口径变更):** 语义检索/语音转写/联网搜索三类设置由「每个数据集一份」改为**全局一份**(切数据集不再变化;旧数据集配置迁移一次,回退旧版只能看到升级前配置);联网搜索「每次返回条数」上限 15 → 50、分类由多选改单选;求职进度「停滞」口径改为最后一次状态变化;简历生成同时运行默认 4 条、同岗位去重;采集筛选接口 `GET /api/collect/filters` 的分组新增 `multi` / `visible_when` 字段,`PUT /api/collect/config` 与 `POST /api/collect/filters/test` 的 `filters` 由 `Record<string, string>` 改为 `Record<string, string[]>`(旧单字符串仍可读、自动归一;求职类型/薪资待遇单选,其余多选,兼职联动换组);资料箱附件图片仍最多 4 张(文档 4 份,多格式共用上限);备份包新增 `external_resume` 表(表清单 77),回收站 24 类。

### Fixed

- **第二轮复核与补完(2026-11-10)修复的无障碍问题(都会让 `ui-quality` / `a11y` 冒烟在真实数据下变红):**首页提醒弹窗的链接色字面量 `#5b8ff9` 在白底只有 **3.11:1** → 改用 `--rf-info-strong`(7.26:1),弹窗另外三处硬编码色(标题图标、分隔线、hover)一并换成语义 token;岗位卡技能标签的 antd cyan `#08979c` 在 `#e6fffb` 底上只有 **3.39:1** → 复用 `--rf-info-strong`(6.94:1);求职进度构成条的 `aria-label` 含「条,占」导致可见文本不在可访问名内(WCAG 2.5.3)→ 改为可见文本充当名称、完整口语版放 `title`;语义检索命名配置卡的 `role="button"` 套着三个按钮(`nested-interactive`)→ 卡根降级为普通容器。
- **实施批次(2026-11-10)修复的问题(对应问题清单):** Responses 链路思考未真正关闭导致生成慢一倍(第 10 条);事实台账「已确认事实基线」在真实生成中恒为空(第 20 条);草稿预览超额页只有红框没有正文(第 7 条);搜索来源卡勾选后卡内大片留白(第 4 条);资料箱卡片同行高矮不齐(第 22 条,同行底边差 22.39px → 0);首页任务卡标题贴顶(第 12.1 条);面试设定右侧空列与「指定轮数」撑高整行(第 8 条);桌面版「页内退出 + × 直接退出」双入口冲突(第 18 条);事实台账关联判据与界面提示永远对不上(第 20 条);思考参数对不可关闭型号会发出去导致 400(第 11 条)。

### Security

- **备份密钥剥离补齐(2026-11-10):** 「不包含 API Key」导出此前仍会带出 `app_setting.asr_api_key` 与 `app_setting.search_deepseek_api_key`(非 Windows 平台为明文);现已加入收集与剥离名单,并补反证测试(导出包内搜不到明文、`include_api_keys=true` 时保留)。
- **求职 Agent 三步落地:跟进巡检 ·「我已提交」+ 投递交付单 · 求职偏好档(2026-11,迁移 `0006`–`0008`)。** 把「投出去之后」这一段接上,全部本地运行、不新增账号/云依赖,**不自动提交、不自动发消息、不读邮箱、不保存招聘网站凭据**。
  - **跟进巡检(期一)**:复用「任务卡」总开关与同一后台节拍,扫描「进行中且超过 7 天没有任何更新」的投递——>7 天且 ≤21 天生成**跟进清单卡**,>21 天生成**确认状态卡**(两桶互斥);每桶最多 5 家、按最久未更新优先,同一桶同一 ISO 周只生成一张卡(`agent_task` 新增 `source_ref` / `occurrence_key` + 唯一索引,旧提醒卡去重语义不变);「投出后 7 天」提醒 7 天内出过卡的让位,近期已决策(`contacted`/`replied`)与 `stopped`(永久)不再入选;文案只由确定性模板产出、不调模型,禁词 + 必备要素双向守卫。新增 `POST /api/followups/decisions`(整卡一事务、乱序可提交、幂等;**只记录决策,不改投递记录的状态与更新时间**)与新表 `application_follow_up`。
  - **「我已提交」+ 交付单(期一)**:`POST /api/apply/records/{id}/submitted` 把成功的投递记录补成一条「已投递」进度(合并键去重、已到更后阶段不降级)、挂一条 +7 天的 `hr_reply` 提醒(同 track 待办提醒复用,幂等)、冻结投递快照(复用既有 `freeze_delivery` 与幂等键 `apply_task_item:{id}`),并维护一次性引导键;`apply_task_item` 新增 `receipt_acked_at`(迁移 `0007`)。`GET /api/apply/records/{id}/receipt` 把既有快照读成可验收单据:投递版本/目的地/导出产物/填充字段统计/事实台账疑点/待人工步骤/跟进计划,三态优先级 **卡在人手上 > 有疑点 > 可验收**;「投出后简历又改过」只展示、不参与三态;填充记录按 URL 精确匹配、匹配不上标「未知」而不是缺件。`POST .../receipt/ack` 只写验收时间(幂等、不碰漏斗状态)。
  - **求职偏好档(期二,迁移 `0008`)**:新表 `job_preference` 存 13 类闭集偏好(城市 / 行业与公司排除 / 薪资底线 / 工作方式 / 用工形式(正职 / 实习 / 外包 / 兼职 / 劳务派遣)/ 写作红线 / 可到岗时间 / 仅本次对话等),一行 = 一次确认的完整取值;版本状态 `state`(active/superseded/undone)与 `deleted_at`(只表示用户删除)分离,活动行唯一用条件索引 `(dataset_key, kind) WHERE state='active' AND deleted_at IS NULL`。写入必须经用户确认的提案(`create_job_preference` / `update_job_preference` 登记为永远走提案的写工具;绕过提案直写会被服务层拒绝);「以后都这样」落长期偏好,「只在这次对话」落 `session_note`。偏好块以「不是发生过的事实」的显式格式注入系统提示(24 条 / 单值 120 字符 / 列表 8 项 / 整块 900 字符,先省来源时间再按优先级裁尾;四档数据权限全注入),**永不进入简历生成、纸面渲染、网申取数与事实台账**(有隔离反证测试)。设置页新增偏好管理:编辑 / 撤销到上一版 / 删除进回收站 / 恢复;恢复撞同 kind 活动行返回 409 `trash_restore_conflict`。
  - **回收站 22 → 23 类**(新增「求职偏好」):`TRASH_SPECS` 仍为唯一注册表(拆到 `services/trash_registry.py` 以守住 500 行预算,`trash.py` 原样重导出全部符号);能力地图、README 与用户指南同步为 23 类。
  - **接口与迁移**:新增 `POST /api/followups/decisions`、`POST /api/apply/records/{id}/submitted`、`GET /api/apply/records/{id}/receipt`、`POST /api/apply/records/{id}/receipt/ack`、`GET/POST/PATCH/DELETE /api/job-preferences[/{pref_id}]`、`POST /api/job-preferences/{pref_id}/undo`、`POST /api/job-preferences/proposals/{id}/session`;迁移链保持单线 `0005 → 0006 → 0007 → 0008`,旧数据零破坏(`agent_task` 旧行 NULL 不受新唯一约束影响、`receipt_acked_at` 旧行 NULL = 未验收、`job_preference` 空表起步不回填)。

### Changed

- **可用性收口 8 项:列表行「···」、设置页三张 AI 卡对齐、面试轮数与人设互斥、更新公告渲染、清理文件定位(2026-11-09)。** 无数据库迁移、无新增依赖;旧配置与旧数据零破坏。
  - **列表行操作**:岗位广场与简历中心每行最右侧新增「···」,内容与该行右键菜单一致(不含已平铺的「详情/生成简历」「预览/导出」,也不含右键独有的「复制」子菜单)。**进入选择模式(勾选)后,三点与所有单行操作一并禁用**(含表格收藏星标、窄档卡片的预览、星标与标题),批量勾选框保持可用;窄档卡片补上「批量选择」入口(此前 ≤1092 宽度下进不了选择模式)。岗位广场操作列 170 → 190、`scroll.x` 1330 → 1350。
  - **设置页三张卡向「大模型 API 配置」看齐**(LLM 卡与其弹窗一行未改):卡头统一「标题 + 唯一主操作」、说明统一 `Alert info`、字段统一 `SettingsField`、状态统一 `Tag`(蓝=生效中 / 绿=已配置 / 灰=未配置)、测试结果统一 `Alert`。语义检索远程服务由下拉改为厂商卡片网格(6 家,选中态浅色底 + 主色边框,下方展开字段);语音转写新增 OpenAI / Groq / 硅基流动 / 阿里云百炼 / 自定义预设;联网搜索改为 4 张可多选来源卡 + 下方「通用参数」,**保留 400ms 自动保存与三态提示**;语义检索三处裸的红按钮(删除已下载文件 / 删除本地模型文件 / 清除已保存的密钥)收进「···」并沿用列表同款二次确认。
  - **语音转写新增调用协议**:`AsrSettings.protocol`(`openai` | `dashscope`,默认 `openai`),配置仍存 `AppSetting.asr_config` JSON,**无需迁移**;`dashscope` 走 `POST {base}/chat/completions` + `messages[0].content=[{type:"input_audio",...}]`(Base64 Data URI、`stream:false`、可选 `asr_options.language`),响应兼容字符串与分段数组,400 时提取上游 `error.message`。新增 `POST /api/settings/asr/models`(`GET {base}/models`,上限 100,失败返回 `[]`),前端「拉取模型列表」按 `whisper|transcribe|asr|audio|speech|sensevoice` 分组、默认只显示疑似转写模型。**旧配置无 `protocol` 时按 `openai` 处理,行为不变。**
  - **面试设定**:轮数改为「自动 / 指定轮数」两档(**默认自动**,此前默认固定 6 轮),指定时手输 3–12;面试官风格与自定义人设合并为同一分组并纵向排列,**填了人设即禁用风格**且 `extra` 明示原因(值不清空、后端 Literal 仍必填)。
  - **行为变化(进行中的会话也生效)**:后端提示词从「风格 + 人设叠加」改为**人设优先**——`persona` 非空时 `interviewer_style` 传入占位文案、`behavior_directives_for(..., None)` 跳过风格行为指令,收尾提示词同样不注入具体风格名。老会话下一轮即走新逻辑。
  - **更新公告渲染**:补齐 `#{1,4}` 标题、表格(首行进 `thead`、列数不齐补空)、`---/***/___` 分隔线、行内代码、`* ` 与 `1. ` 列表、`[文字](地址)` 链接(**只放行 http/https**,其余降级为纯文字);全程 React 文本节点,不引入 `dangerouslySetInnerHTML` 与 markdown 依赖。容器 `maxHeight` 200 → `min(40vh, 420px)`(1440×900 下实测 360px),保持内部滚动。后端公告截断由「硬切 4000 字符」改为**按行回退**(预算 3986,回退点不早于 60%,末尾追加「……(完整说明见发布页)」),不再把表格或句子从中间切断。
  - **清理旧文件可定位**:新增 `POST /api/stats/storage/cleanup-reveal`,请求体为清单里的**相对路径**(不下发绝对路径);拒绝绝对路径与 `..`、`resolve()` 后必须仍在数据目录内、首段白名单 `backups`/`auto-backups`/`logs`、文件不存在返回 404。前端文件行末尾加「打开文件夹」图标按钮(`preventDefault` + `stopPropagation`,点它不会切换勾选)。`reveal_path()` 抽到 `services/system_reveal.py`,与「我的文件」的行内定位共用一份实现(Windows `explorer /select,`、macOS `open -R`、Linux `xdg-open` 目录)。

- **设置页「各服务商协议支持」按 2026-11-09 官方文档复核并重排(热门优先)。** 修正 Qwen / StepFun 的 Responses 判定为部分支持(前者仅基础兼容、后者仅两款模型),xAI 的 Anthropic 判定改为部分支持(官方 OpenAPI 已定义 `/v1/messages`);Perplexity 的 Anthropic 改为部分支持(Orchestrator 私有预览,需申请)。Groq 预设模型改为 `openai/gpt-oss-120b`(`llama-3.3-70b-versatile` 已于 2026-08-16 对自助层关停),Perplexity 改为 `perplexity/sonar`。23 行按热门度重排,表尾仍标核对日期。

- **助手提示词预算守卫阈值显式调整(2026-11)**:`test_assistant_prompt_budget` 的系统提示固定成本上限 23,800 → 24,100。原因是工作树里**既有**(非本批)的助手装配 / 平台事实改动已把基线推到约 23,824(本批之前即已超 23,800 共 24 字符),本批偏好能力点名再 +174 字符(`test_assistant_prompt_surface` 要求每个写工具名出现在静态提示里);真正的成本闸门 `MAX_PROMPT_TOKENS_ESTIMATE = 60,000` 未动,测试注释记录了实测数字与"既有基线"归属。

- **新增 RAG 语义补召回(默认关闭;v2 检索链路 + v3 真实本地 / 远程 provider)。** 给简历生成链加一条**候选选择层的可选增强**,两个开关**默认都是 `false`**:`rag_retrieval_enabled`(向量补召回 + 加权融合)与 `rag_query_expansion_enabled`(Phase 0 查询扩展)。**都关闭时(默认)** `prepare_selection_boost()` 返回空作用域,生成链行为与改造前逐字节一致——RAG 只决定「哪些条目进入候选」,**不碰 prompt 字节**(候选的字段形状、键序与文本不变)。**不新增数据库表 / 迁移 / 数据目录**:向量索引是进程内 LRU 缓存(可重建派生数据,重启重算、不进备份,备份导入后没有索引也能正常生成)。
  - **链路**:冻结的 `ConsumedProfile` → 模型输入白名单投影出的 chunk(`canonical_item_hash` 桥接回下游候选字典,下游候选里已经没有 `entry_id`)→ 进程内向量索引 → 关键词分 + `10 × cosine` 加权融合(向量权值 10 < 技能命中权重 12,保留精确命中的硬优先级;融合分先 `round(4)` 再比较、平局回落原顺序)→ `rag_seam.selection_scope` 作用域 → `_select_entries()` 的 boost / `build_job_focus()` 的扩展词。boost 只对教育 / 工作 / 校园 / 项目四个内置分区与 `filter_by_job=True` 的自建板块改变顺序;`skills` 走独立硬优先级路径,`awards` 与其它分区不受影响。SSE 与后台任务两个生成入口都已接入。
  - **Phase 0 查询扩展**复用已配置的大模型与岗位已有的 `analysis_cache` 列做缓存(岗位内容一改指纹即失效,不需要第二张缓存表),扩展词**只进 `JobFocus.terms`、不进 `skills`**;未配置模型 / 超时 / 坏 JSON / 空结果一律静默回退。
  - **fake provider 守卫**:`FakeEmbeddingProvider` 是确定性 hash 向量、与语义无关;默认配置下即使打开 `RAG_RETRIEVAL_ENABLED` 也只回退(`fallback=fake_provider`),只有显式 `RAG_EMBEDDING_ALLOW_FAKE=true`(测试/链路对比)才参与排序。
  - **请求预算与后台预热**:单次请求最多花 `RAG_REQUEST_BUDGET_SECONDS`(默认 1.5s)算 boost;冷缓存超预算立即回退(`fallback=budget_exceeded`),缺失向量由 `ai_task` 的 `rag_index_warmup` kind 在后台补齐(复用现成执行器,不阻塞首字节)。作用域只覆盖**候选构造段**(以 `mark_selection_done()` 为界),不再覆盖整条流。
  - **真实 provider(v3)**:设置页「AI 模型 → 语义检索」二选一——**本地模型**(按需下载约 110–126MB(按平台 zip 传输体积:Windows 111MB / macOS 110MB / Linux 122–126MB;打包模型约 74.6MB + 运行时按平台约 35–51MB,解压后合计约 237–280MB;设置页显示本机实际值);按平台选资产、每个下载源各自 sha256 校验、目录带版本;数据不出本机)与**远程服务**(任意 OpenAI 兼容 `/embeddings`,0 下载;首次按 host 弹隐私确认、维度自动探测、按 `batch_size=25` 切片;内置百炼 / OpenAI / 智谱 / SiliconFlow / 本机 Ollama 预设与自定义端点模板)。**在设置页启用任一真实 provider 即等于启用语义检索**(无需改环境变量;`RAG_RETRIEVAL_ENABLED` 保留为开发 / 评测总闸)。配置持久化在 `global_settings.json` 的 `embedding` 段(不进备份),切换 provider / 模型 / 维度会清空索引并记 `provider_switched`;请求体只含 chunk 的白名单投影,姓名 / 电话 / 邮箱 / 私密链接不出现。
  - **回退语义**:未配置 provider / fake 未允许 / 超预算 / 超时 / 坏响应 / 空索引 / 本地下载或校验失败 / 运行时不可用 / 远程 401·429·维度不符 → 一律空作用域并回退关键词链;设置页如实显示失败原因,**不伪装成已启用**。日志只记 provider、host、候选数、命中来源、耗时、回退原因、哈希前缀 7 类元数据,**不含 JD / 简历正文、向量、密钥或完整 URL**。
  - **配置项**:v2 的开关与模型属性继续可用(`RAG_RETRIEVAL_ENABLED=false`、`RAG_QUERY_EXPANSION_ENABLED=false`、`RAG_EMBEDDING_PROVIDER=fake`、`RAG_EMBEDDING_*` 等,见 `backend/app/config.py`);v3 新增 `RAG_ASSET_BASE_URL`(默认 `https://github.com/magicapple123/ResumeForge/releases/download/rag-assets-v1`)、`RAG_ASSET_HF_BASE_URL`(HuggingFace 备用源)、`RAG_LOCAL_MODEL_DIRNAME=rag-model`、`RAG_LOCAL_RUNTIME_DIRNAME=rag-runtime`、`RAG_EMBEDDING_ALLOW_PRIVATE_ENDPOINT=false`(本机 Ollama / LM Studio 等回环 / 内网地址默认拒绝,需在「高级」显式允许)。
  - **平台矩阵与体积**:本地模型统一 onnxruntime **1.31.1**,支持 Windows x64 / macOS **14+** arm64 / Linux x86_64 / Linux aarch64;**Intel Mac 没有 onnxruntime macOS x86_64 wheel,本地模型不可用,只能用远程**(应用本身仍可运行,dmg 仍出 x64 / arm64)。权重与运行时按需下载,**不进安装包、发布压缩包与备份**(登记为 `RUNTIME_ONLY`);只用远程的用户 0 下载。因此备份包与安装包体积不变;`global_settings.json` 不在备份成员里,provider 配置与加密密钥不随备份走。
  - **未验证项(如实记录)**:真实远程端点**未做端到端验证**——本机没有 `EMBEDDING_API_KEY`,只有 mock transport 的协议证据,报告给出将来验证命令;**ARM 平台(macOS arm64 / Linux aarch64)的本地量化变体未真机验证**(若只能退到 fp32 必须回给用户决策);Qwen3-Embedding / harrier 等其它模型未下载未验证;`FakeEmbeddingProvider` 不得用于质量指标。
  - **已知边界**:接缝用请求内 ContextVar 作用域而不是给 `build_targeted_consumed_profile_context()` 加透传参数(两个候选接入文件已贴 500 行硬闸门且被 `monkeypatch` 钉死模块命名空间,详见交付报告);本机 / 内网地址需显式允许;不兼容厂商(DeepSeek / Groq / OpenRouter / Together / xAI 等)没有 `/embeddings`,界面给可读提示而不是裸 404(名单有时效性,不写成不可配置的白名单)。
  - **离线评测**:`scripts/rag_eval_dataset.py` 生成 60 组「同义改写型 JD × 种子条目」评测集;`scripts/rag_eval_run.py --mode keyword|expanded` 量 baseline / Phase 0,`scripts/rag_eval_embedding_spike.py` 用隔离 venv 的真实 ONNX 量 Phase 3(v2 实测 Recall@10 0.4056 → 0.6361);v3 接入产品路径后的本地 / 远程 60 组对照、平台矩阵与失败矩阵数据见交付报告。

- **新增 Linux 发行版(x86_64 / aarch64)。** 三种形态并存:`start.sh` / `stop.sh` / `update.sh`
  一键脚本(零依赖,首次启动自备便携运行时)、`ResumeForge-<版本>-linux.zip` 便携包、
  `ResumeForge-<版本>-linux-<arch>.AppImage` 单文件。验收基线 **glibc ≥ 2.31**
  (Ubuntu 22.04+ / Debian 12+ / Fedora 38+ 一类),更老的发行版与其它架构未经验证。
  - **便携 Python 换成 `install_only_stripped`**:默认的 `install_only` 包 106MB,精简包
    x86_64 34MB / aarch64 29MB,与 macOS 的 25MB 同一量级。摘要钉在仓库里,镜像只能加速。
    Linux 上运行期**不需要 Node**——发布包自带 `frontend/dist`,页面由后端同源提供。
  - **多一道 venv 预检**:Debian/Ubuntu 把 `venv` 与 `ensurepip` 拆进了单独的 `python3-venv`
    包,系统里有 `python3` 也照样建不出虚拟环境,而报错指向 `ensurepip` 完全不解释原因。
    启动器会实测这两者可用性,缺了先给 `sudo apt install python3-venv` 提示,再回落便携版。
  - **修复退出时在 Linux 上跑错停止脚本**:`_stop_command()` 此前把所有非 Windows 平台一律
    指向 `scripts/macos/stop.sh`,而那个脚本把状态目录写死成 `~/Library/Application Support`,
    于是 Linux 上「点退出」会去一个不存在的路径找记录、静默地什么都不停。现在按
    `sys.platform` 分流到 `scripts/linux/stop.sh`,并让「脚本存在性守卫」与「实际执行」
    从同一个函数取路径(此前两处各写一份 `if`,守卫看 A、执行 B)。
  - **修复 Electron 壳在 Linux 上调用 macOS 启动器**:`backend-manager.js` 的
    `launcherCommand()` 同样只判了 `win32`,其余平台一律走 `scripts/macos/start.sh`;
    现在按平台在 `macos` / `linux` 之间选择。
  - **PDF 中文字体补上兜底**:`_FONT_CANDIDATES` 里的 Linux 条目(Noto CJK / 文泉驿 /
    arphic)没有任何主流发行版默认安装,此前 Linux 上探测必然返回空、导出直接 409。
    现在系统字体缺失时回落到**已下载的内置思源黑体**(`resume_font_builtin` 按需下载的
    那两个字重);两边都没有时仍如实返回 409 并保留浏览器打印,界面给出下载入口。
    兜底**排在系统字体之后**,Windows/macOS 上「用系统字体」的语义不变。
  - **测试隔离补齐**:`conftest.py` 此前只把 `user_fonts.DATA_DIR` 指到测试目录,没有隔离
    `resume_font_builtin.DATA_DIR`。字体兜底上线后,开发机上真下载过 33MB 字体就会让
    「缺字体」类断言随机器状态漂移,所以同批隔离并纳入每个用例的清空夹具。
  - **`update.sh` 支持「下载新包 → 校验 `.sha256` → 只覆盖程序文件」**:`backend/data/`、
    `backend/.env`、`runtime/`、`desktop/` 一律不动;覆盖前把将被替换的内容复制到
    `runtime/backup-<时间戳>/`(本机没有回收站,禁止硬删),失败可回滚;`--check` 只检查。
  - **新增 CI 三道 Linux 把关**:`linux-launcher-guards`(跑守卫测试,并断言输出里确有
    `ok` 行,防止脚本空转而"绿得没有内容")、`linux-runtimes`(真下载两架构运行时、验
    sha256 与字节数,并实际验证 `venv` + `ensurepip` 可用)、`linux-end-to-end`(干净检出
    走完整启动链、浏览器冒烟、只读安装根状态外落、停止脚本);`release-assets` 新增
    `linux-appimage` 作业产出双架构 AppImage 并附 `.sha256`。
  - **已知边界**:`scripts/tests/Test-Build-Release.ps1` 的平台清单仍是 `windows|macos`,
    `-Platform linux` 要等根目录启动脚本进 git(`git archive` 只打包已跟踪文件)之后才能加上;
    `desktop/main.js` 与 `desktop/backend-manager.js` 现为**恰好 500 行**,再到这两个文件里
    加代码必须先外移一段。

- **求职助手输入区重做:四档数据权限 + 只读模型 chip(T11)。** 输入框旁新增四档权限——
  **仅查看 / 放宽读取 / 可修改 / 完全权限**,一档同时决定「读」(是否看姓名、电话等真实资料)与「写」
  (是否直接改数据)。设置页「求职助手 · 数据权限」存全局默认;输入框下拉只覆盖**当前对话**
  (本机 localStorage、按会话保存,刷新/切页保留、**换设备不同步**);选放宽读取 / 完全权限时输入框上方
  出现可关闭的琥珀色提示,**首次**切到写档位有一次确认。请求体发送解算后的两轴值
  (新可选字段 `relaxed_mode`,缺省沿用全局,旧客户端零改动)。输入框发送键左侧新增**只读模型 chip**
  (显示模型名,悬停「记录名 · 模型名 / 在顶部切换」,无记录不显示、<480px 隐藏;顶栏切模型后跟随更新)。
  **安全边界**:完全权限下写操作不再逐条出提案、直接执行;**技能的新建与修改仍永远走确认**。
- **接口:`GET/PUT /api/assistant/permission`。** 读写全局默认档位;写入时同步写旧键
  `assistant_relaxed_mode`(兼容其它读取点),旧键不删。
- **设置页新增第三种接口协议 OpenAI Responses(实验性,T09/T09.1)。** 默认仍是 Chat Completions,
  **已保存配置零影响**——只有用户主动切到 Responses 才走新链路;Claude / Gemini / 硅基流动 不支持该协议
  (MiniMax 自 2026-11 起已支持,见下方 Fixed)。切换协议时只有声明支持 Responses 的预设才改 `Base URL`
  (智谱→`https://open.bigmodel.cn/api/v1`,其余在预设地址后追加 `/responses`;DeepSeek 实测可用)。
  设置页「模型配置」区新增可展开的**「各服务商协议支持矩阵」**(23 行,含核对日期列;表尾
  「核对日期 2026-11-08」,2026-11-08 逐家复核后重写;2026-11-09 再次复核,见本段 Changed 顶部),并新增 6 家预设(腾讯混元 / 百度千帆 /
  讯飞星火 / 阶跃星辰 / Together AI /
  Perplexity),**预设总数 16 → 22**。同时修正 MiniMax 域名(`api.minimaxi.com/v1` + MiniMax-M3.1-Flash)、
  Gemini(`gemini-3.8-flash`)、xAI(`grok-4.7`)等预设,标签缩短为「Claude(Anthropic 原生)」
  「Google Gemini」。**没有各厂商的真实 Key,新协议与预设按接口文档实现,未逐家真机验证。**
- **助手输入框、模拟面试答题框与面试复盘「追问」支持语音听写(T12/T13)。** 说话→文字先填进输入框,
  由用户确认后提交,**不自动发送**;面试追问同样只填入、不触发发送。设置页新增「语音转写」卡
  (OpenAI 兼容 `/audio/transcriptions`:base_url / model / Key,Key 掩码回读,可测试连接)。
  单段上限:助手与答题框 **3 分钟**、复盘追问 **1 分钟**(音频超过 10 MB 也会被拒);音频会发送到
  用户配置的语音服务商,本应用不保存音频。**真实识别质量未验证(无 ASR 服务商 Key,本地假 ASR
  服务器只验证了管道与交互)。**

- **首页新增「任务」卡,任务卡从助手页搬来(T02/T03)。** 待确认在前、进行中在后,两者合计最多 3 条
  (超出收成「还有 N 条」,点一下就地展开);「已结束(N)」默认收起,展开先看最近 5 条 + 「查看全部」;
  折叠状态不记忆(D4)。不按会话过滤(提醒到期生成的卡没有会话归属);没有可显示任务时整卡不渲染,
  空壳任务(无步骤的草稿 / 已取消)不显示也不计入任何计数;轮询仍为 5s,页面隐藏时暂停。查看步骤改成
  手风琴(同一时间只展开一条),展开后第一个未完成的步骤加粗并标「当前」。
- **提醒列表新增逾期红标与「时间范围」筛选(T06)。** 列表接口在原有字段外补 `urgency` / `days_until`
  (复用 `reminder_service` 的唯一实现,前端不按小时重算);pending 且 `days_until < 0` 的行显示红色
  「已逾期 N 天」标签。新增「全部 / 已逾期 / 今天 / 以后」筛选,筛选后回到第 1 页并重算总数。
- **提醒新建弹层 5 处改进(T05)。** 默认时间改为「1 小时后」;新建时校验「提醒时间需要晚于当前时间」
  (编辑时放行,只改备注不被旧时间卡住);时间只显示到分钟;时间字段下方按「任务卡」开关状态提示到期
  会不会生成卡片;进度 / 岗位 / 简历三个关联字段收进「关联(选填)」折叠区,默认收起并显示「已关联 N 项」。
- **求职助手写操作状态可跨轮、跨重启如实回答(P0)。** 写入提案从进程内存改为数据库单一事实源
  (新表 `assistant_write_proposal`,迁移 `0002`):新增 `pending/approved/executed/failed/unknown/rejected/expired`
  状态机(TTL 30 分钟)、创建时记录目标版本指纹并在批准前重查(目标被改过返回 409,不用旧快照写库)、
  终态写一条 `kind=proposal_result` 的派生消息(幂等、写失败不影响状态)、下一轮系统提示注入本会话最近 10 条
  写操作状态(以此为准)。启动时把上一次进程遗留的 pending/approved 置为失效并清理 24 小时前过期提案的参数与预览。
- **助手页新增「任务」区块与任务层(`agent_task` / `agent_task_step`)。** 任务支持
  `draft/running/paused/waiting_approval/done/failed/cancelled` 跨轮推进、等待人工确认不自动放行、
  重启后变「已暂停」可继续、同一步幂等重试只产生一次副作用;新增 `/api/agent-tasks` REST 与助手页任务区。
- **设置 →「助手与 AI 行为」新增「提醒到期生成任务卡」开关(默认关闭)。** 打开后到期的日历提醒自动生成
  「准备:<提醒标题>」任务卡;去重键为 `(reminder_id, occurrence_due_at)`,只补最近 24 小时,关闭后不再生成、
  已生成的卡保留;不自动执行任何写操作、不发消息、不弹窗。
- **助手可感知当前界面上下文(shared application state)。** 聊天请求新增可选 `client_context`
  (route/view + 选中对象 type/id);后端按当前数据集重查对象并取服务端标题(截断 40 字),查不到时如实
  注入「选中项已失效」,前端输入一律视为不可信线索。
- **助手新增 4 个只读工具 + 1 个错误诊断工具。** 简历质量检测(只跑本地规则,不触发远端增强)、
  岗位 ATS 匹配估计(强制免责文案)、导出预检、简历版本对比,以及 `diagnose_recent_errors`
  (只回白名单结构化字段:时间/级别/logger/异常类/HTTP 状态/路由模板/request_id,日志原文不出进程)。
- **文档检索质量重做。** 切块从 `##` 降到 `###`(保留父级面包屑),打分改为「标题命中 > 正文密度 > 次数」,
  步骤类片段上限 1500 字,返回结构新增 `source`(相对路径 + 标题路径);20 问标注集 top1 命中率 1/20 → 20/20。
- **action 契约层内核与 3 个试点。** 新增 `services/actions/`(ActionSpec/Registry/tool_adapter/route_adapter):
  参数 schema、权限级别、确认预览与实现收成一份定义;路由注册顺序仍由 `application_routers.ROUTERS` 显式决定。
- **action 契约层按域迁移(T28)完成:64/79 个助手工具收成一份 ActionSpec。** 覆盖 jobs / materials /
  claims / profile / tracker / referrals / reminders / knowledge / interview-experiences / candidate jobs /
  skills / format templates / resumes(列表与版本/版式)/ interview / drill / apply / analytics / share
  packages / chat / webform / match analyses / ATS / 版本对比等域的读写动作(新增
  services/actions/domains/ 与自动聚合的 actions/catalog.py;路由 path/method/响应模型/状态码与工具
  name/description/permission/preview 保持不变)。新增五个兼容机制:tool_defaults(仅工具侧默认值)、
  ignore_unknown_arguments(旧路由忽略未知字段)、run_tool(路由与工具既有写语义不同时让工具侧复用旧
  read-modify-write 实现)、tool_fields(工具侧字段投影,路由专属字段不暴露给模型)、
  tool_argument_aliases(旧工具参数名与路由路径参数名不同时对齐)。路由与工具统一由「旧实现委托」kit
  (actions/legacy.py)生成,零业务逻辑复制;工具 schema 增加 compact 模式,去掉
  default/maxLength/additionalProperties 等模型侧装饰,prompt 固定成本从 46k 降到约 41k 字符。
  剩余 15 个静态声明逐个登记在 backend/tests/fixtures/actions_legacy_allowlist.json(含 reason 与
  review_after;删除/新增都会让守卫变红),新增工具一律必须走 define_action + as_tool。
- **工具权限四级化。** `Tool.permission` 取代单一 `writes` 布尔:read_only / reversible_write / irreversible_write /
  external_side_effect(当前无 external_side_effect 工具),守卫要求每个工具显式声明。
- **安全验收清单。** 新增 `docs/security-agent-checklist.md`(OWASP Agentic / NIST AI RMF / OTel GenAI 落点)。
- **开发者文档新增四篇方法论,并与「提交前检查」串起来。** [验证手册](docs/verification-playbook.md) 给出证据六级阶梯、反证三件套与四类假绿判据;[协作规程](docs/ai-collaboration-sop.md) 固化任务书九节、按文件所有权的并行边界、脏工作区纪律与交付报告结构;[缺陷模式](docs/defect-patterns.md) 归纳 31 类高频缺陷的判据与守卫方式;[环境排查](docs/troubleshooting-stale-build.md) 给出「改了没用」的固定判定链。另附[任务书与交付报告模板](docs/templates/)。README 文档表与 CONTRIBUTING 已同步索引。
- **文档 PII 巡检进 CI(`docs-guards` 作业)。** 新增 `scripts/check_docs_pii.py`,扫描 `docs/`、README、CONTRIBUTING 与 `.github/`,拦下密钥、私钥块、平台令牌、手机号、邮箱与带用户名的家目录路径(含中文用户名);占位符与带 `pii-ok` 注释的行自动放过。配套 `backend/tests/test_docs_pii_guard.py` 用正例/反例/空目录/打码四组用例做反证,避免"正则失效→恒绿"。
- **Bug 反馈模板升级为七字段。** `.github/ISSUE_TEMPLATE/bug_report.yml` 增加版本号、期望行为、证据与「怎样算修好」,并在提交前自查里要求先升级到最新版本、先搜重复 issue。
- **Windows 安装包接入 SignPath Foundation 的免费代码签名计划(test-signing 已跑通,生产证书待签发)。** `release-assets.yml` 的 `windows-installer` 作业在打包后把安装包作为本次 run 的 artifact 上传,经 SignPath 校验来源(origin verification)后签名并取回,再用**签名后**的文件写 `.sha256`——先算哈希会与发布文件对不上,所以写哈希的步骤整体后移。`SIGNPATH_ORGANIZATION_ID` 未配置时整段跳过,产物、哈希与 Release 附件与接入前完全一致;配了它但其余必需项(Variable `SIGNPATH_PROJECT_SLUG` / `SIGNPATH_ARTIFACT_CONFIGURATION_SLUG`、Secret `SIGNPATH_API_TOKEN`)不全则直接报错终止,不做静默降级。官网新增中英《代码签名策略》页(签名覆盖范围、签名请求来源、Authors / Reviewers / Approvers 角色与隐私说明)。2026-11-06 的演练(run 42077336929)走通全链路,签名请求 `ec1837db-…` 状态 Completed,实测附带 DigiCert 的 RFC 3161 时间戳。

### Changed

- **macOS 兼容性地板从 13 抬到 14(行为变更)。** 官方支持改为 **macOS 14+**,**macOS 13 及更早不再获得官方支持**(应用可能仍能启动,但不再测试、也不提供支持)。原因:本地语义检索的隔离运行时 onnxruntime 1.31.1 只发布 `macosx_14_0_arm64` 等 wheel,**没有 macOS x86_64 wheel**。README(中英)、`docs/upgrading.md`、`docs/user-guide.md`、`docs/mac-build-handoff.md` 同批更新;**启动链没有版本闸门、脚本不改**。Intel Mac 可运行应用并使用远程语义检索,但不能用本地模型。
- **README 删除过期说法「Apple Silicon via Rosetta 2」。** 启动链按 `uname -m` 下载原生 `aarch64-apple-darwin` 的 python-build-standalone,Apple Silicon 一直是原生 arm64;dmg 也提供 x64 / arm64 两份(见 README 中英与使用指南)。

- **打开联网搜索不再先自动搜一次(R08,行为变更)。** 打开输入框下方的「联网搜索」现在只等于
  **允许联网**:`web_search` 工具随之下发,搜不搜、搜什么、换几次关键词都由模型自己判断;**开箱不再有
  「开场资料」**——此前每条消息都会先用用户原话自动搜一遍,模型还没看到问题、网络请求就已经发出去了,
  看到"帮我把某项目整理成条目"这类问题时还会被带偏成查某个字的字典解释。搜索次数上限仍是 **3 次**,现在
  这 3 次**全部由工具发起**(此前那次自动预搜也占一次额度,所以模型自己实际只剩 2 次)。系统提示里随附的
  「这次回答已经自动搜过一次……你自己最多还能搜 2 次」整段删除:联网工具的完整度口径(只看摘要 / 会抓正文)
  本来就写在工具描述里,跟设置里的「抓取正文条数」走,不需要在系统提示里重复一份会漂移的副本。
  「简历通自身的使用问题不搜公开网页」这条保留,拦截改由工具完成(工具直接回本地指引、不打网络)。
  使用指南与助手页内嵌引导的联网搜索一节同步改写(含英文文案)。

- **技能由「全局启用」改为「按对话启用」(T14,行为变更)。** 新增表 `assistant_conversation_skill`
  (迁移 `0005`,含完整 downgrade;备份表快照 73→74):一段对话启用哪些技能成为唯一真源。
  **新对话默认不启用任何技能**,在助手输入区用 chip /「+ 技能」菜单勾选,**只对当前对话生效、换对话各有一套**;
  空态可以先勾,发第一条消息时随创建会话一次写入;fork 对话时技能一并复制。单个对话**最多 8 个技能**,
  注入预算仍是 12,000 字,靠后技能超预算会被截断并在菜单 / chip 上提示(不再悄悄少加载)。
  **技能库(工作台 → 技能)里的「启用」列与开关已移除**,技能只在对话里启用;旧 `AssistantSkill.enabled`
  列保留但不再参与注入(数据不迁移、不回填)。新增对话技能接口
  `GET/PUT /api/assistant/conversations/{id}/skills`(整体替换,最多 8 个)与创建会话时的可选 `skill_ids`。
- **技能上限统一并收紧(T14)。** 提示词 4,000 字 / 最多 10 个知识文件 / 单文件 20,000 字 / 合计 60,000 字
  (此前后端校验口径是 200,000 字、50 个文件,与工具描述、注入预算三处打架);新建/编辑弹层去掉「启用」开关,
  提示词输入框带 4,000 字上限与字数反馈,知识文件区显示个数与合计字数。
- **技能的新建与修改永远走确认(T14)。** 即使把数据权限切到「完全权限」,`create_skill` / `update_skill`
  仍逐次出提案(提示词下一轮就进系统提示,不能让模型自己放开);助手消息 context 记录本轮用到的技能名
  (只记名字、不记正文),运行轨迹显示「本轮技能」。
- **助手的数据权限从「放宽模式」改为四档控件(T11,行为变更)。** 设置页「助手放宽模式」卡片升级为
  「求职助手 · 数据权限」四档(仅查看 / 放宽读取 / 可修改 / 完全权限),**「放宽模式」在助手侧成为历史名**;
  网申填表页的「放宽模式」是另一个开关,仍按原样工作,两者不再混用。旧键 `assistant_relaxed_mode` 保留且
  与新键同步写,不做迁移。
- **Anthropic 协议升级到 2026-10-21(T10)。** 官方 Claude 4.7 及以后改用自适应思考
  (`thinking: adaptive` + `output_config.effort`),不再发送 `temperature` / `top_p` / `top_k`;
  思考强度 minimal→low、high 档 `max_tokens` 至少 16k。**更早的 Claude 模型与第三方 Anthropic 兼容
  网关保持 2023-06-01 行为不变**。没有官方 Anthropic Key,本轮以假服务器验证请求体与分支,**未真机验证**
  上游真实响应。
- **任务卡位置与开关语义变化(行为变更,T01/T04)。** 助手页的任务区整块移除——它原本是
  `.assistant-workspace` 的第 4 个直接子元素,会把消息区那一行顶替成 320px 高,表现为标题下方一大片
  空白、消息区失去滚动;工作区同时改成 flex 布局加固,以后再加直接子元素不会再错位。任务统一在首页
  「任务」卡查看。设置里「提醒到期 · 生成任务卡」更名为「任务卡」并升级为**总开关**:**关闭 = 不再生成 +
  首页不再显示**(旧版关闭只停止生成,已生成的卡仍显示),数据保留、重新打开即恢复;关闭前若还有未完成
  任务(未开始 / 进行中 / 已暂停 / 待确认)会先弹确认。配置键 `agent_task_events` 不变、不做迁移;
  后端列表接口不按开关返回空数组(显示层由前端 gate)。
- **术语「绑定漏斗」改为「关联求职进度」。** 提醒表单与详情抽屉统一为「关联求职进度 / 关联岗位 /
  关联简历」,动作统一叫「关联」;`docs/ui-copy-guide.md` 新增术语 #66,并停用「绑定漏斗」。
- **能力地图(助手功能目录)纠偏 8 处。** 简历照片上限改为 5MB;回收站类别改为 22 类并注明「求职进度」与
  「投递记录」在回收站里同名;全局搜索第 8 域改为「我的资料」(技能域已下线);面试深挖补第 5 档「未覆盖」;
  日历提醒补第 4 档「稍后(later)」;快捷入口改为「默认 7 个,可自定义」;知识库入口改「新建知识」;
  「导入参考模板」补「当前界面不可达(已下线)」前缀。新增内容护栏(文案里的 N 类/N 套/N 个/N 档必须能从
  同句清单数出来)。
- **写操作提案有效期从 5 分钟改为 30 分钟**;token 仍只在服务端内存流转、同一请求内核销。
- **助手运行轨迹进入消息 context。** run id/step/tool/起止时间/状态/模型与用量/授权决策/脱敏摘要随助手消息
  `context.run_trace` 落库,不含简历正文、附件、Key、思维链与提示词原文。
- **新增「路由+工具」对必须走 define_action**,存量在 allowlist 登记、只减不增(T28 已迁移 64/79,
  静态声明剩 15 个、逐名登记 reason 与 review_after);路由注册顺序仍由 application_routers.ROUTERS
  显式决定。
- **多代理/多会话协作纪律。** AGENTS.md 补充文件所有权、禁止回滚命令、改前快照与反证要求等规则。
- **运营手册补充「凭据泄露的处置顺序」。** 明确"先吊销再清理"、历史与产物一起查、替换为占位符而不是部分打码,并指向新增的扫描守卫。
- **`AGENTS.md` 校正 2.0 后的过期口径,并补四条规则。** 迁移章节从"grep 旧 head revision"改为"单基线 `0001_baseline_v2` + `app_schema_epoch` 闸门 + 测试统一从 `migration_refs` 取常量";用例规模与耗时改为不写精确数字;`-n auto` 补"内存受限用 `-n 4`"。新增「多会话/多代理并行」(文件所有权、禁 `checkout`/`stash`/`restore`、改前快照、基线三件套、回报格式)、「规模约束(500/800 行预算与豁免规则)」、「文档与个人信息」(PII 巡检与事故处置)、以及"新增守卫必须反证"一条;并把"并行/覆盖率/构建"的历史叙事下沉到 `docs/maintenance.md` §5.1,同步写清两边的权威分工(**红线看 AGENTS.md,执行看 maintenance.md**)。

### Removed

- **清理 20 个零引用源码文件(1,945 行),全部为重构或功能下线后的残留。** 判定方式不是"看着像没用",而是建立导入图逐条验证:前端 18 个 TS/TSX + 1 个 CSS 在整仓(含测试)0 处真实 import,后端 1 个 API 模块在自身文件头就写着"已下线、不再注册路由"。具体为:`StyleTemplateEditorModal.tsx`、`ReferenceFileField.tsx`、`useWorkbenchInlineMarks.ts`、`entryReorder.ts`、`RiskScanWatcher.tsx`、`SuggestionScanWatcher.tsx`、`UpdateCheckButton.tsx`、`WebFormTargetFloatingBalls.tsx`、`CurrentGroupSummary.tsx`、`paginationDomain.ts`(re-export 兼容层,实现在 `features/resume-pagination/`)、单数版 `utils/backgroundTask.ts`(现役是 `backgroundTasks.ts`)、`api/resumeRisk.ts`、`styles/resume-structure-example.css`,以及被通用板块渲染器取代的 7 个资料页组件(`AwardSection` / `SkillSection` / `ProjectSection` / `ExperienceSection` / `CampusExperienceSection` / `SectionVisibilitySwitch` / 见上 `CurrentGroupSummary`);后端为 `app/api/resume_template_import.py`。**这与 1.0 时"文件保留、只在文件头标注已下线"的口径相反**——2.0 换了全新基线,回滚价值已由 git 历史承担,留着只会让导入图和代码搜索持续失真。同时删掉 `AppShell.test.tsx` 与 `ResumeRiskPanel.test.tsx` 中三行指向这些模块的 `vi.mock`(模块不存在时它们会直接报解析失败)。
- **`docs/ui-copy-guide.md` 标记对应条目的定位失效。** 映射表第 7 / 8 / 23 条以 `StyleTemplateEditorModal.tsx` 的行号定位,组件删除后不再可查,就地加注说明作废,保留编号以免破坏历史索引。

### Fixed

- **本地语义检索资产下载的失败原因被混为一谈:`rag-assets-v1` 还没发布时,404 被报成「网络不可用」。**
  两个整包 URL 都返回 HTTP 404,设置页却提示检查网络,用户只能一直等一个不会出现的包。现在
  4xx/5xx 与连接失败分开归类(可判定关键词「下载源不可用」/「网络不可用」)。同时修正
  HuggingFace 备用源的语义:它是**一组必须全部到位的文件**,不是另一个整包——任一文件失败即
  整体判失败并清掉 `*.tmp*`,不再把「第一个文件下载成功」当成整个资产就绪(否则磁盘上留下
  一份 88% 的残缺目录,而 `assets_present()` 会把它认作可用);逐文件下载的进度分母随文件数
  抬高,不倒退、不中途假到 100%。
- **切到 Responses / Anthropic 协议后被判「Base URL 与配置记录不一致」,且 Anthropic 入口此前根本走不通(用户反馈)。**
  两处根因:① 密钥引用与 Base URL 的绑定用的是**完整地址(含 path)**——切协议时前端把
  `https://api.deepseek.com` 改写成 `https://api.deepseek.com/responses`,path 变了就判「不一致」,
  用户手里没有第二个 Key,只能卡死。现在按 **origin(scheme + host + port,默认端口归一)** 绑定:
  同一主机上换 path(`/responses`、`/anthropic/v1`、`/v1`)照常解析已保存的密钥;**换主机仍旧拒绝**
  (含 `api.deepseek.com.evil.example.com` 这类子域伪装),安全底线不变。② 「获取可用模型」只剥离
  `/chat/completions`,切到协议专用地址后请求打到 `.../responses/models`、`.../messages/models`
  返回 404;现已一并剥离 `/responses` 与 `/messages`。
- **「各服务商协议支持」矩阵与预设逐家重核(首次核对 2026-11-08;2026-11-09 复核见本段 Changed 顶部),并让协议切换真正落到可用地址。**
  原表把 DeepSeek、豆包、通义千问、百度千帆、讯飞星火、阶跃星辰、OpenRouter、Together、Ollama
  (≥ 0.14.0)、LM Studio(≥ 0.4.1)的 Anthropic Messages 入口全标成「不支持」,MiniMax 的
  Responses 也标成不支持——后者还被 `responses_unsupported_hint` **硬拦截**(连接测试直接返回失败、
  一个请求都不发),用户永远测不通。现在:矩阵按官方文档重写 23 行;预设新增
  `supports_anthropic` / `anthropic_base_url`,切协议时按预设自动改写为该服务商的专用地址
  (DeepSeek → `.../anthropic/v1`、豆包 → `.../api/compatible/v1`、Kimi → `.../anthropic/v1`、
  通义千问 → `.../apps/anthropic/v1`、智谱 → `.../api/anthropic/v1`、MiniMax → `.../anthropic/v1`、
  千帆 → `.../anthropic/v1`、星火 → `.../anthropic/v1`;硅基流动 / OpenRouter / 混元 / 阶跃 /
  Together / Ollama / LM Studio 与 chat 同址),切回 Chat Completions 自动还原;`matchingPreset`
  也认这三条地址,编辑已切过协议的记录不会掉成「自定义」。预设同步更新:豆包
  `doubao-seed-2-1-pro-260915`、混元 `hy3`、星火 `spark-x2.6`(换到新 MaaS 域名
  `maas-api.cn-huabei-1.xf-yun.com`)、阶跃 `step-5-preview`、Together `zai-org/GLM-5.3`、
  Mistral `mistral-large-4`、硅基流动 `deepseek-ai/DeepSeek-V4-Pro-0813`、OpenAI `gpt-6-astra`
  (模型名一以各家控制台为准)。**DeepSeek Responses(`POST /responses`)与 Anthropic
  (`POST /anthropic/v1/messages`)已用真实 Key 各发一次最小请求,均返回 200;其余厂商仍按官方文档
  实现,未逐家真机验证。**
- **联网搜索失败时工具会把异常原文回给模型(R08 顺带修复)。** `assistant_tools/search_tools.py` 的失败

  分支直接拼了 `{exc}`,而 `AssistantSearchError` 的 `str()` 就是构造时传进来的原文——只有白名单里的
  公开文案才允许进 SSE、模型上下文与落库历史(`search_errors.py` 的既有约定)。现在改用
  `exc.public_message`,并补上"请勿声称已获得联网资料"的提示;回归用例见
  `test_codeql_error_privacy.py::test_search_error_does_not_enter_sse_history_or_model_context`。
- **确认执行写操作后,助手仍说"等待确认"(P0 + 任务/界面上下文/诊断等本轮修复;逐条证据见交付报告)。**
  P0 根因是提案状态只存进程内存、执行成功后不回写会话、下一轮上下文也不读提案表;现在状态以数据库为准并在
  下一轮注入权威状态块——已执行仍回答已执行,重启后未确认的显示已失效。
- **助手工具调用轨迹与对话内容错位**:轨迹按消息 context 持久化,历史回看能看到每一步的工具与授权决策。
- **清理过期 xfail、备份表覆盖守卫同源恒等、文档耗时数字失真**  (详见 T12/T15 证据)。
- **官网与 README 的演示数据脚本在 2.0 上重新可用。** `scripts/demo_data/base.py::init_db()` 原来用 `Base.metadata.create_all` 建库,而 2.0 的 `app_schema_epoch` 那一行由基线迁移播种——造出来的库既没有 epoch 行也没有 `alembic_version`,启动闸门判成 `REFUSE_UNKNOWN`,用 `uvicorn` 起服务直接拒绝启动;原有测试因为 `TestClient` 不触发 lifespan 而一直是绿的。现在改为跑真实 `alembic upgrade head`,并在脚本内硬断言「恰好一行 epoch=2 且 revision 非空」。同时把演示岗位状态从旧值 `active` 改成规范值 `开放中`(首页「开放中岗位」不再恒为 0),时间锚点改为「运行日的昨天 08:30」(需要字节级复现时用 `RF_DEMO_NOW` 钉住),首页「近 7 天生成」与提醒分色不再依赖某个固定月份。
- **官网在线体验站的 ATS 检测不再被当成「写操作」拒掉。** `frontend/src/demo/demoFetch.ts` 的只读豁免清单里还留着早已不存在的旧路径 `/api/ats/check`,而真实接口是 `POST /api/resumes/{id}/ats-check`——演示站里点「ATS 检测」拿到的是 403「这是在线体验版,无法真正保存改动」,看起来像功能坏了。现在豁免按路径形状匹配 `/api/resumes/{数字}/ats-check`,该形状走快照回放;其它写操作照旧 403。

## 2.0.0 - 2026-11-05

### Changed

- **2.0 以全新库为起点:迁移链只保留一条基线迁移。** 原 74 条历史迁移(v1.0.2 已发行的 46 条 + 2.0 开发期新增的 28 条)全部下线,空库执行一次 `upgrade head` 即建全 2.0 的表 / 列 / 索引;数据库只由这条基线代表当前结构,不再有"逐版本演进"的兼容分支。
- **库版本闸门按 epoch 判定,不再按 revision 判定。** 新增单行表 `app_schema_epoch`(由基线迁移写入唯一一行 `epoch=2`)。启动时先读 epoch:缺失 epoch 表且库里有业务表 → 视为 pre-2.0 库,整包归档后建空库;epoch > 2 → 拒绝启动;epoch == 2 时 revision 才用于判断"在不在迁移链上"。这样"老库的 revision 不在新链上"不会再被误判成库损坏。
- **1.x → 2.0 的启动顺序改为「判定 → 整包归档 → 校验 → 移走旧库 → 建空库 → 迁移」。** 任一步失败都中止启动、原库原样保留(不移动、不建库)。
- **文档与界面文案同步到 2.0 口径。** README 版本号改 `2.0.0`、「旧资料升级说明」整行改为归档说明(功能条目数与表格行数一致);`docs/upgrading.md` 新增「从 1.x 到 2.0:整包归档 + 全新库」;「我的资料」升级说明与使用指南改为"已归档、2.0 使用空库"。
- **更新前快照从"只拷数据库"改为完整整包。** Windows 更新器在覆盖程序文件之前先调用 `backend/scripts/create_user_data_snapshot.py`:复用「导出全部数据集」链路把数据库 + 按引用收集的附件 + 字体 + 全局模型配置打成整包 zip,并用 `verify_archive` 校验包可恢复;失败即中止更新(不再继续覆盖程序文件),随后仍保留旧的 `*.db` 拷贝兜底。跨 major 更新的快照目录标 `pre-major-<时间戳>` 且永不被轮转清理(普通快照仍保留最近 3 份)。

### Removed

- **补充事实(`profile_facts`)整套下线。** 删除后端路由 / schema / 服务 / 模型与前端 `features/profile-facts/` 模块;2.0 基线不再创建 `profile_fact`、`profile_fact_field_pref`。
- **57 个迁移测试文件随历史迁移一并下线。** 它们守住的不变量改由 2.0 基线 schema 测试重建:`upgrade → downgrade base → upgrade` 往返可用、表集合与模型注册表一致、`app_schema_epoch` 单行 = 2。

### Migration

- **1.0.x → 2.0 不搬数据。** 首次启动检测到 pre-2.0 库时,先把整个库导出成与「导出全部数据集」同格式的整包归档到 `backend/data/legacy-archive/<时间戳>/`(含 `package/` 内的 zip、`files/` 内的原库与数据集文件、`ARCHIVE-README.txt`),校验归档通过后才移走旧库并建空库。1.0 系列只提示、没有搬数据,新库从空白开始。
- **1.x 备份包在 2.0 不导入。** 导入侧在迁移候选库之前校验候选库的 epoch:缺失 `app_schema_epoch` 的包直接拒收,提示用 ResumeForge 1.0.2 打开;2.0 自己导出的包照常可导入。
- **2.0 的库 / 备份包在 1.0.2 上会被拒收。** 既有 revision 校验会以"来自更新版本"拒绝,方向正确,本轮不改 `BACKUP_FORMAT_VERSION`。

### Fixed

- **「不限篇幅」在制作台里真的可选、可保存、可预览了;`PATCH /layout` 不再"只改一项、顺手重置其它项"。**
  - **版式局部更新**:`PATCH /api/resumes/{id}/layout` 一直是"全量替换"——只传 `{font_scale: "large"}` 也会把 `page_limit` 写回 1(`ResumeLayoutUpdate` 的字段带默认值 1)、把悬空的 `format_name` 清成空串,返回 200 看不出异常。现在路由按 `payload.model_fields_set` 只写**请求里真的出现过的键**,没带的保持库里原值(schema 默认值一个字节没动,老客户端行为不变)。显式传 `page_limit: 0` 或 `format_name: ""` 仍然照写。
  - **制作台可选「不限」**:篇幅上限控件以前只给 1/2/3(长 CV 1–10),「不限」只存在于生成配置。现在按 `document_mode` 传 `pageLimitOptions()`(末尾带 0),并把 `layoutFromDetail`(`|| 默认值` → `??`)、`buildPaginationRequest`(`Number(...) || 1`)两处折弯改掉;`DocumentModeSwitch` 的选项与 `clampFor` 也认 0。选「不限」后保存、刷新、再打开仍是「不限」。
  - **预览/画布/导出接 0**:`ExportRequest` / `PreflightRequest.layout` 的 `page_limit` 从 `ge=1` 放宽到 `ge=0`(否则"选了不限"一点导出就 422);`PreviewPdfRequest` 同理;分页报告多返回 `page_limit_is_unlimited`,前端在不限时把缩略图摘要切成"实际 N 页"(不再说"上限 10 页");画布链不再把 0 夹成 1——`ResumePreview` 的 `pageLimit` 保留 0、`resolvePageCounts` 对不限单列一支(`overflow=false`、不画红框、不弹"第 N 页起不计入导出"),精确预览与测页请求都按 0 发。
  - **没动的边界**:`build_pagination_report` 的数字 `page_limit` 仍回报渲染保护上限(10),由新增的布尔字段区分;「不限」在长 CV 模式下同样走 10 页保护。

- **「不限篇幅」(`page_limit = 0`)不再在任何链路里被悄悄折成 1 页。** `page_limit` 的 0 是**语义值**(不限),但一串 `X or 1` 把它当成了"缺省":投递存档记成 1 页、按存档重放就只剩 1 页;简历详情接口回 1,编辑器显示"上限 1 页";版本快照/恢复一次也会把它变成 1(恢复路径甚至会变成 10);版式预览、助手导出、生成的字数预算同样按 1 页算。现在分成两层:**语义字段一律原样保留 0**(`X if X is not None else 1`,只有 NULL 才回落 1)——API 出入参、投递存档与 freeze/replay、版本快照与恢复、版式保存与生成落库;**渲染入口才折成保护上限**(`mode_page_limit` / `UNLIMITED_RENDER_MAX_PAGES`,即长 CV 的 10 页),并且 `build_export` 加了一层中央兜底,任何把语义 0 直接交给渲染上下文的入口都不会再被 `max(1, …)` 压成 1 页。助手的状态描述在「不限」时改说"不限篇幅",真超 10 页保护上限时才提示"超出渲染保护上限"。原先遗留的两处(精确预览/分页报告请求体 `ge=1`、分页报告对不限记录只回数字)也在同批收口,见上一条。

- **2026-11-04 收口(③④):PDF 也会说"文件里少了内容"、「不限」不再回一个假的上限、生成顺序不再漏科研四栏。**
  - **PDF 的裁剪事实**:`X-Resume-Truncated` 以前只有 Word/HTML/Markdown/纯文本会发——同一份简历被裁到 1 页,Word 会说"从某板块起没有",PDF 只说"溢出(`X-Resume-Overflow`)",界面读到的口径不一致。现在 `build_export` 把"PDF 引擎真的按页删了内容"(`pages_needed > pages` 且不是「不限」)也算进 `truncated`;PDF **不**发 `X-Resume-Truncated-At`——它按页裁,指不出板块/条目。
  - **「不限篇幅」的 page-limit 头**:`X-Resume-Page-Limit` 以前会把 `mode_page_limit(0)` 折出来的 **10 页渲染保护上限**当成用户选的值回给界面,于是选了「不限」却显示"上限 10 页"。现在「不限」回语义值 **0**(前端解析为"没有上限"),保护上限只在渲染内部生效。选「不限」时也不会被报成"已按上限裁剪"(那份 10 页保护不是用户的篇幅承诺)。
  - **生成弹窗的板块顺序**:`PROFILE_SECTION_TO_RESUME` 补上 **publications / research_projects / patents / credentials** 四栏。此前它们两侧都存在却漏了映射,后果是「调整板块顺序」只列出 9 个板块(这四栏永远排不了),且新建简历的默认顺序把它们一律丢到末尾、与资料页排好的先后不一致。现在清单是 13 项(12 个内置正文 + 自建板块),默认顺序跟随资料顺序;空板块所有渲染器都不产生节点,旧产物不变。`basic_info` 仍在页眉、不进清单(所以摘要的"N 个板块"会比清单多 1,这是设计)。

- **2026-11-04 问题清单(12 条)一次性收口。** 清单与逐条验收见 `.workbuddy/issues/ResumeForge-问题清单-2026-11-04-定稿.md`,交付报告见 `deliverables/software-company/resumeforge-2026-11-04-delivery.md`。
  - **Word 导出补齐三处保真**:页头最后一段现在画 `w:pBdr/w:bottom`(粗细/颜色取模板的 `header_border_width`/`header_border_color`,`soft_accent` 页头不叠加);技能标签按 PDF 同一套 `tag_*` 形状落成 run 级 `w:shd` + 同色 `w:bdr` 内边距(**按 PDF 的流式排列,不强行一行一个**——定稿方案里"一行一个 + `w:br`"与验收里"换行位置与 PDF 一致"互斥,取后者;圆角、`tag_columns` 网格、逐边 padding 是 OOXML 表达不了的,已在模块 docstring 写明);项目符号从 `·` 改为 `•`,不再显得比正文小一号。(1-A / 1-B / 1-C)
  - **「最大篇幅」对五种导出格式成为同一件事**:`page_limit` 以前只管 PDF,Word / HTML / TXT / Markdown 会把全部内容导出来。现在唯一出口 `export_pipeline._render_with_page_limit` 先按 PDF 版式真渲染一次,超限时按**整块**(要点/条目/板块)裁掉尾部再渲染,Word 最多收敛 3 轮;`X-Resume-Truncated` / `X-Resume-Truncated-At` 回裁剪事实,JSON 保持完整,「不限篇幅」靠 `page_limit_is_unlimited` 保住语义。(1-D)
  - **未收录资料清单重做**:一条一行、组头 `分区名 · N 条` + 原因汇总、默认前 5 条可展开、按钮移到组底;每条 note 新增显式 `reasons`(旧记录按 `capacity > filtered > model` 回退)。(5-A)
  - **自建板块「补进简历」**:新增 `POST /api/resumes/{id}/append-profile-entries`,按 `entry_ids` 确定性写入并修剪清单;「AI 补上这段」只留给能靠改写完成的技能/语言/个人总结,条目级改为「去制作台添加」并说明 AI 不能新增条目。(3-A)
  - **基本信息的 5 个字段有了纸面去路**:意向城市 / 作品集链接 / 政治面貌 / 驾照 / 到岗时间进入页眉目录(`header_fields`),字段目录同时下发当前值,加入时连值一起拷贝;默认生成产物不变。(6-A)
  - **生成弹窗只列当前组参与的板块**:模板目录新增 `group_section_keys` / `group_section_order`,前端按组过滤并在字段缺失时回退全量;删掉无人调用的 `sectionPresence.ts`。(4-A)
  - **资料自定义字段可隐藏**:字段设置行补上与默认字段同形态的隐藏开关;全局搜索改为排除 `hidden ∪ private`(不含「仅素材」),标题也不再回显被隐藏的值。(7-A / 7-B)
  - **求职进度漏斗删掉百分比**:条长仍按组内相对值归一,tooltip 写明"不是转化率";**悬浮球旁的小卡贴左不再被裁**:抽 `clampAnchoredCard` 纯函数 + `useTouTouAnchoredCard` 夹进视口,尾巴继续对准球心。(2-A / 8-A)

- **拖动 chip 时不再弹「松开鼠标以搜索文本」。** `user-select: none` 只关掉了"选中高亮",**管不住拖拽语义**——Chrome 判断"这次拖的是文本还是元素"看的是 `-webkit-user-drag`,实测 `.document-chip-value` 当时仍是 `auto`,于是按下拖动被当成拖动那段文字,浏览器弹搜索浮层、条目却没动。现在编辑器里除 `[draggable="true"]`(把手与 chip 本体,它们才是拖拽源)之外,所有元素一律 `-webkit-user-drag: none`;一条通用规则而不是逐个类补——`user-select` 那轮已经因为"漏一个类就重现"踩过一次。`-webkit-user-drag` 只影响"把选中的字拖出去",不影响输入框里的选中、复制、剪切。

- **制作台:选中文字后右键只出一张卡片、正文块又能拖了(两个回归一起修)。** 上一轮为了让拖动不再弹 Chrome 的「松开鼠标以搜索文本」,给正文块把手加了 `mousedown` 上的 `preventDefault()`——它连 HTML5 拖拽的起点一起吃掉,表现为**按住把手完全拖不动**(自动化实测 `dragstart` 计数为 0,去掉后恢复为 1);防选区改由 `user-select: none` 加容器上的 `selectstart` 拦截承担,两者都不碰默认拖拽。另一处是"两张卡片叠在一起":选区菜单走全局单例浮层,字段菜单走 `DocumentEditor` 自己的 `onContextMenu`,**同一个 `contextmenu` 被两套监听各处理一次**;现在 `features/context-menu/selectionGuard.ts` 给出一份判据(输入框 `selectionStart/End` 或文档选区,纯空白不算),`useDropdownContextMenu` 与 `DocumentEditor.handleContextMenu` 打开前都问它一次——选了字就只出选区菜单(剪切/复制/润色),没选字照旧出字段菜单(收藏/排版/隐藏/移除),键盘 Shift+F10 不受影响。实测:有选区 `{rf:1, antd:0}`、无选区 `{rf:0, antd:1}`。

- **模拟面试答完最后一题:面试官先回应这一答、再说结束。** 此前轮数用尽直接收盘——界面上最后一句话是候选人的「不知道」,紧跟着就是「本场正式问答已结束」,真实面试里不会这样。现在收盘前多跑一次模型(新提示词 `interview_closing.md`),产出「针对最后一答的点评 + 收尾话(回去等通知这类)」,落成一条 `context.closing` 的面试官消息,之后才出现决策卡;「继续追问 / 结束并生成报告」都不变。这次调用失败时**不写空消息**、照样收盘,界面用本地化兜底文案补一句;**模型自己收尾(`done`)时那句就是告别**,不会再补第二段。代价是每场面试多一次模型调用(约 2–5 秒),换取「话说完才落幕」。

- **面试「开始面试」请求体混入上传接口的 `warnings`,两侧用导入文件时必 422。** 上传端点返回的是读取 DTO(`{name, text, warnings}`,警告供界面上的警告条使用),前端把它整份存进表单、整份塞进 `POST /api/interview`,而写端点 `InterviewMaterialIn` 只有 `name`/`text` 且 `extra="forbid"`——于是两侧各报一条「包含不支持的字段」,面试停在设定页开不起来;文件本身其实已经解析成功。现在提交边界上有唯一的投影函数(`frontend/src/features/interview/materialPayload.ts`),state 继续保留 `warnings` 供展示,只在提交时丢弃。**没有放宽后端 `extra="forbid"`**:那会让"客户端字段拼错"一律静默吞掉。这与 `PUT /api/apply/config` 上把 GET 响应整份回传导致 422 属同一类错误,决策已补进 `docs/architecture.md` 的决策表;单测从"断言某个字段的值"改为断言 `Object.keys(job_material)` 恰好是 `["name","text"]`。

- **ATS 总分与覆盖率重新挂钩(评分口径变更)。** 此前关键词分项与覆盖率完全脱钩:缺 1 个核心词和缺 12 个都只扣一条 `medium`(12 分),"量化加分"传的其实是简历里带数字条目的占比、与关键词无关,而核心词为空的 JD 会被判成覆盖率 100%——于是一份对岗位零关键词命中的简历能拿到覆盖率 0%、关键词 98、总分 92。现在关键词分项 = `round(100 × 覆盖率) − 堆砌扣分`,量化加分挪进格式分项;核心词为空时退回"核心+补充"做分母,两者都为空才显示"未识别到可比对关键词"(不再显示 100%)。**同一份简历同一份 JD 的分数会普遍下降**:完全不对口的组合从 92–95 降到约 55,区分度才出现。
- **「没写进这份简历」的技能归因不再冤枉模型。** 技能名额在候选阶段就被上限(默认 14 条)压掉,旧实现把没写进去的一律记成"模型省略",用户会看到「这些技能在候选资料里,但模型没有写进简历」——事实上模型一条都没漏。现在按"是否进过候选"三分:被相关性/预算拦下的记 `filtered`、**被条数上限裁掉的记 `capacity_omitted`(新字段,旧记录缺省时按无处理)**、真正进了候选而模型没写的才记 `model_omitted`。说明文本随生成落库,**存量简历不回填**(候选集未持久化、无法可靠重算),重新生成后即用新口径。
- **资料条目写入按读取侧的字段上限校验,"写入成功但资料页打不开"的坑被堵上。** 极端数据验证发现:资料工作区的条目写入(`EntryCreateIn.values`)不校验读取模型的字段上限,160 字的奖项标题能写入成功(201),但 `GET /api/profile` 随即 500(`AwardOut.name` 上限 128)——**一页资料整页打不开**,且用户看不出是哪条惹的祸。现在写入收口(`entry_store._validated_values`,upsert/PATCH/bulk 三条路径共用)按投影真正使用的同一组读取模型注解构建上限表(`services/profile/read_field_limits.py`,43 个字段,field_id 复用 profile_service 的映射、两侧永不漂),超限返回 400 `value_too_long`,detail 写明字段、上限与当前长度。只校验提交值:存量已超限的行 PATCH 其他字段仍能保存(写合并值会把已中毒的行彻底锁死,用户连改别的字段都不行),把超限字段本身改成合法值即可自行修复。自建分区字段与照片没有读取模型上限,维持宽松不新增拒绝面。

- **「为什么是这样一份简历」的标题式段落对真实文本生效了。** 问题 11 的首版判据要求"整行以「:」结尾且不含句号",而后端生成的是「事实约束:……。」(标题与正文同一行、带句号),6 份真实 rationale 里 0 行命中——单测夹具用了假的独立行所以一直是绿的。现在识别「短标签:正文」形态,只把标签加粗、正文保持常规字重;整行标题的旧形态仍按原样处理。

- **制作台第三批修复(18 条,用户逐条实测上报)。** 清单与逐条验收写在 `.workbuddy/issues/ResumeForge-问题清单-2026-11-03.md`。
  - **预览与生成说明**:「这些资料内容没有写进这份简历」改为**默认收起**并用条数徽标保留"还有 N 条"(1),同一分区的多条 note 先按归一化分区键聚合,重复的 React key 与三行同标签因此消失(2);「为什么是这样一份简历」不再是一整块 `pre-wrap` 密文,按段渲染(段距 10px / 行高 1.7),未收录清单上移到覆盖块、说明里只留一句指路 + 条数(11)。
  - **底栏按钮**:主网格从 `auto-fill` 改 `auto-fit`,装不满时不再留空列;同一行只剩一个主按钮(「完成」降为次按钮);间距收敛为组内 8px / 组间 12px 一套口径,左右同排的换行阈值随之从 623/563 重算为 615/555(3)。
  - **任务完成弹窗**:「查看简历」从正文句子里挪进 `footer`,排在「知道了」左边,不再把一句话切断(6)。
  - **AI 修订**:模型返回空 updates、或返回与原文逐字相同的内容时不再落库并报「修改成功」,改为 409 `revision_not_applied`,并逐条列出被事实锁挡下的字段(16)。**同时保留**"隐藏/排版/锁标记不算正文变化"的既有契约:模型只在非正文标记上作出响应时仍按成功处理,不误报未执行。
  - **生成结果**:技能列表按名称归一去重(`MySQL` / `SQL` 各两次,14);未收录清单比对前剥掉行首列表标记(`- ` / `* ` / `1. `),大量误报因此消失(15);本次候选资料里根本没有的分区不再进提示词的输出模板,模型不会再为没启用的板块编造内容(4)。
  - **ATS 覆盖率**:JD 抽词不再把 `C++` / `CI/CD` / `.NET` 拆碎,补充词命中可加分但不进核心分母,13% 这类"假低"消失;格式问题另算一个分项,不混进关键词命中(5)。
  - **面试**:消息正文接上 Markdown 渲染(`components/common/MarkdownBlock`,与助手共用同一份),`**粗体**` 与 `- 列表` 不再裸露(9)。
  - **关闭专用浏览器**:从 `Popen.terminate()`(`TerminateProcess`)改为走 CDP `Browser.close`,让 Chromium 自己 flush Cookie 后退出,登录态不再丢(10);`StopAwareCdpClient` 补上 `close_browser` 转发,并在基类给空实现的能力上按"漏转发即静默失效"的判据加了守卫测试。
  - **制作台条目**:正文块新增拖拽把手(`draggable` 只挂在把手上,正文文本区仍可选可编辑),上移/下移与键盘重排沿用同一份 `placement` 判据(12)。
  - **补充要求**:不美化档位下 grounding 会把正文换回资料原文,补充要求改不了任何东西——该档位下输入框直接禁用并说明原因,后端也把这条写进 warnings(13)。
  - **面试官设定**:「从记录选 / 导入文件」开关与上下元素之间的间距补齐、选中的分段项不再与主按钮抢权重(7)。
  - **网申面板空态**:把「资料里还没填」和「当前方案未包含」拆成两个独立原因——前者给「换个资料…」入口,后者给「切换为全部资料」入口,不再用同一句话糊过去(8)。**同时修掉一个从没被发现的必修 bug**:整页自动填写把 `filtered_fields` 传给了不接收它的函数,`TypeError` 被宽泛的 `except` 吞掉,于是「整页自动填写」在没有待填字段的页面上永远失败、还只报"读取当前页面失败"。
  - **排版优化器**:「保持条目完整」此前只约束结构化条目,内容候选不受它管,开关开了照样给"删内容"的方案——现在两个候选来源共用同一个判据;「最小页边距」从"候选筛选条件"改回"排版参数下界",不再因为保住边距而把候选整体砍掉(17)。
  - **方案与板块**:三层界面按条重排——方案行改为单选并显式标注当前方案,板块/字段行去掉嵌套的多余操作,重复说明合并,导航不再出现两个入口(18)。

- **桌面打包的 Python 运行时重建在 CI 上稳定失败(get-pip 的 Windows 自我保护)。** 2026-11 起 bootstrap.pypa.io 的滚动版 get-pip.py 改为把内嵌 pip 解包后以 `python <tmp>\pip` 运行,`sys.argv[0]` 的 basename 恰为 `pip`,在 Windows 上命中 pip 自身的防自改保护(`protect_pip_from_modification_on_windows`,pypa/pip#1299)直接报错退出——缓存失效触发的「一次性重建」从此必挂。现改为绕开 get-pip:下载 pin 版本(25.3)+ SHA-256 校验的 pip wheel 直接解包进 site-packages,`python -m pip`(argv[0] 是 `__main__.py`)天然不受该保护影响;pin 版本让重建产物可复现。只影响打包机,用户侧产物与运行行为不变。

- **制作台第二批修复(22 条,用户逐条实测上报)。** 逐条证据见 `.workbuddy/issues/report.md`,实机脚本在 `.workbuddy/issues/verify/qNN-*.mjs`,截图在 `.workbuddy/issues/shots/`。
  - **草稿预览与页数链**:草稿画布不再双重滚动、不再按列跳(1);页数改由服务端分页报告驱动(`pages_needed`),左栏「约需 N 页」、黄条、画布三处同源,拿不到报告时自动退回浏览器测量且不阻塞编辑(3、20);内容超过篇幅上限时**超限页照样画出来**,第 N 页起标「超出上限,不计入导出」并加淡红虚线,导出仍按上限截断(2);12 号这类「上限 10 页」的简历不再被算成 21 页假超页(11);切换精确/草稿模式后预览不再永久冻结(12)。
  - **渲染引擎**:多行字段在 HTML / PDF / Word 与草稿里都按原文换行(16);灰字组的段距下沉到组首尾,PDF 行距与草稿一致(22.1px,原 28px),组高 168→132.8(17);条目灰字段支持「正文前 / 正文后分侧列」与「分布:靠左 / 两端」,`gap` 上限由 8 调到 16 且单位为字号倍数,HTML / PDF / Word 同语义(4);Word 导出的联系方式改为**组内按可用宽度装行**,与预览、PDF 的断点一致(9)。
  - **交互与控件**:条目位置下拉全中文,不再漏出 `inline` / `body` 等枚举(10);「从资料填入」菜单滚到底不遮不截、标题吸顶(5);浮层滑杆轨道宽度还原(15);拖拽落点的 `toIndex` 只有一份实现,落在原位不再画假插入线,四种拒绝原因各有文案(13);提示条改为 portal 到滚动容器外,字段区零位移,info 2.5s / warning 8s 分流(14);「跟随模板」在 44px 值列里不再溢出(21);三个标题布局图标从卡片头搬到「标题与元信息」域标题行(6);板块 / 条目选中改为按粒度高亮,不再是几十个灰方块(7);字段值输入框可纵向拖高,不再是塞满一行的单行框(8);4 处密度问题(新页区可收起、末页签可见、说明不折行、底部提示距视口 ≥16px)(22)。
  - **照片**:裁剪框 8 个手柄各自只动对应边,1:1 中心夹回、Ctrl 全程等比、键盘可微调(18);modern 模板照片的白边去掉了,8 套模板里只剩它多画一条 2px 白框(19)。

- **Word 联系方式与标题竖条的排版修正(问题 9 的产物层变化)。** 同一联系方式组内按可用宽度装行(超宽字段整串独占一行,不切开),组间不合并——用户显式换行仍是意图;分隔符从两个全角空格改为两个半角空格(CJK 字体下约 1em,贴近预览的 1.14em)。分区标题前的竖条不再把「段前距 + 行盒 + 段后距」一起包住(原实现视觉高度约为行盒的 3 倍):分区间距落到上一段段后、0.4em 落到下一段段前,竖条高度等于行盒。

### Changed

- **「最大篇幅」现在约束**每一**种导出格式,不再只管 PDF。** 以前用户选「1 页」,PDF 是 1 页,Word / HTML / TXT / Markdown 却把全部内容都导出来——`page_limit` 在代码里从来只是"PDF 的页数上限",而界面文案叫「最大篇幅」,承诺与实现不一致。现在唯一出口 `export_pipeline.build_export` 统一分派:**PDF** 走原有的真渲染 + 按页截断(不额外测量);**Word / HTML / Markdown / 纯文本** 先按 PDF 同一套版式真渲染一次拿到 `pages_needed` 与绘制日志(`resume/document_truncate.py`,纯函数),超限时取第一条越界内容所在的**整块**(要点 → 该要点及其后;条目 → 该条目及其后;板块 → 该板块及其后)裁掉尾部再渲染,绝不切半句;Word 因页数只是连续流估算(±1),裁后再核对一次、最多收敛 3 轮,仍超就保留内容并回 `X-Resume-Overflow: true`。**JSON 不受约束**(数据交付物,导出面板在它旁边标注了这一点)。响应头新增 `X-Resume-Truncated` / `X-Resume-Truncated-At`(`板块/条目`),`X-Resume-Pages-Needed` 继续回真实需求页数(html/md/txt 也带了);导出成功的提示改为「已按 N 页篇幅上限裁剪,超出的部分不会出现在文件里」。**「不限篇幅」的语义也被保住了**:`RenderContext.page_limit_is_unlimited` 单独承载原始 `page_limit == 0`(`mode_page_limit(0)` 会把它折成 10,只看数字分不清"选了 10 页"与"选了不限"),五格式都不裁。性能:非 PDF 导出每次多一趟 PDF 测量,本机实测 93–125ms(字体缓存已热),远低于 1.5s 预算。**两处不可根除的取舍**已写进 `docs/user-guide.md`:裁点按 PDF 版式计算,Word 打开仍可能多出半页;单条要点本身超过整页时会被整条丢掉(与 PDF 既有行为一致)。

- **批量生成 / 批量导出的参数改成"当场设置、只作用于这一批",不再把人赶去设置页。** 以前点「批量生成简历」会先弹「先完成批量操作设置」,`onOk` 直接 `navigate("/settings?tab=appearance#batch-operation")`——SPA 里跳转等于卸载岗位页,已勾选的岗位(常是 3 个)全部丢失。现在两个动作都改为打开一张**受控**弹窗(`components/batch/BatchConfigModal`):初值在打开时重读一次全局配置,从未配置过就用与后端一致的默认值并提示「当前是默认参数,可直接开始或先调整」;确认后才开跑,参数**不写回**设置页(那两张卡保留为默认值入口,intro 文案已说明两者关系)。为此字段组抽成 `BatchGenerationFields` / `BatchExportFields` 一份实现,卡与弹窗共用,不做两份会漂移的表单。**导出侧有后端契约变更**:`POST /api/resumes/batch-export` 的请求体新增可选 `config`(`extra="forbid"` 保持),路由按「本次参数 > 全局默认」取值,**既没传 `config`、全局也没配置**才 409——只传 `resume_ids` 的老调用方行为逐字不变;传入的 `config` 走与设置页同一套校验(字号/页数/语言/格式枚举 + 模板名白名单),非法值 422。前端同时补上此前完全缺失的份数上限:一次最多 50 份(与后端 `MAX_BATCH_EXPORT` 同值),超出时弹窗确认按钮直接禁用并写明原因,不再等一次 422。旧的 `useBatchOperationGuard.ensure()` 与其两条 `batchOperation.guard.*` 文案已删除(没有第二个入口依赖它),未配置不再是一道"看起来还在的墙"。
- **批量 AI 进行中现在跨页可见:新增全局浮条 `BulkAiFloatingBar`。** 挂在 `AppShell`(与 `BackgroundTaskBar` 同级),订阅既有的 `useBulkAiState()`,显示「正在批量生成简历 2/3 · 当前:岗位名」+「查看」(回岗位页)/「停止」(沿用 `stopBulkAi` 的"当前岗位跑完再收尾"语义)。**只在非岗位页渲染**:岗位页已有工具栏内联进度条,两条同时出现是重复信息;完成通知仍由 `notifyTaskDone` 单一来源负责,浮条不再挂第二套提示。刻意没有并入 `utils/backgroundTasks` 登记表——那是 adapter + 轮询服务端任务的模型,而批量 AI 没有服务端任务实体。
- **面试材料的接受范围两侧统一。** 此前岗位侧只收 PDF / TXT、简历侧只收 PNG / JPEG,那是 #41 实现时的假设而不是产品约束。现在两侧一致接受 图片(png / jpg / jpeg / webp)、PDF、TXT、Word(.docx)、Markdown(.md / .markdown),**解析路径由文件类型决定**——`kind` 只表示"放哪一侧"。文本类走本机提取(更快更准,未配置模型也能用),图片与扫描件 PDF 走视觉识别;扫描件判定为「PDF 文字层 < 200 字」,命中后把前 8 页渲染成位图识别并与文字层拼接,警告会写明「已按图片识别」。**有文字层的 PDF 不会被这条兜底拖去要求配置模型**。`.doc`(Word 2003 二进制格式)在选文件那一刻就被拒绝并提示另存为 `.docx`。字数上限从"文本 20,000 / 图片 24,000"拉平为 20,000,同一份内容存成 PDF 与 PNG 导入后字数相同。
- **面试设定卡的材料行改为横跨整行。** 边界文案搬出简历列的 `extra` 之后必须另起一行,首行只剩两个材料字段,而外层栅格是 `auto-fit`(列数随窗口变化),于是右侧留出一大片随窗口"呼吸"的空白。现在两个材料字段包进 `.interview-setup-materials`(自身也是 `auto-fit` 两列、容器宽度不足 496px 时塌成一列,不需要媒体查询),`grid-column: 1 / -1` 让这一行占满整行;岗位下拉因此变宽,长岗位名不再被截成「选中后按该岗位的 JD 提问(…」。
- **草稿画布的页数改为跟随服务端分页报告,而不是只靠浏览器测量。** 一次排版变化会多一次 `GET /api/resumes/{id}/pagination/report`(原先只有「获取分页报告」按钮才请求);报告不可用时退回浏览器测量,功能不降级但页数可能与真实 PDF 差 1 页。**刻意没有做 `page_breaks` 注入**:注入会在整数页之间插半空页,改用「报告页数直接定页帧数」后整数页数已与 PDF 一致。
- **Word 里没有 `contact_breaks` 的旧简历,联系方式行数可能变化**(由 Word 自由断行改为按宽度装行)。这是让三端断点一致的必要变更,字段内容、顺序、不丢不重都保持不变;`tests/test_export_bundle_contact_breaks.py` 的旧断言已按新契约更新。
- **超限内容的画布呈现变了**:以前只画到篇幅上限,现在会把超限页也画出来并标注,方便用户在导出前看到「多出来的是哪几页」。导出行为、页数上限、缓存键都没有变。

### Added

- **深色主题下草稿画布以「深色纸」呈现,导出与打印不受影响。** 预览 iframe 整体做 `invert(1) hue-rotate(180deg)`,照片与内联矢量图再抵消一次(证件照不会变负片);纸面容器底色改走新 token `--rf-preview-paper`(浅色 `#fff` / 深色 `#000`),深色下不再有投影。**只作用于预览**:`--rf-paper`、后端模板、`render_config_fingerprint` 一行未动,`@media print` 里把滤镜关掉(`Ctrl+P` 仍是白纸),导出 PDF / HTML / DOCX 逐字节不变。切主题只刷 iframe 文档的 class,不重载(不闪白、不丢滚动位置)。
- **导出前自动预检,超页先问一句。** 导出弹窗点「确认导出」时先跑一次预检(与导出同一份版式草稿),存在必须解决的问题(当前只有 `product_pdf_overflow` 这一类)就在面板内挂一条确认——**文案带三个具体数字**:内容需要 N 页、当前上限 M 页、第 M+1 页起不会导出;按钮「仍然导出 / 先调整」。同一读数只问一次,无 blocker 不打断,预检本身失败(网络断、500)一律放行并 `console.warn` 留痕——预检是"多一道提醒",不能变成新的导出失败点。此前 `page_limit` 小于实际页数时后端照截断并返回 200,第三页就这样静默丢失。
- **网申资料的「取数范围」可切换:仅当前方案 / 全部资料。** 悬浮面板清单顶部与网申页各有一个分段开关(两处同一份语义、同一份实现),切换后**清单、批量预览与自动填表三处同源**——不会出现"清单按全部、预填按当前方案"。范围行为发生在快照构造处(`profile_snapshot` 按 scope 组装),不是"渲染时挑一挑",所以自动填写真的会拿到全部资料。页面侧记住用户的选择(默认仍是"仅当前方案"),空态按原因分两句;**资料里从来没配过的字段即使不在当前板块也会被补进来**并标注「资料里还没填」——这两个 pin 原本就存在,只是以前被范围逻辑盖住了。
- **面试收尾改成「先问是否追问,再出报告」。** 正式问答结束后不再直接生成报告:后端把 `finish` 拆成"收盘"(插收尾 note、置 FINISHED)与"出报告"(幂等)两步,自动收尾只收盘并在响应里给 `needs_report`;前端据此显示一张决策卡(继续追问 / 结束并生成报告),刷新后仍回到这张卡而不是空白报告。`POST /finish` 三分支(未收盘→收盘并生成;已收盘无报告→只生成;已有报告→直接返回)保证"提前结束"与"补生成"都不会吞掉报告。报告只收正式问答的 transcript,**报告前的追问不进报告**。顺手修掉一个语义错误:走流式接口答完最后一轮时,最后一条回答被标成 `interrupted`(它其实是正常答完),于是「重新回答」按钮点了必 409——现在这条分支置 `completed` 并按正常结束处理。
- **自建板块可以选择是否参与岗位筛选。** 板块级开关(`profile_custom_section.filter_by_job`,迁移 `0075`,默认 `false`),随板块走;开启后该板块的条目参与岗位关键词筛选,判据与内置分区**同一份实现**,被筛掉的条目会出现在「没写进这份简历」里——否则用户只会看到"东西少了"却查不到原因。旧数据零迁移成本(缺列按 false 读),备份表清单不变。

- **ATS 检测第三层:核心词 / 补充词 / 格式规则,全部纯规则、不调用模型。** 核心词决定覆盖率分母,补充词(同义、上下位、工具举例)命中加分但不进分母,含符号的库内词(`C++`、`C#`、`CI/CD`、`.NET`、`Node.js`)整体保留不被拆碎;「格式」成为独立分项(解析类问题落在它自己身上,不混进关键词命中)。别名与 JD 分词分别住在 `services/ats_aliases.py` 与 `services/ats_jd_tokens.py`,格式检查在 `services/ats_parse_check.py`——三处都不依赖模型,因此同一份简历同一份 JD 的分数可复现。
- **CSS 类名存在性守卫:JSX 里写的项目类名必须在样式表里真的存在。** 防的是"组件不报错、测试全绿、只是没排过版"的静默退化(`.task-notice-actions`、`.interview-material-source` 这一族都这么退化过)。守卫扫 `src/**/*.tsx` 的 `className`(只取可静态确定的部分:字面量、模板字符串静态片段、`clsx` 的字符串参数),按项目自有前缀过滤,再要求 token 作为选择器出现在**项目 CSS** 里;以分隔符结尾的动态修饰符按基类判定。存量零样式类名进豁免册,每条必须写具体理由 + 到期日,且有一个用例专门查"幽灵豁免"(类名已补样式或已不再使用时条目必须删除)。
- **求职助手联网搜索接入 DeepSeek Search / Fetch(可选来源)。** 与"搜索来源"里其他项并列,但**只在填了自己的 API Key 且勾选它时**参与并发;没填 Key 时它不参与、也不会显示成"搜索成功"。它不跟随大模型的对话配置(额度与账单独立计费),已保存的 Key 回显为 `********`,清空输入框即移除;勾选后抓取正文会把网页地址发给 DeepSeek 读取,失败才回落本机读取——这两条隐私与计费口径都写在设置页的说明里。
- **篇幅支持「不限篇幅」,并让收敛目标跟随用户选的目标页数。** 篇幅控件末尾新增「不限」(`page_limit = 0`,后端同名字面量 `UNLIMITED_PAGE_LIMIT`):提示词改为"按内容需要展开",不再给总字数上限;篇幅闸门不裁剪、不发超页/删减警告,但仍然会提示"某个核心板块一条都没留下"。渲染侧总要有个 `max_pages`,所以 0 在渲染/导出路径上映射到长 CV 的上限 10 页(`UNLIMITED_RENDER_MAX_PAGES`)——这是保护,不是约束。**收敛目标不再恒为 1 页**:排版收敛从用户选的目标页数起试,超页提示补上"目标 N 页、实际 M 页"(保留原有的"超出"子串,既有守卫不受影响);「自动一页」入口是 `page_limit = 1` 的特例,行为逐字不变。**生成期刻意不做自动收敛**(要真实 PDF 渲染逐档试版,一次生成跑 N 次渲染会撞长 CV 的 20s 资源预算,失败时也没有确定的可回退版本),这条决定与理由写在 `resume_fit.py` 的目标页数入口。
- **`load_all_consumed_profile` 的使用契约有了守卫。** 该函数是"AI 能看到哪些资料"的唯一入口,此前任何新代码都能绕过它直接读资料表,导致"界面显示已启用、模型却看不到"。现在有测试扫描全部调用点(`tests/test_all_consumed_callers.py`),新出现的直读会被点名。
- **自建板块可选择是否参与岗位筛选,且选了就真的参与(问题台账需求 6)。** 「资料设置」→「板块与字段」的自建板块行下方新增**板块级**「参与岗位筛选」开关(跟随方案保存)。**默认关闭 = 与升级前逐字节一致**:整块无条件进简历,适合「作品集」「通用补充」这类本来就该全带的板块。开启后该板块的条目与系统板块用**同一个判据**(`profile_matching._score_item` + 同一个 `JobFocus`,没有第二套打分)判断与岗位关键词的交集:交集不足的条目不写进这份简历,并逐条出现在生成结果上方的「这些资料内容没有写进这份简历」里(说明原因、可在那里补回),生成说明的「候选资料组成」也会带上该板块的入选条数。存量数据只加一列 `profile_custom_section.filter_by_job`(迁移 `0075_profile_section_job_filter`,旧库/旧备份缺列按 false 读)。

- **制作台可以新建「仅本份简历」的板块,也能删掉自建的。** 中栏页签条末尾的「+」打开输入框,建好即出现在末尾并选中、左栏结构树同步;保存后重开仍在,资料页(「我的资料」)不会多出这个板块——它只属于这份简历,所以「从资料填入」也不会包含它。自建板块可在页签右键「删除板块」删除(系统板块不提供删除,只能隐藏或改标题),删除可撤销。后端 `normalize_document` 相应放宽一条:空的 `custom:*` 板块且有标题时保留(新建后先保存、内容随后再填是合法中间态),空的系统板块照旧丢弃。
- **条目灰字段支持「逐列宽度」与「逐间隔」。** 两列以上(且对齐方式为等宽列)时,左栏「条目字段排版」多出两行:「列宽」按列填百分比(合计必须 100%,不等于 100 时「应用列宽」禁用并标红合计),「逐间隔」按相邻列分别填字号倍数。三个出口共用一份判据(`column_geometry.resolved_column_geometry`:长度对不上就整体作废、回落等宽):HTML 走 `grid-template-columns` 加逐格内边距,PDF 的独立绘制器按同一组百分比折算列宽与列间距,Word 落成表格列宽加单元格右边距。旧数据没有这两个字段时产物逐字节不变(回落等宽 + 统一列间距)。

- **导出弹窗新增「导出形式:交付包 / 单独文件」。** 单独文件可多选格式(PDF / Word / HTML / 纯文本),逐个串行导出并显示「正在导出 2/3:Word」;某一项失败会报「已完成 1/3,Word 失败:原因」,不会静默中断。交付包的请求与产物逐字节不变,选单独文件时「归档原件」禁用并说明只属于交付包。
- **模板试用卡片可以直接「采用模板默认」。** 采纳前弹出二次确认写清「会清空全部排版参数(字号、行距、颜色、页边距与局部排版),换模板后找不回来」;「保留当前排版覆盖」走另一条分支。

- **新建简历支持「板块顺序」与一键清空。** 从头编写通用简历时,默认板块顺序跟随「我的资料」;顶部「顺序」可在保存前逐个上移 / 下移,或一键「与资料顺序同步」;底部新增「清空所有内容」(保留板块与页眉字段结构,清空条目与值)。AI 生成的配置页同样新增「顺序」面板,手动调整结果随生成请求落库。(Q36)
- **来源核对:新建来源可以填类型 / 引用 / 原文摘要(P5-1)。** 「建立来源关联」弹窗里新建来源不再只有标题:可选来源类型(事实台账 / 资料箱 / 岗位 / 个人资料 / 手工登记)、可选的「来源引用」(同一类型 + 同一引用会复用那条来源)与可选的「来源原文」摘要(只参与内容指纹与修订判断,来源表仍不存全文)。只填标题的旧行为完全不变:kind=manual、默认 payload 仍是 `{"title": 标题}`,历史来源指纹不会因此变化。后端 `POST /api/provenance/sources` 只增可选字段 `kind` / `external_ref` / `payload`(非法 payload 类型/超长返回 400),新建后仍走既有 `POST /api/provenance/links`,没有第二套写入链路。
- **来源核对:旧关联无法定位时不再静默消失(P5-2)。** 旧记录里只存旧路径、当前正文找不到对应内容时,来源视图与界面会明确提示「有 N 条旧关联无法定位」,逐条给出来源、旧路径与原因(对象标识不合法 / 没有可定位信息 / 当前正文找不到这个位置)。用户可以直接「删除旧关联」,或先在列表里选中目标块、再点「重新关联」;此时只预选来源、不自动挑块,新关联建立后才删除那条失效旧行。读侧只增字段 `unresolved_links`,**不迁移、不改写、不按同下标猜目标**,旧行原样留在库里。
- **条目字段排版:看得出「继承板块」还是「本条覆盖」,并可逐项恢复继承(P5-4)。** 只调一条条目时,列数 / 对齐方式 / 列间距三项各自显示来源标记,覆盖项另给「恢复继承」按钮——只把 `entry.layout` 的对应项置空,**不写板块默认**,也不影响同一条目的其它覆盖项。整体「恢复默认排版」仍然保留。
- **第三阶段:剩余入口收尾 + 制作台按 v2 模型精简。** 页缩略图与「获取分页报告」回到分页面板(页数/末页占用/溢出与手动分页标记来自**真实 PDF 同一条渲染管线**);来源核对从只读升级为可写(按选中内容建立 / 解除来源关联、新建手工来源、**采纳最新来源写回正文**并带 revision 条件写、事实锁增删);双语「在工作台查看这份译文」接上只读预览(不改主稿、不推进 revision、不产生脏值)。
- **制作台左栏按 v2 语义重组为「模板 / 版面 / 样式」三组。** 模板只管 shell + CSS 与版式预设;版面集中文档模式、篇幅上限(去重后唯一入口)、联系方式换行、分区/条目分页与页缩略图;样式集中字体字号、行距、页边距、颜色、角色/对象/文字级别/格式刷/批量。「本份简历结构」页签吸收**条目字段排版**,成为唯一结构编辑中心(结构树选中条目 → 条目排版区跟随);**删除** v1「板块顺序」控件与「简历结构示例」示意弹窗,重复挂载的角色局部排版 / 局部排版工具分组收进「样式」。
- **导出 / 分享 / 交付重放 / 模板预览不再折 v1 分析视图(T4 首批)。** 新增 v2 原生只读访问器 document_view.py;这些导出衍生链路直接消费 v2 文档(导出管线本就 v2 直通),避免自定义位置/隐藏语义在只读路径上失真。

- **补完批次:制作台的暂缓入口已补回。** 文档模式(「排版参数」顶部,普通 / 长 CV,保存走版式条件写);分页控制(分区级 + 条目级「从新页开始 / 整块不跨页」,随简历保存,HTML / PDF / Word 同口径);局部排版工具(对象级排版 / 文字级别 / 格式刷 / 多选批量);双语版本(按 v2 字段路径对照,采纳只写语言变体、不替换主稿);表达资产库;来源核对(只读);**新建简历弹窗切到与制作台相同的新编辑器**;中栏「从资料填入」菜单(点开才拉当前方案投影、按来源去重、只补空字段)。**仍暂缓**:页缩略图、来源核对里的建立 / 采纳写操作、双语「在工作台查看这份译文」入口(占位)。
- **真实 AI 生成验收通过(deepseek-flash)。** 通用简历与岗位导向简历各真实生成 1 份,`v2_check --profile-coverage` **45/45 通过**(内容 v2 / 字段位置 / 仅素材不泄漏 / 资料覆盖 / 六出口 / 事实一致);首轮发现的 5 项生成后处理问题已修复并复验。证据见计划 §20。
- **简历内容升级为「字段级文档模型 v2」。** 每个字段(含自定义字段)都是一条可拖动的独立内容,位置本身是数据:**标题行 / 元信息 / 正文前灰字 / 正文 / 正文后灰字 / 隐藏**;同一条目内可任意排序、隐藏、放进正文,跨位置拖拽即改位置。v1 的「承载区文本」与模板写死的「固定灰行」取消——绩点、核心课程、技术栈、证书详情等从此和别的字段一样可调。旧简历打开时自动转换(文字与条目数不丢,允许视觉变化),保存后即写为新结构。
- **自定义字段可编排。** 「我的资料」添加自定义字段时可选「纸面位置」(短字段默认正文后灰字、多行文本默认正文);制作台里它作为真实字段出现,可拖动排序、隐藏、放进正文;「仅素材」字段标注「不上纸面,仅生成素材」。
- **条目头布局三选一。** 条目的标题与元信息支持 **左右 / 上下 / 元信息在上** 三种排法,长元信息换行显示,不自动缩字号。
- **旧简历自动升级提示。** 打开旧简历时服务端在内存里完成转换(不写库),制作台显示提示条「旧简历已转换 / 已整理为字段级结构,文字与条目都在」:「查看转换摘要」可看识别字段数、保留文本行数、警告与空板块,可收起、不阻塞编辑。保存前每次打开都会提示(收起只隐藏本次会话);保存后即为新版结构,不再提示。逐条报告仍由 `scripts/convert_resume_documents.py`(默认 dry-run)产出,不自动跑真实库。

- **制作台新增「简历结构示例」只读弹窗。** 左栏入口打开一张用 HTML/CSS 画的示意 A4 简历:16 个编号标注 + 图例逐条说明每个参数管纸上哪一块(页边距、板块、板块标题、条目、条目标题行与日期、正文与要点、项目符号、行距、段前段后、缩进、对齐、区块间距、条目间距、分页线、字体字号、照片大小);悬停或点图例时对应区域高亮,点一下固定、再点取消。图只用于对照,不读也不改这份简历;中英文界面的示例内容与图例都在语言包里。
- **条目里的单行补充字段可以整组移到要点/描述之前。** 制作台左栏「排版参数 → 条目字段排版」新增「短字段位置:正文后 / 正文前」,作用于分区默认或单条条目(默认「正文后」= 现有产物逐字不变)。选「正文前」后,教育 / 工作 / 校园 / 项目 / 奖项 / 论文 / 科研项目七个分区的信息头按「标题与时间 → 单行补充字段 → 要点或描述」排列;奖项分区会把「名称 · 时间」「颁发机构/级别/获奖比例」「说明」拆成三层。HTML / PDF / Word / Markdown 四格式同序。
- **补充字段可以单独隐藏(内容保留)。** 「条目字段排版」每个字段行最左边新增眼睛按钮:隐藏后该字段不再出现在 HTML / PDF / Word / Markdown 纸面,但正文数据、保存与 AI 回填都不受影响;支持分区级与单条覆盖,隐藏全部字段不会留下空白行。
- **设置页新增「批量操作」,批量生成与批量导出各自一套参数。** 两张卡分开配置:生成卡(版式、字号、页数、语言、AI 润色、附加要求)与导出卡(版式、字号、页数、语言、导出格式),改一张不影响另一张。**没有保存按钮**——改动 500ms 后自动保存;第一次打开卡片时若从未配置,会用当前默认值启用一次,避免"只想用默认值"的用户被守卫反复挡回去。未启用时两个动作仍会先弹「去设置」,后端也会拒绝未配置的批量导出请求(409)。
- **简历中心新增「批量导出」:按导出卡的参数把所选简历打成一个 zip。** 多选工具条与整行右键改成与岗位广场同一形态的「批量操作」下拉(**批量删除 / 批量导出**),原来单独的「批量删除」按钮收进下拉;「退出多选」统一改名「退出选择」,计数样式、按钮尺寸与岗位广场对齐。批量导出一次最多 50 份,单份正文仍有未完成标记时跳过该份并在完成提示里说明,其余照常导出。
- **左侧导航可以手动收起。** 宽屏侧栏由 200px 加宽到 216px,品牌区右上角新增「收起侧栏」按钮;收起后只剩图标,页头左侧出现「展开侧栏」。选择保存在本机(localStorage),刷新与重开应用保持;≤1092 的窄档仍按断点自动折叠,不显示手动切换。

- **岗位广场多选态新增统一的「批量操作」下拉,整行右键菜单也换成同一组动作。** 下拉含 **匹配度分析 / 岗位需求解读 / AI 生成简历 / 加入投递队列 / 导出 CSV / 批量删除** 六项,原来是平铺的按钮(导出、入队、删除)加三个只能逐条点的 AI 动作。语义是**逐个执行原来的单项操作**,不是合成一份结果:生成简历与岗位解读在页面上串行执行(一次只打一次模型),显示 `已完成/总数` 与当前岗位,可「当前岗位完成后停止」,结束时弹完成通知;删除仍可撤销,导出/入队沿用原行为。

- **「我的资料」编辑态会标出改过的字段。** 此前改过的字段和没改的看起来一模一样,用户看不出"这次要提交哪些改动"。现在每格字段以自己的**已保存值**为基线比对:改过的字段标签变强调色、输入框换浅底,改回原值就自动恢复;新加的条目写完内容同样会标出来,只读查看态不标。

- **费用估算按「缓存命中 / 未命中 / 输出」三档分开算,单价按每百万 token。** 用量账本新增 `cache_hit_tokens` / `cache_miss_tokens` 两列(迁移 0067,可空——旧账本没这个信息),OpenAI 兼容端点的 `prompt_tokens_details.cached_tokens` 与 Anthropic 的 `cache_read_input_tokens` 都会被抓到并落库;用量卡上直接列出三档 token,命中数未知的旧记录单独计数说明,不混进合计。缓存命中价通常只有未命中的 1/10,合并成一个"输入价"会把省下来的钱算没。

- **题库每题都能写自己的答案,并让 AI 打分 + 解析 + 给出修改建议。** 每道题下面新增「先写我的答案」:写完点「交给 AI 批改」,得到 0–100 分(按切题/结构/证据/岗位相关性/表达五维综合)、档位评语(可以上场 / 还能更好 / 需要重写)、总评、做得好的地方、差在哪、怎么改,以及一段保留你自己经历与口吻的**改写示范**。批改会把已生成的参考答案一并带上做对照,所以"差在哪"是相对标准答法说的;作答与批改结果随题库历史保存,重新打开还在,改过内容会提示"重新批改才会更新分数"。
- **「我的文件」里能预览文档了。** 原先只有 PDF 能内嵌预览、图片只能点缩略图,Word 文档只能去文件夹里打开;现在 docx / pdf / txt / md / csv / json 等都能在抽屉里直接看——文字由后端**在本机**提取(不发给第三方),并明确标注"这是提取出的文字、不是原件排版",想看原件仍可用「打开所在文件夹」。

- **模拟面试新增「关联岗位 + 关联简历」必选:面试官只读这两份材料。** 此前面试官拿到的是「个人资料」,问什么都可能跑出简历与 JD 之外。现在开场两项都必填(缺任一项 422,指向已删除的记录 404),会话落库 `resume_id` 与简历标题快照(简历被删后历史仍能说清当时面的是哪份);送给模型的上下文只剩「候选人的简历」与「该岗位的招聘信息」,提示词新增「信息边界(硬性)」:不得引用、猜测或索要这两份之外的背景,简历里没写过的公司/项目/数字一律不许编。关联简历下拉的候选是「本岗位关联的简历 + 通用简历」(`GET /api/resumes?job_id=X&include_general=true` 新增参数,默认关闭、旧调用产物不变)。
- **模拟面试新增计时器:先问「准备好了吗」,点了才走表。** 进房间先过一道闸门(首次「准备好了」、离开后回来是「继续面试」),点下之后才开始计时、作答区才出现;离开房间会暂停,回到房间必须再点一次才继续。累计有效时长随每轮与结束上报(`PUT /api/interview/{id}/duration`,按绝对值取大值防回退),报告卡展示「面试用时」,生成评分报告时把用时作为输入之一。
- **资料箱的附件都能点开预览。** 详情抽屉里每个附件行改成原生 `<button>`(回车/空格可用、有焦点环),图片给大图,文档给系统提取出的全文并注明「这是提取文本、不是原件排版」,两者都没有时明确说明没有可预览内容。

- **排版参数新增「条目字段排版」,承载区补充字段可以两两一行。** 教育 / 实习工作 / 项目 / 校园 / 荣誉 / 学术成果 / 科研项目这些条目的补充字段(学习形式、学位类型、院系、成绩排名、导师、研究方向、团队规模、项目规模、颁发机构、级别、成果类型、署名位次…)此前在纸面上固定逐字段一行、全部左对齐;现在可在「排版参数 → 条目字段排版」里按分区设置每行字段数(1–3 列)、字段顺序与「独占一行」,设置写进这份简历的 `format_config.field_layout` 并随简历保存。HTML 导出、服务端 PDF、Word、Markdown / TXT 共用同一份行装箱结果(行内用「 · 」连接);没有调整过的分区,四个格式的产物与改造前逐字节一致。
- **自建板块的每条条目也能「从资料填入本条」。** 自建板块此前的条目级填入被跳过(正文形状是「主文本 + 标签行」,没有可按字段合并的结构);现在按「只补空值」合并:主文本为空才填,标签行按标签名补空值、缺失的按资料顺序追加到末尾,已有非空内容与行顺序一字不动,也不复制资料条目的稳定身份。同一板块内复用「当前条目 / 已在其他条目」禁用语义;候选仍来自当前方案投影,跨板块互不影响。
- **英文界面补齐枚举与后端下发标签的翻译(阶段 0.5,前端收尾)。** 岗位类型/状态、投递队列与批次状态、准入与匹配结论、失败分类与步骤、浏览器状态、事实台账的分类/核实状态/承担程度/证据来源、求职进度状态与来源、内推与提醒状态、面试类型/难度/风格、面经来源与轮次、知识库与资料箱分类、深挖反馈/演练/行动类型、简历目录的版式预设与板块名——这些原先直接渲染后端/类型层中文的展示点,现在都在页面层通过「协议值 → 语言包 key」映射(映射不到时回退原文,协议值本身一个未改);数据看板的漏斗阶段、趋势月份、周内分布、记录来源,以及简历目录下发的板块名、版式名/说明、字号档位也一并接入。英文界面下这些位置不再露出中文,中文渲染逐字不变。
- **英文文案统一第一轮 + 英文术语表。** 修正 71 处大小写/标点/美式拼写/跨页小节名不一致(Target job、Honors & awards、Campus activities 等),并新增 `docs/ui-copy-guide-en.md`(246 行:九组术语正名、大小写与拼写规则、语境差异判据、未收口清单)。同一中文含义对应多译文的分组由 259 组降到 242 组,其余为刻意的语境差异。
- **英文界面最后三处「后端直接下发的中文」按语言收口。** ① 回收站的类型筛选与列表行:新增 `TRASH_LABELS_EN`,按与语言无关的 `TrashSpec.key` 取英文名,中文名仍是 `spec.label`(日志、测试与中文界面照旧);② 事实台账的黄色提示:`claim_warnings(record, locale)` 的 7 条规则全部有英文分支,承担程度的英文用词与前端 `en-US.enumsClaims.ts` 逐字对齐(徽标与建议同词);③ 投递浏览器首次登录提示:`login_hint(locale)`(中文原文留在 `browser_manager_i18n.py`)。站点显示名另加可选扩展点 `display_name_for(locale)`,英文界面显示 `BOSS Zhipin`、中文仍是 `BOSS直聘`,`BOSS_DISPLAY_NAME` 常量与 `matches()` 识别路径未动。协议值与中文文案逐字未变,未设置语言时一律回落中文。

- **排版参数新增「联系方式换行」,并把联系方式默认改为"整字段不拆行"。** 页眉里的手机 / 邮箱 / 城市 / 出生年 / GitHub / 个人主页现在每个字段整体不拆行、字段之间自动换行——长网址不会再被从中间截成两行;可以在「排版参数」里指定在哪个字段前强制换行(例如让个人主页单独一行)。设置写进这份简历的 `format_config.contact_breaks` 并随简历保存,草稿预览、精确预览 PDF、Word、HTML 导出读同一份取值;旧简历没有这项设置时保持默认(不强制换行、每个字段整体不拆行)。
- **简历制作台新增「文字级别」。** 在「局部排版」里选中内容后,可套用一级标题 / 二级标题 / 三级标题 / 正文 / 次要说明五个固定级别(类似 Word 的标题级别);级别只写一组对象级样式覆盖(字号系数、加粗、段前段后),颜色 / 缩进 / 项目符号等其它局部设置不动,可多选批量应用、可「恢复跟随模板」只清掉级别,每次应用在历史里算一步、可撤销。为让草稿 HTML 与精确预览 / 导出观感一致,对象级覆盖现在同时作用于条目内部的角色元素(标题 / 元信息 / 要点),HTML 与 PDF / Word 按同一份模板基准比例投影(默认路径与无对象覆盖的简历产物不变)。

- **英文版迁移先建立语言无关的文案守卫(阶段 0.1)。** TypeScript AST 按界面用途识别中文与英文硬编码,保留原中文总量、逐文件及 ByText 预算,新增存量文案指纹和扫描范围完整性检查;基线维护不能登记新文案。语言包 fixture 校验双向 key、未翻译、CJK、空值及占位符名称与次数,语种无关等值按自动 token 与逐 key 理由两级管理。此阶段不开放英文界面、不改变生成语言或数据库。

- **补齐现有语言包的 2,549 个英文 key(阶段 0.2)。** en-US 按领域拆成 29 份文案与一份聚合入口,每个命名空间独立校验;真实中英文聚合包的 key 全集、译文与占位符契约现已强制执行,并保留编译期 key 校验。此处仅提供静态语言包,界面切换、剩余硬编码迁移、生成与安装器接线仍在后续阶段。

- **简历详情显示「本次使用」追溯,并在资料/组定义变化后提示需重新生成。** 已保存/已生成的简历不会因切组或改资料而改变内容,只记录一条可追溯的「本次使用:<组名>(N 个大字段)」快照:简历详情新增只读行,hover 或键盘聚焦可查看参与的大字段名;快照里的组被删除后仍按快照显示并标注「组已删除」。当快照之后资料条目、当前组或大字段/自定义字段定义发生变化时,详情内联提示「资料或组定义已更新 / 已保存简历不会自动变化,需重新生成才会体现在纸面上」(只读,不改正文、不自动重新生成)。后端新增只读接口 `GET /api/resumes/{id}/profile-usage`(返回快照与过时原因 content_changed / group_changed / definition_changed),旧简历无快照时不显示也不报错。

- **「我的资料」每个大字段都可显示/隐藏(F9)。** 语义:隐藏 = 从**当前组参与清单**移除(因此也不参与生成、导出与网申),显示 = 加回;不新增第二套存储,与组编辑勾选框同一份数据。入口三处:组管理 →「大字段与字段」每个大字段行、「字段设置」视图顶部、资料页顶部的「已隐藏的大字段(N)」清单(查看态即可恢复)。基本信息参与网申,恒在清单首位,开关禁用并说明原因。新建组默认只含基本信息——即新组创建后所有大字段默认隐藏(基本信息除外);非编辑态创建成功后自动切到新组并给提示,编辑态不切组(避免绕过未保存确认)。组管理列表的「改名」按钮改为「编辑」(它本来就会打开名称 + 参与清单)。

- **「我的资料」小字段改为响应式多列网格(F9)。** 单行文本在宽屏 3 列 / 中屏 2 列 / 窄屏 1 列;多行文本与日期区间整行;行距收紧。字段顺序、默认收起/隐藏逻辑与读屏 label 关联不变。

- **跨行业与专业工作台(A0–A8):职业场景、六类补充事实、通用简历入口与长 CV 生成。** 详见 `docs/resumeforge-industry-benchmark-plan.md`。
  页头新增可选的**职业场景**(职业阶段 / 使用目的 / 当前就业状态 / 目标行业与职能 / 合作形式 / 表达偏好;内置快捷场景可自定义)。
  **切换或编辑场景只改本窗口的引导与建议**,不写个人资料、岗位、简历、投递准入、网申或后台任务;生成简历时必须显式开启
  「用于本次生成」,服务端只解析允许的维度、默认不发送就业状态,并把一份不可变快照写进生成任务与记录(不塞进 render_context /
  format_config / content)。「我的资料」新增**补充事实**(论文 / 科研项目 / 专利 / 资格证照 / 作品项目 / 实践或服务经历六类,
  稳定身份 + 条件更新 + 软删进回收站;证照编号只存本地,默认不进模型、日志与纸面);旧「保存全部资料」不会清空新事实。
  简历中心页头新增「AI 生成」通用简历入口(保留手写「新建简历」);生成弹窗的文档模式可选普通简历或长 CV(最长 10 页,
  不自动切换,超页如实提示而不缩字号)。新栏目贯通生成、AI 修订、事实锁、预检、命名版本与变体、双语、六格式导出与备份;
  科研三栏目在 AI 生成与修订中的字段丢失(coerce_resume)已修复,并回填条目稳定身份与锁标记。

- **已有简历可以按职业场景派生变体:先派生、再看差异、最后采纳。** 「版本与变体 → 岗位变体」新增
  「按职业场景派生变体」:选已保存场景或快捷场景并起名后,先创建一条独立变体(源简历零写入、记录本次
  场景快照);对变体点「生成场景候选」只返回候选与差异(不落库),点「采纳到这条变体」才写入这条变体。
  采纳按「变体内容指纹 + 场景上下文哈希」双条件写:变体在别处被改过或场景在候选生成后被改过都会明确
  提示冲突并拒绝覆盖;锁定正文的条目被改动时返回 409 且一个字段都不写。后端新增
  `POST /api/resumes/{id}/scenario-variants`、`POST …/variants/{vid}/scenario-preview`、
  `POST …/scenario-adopt`,源简历的内容 / revision / 锁 / 来源标注全程不变。

- **补充事实可从资料箱显式选入。** 「我的资料 → 补充事实」的添加入口新增「从资料箱选入」:选一条资料箱
  里的资料、确认它要变成哪类事实后,只生成编辑器草稿(标题、说明、公开链接按资料真实字段映射,证号等
  私密/不确定字段留空),用户核对并保存后才写入;不会把资料箱内容静默变成简历事实。

- **补充事实支持九类、自定义字段,以及删除/恢复默认字段;并接入网申填表。** 「我的资料 → 补充事实」
  在论文 / 科研项目 / 专利 / 资格证照 / 作品项目 / 实践或服务经历之外新增**荣誉与获奖 / 培训与课程 /
  语言能力**三类;每类事实可加**自定义字段**(label + 值,随该条事实保存),也可在**字段设置**里隐藏
  默认字段或恢复默认(隐藏后表单、AI 候选、导出与网申取数都不使用,但本地旧值保留,恢复默认后重新出现)。
  字段设置按类型条件写(多窗口冲突 409),新增表 `profile_fact_field_pref` 与接口
  `GET /api/profile/facts/schema`、`PUT /api/profile/facts/schema/{kind}`、
  `POST /api/profile/facts/schema/{kind}/reset`。新类型进入生成候选:荣誉→奖项、培训→项目(成果类型=培训)、
  语言→技能;自定义字段作为该条事实的补充说明进入候选。网申填表取数(「换个资料」清单、批量预填与
  放宽匹配)也纳入事实可见字段;隐藏字段与资格证照编号永不进入网申取数。

- **资料大字段 / 组 / 当前组:后端存储层与接口(F1,同批 F2–F6 接入界面与三条链路)。** 新增迁移 `0064_profile_groups`(只加表:自建大字段与自定义小字段定义、稳定条目与 JSON 值、组与参与关系、数据集级单行「当前组」状态、字段偏好、生成使用快照),并新增 `/api/profile/workspace`、`/api/profile/groups`、`/api/profile/sections`、`/api/profile/field-preferences` 三组接口。**13 个内置大字段与 106 个默认小字段由代码目录唯一提供**(学术成果 / 知识产权 / 语言能力等按 v3 口径就位);组只记录「名称 + 参与清单」、不拥有任何资料副本,当前组是数据集级唯一状态,删除当前组会自动回落到默认组并留下降级告知(资料页顶部展示)。默认收起与用户隐藏是两个独立状态:收起照常进模型与纸面,隐藏才排除且值保留。自定义小字段可挂在**任意**大字段下(含 13 个内置分区)。**旧资料表一张不删、不搬数据**:升级后旧内容仍在本地与备份里,可从备份取出后手动重填(升级提示与生成/新建/网申三条链路的接入见本批 F2–F6 条目)。备份格式号不变(只加表),旧备份仍可导入。

- **「我的资料」改版:13 个大字段 + 自建大字段 + 组与当前组,补充事实模块退役(F2–F6)。** 13 个大字段(基本信息 / 教育经历 / 实习工作经历 / 项目经历 / 专业技能 / 校园经历 / 荣誉奖项 / 个人总结 / 学术成果 / 科研项目 / 知识产权 / 证书资质 / 语言能力)与 106 个默认小字段由代码目录唯一提供:学术成果 / 知识产权 / 证书资质同批改名,新增作品集链接、政治面貌、驾照、到岗时间等 18 个字段,移除教育 CET4/CET6 与专业技能的证书/成绩/取得时间(旧值不迁移)。资料页按**当前组**展示参与分区(参与即出现、没数据显示空态),基本信息与个人总结是单例,其余可增删与拖动排序;可自建大字段(如开源贡献)、在任意分区(含 13 个内置分区)加自定义小字段;每条记录有稳定 id 且支持显式排序。**收起 ≠ 隐藏**:收起只影响输入框是否展开、有值照常进 AI 与纸面;隐藏才排除参与且值保留、可恢复。保存改为合并语义:未提交字段(含隐藏/已删字段)保留旧值,显式清空才删除。新增「组」只记录名称与参与清单、**不复制资料**(任何组内编辑立刻对所有组可见);「当前组」是数据集级唯一状态,生成 / 新建简历 / 网申三条链路都按当前组取数(只减少、不增加可发送资料),默认组不可删除、删当前组自动回落默认组并提示。新增迁移 `0064_profile_groups` 只加 8 张表(旧表不删、不搬数据,旧备份仍可导入),配套 API:`/api/profile/workspace`、`/api/profile/sections`、`/api/profile/section-order`、`/api/profile/groups`、`/api/profile/current-group`、`/api/profile/field-preferences`。

- **资料纸面与生成改为服务端确定性口径:C1 空分区、C2 自建栏目、C5 标签行补回(F2–F6)。** 新增 `GET /api/profile/current/projection`:服务端解析当前组后返回纸面内容、逐条候选、来源布局、长度警告与快照指纹,前端不再自行拼「标签:值」。承载区映射(13 个系统分区)与「仅素材」判定只有一处定义:参与分区即使没数据也保留空数组(C1);自定义小字段按定义顺序以「标签:值」追加到承载区,空值跳过、超长如实提示(R-44);**自建大字段在简历上独立成一栏、排在 13 个系统分区之后**(C2),HTML / PDF / Word / Markdown 四格式同步;语言能力按「语言(水平 · 证书 · 分数)」映射为专业技能尾部条目。AI 改写正文后按「去空白与标点」归一化逐字段检测,缺失的「标签:值」自动补回、不因此拒绝保存(C5-B);证书编号等私密字段在任何投影/候选/警告里都不出现。三链外(助手 / 评分 / 审阅)仍读全量资料,不受当前组限制。

- **简历编辑器补齐资料里的 20 个小字段,四格式按「标签:值」渲染,生成结果确定性回填(F8;R-45 以 R-81 再次修订)。** 教育经历的学习形式 / 学位类型 / 院系 / 成绩排名 / 导师 / 研究方向、实习工作的团队规模 / 工作类型、项目的项目规模 / 成果类型、技能的技能分类、校园经历的活动类型、奖项的颁发机构 / 奖项级别 / 获奖比例、学术成果的成果类型 / 我的署名位次、科研项目的项目级别 / 基金·资助、证书资质的证书类型,现在在简历编辑器里都有输入框;手写新建、从资料填入与 AI 生成都能带上。纸面在条目承载区之后按「标签:值」逐行渲染(空值跳过、顺序固定),HTML / PDF / Word / Markdown 四格式同序;生成链路按本次快照确定性回填资料值(不覆盖 AI 已改写的值、不因此拒绝保存)。`ResumeContent` 只增可选字段:空串不序列化,旧简历内容、渲染指纹与导出逐字节不变。

### Changed
- **「列间距」上限从 4 放宽到 8,并写清它在调什么。** 单位一直是**字号倍数**(纸面 CSS 是 `column-gap: calc(var(--fs) * gap)`),不是像素;面板现在直接显示换算「4.00 倍字宽 ≈ 56px」,px 数从纸面真实的 `--fs` 读、不是写死的常量。三列且每列已被挤到 60px 以下时给一句提示(调小列间距或改用两列),不再静默溢出。旧值(≤4)的产物与改前一致,语义不变。
- **照片裁剪改成非破坏的「取景」参数。** 以前「应用裁剪」会把新图写回照片字段,原始照片就此丢失;现在裁剪只记录取景框(`format_config.photo_crop`:缩放比例 + 中心点 + 原图 sha),照片字段本身一个字节都不改,「恢复原图」只删这条记录。取景为 100% 时整张原图都在框内、无从平移,两个位置滑块置灰并说明原因。页眉缩略图、编辑器预览、HTML / PDF / Word 导出都按同一份取景渲染。
- **断页入口按「谁能设置、谁才能清除」收敛。** 板块级「从新页开始」在参数页签的表格里设置、同页签的汇总条用 ✕ 清除;条目级的两个开关(从新页开始 / 尽量不跨页)只在「本份简历结构」里改,汇总条只给一个 →(点了跳到结构页签并选中该条目)。历史遗留的板块级 `keep_together` 不再出现在汇总条里(字段仍留在 JSON,不迁移、不删除)。
- **面试材料可以直接导入文件。** 开场设定里岗位与简历两侧各是「从我的记录选 / 导入文件」二选一:岗位侧吃 PDF / TXT 的 JD(本地抽文本,10MB 内),简历侧吃截图(走视觉模型逐字抄录);解析成功显示文件名与字数,失败给明确提示而不是静默留空。材料正文随会话存为快照,提问直接引用文件内容,历史卡与面试房间头部都标出用的是哪份文件——记录被删也不影响这场面试。
- **面试结束后的追问独立成相,不再吃掉正式轮次。** 报告下方新增追问区(常驻说明「追问不占正式轮次,也不计入评分报告」):追问两轮、停止、刷新再继续,轮次数字与报告都原样不动;追问内容也不掺进评分依据。
- **基本信息升级成与正文一致的字段 chip。** 页眉按「姓名 / 姓名附注 / 一句话介绍 / 联系方式 / 照片」分组排布,每个字段都能单击选中、双击(或 F2)展开编辑、右键隐藏或删除、拖拽换位,也支持方向键换序(←→ 换位置组、↑↓ 组内换序、Esc 回滚);添加入口新增「自定义页眉字段」,起的名字会连着值一起印在纸面上,不再是裸值。顺带修掉三处真问题:页眉字段的「隐藏 / 删除」此前点了没反应(走的是正文条目分支、对页眉是空操作);选中页眉字段时编辑器停在上一个板块、看不到被选中的 chip;以及双击手势本身——真实浏览器里第二下 click 会先翻转选中、把刚展开的编辑器又关掉,表现为「双击第一次不展开、展开后再双击收不起来」。现在第二下 click 直接承担展开 / 收起,`dblclick` 只做去重,单击仍然立刻选中、不引入等待;另外新加字段后的提示条原本压在页签上方,一出现一消失就把整块字段区顶动约 52px(快速双击时第二下正好落空),现已移到内容尾部,字段区位置恒定、也不挡任何可点区域。
- **照片改成缩略图 chip,裁剪入口回来了。** 此前照片字段会把整串 data URL 铺在文本里;现在是 32×40 的缩略图,展开后给出「更换 / 裁剪 / 移除」三个动作——裁剪弹窗支持四种比例档位加缩放与水平 / 垂直微调,写出新图后仍可从资料照片库重裁。另外全局搜索的命中类型标签不再显示 `referral` / `reminder` 这类原始码。
- **AI 边界与文案批次。** 每一次 AI 调用现在都带四项数据边界声明(输入范围 / 是否脱敏 / 目标语言 / 预算上限)并写入审计日志——四个用户可触发的入口(简历生成、岗位分析与匹配族、求职助手对话、设置自检)全部接入,日志里 `missing` 即代表有入口漏声明;结论归档在 `deliverables/software-company/ai-data-boundary-audit-2026-11-01.md`。排版浮层、生成配置与批量卡上那个语言下拉统一改叫「字段语言」(说明补一句「要翻译正文请用双语版」)——它控制的是产物的小节标题与字段名,旧名字「简历语言」容易让人以为会翻译正文。
- **面试与投递诊断批次。** 模拟面试的类型 / 难度 / 风格不再是三个只写在开场设定里的标签:每个枚举值现在对应一段可执行的行为指令(行为面按 STAR 追问链逐环补问、初级题面更短并给提示、压力质询直接指出回答里的问题),随会话注入面试官提示词,新加枚举值而漏配行为会被守卫测试拦下;投递记录每行失败原因附带具体诊断摘要,工具栏可按失败分类筛选(选择器失效 / 需要登录 / 验证码等十类),批次组头直接显示「选择器失效 ×2、验证码 ×1」这类同因聚合;求职进度的失败记录不再新增自由文本三列(失败环节 / 卡点对象 / 一句话原因)——该方案已废弃、对应迁移 0070 已删除,失败分类只保留投递任务项的稳定码(迁移 0073),避免同一需求两套实现;分页面板的「页缩略图」改名「页面概览」,页格子等宽排布,页数正好用满上限时显示「已达页数上限」。
- **用量与双语批次。** 「模型用量 → 按任务」的 22 个任务类型全部显示中文标签(此前冒出 `interview`/`unknown` 这类协议码;英文界面显示英文),后台 AI 任务(岗位解读、风险扫描、优化建议、网申填写)此前漏记的用量现在如实入账,「测试连接」每次点击也会在账本里留一条「设置自检」记录(失败同样留痕);「从我的资料添加」菜单顶部新增搜索框,资料多时输入关键字即可过滤候选,分组与限高保留;双语对照的译文预览支持按住 Ctrl 滚轮缩放(50%–200%,步进 10%,关闭抽屉自动复位),不按 Ctrl 时滚动行为不变。
- **弹窗与深挖批次。** 「从头编写简历」弹窗改单列布局:资料来源从右侧一整列改为顶部标签行,名称输入框加宽到 420px 以上,不再右侧空一大块;「版本与变体」弹窗内容限高内部滚动、页签条吸附在顶部(长列表滚到底标题不动,差异本来就是字段级三态对比);「岗位变体」页的选择性同步里,基本信息字段显示「姓名」这类中文名而不是 `name`/`gender`(与制作台同一份字段目录);台账页点「开始深挖」改为就地弹出参数弹窗(已预填勾选的主张,不选主张时按钮不可点),确认之后才进入深挖页——不再一点就跳走。
- **面板与隐私批次。** 「版面 → 分页」删掉了板块级「不跨页」开关(渲染引擎从未消费它的死开关),表格只剩板块名与「从新页开始」两列;条目「尽量不跨页」文案如实标注 best-effort;条目排版面板滚到底时最后一条提示完整可见;「外观」的颜色行改成手风琴——一次只展开一行色板,未自定义的颜色如实显示「跟随模板」(不再出现「—」与红色斜线占位),四个颜色字段改叫「强调色 / 正文 / 次要文字 / 分隔线」;灰色短字段列表按「正文前 / 正文后」分组、顺序与纸面一致(此前「核心课程」这类正文前字段排在整张列表最后),每行的「独占一行 / 显示字段名 / 隐藏」收进「⋯」菜单;生成弹窗的「取消 / 开始生成」固定在弹窗底部不再跟着表单滚走;来源下拉可以清空选择;「收藏当前要点」文案补上宾语;「从资料填入」菜单加了高度上限不再延伸到视口外;网申放宽模式的说明写明**四类始终不代填**(日期选择器、文件上传、密码/验证码/银行卡、无弹层的只读点选框);网申填充记录里的证件号默认遮罩(保留前 3 后 4),点「显示」才看全。
- **制作台交互第二批。** 字段右键菜单新增「调整排版」:范围按右键命中的字段自动推断(标题/正文/说明),浮层里直接改字号、加粗、对齐、颜色,不再绕道左栏找对应控件;条目「···」按钮的菜单贴着按钮弹出(此前锚在整行上),右键仍在鼠标处;右键浮层和字段菜单在滚动、窗口缩放、切走窗口时都会立即收起(此前会留在原地错位);中栏字段单击只选中,双击或 F2 才展开字段编辑器,扫视字段不再被误触弹出的大编辑器打断。

- **AI 生成按真实排版闭环篇幅。** 生成后按导出同一条渲染管线数页:超上限先带着"当前 N 页、需压到 M 页"的差距重试一次,仍超就按"最长要点优先"确定性删减(事实锁字段不动),删了几条、还超几页都在结果里如实写明——不再出现"按 4 页写、渲染砍成 1 页"的静默截断。三档美化(轻度/均衡/深度)改成可度量的差异(每条要点 20-28 / 28-36 / 40-55 字、每条 1-2 / 2-3 / 3-5 条),档位说明同步写明数字;核心板块(工作/项目/技能/校园)生成后有代表条目检查,缺失会如实报告。职业场景升格为可执行约束:勾选场景后板块按「优先栏目」顺序输出、未列出可省略;斜体功能整体移除(PDF 无斜体字形、Word/HTML 行为不一),正文里成对的单星号仍按"去标记"剥壳、不残留星号,能力矩阵不再有斜体一项。生成弹窗的「调整板块顺序」纳入自建板块,自建板块可以插进任意位置。
- **制作台交互修正。** 中栏点选字段不再把整个工作台抬起(只滚编辑器自己的滚动容器);结构树的位置按钮从「⇅」换成「···」并改叫「移动位置」,锁按钮改叫「锁定设置」,读屏不再把两个按钮念成同一个名字;拖拽字段的高亮框在松手、取消、拖去别处后都会立即消失(此前要刷新才消失);拖到某个字段前半/后半会显示插入线,落点与所见一致(同组内连续拖不再差一位);改条目头布局这类开关操作**立刻**出现在「历史」页签(不再等 300ms 或看到空栈),历史标签按操作类型显示(「调整条目排版」而不是一律「编辑正文」)。
- **来源与枚举的显示层修复。** 来源影响列表不再显示 `education.ob_…` 这类内部标识,改显示「条目名 · 字段名」可读文本;来源详情的指纹只显示前 8 位(完整值放悬停),文案改叫「版本指纹」;「工作类型」等枚举字段在中栏、结构树、填入菜单里显示「全职」而不是 `full_time`。
- **双语版本不再互相污染。** 在「中文版」上点「生成翻译」会被禁用并说明原因(它只是翻译源);译文只能采纳进对应语言的版本(后端也校验,绕过前端直接调用返回 422);被污染的版本可以用「用当前简历覆盖此变体」(二次确认)恢复,名称与排版不动。

- **验收返工批次(2026-11-01):43 条清单的 25 条返工 + 6 条附加发现。**
  - **制作台左栏收口**:独立「局部排版工具」面板下线,字段级排版统一走中栏右键 chip「调整排版」浮层(颜色 / 行距 / 缩进 / 项目符号 / 对齐 / 字距 / 日期位置),浮层里不再有作用范围单选、「扩大到整条」与斜体入口;「字段语言」从模板组移入「版面」组(文档模式之后),「联系方式换行」只保留一个入口;「外观」四行(强调色 / 正文 / 次要文字 / 分隔线)共用同一条色板,点任一行都展开并可改色。
  - **分页面板「页面概览」两格等宽**:两页格子实测 64.00px / 64.00px(改前 70.00 / 81.88),「已达页数上限」「末页占用」保留。
  - **字段右键菜单与 chip 手势**:点界面空白 / Esc / 滚动都会关闭,右键另一个字段时菜单跟到新鼠标位置;单击只选中、双击(或 F2)才展开、再双击收起,先单击选中再双击不再丢选中;Esc 改为分层——先收编辑器 → 再取消选中 → 最后才关制作台,焦点回到原 chip。
  - **日期位置在 PDF 真正生效**:`object_roles` 覆盖 `meta_align=inline` 时,精确 PDF 里的日期从贴右缘(468.0pt)移到标题之后(196.1pt),不再等于没设置;元信息加粗在 PDF 仍是粗体面。
  - **枚举与照片的显示层补齐**:自建板块 / 自定义枚举字段在纸面与 PDF 也显示「全职 / Full-time」而不是 `full_time`(四族词表对同一 code 显示名唯一才翻,`pending` 这类跨家族歧义值不猜);照片字段在所有入口都显示缩略图,不露 data URL。
  - **灰色短字段顺序与纸面同步**:列表按「正文前 / 正文后」分组、组内顺序即纸面顺序,↑↓ 只在组内移动(组首 / 组尾按钮置灰);改完「前 / 后」后实时预览约 0.6–1 秒内跟上,不必切页签或重开。
  - **版本与变体**:命名版本支持空名称按时间自动命名;版本比较改为字段级「旧值 → 新值」并默认与上一版比,弹窗内容限高、页签吸顶;岗位变体创建入口合并为一个 Segmented(从当前简历 / 从职业场景)+「创建变体」,基本信息字段显示中文标签,页眉字段可选择性同步进变体。
  - **求职助手上新**:新增 `get_resume_status` / `list_versions` / `list_variants` / `get_entry` / `update_resume_field` / `add_resume_entry` / `reorder_sections` / `set_entry_layout` 八个工具;文档索引扩到 `docs/` 递归 + `CHANGELOG.md` + zh-CN 界面语言包(实测 197 文件 / 1121 章节);写操作一律先出「待确认改动」diff,确认后 revision 前进并自动留一条「助手修改前…」命名版本,可在「版本」里恢复,拒绝则不动库。
  - **投递记录 / 模拟面试 / 事实台账**:投递记录失败列带稳定子码与诊断摘要(新迁移 0073 给 `apply_task_item` 加 `failure_step` / `failure_target` / `failure_reason` 三列并回填历史失败行)、失败分类筛选后明细同步过滤、组头同因聚合并支持「只重试这 N 条」(新端点 `POST /api/apply/records/retry-batch`)、详情抽屉改「发生了什么 / 在哪一步 / 你该怎么做」三段建议;模拟面试材料边界文案改为「面试官只会看到你选定的这两份材料」,报告下方追问区支持「停止追问 / 继续追问」且不占正式轮次(新迁移 0072 加 `follow_up_closed_at` + `PATCH /api/interview/{id}/followups`);台账「开始深挖」就地弹窗预填已勾选主张、提示已忽略的未确认条数,未选主张时按钮禁用,后端空列表返回 422。
  - **AI 数据边界与参数**:生成 / 岗位解读 / 面试 / 深挖 / 设置自检五条链路的 `llm.call` 四字段(input_scope / masked / language / budget)全部有值、无 unspecified / missing;美化三档的生成温度随档位不同(0.05 / 0.10 / 0.20);放宽模式的「四类始终不代填」(日期选择器、文件上传、密码 / 验证码 / 银行卡、无弹层的只读点选框)在设置页与网申填表页默认态都可见。
  - **表达资产库与来源核对**:「收藏当前要点」在未选中时禁用并说明原因(tooltip「先在左侧选中一个要点」),正文要点右键新增「收藏为资产」第二入口;搜索 / 筛选 / 下拉统一叫「岗位标签」;卡片元信息读出类型 / 语言 / 来源 / 已用于 N 份简历四个维度,计数改「共 N 条资产」,卡片动作收进「⋯」菜单;来源核对里内容块可清空回空态、事实类型是必填下拉(带 * 且不可清空);「从我的资料添加」菜单加「N 项可添加」计数、分组标题滚动吸附、条目缩进与板块灰字。
- **真实模型验收(DeepSeek-V4.1-Flash)补记**:25 条返工 + A1–A6 在真实模型上复验(生成/篇幅闭环/场景重排/三档密度/双语写回/助手 P0-P2/面试行为),并修掉一处真实调用才暴露的审计缺口——双语翻译入口的 `llm.call` 此前记成 `task=unknown / input_scope=unspecified`,现登记为 `resume_translate / resume+variant`(新增用例 `test_bilingual_translate_entry_is_audited`)。
- **验收返工批次的测试与证据**:新增 / 更新用例覆盖 PDF 日期位置(`tests/test_pdf_exporter_style.py`)、自定义枚举纸面兜底(`tests/test_document_plan.py`)、灰色短字段组内移动(`EntryLayoutPanel.fields.test.tsx`)、chip 手势(`useChipExpandGesture.test.tsx`)、助手文档工具(`test_assistant_document_tools.py`);浏览器实机证据(截图 40+ 张)在 `deliverables/software-company/verification-2026-11-01/shots/`。

- **模板「完整预览」改成一张 A4 纸。** 大图只渲染 1 页(以前按 `page_limit` 排整份),纸按 contain 放进 20px 灰底画布、带 1px 描边与投影,弹窗正文完全不用滚:3 页简历以前要滚 4.6 屏才看得到纸下沿,现在纸 523×740、下沿距视口底 150px。
- **左栏页签吸顶。** 页签条 sticky 在列顶(360 / 640 两档滚到底时与列顶相差 0px,改前分别是 −2765px / −2581px),列顶内边距归零,切页签把整列 `scrollTop` 归零。
- **双语页签说清「现在看的是哪一版」。** 版本名从裸文本改成「正在查看:英文版」;没有译文的语言并进分段控件第三项「+ 未创建」(点它创建),按钮文案统一成「生成翻译」「预览英文版」,禁用项都带 title 说明为什么不可用。
- **双语字段对照提前到 480px 就并排**(原来 620px 才并排),窄档每个字段压到 3 行内、状态做成字段行右侧徽标;默认只列**有差异**的字段,一致的收成「另有 N 个字段一致」可展开。
- **导出弹窗的按钮固定在底部**,不再随内容滚动(内容再长也不用滚到底才点得到「导出」)。
- **文字级别状态行区分「单条」与「批量」。** 多选时单独一句「批量:N 个条目」,并给「只改当前条目」把目标收回聚焦的那一条。
- **历史每一步显示「改了什么」而不是「这一版长什么样」。** 按优先级取一条最有信息量的变更(板块增删/排序 → 条目增删/排序/隐藏/排版 → 字段增删/移动 → 字段值「各 N 处修改」→ 版式参数),姓名 · 条目数 · 页数挪到悬停提示。
- **选中联动按来源分档。** 纸面点条目 → 中栏切到对应页签并滚到条目卡(`scroll-margin-top: 8px`),右栏**不再**跟着二次滚动;结构树选中时给板块祖先链路一条更淡的 2px 色条;选中档(20% 底 / 2px 描边)与悬停档(14% / 1px)分两个 style id,悬停扫过不会把选中擦淡。
- **编辑与点击的几个热点做了降耗。** 条目卡一次遍历把字段分成 6 组(原来 6 个位置组各 filter 一遍、域计数再 filter 一遍);位置组与条目卡加 `memo`;每键全文档序列化从 3 次降到 1 次(保存侧 `serverSignature` 改 `useMemo`、历史写入判等改引用比较);纸面高亮只注入当前可见页,滚动时按帧补齐。

- **制作台有一批视觉与交互修正(2026-10-31 审计 34 条的批次 0/1)。** 结构树与条目排版面板此前引用了一族**从未定义过**的 CSS 变量(`--rf-color-*`),`var()` 找不到变量时整条声明失效,于是树没有外框、悬停没有底色、选中没有底、次要文字与正文同色——现在全部改到真实存在的 `--rf-*` 语义变量上,并补了 `--rf-fill-secondary/tertiary/quaternary` 与 `--rf-error-bg` 四个 token(浅色/深色同名同序,从正文色与容器底色 `color-mix` 派生)。新增守卫测试扫描全仓 CSS,无 fallback 的 `var()` 引用必须都有定义。
- **条目头选「上下 / 元信息在上」时,草稿与导出都会真的分两行。** 之前只把容器改成 `block`,而标题与元信息是行内 `<span>`,于是一条长元信息仍与标题挤在同一行(切了布局看不出变化)。现在两段各自成块,且长元信息允许折行(模板给的 `nowrap` 被更高优先级的选择器覆盖)。
- **精确预览(PDF)的项目符号改成实心圆。** 原来画的是「·」字形,大小取决于字体里那个字形——换字体点就变大变小,而且比草稿的 `disc` 小一圈。现在按字号画实心圆(直径 0.32×字号),草稿的 `list-style-type` 同步改成 `disc`;像素级对比显示差异**只落在符号那一列**,正文左边缘与折行位置没动。
- **检查简历的误报清掉四条。** ①「隐藏内容泄露」改按**整值**比对(原来按单字 token,隐藏「8 人」时正文里有个「8」就报 blocker);②PDF 已超页时不再同时报「缺 N 处正文」(只报超页);③制作台送检时带上浏览器实测的版面高度,「量不到版面」不再每次都出现;④字体本身包含这些字、只是 PDF 抽不出文本时,降为「需核对:纸面可能正常,请用精确预览确认」,不再冒充缺字。
- **「机器读正文」的黄色高亮不再断断续续。** 相邻且同类的差异合并成一个片段:「日更看板」是一个高亮块,而不是四个紧挨着的单字块;跨行、跨页不合并(避免在一页里找另一页的词)。
- **格式能力矩阵的每一格都能解释「为什么」。** 近似 / 不支持的格子新增悬停说明(PDF 斜体为什么是近似、Word 为什么只能近似三列技能、纯文本为什么不带样式、JSON 为什么全是「不支持」等 11 条原因),「支持」格不弹;文案按语言走界面词条,后端只下发稳定原因码。
- **来源核对的内容下拉不再显示内部标识。** 原来条目显示「上海财经大学 · ob_84c9…」、要点显示「… · it_512bcf…」,现在条目只显示名称,要点显示所属字段(字段名走前端字段目录翻译,后端不下发中文)。
- **导出核对卡:目标一行改叫「投递目标」并说明来源。** 悬停会说明它来自投递记录或进度关联、不是简历里的求职意向;版本一行在能取到改动时间时显示「第 4 版 · 10-28 17:40」(跨年补年份),取不到时仍回退 `#4`。
- **模板的角色基准补齐缩进与段后。** 面板里「缩进」在未覆盖时会显示模板的真实基准(经典 1.43 字、精简 1.30、学术 1.28 等,由解析模板 CSS 得到),不再一律显示 0。

- **「本份简历结构」顶栏三个折叠按钮合成一个。** 原来并排的「展开全部 / 收起全部 / 收起所有条目」改成一个开关:全展开时显示「收起全部」(向上箭头),点一次全收起;收起后变成「展开全部」(向下箭头),再点回全展开。按钮名字与图标都随状态切,读屏念出的也是当前动作;「只收条目」那一档(板块保持展开的中间态)取消,`structure.action.collapseEntries` 两条文案同步删除。
- **结构树行更紧凑,计数紧贴字段名。** 行高 26px → 24px、纵向内边距 2px → 1px、缩进 14px → 12px(4 层缩进少用 8px);行内按钮统一 20×20,不再把行撑回 26px;标签不再吃掉整行剩余宽度,分组行末尾的字段数紧跟标签(≤8px),行尾操作按钮仍贴右,长值照旧省略号、悬停不抖动。163 行的演示简历一屏能多看到 4~5 行。
- **基本信息归并成一个「基本信息 N」分组。** 页眉原来是「姓名行 / 姓名旁小字 / 求职意向 / 联系方式 / 照片」各占一行,现在树顶只有一行「基本信息」(括号里是字段数),展开后按纸面顺序平铺所有页眉字段;搜索改成匹配**看得见的字**(板块中文名、字段目录标签、位置名),所以搜「联系方式」能直接命中联系方式那一组里的每个字段——以前搜这个位置名什么也搜不到。
- **结构树点行不再额外滚动,多选改成 Shift 连选。** 点树里的行只展开并高亮,不再 `scrollIntoView`(点了第 5 行不会把整棵树滚一下);同时删掉条目行的勾选框——它是多选的唯一可见入口,却让行高与行宽都不稳定。连选改为 **Shift + 点击**(从上次点击的条目到这一条整段选中,行左侧出现 2px 竖色条)与 **Shift + ↑/↓**(键盘等价);不按 Shift 的普通点击清空已选;底部提示补一句「Shift + 点击可连选条目」。

- **「本份简历结构」补回板块顺序入口并新增一键收起条目。** 顶栏「顺序」按钮打开板块列表,逐项上移 / 下移即改文档 `sections` 顺序,纸面、导出与撤销同步;新增「收起所有条目」按钮,只折叠每个条目,板块标题和页眉字段保持可见(163 行演示数据可压到 38 行)。字段行的悬停提示改用字段目录中文名(如「邮箱」),不再露出 `email` 这类英文 key。
- **制作台字段 chip 支持二次点击收起。** 点开字段后再次点击同一个 chip 会收起下方编辑器,键盘 Esc 同义;不会因为 focus 先选中而出现“点开立刻收起”。
- **制作台字段 / 条目的隐藏与删除移到右键菜单。** 字段 chip 上不再裸露眼睛和删除按钮:右键字段(或 Shift+F10 / 菜单键)出现「隐藏字段 / 显示字段 / 移除字段」;条目标题行同样右键出现「隐藏条目 / 显示条目 / 移除条目」。右键字段不会打开字段编辑器;字段隐藏后再显示回到隐藏前的位置(位置随文档保存,旧数据回落正文后灰字)。展开后的字段编辑器不再重复放隐藏 / 删除按钮。(Q30 / Q31 / Q33)
- **制作台恢复「检查简历」「版本与变体」入口,并接回键盘撤销/恢复。** 动作条「检查」按未保存的正文与版式送检,问题清单里的修复动作写回当前草稿;「版本」支持命名版本列表 / 比较 / 恢复,恢复会生成新的当前版本并刷新制作台基线。⌘/Ctrl+Z 与 ⇧⌘/Ctrl+Z 一次一步,输入框内保留浏览器原生撤销。(Q01 / Q03)
- **制作台分页与局部排版重排。** 左栏「分页」只保留板块级开关、已设置断页汇总(可跳转 / 单个清除)与「恢复自然分页」,并给「切到精确预览」;条目级断页移到「本份简历结构」选中条目后设置。局部排版工具的条目排版新增「整条 / 条目头 / 正文 / 灰字」作用范围,文字级别改为状态行,格式刷文案与固化按钮归位;板块名统一为实习/工作经历、荣誉奖项、学术成果、科研项目、知识产权、专业技能、个人总结。(Q07–Q14)
- **行内板块(技能 / 语言)编辑器每条一行。** 每条可单独上移 / 下移 / 隐藏 / 移除,选中行内可继续添加字段;底部「添加专业技能 / 语言能力」按字段目录预填常用字段并自动选中新行。(Q06)
- **结构树与条目字段排版接字段目录。** 结构树、灰色短字段和条目下拉显示中文标签而不是原始英文 key;条目名统一由标题值或正文首句截断生成,结构树对已设置断页的条目显示标记。(Q08 / Q20)
- **来源视图接口只增字段(P5-1 / P5-2)。** `POST /api/provenance/sources` 接受可选的 `kind` / `external_ref` / `payload`(只传 `title` 的旧请求请求体与来源指纹逐字节不变);`GET /api/provenance/resumes/{id}` 响应新增 `unresolved_links`(默认空数组),旧客户端忽略即可。前端为满足 React Fast Refresh 与 hooks 规则把纯规则/hook 从组件文件拆出(ProviewContext、MaterialPickView、ProfileEntrySection、ResumeDetailModal 的辅助函数),并对 effect 内同步 setState 按官方「渲染期对齐」或微任务口径整改;前端 lint warning 由 34 条降到 0 条(未改 lint 配置、未整体 disable)。
- **第四阶段 T4:分析与写作链路完成 v2 原生读取。** ATS / 面试 / 风险 / 精简提示、版本与双语候选、来源链 / 事实锁 / 正文锁 / 批注 / 导出检查直接消费字段级文档;AI 修订与表达资产插入保留原有 ID、位置、隐藏和仅素材内容。移除后端与前端反向投影及无调用包装;保留 v1→v2 读时转换。API 外层字段保持,来源 / 锁 / 批注和问题定位的新路径使用稳定 item ID(旧请求路径可读);旧数据库行不迁移,身份失联时不会关联到同下标的其他内容。资料投影响应删除未消费的 `legacy_content` / `paper_content_path`,使用现有 `document_path` / `document_entry`;教育候选定位按实际文档分区解析,避免资料分区复数键无法定位。风险说明卡迁移为原生 Listy,消除废弃组件的控制台错误。
- **第四阶段 B2:成套导出与离线分享直接使用 v2 文档。** 成套导出指纹取完整文档,保留字段位置、条目布局与自建板块;导出闸门扫描同一份纸面可见内容,隐藏项不拦截,自建板块中的待补标记也会提示。新分享包的 `snapshot` / `resume_snapshot.json` 与现有 JSON 导出一样带 `version=2`,排除隐藏内容与照片 data URL;API 外层字段不变,历史 v1 分享快照重建时仅在内存转换、不改原记录。手写简历标题和岗位意向直接读取页眉,旧请求仍可转换。
- **第四阶段 T4/B4 与 T7e 收尾。** 分析、写作、来源链、事实锁、正文锁、批注、preflight、版本比较和双语候选直接消费 v2 节点;稳定 item 路径优先,旧数据库 path-only 行只读兼容且不改列。删除反向投影桥与投影过渡字段,资料候选使用 `document_path` / `document_entry`。表达资产插入自动选中新 item,字段格式编辑支持日期/枚举/链接/列表/作者属性,结构树多选与样式批量操作共用同一选择状态。验证包括 6962 条后端用例、87% 串行覆盖率、4314 条前端用例(595 files)、真实 AI 2/2、v2_check 155/155、smoke 13/13 与 8005 只读验收。
- **第四阶段收尾(M6)。** U7 结构树多选完成宿主接线:勾选框与「样式 → 局部排版工具」的批量目标是同一份选择状态,Shift 连选按可见条目顺序取区间,没有宿主回调时不渲染勾选框(不再出现死控件)。表达资产插入成功后自动选中新内容(工作台结构与编辑器同一选中状态)。修复富文本回填后的回读脚本异常(先前 contenteditable 回读会抛错、富文本填完永远判「未验证」),并补离线回归。canary 真机用例在独立临时浏览器上 89/89;交付门禁:后端 6962 passed / 0 failed、前端 4314 passed / 1 skipped(595 files)、shoot 16/16、v2_check 155/155。
- **写边界:v2 记录不再保存 v1 的 format_config.section_order。** 生成落库、手写创建、版式保存三条写入路径都不再写这个键——分区顺序的唯一真相是 document.sections;旧数据与旧请求里的 section_order 仍由 converter 在 v1→v2 转换时一次性消费(读兼容保留)。版式 PATCH 收到旧客户端的该键会剥离,不报错。
- **分页报告 / 精确预览 / 导出前置检查直接消费 v2 文档。** 删除 v2→v1→v2 的往返投影:分页报告与真实 PDF 走同一 build_resume_pdf 管线,条目级分页与自定义字段位置不再因折返 v1 而失真。
- **来源采纳写回改为 v2 原生、保留隐藏内容。** 采纳不再走「v2 → v1 分析视图 → 改字符串 → 转回 v2」:按 v2 身份锚点只改目标 item,隐藏项 / 仅素材字段 / 自建板块 / 自定义位置原样保留(实机发现采纳一次会把「背调联系人」这类不上纸面字段写没,已修复并加回归用例)。
- **PDF 页头联系方式恢复字段分隔。** 修复一处 v2 渲染遗留:PDF 页头里手机号/邮箱/城市等字段曾被整行拼接粘在一起(HTML / Word 正常),现在按字段用与历史一致的分隔空格排版。

- **分页控制改为 v2 文档布局。** 分区 / 条目断页写入 `SectionLayout` / `EntryLayout`(随简历保存,四出口同口径),不再走旧的 `format_config` 分页域;分页面板的「页缩略图 + 获取分页报告」已恢复,页数、末页占用与溢出都来自真实 PDF 同一条管线。
- **来源核对与双语工作台恢复完整入口。** 来源核对页签可查看来源链、关联覆盖与过期状态,并按选中 v2 锚点建立 / 解除关联、新建手工来源、看影响面与修订、采纳写回(带条件写与冲突提示)、增删事实锁;双语「在工作台查看这份译文」打开只读真实版式预览(关掉不影响主稿与脏值);**新建简历弹窗改用新编辑器**,v1 编辑器最后入口移除。
- **「条目字段排版」并入位置体系。** 原「短字段位置:正文前 / 正文后」与逐字段隐藏,改为「正文前灰字 / 正文后灰字 / 隐藏」这些位置;列数 / 对齐 / 列间距仍按分区或单条条目调,固定灰行与补充字段从此同一套规则。
- **「文档大纲」升级为「本份简历结构」。** 树按 页 / 栏 → 板块 → 条目 → 位置分组 展示,可拖拽排序、跨位置搬移、搜索,并与右侧画布联动;旧大纲的排序 / 复制 / 隐藏 / 锁定能力保留。
- **四个导出格式改由同一份布局结果渲染。** HTML / PDF / Word / Markdown 消费服务端同一份排版结果:同字段、同顺序不再各格式不同;PDF 的标题 / 元信息右对齐、灰字段列对齐、分页与溢出提示沿用原口径(**仍然不自动缩字号**)。JSON 导出改输出新文档结构(含每条的布局);导入旧备份时先转换。
- **AI 生成直接产出新结构。** 模型只写字段值与标签,纸面位置按字段默认 + 用户拖拽决定,不再把自定义字段拼成「标签:值」塞进正文;生成后仍可编辑、改位置。
- **新的制作台本批接入范围。** 中栏换成三区卡编辑器,左栏保留「排版参数 / 本份简历结构 / 历史」;本地可用的还有条目字段排版、页数上限(「排版参数 → 模板与版式 → 最大篇幅」)、溢出提示、自动一页、角色排版与旧简历转换提示。**双语版本、表达资产、来源核对、对象级排版、文字级别、格式刷、多选批量、制作台内的文档模式切换、分页控制(含页缩略图)** 9 项数据与接口都保留,但本批暂未接入新制作台界面,后续批次恢复入口;页数上限仍在「排版参数 → 结构 → 最大篇幅」,溢出提示在右栏画布与精确预览,自动一页在主预览「版面诊断」卡;**「新建简历」弹窗仍沿用旧编辑器**,保存后在制作台用三区卡编辑,下一批切换。
- **分析链路暂经只读兼容视图。** 双语对照、覆盖度、来源关联 / 事实锁、风险遮罩、历史差异等本批尚未迁移的分析算法,暂时通过「新结构 → 旧结构」的只读兼容视图取数;**渲染与编辑已全部走新结构**,后续批次迁完分析链路后删除这层桥。
- **「局部排版」记住上次展开了哪些段落类型。** 以前每次打开都固定展开「姓名」和「正文」,用户刚收起的段落下次又被弹开;现在展开/收起状态存在本机偏好里,重新打开制作台保持上次的样子(不写进简历、不影响导出)。
- **精确预览的项目符号圆点放大了。** PDF 里的圆点此前与正文同号,在精确预览里几乎看不清;现在圆点按正文的 1.35 倍绘制(只放大字形,不改变项目内容与文字提取),破折号/无符号不受影响。
- **我的资料:自定义字段的删除确认移到该字段行下方。** 以前确认条渲染在整张卡片底部,长列表里点完删除还要往下找;现在确认条紧跟被点的字段出现,含受影响条目数与回收站说明。
- **制作台调参数时预览保留缩放与滚动位置。** 此前左栏改一个字/一个滑杆,实时预览换 html 就把缩放复位成「一页完整」(用户实测:放大到 70% 调一个值就打回 58%),滚动位置也跟着丢。现在换文档不再复位缩放,并在刷新后把外层滚动位置写回(内容变短时夹到新的可滚上限);只有点「适应页面」才回到 fit。主预览与制作台共用同一套行为。
- **专业技能标签去掉「技能分类:」前缀。** 纸面只显示分类值(如「SQL(精通) · 编程语言」),HTML / PDF / Word / Markdown 四格式一致;编辑器与资料页的字段名不变。
- **荣誉奖项选「正文前」后改成条目式排版。** 原来「· 名称 · 时间」下面直接跟字段行,整体挤在左边;现在与教育/经历条目一致:名称在左、日期在右,补充字段行缩进对齐,说明作为条目正文要点。默认「正文后」的单行列表不变。
- **制作台勾着「草稿模式编辑」时,Ctrl+滚轮也会边缩放边滚动。** 那条链路在 iframe 里挂了**两个** wheel 监听:缩放的(`ResumePreview`)与转交纵向滚动的(`useClickToEditPreview`),后者不看 Ctrl、只管滚。现在后者在 Ctrl/Cmd 按下时让路,滚轮只缩放不滚动。

- **精确预览内部那两条黑粗滚动条关掉了。** 它们是 PDF 查看器自己画的(外层 CSS 管不到),只能用 iframe 的 `scrolling="no"` 关;横竖滚动统一由外层纸面滚动区承担。
- **按住 Ctrl 滚轮不再让预览上下滚动。** 以前靠 `zoomByWheel` 的返回值决定「缩放还是滚动」,一旦判断落空就变成滚动(用户实测:按住 Ctrl 反而上下移动)。现在 Ctrl/Cmd 单独一支:先 `preventDefault`(挡掉浏览器整页缩放),再无条件缩放。

- **精确预览下方/右侧那两条黑粗滚轮去掉了。** 它们是精确相框(`.exact-preview`)自己的滚动条:走浏览器默认样式(没被全局细滚动条覆盖),而且 PDF 层是 `pointer-events: none`,拖也拖不动。现在这一层完全不滚,横竖滚动统一交给外层纸面滚动区。

- **精确模式下 Ctrl+滚轮不再连带缩放整个页面。** 滚轮监听原来只挂在草稿视口上,而精确预览的 PDF 是它的兄弟节点——事件没人接,浏览器的默认动作就把**整个界面**一起缩了。现在监听挂到纸面滚动区,两种口径都走同一套缩放(已处理的事件按 `defaultPrevented` 判重,不会一次缩两格)。
- **精确预览缩小之后,纸下面不再留一大块灰。** 相框高度改回跟着缩放走(未缩放高度 × 比例):80% 时相框 1034 = 1292 × 0.8,不再停在整页高度留一片空白。

- **精确预览的「实际 N 页(上限 M 页)· 在新标签打开」并进了缩放条那一行。** 原来是纸面下方两条各自居中的横栏,中间还夹一档间距,读起来像两块无关的东西;现在是一条:状态 → 链接 → 缩放按钮 → 提示语(窄档照常换行,制作台右栏因此顺带省掉约 30px 高度)。状态行由面板通过 `onStatus` 交给宿主渲染,面板自己不再决定它放在哪。
- **精确预览的缩放终于既真的缩放、又保持清晰。** 前两版都不对:改 iframe 宽度时 Chrome 在 iframe 里**忽略** `#zoom=page-width`(那是 PDF.js 的写法,Chrome 只认数值百分比),纸恒定按 100% 画、只有边框在变;改用 `transform: scale()` 则是把已栅格化的位图拉大,100% 以上肉眼可见变糊。现在按"目标纸宽 ÷ 794px"算出百分比,用**新的 objectURL** 带上 `#zoom=<百分比>`(同一份字节,只改 hash 不会重新加载),查看器按该比例原生重绘——任意倍率都清晰。每次换档会重新载入一次查看器(同一份 PDF,一两百毫秒)。
- **精确预览的放大 / 缩小,缩放的终于是简历本身。** 旧实现改的是 iframe 宽度、指望 PDF 查看器按新宽度重排——它只在加载时适配一次,于是纸原样不动、动的只有两侧那圈灰边。现在 iframe 宽度恒等于相框宽度,缩放对整块 PDF 图层做 `transform: scale()`。缩放时**只让纸变大变小、相框不动**(缩小停在 fit 高度、下面留白变多;放大才让相框跟着长、由外层滚动接管)——与 Ctrl+滚轮在 PDF 查看器里的观感一致,按一下按钮不会整块预览跟着跳。
- **换页处不再整条搬走、空掉一大块。** 分页 CSS 原先对 `.entry` 整条 `break-inside: avoid`,装不下就把整条推到下一页(实测第一页空掉 331px、三页平均浪费 293px)。现在只禁止劈开"条目头(标题 + 日期那一行)"与列表项 / 表行,条目正文允许在页边界续排:同一份文档的空洞 331 / 154 / 130 / 110 / 184 → 53 / 53 / 226 / 99 / 163,平均浪费 30% → 11%,也更接近导出 PDF 的按行续排。
- **点字段就能直接加粗 / 斜体 / 加链接了。** 中栏点字段的标签或边框会把这一栏文字整段选中(点输入框内部仍然只放光标,不抢正常编辑);从右栏纸面点进来的字段同样"聚焦 + 全选",三个字符级按钮因此一进去就是可用的。另外**纸面上拖选文字不再被"点一下跳字段"抢走**:有选区时那一拍不接管,选完可以直接复制。
- **两栏的页数提示统一成同一号字。** 精确口径那条原本走 antd 默认 14px,与草稿口径的 12px 不一致;现在两边同字号、同内边距(10 / 14),并且不再像上一版那样把文字挤到贴边。
- **简历制作台右栏的头部矮了一截,纸面按"真实可见高度"缩放。** 分页报告横幅压成一行(空状态此前占两行)、工具条与专业视图工具条的行距各收一档、溢出提示改 12px 字号并收内边距,纸面上方那一块整体少掉约 55px。同时修掉「整页」档位的错算:原先用"根节点减草稿宿主"推可用高度,纸一旦比容器高,宿主反而更大、差值被夹成 0,预算退回整栏高度、纸更高,永远收敛不了——表现就是读数 88%、纸却被切掉一截还得滚;现在直接量纸面滚动区的真实高度,点「整页」就是一整页完整可见(窄到 55% 可读下限时仍按既有约定改为滚动)。
- **精确模式下不再出现两条页数提醒。** 草稿画布在精确模式里只留作测量来源,它的"按当前设置约需 N 页"与导出引擎算出来的数字本来就不一样(实测 6 页 / 5 页),两条并排挂着像在互相打脸;现在精确口径只显示服务端 PDF 那一条。
- **制作台右栏只保留三条悬停提示:「草稿模式编辑」「恢复默认布局」「辅助线」。** 缩放档位、翻页箭头、灰度校样、打印检查、工作区预设、照片裁剪与命令面板的 Tooltip 全部撤掉——按钮文字已经说清它们做什么;`aria-label` 一个没动,读屏与键盘行为不受影响。
- **设置页的删除动作不再裸露。** 「简历字体」里已下载的内置字体、以及「已保存的单价」记录,删除都收进行尾的「更多操作」菜单;行上只保留「设为当前」「重命名」这类常用动作,降低误点与视觉噪音。

- **「清理旧文件」支持更细的粒度。** 保留期可在 7 / 30 / 90 / 180 / 365 天之间切换;候选文件逐个可勾选(默认全选),并可用「全选 / 清空」调整;执行时只移动勾选的文件。后端清理接口接受 `paths` 白名单,预览与执行仍走同一份计划。
- **精简设置页说明文字并重排分组。** 数据集卡改成一句摘要 + 「查看说明」弹层(全局模型配置 / API Key / 字体的细节不再常驻);模型、助手、外观三页去掉与分组标题重复的页首长说明;自动备份的间隔说明缩短为一行,并保留「仅数据库快照」的范围提示。
- **软件更新卡改成「说明 + 操作 + 横向版本信息」。** 版本信息(更新方式 / 当前版本 / 最新版本)并排铺在操作按钮下方,填满宽度并消除左下角空白;窄档自动单列。
- **「外发与用量」的模型用量改成统计卡网格。** 调用次数、Token 总量、缓存命中 / 未命中、输出、费用估算各占一格(3 列),不再是一列文字右边空一大片;明细表保持原样。
- **「联网搜索」去掉「恢复为已保存值 / 保存搜索设置」两颗按钮。** 字段改动合并 400ms 后自动保存;来源为空或地址缺协议时只在卡片底部提示、不发请求,改对后自动重试。
- **专用浏览器里的注入面板也跟随界面语言了。** 「当前页面助手」悬浮球菜单与问答、焦点填充面板(填入 / 记住这条 / 换个资料 / 分组快速跳转 / 搜索占位 / 分组名与条数)、「记住这条」编辑器,以及 Python 侧回写到面板的状态说明(已填入、AI 正在识别、代选失败…)都按界面语言渲染;**只有注入脚本的文案跟着变,中文界面逐字不变**。语言取自开启填表会话那次请求(带数据集身份),因此切换界面语言后需要重新开启一次填表模式才会换语言。
- **填表面板的「内容」也英文化了,缩放与默认尺寸一并修好。** ①上一版只翻了界面文案,面板里的**板块名、分组名、字段名**属于清单数据仍是中文;现在按稳定 key 下发显示名,13 个系统板块、106 个内置字段、115 个网申目录字段,以及网申补充记录的 12 个分组名与 16 个字段名都随界面语言显示,跳转条、分组树、行内标签与面板标题一致。用户自建板块 / 自定义字段名与填写内容仍原样显示(那是用户数据),中文界面逐字不变。②拖右下角把手**只改宽高**:拖拽期间不再按焦点输入框重算落点,左上角锚死、宽高按视口钳制,松手把当前位置换算成偏移——此前宽度一变就会翻到输入框另一侧(表现为"左边框跟着动",继续拖整块跳走)。同时补上鼠标松手消息丢失时的兜底复位。③默认尺寸不再顶格:宽度上限 520(窄窗口退化为满宽减 32)、高度上限 `min(560px, 视口高 - 48)`,四周留出可见留白;用户拖大过之后仍按拖拽钳制的硬上限走。
- **首页「近期提醒」弹窗的倒计时胶囊跟随界面语言了。** 那一颗 Tag 此前直接渲染后端拼好的中文(`已逾期 1 天` / `今天` / `2 天后`),英文界面里只有它是中文。现在后端只下发结构化的 `days_until`(`/reminders/upcoming` 新增字段,负数=逾期天数),前端按界面语言组合:`2d overdue` / `Today` / `Tomorrow` / `In 3 days`;中文文案与改造前逐字一致,`due_label` 照旧下发以兼容旧前端,缺 `days_until` 时回退到它。
- **「记住这条」卡片与「网申资料」的自定义板块一起理顺了。** ①卡片被拖高后,编辑器(字段名 / 字段内容 / 保存位置清单)会跟着长、清单在自己内部滚动,不再固定在 `680px / 76vh` 而在底部留一大片空白;面板被拖到极矮时整块编辑器可滚,清单仍有可点高度的下限。②保存位置清单里原先并排出现两个「网申资料 · 自定义」——顶部那条其实是"按上方字段名新建"的合成行,它带着单选圈、还和真实自定义分组同名,读起来像"已经有一条叫这个名字的字段";现在这一行整条去掉,清单只列**已存在的保存目标**("新建"仍是默认行为:名字对不上任何目标时提示语会说"将按上方字段名新增")。③卡片收起(资料区折叠)后不再保留拖拽时的高度——没有内容区时那个高度只会变成按钮下方的一大片空白,重新展开会还原。
- **「记住这条」保存之后,卡片上的清单立刻刷新。** 面板里的「可能是这几个」是聚焦那一刻算好的快照,此前保存成功仍显示旧内容,要等用户换一个框才看得到刚记下的字段(「换个资料…」的完整清单也要等下次聚焦)。现在保存成功会先重新读取网申资料、重算这两份清单,再把带结果提示的面板推回页面——刚记的字段当场就在列表里。两条落库路径(逐框编辑器、界面里选目标后保存)都走这一处。③「网申资料」默认就带一张「自定义」卡片:查看态给出空态提示,编辑态可直接在卡片里添加自定义字段,空组不会写入任何数据;开「只看已填」或输入搜索词时该空卡片自动隐藏,不会干扰筛选结果。

- **英文界面补齐了几处仍显示中文的"系统文案",并把左上角品牌区排开。** 批量生成/导出卡的模板与字号、存储占用与清理类别、诊断包说明、资料方案默认名、主数据集与模型配置默认名、"我的资料"里 13 个系统板块的分区名与 106 个内置字段名、数据集来源(本机/导入/随备份包导入),现在都按界面语言显示;用户自建的分区、字段、方案、数据集与模板名一律原样显示(那是用户数据)。后端为此新增三个稳定标记:`source_code`(数据集来源)、`name_is_default`(方案 / 模型配置是否仍用内置默认名)与 `profileField.*` 文案表。左上角:英文品牌区把侧栏放宽到 252px、内边距收紧到 12px,应用名与副标题都改为单行省略——此前 `ResumeForge` + `v1.0.2` 挤不下,副标题折成两行、版本号贴到折叠按钮上。另修「投投」标语气泡直接显示 `touTou.tips.*` 原始 key 的问题(渲染时漏了 `t()`)。
- **「我的资料」的管理面改成顶部页签 + 一个总入口,基本信息也有字段设置。**
 ①「方案与板块」「板块与字段」弹层底部那排按钮(板块与字段 / 方案 / 关闭)整排去掉——它们是导航却长得像页脚动作;现在顶部两个页签 **方案 / 板块与字段** 直接切换,关闭用右上角 ×。②页头新增 **「资料设置」**:同一个弹层里统一管方案、板块与字段(板块行右侧齿轮进该板块的字段设置);**单独入口全部保留**(当前方案旁的「管理方案」、每张卡标题栏的齿轮)。③**基本信息卡片补上和其他板块一样的字段设置齿轮**:它的姓名 / 联系方式等默认字段同样可隐藏、可加自定义字段,此前只有它能从别处(板块与字段列表)进设置,卡片上没有入口。④资料页的**自建板块在标题旁标「自建」**(胶囊形浅底标记),和 13 个系统板块一眼分清。
- **侧栏收起后页头标题回到左侧。** 收起时页头多出一颗「展开侧栏」按钮,`space-between` 会把标题挤到中间;现在标题吃掉剩余空间,两种状态下都紧跟左侧按钮。另:侧栏收起/展开入口与宽度调整见上一条 Added。
- **简历中心的场景建议条与批量操作条之间补了 16px 间距。** 此前两块贴在一起,读起来像同一块内容;现在批量条与上方提示条之间有明确的呼吸区。

- **分页面板补齐了"看得见、清得掉"的四件事。** ①「已设置断页」现在同时列出板块与**条目**断页(此前条目断页设完就再也找不到、也无法清除),点 × 只清那一条;②没选中条目时面板会给一句指路,不再整块空白;③切回「自然填充」会先确认(会清掉 N 个板块的从新页开始),取消则一个都不清;④页缩略图空态与标题行直接提供「获取分页报告」,不用再去画布顶部找;报告取回后若又改过内容,画布横幅提示「报告基于上一版」。另:写分页设置时会顺手清理已删条目/板块的残留断页(只在正文已加载时执行,避免误清)。

- **岗位广场的「AI 分析适配度」改成「AI 对比选岗」,从"逐个出结论"变成"放在一起排序"。** 勾选 2–12 个岗位后,它先逐个核对匹配情况,再横向排出适合程度(第 1 名到第 N 名)并说明每个名次的原因与短板,回答"哪个更适合我、为什么";排序来自相对比较、不出现百分比,参考分仍只作展示、不参与投递准入。结果随批次保存,历史记录带「横向对比 / 匹配度分析」标签;未配模型时给本地降级结果(按参考分排序、理由只复述已有结论),单个岗位失败不吞掉整批。
- **AI 读取个人资料统一成「当前方案 + 未隐藏字段」。** 此前助手、岗位匹配与批量分析读的是"全部方案"的资料(`load_all_consumed_profile`),批量匹配那条甚至直接读编辑视图(隐藏字段也会被当成证据)。现在助手上下文、单岗匹配、AI 对比选岗、批量匹配、简历岗位建议与审阅预检统一走 `profile.ai_profile.load_ai_profile`:只读「我的资料」**当前选中方案**里**未隐藏**的字段。切换方案后新发起的任务自动跟随;已经生成的结论 / 简历各自冻结了快照,不受影响。

- **顶部数据集下拉给了最小宽度 280px。** 弹层宽度此前完全跟随触发框,名字短(「主数据」「体验示例」)时框只有 100 出头,条目全被截成「简历通…」「resume…」。现在取 `max(框宽, 280)`,CSS 也补了同值的 `min-width` 兜底(首帧、快捷键打开都不会缩回去)。
- **制作台两个下拉的宽度按"四个字"重排。** 「版式」的触发框与弹层统一成 104px(此前框 88 / 层 130,点开像跳出来一块);「联系方式换行」从 260px 收到 104px——空白比字多,现在刚好放下四个中文(已选标签由 `maxTagCount="responsive"` 收成 +N)。
- **简历预览界面移除「手动调整」与「草稿模式编辑」。** 详细编辑统一去简历制作台(那里有实时预览与板块导航)。覆盖提示里的「打开手动调整」改为打开制作台;预览不再就地渲染编辑器弹层,也不再因为没有第二个编辑器而禁用任何按钮。
- **版面诊断默认收起详情。** 标题行的状态标签与占用率一直可见("现在到底行不行"),结论正文与优化候选要点「展开详情」才出来,把首屏留给简历本身。

- **单价从"一个覆盖写的配置"升级成"可命名的记录",单位改每百万 token。** 新增 `usage_pricing_record` 表:每次保存新建一份(名称 + 三档价 + 币种),列表里可以重命名、切回任意一份、删除(删掉生效那条时剩下最新的自动顶上)。单位从每千改成每百万——按千计的数字小到 0.000002,估出来的金额没有参考价值;老配置(每千、单一输入价)在升级时自动换算成一条「旧版单价」记录,不会突然变成"未配置"。设置页的输入框也补上了可见标签与示例。

- **设置里的界面语言改成"点一下就换"的两张卡片,不再需要保存。** 此前是一个下拉 + 一个「保存」按钮:换了下拉还得再点一次才生效,用户没法确认第一次到底存没存。现在中文 / English 各是一张可点选的卡片(带一句"它影响什么"的说明,选中态有勾),点下去立即切换并落库,失败才给红字提示——没有保存按钮也就没有"点两次"的疑问。

- **设置里的「费用估算单价」改成看得懂的样式。** 此前是三个没有标签的输入框(只有 placeholder)+ 一个保存按钮,空着时连"哪一格是输入、哪一格是输出"都分不出来。现在每格上方有标签(输入 / 输出单价,按每千 token)、币种单独一格,下面给一句示例("输入 0.002、输出 0.006,一次 3 千输入 + 1 千输出的调用约 0.012 元")与一句边界说明(只影响费用估算展示,不改变实际计费)。

- **题库参考答案改成「查看 / 收起 + 重新生成」两个独立动作,并重排版面。** 此前收起参考答案后再点,会**重新生成一遍**(缓存被一起丢掉),既慢又多花一次模型调用;现在收起只关显示、缓存留着,再点就是查看,想换一版才点旁边的「重新生成」。版面从"一坨纯文字"改成三段式:正文按空行分段(保留段内换行)、答题要点成带勾的列表、话术单独成引用块;三张题型卡之间也拉开了间距。
- **点「准备好了」之后面试官才开口。** 此前一进房间第一题就已经显示在闸门上方,"还没说我准备好了、问题已经摆在那";现在闸门之前不显示任何对话,点了「准备好了 / 继续面试」才出现(中途离开又回来、已经答过题的会话仍照常显示历史)。

- **通用简历生成的「篇幅与版式」也只留 5 个参数。** 上一轮只收敛了岗位广场那条入口,通用简历生成仍铺开全部版式;现在两条入口一致——只给文档模式 / 模板 / 最大篇幅 / 简历语言 / 字体,版式预设、板块顺序、字号、行距、页边距、照片大小都留到生成之后去简历界面调(那里有实时预览)。

- **设置里的数据集改为按创建时间倒序(新导入的排最上)。** 此前按名称排序,看起来是乱的;现在当前激活的仍固定第一条,其余按 `created_at` 倒序,缺时间的历史数据集按「最旧」处理并用名称做稳定兜底(`None` 不参与比较)。
- **「自动」轮数改为尽量多聊几轮。** 最早可收尾轮数从 3 提到 8,提示词明确「真实面试通常要问到 8 轮以上,自动不等于尽快结束」;接口层再兜一层:模型在最早轮数之前就想收尾时忽略这次 `done`、把它当普通一轮继续,避免只问三四个问题就出报告。
- **岗位广场生成简历的「篇幅与版式」只留 5 个参数。** 从岗位广场进入时只显示文档模式、模板、最大篇幅、简历语言、字体;其余版式(版式预设、板块顺序、字号、行距、页边距、照片大小)留到生成之后去简历界面调。通用简历生成(无岗位)保持全部参数不变。

- **手写简历的逐条填入改为每个板块每条都有「从资料填入本条」,并移除板块顶部的「从我的资料追加一条」与来源面板的「追加本分区」。** 教育 / 实习工作 / 项目 / 校园 / 技能 / 荣誉、科研三栏、证书资质与语言能力的每条卡片头都带候选下拉,选中后只写这一条的空字段——手写过的内容一字不动、其它条目不受影响,填完焦点回到这一条的字段;需要新条目时用板块自己的「添加…」。候选按当前方案投影、与资料页同源,不会再出现整条重复的副本;同一套候选与空字段规则只有一份实现(`profileEntryFill.ts`),不再有按板块类型的第二份追加逻辑。

- **「板块顺序」列表只列出当前简历里真的有内容的板块。** 此前 12 个系统板块全列出来,空板块也占一行、还要为它排顺序;现在与纸面一致,只显示有内容的板块(内容为空的简历仍保留完整默认列表可看),上下移只交换可见板块在完整 `section_order` 里的位置,空板块的位置原样保留。自建板块固定排在系统板块之后、只读列出(沿渲染器口径,不参与上下移),列表为空时给出明确空态。

- **自建板块里的条目改为按序号命名。** 非内置板块的条目卡片不再显示「条目 1」这类名称,改为与系统板块一致的 1 / 2 / 3 序号;条目数据、保存与导出格式不变。
- **「文字级别」新增「作用范围」:可以只改选中的字段,而不是整条内容。** 此前给一条内容套用一级标题时,条目标题、正文与说明会一起变;现在工具条多了「整个条目 / 条目标题 / 正文 / 说明」四档(默认整个条目,行为与以前逐字节一致)。选字段范围后只写该角色的覆盖(新 `format_config.object_roles` 域,键 `<objectId>:<role>`),其它角色不受影响;「恢复跟随模板」也只清所选范围。后端 HTML / PDF / Word 都按「角色 < 对象 < 对象角色」优先级合并,旧简历没有该域时产物不变。

- **资料页字段按「短字段同排」重排,日期不再独占一行。** 年/月/日三个下拉收进一格里、「至今」紧随其后,字段网格从 1092 起三列:「姓名 / 性别 / 出生年月」「开始 / 结束」「技能名称 / 熟练度 / 获得日期」都落在同一行(多行文本仍整行);「添加字段」并入「自定义字段」段,填写规则里删掉「培训与课程怎么记」;职业场景下拉去掉页头齿轮与底部「编辑当前场景」,改为每条场景右侧的编辑入口(编辑不自作主张切换当前场景)。
- **「我的资料」把「组 / 大字段 / 小字段」统一改名为「方案 / 板块 / 字段」。** 四层一词一义:方案(一次投递用的板块组合)→ 板块(教育经历这类资料)→ 条目(一段经历)→ 字段(公司、岗位)。只改界面文案与规范,数据库列、接口字段(`section_key` / `group_id`)、类型与文件名一律不动;`docs/ui-copy-guide.md` 术语表 #57–#61 与 §2.7 同步改写,旧名列入禁止别名,避免文案回潮。
- **求职助手本地上下文改为总预算分配。** 岗位 / 简历 / 资料三类本地上下文合计上限 24,000 字符,按岗位 → 简历 → 资料顺序分配剩余额度;历史消息在 20 条之外再加 12,000 字符上限,避免单轮 prompt 随资料或对话量无界增长。
- **简历中心搜索改为边输边搜(350ms 防抖)。** 搜索框抽成独立组件,输入停顿自动提交、回车立即提交、清空立即提交;其它页面的搜索语义不变。

- **新建简历、简历预览「手动调整」与制作台「简历内容」跟当前组同步(F9)。** 新建简历(含从岗位进入的自行编写)的页签/大字段按当前组参与清单过滤并按其全局顺序排列:当前组有的分区即使没有内容也显示输入框,当前组没有的分区不再出现;正文里已有内容的分区永远保留(切组不会把已保存内容藏起来)。新建简历的初始内容改为当前组投影(岗位模式仍预填岗位名作为求职意向),不再空白起步;修改只作用于这份简历,不回写资料。资料页保存/切组后,已打开的新建简历会自动重新同步;若表单已有未保存编辑则改为内联提示「我的资料已更新」,由用户选择「用最新资料覆盖 / 保留当前编辑」,不静默覆盖。投影缓存的失效会通过 BroadcastChannel(不支持时退回 localStorage 的 storage 事件)广播到同源其它窗口,预览「手动调整」、制作台与网申页的资料来源/候选因此能跟着资料变化自动刷新;已保存简历仍遵守 R-34(切组不改正文)。

- **资料小字段的纸面呈现从「承载区追加」改为「条目字段 + 承载区后『标签:值』行」(F8/R-81)。** 这 20 个默认字段不再重复追加进承载区,避免同一信息在纸面上出现两次;自定义小字段的「标签:值」追加与 R-80 补回机制不变;旧简历没有这些字段,纸面与导出逐字不变。

- **生成 / 新建简历 / 网申三条链路改为按当前组取数;旧 `/api/profile` 读接口改从新模型投影。** 组过滤发生在构造候选的最前端(早于岗位相关性筛选、名额裁剪与预算压缩),未参与分区整键不出现、不占名额与预算,诊断也不再把「当前组不含」说成「资料中没有」;新建简历与「从资料填充」只出现参与分区的候选;网申简历侧同样按组裁剪,**网申专用项不受组过滤**。旧 `/api/profile` 的读取改为从新模型全量投影(响应形状不变),写入适配到新条目、不再触发旧六表整体重建;助手、岗位匹配、审阅等三链外继续读全量资料。旧资料表与补充事实表不删、不搬、不再被新链路读取,备份继续完整包含它们;资料页对「旧表有内容且新模型为空」的库展示重填说明(不阻断操作,录入首份新内容后消失)。

- **分页模块重排,并让"是否按模块断开"成为显式选择;精确模式首页大片留白可自查。** 制作台左栏「分页」重排为:草稿说明一行 → **板块分页方式**(自然填充 / 每板块新页,值为派生)→ **已设置断页芯片**(板块名 + × 逐个清除,旁边「恢复自然分页」一键清全部)→ 逐板块设置与条目级控件收进可折叠分组 → 页缩略图 → 续页信息 → 格式支持折叠。默认仍是"板块自然续排,只有设了「从新页开始」的板块才换页",但以前这个开关藏在"先选板块才出现"的控件里,用户看不到也清不掉——多页简历第一页出现大片留白时无法自查。现在已设断页与"N 个板块从新页开始"直接可见;全局写入/清除只作用于板块级断页,条目级断页与续页信息不动。

- **文档大纲拖动排序时,大纲树与右侧预览实时跟随;条目与要点用不同手柄。** 拖动条目/要点经过目标行时,左侧树立即按新顺序重排、右侧简历画布同时按新顺序重排(只预览,不写库、不进历史);松手才走既有结构命令提交为**一步**历史,拖到行外松开则完全取消。条目使用大号六点手柄、要点使用小号横线手柄(悬停说明分别写「移动…」),不再出现两者同样式、分不清拖的是哪一层的问题。拖动中只在新目标变化时更新,不逐帧重渲染整屏。

- **修复双语版本「生成翻译候选」在大简历上必然请求超时(15 秒预算)。** 根因两条叠加:前端按普通请求 15 秒预算等待;后端对每个可译字段**串行**各调一次模型(20–60+ 段简历远超 15 秒)。现在后端把可译段按原顺序切块(每块 ≤24 段且 ≤4000 字符)后**每块一次**调用、返回 JSON 严格校验条数与顺序,坏响应只回退该块逐段翻译(不整单失败),术语锁定 / 事实锁 / 正文锁与 blocked_fields 语义不变;前端新增 translate 请求预算(180 秒)并只用于这条链路。新增提示词随包校验,打包清单与 preflight 同步登记。

- **修复鼠标悬停时两个提示叠在一起(工作区预设、投递队列)。** 工作区预设把同一个"只切换面板可见性"提示同时挂在整组和每个选项上,悬停时两层 Tooltip 一起弹出;投递队列的"加入队列于…"外层提示包住了整个单元格,单元格里的「来源不支持」标签再套一层。两处都改为**一个触发区只留一个提示**(预设选项只用组级提示;队列的创建时间提示只挂在岗位标题上,来源提示留在标签上),并补了回归用例;同时用浏览器悬停审计扫过简历中心、资料页、设置页,未发现其它同类叠加。

- **制作台第一列可调宽,并可一键恢复默认列宽。** 「简历制作台」左栏右缘新增分隔条:参数栏可在 360–640px 之间拖动(默认 380),内容栏仍是 380–760px;拖动只写本机偏好(localStorage),不写进简历、不影响导出。顶部动作条新增「恢复默认列宽」,一键把两栏一起复位(双击任一分隔条同义;两栏/单栏形态没有可拖的栅格,因此不显示这颗按钮)。左栏六个页签从 antd Segmented 改成可换行的 chip 页签:380 宽下六项一行放得下,拖窄到 360 仍读得全,不再出现「排版… / 文档…」半截标签;←/→/Home/End 可在页签间切换,读屏按 tablist / tab 播报。

- **制作台与补充事实的几处排版收口。** 「局部排版」的属性行改成统一网格:数字类两行(控件行 + 读数 / 来源行)、其它类单行,标签列 72px,读数不再被 44px 固定宽压住(原先「2× · 约 1 已调整」那种值与来源胶囊重叠消失),六类角色的控件与来源胶囊落在同一批竖线上;「文档大纲」的树行改为单行优先(条目 28px / 要点 24px),搜索框与 ↑↓ 按钮同排、命中状态独立一行,并修复**大纲样式文件从未被 import**(样式写了却不生效)的缺陷;「表达资产」的筛选条改成自适应网格(窄栏 2×2、宽栏一行四个),「全部类型 / 全部语言 / 全部标签 / 最近使用」不再被截成「全 / 最」;「历史」的步骤行改用 antd 按钮并统一选中态,预览摘要从列表底部移到按钮下方、标成「当时:」信息条,不再与选中行重复;「按条件优化版面」的约束区在宽卡下铺满整行:复选框一行、最小页边距与目标篇幅并列,生成按钮跨两行贴右缘(用户反馈"右边太空");窄卡回落到单列;「补充事实」头部把「新建事实」提为主按钮,分组卡、条目行与编辑表单统一了间距/作者行/自定义字段小节。

- **修复进入「我的资料」时旧补充事实导致整页崩溃(Cannot read properties of undefined (reading 'map'))。**
  灵活字段上线前保存的事实 payload 没有 custom_fields;一旦某类事实配置了自定义字段,列表渲染会对
  它调 .map 并触发错误边界。后端读取旧 payload 时统一补 custom_fields: [],前端列表/编辑器的
  自定义字段读取也做数组兜底与旧记录归一化;补了后端与前端各一条回归用例。

- **生成日志记录上下文版本但不出场景正文。** 冻结场景快照时写结构化日志(transport / task_id / origin /
  scenario_id / revision / catalog_version / context_sha256 前缀),不记录场景名称、就业状态或任何正文;
  "任务创建与 runner.start 之间切数据集"的竞争窗口由请求租约 + 长任务租约交接封住,并补了真实
  HTTP 并发回归(`backend/tests/test_task_start_dataset_race.py`)。

- **生成请求、简历内容与数据库的兼容性说明(0061 / 0062 两个新迁移)。** 新增表 `career_scenario`(每数据集保存的场景)与
  `profile_fact`(六类补充事实,含 `deleted_at` 已登记回收站),并给 `resume_generate_task` / `resume_record` /
  `resume_version` / `resume_variant` 各加一列 nullable `context_snapshot`。只加表 / 只加列,不重写旧 content,旧行读 NULL
  即"未记录/未使用";旧请求(不传 `options.career_context` 与 `document_mode`)的候选资料、提示词与落库行为逐字节不变,
  旧备份仍可导入。新增 API:`/api/career-scenarios`(目录 / 列表 / 创建 / 条件更新与归档)、`/api/profile/facts`
  (列表 / 创建 / 条件更新 / 软删);简历详情响应新增只读 `context_snapshot`(旧记录为 null)。`GenerateOptions` 新增可选
  `document_mode` 与 `career_context`;普通简历模式仍按 1–3 页校验旧协议不变。

- **跨行业(非技术)岗位筛选不再把经历全部筛空。** 岗位领域未识别(护理、会计、教师、门店运营等)且
  没有任何条目直接命中时,候选资料会按资料顺序保留少量真实经历/项目/技能,并在生成说明里如实标注
  "交集较少、资料中未提供的岗位要求不会编写";识别出领域但资料确实没有证据时仍返回空候选(诚实筛选),
  技术岗既有筛选语义不变。回归见 `backend/tests/test_profile_matching_cross_industry.py`。

- **简历正文支持字符级斜体与链接,局部排版可以做多选批量与格式刷。** 中栏文本域工具区新增「斜体」「链接」按钮:选中文字即可加 `*斜体*` 或 `[文字](url)`;HTML 导出为可点击链接(仅 http/https/mailto,带 rel="noopener noreferrer";其它协议按普通文字保留),Word 为真斜体 + 「文字 (URL)」可见文本,PDF 斜体按近似(不倾斜但文本完整)+ 可见 URL,Markdown 保留标记、TXT 只留文字;未闭合标记与超长 URL 一律按字面文本保留。局部排版支持大纲**多选**后批量应用间距/段落规则,并提供**格式刷**(复制一条的排版覆盖到另一条)与重复条目提醒;每次批量/格式刷应用在统一历史里只算一步。

- **版面优化新增「内容精简」差异候选:要删要并先给你看。** 除按参数收紧版面外,还能生成最多三个**内容层**候选:合并重复条目、删除重复要点、合并短要点,以及在有岗位信号时精简低相关要点;每个候选列出具体改动与影响,先看差异、逐项确认才写入(写入进统一历史、可撤销);没有可行方案时明确说明原因,不偷偷缩字号。

- **来源核对链路补齐「建立关联」和「采纳写回」:不再只能看,不能动。** 左栏「来源」可打开来源列表(数据集内已有来源 + 手工新建来源),给选中内容建立来源关联并选择修订;来源更新后,可把来源正文**采纳写回**目标块:采纳前先展示/允许编辑要写入的文本,提交走条件写(版本过期 409、事实锁或正文锁命中 409 并逐条列出违规、来源修订已变 409 提示先刷新),成功后只改目标块并记一步历史。手工来源没有原文时界面明确说明、由用户粘贴后提交,不伪造来源正文。

- **岗位变体可以保存独立排版,并按变体直接导出。** 「版本/变体」里每个岗位变体可单独设置页数与字号等排版覆盖(NULL=继承当前简历),界面显示「继承 / 独立」并可一键恢复继承;导出与导出前核对可选择按某个变体取内容+排版快照(新增 export-input 端点与三处导出请求的 variant_id),不覆盖当前简历、不推进 revision。

- **表达资产支持岗位标签,编辑前先看差异。** 表达资产可设置岗位适用标签(≤20 个、每个 ≤32 字)并按标签筛选;编辑正文时先展示词级「修改前 → 修改后」差异再确认保存;插入前展示「当前文本 → 将插入文本」确认;后端保存返回更新前快照供本地 diff。

- **新增「导出交付包」:导出配置、成套导出与交付原件归档。** 已保存简历的导出弹窗可选格式与文件命名规则、配水印/脱敏并**预览真实产物**(与最终导出一致,标注这是配置预览不是编辑预览);配置可保存为数据集级预设;成套导出生成 zip + manifest(逐项哈希、单格式失败不影响其它格式、目标版本 409 保护);可选**归档交付原件**(默认关闭,归档进备份),归档列表可查看/删除,记录文件哈希与字体/渲染版本清单。修掉一个真实缺陷:内容寻址文件被多条归档共享时,删除一条不再误删另一条仍在引用的文件。

- **预览与分页:水印/脱敏配置可先预览真实 PDF;分页新增页缩略图,PDF/Word 补孤行控制。** 预览 PDF 请求支持 watermark/redact/redact_options 并走同一导出管线(不写副本、不冻结、不推进 revision),界面明确「配置预览」。分页面板新增**页缩略图**(实际页数、末页占用、溢出/手动分页/问题标记),不额外发请求;PDF 把板块标题与整段摘要整体预留、Word 用 keepNext/keepLines,避免标题或要点孤行落在页首/页尾,不产生意外空白页。

- **文档大纲支持搜索与「回到之前位置」,要点可单独隐藏/复制。** 大纲顶部可搜索姓名、板块、条目与要点(过滤高亮、↑↓ 选择、Enter 定位、Esc 清除),从纸面定位后可通过「回到之前位置」恢复树与画布滚动位置;要点级新增**隐藏**(正式导出不含、编辑态保留)与**复制**(生成新 point id,复制继承隐藏态,锁定内容时阻止);与后端 hidden_points 契约一致(按稳定 point id,不按下标猜)。

- **制作台检查新增两类规则,导出前核对改由服务端同快照做。** 内容层新增「关键链接格式/协议非法」(必须解决)与「导出未开脱敏但含联系方式」(需核对,仅导出用途);导出弹窗的「导出前核对」改调服务端 preflight(purpose=export),摘要来自与导出同一内容/排版快照(含变体、版本是否变化、隐藏条目数、文档模式),服务端不可用时回退本地并明确标注来源。

- **设置页备份与用量更诚实:自动备份禁用是显式状态,用量可看结束原因与费用估算。** 备份状态新增「自动备份已禁用(由部署配置控制)」状态(禁用时不再显示"受保护");用量账本按任务/模型/数据集汇总 token、耗时、**结束原因**与重试次数,费用只在用户配置单价(设置页或环境变量)时估算,未知 token 如实标未知,不保存提示词正文与密钥。

- **两个窗口同时改一份简历时,会先看见冲突,而不是被静默覆盖。** 保存现在带"我是基于哪一版改的":服务端用**一条**条件更新(`UPDATE ... WHERE id=? AND revision=?`)比较版本号,命中才写入并推进一版;不命中返回 409 并带回库里当前版本,**一个字段都不写**。制作台底部保存收敛成唯一状态机:连点只发一次(结束后仍有改动再补一次)、迟到回包丢弃、失败保留草稿与纸面加粗;冲突时顶部出现三选一——**用最新的**(覆盖本地并清草稿)、**比较**(逐字段列出差异)、**另存副本**(把本地这份另存为新简历,标题带「(副本)」),**不再允许"再点一次保存硬覆盖"**。旧接口形状一个字节没改:旧客户端仍可保存(推进一版),于是新客户端下一次保存会看到冲突——用户看得见"别处改过",而不是内容悄悄消失。

- **制作台左栏多了「文档大纲」:条目的排序、复制、隐藏、锁定都能在这里做。** 左侧「排版参数 / 文档大纲」可切换:个人信息、教育、经历、项目、技能、奖项分组展开,点一条会在纸面高亮对应范围并把表单定位过去。排序支持拖动与键盘(方向键移动、Home/End、Enter 确认、Esc 取消),**排序不换身份、复制生成全新身份**(含要点级 id),所以加粗、来源标注、局部排版在移动后不会串到别的条目。**隐藏**只是把条目从所有正式导出与机器文本里排除(HTML / PDF / Word / Markdown / TXT / 交付 JSON),编辑态与备份里原文完好;**锁定内容 / 锁定排版**是条目上的两个标记,AI 改写与自动排版会尊重它。取消/清除这些标记是**删键**,旧记录与既有交付指纹逐字节不变。

- **制作台多了「检查」:内容、排版、产物三层问题一次列清,每条都能定位。** 顶部动作条新增「检查」:内容层查待补标记(与导出闸门同口径,必须解决)、ATS 本地规则(high=需核对、medium/low=建议);排版层查超页/过稀/过密、缺字与粗体可用性(用 fontTools 读字体 cmap,不新增依赖);产物层**真的渲染一遍 PDF/Word 再读回来**,报告文本缺失、阅读顺序差异、隐藏内容泄漏与按上限截断。每条问题有对象定位、原因、影响与修复入口(聚焦字段 / 打开版面诊断 / 加页 / 精确预览 / 去设置下载字体),并带稳定 id 用于「复检后是否消失」。所有层结果来自**同一内容+参数快照**(响应带指纹),过时回包会被丢弃;产物层只在你点检查时才渲染,不随打字跑。格式能力矩阵由机器表统一下发(PDF 斜体按近似标注、链接不可点如实标不支持),前端不再另写判定。

- **选中一条成果就能核对它的来源;AI 改锁定的事实会被服务端拒绝。** 来源关联(`resume_source` / 修订 / 目标块)与事实锁是持久数据:选中有来源的条目可看到来源实体、修订摘要与指纹、当前改写与待核对项;旧记录没有可靠关联时明确显示「未建立来源关联」,不会用相似文本猜。来源更新后可反查受影响的目标块,逐条采纳或保持原稿,采纳过程可撤销。**事实锁**锁住姓名/机构/时间/成果数字/职责边界:AI 修订落库前服务端硬校验,冲突返回 `409 fact_lock_conflict` 并带逐条 `violations`(界面展示锁定值→当前值与原因),**一个字段都不写**;AI 迟到返回也不会覆盖新内容。**外发前预览**:调用模型前可看到目标服务、资料类别、附件类型、已排除字段与是否抓取网页正文,预览正文只在内存、不写 localStorage/日志;用量账本按任务/模型/数据集汇总 token、耗时、结束原因与重试次数,缺失 token 标「未知」、费用只在用户配置单价时估算,**不保存提示词正文、密钥或个人资料**。

- **简历可以保存命名版本和岗位变体;恢复历史会生成「新的当前版本」,不动已投递记录。** 版本是不可变快照(内容 + 渲染上下文 + 目标岗位 + 指纹,可冻结),列表可看名称、时间、岗位与变化摘要,支持并排比较(内容三态差异 + 排版顶层键差异)。恢复不是把旧内容覆盖回来,而是按旧快照生成一条**新的当前版本**(条件写推进 revision),历史版本与交付快照保持原样;过期恢复返回 409 不覆盖。岗位变体从同一份资料派生、各自独立内容与排版,来源更新后可**选择性同步**(只改勾选的变体与路径,保留岗位化改写)。版本不提供删除——历史不可变是它的语义。

- **制作台新增专业视图:整页/适宽/100% 缩放、页导航、辅助线、灰度校样、命令面板、工作区预设,以及非破坏照片裁剪。** 画布工具条可切整页/适宽/100%(逻辑像素受系统缩放影响,界面写明);页导航支持上一页/下一页、PageUp/PageDown 与当前页高亮。**辅助线**(页边距线、列边界、基线、段落边界)与**灰度校样**只存在于编辑覆盖层,**逐字节不进入任何导出产物**(有 HTML/PDF/Word 产物断言)。命令面板(⌘/Ctrl+Shift+P)可搜索高频操作并显示快捷键与最近使用,动作调用真实既有入口。工作区预设(内容撰写 / 精细排版 / 投递检查)只切面板可见性,偏好只写 localStorage、不进文档与备份。照片改为**非破坏裁剪**:原图不动,只保存裁剪参数(归一化矩形/比例/清晰度提示),HTML/PDF/Word 导出都按派生裁剪图渲染,移除照片后空位自动收回;裁剪参数保存在 `format_config.photo_crop`,删对象/换照片时随保存自动清理。

- **Word 版现在写你选的字体、模板的条目间距和正文/辅助色;学术模板的三列技能在 PDF 里也排成三列。** Word 导出不再写死「微软雅黑」,而是写入所选字体的**真实族名**(查看端缺字体时由 Word 替代,界面与矩阵标注为近似);条目间距取模板 `entry_gap`,正文色/辅助色与 HTML/PDF 共用同一份解析结果;项目符号缩进改为随模板 `li_indent`(修掉写死的 3.0mm)。学术模板(scholar)的三列技能在 PDF 里补齐网格表达,与 HTML 列数同源。另修一个真实缺陷:**同一进程内先渲染经典模板再渲染学术模板时,学术模板 PDF 的项目符号「•」会丢失**(字体子集只登记了「·」)——现在纸面固定字符一次性收全并有跨模板回归;该问题已用最小复现脚本取证后修复。格式能力矩阵(HTML/精确 PDF/下载 PDF/Word/Markdown/TXT/JSON × 14 项能力)给出支持/近似/不支持与理由,检查面板与文档共用同一张机器表。

- **制作台内所有会改内容的操作,现在共用一套撤销/恢复。** 左栏「历史」页签按步列出每次改动(摘要预览),顶部/键盘 ⌘/Ctrl+Z 与 ⇧⌘/Ctrl+Z 前后翻:一次动作=一步,连续打字和拖滑杆按 300ms 合并(不会一个字一步),大纲排序/复制/隐藏这类结构命令单独成步;撤销/恢复会**同时**回写中栏正文(含条目身份)和右栏版式,换记录或外部正文变化会清栈,保存失败不清栈(保留可重试的改动)。输入框内的原生撤销优先,制作台内关闭了表单层快捷键避免一次按键退两步。

- **局部排版:可以只改某个角色、某类对象或某一页,不必全局动。** 「局部排版」按六类角色(姓名/小节标题/条目标题/正文/辅助文字/标签)与 12 项对象属性(字号/行距/字距/颜色/加粗/缩进等)分层覆盖,支持角色级与对象级;字号比例是**相对模板基准的系数**(存系数、界面显示系数×基准),恢复继承会删除该角色/对象的覆盖回到模板默认。HTML、PDF、Word 三个渲染器消费**同一份解析结果**(可复现:前端测试直接读后端 schema 逐项对拍),草稿与导出不会各排一套;局部规则随简历保存、进撤回/备份。尚未提供字符级斜体/链接入口(PDF 斜体按近似标注、链接不可点如实标不支持)。

- **分页控制:板块与条目可以「从新页开始」,标题跟随、条目允许劈开、长链接自动断行都有开关。** 左栏「分页」按板块设置起始页与标题跟随,选中条目后可对单条设断页;PDF 只在当前页已有内容时才换页(连续强制分页不会吐出空白页,测量模式不换页),Word 用 `page_break_before`/`keep_with_next`/`keep_together`,HTML 仅在打印时生效。PDF/Word 可开启续页页眉页脚(重复姓名 +「第 N 页 / 共 M 页」;上边距过小时不画姓名并给提示);超宽链接按字符断行。**分页报告**用真实渲染指出「按当前设置约需 N 页(上限 M 页)/ 第一条未显示的内容」,可一键定位到条目;草稿按 D7 不显示真实分页、不画假线,Word 页数为估算。

- **科研简历:论文发表 / 科研项目 / 专利三个栏目可以完整写、完整导出。** 内容模型支持论文(标题/作者顺序/本人/通讯作者/期刊会议/年份/状态/DOI/链接/卷期页码)、科研项目(名称/角色/单位/起止/经费/描述/成果)、专利(名称/发明人/专利号/国家/状态/申请授权日期/链接);状态枚举含「在投(under_review)」,不会被当成已发表。新增 **长 CV 模式**:顶部「简历语言」旁可切普通/长 CV,普通 1–3 页、长 CV 最多 10 页,切换页数超限会被**明确夹紧并提示**(不会静默缩字号);正文超 20 万字符返回 400 级中文原因。三个栏目在 HTML/Markdown/TXT/PDF/Word/JSON 六种输出里都在(作者本人加粗、DOI/专利号落纸),隐藏条目按统一规则排除;ATS、隐私脱敏、完整性提示与能力矩阵同步支持。

- **双语版本与术语表:可以给同一份简历做英文版,但绝不覆盖当前语言正文。** 每个语言变体独立内容与排版;「双语版本」做字段对照、逐项翻译候选(**候选不落库**,关闭即丢)、采纳只写入**该语言变体**(返回变体详情,当前正文不变,采纳可用旧文本再采纳一次撤销);事实锁字段与锁定术语保留原文并标 `blocked_fields`,不可勾选采纳。术语表是数据集级共享:新增/批量导入/软删进回收站第 18 类可恢复;锁定术语必须在译文中逐字出现。目标语言目前 zh-CN/en-US。

- **表达资产库:把常写的好句子存起来,插入时生成新条目而不是复用一个 id。** 左栏「表达资产」可按类型/语言/标签/关键词搜索,支持收藏(从当前内容抽取)、新建、编辑、软删 + 5 秒撤销;**插入**会生成全新 `object_id`/`point_id` 并走条件写(版本过期 409 不写库),成功后只刷新对应内容并记一步历史;有未保存草稿时插入禁用并说明原因(先保存再插入,避免覆盖本地编辑)。

- **离线分享包可以带对象批注:别人填完回传,你导入后逐条处理。** 「分享」可生成评审包(zip 含 `annotations.json` v1 模板与说明),对方离线填写后回传导入:严格校验格式/大小/token/快照指纹,超上限或格式不对整批拒收;重复导入按键 `(package, external_id)` 去重,不覆盖已处理状态;锚点失联的对象标 `stale`,只能显式重挂到现有对象或关闭,**不会自动回退到父级**。状态机 open/resolved/dismissed 有合法迁移(非法迁移 409);**批注正文不进任何投递产物**(HTML/MD/TXT/JSON/PDF/Word 均有断言),只读分享包不能导入。

- **导出交付包:预览和下载用同一份快照,能保存导出配置、成套导出并归档原件。** 制作台「导出」打开「导出交付包」:可选目标(按岗位/版本/日期生成安全文件名)、逐项核对(岗位/文件名/联系方式/版本)后成套导出(PDF/Word/Markdown/TXT/JSON 一次打包,附带 manifest 与逐项哈希;某一格式失败只进失败清单,其余照出)。**下载按当前草稿内容与版式渲染**(同一 content_override 与预览同快照,不落库、不冻结、不触发 AI);版本过期走条件写 409 并提示刷新核对,不自动覆盖。可选「保留原件」:默认关闭,开启后原件归档到独立资产目录并进备份,带文件 SHA-256 与模板/字体/渲染版本清单,删除有路径与条数限制;重排产物与原件明确区分。

- **试模板可以直接用当前内容(含未保存草稿)实测页数与溢出。** 图库「试用」两侧用当前简历内容真实渲染 PDF 量页数并标溢出,未采纳前不改任何数据;「采用模板默认」会换成该模板并清空局部覆盖(模板默认版式),「保留当前覆盖」则保留已有局部规则。

- **版面优化:按「不要缩小字号」的约束给出可选方案,而不是偷偷压缩。** 版面诊断卡下方新增「按条件优化」:候选来自既有排版阶梯,遵守四条硬约束(不动字号、最小边距、条目完整声明、目标页数),最多 3 个候选并列出差异与可读性分级;应用会整体回写模板/版式/页数与字号并记一步历史,失败/409 不标记已应用。

- **新增第 6 套内置模板「学术」(`scholar`)。** 按一份学术简历的参考版式移植:**纯黑白、没有彩色色块**;姓名大而重、右上角方形照片(没照片时不留空洞);分区标题加粗靠左、下面压一条整宽细线(不是色块也不是左侧竖条);首段信息区是浅灰底的一整块;条目头左侧「机构 · 角色」加粗、右侧日期右对齐在同一条基线上;技能 / 证书这类短条目走**三列并排**的项目符号列表。整体行距紧、一页能装更多内容,适合科研、学术与技术岗的"信息密度优先"。选择模板从 5 套变 6 套(经典 / 现代 / 精简 / 优雅 / 极简 / 学术);`RESUME_TEMPLATES` 里仍在的 technical / editorial / split 解析与渲染一个字节都没变,旧简历外观不受影响。

- **简历列表的右键菜单多了一个「复制」子菜单。** 里面是**复制标题 / 复制目标岗位 / 复制公司名 / 复制备注**,以及**复制整行为一行**(Tab 分隔,粘进 Excel 或飞书表格直接对齐成列)。空字段不出现在菜单里——备注为空还给一条「复制备注」,点了只得到空字符串。这个子菜单只在整行右键里,行内按钮不变:它是一次性的取数动作,不该占列表宽度。同一批把行菜单项类型补上**一层子菜单**支持(`RowActionItem.children`),以后别的列表要把一组同类动作收进子菜单不用再改菜单实现。

- **内置思源黑体:按需下载,不占安装包体积。** 设置 → 应用 → 简历字体新增一行内置字体:**约 32MB**,点「下载」联网拉取,界面上有进度(`下载中… 已下 / 总量`)与失败重试;下载完**自动设为当前字体一次**,之后可以随时在列表里重新选中它,也可以切回系统字体。来源与授权写在按钮的悬停说明里(Adobe 发布,SIL Open Font License 1.1,个人与商业文档均可免费使用)。没用它的人不用付这 32MB。**还没下载时**,每份简历的「字体」下拉里它是灰的并标着「未下载」,第二行指出下载路径——选中一个磁盘上不存在的文件只会得到"没有字形",不如直接说清。接口是 `GET/POST /settings/fonts/builtin`、`GET /settings/fonts/builtin/download`(轮询)、`DELETE /settings/fonts/builtin`;`GET /settings/fonts` 的条目新增 `builtin` / `downloaded` 两个可选字段(老前端读不到也不受影响)。

- **「查看大图」改名为「简历制作台」,并升级成三栏:参数 · 内容 · 版面同屏。** 以前它是"放大看一遍、顺手在旁边拖两个滑杆"的全屏弹窗:想改一句措辞要退出去开「手动调整」,改完再进来重新找位置。现在左栏是模板与版式、排版参数,中栏是简历内容编辑器,右栏是按原始尺寸显示的版面——**改一句措辞 → 点「保存内容」→ 右栏立刻重排,窗口不关**,接着还能调参数。中栏与「手动调整」是同一份表单实现(只是不含弹窗壳),所以"能不能改照片、有没有写作增强"不会在两处出现两种答案;中栏还有没保存的改动时,底部「手动调整」会禁用并说明原因,避免两个编辑器同时改一份稿子。三栏各自滚动,中栏可折叠(折叠后就是原来的两栏观感);窄于 1280px 中栏收进真正的抽屉(顶部「编辑内容」打开),窄于 1000px 左栏也收进「排版参数」抽屉——内容编辑在任何宽度下都可达,见下面「多分辨率与系统缩放适配」一条。**改名只动界面文案**:组件名、文件名、路由与缓存键一律没变(模板画廊里那个「查看大图」是另一个功能,保持原名)。

- **排版参数补上四个"层次与间距"、四个颜色的旋钮。** 「简历制作台」左栏新增两组:**层次与间距**(区块间距、条目间距、姓名大小、小节标题大小)与**外观**(强调色、正文颜色、辅助文字颜色、分隔线颜色)。三个新系数(`name_scale` / `section_title_scale` / `entry_gap`)与照片大小完全同构:乘在模板自己的基准尺寸上、`1.0` 表示没设过、松手静置 120ms 提交一次,拖动中走 `:root` 探针即时反馈。**同源范围要分开说**:姓名大小与小节标题大小在 HTML(8 套模板)、直出 PDF、Word 三处同源(docx 走 `resolve_layout()` 给的 `name_ratio` / `section_title_ratio`);条目间距只影响 HTML 与直出 PDF,**Word 侧沿用其自身的条目排版模型、不读这个系数**——这是既有差异,与本次改动无关。**默认参数下的导出字节逐字节不变**(系数等于 1 时一个字符都不写)。恢复默认是**删键**而不是写回默认值,以后模板基准调整时这些简历会跟着变。这两组只在制作台里出现:生成前的配置页与预览工具栏仍是原来那一组,宽度放不下。

- **表单弹窗现在会替你留住没填完的内容(草稿只在本标签页)。** 台账、投递进度、简历编辑器、LLM 配置这四个长表单接入通用草稿:改动停手 450ms 就写进 `sessionStorage`(关标签、刷新都会立即落盘),下次打开同一张表单时顶部出一条「找到了上次没提交的内容」,**必须点「恢复这份草稿」才应用**——自动填充会把"上次没填完的东西"变成"这次打开就是它"。存储上限 32KB、超过 24 小时的草稿不再恢复;字段名命中 `apiKey` / `token` / `password` / `secret` 的一律在写入前剔除,图片类字段整块丢弃(草稿只存文本)。**刻意不跨重启**:正文只进 `sessionStorage`,不进 `localStorage`。草稿槽按"哪一条记录"分——同一条记录的编辑与新建不共用,从岗位页进入的「自行编写简历」再按岗位隔离,否则 A 岗放弃的内容会出现在 B 岗的表单上并被一键填入。

- **页面顶部多了一条公告条(更新可用 / 更新包已下载)。** 以前"有新版本"只写在设置页的更新卡片里,不主动逛设置页就永远看不到。公告条常驻在标题栏下方、黄底或红底、**不会自己消失**,点「去更新」跳设置页、点 × 关掉这一条(版本号变了会再出现)。它与页内提示的分工写进了 `AGENTS.md`:影响全站 + 需要你处理 + 可以先不理,三条同时满足才用公告条。

  觉得它烦可以点「不再提醒」——但**这个开关是可撤销的**:设置页新常驻一张「公告提醒」卡片,列出被静音的来源,点「恢复提醒」就回来(在设置页当场看不到是设计使然,那里的更新卡片正在说同一件事;回到任意其它页面立刻出现)。静音按来源记,不随版本号自动失效——用户按它就是为了"别再烦我",一个版本一变就复活的开关是假开关。

- **删除可以先做,再反悔。** 岗位删除与批量删除不再先弹一次确认:删除是软删(进回收站、16 类都能恢复),确认框的成本是"每次多一次点击 + 一次判断",收益在"可撤销"面前等于零。改成直接执行 + 底部 Toast 里给 5 秒「撤销」,撤销调的是回收站同一个恢复接口,不是"取消请求"。**不可逆的操作仍然要确认**:清空回收站、彻底删除、批量超过 100 条。

- **照片大小可以单独调了(60%–160%)。** 生成前与预览的版式参数里新增「照片大小」滑杆:步进 2%,读数写百分比(`115%`),落库是**系数** `format_config.photo_scale`。照片的绝对尺寸仍由模板自己决定(8 个模板各不相同),这里只乘一个比例,所以宽高同倍缩放、**宽高比与取景裁剪一像素都不变**,只有画框变大变小。HTML 走 `--photo-scale` 变量,PDF / Word 由 `resolve_layout` 乘进 `photo_width_ratio / photo_height_ratio`,三条产物同源。拖动中靠 `:root` 探针即时反馈、静置 120ms 提交一次(与行距 / 页边距同一套防抖)。

  **没有照片的简历上滑杆直接禁用并写明原因**;生成前配置阶段拿不到内容时不禁用,只是没有即时预览。不设「过大警告」:`1.6×` 时经典模板照片约 37×48mm、页头仍宽裕,写死一条警告只会变成噪音,而「装不下就报溢出」的既有口径已经兜住了真正的后果(参数即呈现,不自动缩照片)。

- **页边距可以四边分开调(上 / 右 / 下 / 左,0.1mm 步进,下限一路放到 0)。** 总滑杆照旧(一个值管四边),下面新增一行「分边调整页边距」:**默认收起**,展开后是一张卡片,四条支路各有 ± 与滑杆,两列排开(上/右、下/左)。**只有真的和统一值不同的那一条边**才显示 ↺(点它或双击边名回到统一值)——"哪条边被单独调过"一眼可辨,不用每行挂一个「随模板」胶囊。下限从 8mm 放开到 0:要让正文铺满整张纸时,8mm 是一道没有道理的墙;上限 26mm 不变。两种写入语义**互不越界**:**拖总滑杆只写 `page_padding` 并清掉四条支路**(回到「统一」);拖某一条支路则**写全四条**、且不留下并存的 `page_padding`(否则下一次解析会把没动的边拉回去)。总滑杆因此有三种态:统一(一个读数)、**自定义**(四边不全等,读数停在「把整体拉回来」最短的那条边)、随模板(双击标题整组清掉)。

  **入口不再挂在页边距那一行里**:那一行在窄窗口本来就贴着左右边缘,再多一颗 30px 的图标按钮,尾部的读数与「随模板」就会被压成 0 宽、胶囊滑到按钮底下(2026-10-22 用户截图)。现在入口是自己一行的折叠标题,行内只剩「标签 + ± + 滑杆 + 读数 + 来源」,放不下时尾部折到第二行,不再互相盖住。

  键名与区间与后端 `resume_layout_overrides.py` 逐字一致:前端**夹紧**(滑杆不该产出非法值)、后端白名单**丢弃**(非法输入不进 CSS / 渲染),是两条既有契约;「支路不足四条又没有总」则整组作废——这条与后端同一判定,是 HTML(不知道模板默认值)与 PDF(知道)在同一输入下不出现分歧的唯一保证。

- **桌面壳启动自检:先问"我能不能写",再决定往哪写。** 新增 `desktop/lib/preflight.js`,在主窗口出现前探测运行期目录与日志目录,结论(可用/降级、路径、原因)落进主日志;降级时给一次非阻断提示并提供"打开日志目录"。同一套路径解析(`desktop/lib/shell-paths.js`)同时服务于壳自身的日志与后端日志,避免"自检说能写、实际写的是另一处"。

- **更新原子化能力(版本目录 + 指针 + 回滚),已就绪但尚未接线。** 新增 `desktop/lib/update-apply.js`:新版本解压到 `runtime/versions/<ver>`,自检通过才把 `current` 指针原子切过去;校验失败连目录一起清掉、`current` 保持原样;`rollback()` 回到上一版,`pruneVersions()` 只留最近两版。版本号只接受 semver,`..` 与路径分隔符一律拒绝。**当前发布仍走 NSIS 覆盖安装**,这条能力要等 1.1 把"整包替换"接进安装流程后才在生产生效。

- **代码签名与公证脚手架(可选,缺证书不失败)。** 新增 `scripts/sign-macos.sh`(`codesign --options runtime --timestamp` → `notarytool submit --wait` → `stapler staple` → `spctl` 断言)与 `scripts/Sign-Windows.ps1`(`signtool` + RFC 3161 时间戳)。四个 macOS 变量或 Windows 证书变量未配置时,两者都只打印一行"未配置签名,产物为未签名包"并以 0 退出,构建与发布流水线照旧;`release-assets.yml` 已将对应 secrets 透传给构建步骤,配好证书后无需改工作流。

- **简历语言:让一份简历的纸面文案可以是英文。** 简历预览的版式浮层(换模板 / 页数 / 字号那一组)新增「简历语言」:中文 / English。它只改**纸面固定文案**——七个正文小节标题(个人总结 / 教育经历 / 实习/工作经历 / 校园经历 / 项目经历 / 专业技能 / 荣誉奖项 ↔ Summary / Education / Experience / Campus Activities / Projects / Skills / Honors & Awards)、字段前缀(求职意向 / 绩点/排名 / 核心课程 / 技术栈/工具 ↔ Objective / GPA / Rank / Core Courses / Tech Stack)、列表分隔符(`、` ↔ `, `)、技能等级括号(全角 ↔ 半角)与照片 alt;**正文内容一个字都不会被翻译**,你写的句子原样输出。

  语言是**这份简历自己的属性**(落在 `resume_record.language`,迁移 `0049_resume_language`),不是全局设置、也不是版式模板的一部分:同一个人可以同时有一份中文版和一份英文版,各自记住自己的语言。旧简历全部按 `zh-CN` 读,**导出与预览逐字不变**(黄金基线 15 个既有 case 的页数 / 文本长度 / 文本指纹零变化)。

  五条产物(服务端 PDF、Word、HTML、Markdown、纯文本)与「精确」预览同步消费这个值,所以选英文之后不会再出现「Education + 绩点/排名:3.8」这种一半中文的产物。分隔符与括号也一并跟随,英文简历里印的是 `Python, MySQL` 与 `Python (Expert)` 而不是 `Python、MySQL`。

  **兼容性与已知限制**见本节末尾。

- **新增「导出为图片(PNG)」。** 面向"投递平台只收图片"的场景:把**精确 PDF 的同一份字节**逐页光栅化(A4 300dpi ≈ 2480px 宽),单页得到 `.png`、多页打成一个 zip。图片是**补充格式**,不替代 PDF——ATS 只解析矢量文本,图片里的字它一个也读不到,导出面板里写明了这条边界。实现放在前端(pdfjs + jszip,动态加载,详情页首屏 chunk 从 575KB 降到 142KB),后端导出格式枚举与管线一行没动,因此也不会为它引入原生依赖。

- **简历预览多了「精确」口径:显示的就是导出产物本身。** 工具栏第一行新增「草稿 / 精确」切换。草稿模式仍是原来的 CSS 多列实时预览(改一下立刻看到,可点选编辑);精确模式走新的 `POST /api/resumes/{id}/preview-pdf`,由**导出同一条管线**排出 PDF、交给浏览器原生查看器显示,并顺手带上「实际 N 页 / 上限 M 页」与超页提示。两条口径的差别不是"新旧版本":此前的预览用 CSS 多列切分连续流、导出 PDF 由 `pdf_exporter` 另排一遍,两套引擎的断点规则不同,"预览 2 页、导出 3 页"是结构性的,靠调 CSS 只能更像、不可能相同。精确预览复用同一条管线与同一组参数(页数 / 字号 / 版式都是**当前草稿值**,未保存也生效),因此与点「导出 PDF」下载到的是同一份产物。三个"不是导出"的边界:不写「生成内容保存位置」、不冻结交付快照、`inline` + `no-store`;正文里还有【待补】时也能看(预览的对象本来就是草稿)。测试钉住的是逐字逐点的排版一致(`_text_runs` 序列相等),不是字节——fpdf2 每次都写 `/CreationDate` 与 `/ID`。

- **备份包现在带上自定义字体(格式 6)。** 之前 `data/fonts/` 里的字体从不进包:换台电脑导入备份后,简历里引用的那款字体就不在了,导出的 PDF 会悄悄换成系统字体——这类问题通常要到下一次投递后才被发现。现在导出会在包内写入 `global_assets.json` + `fonts/`(逐文件 size/SHA-256),导入时恢复到本机字体目录;**默认不覆盖本机同名文件**(同名计入跳过数),`fonts/names.json` 只补缺失的显示名,`active.json` 只在本机还没有选择时采用。字体恢复失败不回滚数据集导入,界面单独提示"字体未完全恢复"。字体目录为空时格式号仍是 5,不虚升。

- **导出会明确告诉你"还有哪些东西没进包"。** 数据目录里出现既不在备份内、也不属于已知运行产物(登录态、缓存、日志、备份产物)的顶层项时,包内清单多出 `uncovered` 段,导出成功的提示与响应头(`X-Backup-Uncovered` / `X-Backup-Uncovered-Bytes`)都会给出数量与体积;达到扫描上限时标明"至少 N 项"。只 `stat` 不读内容、最多 3 层 / 5000 个文件,不会把导出拖成大目录遍历。**可见但不阻断**——未覆盖不等于导出失败。

- **「写作增强」的入口从编辑器页签搬到了右键菜单。** 以前要"先在简历里选中文字 → 复制 → 切到「写作增强」页签 → 粘进去"。现在**选中一段文字直接右键**,菜单里就有「写作增强」(排在「AI 润色」前面),弹窗打开时那段文字已经填好。**结果是回填**:生成后直接写回你刚选中的那一段(`replaceSelection` 按捕获时的偏移还原),不满意关掉即可、原文一字不动;只有右键落在不可编辑的位置(没有可写目标)时,动作才退化成「复制」。写不进去(选区已失效、超出 `maxLength`)会如实提示,不谎报"已回填"。这一项只在**打开了某份简历**时出现:四个动作都要 `resume_id`,在岗位页、资料页右键时摆出来也点不动,所以干脆不显示(当前简历由详情弹窗登记进 `features/resume-writing/activeResume`)。编辑器里那个页签同时下线,不留空壳。

- **导出的 Word 里也有色块了(modern 的绿色页头与标题带、technical 的实心标题块、classic 的标题左竖条)。** Word 没有 CSS 的 `background`,能表达"色块"的只有**段落底纹**(`w:shd`)与**段落边框**(`w:pBdr`):现在按模板的 `section_title_style` 分别映射成"浅底 + 同色边框撑出内边距"(`soft_box`)、"强调色底 + 白字"(`accent_box`)、"只有左竖条"(`left_bar`),页头在 `header_bg = soft_accent` 的模板上也会带浅底色(姓名/意向/联系方式三段同色、段间距为 0,连成一块)。**已知差别**:OOXML 的段落底纹画不出圆角,预览里的圆角标题带在 Word 里是直角色带;色带高度按"行高 + 上下 spacing"近似 CSS 的 `line-height + padding`,差 1–2pt。子元素顺序按 schema 插入(`w:pBdr` → `w:shd` → `w:spacing`),顺序错了 Word 会判文件损坏。

- **导出的 Word 里,照片改成「浮于文字上方」。** 以前用 `add_picture()` 得到的是**嵌入型**图片:它是正文流里的一个"大号字符",独占一行、把姓名整体下推,页头因此比预览高一截,后面的分页也跟着往后错——这是"Word 版和预览差别很大"的主因。现在导出时把 `wp:inline` 转成 `wp:anchor`(`behindDoc="0"` + `<wp:wrapNone/>`),照片脱离正文流、贴版心边缘,姓名回到第一行,页头高度与预览一致;照片在左的模板(优雅)锚点也贴左。**文字不绕排**(`wrapNone` 的固有行为),姓名/联系方式特别长时右端可能被照片压住,这一点与「四周型」的取舍写在 `docx_blocks.float_picture` 的注释里。

- **设置页的卡片之间松了一点。** 「公告提醒」与「帮助与诊断」这类相邻卡片原来只隔 16px,两张卡都带 1px 边框和独立标题栏,看上去几乎连成一条线;间距统一放宽到 22px(所有 `settings-card` 一起改,同一页里的节奏才一致)。

- **两个下拉的宽度收窄。** 字体 / 版式的展开面板从 260px 收到 216px——「标准」「紧凑」这类两字选项在 260px 里右侧会空出一大半;字体下拉的触发框同步从 176/140 收到 152/128。长名字(含「(自制)」后缀)仍走单行省略,完整名字悬停可见。

- **「我的文件」副本缺失或损坏可以自愈了。** 以前 `user_file` 行还在、磁盘副本丢了(旧备份包导入、被外部清理工具删掉),回填会因为"同 sha 已有行"直接跳过,预览永久 404、导出永久报缺失。现在保存/回填时会核对磁盘副本,缺失、大小不符或哈希不符就按库内原件重写(临时文件 + 原子替换);导出前也会对活动数据集修一遍,修不好才保留告警。安全边界:只有"库内原件 SHA-256 == 行登记 SHA-256"时才动磁盘,两者不一致只报警、不改文件,也不删除任何磁盘文件。

- **`PATCH /api/profile`:只改传了的字段,别的一律不动。** 此前只有 `PUT /api/profile`(整体替换:没传的字段落回空值),于是任何"只改手机号"的调用都得先把整份资料读出来再整份写回去——中间任何一次并发编辑都会互相覆盖。现在 PATCH 只写请求里出现过的键,子资源数组按"整个数组替换"处理,所以**显式传 `[]` 是清空**、不传才是"不动"。校验规则与 PUT 完全同源(`ProfilePatch` 直接继承 `ProfileUpdate`,没有第二套字段定义)。`PUT` 的行为一字未改,老调用方不需要动。

- **岗位解读可以真的取消,也可以丢到后台继续跑,顶部还多了一条常驻任务条。** 生成中显示「已生成 N 秒」;点「取消」会中断本次请求(服务端在 400ms 内检测到断连就停掉生成、不写缓存、不白烧 token),并回到「开始生成」的初始态;点「后台继续」或直接关窗口则任务继续跑,完成后弹通知并响一声,下次打开弹窗直接看到结果。同时进行的生成会以 chip 形式常驻在页头下方的任务条里(「岗位解读 · 公司 · 岗位」+ 已运行时长 + 查看 / 取消),换页也还在;点「查看」跳到该岗位并自动打开解读弹窗。页头原有的后台任务面板继续保留,并把这第三类任务也算进计数。

- **简历中心的排序改成和岗位广场一致:点列头排序,三态循环。** 「简历标题 / 目标岗位 / 创建时间 / 最近修改」四列可点,升序 → 降序 → 第三次取消并回到默认(最近修改在前),默认态任何列都不显示箭头;筛选栏右侧新增「恢复默认排序」,默认态禁用并说明「当前已是默认排序:最近修改在前」。顶部原来的「排序方式」下拉已删除。后端排序键从 5 个扩到 10 个(每个字段配一个反向键,拼音 collation 与二级排序方向全反,并统一以 `id` 收尾保证跨页不重不漏);排序仍写进地址栏(`?sort=`),刷新与分享保留,非法值或旧链接里的 `company_asc` 会回落默认而不再让后端 422。

- **粘贴识别的文件可以先看一眼再决定识不识别。** 截图变成 48×48 缩略图,点开可放大、多张可左右切换;PDF 给「预览」按钮,点了在弹层里看这份文件的**首页位图**——图由后端现场渲染(新增 `POST /api/attachments/pdf-preview`,默认只渲 1 页、上限 3 页,因为预览要回答的是"我传的是这份文件吗");此前是把文件塞进 `window.open(data:)` 交给浏览器内置阅读器,而顶层导航到 `data:` 已被 Chrome/Edge 禁止,点了必然没反应。Word 只显示图标、文件名与体积,并提示「不支持浏览器预览,识别时会提取文字」。上传的是文件本身,与识别结果无关;数量 / 体积上限与删除按钮不变。

- **页头的模型下拉支持双击测连接。** 单击仍是切换(延后约 220ms 提交,给双击留出取消窗口);双击某一项只跑一次 `POST /settings/llm/test`,显示「连接正常(123ms)」或失败原因,**不会**把当前配置切走、下拉也不收起。同一时间只测一条。

- **⌘/Ctrl+K 的全站搜索从居中弹窗改成右上角浮层。** 不再有遮罩、不锁滚动、不困住焦点,页面照常可点可滚;点面板外或按 Esc 关闭,打开即聚焦输入框。搜索逻辑没变:250ms 防抖、按岗位 / 简历 / 其它分组、↑↓ 选择 + 回车上跳转。

- **新增全局 AI 润色:在任意输入框里选中一段文字,就地改写。** 选中后选区下方浮出「AI 润色」按钮,卡片里可选填要求(更简洁 / 突出成果 / 改成 STAR 结构等)→ 看结果(可直接微调)→ 点「替换选中」才写回,取消则原文一字不动;新接口 `POST /api/writing/polish-selection`(请求体只有 `text` ≤4000 字与 `instruction` ≤500 字,`extra="forbid"`)。**只发送选中的这一段文字**,不发送整份简历、不带字段上下文;提示词硬约束"不得新增事实"(数字、日期、公司、学校、专有名词不新增也不改),并按"用户要求与正文都是不可信数据"处理注入。密码框、只读字段、下拉搜索框与带 `data-ai-polish-ignore` 的区域不出现按钮;未配置模型时卡片内给出「去设置配置模型」入口且不发请求;字段字数上限放不下时不替换并明确提示。

- **岗位与备选岗位的「导入 CSV」可以一次导入多个文件,也可以直接粘贴表格内容。** 一次最多选/拖 10 个 CSV,合计不超过 2000 行(超了会如实拒绝,不静默截断);也可以在导入区按 Ctrl+V 粘贴从表格复制出来的内容。预览按文件逐行给出账目(可导入 / 重复 / 错误,错误带文件名与行号),其中一个文件坏了只标它自己,其余文件照样能导入;提交时按每批 500 行自动拆分,并把每一批的导入与失败条数累加告诉你。

- **页头的数据集菜单可以直接新建数据集。** 菜单底部新增「新建数据集…」:命名后立即创建,并询问「是否立即切换到这个数据集」——确认就切过去,选「稍后」只关闭询问、不动当前数据集。

- **新增 6 个可改键的快捷键。** Ctrl/⌘+K 搜索、Ctrl/⌘+S 保存当前编辑、Ctrl/⌘+Z 撤回、Ctrl/⌘+Shift+Z(或 Y)恢复、Ctrl/⌘+J 开关求职助手、Ctrl/⌘+Alt+N 新建岗位;在「设置 → 应用 → 快捷键」里可逐条改键、单条或全部恢复默认,与其它动作冲突或撞上浏览器保留组合(如 Ctrl+W / Ctrl+T)会当场拒绝且不保存,改完立即生效。「我的资料」与简历编辑弹窗里的撤回/恢复跨字段生效,焦点在输入框内时 Ctrl+Z 仍是浏览器原生的逐字撤销。键位存在本机,切换数据集不会丢。

- **手写简历的每个模块都能从「我的资料」逐条填入,每个条目旁还能「从资料填入本条」。** 教育、经历、校园经历、项目、技能、奖项的编辑器顶部有「从我的资料追加一条」下拉(基本信息页只提供「个人总结」):选中一条点「填入」就在末尾加一条,已经在简历里的条目在下拉里标「已加入」并禁用,重复点不会加第二条,也不会覆盖你改过的内容;技能按名称合并去重。每个条目头部的「从资料填入本条」把这套候选填进**当前这一条**:只写空字段,手写过的内容一字不动,其它条目不受影响;填完焦点落在这一条的字段上,不会留在下拉里。

- **ATS 本地检测给出可执行的细项,而不只是一个总分。** 分数旁显示格式 / 关键词 / 位置三个分项与关键词覆盖率,命中词按出现次数排序,每条问题都带具体改进建议;正在作答的简历若关联了岗位,打开面板就会把该岗位的 JD **自动填进输入框**(只填不跑检测,也可以随时改写或清空),读不到时给一行提示并可「重新载入关联岗位 JD」;还能「复制改进清单」把标题与建议整段拷走。仍是纯本地静态规则、不调用模型。

- **页头可以直接切换「浅色 / 深色 / 跟随系统」。** 三档图标控件,当前档用实心图标(浅色太阳实心、深色月亮实心、跟随系统显示器实心),悬停有名称提示;选择仍存在本机并同步到服务端,刷新与换数据集后保持。设置页原来那张「主题外观」卡已删除,只留一句说明:深色只作用于应用界面,简历预览与导出 PDF 仍是白纸。

- **简历与岗位的 A→Z 排序改为拼音 + 数字自然序(`rf_pinyin` collation)。** 以前 SQLite 按 UTF-8 字节比较:「UI 设计师」永远排第一(ASCII 全在中文前),第 195 版排在第 75 版前面。现在中文按拼音、英文按字母、数字按大小(第 2 版 < 第 10 版 < 第 75 版);岗位标题/公司、简历标题/公司/目标岗位共用同一份排序键。新增依赖 `pypinyin==0.55.0`(纯 Python、MIT、无传递依赖)。

- **岗位广场新增「恢复默认排序」。** 排序状态不再写进 URL:离开岗位广场即恢复默认(最近导入在前),从简历中心回来不会停在上次的排序上;工具栏末尾的按钮可随时手动恢复,默认态禁用并提示「当前已是默认排序」。

- **模型配置只剩一套,且是全局的。** 原「大模型 API 配置」模块(配置记录卡、使用/测试/编辑/删除、预设、模型列表、思考模式)整体提到全局:每个数据集看到的记录、当前「使用中」的模型完全一致,切换数据集不用重填。首次打开设置页会把该数据集原有的配置记录与当前生效配置一次性迁到全局(`global_settings.json`),旧表保留不删;保存时仍会双写一份数据集快照,导出/导入不受影响。

- **导出的备份包可以带上「全部全局模型配置」,并且默认就带。** 设置 → 数据 →「备份中包含全局模型配置(所有已保存记录)」默认勾选;导出时在包内新增独立的 `global_llm.json`(记录名、端点、模型、非密钥参数、使用中的那条),包格式升到 **5**——旧版本导入会明确提示升级,而不是静默丢掉配置。密钥仍由「包含大模型 API Key」单独控制:不勾选时配置段里的密钥一律为空串,不会新造明文。取消「包含全局模型配置」则包内没有该段,格式号与旧版完全一致。

- **导入备份时会把全局模型配置合并回来,并如实报数。** 导入完成后提示「全局模型配置已恢复 N 条(跳过 M 条同名)」;合并规则是**只补不覆盖**:本地没有同名记录就新增;同名但本地是空壳(地址与模型都没填)就用包里的补全;同名且本地已有内容则保留本地(不拿别的机器的端点/密钥覆盖你正在用的);「使用中」只在本地没有可用配置时才采纳包里的那条。配置段损坏或合并失败不会回滚已恢复的数据集,只在提示里说明配置没回来。

- **页头新增模型切换下拉,就在数据集下拉的左侧。** 显示当前「使用中」的记录名与模型,展开列出全部已保存记录,点一条即切换(所有数据集立刻生效);没有配置记录时不渲染,避免一个点不动的空下拉。每次切换路由都会重新拉取,在设置页改完模型回到其它页面不会显示旧名字。

- **简历字体可以先预览再决定。** 点列表里的字体名(或「···」→「预览字体」)打开弹窗,用该字体渲染姓名、经历与中英数字示例;确认后点「设为当前」才真正切换。新增 `GET /api/settings/fonts/{name}/file`(任意已上传字体,不必先设为当前),强制 `no-store`。

- **每一份简历都能自己挑字体,而不只是跟随设置里的那一款。** 版式栏(生成前与预览工具栏同一行)新增「字体」下拉,选项是已导入的字体 + 「系统字体(不使用自定义字体)」;下拉打开时每个选项用它自己的字形显示(前端注入一份只在打开期间存在的 `@font-face`,关掉即移除,不预下载几十 MB 字体)。三个状态互不混淆:**没设过**跟随「设置 → 应用 → 简历字体」的当前字体(老简历外观不追溯改变)、**选某款**就用它、**系统字体**是显式"这份简历不用自定义字体"(写哨兵值 `__system__`,不是删键)。后端 `format_config.font` 只做白名单放行——不进 `format_fields`(否则格式模板编辑器会为它长出一个数字输入框),文件不存在或带目录成分一律静默丢弃、回落"跟随当前字体",不让字体问题变成打不开简历。字体只影响预览、HTML 与下载的 PDF(`@font-face` + PDF 侧按名字解析字体文件),Word / Markdown 不跟着变。

- **粘贴识别变成"先校对再填入",而且随时能取消。** 识别成功后弹窗不再自动关闭,结果先列成一张可原地修改的清单(按字段目录分组,标注识别来源),看过、改过、点「确认填入 N 项」才写进表单;识别过程中主按钮变成「取消识别」,真的会掐掉请求(`AbortController`),取消不算失败、不弹错误、原文保留可重试。

- **「网申资料」也能整段粘贴识别了。** 编辑态卡片右上角「粘贴文本识别网申资料」:把网申页面上的一屏栏目整段粘进来(或给截图 / PDF / Word),能归到字段目录的直接填好;**目录外的新栏目先列成带「新增字段」标记的待确认项,默认不勾选**,勾哪几条才新增哪几条——写进去等于往字段清单里长出一个字段,猜错一个值改回来即可,凭空多出一个字段要先发现自己再删掉。新增 `POST /api/webform/extra-profile/parse-text`(与资料侧同形:走 XHR 上传进度、支持取消、只返回草稿不写库)。

- **预览简历的版式控件并入抓手那一行。** 模板 / 板块 / 页数 / 字号 / 行距不再单独占一行,与「抓手 / 编辑 / 缩放」同处工具栏;「查看简历大图」里也给了同一套控件(大图有自己的预览句柄,字号拖动即时作用在大图上)。

- **「我的资料」的未保存提示改为居中确认卡片,条目卡动作收进右侧一行,板块与字段弹层补齐层级。** ① 编辑态点侧栏 / 取消编辑不再用浏览器原生确认,而是页面内居中的确认卡片(遮罩 + 两个具体动作「放弃修改 / 继续编辑」,放弃才执行、继续则草稿与当前方案原样留下);卡片弹出时**收起**右下角悬浮球「去改资料」提示——同一屏两个「未保存」提示、其中一个还压在遮罩上,用户分不清该点哪个。② 条目卡的上移 / 下移 / 删除 / 拖拽把手从 `position: absolute` 浮在卡片右上角(为此留了 76px 右内边距空白)改为流式布局的一行、右对齐;删除仍是行**外**的兄弟节点(行是 flex 横排,塞第三个子元素会被压扁),并改为就地 `Popconfirm`「删除这条」(气泡本身就是说明,不再叠 Tooltip)。③ 统一卡片内边距为 12px 16px(此前 8px 4px 让齿轮 / 折叠图标离卡片边缘只有 4px),分区标题栏与板块卡外观统一到 `profile-card.css`,行内动作行上下各留 8px(此前贴着卡片下边缘);④ 弹层层级统一为「弹层标题 > 卡片标题 > 行标题 > 说明」,板块与字段弹层不再出现标题被卡片标题盖过的情况,行内图标动作补齐可访问名称与删除确认。

- **新建简历的自建板块改为按「我的资料」里定义的字段渲染,历史「标签 / 值」行不丢。** 此前自建板块在编辑器里只有一只承载区,用户在资料里定义的公司、时间、描述全被压成「标签:值」文本,改动无处下手。现在投影为每个字段给一个输入框(`EditorFieldOut.primary` 标记**主文本字段**:`primary_field_id` 优先、否则第一个多行文本字段,与纸面投影同一个判定函数),主文本绑定 `ResumeCustomEntry.text`、其余按定义顺序绑定 `lines`——两处必须同一套判定,否则用户改的值会在保存后落到另一列;系统板块恒为 `False`(它们有槽位 / 承载区,不需要这个标记),旧快照缺这个键时按「第一个 multi_line 字段」兜底。此前已经存成「标签:值」的历史行原样保留,不会被新渲染吞掉。

- **精确预览现在包含"还没保存的正文"。** 以前在制作台中栏改完一句、不点保存就切「精确」,看到的是数据库里上一版排出来的 PDF——预览显示的就是导出产物,正文却是旧的。现在 `POST /preview-pdf` 接受可选的 `content` 覆盖:改完直接切就能看到新内容,**不落库、不冻结交付、不触发 AI**;内容指纹本来就在缓存键里,换一句就重排、不会命中旧产物。预览底部会注明"这份预览含还没保存的修改,导出前请先保存"——预览按眼前的草稿排,下载仍按已保存的记录(这条边界如实写出来,比让两处悄悄不一致更好)。`content` 走与保存同一份 `ResumeContent` 校验,长度/图片/字段上限全部继承。

- **简历的每条经历、项目、要点有了稳定标识(内部契约,不改现有界面)。** `ResumeEducation` / `ResumeExperience` / `ResumeCampusExperience` / `ResumeProject` 新增可选 `id` 与 `point_ids`(要点级 id 的伴随映射),`ResumeSkill` / `ResumeAward` 新增 `id`;事实来源标注新增可选 `object_id` / `point_id`。**读旧记录不物化**:打开一份旧简历不会在库里凭空长出 id、也不刷新 `updated_at`;只有保存时客户端带上来的值才原样落库,排序/隐藏保留 id、复制才生成新 id。写侧严格:非法字符、同一数组重复、`point_ids` 与内容不等长、未知要点字段一律 422,**不静默重生成**(重生成等于把用户设备上的标识换掉)。AI 改写路径不适用 422(模型不是可回错误码的客户端),改为丢弃不合法 id 后落库。迁移与备份格式都不变(id 全在 `content` JSON 内部);旧版本读新内容会忽略这些字段,保存一次后 id 全丢,这是如实的退化。

- **保存简历时能发现"另一个窗口已经改过"。** `resume_record` 新增 `revision`(迁移 `0052_resume_revision`,只加列、`server_default="0"`,旧备份仍可导入;旧行保持 0,因为它们的"第几版"在数据里无从得知)。详情与列表都会返回这个值;新的 `PUT /api/resumes/{id}/content` 接受 `expected_revision` 做**原子条件写**(`UPDATE ... WHERE id=? AND revision=?`):命中就推进一版,没命中返回 `409` + `code: "resume_revision_conflict"` 与库里当前版本,**一个字段都不写**。版式 `PATCH` 与 AI 修订同样支持可选版本号,冲突时 AI 结果直接丢弃。**旧端点 `PUT /api/resumes/{id}` 的请求形状一个字节没改**:仍是无条件写并推进一版——于是旧客户端写完之后,新客户端下一次条件写必然拿到 409,用户看见的是"别处改过",而不是被静默覆盖。内容与库内完全一致时重复保存不推进版本,避免把正常操作打成假冲突;改名 / 备注 / 收藏 / 软删 / 回收站恢复都不推进(它们不是文档内容)。

- **同一个链接字段现在只有一个名字:「个人主页」。** 资料页原先叫「个人网站 / 博客」、编辑页叫「个人主页」、简历纸面印的却是「主页」——同一个 `personal_website` 在三处三个叫法,在导出文件里搜「个人主页」是搜不到的。现在资料页、编辑页、字段定位、导出脱敏选项与纸面页眉统一为「个人主页」;**纸面因此多两个字**,导出基线一并重算(页数一个没变)。

- **模板示例内容补齐了字段,也按字号分了三档详略。** 模板预览里那份示例简历现在有**照片、性别、出生年份、个人主页与 GitHub**(照片是一张 132×176 的虚构证件照,没有照片的模板位不再留空框),每个模板位都能看出"这里有内容时长什么样"。内容量跟着字号档位走:小字号一页装得多、示例就更密,大字号就精简,**换字号不会再看到"缩略图只剩半页"**;三档在六套模板里都只占一页。**大字号档只保留教育、实习/工作、项目、技能与总结五节**:18px 下页眉加七个小节标题本身就要占满一页,留七节必然被截断,只有五节才能一页排完、每节都读得完整——这是排版密度的取舍,不是内容缺失(小字号与标准档七个板块齐全)。

- **新建简历与「我的资料」的默认板块顺序改成「教育经历」在前、「个人总结」在后。** 默认顺序现在是 教育经历 → 实习/工作经历 → 项目经历 → 专业技能 → 校园经历 → 荣誉奖项 → 个人总结(此前个人总结排第一、教育经历排第五)。**已保存的简历、以及你排过的资料顺序都不受影响**:它们读的是自己那一份顺序,只有从没调过顺序的记录才跟着新默认走。

- **制作台的版式下拉收窄到 6 个汉字宽。** 展开面板从 216px 收到 130px:6 个汉字加省略号正好放得下,长名字(如「虚构版(自制)」)走省略号、悬停仍能看到全名;字体下拉的宽度不变。

- **这一批的八处交互优化(都只改"看与调"的路径,不动数据与导出)。**
  1. **模板大图不再有三条滚动条。** 「选择简历模板 → 查看大图」原先是一颗固定 794×1123 的 iframe:弹窗窄于 794 出横向滚动条,文档高于一页时 iframe 内部再出一条,加上弹窗本体那条一共三条。现在复用 `A4PreviewFrame` 的 `fit="width"`——整页按弹窗宽度等比缩放、高度按页数走,整块只剩一条。
  2. **版式下拉收窄到 88px。** 原先按 `4em + 36px` 跟着字号 token 漂(≈100px),一个字的名目占掉大半格。
  3. **模板一览的字号档位改成真生效。** 它原先是"只读对比"(只影响缩略图),现在写回**这份简历**的 `font_scale` 并立刻重排;作用范围只有当前简历与它之后的版本,已保存的其它简历一个都不动(`font_scale` 是记录级字段)。
  4. **模板预览内容固定为内置示例。** 不再读你的简历——既省掉每次开都排一份大简历,也让"这是模板的效果还是我内容的效果"不再混在一起。要看你自己的内容,制作台右栏一直是实时预览。
  5. **写作增强补上用法提示。** 面板顶部常驻一句"贴一段原文 → 选一个动作 → 生成后点「回填」",动作切换时下面多一行"这个动作会怎么改你的句子"(STAR / 话术 / 润色 / 互译各一句)。
  6. **简历中心点开预览更快。** 预览取的是 `POST /export` 的渲染产物,从前每次点开都落一份文件、并且每次都要重排一遍;现在走新增的 `preview: true`(不落盘 + 结果进服务端预览缓存,同一条管线、同一份字节),同一份简历第二次打开是 0ms。
  7. **精确预览的空白页就是 A4 纸。** 补出来的空白页不再画虚线框,改成白底 + 与 PDF 页同一道投影;它紧跟在最后一页之后,并且与 PDF 一起缩放(缩放改的是"纸列宽度",查看器重新排版,矢量不糊)。精确模式现在也有和草稿模式同一条缩放条,两边共用同一个比例。
  8. **每个排版参数右侧多一颗 ↺(恢复到打开这份简历时的值)。** 与"双击标题 / 随模板"的删键语义分开:基线是打开这一屏时的实际取值(可能本身就是一条覆盖),恢复是把那条覆盖原样搬回来,不是跳到模板默认值。

  另外两处小修:下拉里**已选中项**的悬停底色钉回品牌浅 tint(此前在部分子树里会变成中灰底 + 深色字);使用指南弹窗上下滚动时,左侧步骤栏固定在顶部。

- **安全升级:pypdf 6.21.1 → 6.21.2。** 上游披露 3 个新 CVE(CVE-2026-115572 / CVE-2026-115585 / CVE-2026-115580),均在 6.21.2 修复;CI 的生产依赖漏洞门禁(pip-audit,发现即失败)拦下后升级。pypdf 用于本地上传 PDF 的文本读取与水印叠加,升级后 65 个 PDF 相关用例全部通过,无接口变化。

- **多分辨率与系统缩放适配:断点收敛成 6 个值、外壳宽度有了唯一来源、A4 画布有了可读下限。** 以前这套界面假设"窗口一定够宽":断点散落 14 种数值(480/575/600/640/700/720/760/767/768/900/991/992/1000/1280),侧栏 200px 写在三个文件里,纸面缩放只有上限没有下限——在 1366×768 或 1080p@150%(CSS 1280)上,纸被压到 34%(正文 4.7px),中栏表单三个字段挤成 138px、邮箱显示成 `user@exa…`。

  现在:**6 个断点**(767 / 1092 / 1366 / 1536 / 1920 / 2048)成为唯一事实源(`theme/breakpoints.ts` + `--rf-bp-*` + 一条守卫,新增非标准断点即红);**侧栏宽度与内容内边距走 `--rf-sider-width` / `--rf-content-pad`**,改一次全站生效(1092 以下侧栏 64、767 以下让位);**A4 画布保 55% 可读下限**(纸面 ≥437px),容器装不下时改为纵向滚动并显示「55% · 已到最小可读缩放」,不再继续缩小;**制作台的三档改用弹窗体自己的宽度判断**,1280 以下中栏进抽屉、1000 以下左栏也进抽屉(此前中栏是绝对定位,会把参数栏整块盖死;1000 以下更是直接不渲染,编辑功能不可达);**简历表把「操作」列钉在右侧**,横滚时也能点到导出。桌面壳同步:默认窗口按屏幕工作区算、最小宽度 1120、记住尺寸/位置/缩放系数。

  弹窗内两列布局在 1092 以下改单列,矮屏(≤760 高)弹窗内容区多留 40px;首页统计卡从 1200 提前到 992 变四列。**验证**:`frontend/tests/responsive/overflow.spec.ts`(8 视口 × 18 路由 = 144 条硬断言)进 CI;截图 harness 与 rubric 在 `deliverables/software-company/responsive-20261023/harness/`。

- **「我的资料」页不再有「通用简历」区块,入口收进简历中心的「新建简历」。** 那块卡片(含它常驻的名称输入框与「AI 生成 / 从头手写」两个按钮)从资料页整块删除,资料页也不再指向它——**新建一份不针对岗位的简历只有一条路**:简历中心页头的「新建简历」→「从头编写通用简历」编辑器,不绑定岗位、从空白开始,需要资料时点编辑器顶部的「填入我的资料全部数据」一次带入。**当前界面上没有"AI 生成通用简历"的入口**:生成口径本身还在(不按岗位筛选、完整使用你的资料),但没有任何按钮会在不选岗位的情况下直接生成,要 AI 写就先按岗位生成再改。已有通用简历不受影响:仍在简历中心标成「通用简历」,可重命名、删除,导出方式与岗位简历一致。

- **「新建简历 → 前往简历制作台」改为先建草稿:不保存就关掉,简历中心里不会有它。** 此前点这个按钮会先落一份空白简历再打开制作台,于是"点进去看一眼又关掉"也会在列表里留下一条空简历。现在建出来的是**草稿**(迁移 `0051_resume_draft`):在制作台里保存过内容**或**排版(任一项即可)才转正、出现在简历中心;制作台顶部在"什么都没改"时也给一个「保存到简历中心」——没有这个按钮,草稿就永远没机会现身。改了排版没保存时关窗会先问一句;内容那一半仍由「手动调整」自己的未保存守卫兜住(两层各问一次,用户要连点两次才关得掉)。草稿本来就不该让任何列表重取一次,所以这一步不广播列表刷新,转正后那一次刷新由制作台宿主负责。

- **「分边调整页边距」搬进「简历制作台」。** 四条支路(上 / 右 / 下 / 左)需要一列纵向空间,放在预览工具栏那一行会把它顶宽、尾部的读数与「随模板」被压成 0 宽(2026-10-22 用户截图的成因)。现在它紧跟在「页边距」总滑杆之后,只在这一处渲染;生成前的配置页与预览工具栏只保留一个总页边距。写入语义一个字都没改:拖总滑杆只写 `page_padding` 并清掉四条支路,拖某条支路则写全四条。

- **「查看简历大图」已升级为全屏工作台。** 左侧集中调整模板、版式和排版参数,右侧预览;窄屏切换为上下布局。主预览与大图继续共用参数和草稿 / 精确预览状态。

- **样式表也进 500 行预算了,豁免不再能"留在册上养老"。** `test_file_size_budget` 的扫描后缀补上 `.css`:此前 31 个样式表**完全不在视野里**,最大的 `styles/modules.css` 已到 2056 行(预算的 4.1 倍)也从来没人被拦过。本批只止血不改布局:5 个存量超线文件(`modules` 2056 / `base` 1039 / `resume` 803 / `tou-tou` 554 / `tracker` 514)登记为带到期日(2026-12-31)的书面豁免,拆分按 `modules.css` 的 12 个分段标题另立项;新增/改动的样式表从今天起就受 500 行约束。同时新增幽灵豁免守卫 `test_exemptions_only_for_over_budget_files`:豁免指向的文件一旦不再超预算(通常是拆完忘删条目)就变红,并配一条自检用例确保检测器不是空转——豁免册从此只增不减的问题被堵住。

- **弹层不再叠成两层("同时只有一个容器")。** 四处"弹窗里再开一个确认框"都改掉了:备选岗位抽屉里的详情 / 新建编辑表单 / CSV 导入改成**抽屉内的三屏视图**(标题随视图变,× 与 Esc 先退一层,列表的滚动与筛选不丢);导出遇到「简历里还有未完成标记」改成导出面板内的一条 `Alert`(「仍要导出草稿」/「我去改简历」);「重新生成建议」改成按钮上的就地 `Popconfirm`;简历编辑器的「未保存就关」与「用我的资料覆盖」改成 footer 里的内联确认条(同一位置换态,焦点不跳)。新增守卫 `popupStackGuard.test.ts`:`*Modal.tsx` / `*Drawer.tsx` 里再写 `modal.confirm(` 直接红。

- **「先做再反悔」扩到投递、台账、资料、知识库与会话删除。** 这五处的单条删除此前仍先弹确认、删完只留一句"已移入回收站";现在与岗位删除同一口径:直接执行,Toast 给 5 秒「撤销」,撤销调回收站恢复接口。备选岗位的**批量删除**保留确认,但改成按钮上的就地 `Popconfirm`——它开在抽屉里,用 `modal.confirm` 就是抽屉之上再叠一层;超过 100 条的确认语义不变。

- **备选岗位删除进回收站了(不再一删就没)。** 此前备选岗位是硬删除,界面只能如实写"无法恢复"——而它与「岗位」只差一步导入,误删一条待处理候选的代价和删掉一条正式岗位是一样的。现在 `candidate_job` 有 `deleted_at` 列(迁移 `0050_candidate_job_soft_delete`,只加列、旧库升级后所有已有候选仍是"没删"),删除改成软删:列表 / 分页 / 全选 / 单条 / CSV 导出 / 助手工具全部不再看到它,回收站里多一个「备选岗位」类型(紧挨着「岗位」),可恢复、也可彻底删除。删除确认的正文随之从"无法恢复"改成"会移入回收站,之后可以恢复"。

  **去重判据与岗位广场刻意相反**:回收站里的**岗位**仍算"已存在"(否则"删掉再采集"会造出第二条),但回收站里的**候选**不算——删掉一条候选正是让它从待办里消失,若它继续拦住采集,这条岗位就再也采不回来了。已删的候选也不参与 CSV 导入判重、不能被"勾选导入"成正式岗位(结果里记 `missing`)。

- **弹窗的关闭语义统一成一套(新增与改到的弹层)。** 有提交动作的表单弹窗**不再能点蒙层关掉**(误触一次就丢一屏内容),× 与 Esc 同义;改过内容又没开草稿时按 Esc 会在底部就地出一条「放弃未保存的修改?」——**不再叠一个确认弹窗**(那是第二层容器);提交中 Esc 被忽略、× 隐藏。主按钮一律写结果(「保存台账」「保存进度」),antd 默认的「确定」不再出现。只读预览类弹窗保持原样:不动蒙层、不加"关闭"按钮。

- **Toast 的默认时长从 3 秒改成 2.5 秒,同屏最多 3 条,鼠标悬停暂停计时。** 一处全局配置,620 个调用点的行为一起变;显式传了 `duration` 的(如批次完成通知)不受影响。

- **macOS 的桌面运行期目录去掉重复的一层。** 桌面壳此前在 `userData` 之下又拼了一次产品名,得到 `~/Library/Application Support/ResumeForge/ResumeForge/runtime`;而启动器写的是 `.../ResumeForge/runtime`。现在壳在 macOS 上直接用 `~/Library/Application Support/ResumeForge/runtime`,与 `stop.command` / `update.command` 的默认值一致(Windows 路径不变)。**升级影响**:mac 上已存在的 `python/` 与日志留在旧目录,首次启动会重新解压一次运行时(约几秒),旧目录可按需手动清理;`stop.command` 仍会检查旧位置,升级前留下的 pid 记录不会失效。

- **小节标题与字段文案改为从唯一来源取,四个渲染器不再各写一份中文字面量。** 同一组标题过去在服务端 PDF、Word、HTML 模板、Markdown 四处各自维护(纯文本复用 Markdown),改一处漏三处就会得到"预览是 Summary、PDF 里还是个人总结"这种没人能从界面上看出来的差异。现在纸面固定文案只有 `app/services/resume/resume_labels.py` 一份表,渲染器一律按语言取用、**不许自己判断语言、也不许自带文案**;中文字面量从渲染器源码里消失,并由一条源码守卫钉住(失败信息直接给出文件与行号)。

  语言同时进入两处"会影响结果"的哈希:**渲染配置指纹**(交付记录的 `X-Render-Config-Sha256`)与 **PDF 字体预子集的缓存键**。前者不包含它,就会出现"指纹相同、下到的文件却是另一种语言";后者不包含它,同一份内容先排中文再排英文时会复用上一语言的子集字体,英文标题的字形不在里面——fpdf2 只打一行 missing glyph 警告,**纸面上那一行标题会静默消失**。

- **PDF 与草稿预览的版式差从 12mm 收到 3mm 以内,8 个模板各自还原。** 此前 `pdf_exporter` 只有一套写死的版式:联系方式只画 4 个字段(漏掉 GitHub 与个人主页)、标签一律画成同一种淡色块、分区永远单栏。现在这些参数全部来自模板 CSS:页头两处间距(`.intent` / `.contact`)、`accent-soft` 的真实色值、五种标签形态(填充 / 描边胶囊 / 左侧色条 / 纯文字 / 点分隔)、分栏与栏间距、列表缩进、标签居中与等宽字族。逐模板守卫直接从模板文件里读 CSS 反查注册表,不是把数字抄第二遍。默认 14px 下实测草稿 307.8mm vs 精确 304.8mm(差 1.0%;改前 12.0mm / 4.1%)。

- **split 模板的 PDF 现在是真正的双栏。** 分区按「两两一行」并排(对应草稿的 `.9fr / 1.1fr` 网格),行高取两格较大者、两格都从行首起画(对应 `align-items: start`);空分区不参与配对(草稿根本不会为它生成节点,按七个键全额配对会让整份错位近百毫米);单个分区高于一页时降级为顺序渲染,不丢内容也不死循环。同一份简历的 PDF 从 270.75mm / 2 页降到 218.33mm / 1 页,且文本与单栏版本逐字相同。

- **脱敏导出现在也遮 GitHub 与个人主页。** 这两个字段此前不在脱敏范围里,于是"脱敏导出"的联系方式行会原样留着 `github.com/<用户名>` 与个人域名——一个 handle 就足以反查到人。新增 `mask_github` / `mask_website`,**默认开启**,导出面板里可以单独关掉(想保留作品集链接时)。

- **跨引擎版式对拍进了 CI。** 新增浏览器实测:用 Chromium 量草稿的真实内容高度,与服务端 PDF 的排版高度对比,阈值 `max(2.5mm, 1.5%)`(实测 classic +0.69%、modern +0.58%);同时把那三条此前被整体 skip 的浏览器对拍接通(预览的 `data-pages` / `data-overflow` 与响应头一致、宽边距下两边页数不分歧)。顺带补上一个此前没人注意的洞:`requirements-dev.txt` 从来没声明 playwright,任何干净环境(包括 CI)里这组对拍都会静默退化成 skip。

- **HTML 黄金基线的口径收窄了,这是有意的取舍。** 语义指纹现在会整块剥掉 `<script>` / `<style>`,所以模板脚本里的注释或样式表小改不会再让指纹变红。代价要说清:**导出的 HTML 如果只有 CSS 变化,金样从此抓不到**,CSS 侧要靠在 CI 里跑跨引擎版式对拍兜住。用"更准的指纹"换"更窄的覆盖面",写在这里免得日后被当成漏测。

- **预览与导出都严格按你设定的字号排版,不再"装不下就自动缩小"。** 此前有两处会自动压小字号:模板里的自适应脚本把 `--fit-scale` 从 1 逐档压到 0.5(草稿预览),服务端 `decide_fit_scale` 做同样的事(精确预览与导出)。于是滑杆上是 18px、纸面上却可能是 9px——**参数与呈现不一致**,而且缩到下限仍放不下时只得到一句"字号已降到最小"。现在字号的唯一来源就是版式参数:模板的 `--fs` 直接等于基准字号(`calc(... var(--fit-scale))` 这个挂钩整个删掉),服务端按真实参数排一遍,超过页数上限就**截断到上限**并标记 `overflow`(Word 不截断,只标记"超过所选页数")。草稿与精确用同一套措辞提示"按当前设置约需 N 页(上限 M 页)",并写明"字号、行距、页边距都按你设定的值原样渲染,不会自动缩小";精确侧新增 `X-Resume-Pages-Needed` 响应头(未截断的真实页数),状态行仍报文件里实际排了几页。想压进一页请显式操作:增加页数、收紧行距/页边距,或用「版面诊断 → 自动一页」——它把收紧后的参数写回滑杆,你能看见它改了什么。三条守卫:模板逐模板断言 `--fs` 不含缩放系数且脚本里没有 `setProperty(`;预览目录源码不得再出现 `--fit-scale`;服务端不再导出 `decide_fit_scale` / `MIN_FIT_SCALE`。

- **简历预览改成「只看不改」:去掉了抓手右边的「编辑」模式。** 那套交互要求 iframe 接管指针事件(点纸面字段直接打开对应栏的编辑器),而"按住拖动平移"依赖事件落在**外层容器**上——iframe 文档里的事件不冒泡到父文档,两者本来互斥。既然"选中文字 → 右键 → AI 润色"与「手动调整」已经覆盖了修改需求,预览里再留一套命中区等于同一件事两个入口、两套命中规则。一并退场的还有「让 AI 改这一栏」:它原本只在编辑模式下可用(同一个指针事件依赖),而预览的悬停角标("现在指向哪一栏")也随它一起收掉。工具栏第一行现在只剩「草稿 / 精确」,第二行右边那句换成了「这一版由导出引擎排版」的说明。**修改入口不变**:编辑器里选中文字右键「AI 润色」,或点「手动调整」通改。

- **行距精度 0.05 → 0.01,页边距 1mm → 0.1mm。** 两者都是"差一点点就压不进一页"的旋钮,粗档位下只能靠 ± 按钮一点点蹭。滑块步长、两端按钮、读数与落库精度同步收细;`clampPagePadding` 也从取整改成保留一位小数(范围内的小数不再被抹掉)。前后端 `FORMAT_FIELDS` 的 `step` 一并更新。

- **内容超出最大篇幅时直接截断,不再顺延出新页(草稿与精确同一口径)。** 以前溢出会多排一页 / 多导一页,本意是"让你看见还差多少",但那份多出来的页与导出的真实结果并不是一回事,反而让人以为导出也会多给一页。现在页数**恒等于用户设定的上限**:草稿画布只排 N 列,超出部分落在任何一页之外;PDF 侧给画布加了页数硬上限(同时关掉 fpdf2 的行级自动分页,否则长条目仍会自己翻页),按真实参数排一遍后仍超出就在第 N 页截断(**不缩字号**,真实所需页数由 `X-Resume-Pages-Needed` 报出)。告警条与精确预览状态行都改成"超出上限的部分不会显示",想看更多就把篇幅调大。原先那个"1 页上限是否展开"的风险开关随之删除。

- **多页草稿预览从「横向并排」改成「竖向堆叠」。** 横向并排时看第 2 页得先把预览往右拖,而简历的阅读顺序本来就是从上往下。现在每页各占一张 A4、上下摞着,页脚下方标"第几页 / 共几页(近似)",往下滚就能逐页看。**分页口径一点没变**:内部仍是同一套 CSS 多列(列宽 / 列间距按页边距算、`break-inside: avoid` 照常生效),只是把"第 i 列"单独开一个 A4 窗口显示(`scrollLeft = i × 794`)——同一份排版结果被裁成 N 张独立的纸。代价是同一份 HTML 渲染 N 次(N ≤ 3),照片这类内嵌图会各存一份。

- **整包备份的每份数据集都带行数快照。** 导入时按清单复核每份随包数据集的行数(此前只核表集合),被改过行数的包会被拒收,且不影响同包里其它份的恢复。

- **自动备份的边界写进了界面与接口。** 状态接口新增 `coverage: "database_only"`,设置页据此说明"自动备份只保护数据库快照,不含附件与字体,需要完整备份请手动导出";`auto_backup` / `global_settings` 里过时的文案("用户数据只有 SQLite 一处"、"这个文件不进任何导出包")已纠正。

- **界面用词按「模板 / 版式 / 排版参数 / 版面」四层定名收口,同一个东西不再有两个名字。** 排版这件事此前在界面上有四套混用的说法:「样式」「样式模板」「格式模板」「内置样式」都指外观骨架,而「版式」既被拿来指一键参数组合、又被拿来指整条排版栏(以及字号行距页边距)。现在一层只有一个名字:外观骨架是**模板**(8 套内置 + 自制)、一键参数组合是**版式**(标准 / 紧凑 / 舒展 / 单色强调)、字号行距页边距是**排版参数**、排出来的效果是**版面**;整条栏统一叫**排版设置**。工作台随之对齐为「内置模板」「复制为新模板」「自制版式」,「随模板 / 随版式」这组区分保留(它正是模板与版式的分界)。界面上不再出现「样式」「样式模板」「格式模板」「内置样式」「新版式」。改的是词,`template` / `format_name` 这类内部键与类型名一个没动。

- **二次确认统一成「标题 + 对象/数量 + 后果」三段,删除类一律写明还能不能回来。** 全站走 `rowActionMenu` 的确认框此前只传了标题(`modal.confirm({ title })` 没有 `content`),于是"删除这条面经"点下去既不说删的对象,也不说后果。现在 `RowActionItem` 多一个 `confirmBody`,软删除类统一复用 `TRASH_RECOVERY_NOTE`——**「会移入回收站,之后可以在「回收站」里恢复。」**,不在各调用点各写一份。**数据集是唯一的例外**:它删掉的是整份数据目录、界面里没有恢复入口,所以正文如实写"会移出列表,界面上没有恢复入口;删除前请先「导出」备份",绝不承诺应用做不到的事。

- **同一批术语在全站统一到界面的现状。** 材料 → **资料**、永久删除 → **彻底删除**、新增 / 创建 → **新建**、重新加载 → **刷新**、职位 → **岗位**、拿去深挖 → **开始深挖**。这几组此前在按钮、空态、提示语、二次确认正文里各写各的,同一个动作在不同页面叫不同名字;现在只留一种说法。随附 `docs/user-guide.md` / `README.md` / 应用内指南同步改到与界面一致,`CHANGELOG.md` 的历史发布记录不动。

- **设置页不再能改技能:技能的写入口只剩工作台一个。** 此前设置 → AI 模型 → 「助手技能」卡片能导入、启用停用、删除,工作台的「技能」页也能——用户在设置页关掉一个技能、回工作台发现还开着,而两边都"操作成功"过。现在设置页那张卡是**只读摘要**:只显示名称 / 状态 / 描述,点技能名可以只读查看提示词与知识文件(弹窗不提供「保存」,输入与开关全部禁用),并给一句「去工作台管理」跳到 `/skills`。新建、导入、编辑、启用停用、删除全部收归工作台。这是行为变更:**想在设置页顺手开关技能的用户会找不到那个开关**,去工作台即可。

- **新增两条源码守卫,把"文案写错地方"变成会红的测试。** 一条守**指南引用**(`components/userGuideCopyRef.test.ts`):抽出 `userGuideSteps.ts` 里所有 `「…」` 的内容,断言它们作为字面量出现在 `src/` 的源码里,失败信息直接给指南行号。它上线的第一件事就是抓到一处真错——指南说"结束时给「该去补什么」",而界面上的清单叫「面试前的行动清单」;另一处「已接收多少字」也不存在,界面写的是「已接收 N 字」。局限写在文件头:只能证明字符串出现过,验不了可渲染位置与 `${}` 动态拼接。另一条守 **antd 废弃属性**(`theme/antdDeprecatedProp.test.ts`):`Alert` 正文用 `title=` 不用 `message=`(`description=` 是正选,放行)、`notification.*` 用 `title:` / `actions:` 不用 `message:` / `btn:`,判定依据指向 `docs/ui-copy-guide.md §4.14` 与安装版本的 `.d.ts`;浏览器原生通知 `fireWebNotification(...)` 与自定义组件的同名 prop 不在判定范围内。

- **简历预览的多页分栏改成按页边距计算。** 列宽/列间距此前写死 182mm / 28mm——只对默认 14mm 页边距成立:页边距调到 15mm 以上,正文会溢出到第二页(看起来"两页拉通");调到 14mm 以下,第一页右侧、第二页左侧的文字被页边界切掉。现在列宽 = 210mm − 左右页边距、列间距 = 左右页边距之和,第 N 列永远落在第 N 张纸的内容区里,页间隙(分页条)的宽度也跟着变。选 2 / 3 页时仍是几张 A4 左右并排;选 1 页且内容放不下时,不再把溢出部分排成第二列——超出的内容不显示,提示写明「超出上限的部分不会显示——先收紧版式或增加页数才能看全」(由 `EXPAND_OVERFLOW_ON_SINGLE_PAGE` 开关控制)。

- **版式控件三条滑杆等长,读数排成一条竖线。** 之前行距 / 页边距 / 字号三条滑杆各自伸缩(约 90 / 60 / 260px),看起来没对齐;现在弹窗里统一 200px,标签固定 44px 宽(否则「行距」24px 与「页边距」36px 会把滑杆推开 10px)。读数与来源标签各占一列、左对齐,三行读数因此落在同一条竖线上(此前靠右收口,没有标签的字号那一行会整体右移);「随模板 / 随版式」收成小胶囊,不再是一段裸露的说明文字,没设过覆盖时只留空位、不显示内容,拖滑杆时读数也不会跳格。字体下拉与上一行的页边距滑杆**同宽同起点**(200px,左侧补 34px 让开减号按钮),版式下拉的宽度则收到刚好框住 4 个中文字符(`4em + 36px`,长预设名展开后按内容撑开)。字体下拉的选中态改显示短名「系统字体」——这一格只有 200px,全称会被截成「系统字体(不使用自定义…」;完整说明留在展开的列表与悬停 title 里,弹层本身按内容撑开、不再跟着触发框变窄。工具栏(compact)布局不变。窗口窄于 1000px 时退回单列——固定宽度后一行至少要 434px,否则读数会压到「+」上。

- **岗位广场「生成简历」卡片里的「篇幅与版式」重新分行。** 「样式 / 版式 / 板块」回到左边、与行标题对齐,「最大篇幅」独占右端——页数是"排几页 A4"的选择,和前面三个"长什么样"的选项分开站(此前四个挤在一行,要么整排贴左、要么整排贴右)。下面的行距 / 页边距 / 字号 / 字体本来就是同一套两列栅格,未改动;预览弹窗工具栏用的是同一个组件,也不受影响。

- **AI 润色卡片支持「继续改写」。** 改写结果下方多了一个「继续改写要求」输入框:以上一次的改写结果(含你手改过的内容)作为输入再送模型,可以反复迭代,直到点「替换选中」或取消。原来的「重新改写」仍然从原文重来。超过 4000 字时会禁用继续改写并提示先精简。动作区改为吸在卡片底部,卡片变高时按钮不会被挤出可视区。

- **换「版式」时,下方的行距 / 页边距 / 字号会跟着变。** 之前换版式只改了个名字:手动拖过的行距、页边距优先级更高,会把新预设整个盖住(选了「紧凑」,预览和滑杆都停在原值)。现在换版式会先清掉该预设管辖的手动覆盖,滑杆直接显示预设值;「紧凑」行距 1.45 / 页边距 11mm / 字号 ×0.94,「舒展」2.0 / 20mm / ×1.06(预设新增了字号系数)。行距与页边距旁的来源标签也从「随模板」变成「随版式」。字号仍是独立一档:用户拖过之后以自己的值为准。

- **「我的资料 → 简历照片」单张上限从 2 MB 放宽到 5 MB。** 前后端同步:`MAX_PROFILE_PHOTO_BYTES` / `PHOTO_MAX_BYTES`、上传提示与校验文案、使用指南。岗位备注图片仍是 2 MB 一档(一次最多两张,跟着放宽会撞 8 MB 请求体上限),校验函数改成按调用方传上限。

- **AI 润色卡片改成贴着右键位置出现,并且可以拖动。** 原来卡片固定让开整个右键菜单的高度(210px),总在点击处下方两百多像素;菜单点完就关闭了,卡片现在直接占用它的位置(指针右下 8px,下方放不下会翻到上方)。按住标题栏可以用鼠标左键拖动卡片,位置被夹在视口内;拖动只在本次打开期间有效。

- **版式控件改成两行两列,并去掉四颗「恢复默认」图标。** 第一行是「行距 / 页边距」,第二行是「字号 / 字体」,两列铺满整行,右侧不再空出半屏;恢复默认改成**双击标题**(字号 / 行距 / 页边距),已经是默认值时双击不提交。加号 / 减号上不再弹悬停提示(可访问名与读屏支持保留)。

- **简历预览的版式控件重新排版。** 字号 / 行距 / 页边距 / 字体从"一串靠左挤着"改成等宽四列、每列带常驻小标签(之前只有弹窗里才有标签),宽屏下铺满整行、窄屏自动折行。

- **简历预览底部的「取消修改 / 保存修改 / 导出」与上方按钮对齐。** 确认行改用 subgrid 复用同一套列,三个按钮落在上方按钮的同名列上;不支持 subgrid 的浏览器退回同列宽的普通网格。

- **字体的展示名可以自己起,原始文件名照样保留。** 「设置 → 应用 → 简历字体」的「···」菜单里新增「重命名」,只改展示名(存 `fonts/names.json`),**磁盘文件名与 `format_config.font` 写的键都不动**,所以已经用过这款字体的简历不会失联;列表里展示名旁边仍标着原文件名,简历字体下拉里也一并显示。新增 `PATCH /api/settings/fonts/{name}`。

- **设置里改完大模型配置,页头的模型下拉立刻刷新。** 此前只有切换路由才会重新取数,于是"设置里换了模型、顶上还显示旧的"。现在保存 / 启用 / 删除记录后广播一次,页头下拉与设置页各自重新取数。

- **网申资料的「粘贴文本识别」移到与简历资料相同的位置。** 两个入口都在「我的资料」页头、「取消 / 保存全部资料」左侧,切换分页即切换目标;网申资料区块里原来的按钮已删除。

- **许可证由 MIT 变更为 Apache License 2.0。** 在保留商用、修改与闭源分发自由的前提下,补上显式的贡献者专利授权,以及「发起专利诉讼即失去授权」的终止条款;`LICENSE`、README 的徽章与许可证章节、`frontend` 与 `desktop` 的 `package.json` 同步更新。全部 1,902 个第一方源文件(`backend/`、`frontend/src/`、`scripts/`、`desktop/` 下的 `.py`/`.ts`/`.tsx`/`.js`/`.mjs`/`.css`/`.sh`/`.ps1`)首行加 `SPDX-License-Identifier: Apache-2.0`,shebang 与 Python 编码声明保持在最前;5 个已顶满 500 行预算的文件(`desktop/main.js`、`ProfilePage.tsx`、`TouTouOrb.tsx`、`boss_search.py`、`test_api_datasets.py`)暂不加头,避免破 `test_file_size_budget`。**1.0.2 及更早版本仍按 MIT 授权**——已发出的授权不可撤销,Apache-2.0 自本版本起生效。仓库内暂无 `NOTICE` 文件,因此不产生额外的 NOTICE 传递义务。

- **通用手写简历改成从空白开始,需要资料时一键带入。** 以前新建通用简历会自动把资料里的姓名、总结和全部经历/项目/技能/奖项倒进表单,用户往往要先删掉一半才写得下去。现在打开是空表单,头部多一个「填入我的资料全部数据」:表单空着就一次填满(基本信息 + 六个模块),已经有内容则先问「将用「我的资料」覆盖当前已填写的内容?」再动手,取消则一个字符都不动。岗位详情里的「自行编写」保持原样(只预填基本信息 + 按需追加条目)。

- **预览的「导出」按钮挪到卡片右下角,「取消修改 / 保存修改」这类额外操作独占一行。** 导出按钮不再整行铺开,而是落到「离线分享」所在行的最后一格、右对齐贴着卡片右下角;生成预览阶段的三个按钮(重新生成 / 去简历中心 / 完成)单独一行靠右,不会被挤进 150px 的格子里。窄屏(≤720px)退回整行,不会只占一小格。

- **导出弹窗只剩「格式 / 水印 / 脱敏」三块,版式统一由预览决定。** 删掉了「版式」整栏(页数 / 字号 / 页边距)与「预览脱敏效果」按钮:导出的字号、页数、页边距、行距全部取自你眼前的预览草稿,不再有「面板 14mm、预览 11mm」两套数字;要改就在预览工具栏改,改完直接导出就是同一个版本。「显示照片」改名「包含照片」并归入「格式」;弹窗宽度 560 → 520。**页边距没有丢**:预览工具栏的行距旁新增「页边距」(8–26mm,可复位为随模板),写的是 `format_config.page_padding`。

- **「目标岗位」列收窄到 200px,长岗位名 / 公司名不再把表格撑开。** 单元格允许换行,单个标签超长时截断并保留完整文本可查;其余列宽未动。

- **求职进度的配色按阶段语义重做,暗色下不再是「同色卡片 + 撞色边」。** 新增 6 个阶段 token(已投递 / 筛选中 / 测评笔试 / 面试 / Offer / 已结束),浅色低饱和、深色降饱和提亮,与状态标签同一套语义(此前漏斗用绿/琥珀/紫、标签用青/紫/绿,同一条记录左边竖条和右侧标签是两种颜色);漏斗与卡片**共用一份**阶段色映射(原来两处各写一遍,同一个蓝写死两次,还有一条永远命中不到的 index 5);步骤卡改用次级表面色 + 强描边,暗色下与漏斗容器分层;进度条改成不透明实色数据条,选中态外发光从 18% 降到 12%(暗色 16%);统计卡的彩色顶边由 `nth-child` 改为显式类名——此前后端统计报错时先渲染的那行错误文案会抢走「总数」的灰边,五张卡整体串位。

- **备份不再单独勾选「包含大模型 API Key」。** 密钥跟随「备份中包含全局模型配置与 API Key」这一项:勾选时配置与密钥一起带走,取消则两者都不带——用户不需要理解"配置段"和"密钥"是两份东西。

- **求职进度的阶段条改成实色数据条。** 原来的渐变在 100% 时是一条从灰白到彩色的宽带,像加载动画;容器还混了一块硬编码的 `#fbf8ff`,深色下是一块亮斑。现在容器用表面色,进度条用阶段色实色 + 轻微透明,高度 6px。

- **深色主题按新 token 表重做,表面层次从两档扩到四档。** 页面底 / 次级面 / 卡片面 / 浮起面各自有明确取值(#14171c / #15181d / #1a1e24 / #22272f),正文 #e8ecf2、次级文字 #aab5c4、链接 #8fc0f0;表头、菜单选中项、下拉选中项、Tooltip 都有独立的深色值。浅色 token 一字未改。根因是旧实现把浅色的 `colorText=#223047`、`colorTextHeading=#16365c`、`colorLink=#0958d9` 覆盖在 `darkAlgorithm` 结果上,深色下正文对比度只有约 1.3:1。

- **弹窗容器类名跟上 antd 6.6.5:`.ant-modal-content` → `.ant-modal-container`。** 旧类名在当前版本已不存在,仓库里 11 条「弹窗最大高度 / body 内部滚动 / 圆角阴影 / 深色弹窗背景」规则全部未生效——技能详情、清理旧文件这类长内容弹窗才会上下顶格。现在容器统一 `max-height: calc(100dvh - 96px)` + 固定标题/页脚、body 内部滚动;两个问题弹窗另加 `maxHeight` 与清单内滚动。

- **「发布时间」列真的按发布时间排序。** 新增后端排序键 `posted_desc`:按 `date(posted_at)` 倒序,解析不出日期(空串、「3 天前」)的岗位回落到导入日期,不会在点击排序后整批跳到最前/最后;列头 tooltip 改为「按发布时间排序(没有发布时间的按导入时间)」。默认排序不点亮任何列,表头不再常驻灰底。

- **页头只留高频入口:全站搜索的放大镜与全局模型选择器都移除了。** ⌘/Ctrl+K 与搜索弹层保留(改由全局浮层挂载,任何页面仍可唤起);模型配置统一在设置页。

- **桌面版的「打印 / 另存 PDF」改用 Chromium 渲染,和预览同一个引擎。** 浏览器版仍是后端 PDF 引擎(可用),但两套排版在分页、行距、孤行上会有细微差异;桌面版现在把**预览用的那份 HTML** 交给 Electron 内嵌的 Chromium 打成 A4(打印背景、无边距,留白由模板自己决定),并弹出保存对话框。隐藏窗口 + 关闭 Node 集成,HTML 里就算有脚本也拿不到系统能力。

- **深色主题不再是「外壳变暗、里面还有白块」。** 全仓 CSS 的 534 处硬编码颜色已改成语义变量(新增信息色、强调色、半透明面板、遮罩、品牌实底、提醒底等 token),并新增一条守卫测试:**业务 CSS 里再写死颜色会直接测试失败**(白字与掩膜/滤镜/滚动条除外)。深色下也补齐了全部颜色 token 的对应值,缺一个同样失败。

- **新增 5 个快捷键,全部有真实落点。** `Ctrl/⌘+Shift+N` 新建简历(简历中心有了自己的「新建简历」按钮,不必再先去岗位页)、`Ctrl/⌘+Shift+D` 打开页头数据集菜单、`Ctrl/⌘+[` / `Ctrl/⌘+]` 给当前列表翻页(所有用 antd 分页的页面通用)、`Ctrl/⌘+/` 打开求职助手并聚焦输入框。落点不存在时不拦截按键——按了没反应,比吞掉一个什么都没发生的按键好。

- **快捷键默认键位逐条核对过计划表:六处「不符」是刻意规避浏览器保留键,不是漏改。** 导出 `Ctrl/⌘+Shift+E`、新建简历 `Ctrl/⌘+Shift+N`、设置 `Ctrl/⌘+Alt+S`、助手技能 `Ctrl/⌘+Alt+K`、深色/浅色 `Ctrl/⌘+Alt+D`、上一页/下一页 `Ctrl/⌘+[` / `Ctrl/⌘+]`,全部有真实落点(`GlobalShortcuts`、`useAppShortcuts`、简历预览)。没有采用 `Ctrl+E`(浏览器地址栏搜索)与 `Ctrl+,`(浏览器/系统设置):这两个组合在多数浏览器里到不了页面,绑上去等于按了没反应。设置页给相关动作加了「?」说明(含规避理由),上下页的标签改为「上一页(列表)」「下一页(列表)」——它们作用于当前列表的分页控件,**不是**简历预览翻页。

- **模型配置可以设一份全局的,所有数据集共用。** 一份数据集就是一个完整的库文件,「用哪个模型」本该是跨数据集的偏好:在「秋招」和「社招」两份数据之间切换时,不必每换一次重填一遍地址、模型和 API Key。设置 → AI 模型最上方新增「全局模型(所有数据集共用)」:开关打开且地址与模型都填了才接管所有调用,否则各数据集仍用自己的配置。配置存在数据目录的 `global_settings.json`(密钥加密、不进备份包),关掉开关或点「清除」即回到按数据集配置。

- **自定义简历字体:自己上传 ttf/otf/ttc,导出 PDF 用它。** 不随应用内置任何字体——一款中文字体动辄十几 MB,体积与授权都留给用户自己决定。系统字体缺少你需要的字形(生僻字、繁体)时,在设置 → 应用 → 简历字体里上传一个字体文件,点「设为当前」后 PDF 导出即生效;删除当前字体会自动回到系统字体。界面上写明**只影响 PDF 导出**——网页预览与界面仍跟系统字体走。

- **岗位解读结果会缓存下来,再打开是秒开。** 解读是一次 10–60 秒的模型调用,此前每次打开弹窗都重新生成;现在结果连同岗位内容的指纹一起存在岗位上,打开直接显示,标题/描述/要求/标签任一改过就提示「这份解读可能已过期」(旧结果仍可读,是否重新生成由你决定)。「重新生成」需要二次确认。

- **简历列表默认按「最近修改」排序,还能按生成时间、标题、公司、岗位排。** 此前只有「最近生成」一个顺序:在一份老简历上改完内容,它仍停在很下面。现在列表接口新增 `sort` 参数(默认 `updated_desc`),记录新增 `updated_at`(迁移 `0047_resume_updated_at`,升级时用创建时间回填,所以升级那一刻顺序不变);改正文、调版式、改备注都会把它顶到最前。筛选栏多了一个排序下拉,选择写回 URL。

- **可以把文件直接拖进应用了。** 拖到窗口任意位置出现「松开即可导入」,松手按扩展名分发:`.zip` 问一句后导入为新数据集(当前数据不受影响);`.json` 作为一份新手写简历保存;`.csv` 引导到岗位页(多文件与粘贴表格仍在那里);其它类型明确说不支持。拖选中的文字或页面元素不会触发。

- **深色主题:页头、侧栏、卡片、弹窗、表格一起变暗,简历预览仍是白纸。** 设置 → 外观里可选「浅色 / 深色 / 跟随系统」,选择记在本机;`html[data-theme]` 驱动自定义 CSS,antd 组件走 `darkAlgorithm`,原生滚动条与输入控件也跟随(`color-scheme`)。简历预览与导出的 PDF/打印结果**不跟着变**——那是要投出去的东西。切换深色/浅色也绑了快捷键(见下条)。

- **新增 4 个有真实落点的快捷键。** 切换深色/浅色(默认 `Ctrl/⌘+Alt+D`)、打开设置(`Ctrl/⌘+Alt+S`)、打开助手技能(`Ctrl/⌘+Alt+K`)、在简历预览里打开导出(`Ctrl/⌘+Shift+E`)。设置页可改键或解绑;没有真实落点的动作**没有**加进表里——按了没反应比少一条更糟。

- **存储占用卡新增「清理旧文件」:先看清单,再移走,永不硬删。** 列出 `backups/`、`backups/.trash`、`runtime/logs`、`logs` 里超过 30 天没变动的文件与合计体积;确认后移到数据目录下 `.cleanup-trash/<时间戳>/`(按原相对路径归档),数据库、附件与 `datasets/` 不在扫描范围。移不动的文件(被占用)逐个报数,其余照常清理。

- **离线分享包多了一个「下载离线包(zip)」。** 一个 zip 里是单文件 HTML(样式与照片全内嵌,双击就能看)、PDF(可直接转发)与一份说明(怎么打开、怎么按 A4 打印)。本机没有中文字体时 PDF 缺席,但整包照常生成,并在说明里写清原因。

- **全站搜索挪到了页头右上角(⌘/Ctrl+K)。** 任何页面都能唤起,输入 250ms 防抖后按「岗位 / 简历 / 其它」分组展示,点一条直接跳转;空输入不发请求。数据仍只来自本机,不联网。

- **岗位解读不再「一打开就转圈」。** 打开先说明这一步会发送什么(岗位标题、公司、JD 与标签,**不含**整份简历)、大概多久、能不能走开,点「开始生成」才真的调用模型;生成中可以「取消生成」(立刻停止等待,不是失败),也可以关掉弹窗后台继续(完成后照常提醒);「重新生成」会先确认覆盖当前结果。

- **快捷键可以解绑了,不再只能"改成别的键"。** 设置 → 应用 → 快捷键里每一行多了「清除」(二次确认):解绑后这一行显示「未绑定」,对应的组合键不再触发任何动作,刷新后仍然保持;「恢复默认」随时能把它救回来。存储格式升到 v2(`null` 表示显式解绑,与"没配过用默认"区分开),旧设置读取时自动升级、改过的键不会丢。与其它动作冲突的组合仍会被拒绝且不保存。

- **粘贴识别资料时不再"上传一完就打勾"了。** 以前进度条跟的是附件上传字节,`upload.onload` 一到就画满并显示成功——可服务端此时才开始抽文本、调模型,用户会以为卡住了。现在上传阶段只占整条等待的 0–15%,字节发完后切成不确定态(转圈 + 「正在解析资料…」),不显示假百分比;识别成功才收起弹窗,失败仍留着重试。

- **导出只有一个按钮了:格式、水印、脱敏(含效果预览)、页数、字号、行距都在同一个面板里。** 以前预览页有「下载 PDF」「更多格式」「导出选项」「一键脱敏」四条路径,各自拼一套参数——于是"预览里调过、导出没生效"可能只出现在其中一条上。现在底部只有一个「导出」,点开是统一面板;「浏览器打印」在面板左下角。脱敏不再是独立按钮,勾选后就地「预览脱敏效果」(只显示遮罩后的姓名/电话/邮箱),导出仍是独立文件、不回写。简历里还有未完成标记时,409 拦截与「仍要导出草稿」的出口原样保留。

- **数据集导出可以自己起名了。** 点「导出」先弹一个小输入框,默认填 `<数据集名>-YYYYMMDD-HHMMSS`;可含中文,Windows/macOS 不允许的字符会被替换成 `-`,扩展名自动补 `.zip`,空着就用后端默认名。导出全部数据集的默认名是 `全部数据集-<时间戳>`。带时间戳是为了避免"连续导出两次、第二次把第一次顶掉"。

- **桌面版安装目录里多了一个双击更新入口。** Windows 安装后,`ResumeForge.exe` 旁边会出现 `update.cmd`:双击就检查更新,有新版本走「确认 → 下载 → 校验 → 安装」,已是最新也会明确告诉你,应用正在运行时不会再启动一个实例。macOS 的 `update.command` 打开的是发布页(那边没有 NSIS 安装协议,更新是下载新 dmg 覆盖安装,不会假装能静默升级)。

- **欢迎卡片的标题下面不再有一条灰色分割线。** 那是全局 `Modal` 标题的样式,只在「欢迎使用简历通」这张封面上显得多余;现在只对这个弹窗关掉,其它弹窗不变。

- **数据集下拉菜单不再被长名字撑宽。** 菜单宽度上限 360px、条目 340px,超长名字省略显示,悬停可看全名;页头的 chip 保持 220px。技能详情弹窗的内容与页脚之间也留出了间距(正文底部 28px、页脚上方加了分隔线),内容有 3 屏时首尾不再贴边。

- **导出与预览改用同一份版式参数,「7px 预览一页、导出两页」不再出现。** 此前预览走 `/render` 会带上你在界面上调过的字号系数,但导出请求体里没有这一项,后端只能读记录里存的旧版式——所以只要没点过「保存版式」,导出的就是另一份排版。现在 `POST /export` 接受 `format_config`(白名单校验,未知键与越界值丢弃),**请求值 > 记录值 > 模板默认**;统一导出面板会把页数、字号档位、字号系数、行距、页边距一起带上,脱敏预览、浏览器打印走同一条路径。导出用的草稿版式**不写回记录**。Word 导出的纸张也从 python-docx 默认的 Letter 改成 A4(210×297mm),与预览和 PDF 同一张纸。

- **预览里可以直接调行距了。** 版式控件新增「行距」一档:滑块范围 1.2–2.2、步长 0.05,两端各有一个 ±0.05 的按钮与读数,拖动后 120ms 内有结果;没调过时显示**当前样式模板自己的默认行距**(模板之间 1.55–1.80 不等)并标注「随模板」,「恢复默认」是把这个覆盖删掉而不是写回一个数字。导出 PDF/HTML 会按同一个行距渲染。

- **选中文字后的 AI 润色改成右键菜单触发。** 选中一段文字后右键,指针旁出现「剪切 / 复制 / 粘贴 / 全选」;选区在输入框、多行文本框或可编辑区域里时,菜单里额外出现「AI 润色」。只选中文字不再自动弹按钮,没选中文字时保持浏览器原生右键菜单。剪切/粘贴只对可编辑区域可用,复制对普通段落也有效;AI 润色仍只发送选中的那一段文字。

- **字号滑块可以按 0.1px 调整,两端还能点按微调。** 手动字号仍为 5–18px,滑块步进从 0.5px 改为 0.1px,读数保留一位小数;滑块左右各有一个「缩小 / 放大」按钮,每点一次 ±0.1px,到 5px / 18px 自动禁用。自动一页的收紧阶梯不变。

- **日期年份下拉可覆盖未来 5 年,默认仍从今年开始。** 年份下拉的未来范围从「今年 +15」收敛到「今年 +5」,打开时第一项仍是今年;之前已选的更远未来年份仍会保留在列表里,不会变成选不中的值。

- **「我的资料」拖大板块时,所有大卡片一起折叠成紧凑条。** 按住排序手柄后,所有分区(包括没被拖的)一起收成 52px 的紧凑条,被拖的那张紧凑卡片**本身**跟着指针走(不再有额外浮条);松手、取消或中途离开页面都会恢复展开,并自动把视角滚回这张卡片、居中显示。此前只折叠被拖卡片,其余上千像素的大卡片仍铺满屏幕,落点不容易看清;松手后页面还会因为折叠把 `scrollTop` 夹到 0 而跳回顶部。

- **大卡片里的小条目改为「接近邻卡边缘」才换位。** 教育、经历、项目等列表的多条记录,拖动时不再按 48px 行程一过就换:指针**接近**下一张卡片的**底边**、或上一张卡片的**顶边**(约 24px 内)就换一位,并留 6px 滞回;停在卡片中间或边缘 ±2px 抖动都不会换位。把条目拖到父卡片底部时会一次推进到末位(指针已越过后面所有边缘),不会再停在中间。

- **岗位详情的「自行编写简历」只预填基本信息,其余模块留空。** 从岗位详情进入时,姓名、联系方式等基本信息仍从资料带入,个人总结与教育、经历、校园、项目、技能、荣誉默认都是空的;条目旁不再显示「从资料填入本条」,只在每个模块顶部保留「从我的资料追加一条」。通用手写简历与手动调整简历不受影响,仍保留条目级填入。

- **「岗位优化建议」先说明再生成,可以后台跑完,也可以取消。** 打开弹窗先看到说明卡(要读什么、大概 10–60 秒、能不能走开),点「开始生成」才真的调用模型;生成中显示已用时与「可以关闭此窗口,生成会继续」,点「取消生成」才中止(关窗口不中止)。关掉窗口后在后台继续,右上角有带「取消」的常驻通知,完成/失败都会通知;重新打开直接看到上次结果、不会重复扣一次模型调用;「重新生成建议」会先确认再覆盖;简历内容改过则旧建议自动清空,避免对着旧正文点采纳。

- **桌面版普通退出不再被弹窗拦下,但更新安装的拦截没有放宽。** 端口探测改为最多三次重试并区分「仍在监听 / 已关闭 / 读不出来」——连接被重置或探测超时不再被当成「后端还活着」,这正是以前普通退出也会弹「退出时未能确认后端服务已停止」的原因。现在普通退出只在 `desktop-main.log` 里留一行证据(不再弹阻塞对话框),等待上限从 10 秒降到 6 秒;**只有「已有待安装更新」时才会因为停止未被确认而阻止安装并报错**(这条路径等待上限 15 秒),不会覆盖可能仍在写数据的安装目录。

- **生成简历时的板块顺序默认跟随「我的资料」的顺序,并且这份顺序与字号系数会随简历一起保存。** 此前在生成界面调过的这两项只在本次生成里有效;现在它们写进这条简历记录,生成后关闭、再从简历中心打开(编辑、预览、再次导出)都保持你调过的那一份。没有记录的老简历继续用内置默认顺序,外观不变。

- **字号滑块可以一直拖到 5px。** 手动范围由 11px 放宽到 5–18px,拖到 5px 导出的 PDF 也是这个字号,小于 8px 时读数旁提示「打印可能看不清」;**自动一页的收紧阶梯没有变化**:它仍以 11px 为下限,四个字号档位的收紧结果与之前逐字一致。

- **手写简历的左右两栏各自独立滚动。** 宽屏下左栏(编辑)与右栏(岗位要求参考)不再共用一条滚动轴:改到简历末尾时右侧参考内容仍停在原处,反之亦然;窄屏仍是单列 + 「查看岗位要求」折叠面板。从预览点字段跳转、以及校验失败后的定位仍会滚到目标字段。

- **风险扫描改为「先说明、你点了才扫」,并且可以在后台跑完。** 打开质量检测不再自动发请求:先显示说明卡(查哪五类、AI 增强会发什么、预计多久),点「开始扫描」才发;扫描中显示已用时与「可以关闭此窗口,扫描会继续」,可随时取消;离开页面后扫描继续,右上角给出带「取消」的常驻通知,完成时提示「共 N 条风险点」,失败也会通知;关闭再打开面板直接显示上次结果(不重新请求),内容改过则提示「建议重新扫描」。AI 增强有 45 秒硬上限,超过后按本地规则结果返回并在面板上说明原因,不会一直转圈。

- **点「上移 / 下移」后键盘焦点会跟着被移动的模块走。** 移完焦点落在该模块(优先拖拽把手,没有把手时落在模块本身),可以接着按方向键一直移动,移到首位 / 末位也不会掉到已禁用的按钮上,同时向读屏器播报「已移到第 N 位」。

- **交付包归档在磁盘上的文件名改为「内容摘要 + 白名单扩展名」。** 旧名字是「sha256 前缀 + 安全化后的原始文件名」,用户文件名经过净化但仍参与路径拼接;现在磁盘名只由服务端侧的值构成(内容 sha256 前 16 位 + 按 `export_format` 白名单取的扩展名,不认识就 `bin`),用户文件名只存在数据库的展示列里,不再进入任何路径。**已有归档不受影响**:数据库行仍指向旧文件名,继续可下载、可删除;新的归档(同一份字节 + 同一格式)仍内容寻址去重。同批把交付包 409(来源修订已过期)的响应文案改成静态字符串并记录日志。

- **桌面壳依赖安全升级:electron 44.7.0 → 44.9.3。** 覆盖 GHSA-jr5w-w6x2-84p9(eMudhra 中间证书事件缓解,44.x 补丁线修复,不跨大版本);同批把供应链告警涉及的传递依赖钉到补丁版(`undici` 6.29.1 / 7.30.1、`fast-uri` 3.1.9),桌面 npm audit 门禁回到 0 findings。

### Removed

- **格式刷整个功能删除。** 「局部排版工具」里的复制样式按钮(复制一条的排版再应用到其它条目)移除:它复制的是"源对象显式设过的键",用户很难预期会粘上哪几个,而「多选批量」已经覆盖了同样的场景。`FormatPainterButton`、`features/resume-layout/stylePainter.ts` 与相关文案一并删除;**「固定这条的当前样式」保留**(规则搬进 `features/resume-layout/styleFreeze.ts`,语义不变),它解决的是另一件事:把这一个范围的当前生效值固化成显式覆盖。

- **纸面选中文字右键加粗不再提供。** 制作台接收层与宿主的 `richEdit` / overlay 死链移除;需要强调文字请用「局部排版 → 文字级别 / 对象级排版」里的加粗(作用于整个条目或某一段),导出 HTML / PDF / Word 同样生效。
- **死代码清理。** 删除无生产引用的 `useResumeDocument`、`legacyContentBridge`、`useRichTextOverrides` 及其测试,以及动作条上恒为 `false` 的 `editorSaving` 死 prop。

- **制作台删除 v1 遗留入口与重复挂载。** 删除「板块顺序」控件(v2 分区顺序由结构页签拖拽决定)、「简历结构示例」示意弹窗(用真实画布 / 结构页签替代)、独立的「角色局部排版 / 局部排版工具 / 条目字段排版」三个左栏分组(能力分别并入「样式」组与「本份简历结构」页签);`ResumeTemplateControls` / `RoleStylePanel` / `EntryLayoutPanel` 组件本身保留,只是不再重复挂载。
- **删除 v1 渲染路径与字段排版实现。** 旧分区渲染宏 `_resume_blocks.j2`、`profile_field_lines`、`resume_field_layout*`、PDF 侧 `research_render` / `short_sections` / `profile_fields` / `custom_sections` / `section_presence`、Word 侧 `docx_sections` / `docx_academic_sections` / `docx_education_section` / `docx_section_shared` / `docx_custom_sections` / `docx_language_section` 已删除;`_resume_sections.j2` 保留为一行兼容别名(历史自制模板照常可用),启动 preflight 与打包清单已同步。**新建简历弹窗里的 v1 编辑器(ManualResumeModal → ResumeEditorModal)本批保留**,下一批切换到新编辑器;新建 / 从资料的投影写入也随下一批收口。
- **移除「我的资料」里的「收起更多字段」。** 字段不再默认折叠,进入编辑态即显示当前方案里的全部可见字段;不再有「更多字段 / 收起更多字段」入口,也不再读写字段的折叠偏好。隐藏 / 恢复字段的能力保留(隐藏后不进 AI 与导出、内容仍在库里),后端字段与接口保持兼容、不做迁移。

- **「补充事实」模块从界面下线,资料改由「资料大字段」录入。** 资料页的「补充事实」区块、独立编辑/字段设置入口、助手能力地图条目与网申事实取数一并退役;论文 / 科研项目 / 知识产权 / 证书资质 / 语言能力等改由 13 个大字段录入,培训不再提供内置录入入口(确有需要可自建大字段)。**旧数据不删**:`profile_fact` / `profile_fact_field_pref` 的表与行原样保留、继续进备份,回收站里历史事实仍可见与恢复,但新的生成 / 新建 / 网申链路不再读取它们。

- **自制简历模板下线,工作台只留「版式」。** 工作台第二个页签从「简历模板」改名「版式」,并整块删掉「模板」卡片:**导入 HTML**、**导入参考模板**、**复制为新模板**、内置模板缩略图墙与自制模板列表都不再出现(空态里那句"点右上角的复制"也一并消失)。理由很简单:写 HTML/CSS 的模板只有极少数人用得上,而"想压进一页"的人要的是行高、页边距、区块间距、强调色——那正是「版式」,只填参数、不用写代码。**这一批只下线界面,不动数据**:`/api/resume-templates` 的接口与导出函数一个不删(未引用的模块仍会被 `tsc` 检查),`TemplateImportModal` / `StyleTemplateEditorModal` / `BuiltinStyleGallery` 三个组件文件保留并在文件头标注"已下线",已存在的自制模板继续按原样渲染、继续随数据集备份迁移,只是不能再新建或编辑。

- **简历字体不再提供上传入口。** 设置 → 应用 → 简历字体里的上传按钮删掉了:一款中文字体动辄 30MB,体积与授权责任都该由应用统一承担,不该让用户自己去找文件。**升级前导入过的字体不受影响**——它们仍在列表里,继续可改名、可删除、可设为当前;`POST /settings/fonts` 与 `uploadResumeFont()` 保留(既有测试与脚本还在用),只是没有界面入口。同一批里说明段与删除确认也重写了:删除是**直删**,正文明确写"字体文件会被直接删掉,界面上没有恢复入口",不再套"删除先进回收站"的说法。

- **三套内置模板下线,内置模板从 8 套减到 5 套。** 下线的是**技术**(`technical`)、**社论**(`editorial`)、**分栏**(`split`);保留**经典 / 现代 / 精简 / 优雅 / 极简**,名字与顺序照旧。模板一览与生成前的模板下拉都只剩 5 个选项。**旧简历不会变形**:`RESUME_TEMPLATES` 仍保留 8 条的解析与模板文件,引用已下线模板的简历仍按原样渲染、导出结果不变,只是新建时不再提供这三套。README 与使用指南里的"内置 8 种"同批改成 5。

- **删除独立的「全局模型」入口:** `GlobalLLMCard`(多套 profile + 开关)与 `/api/settings/global-llm*` 六个端点全部移除,能力合并回唯一的大模型配置模块(页头模型切换器随后按新接口重建,见 Added);`api/settings_llm_profiles.py` 拆成 `services/global_llm_profiles.py` + `api/settings_llm_global.py`。

- **删除设置页「主题外观」卡**(`AppearanceCard`):主题控件上移到页头。

### Fixed

- **制作台「基本信息」页与其他条目的排版不一致。** 5 个位置组(姓名行 / 姓名旁小字 / 求职意向行 / 联系方式行 / 照片)原来各自是一张带边框的小卡片、组标题按内容居中,看起来像五个独立控件;现在合并进同一张卡片、组内无边框、组标题与条目卡的字段标签左对齐到同一条竖线,字段按等宽网格排(窄栏两列)。拖动换位、双击展开、右键菜单、照片裁剪、添加页眉字段全部照旧。
- **中栏板块页签条会跟着内容滚出视口。** 真正的滚动容器是中栏的 `.document-editor`,页签条是它的第一个子节点,滚下去之后整条(连同右侧「+」)一起消失,只能滚回顶部切板块。现在页签条与「+」贴顶吸住,下方不再透出内容。
- **「已设置断页」的空态被撑成 160px。** 空态说明那一行继承了 `flex: 1 1 160px`,在没有断页时把整块撑成约 195px 高的空白。现在空态只有一行 18px;有断页时也不会在提示下多出一块空白。head 里那条说明保持原样(380px 窄栏下仍能换行,按钮不被挤变形)。
- **「版面」组的四行参数在宽栏窄栏都参差不齐。** 文档模式用 antd `Row/Col`、「篇幅上限 / 字段语言 / 联系方式换行」用 `Space`,两套布局各算各的起点,四行控件左缘分别落在 131 / 93 / 95 / 123。现在统一成「标签列 + 控件列」两列网格(中文标签列 76px、英文 112px),四行控件对齐到同一条竖线;窄栏竖排时不换行、无横向滚动。
- **结构页签里的改动不反映到草稿纸面(全局)。** 画布把「发起渲染那一刻的宿主 html」当作实时结果的有效性判据,而那个值是在结果回来时才读的、判据实际恒真;一旦按字面挪到请求起跑点,又会把「改一次结构、宿主 html 跟着变」的正常路径整份丢弃。现在改成**序号水位**:每发起一次渲染 +1,保存写回时把水位抬到当前值,只有"发起于保存之前"的结果被丢弃——结构一改纸面就更新,保存返回的权威 html 也不会被同一次改动的旧结果盖回。
- **左栏点任意按钮,整列跳回顶部。** 滚动锚定的 hook 先同步复位再重试,而回落分支在动画结束或组件卸载时会用一个 0 值快照覆盖当前滚动位置。现在只在拿到有效快照后才写回,回落也按时读取当前位置;点列数 / 对齐方式 / 恢复默认后视口纹丝不动,切页签仍按预期回到顶部。
- **左栏或中栏选中,右栏纸面不定位。** 纸面被 transform 缩放后,帧内 `scrollHeight` 等于 `clientHeight`,旧实现只调 iframe 自己的 `window.scrollTo`(完全空转)。现在把"目标在帧内的位置 × 缩放比 + 帧偏移"换算到外层相框的内容坐标后居中(只写 `scrollTop`,不碰 `scrollLeft`),并在纸面重排后重放;反过来在纸面上点字段只换高亮、不滚动。
- **条目内拖拽换位无效,字段还能被拖进正文。** `dragover` 只在合法目标上 `preventDefault()`,落在非法位置时浏览器根本不派发 `drop`,用户看到的是"拖了没反应";同时 field 的允许位置里还留着 `body`,中栏会同时长出字段控件与正文控件,纸面却静默丢掉那个字段。现在拖拽按「域」判定(标题行 / 元信息、正文前后灰字段、正文、隐藏),非法落点与跨域都给明确提示(「已经在这个位置」/「这个位置放不下该字段」),`field` 的位置下拉与结构树的「移动位置」菜单都不再出现「正文」。

- **2026-11-01 交接审计的建议项一次性收口(迁移 / 导出 / 审计接口 / 制作台 / 投递台)。**
  - **迁移与失败分类**:删除死迁移 `0070_failure_taxonomy`——它给求职进度加的自由文本三列是同一需求的第二套实现,真实现是 `0073` 给 `apply_task_item` 的稳定码;`0071` 的 `down_revision` 直接接回 `0069`,模型与接口里的三列一并移除。为不打断旧客户端,`TrackRecordIn` 仍**静默丢弃**这三个已废弃键(不写库、也不 422),一个发布周期后可删这段垫片。新增失败词表跨端守卫,把模型常量、Pydantic Literal、`REASON_BY_CATEGORY` 与前端 `failures.ts` 四处取值钉成同一份,缺码会被点名。
  - **导出纸面**:补齐「日期位置」的端到端守卫——此前只有绘制原语的单测,真实导出链路没人守(审计用不存在的对象 id 测出两档 Δx=0 的假象,实际链路是通的);现在从 `format_config` 到 PDF 字节断言 inline / right 的日期横坐标差 ≥20pt,并断言两档产物字节不同。同时修掉纸面印原始字段名:自建板块复用的 `start_date` / `end_date` 不再以英文 key 印出,标签兜底先查通用字段表。
  - **AI 数据边界可查询**:用量账本新增 `input_scope` / `masked` / `language` / `budget` 四列(迁移 0074),`GET /api/usage/records` 直接返回,审计不必再翻日志;`masked` 用 NULL 表示「入口没声明」,与 False(明确未脱敏)严格区分。新增只读 `GET /api/assistant/tools`,把助手可调用的 69 个工具连同参数 schema 暴露出来,与模型实际拿到的清单同源。
  - **制作台**:「外观」四行颜色默认全部收起(此前第一行默认展开,一进来就多占 40px 色板);条目右键菜单补第 6 项「排版整条…」,画布条目卡与结构树条目行共用同一份 6 项定义,结构树条目行此前根本没有右键菜单;R-10 的滚动锚定 hook 从「写了但零引用」接到结构页签面板上,「切前/后字段区不跳动」才真的有实现。
  - **投递台**:「失败分类」列首改为只显示子码(此前大类名与子码重复,窄列里还把原码挤到换行),原码保留为小字便于追溯。
  - **测试基建**:四个测试文件的迁移 head 改为从 Alembic 实际读取,不再每次新增迁移都手工改四处字面量。

- **分页面板的「日期位置」与元信息加粗在草稿与导出两端都生效了。** 「日期紧随标题」此前在草稿里毫无变化(标题把整行占满,对齐没有可分配空间)、在精确预览里日期直接跑出纸边(PDF 内联分支把整版心画给了标题、日期从右缘起笔)——草稿补了日期位置的伴随规则并排在文档结构样式之后,PDF 先量日期实宽再分配左右两栏,超长日期截断而不是越界。「元信息加粗」的开关此前在 PDF 两侧被硬编码丢弃,现在真实生效。

- **模板试用通道没接线(R-03)。** 宿主把 `resumeId` / 当前正文 / 「清空排版覆盖」的回调漏传,试用面板里的「用这个模板」「采用模板默认」点了没有反应;现在制作台与简历详情两处都接上,`format_config` 清空后可撤销一次。
- **岗位变体的选择性同步列出的是 v1 顶层键(R-23)。** 以前读 `content` 的 `version` / `language` / 数字下标,勾选后后端 `resolve_document_path` 直接 400;现在按 v2 文档列出 `header.<字段>` 与 `<板块>.<条目>`,按板块分组、可折叠、支持全选 / 清空,目标变体里没有的条目会提示「有 N 条未应用」。
- **保存期间隐藏或删除字段,纸面停在旧版本(R-32)。** 挂起期把新内容记成了"已渲染过",`resume()` 回来比对不出差异就不再排版;现在挂起期不写基线,恢复时主动比一次并补排。
- **实时排版失败时只显示一行灰字。** 现在按警示样式写明「纸面未反映最新修改,显示的是上一次成功预览」,并保留「重试」。

- **简历中心「新建简历」第二次点不开、新建界面字段点不了(Q36)。** 第二次打开时投影文档引用相同,`baseDocument === syncedBase` 导致状态不重置,弹窗拿到空文档后不渲染;现在每次打开都重新同步基线与选中态。同时把 `selectedPath` / `onSelectPath` 接进新建界面的 `DocumentEditor`,字段 chip 可点击展开编辑。
- **简历中心长标题左侧第一个字被裁掉(Q35)。** 标题按钮原本是 antd 默认的 `inline-flex` + `justify-content:center`,长标题换行后内容比按钮宽时左右同时溢出,左侧「林」等首字被 `overflow:hidden` 裁掉。现在标题改为块级文本从左侧换行,长标题完整显示。
- **「本份简历结构」鼠标悬停时界面抖动。** 行内操作按钮原在 `:hover` 时才 `display:flex`,会让行内 flex 宽度和右侧计数/徽标位置突变;改为始终占位、只用 `visibility` 显隐,悬停前后字段名宽度、行高与计数位置不变。(Q34)
- **「本份简历结构」定位条目时纸面横向错位(Q32)。** 多页预览每个 iframe 都装同一份多列 HTML;旧定位把 iframe 横向滚到目标所在列,导致当前页只剩一条边缘文字。现在只对目标所在页做纵向滚动,目标不在本帧可见列时跳过,横向位置保持。
- **「本份简历结构」悬停 / 滚动卡顿。** 结构树行悬停不再触发整个面板 React 重渲染和 iframe 高亮 effect 重挂,改为直接向画布注入高亮;163 行实测滚轮 / 悬停长任务从每帧 82–266ms 降到 0。
- **制作台预览一致性(Q05 / Q18 / Q21 / Q22)。** 中栏、结构树、分页开关等所有未保存改动会实时反映到右栏草稿与精确预览;精确模式不再整块空白或依赖视口尺寸变化,内嵌查看器 8 秒未加载会给出在新标签打开的兜底;制作台默认按宽度适应画布、纸宽不超出右栏,草稿与精确各自记住缩放,只有显式放大才横向滚动且只发生在纸面层;预览不再尝试执行排页脚本,控制台不再出现 `Blocked script execution` 报错。
- **局部排版四出口(Q11 / Q15)。** HTML 的项目符号 / 缩进 / 日期位置改为定向选择器,对象根不再平移整条;Word 补齐缩进、正文对齐、日期位置、加粗 / 斜体与 `w:spacing` 字距;PDF 补齐正文 B 字重、字距与段落缩进;三出口项目符号统一为「· / – / 不显示」,PDF 斜体仍按计划如实记为不支持。删除从未有模板落点的「重点文字」(emphasis)角色。
- **长 CV 版面诊断与自动一页(Q19)。** `ResumeLayoutMeasure.page_limit` 按长 CV 上限 10 页校验,普通模式仍由保存 / 渲染路径夹紧到 1–3;长 CV 设 5 / 10 页时不再 422,自动一页逐档试并有进度反馈。
- **纸面点击定位与标签样式(Q26 / Q27)。** 列表值(核心课程、技术栈等)与作者署名现在带 `data-resume-path`,点纸面可定位到编辑器对应字段,结构树反选也能高亮;草稿纸面的专业技能 / 语言能力恢复 `<ul class="skill-list"><li>` 标签样式,正文在前、分类灰字在后,不再输出条目间的「;」。
- **面板与双语 / 来源核对小修(Q07 / Q09 / Q16 / Q17 / Q20 / Q23–Q25)。** 行内 / 单段板块不再显示无效的条目级分页与条目字段排版控件;断页设置首次拨动给一句轻提示,面板顶部固定说明与「切到精确预览」;中栏字段 chip 不再溢出压盖、无横向滚动条;「值加括号」只在正文位置显示;双语对照过滤照片 data URL,宽布局不再重复行内列标签;来源核对下拉不再撑宽卡片。

- **修复真实 AI 生成第一轮发现的 5 项问题。** 项目正文同句重复(跨槽保序去重)、正文残留「- 」Markdown 前缀、自建板块长文本丢标签 / 来源(改 body `FieldItem` + `source`)、量化核对漏自定义字段、岗位导向漏覆盖且无记录(按条目身份合并回填 + `coverage_notes`);第 2 轮真实生成 45/45 复验通过。
- **修复 PDF 条目头右侧信息过长时压住左侧标题。** 右侧时间 / 会议名超出右栏宽度时按可用宽度截断并加省略号,左侧标题超宽同样截断,不再互相叠字;正常长度的产物与以前一致。
- **修复 PDF 摘要中英混排出现碎行。** 段落含中文时改用逐字折行(此前按空格断行,「有 2 / 段数据…」这类短行是断行点选错),纯英文段落仍按单词折行。
- **修复「条目字段排版」设成多列后第二列对不齐。** 补充字段此前按内容宽度排布(flex + `flex: 0 1 auto`),每行第一个字段长短不同,第二个字段的起点就跟着漂——「学习形式 / 院系 / 导师」三行里的「学位类型 / 成绩排名 / 研究方向」各落在一个位置。现在多列默认走**等宽分栏**(CSS Grid,列宽按内容自适应:`repeat(N, auto)` + `justify-content: start`),同一 entry 的所有字段共用一个 grid,列宽跨行一致、三行严格对齐;因为列宽贴着内容,`column-gap` 就是**真实的两列间距**(同一组数据实测 1.6→4.0 倍时可见空白 22px→56px;早先用 `1fr` 时同样的调整只有 17px,绝大部分是列内留白,用户几乎看不出变化)。列间距本身充当分隔,等宽模式下不再输出「 · 」(留着反而让分隔符位置随上文长度浮动)。独占行与末行只剩一个字段时占满整行,不再缩在第一列。同批新增两个可调项:**对齐方式**(等宽对齐 / 按内容,后者还原改造前的顺排并用「 · 」连接)与**列间距**滑杆(0–30 倍字号、步长 0.2、右侧实时读数;0 = 两列完全紧挨,30 ≈ 30 个字号的空白、足以让第二列贴到内容区右缘,滑杆带 0 / 1.6 / 8 / 16 / 30 刻度),均按分区保存。**服务端 PDF 也按列并排绘制**(`pdf_exporter/meta_columns.py`,几何与副行同口径、按列测高后取最大行高),精确预览与导出 PDF 同样响应列数与间距;`flow` 在 PDF 里按语义退回单列行内连接。Word / Markdown / TXT 仍是文本流,一行内继续用「 · 」连接。

  同批还支持**按条目单独设置**:面板在「分区」下面多了一个「条目」下拉,选「全部条目」时改的是分区默认,选中某一条(如教育经历里的「上海大学」)后,列数 / 对齐 / 列间距 / 顺序 / 独占行只作用于这一条,没改的项继续继承分区默认,另有「恢复继承」一键退回。配置存在 `format_config.field_layout.entries.<分区>.<条目id>`(条目 id 是 W01 稳定身份,字符集 `[A-Za-z0-9_-]{8,32}`);HTML / PDF / Word / Markdown 同时生效——合并逻辑放在唯一的行装箱入口里,不给四个渲染器各写一份。**PDF 里奖项(awards)同样支持条目级**(`draw_bullets_with_profile_fields` 收 `items` 后逐条算几何),此前它是唯一按分区生效的分区。条目下拉旁另有「上移 / 下移」两个按钮,直接调正文里该条目的顺序(走大纲面板同一份 `reorder-entry` 命令,不是另造一套排序)。
- **修复网申面板「拖右下角缩放,动的却是左边」。** 缩放的上限原来按 `视口 - 两倍边距` 算,宽度一长,定位里的 `maxLeft` 跟着变小,就把面板往左推——拖右下角得到的是"变宽 80px、同时左移 80px";宽度继续长时右沿还会越过视口、把手和鼠标错位(看起来像镜像)。现在**锚定左上角**:上限按"锚点到视口右边/下边"算,拖把手只改宽高;贴住右边界时宽度停住,不会再推走面板。
- **修复拖「分组快速定位」下边框时下面的清单跟着跑。** 胶囊区在 `position:sticky` 的搜索块里,一变高就把清单往下顶;现在把 `.pick` 的滚动量补上同样的差值,正在看的那几行停在原地。同时拖动期间不再"跟着滚动去点亮当前分组"(那会自己滚一下去露出活动胶囊,看起来正是内容在乱窜),拖完再补一次同步。
- **修复「试用与对比」切到示例内容后纸面长度不跟着收。** 示例预览原来沿用简历的「最大篇幅」(如 2 页),而后端 body 高就是 `页数 × A4`,于是切到示例后正文只占约 0.86 页、下半张全是空白纸(表现正是"页面长度没收起来")。现在示例恒按 **1 页** 要(与模板图库缩略图的 `page_limit: 1` 同口径),「我的内容」仍按用户设定的最大篇幅;请求体的 `page_limit` 与画布 `pages` 共用 `previewPageCount()`,不会各说各话。
- **「试用与对比」的导出格式差异改成一项能力一行,并把漏出来的机器码翻成中文。** 原来是按"格式 → 能力"平铺成一串:同一个格式前缀反复出现、相邻两项没有分隔(`精确 PDF 斜体 近似 精确 PDF 链接可点击 不支持 Word 链接可点击 近似 …`),而且 `manual_page_break` / `keep_with_next` 这类**契约键**被原样印在界面上。现在一项能力一行,左侧能力名按最长一项对齐、右侧只放"格式 + 等级"的短标签(`精确 PDF 不支持`、`Word 近似`),全支持的格式仍然不列;标题改成「导出格式差异」,并在旁边补一句「只列与预览不完全一致的项」。翻译补进 `CAPABILITY_LABEL_KEY` 全量 14 项(`manual_page_break`→手动分页、`keep_with_next`→标题不孤行、`entry_spacing`→条目间距、`body_color`→正文颜色、`custom_font`/`section_order`/`hidden_entries`/`scholar_skills_grid`/`research_fields`/`long_cv`),检查面板的同一份矩阵也一并受益。

- **修复网申填表面板「明明还有空间却拖不动」。** 拖动的基准原来是"输入框位置 + 上一次的偏移"——面板被视口夹住之后,被夹掉的那段差值会一直留在算式里:面板已经顶在 62px 处,手指再往上拖 100px 算出来是 600px、又被夹回 62px,看起来就是顶部被什么东西抵住。现在以面板**实际画在哪**为基准,拖多少就走多少;打开「记住这条」编辑器(宽高都会变)时也立刻重排一次落点,不再先撑出视口再被拽回来。
- **网申填表面板可以拖右下角改尺寸,最小 380×300。** 面板贴着表单会挡住相邻字段,此前只能整块挪走;现在右下角有把手,宽高随意拖,但缩到底就停在一块还能读的卡片大小(不是一条窄缝),尺寸在「记住这条」开关之后仍然保留。
- **网申面板的「分组快速定位」分出「简历资料 / 网申资料」两段,网申分组显示中文。** 一边是教育经历、项目经历这些跟着简历走的,一边是身份、家庭、意向这些只在网申表里补的,混成一排全是胶囊、看不出哪条来自哪里。网申的组名是协议标识(`identity` / `contact` / `school`…),此前被原样印在界面上,于是同一片里一半中文一半英文;现在只翻显示,跳转与折叠仍按原始标识走。
- **「分组快速定位」的胶囊区下沿可以拖动伸缩。** 组多时拉大一次看全、组少时压小省地方;上限仍保证资料区至少还剩 150px,拖到最大也盖不住清单。

- **投递台 · 内推详情的「图片备注」改成弹层预览,不再开新页面。** 此前缩略图包在 `<a target="_blank">` 里,点一下就把人甩到新标签页;现在用 antd `Image`(与岗位备注、资料附件、「我的文件」一致)——点开是遮罩层里的大图,带「查看第 N 张图片」的遮罩文案,右上角可关闭。

- **修复「我的资料」每个分区的标题栏与内容贴在一起。** 分区容器的 padding 上边是 0(只给了左右与底部),于是第一个字段/条目卡直接顶着标题栏的下边框,13 个分区看起来都像"标题压着内容";现在四周统一 16px,新增的样式守卫(`styles/profile-section-padding.test.ts`)钉住上边距不允许回到 0。
- **修复题库「参考答案」按钮的两种状态用同一句话。** 还没生成也写「查看参考答案」,点下去其实是在生成;现在未生成显示「生成参考答案」、已生成且展开显示「收起」、已生成但收起才显示「查看」。并且**先写我的答案再交 AI 批改时会自动补一份参考答案**(批改本来就要拿它做对照),之后就不必再单独点一次生成。

- **修复「查看题库详情」整页白屏(`Cannot read properties of undefined (reading 'length')`)。** 后端每道题都会带一个空的 `review` 字段(schema 默认值 `{}`),前端只看"是不是对象"就把它当成"已经批改过",于是渲染批改视图去读 `strengths.length` 直接崩。现在空对象按"没批改"处理,批改视图自身也对缺字段兜底(历史里存的半成品结果同样能显示)。
- **修复设置页「公告提醒」与「帮助与诊断」两张卡片贴在一起。** 卡间距只由 `.settings-card` 给(22px),而公告卡片漏了这个类名,于是它后面没有留白;补上后与其它卡片一致。

- **修复面试收尾时气泡一直显示「正在思考下一个问题」。** 最后一答提交后服务端其实是去生成评分报告(整场转录再跑一次模型,最慢的一段),但界面仍挂着"思考下一个问题",看起来像卡住或还要继续问;现在后端在进入收尾前先推一条 `phase=finishing` 事件,固定轮数模式前端可提前判断,气泡改成「本场面试结束,正在生成报告…」;若模型提前给出了收尾语,则在收尾语下方补一行同样的说明。

- **修复题库点「参考答案」必报「请求超时,请重试」。** 该接口是现场调模型的整段生成,却走 15 秒的默认请求预算,必然超时;现在开场、逐轮回答、结束出报告、题库生成、答题思路、参考答案、反向优化这些会调模型的面试请求统一改用 180 秒的 `analysis` 预算。
- **修复面试复盘「历史复盘 → 查看详情」整页报错(`Cannot read properties of undefined (reading 'length')`)。** 历史记录的 `analysis` 是模型产物直接落库的任意 JSON,只有 `framework` 而没有 `key_points` / `follow_up` / `pitfalls` 的记录一渲染就崩;现在渲染前统一归一化(缺字段补空数组、非字符串丢弃、全空视为「无分析」),建议列表同样兜住缺失的 `evidence`。
- **修复发消息时「正在思考」抢先、自己的回答要等服务端回包才出现。** 现在点提交就把这句话以「我」的气泡放上屏,之后才是 Jane 的思考气泡;成功后本地占位让位给服务端消息,失败时保留占位并把原文放回输入框。

- **修复自定义字段的值混进项目描述编辑框。** 给系统板块加的自定义字段此前直接渲染在承载区文本框里,项目描述框会显示「标签:值」行、和正文混在一起;现在编辑器先按 schema 剥出承载区里的字段行,描述框只显示描述本身,自定义字段回到各自的独立输入框且原值不丢;保存仍只写回承载区一行(反复保存不重复追加),纸面导出与旧简历不受影响。新增 `carrierDisplay.ts` 与 14 条测试钉住显示层与保存层分离。

- **修复文档大纲拖动排序:拖不到目标行、放下后顺序不生效。** 把 drop 命中从行内提升到整棵大纲树容器,整行都可作为拖动来源;拖动中不再让 React 按新顺序重建 DOM(稳定 key + CSS `order` 呈现),因此拖动过程中的节点不会因重排被卸载;并补边缘自动滚动。松手仍走既有结构命令记一步历史、拖到行外取消,实时预览跟随不变。
- **修复文档大纲拖动过程中被拖条目反复跳动、松手可能落错位置。** 拖动中行不接收指针事件,旧逻辑把「被拖行自己」也算作最近行并映射回起始下标:预览把它挪到指针下后目标弹回起点、下一帧又变,2.4 秒内实测来回切换 35 次,松手若在弹回态会提交错误顺序。现在被拖行自身不产生目标、目标切换带 8px 滞回、边缘自动滚动改为按帧率推进且滚动后隔一帧再重算;拖动后顺序稳定,松手提交与最后预览一致。新增 jsdom 几何反馈环回归与纯函数不变量测试。

- **补齐 Word 导出联系方式换行的回归,并修好「联系方式换行」下拉宽度。** Word 导出在当前实现里已按 `w:br` 正确换行(不是 Word 侧缺陷),本轮新增 `test_export_bundle_contact_breaks.py` 5 条用例钉住手机 / 邮箱 / 城市 / 出生年 / GitHub / 个人主页的换行位置;真正的问题是排版参数里这个下拉只有 34px、选项文字被截断,现在改为最小 260px 的自适应宽度。

- **修复「文字级别」在制作台部分入口够不到。** 打开旧简历时正文里还没有稳定条目 id,文字级别面板、大纲、编辑表单与保存通道都拿不到可作用的对象;现在进入制作台时用 `ensureEntryIds` 物化条目 id 并一路传下去,保存版式时顺带把文档落库(`persistDocumentOnLayoutSave`,对象样式与文字级别持久化的前提);没有选中内容时参数栏显示「去大纲选中」提示而不是空白。新增 `WorkbenchTextLevelReachability.test.tsx`(2 例)钉住整条链路。

- **修复英文界面下删除确认正文夹中文、以及部分文案切语言后不更新。** 软删/硬删的后果说明(回收站可恢复 / 彻底删除不可恢复)从共享中文常量改为语言包 key,删除确认正文在英文界面不再出现中文;下载状态错误、数据集切换错误、模型目录提示、远程搜索选项等原先"事件发生时就把 t() 结果存进 state"的位置改为存 key、渲染期取文案,切到英文后无需重新触发操作即可显示英文。
- **修复窄屏下「文档大纲」排序静默失效。** 屏幕较窄(两栏档,编辑内容收进抽屉)时编辑列不挂载,结构命令的正文落点是空引用被静默跳过:大纲树看起来变了、右侧预览不动,改动也不会被标脏(顶部不出「保存修改」、关闭不提示),等于白改。现在窄档下结构命令直接写入草稿正文并上报文档脏值,保存 / 关闭确认走同一出口;三栏档行为逐字不变。

- **为「板块顺序调整后预览不跟随」补上真实链路回归护栏。** 用制作台真实组件链路新增两组集成用例:①在「排版参数 → 板块」上移后,草稿预览收到的 `section_order` 与画布顺序立即跟随、保存通道带上新顺序(宽 / 窄两档);②改顺序后精确预览重新请求 PDF 且请求体 `format_config.section_order` 是新值(不命中旧缓存)。同时覆盖了新建草稿简历路径与"保存 → 重开"路径。制作台内改顺序 → 关闭 → 主预览的同步链路另见上方「放弃并关闭」修复。
- **修复「放弃并关闭」后主预览仍停在未保存的版式。** 制作台里改版式(如板块顺序)是即时重排主预览的,但「放弃并关闭」此前只回滚内容、不回滚版式,导致关掉制作台后主预览仍显示未保存的新顺序、与记录不一致。现在放弃走与「取消修改」同一出口(内容回填 + 版式回滚)后再关闭。

- **修复 Word 导出缺少 GitHub 主页与个人主页。** 预览 / PDF 的页眉联系方式有六个字段,Word 只拼了前四个(手机 / 邮箱 / 城市 / 出生年),GitHub 与个人主页整段丢失。现在 Word、PDF、HTML 共用同一份联系方式字段顺序与文案(含「字段整体不拆行」的装行规则),不会再出现预览有、Word 没有的字段。

- **修复简历照片裁剪不生效、默认被强制裁切。** 照片此前在 HTML 侧被裁剪参数之外的比例框二次裁切(PDF 还先按 cover 裁一次),用户设好的取景看不出效果;现在没有裁剪参数时按原图比例展示、不强制裁成方框或拉伸,裁剪参数在草稿预览 / 精确预览 PDF / Word / HTML 四条链路按同一份生效。

- **修复「我的资料」保存后每个板块的条目自动翻倍。** 保存成功后重载工作区时沿用了"编辑中刷新"的合并口径,把已经提交成功的 `new:` 临时草稿又合并回表单(服务端新条目与临时键对不上),下一次保存再次把草稿当新条目提交,于是每保存一次多一条;保存成功、以及"条目已落库但顺序写失败 / 409"时现在都按服务端真值重载表单,删除条目 / 切组 / 刷新比较等重载仍保留未保存草稿。

- **修复新建简历里的自建板块录入格式。** 自建板块不再显示「自建板块」名字输入框、板块级删除按钮与「添加标签行」自由行入口,改为与系统板块同一套格式:板块名只读(来自当前方案,旧简历退回正文标题)、字段按定义渲染成固定字段表单;没有字段定义时显示一个「内容」字段;老简历已有的正文与标签行仍可编辑、保存不丢。

- **修复系统板块的自定义字段在新建简历里没有输入框。** 编辑器视图现在带上每个系统板块的"承载区"元数据(来自纸面投影同一份规则),项目 / 教育 / 实习 / 校园 / 技能 / 荣誉 / 证书资质(及论文、科研项目)里的自定义字段会渲染成输入框;值写进该条目的承载区「标签:值」行(如项目描述),只存一处,反复保存不会重复追加。旧简历冻结的编辑器快照没有该元数据时降级为旧行为。

- **修复语言能力在制作台里显示成「专业技能」。** 语言能力改为简历正文的独立分区:资料投影不再把它压成一条技能记录,制作台新增独立「语言能力」页签与五个字段(语言 / 水平等级 / 证书 / 分数 / 取得时间),HTML / PDF / Word / Markdown / TXT 五条产物都印出独立「语言能力」小节。已保存简历里原本并进专业技能的语言条目不迁移、保持原样。

- **优化制作台编辑文字卡顿。** 条目字段区按条目 memo、编辑面板的 Form + Tabs 整块稳定引用、状态机回调固定引用并只在宿主需要时合并整份内容;连续输入 10 个字符时条目字段区渲染次数从 180 次降到 0 次、整份内容序列化从 161 次降到 111 次,保存语义不变。

- **制作台 /「手动调整」的板块与字段改为跟随「我的资料」当前方案,并在创建简历时冻结。** 新建简历(手写、AI 生成、前往制作台)的编辑器页签、页签名与每个板块的可见字段来自当前方案投影:自建板块页签显示用户起的名字(如「22333」)而不是泛称「自定义板块」,资料里隐藏的字段不再摆一个空输入框;这份视图随简历创建冻结进「本次使用」快照,之后修改方案只影响此后新增的简历,已保存简历的板块与字段不变(正文和已有值也不会因方案变化被藏起来或清空)。写端点返回的详情不含该快照时,前端按简历 id 黏住首次读到的视图,保存一次不会跳回当前方案;旧简历没有字段级快照,保持原有完整字段显示。

- **修复切换简历照片后旧照片又回来。** 照片字段(`basic_info.photo`)不是普通文本输入(渲染层按 `field_type` 过滤掉了它),资料页一直没有把它注册进表单,而「保存全部资料」只提交注册过的字段——切换主照片只改了本地草稿,从未写进资料条目,重取工作区后回到旧照片,生成的简历也用旧图。现在把照片作为隐藏字段注册进表单,切换 / 上传后随保存一并落库。

- **「我的资料」管理面补齐排版并去掉重复。** 组管理、大字段与字段、字段设置三个弹层此前**只有类名没有样式**,行内按钮与说明挤成一团;现在补上设置行 / 分组 / 确认条的统一布局(一行一条、动作右对齐、行间分隔线、窄屏自动折行),分区标题不再在内容区重复一次;日期区间收窄到 320px(原来三个下拉各占满宽的三分之一);「在资料页显示」从每行重复收成一条图例;填写规则表格补回列宽与分隔线;卡片上的齿轮改为直接打开该大字段的字段设置。

- **修复 Linux CI 上 PDF/Word 字体断言失败。** CI 用 `RESUMEFORGE_PDF_FONT` 固定金样导出字体,而该变量会覆盖测试里现场合成的 `Test Sans` 字体,导致字体族名 / 项目符号 / 缺字形降级三组断言在 ubuntu 作业误报。现在这三组用例在自身 fixture 里隔离该环境变量并在前后重置字体缓存;产品侧「环境变量优先」的 Linux 兜底行为不变。
- **修复简历生成完成瞬间的 `Task was destroyed but it is pending` asyncio 错误与日志误报。** 后台生成在独立线程里自建事件循环,收尾只 `loop.close()`,缺少取消残留任务与 `shutdown_asyncgens()` 两步;真实模型流的内层异步生成器在 GC 时投递的关闭任务恰好在循环关闭时被销毁,因此每次生成完成都刷一条 ERROR。现在按 `asyncio.run` 的收尾纪律清理残留任务,业务异常 / 取消 / 预算语义不变;同时修复日志脱敏过滤器把 asyncio 的 `exc_info=False` 误判为异常元组、给这类日志追加「[日志脱敏失败,异常文本已丢弃]」的问题(脱敏能力不变)。
- **修复岗位匹配分析在模型证据核对失败时不自动重试。** 可重试的失败(模型本次产出不合格)现在自动再试一次,单岗与批量共用同一实现,仍失败才返回可读错误;不可重试失败不重试。
- **修复匹配参考分的年限计算。** 改为按年月计算、进行中的经历计到当前月、资料与简历中同一段经历去重并合并重叠区间;同一份资料从旧实现的 1.5 年被纠正为 3.8 年。
- **修复 AI 端点参数校验返回英文原文并回显超长输入。** 全局 422 处理器统一中文文案、保留 `detail` 数组与状态码;不再返回 `input/ctx`,超长正文不会出现在错误响应里;自定义校验器的中文文案保持原样。
- **修复设置「检查更新」点击后无可见反馈。** 检查完成立即提示「发现新版本 x」/「已是最新版本 x」,卡片内结果保留;失败仍显示错误原因。
- **修复简历删除确认按钮为默认「取消/确定」。** 确认按钮改为具体动作「删除这份简历 / 先不删」,并给行操作菜单增加可配置确认文案能力(其它列表保持原样)。
- **修复生成弹窗「生成过程约需 1-2 分钟」与实际数秒不符。** 改为「通常几秒到几十秒,取决于模型与篇幅」。
- **修复简历中心搜索只有按回车才生效。** 输入停顿 350ms 自动搜索、回车立即生效、清空立即生效,并回到第 1 页。
- **修复网申「打开目标网址」在同一网址已停在错误页时复用并报成功。** 命中同 URL 的标签页也会重新导航并检查导航错误,失败返回可读提示;不新增标签页的复用语义保留。
- **修复 seed 演示数据只写旧六表、新资料模型为空。** 演示数据现在同时写入新资料模型(默认组 / 参与清单 / 资料条目 / 自建大字段),`--check` 自检与守卫测试同步覆盖,演示库可直接打开资料页与生成。
- **修复前端 CI 偶发 `window is not defined` 未处理异常。** 识别 hook 测试漏写 cleanup,antd 通知的逐帧计时器在测试拆卸后才触发 setState;补测试清理,不再出现 Unhandled Errors。

- **修复「新建简历 → 前往简历制作台」必然报 500(服务器处理请求时发生内部错误)。** 根因不在新建逻辑本身:`profile_usage_snapshot.resume_id` 上有唯一约束,而 SQLite 会复用被彻底删除简历的 rowid——用户库里历史快照行仍占着已删简历的 id(641..647),新简历复用 644 回写快照时撞 UNIQUE,于是「前往简历制作台」与 AI 生成完成后的落库都返回 500。现在在绑定快照前先解绑同 id 的历史悬空引用(快照的组名、参与清单、顺序、两枚修订、layout 全部原样保留,仍是"当时那次使用"的不可变历史),回收站彻底删除简历时在同一事务里解除引用,并新增迁移 `0065_snapshot_dangling_refs` 清理旧库里的悬空 `resume_id` / `task_id`(纯数据清理、不删行、缺表或缺少引用列时跳过、幂等、downgrade 为 no-op)。回归用例覆盖老库悬空行、连建 3 轮、purge / purge_many / empty 解绑、attach 冲突,以及"只有最小快照表"的历史库升级。

- **修复网申填表把相邻字段的值写进错误控件(例如「项目名称」被当成「实习时间」)。** 招聘页常把整个区块的标签串进每个控件的旁文,旧启发式会让同区块每个框都"命中所有字段",最后按 DOM 顺序乱分;实时逐项填表面板同样认错。现在旁文像"多个字段标签清单"时只认开头那一段真标签,清单里并列出现姓名/手机/邮箱等两个以上高风险字段就直接判为认不出(宁可不填也不填错);日期「年 / 月」文本框有豁免。新增回归用例钉住歧义控件不产生映射。

- **修复网申专用浏览器冷启动偶发误报「调试端口未就绪」。** 真机实测 stop 后立即 start,Chrome 冷启动需 16.3 / 19.1 / 18.5 秒,原 20 秒就绪上限恰好卡在边缘,偶发超时即回「浏览器已启动但调试端口未就绪」(用户原始报障的文案形态)。等待是幂等的,误报却要用户再点一次,因此就绪上限放宽到 45 秒(覆盖冷启动 + 安全软件扫描的常见尾延迟),并保留原有轮询/失败清理语义。

- **修复网申「打开目标网址」对非法或不可达地址假报成功。** 浏览器已在运行时 `open-url` 跳过了地址校验,且不检查 CDP 导航返回的 `errorText`,于是端口 99999、不存在的域名、拒绝连接都返回 200(页面其实是错误页);现在复用地址校验、检查导航错误并收敛成可读错误;停止状态下读取标签页由 500 改为 409 可读文案,关闭非本次进程拉起的窗口失败时由 500 改为 400 可读文案。

- **修复导出「交付包」必然报 422 且界面无提示。** 导出交付包(PDF / Word / HTML / Markdown / JSON 都走这一个入口)的请求带了 JSON body 却没有 `Content-Type: application/json`,FastAPI 解析失败返回 422,前端既不提示也不下载,用户只看到按钮没反应。补上请求头并新增单测钉住;单格式导出不受影响。

- **修复组编辑里「改名 + 同时修改参与清单」必然 409。** 前端先 PATCH 改名(revision 前进 1),紧接着用旧 revision 保存参与清单,后端条件写判定为冲突,弹「内容已在其他窗口修改」且本次参与清单修改丢失(改名本身生效)。现在改名后用 PATCH 返回的新 revision 保存参与清单,并补回归用例。

- **模型用量账本覆盖全部 AI 调用。** 之前只有「简历修订」一条链路把 provider 用量写进账本,其余 AI 功能(简历生成、求职助手、写作/润色、岗位解析与匹配、面试、网申解析、投递话术等)调用成功但设置页的模型用量与统计不增长。现在所有 AI 调用统一经用法记录入口落账(不改各功能的业务行为),并补端点级回归测试。

- **修复 `stop.cmd` / `stop.command` 在「没有启动记录」时静默失效。** 启动器只在 `runtime/*.json`(macOS 为 `runtime/*.macos.pid`)里保存 PID、命令行与启动时刻;记录缺失(实例被别的窗口/脚本直接拉起、记录被清理、从另一处代码目录启动等)并不等于服务没在跑,旧逻辑却只认记录,于是双击 stop 会静默退出、端口仍被占——看起来就是 stop 失效。现在记录缺失或对不上时会走兜底识别:监听端口上的进程命令行必须匹配本仓库启动特征(后端 `uvicorn app.main:app`;前端带启动器自己传的 `--strictPort`),且 HTTP 必须自报 ResumeForge(`/api/health` 的 `name` 字段;前端在后端已退出时退回检查首页标题),两个条件都满足才按 PID 停整棵进程树;如果端口已经监听、但健康检查还没就绪(例如刚启动的几秒),会先等几秒再判定;仍不响应、而进程/父进程确实来自当前项目目录时也会停(覆盖「启动过程中 stop」与「卡死」两种情况)。`--skip-backend`(应用内退出)语义不变;静态模式下本来就没有独立前端进程,输出不再把它写成异常。Windows `scripts/Stop-ResumeForge.ps1` 与 macOS `scripts/macos/stop.sh` 同步实现,并新增「身份不符绝不杀」的反例用例。

- **修复添加自定义小字段必然弹出「内容已在其他窗口修改,请刷新后比较」。** 根因是字段设置把「大字段定义的 revision」(内置大字段恒为 0)当成后端 `create_custom_field` 的 `expected_revision`;后端校验的是**资料工作区 revision**(当时为 10),于是每次添加自定义字段都 409 `revision_conflict`。现在改为传工作区 revision,并新增回归用例钉住创建请求参数与成功路径;真正的工作区过期仍按既有 409 提示刷新比较。

- **修复点击「我的资料」直接落到错误屏(`useBlocker must be used within a data router`)。** 生产入口此前用声明式 `<BrowserRouter>`,而资料页的未保存离开守卫用 react-router 的 `useBlocker`——它只在 **data router** 内可用,于是每次进入资料页都会抛错并显示「页面暂时无法显示」。入口改为 `createBrowserRouter` + `RouterProvider`,路由表抽成 `appRoutes.tsx`、由生产 data router 与测试里的 `<Routes>` 共用(不会再出现两套路由);守卫在没有 data router 的上下文(测试/嵌入)降级为仅 `beforeunload` 确认,不再整页崩溃。新增路由装配守卫(钉住入口必须用 data router、路由表必须含 `/profile`)与降级回归用例;浏览器端验证:直接打开 `/profile`、从「我的空间 → 我的资料」进入均正常,编辑后点侧栏会出内联「未保存的修改」确认、确认前不会离开。

- **修复「我的资料」里日期区间的三个下拉溢出后与「至今」重叠。** 年 / 月 / 日三个下拉各自守着宽度下限(合计 208px),而控件区被另一份 `width: 100%` 覆写压到 187px,超出的部分整体溢出到右侧,压住「至今」11px(1440 视口实测:控件区右边界 1025px > 「至今」左边界 1014px)——看起来像"「至今」被日期盖住了"。现在日期宽度只在 `profile-layout.css` 里留一份实现(另一份覆写删除),控件区保底 208px,容器更窄时**整体换行**而不是压控件(宁可「至今」另起一行,也不让数字被横向裁掉);这条不变式由 `styles/profile-partial-date-width.test.ts` 钉住。

- **修复「出生年月」也长了一只「至今」勾选框,且勾上再取消会把原值吞成空。** 此前所有日期字段无条件带「至今」,于是「出生年月」(点日期,不是区间)也有了一勾就变「至今」的开关;更糟的是取消勾选写的是空串——用户实测 2004-11-10 勾一下再取消,值直接没了。现在「至今」只给**区间的结束端**(`end_date` 结尾或用户自建字段标签含「结束 / 截止」),出生年月、获奖时间、取得时间、年份一律不给;`PartialDateSelect` 记住最近一个具体日期,取消勾选时恢复它,从来没有具体值时才是空。用例钉住两条路径(`PartialDateSelect.test.tsx`、`ProfileFieldInputs.test.tsx`)。

- **修复识别链路四处串行错误:同名条目串日期 / 重复、页眉页脚噪声被当成记录、`YYYY年M月至今` 只解析出一半、截图识别丢日期。** ① 同名条目(四段「XX大学」)此前按名字合并,日期互相串成一条,现在按名字 + 日期区间各自成条,4 条还是 4 条、日期各自正确;② 「第 2 页」「共 2 页」「1/2」这类排版噪声此前会被当成一条记录的身份锚点,让 1 条教育变成 2 条,现在锚点命中噪声整串正则即丢弃,20 条项目不会再掺脏条目;③ `2023年7月至今` 这种**没有分隔符**的中文写法,分隔符分支只认「空白 + 至今」,于是 `月` 留在原地、日期只匹配到 `2023年7`,残留的 `月至今` 被当成公司名且 `end_date` 为空——现在分支吃 `\s*+`、并新增 `_DATE_CN` 日期形态,正确解析成公司 `上海广播电视台`、`2023-07~至今`;④ 纯图片请求里 `source_text` 为空,本地规则一行都解析不出,任一日期没过锚定校验就没有兜底(用户看到的正是"日期全空、只有「至今」留下"),现在图片通路改用模型抄录文本跑一遍本地规则补基线——抄录内容本身就是出处,反幻觉前提不放宽;两栏截图的三段日期因此全部保留(`2013-09~2017-06` 等)。

- **修复识别把「出生年月」只认成四位年份、以及中英双语小标题导致整段串到相邻板块。** ① `1998年7月生` 只解析出 `1998`(这个字段在资料里就叫**出生年月**)、`2001年生` 这类命中原文的脏写法还会原样落库(日期控件认不出来,显示"当前日期格式无法识别"):现在本地解析按 `YYYY` / `YYYY-MM` 统一收敛,AI 侧在原文同一年只出现一个月份时把月份补回去(同一年有多个日期就保持四位年份,宁可不猜);识别提示词同步写明这个字段要输出 `YYYY-MM`。② `教育经历 Education` / `Work Experience 工作经历` 这种用**空格**拼中英的标题此前认不出来,整段内容被并进上一个板块(用户实测:`Microsoft / Software Engineer` 在教育经历里也出现一次,学校栏填了公司名);现在紧凑化标题时剥掉裸的英文尾缀/前缀(含单独的 `Experience`),并加一道结构性兜底——教育 / 工作 / 校园 / 项目的条目锚点必须落在**它自己那个分区的标题范围**内(复用本地解析的切分边界;原文认不出任何标题时跳过,不误杀)。技能与奖项不套这道校验:技能名本来就会出现在项目正文里。

- **修复悬浮球「小贴士」卡片接走指针、把下面条目卡的拖拽把手"偶尔拖不动"。** 提示卡浮在右侧内容之上且是 `pointer-events: auto`,卡片正好压住条目卡的上移 / 下移 / 删除 / 拖拽把手,`pointerdown` 落在卡上就没了反应,只在提示冒出来那几秒复现,看着像"拖拽时灵时不灵"。现在卡片本体 `pointer-events: none` 让指针穿透,卡内真正要点的关闭按钮单独收回 `pointer-events: auto`;贴边定位只改位置,不重置指针事件(`styles/tou-tou-tip-pointer-events.test.ts` 钉住)。

- **"备份是否带 API Key"的三处说明与实际默认值对齐。** 后端的 `include_api_keys` 默认是 **`true`**(换机整包迁移不用重填),但求职助手的「数据集」能力说明、用户指南 FAQ、架构决策表仍写着"默认不含/默认剥离",于是助手会告诉用户一个相反的事实。三处统一改为"默认包含、可取消勾选剔除"(Windows 上仍是绑定本机与用户的密文,换机后需重填)。同一批把架构文档里"联网搜索不抓取正文"的绝对说法改成条件描述:默认 `fetch_pages=0` 只取摘要,调到 1–3 才抓正文,且解析检查与建连之间存在 TOCTOU 时间窗。

- **静态根文件不再能被查询参数换成任意文件。** `dist` 根目录的散文件(favicon 等)此前注册成 `async def serve(path: Path = file_path)`,FastAPI 会把带默认值的形参当成**同名查询参数**——实测 `GET /icon.png?path=<dist 外的文件>` 返回的是那个文件的字节,`?path=` 指向同目录另一个文件也能改掉返回内容。现在改为闭包工厂返回**零形参**的处理函数,并在注册期与请求期各做一次根目录约束(`resolve()` 后必须仍是 dist 根下的普通文件),越界与"注册后被删除"一律 404。回归用例覆盖:两个根文件各返回自己的字节(防闭包晚绑定)、`?path=`/`?file=` 无法改变响应、路由 `query_params` 必须为空、删除后 404。

- **自动备份不再把"曾经成功过"显示成"当前受保护"。** 旧口径 `protected = bool(last_success) and failures == 0` 只看标记文件:快照被轮转走、被手工清掉、或目录根本读不了时,设置页仍然显示绿色「受保护」。现在状态多一个可判定字段 `snapshot_state`(`ok` / `empty` / `unreadable`),`protected` 必须有**当前可读的 `.db` 快照**;目录读不了显示「无法确认」,有成功记录但没有快照显示「快照缺失」并提示重新备份。`interval_days` 为 0(读不到设置)时也不再被误判成「已到期」。

- **请求预算与取消现在覆盖正文读取。** 以前 `requestRaw` 一拿到响应头就在 `finally` 里清 timer、摘 abort 监听,而 `request()` 与 30+ 个导出/预览调用方是**之后**才 `resp.json()` / `resp.blob()`。于是"响应头秒回、正文挂起"的请求永远不会超时,用户点取消也中断不了正在读的正文。现在预算一直管到正文读完:读取阶段的中止同样产出 `code=timeout` / `code=cancelled` 的 `ApiError`,四条终态(读完、读失败、取消、超时)都会清 timer 与监听;`requestRaw` 的签名与返回类型不变。

- **懒加载失败的页面不再"永久打不开"。** `lazyPage` 原先把 rejected promise 永久缓存,而侧栏会在首屏空闲时分批预热 18 个页面 chunk——任何一次预热失败(dev server 刚重启、网络抖动)都会让那个页面之后每次导航都拿到同一个失败结果,只能刷新整页。现在失败时清除缓存,下一次导航重新加载;预热路径也全部吞掉 rejection,不再产生未处理的 Promise 失败。

- **模板选择弹窗里的缩略图全部报「Extra inputs are not permitted」。** 渲染预览时把"取消令牌"一起塞进了请求体,后端认为多了个不认识的字段直接拒收(422),于是每张模板卡片都只剩一行报错;「查看大图」走的是另一条不带该字段的路径,所以它一直正常。现在取消令牌只交给网络层,缩略图恢复渲染。

- **Markdown 与纯文本导出的联系方式行补上了个人主页与 GitHub。** 这两项在预览、PDF、Word 的页眉里一直有,只有 Markdown / txt 漏印——复制正文到招聘网站时才发现"怎么少了两行"。现在四条链路印的是同一组字段。

- **导出图片不再失败在 `Setting up fake worker failed`。** 症状是"PDF 已生成,图片转换失败",并提示改用 PDF 导出——PDF 其实是好的,塌的只有最后一步像素化。根因不在 pdfjs:`dist/assets/pdf.worker.min-*.mjs` 在 Windows 上被 Python 的 `mimetypes` 认成 `text/plain`(读注册表得来的),而 `FileResponse` 照抄这个结果当 `Content-Type`,浏览器因此**拒绝把它当 ES module 执行**。现在静态资产在挂载前把 `.mjs` / `.js` / `.css` / `.woff2` 的 MIME 钉成规范值,不再问宿主机;守卫测试直接请求一个 `.mjs` 并断言返回的是 JavaScript 类型(`test_static_spa.py`)。同一个修复也顺手堵住了"入口 `.js` 被当成 text/plain"这种会整页白屏的形态。

- **精确预览不再在多页上限下补空白页。** 之前按"上限 − 实际页数"补同比例的白纸,让人以为自己的简历真的是 3 页;而那些纸在导出的 PDF 里根本不存在。现在只画 PDF 实际排出来的页,预览与下载到的文件永远一致(`ExactPreviewBlanks` 连同它的文案与样式一并下线)。

- **「简历字体」里点「设为当前」现在当场有反应。** 以前按钮永远写着「设为当前」,切换成功后要等清单重拉回来才可能变,而它根本不变——用户只能得出"没生效"的结论。现在当前字体的名字旁边有「当前使用」标签、按钮置灰并带对勾,而且**先乐观更新再刷新**(`pendingActive`),不必等那一次网络往返。同一批把这张卡片重排了一遍:内置字体是一块带浅底与左侧色条的独立面板,历史字体每条两行(名字 + 状态标签 / 体积 + 原始文件名),「回到系统字体」独立成页脚并配一句说明,不再和"还没有导入其它字体"挤在同一行。

- **文本输入框的聚焦环不再是一道 4px 的纯蓝框。** 2px 实色 `outline` + 1px 偏移叠在 1px 边框上,在浅色表单里像报错。现在只留边框变色 + 一圈 14% 混色的 3px 柔光,边界仍然清楚。带前后缀图标的输入框额外把内层 `<input>` 的这圈光晕按住,避免画出两层。

- **Word 导出与预览的两处明显差异已消除。** ①条目头右侧的日期现在用**定在版心右边缘的右对齐制表位**,不再跟着标题长度左右浮动(预览是 `space-between`);②项目符号改成**悬挂缩进**,折行的第二行对齐正文起点而不是退回版心左边缘。为此把段落级原语(字体设置、区块标题、条目头、项目符号)抽到 `services/docx_blocks.py`,`docx_exporter.py` 回到 500 行预算内。**仍然不同**的部分如实说明:Word 没有 flex/分页引擎,照片与姓名仍是上下排布、区块标题不画下划线、页数只能估算——这些需要更大的结构改造,不在本次范围内。

- **模板一览不会再整屏卡在"加载中"。** 旧实现要等六张缩略图**全部**返回才一次性上屏:任何一张慢(每个模板都要跑一遍服务端渲染)整屏就一直转圈,看起来像"模板加载不出来"。现在逐张上屏(先回来的先显示)、旧图在新图到达前**继续留着**(换字号不会退回灰点)、关弹窗或换参数时把在飞的请求 abort 掉,失败的卡片给出原因与「重试」。同步给 `previewResumeTemplate` 加了 `signal`。

- **modern(以及已下线的 editorial)不再使用圆形照片。** 圆形头像在简历里会切掉证件照的边角,且 PDF 侧本来就画的是方图——两端不一致。现在统一成 4px 圆角矩形,与「经典」一致。

- **区块标题的底色块改成通栏(modern 的浅绿带、technical 的实心色块)。** 模板里 `.section-title` 是 block 元素,浏览器把 `background` 画成一条贯穿正文宽度的色带;PDF 此前按**文字宽度**画,短标题(「个人总结」四个字)只得到一小块色块——这就是"草稿模式和精确模式绿色背景分布不一样"的来源(2026-10-24 用户截图)。现在按列框宽度铺满,双栏模板里仍只覆盖所在那一栏。

- **「写作增强」弹窗不再被压在简历弹窗后面。** 它是从右键浮层里开的,antd Modal 默认 `z-index: 1000`,而简历详情/编辑器是 3110、容器链 3200/3300——整个弹窗藏在后面,用户看到的就是"点了写作增强,什么都没出现"。现在显式抬到 `layer.aiPolish`(3710,与右键卡片同一层),并有守卫断言弹窗的 `z-index ≥ 3700`。

- **资料页里"没有名字的技能标签"不会再出现了。** `PUT` / `PATCH /api/profile` 的技能数组一直没有写入校验(`SkillIn.name` 允许空串),界面上多出来的空行被提交后就落库成一条永远显示不出来的空标签——没有名字、点不掉,删之前也不知道那是什么。现在写入侧把"名字去空白后为空"的技能行整行跳过(**跳过而不是回 422**:用户此刻是在保存别的字段,不该为一条空行把整份资料打回),响应与下一次读取里只剩真有名字的那些;全是空行时结果是空数组。存量脏数据用 `scripts/clean_blank_skill_rows.py` 清(默认只报告,`--apply` 才删,幂等,且拒绝在"没有 skill 表"的库上动手)。

- **批量匹配里"某一条一直红着,单独跑却一次就过"的岗位现在会自己重试一次。** 原因是"证据无法核对"这类校验失败判的是**模型这一次的输出**,不是这条岗位本身不合格——批量里直接判 failed 就把它钉死了。现在可重试的失败(JSON 解析不出来、引用的 JD 原文在 JD 里找不到、证据在资料/简历里核对不上)在同一次批量内自动再来一遍,成功就照常计入完成;**不可重试的不重试**(未配置模型、模型名/密钥这类硬 4xx、请求预算耗尽),避免在同一个必错的地方把账单翻倍。每次重试都是一次真实请求,照样吃原有预算;**收到取消就不再重试**。重试上限是一个常量(`MAX_MATCH_ATTEMPTS_PER_JOB = 2`),不是散落的魔数。

- **更早版本导出的整包备份不再因为"清单里没有 SHA-256"被拒收。** 逐份校验是后来才补进整包清单的,而老包里没有这个字段时导入侧原本直接拒收——那是用户手上唯一的一份数据。现在缺摘要按"无校验"处理(仍核大小,并在日志里留一行说明),**写在清单里的摘要则照旧必须核对**:摘要拼错/截断只能说明包被改坏了,与"没有摘要"是两回事。

- **桌面版启动失败后既起不来也退不掉,只留一行红字和一个要手动复制的路径。** 后端启动失败时,壳只往启动画面推一条错误就返回了:外层 `whenReady` 的 catch 收不到异常,于是既不弹对话框也不退出;而关窗只是 `hide()` 到托盘(崩溃预算那条弹窗要求后端**曾**成功启动过,所以启动期失败永远进不去)。现在启动失败会弹**带按钮**的原生对话框——「打开日志目录」直接打开 `desktop-main.log` / `desktop-backend.log` 所在目录,「退出」置 `quitting` 后真的退出(不被"关窗即隐藏"的 `preventDefault` 拦住),启动画面照样显示错误原因。用 `showMessageBoxSync` 而不是 `showErrorBox`:后者不能带按钮。

- **诊断包里没有桌面壳的日志,而后端起不来时后端那份恰好什么都没写。** 桌面形态启动后端时新增注入 `RF_DESKTOP_LOG_DIR`(目录仍由 `desktop/lib/shell-paths.js` 唯一裁决);`GET /api/system/diagnostics/export` 按**原名**把 `desktop-main.log` / `desktop-backend.log` 打进包(只点读这两个名字,不递归目录、不带轮转文件,尾部截断 400 行 / 256KB 并过同一道脱敏闸)。未设置该环境变量(控制台 / 纯后端 / 开发态)时行为完全不变;预览里列出的文件与包里实际内容一致。

- **存储清理里的「运行日志」指向一个从来没人写的目录。** 该类别扫的是 `<数据目录>/runtime/logs`,而全后端没有任何 `DATA_DIR / "runtime"` 的写入点——用户在界面上点清理永远清不到东西;真正在滚动的 `logs/`(`backend.log` + 4 个轮转,本机约 21MB)反被标成「旧日志」。现在「运行日志」直接指 `logs/`,两类合并为一类,退役的 `old-logs` 作为参数会被明确拒绝(400 并列出合法值),不再留一个"选了也没用"的选项。

- **桌面测试会往真实用户目录里写日志。** 加载 `main.js` 的 VM 探针把诊断日志写到 `%LOCALAPPDATA%\ResumeForge\runtime\logs\desktop-main.log`;单实例锁被占的分支还自己拼了一条 `startup-probe.log`(第二套路径逻辑)。现在前者由测试夹具把 `LOCALAPPDATA` 指向临时目录,后者改走壳唯一的日志出口(`desktop-main.log`),"跑一次桌面测试就在用户 profile 里留一行"不再发生。

- **预览里把「简历语言」切成 English,导出的 PDF / Word / HTML / Markdown / 纯文本仍然全是中文。** 导出面板的既定契约是"预览里的版式草稿就是导出的版式"——`font_scale` / `page_limit` / `format_config` 都按草稿随请求发;加语言时漏了这条路:前端没发 `language`、`POST /export` 的请求体里也没有这个字段,后端只能回落到记录里上次保存的值。于是**没点「保存版式」**的用户,眼前是 `Summary`、下载到的却是「个人总结」。现在 `language` 与其它草稿参数走同一条路(`ExportRequest.language` → 管线里覆盖 `RenderContext.language`,`None` 仍表示用记录值),渲染配置指纹也同步改成按**生效后**的语言计算——否则同一份内容换语言会得到同一个指纹,交付血缘会把两个文字不同的文件判成同一份。**没有**改成"导出前自动保存版式":那会改变既有交互。顺带补上同一个洞的另一半:`POST /api/resumes/{id}/preview-pdf` 此前收下了 `language` 却没用它排,精确预览同样退回记录里的语言,现在也按草稿语言排。

- **装了 1.0.2 却打不开:桌面壳把运行期日志写进了自己的安装目录。** 桌面壳启动后端时会在 `resources/payload/runtime/logs/` 建目录写日志,而 macOS 从 dmg 双击运行时安装卷是**只读**的(AppTranslocation 还会把它挂到随机路径),Windows 装到仅管理员可写的目录同样拒绝写入——两边的表现都是"启动即失败",日志里只有一行 `ENOENT` / `EPERM`。运行期状态现在一律落在用户目录(Windows `%LOCALAPPDATA%\ResumeForge\runtime`、macOS `~/Library/Application Support/ResumeForge/runtime`),pid 记录写可写位置、读取时兼容旧位置;日志目录不可写也不再阻断启动,会降级到系统临时目录并在主界面提示一次(含"打开日志目录"按钮),提示里不出现 `EPERM`/`ENOENT` 原文。

- **macOS 打包态的后端启动链在只读安装目录里必然失败。** dmg 里的 payload 不含便携运行时,于是壳退回 `scripts/macos/start.sh`,而它在项目根下建 `runtime/` 与 `backend/.venv`。现在启动链先做一次**真实写探测**:项目根可写(源码检出、CI)时行为逐字不变;只读时运行期目录与虚拟环境都改落到 `~/Library/Application Support/ResumeForge`,安装目录零写入,`stop.command` / `update.command` 走同一套解析(并兼容 1.0.2 及更早的壳留下的记录位置),不会再出现"应用退出了、后端还占着端口"。CI 的 `macos-end-to-end` 增加了一条"只读副本启动后无新增文件"的断言。

- **点「重启并安装」后应用关了、什么都没发生(更新器根本没启动)。** 后端用 `DETACHED_PROCESS` 拉起 `Update-ResumeForge.ps1`,而完整的分离进程**没有控制台**——Windows PowerShell 是控制台宿主程序,脚本压根不执行、退出码 0、没有输出;后端 30 秒等不到 `installing` 标记,只好写"更新器没有启动"。现在改用 `CREATE_NO_WINDOW`(隐藏的控制台,脚本能跑、用户看不到窗口),与"停止前端"用的是同一组标志,并补了一条在 Linux 上同样有效的断言(两个标志位显式指定,退回旧写法必红)。

- **覆盖安装"成功"了,前端却还是旧版:`dist` 排除规则按名字误伤了 `frontend/dist`。** 更新器把"任何层级下名为 `dist` 的目录"整体跳过,而 `frontend/dist` 恰恰是唯一会随更新包分发的前端产物(界面版本号就写在 `frontend/dist/version.json` 里),于是升级后后端是新的、页面是旧的。现在 `dist` 只按**项目根相对**排除(仓库根部的发布产物仍然不覆盖),`frontend/dist/**` 走正常复制;守卫测试把 `frontend/dist/index.html` 的期望从"跳过"改成"必须复制",并补了一条真的复制一遍的用例。

- **桌面版覆盖安装后后端起不来:依赖同步被整段跳过。** 更新器只认 `backend\.venv`,而桌面安装用的是 `%LOCALAPPDATA%\ResumeForge\runtime\python`,于是它打印一句"下次启动会创建"就跳过了——1.0.2 新增的 `pypdfium2` 不在旧运行时里,uvicorn 加载即崩。现在按"源码 venv → 桌面打包运行时 → 便携运行时"的顺序解析解释器,真的装依赖;一个都找不到就**明确失败**并给出"桌面版重跑 Setup.exe / 源码版先双击 start.cmd"的指引。桌面形态下 `-Restart` 也不再拉起 `payload/start.cmd`(那等于另起一套服务),而是提示重新打开应用。

- **桌面版设置页里那个"下载 zip 并安装"是条走不通的路。** 桌面安装的 `PROJECT_ROOT` 是 `resources/payload`,更新包却是源码形态整包(不含便携运行时、不含 `desktop/`),页面内覆盖只会把安装目录弄成半新半旧。后端新增 `GET /api/system/form` 暴露形态(壳启动时注入 `RF_DESKTOP_SHELL=1`),桌面形态隐藏下载/安装/后台下载,改成"下载 Setup.exe 双击安装"与发布页链接;「检查更新」保留。旧版壳没有这个变量,因此这项分流从**下一个桌面版**起生效。

- **运行时不完整时报的是 `ModuleNotFoundError`,用户看不懂。** 后端启动即崩、日志里只有 Python 栈。现在启动前会拿 payload 里的 `runtime-manifest.json`(python 版本 + 依赖清单摘要)核对解压出来的运行时,摘要不一致就重新解压;仍然缺依赖时提示"运行时缺少依赖(安装可能损坏):<模块>,请重新安装",并保留日志路径。stderr 只保留末尾 8 KiB 用于诊断,不会把整份输出驻留内存。

- **导出的单文件 HTML 对"略微溢出"漏报。** 探针用 `scrollHeight` 判断,而模板 `body` 是固定页高 + `border-box`:正文溢出到下边距区域时仍在 body 盒子里,`scrollHeight` 恒等于页高,于是同一份内容服务端判"需要 2 页"、导出的 HTML 却写着 `data-overflow=0`。现在改成量"最后一个可见元素的底边 vs 内容区底边"(与草稿测量同一口径),并给不钉 body 高度的自定义模板保留原判据。

- **列表项折行后,续行比首行向左错开一个符号宽。** 此前把「· 」拼进同一段文本、整块左移一个符号宽,结果是符号与续行对齐、首行正文右偏——`list-style-position: inside` 的形态,而 CSS 是 `outside`。现在符号单独画在左侧留白、正文(首行与续行)共用同一左沿,缩进量也改为逐模板取自 `ul { padding-left }` 而不是写死 3mm。

- **「精确」里字号拉到最大,PDF 上的字却明显更小——原因是跑着的后端还是旧版本。** 旧的 `decide_fit_scale` 会按比例把字号压到 0.5–1.0(正是"参数不等于呈现"那一套),而界面上没有任何线索能看出"进程没重启"。现在精确预览只要拿不到「按当前设置约需几页」(`X-Resume-Pages-Needed`),就会直接说明"这版 PDF 可能不是按你设定的字号排的,完全退出应用后重新启动"。另外补了三条回归,把这次的口径钉死在**接口层**:草稿的 18px 必须原样落到纸面(13.5pt)、行距按草稿走、超出页数上限只截断不缩字号——此前只有"预览与导出互相一致"的用例,两边一起错时它是绿的。

- **「查看大图」里三条滑杆终于一样长(且与主预览同形态)。** 大图自己造了一份控件还带了 `compact`:紧凑形态下滑杆靠"吃掉剩余宽度"排布,而大图弹窗更宽,于是行距 / 页边距只有 56px、字号 213px,与主预览的 200px 对不上,看起来像两套控件。现在大图不再走紧凑形态,与主预览同为 `is-stacked`:三条滑杆都是 200px,读数落在同一条竖线上。精确模式下那句"这一版由导出引擎排版"也从第二行右端移到度量控件下方单独一行(贴住它解释的那组控件)。

- **精确预览不再"标志闪一下就干等":旧图留到新图准备好,加载态全程可见。** 之前换一份 PDF 是直接把 iframe 的 `src` 换掉——浏览器要重建整个查看器(工具栏、页布局、字体),这段时间是一片空白,而加载标志早在 HTTP 返回时就关掉了。现在改成**双缓冲**:旧图一直可见,新 PDF 在同一个位置以完全透明的 iframe 预加载,`onLoad` 后一次性换上来;遮罩与"正在按新版式重排…"持续到真正换完。同时后端也省掉了一半排版:`build_resume_pdf` 在"第一轮就判定装得下"时**复用那一版产物**,不再"数页数一遍 + 出图一遍"(实测装得下的场景从 ~1.2s 降到 ~0.5s;1 页上限压 2 页内容仍需多轮迭代,约 2.2s,但期间用户一直看得到旧图)。

- **精确预览现在跟着整套版式走:模板、版式模板、页数、字号、行距、页边距、字体、板块顺序,改完就重排。** 之前只认「页数 / 字号 / 覆盖」三样:换完样式模板或版式模板(还没点保存)时,服务端仍按**记录里的旧值**排版,屏幕上看到的还是上一版。现在 `POST /api/resumes/{id}/preview-pdf` 的版式参数与 `POST /api/resumes/render` **逐一对应**(`template` / `format_name` / `page_limit` / `font_scale` / `format_config`),前端把草稿 `layout` 原样传过去;重排期间状态行显示「正在按新版式重排…」,不再静悄悄留着旧图。另外两个原因也一并修了:①请求依赖里**没有正文**——改完一段经历,PDF 还是旧的(现在并入 `contentSignature`);②`useResumeRelayout` 的存活标记只在卸载时置 `false`、挂载时不置回 `true`,StrictMode 的"挂载 → 卸载 → 再挂载"之后它就永远是 `false`,`onRendered` 再也不调用:`/render` 明明成功了,版式状态却不前进(开发模式下表现为"改完字号,预览重排了但精确预览不动")。三条都补了回归用例(含一条 StrictMode 双挂载)。

- **「查看大图」现在跟着当前口径走。** 之前大图里永远只有草稿模式:主预览切到「精确」,点「查看大图」看到的还是浏览器排版。现在口径(草稿 / 精确)提到 `ResumeDetailPreview` 一层,主预览与大图**共用同一份状态**,任意一处切换另一处跟着走;大图拿到的精确预览上下文(简历 id / 字号 / 版式 / 正文签名)也与外面逐字段相同。

- **AI 润色卡片的输入框在弹窗里打不上字。** 卡片用 `createPortal` 挂在 `body` 上,而 antd Modal 有焦点陷阱(根是 `role="dialog"` 的 `.ant-modal`)——卡片在陷阱之外,点它的输入框,焦点立刻被拉回弹窗里最近的可聚焦元素(实测是右上角的关闭按钮),表现就是"卡片明明在、字一个也打不进去"。在「手动调整」里选中文字右键「AI 润色」必现,普通页面里(没有 Modal)却正常。现在卡片打开时挂进**当前最上层的 `.ant-modal`**:`.ant-modal` 在入场动画结束后不带 transform,卡片自身的 `position: fixed` 仍按视口定位,视觉位置分毫不变。

- **在润色卡片里打字时,滚动不再把卡片关掉。** 滚动关闭原本是为了让 fixed 浮层不与选区脱节,但它监听在 document 的**捕获阶段**:输入时 `autoSize` 让输入框变高、浏览器为露出光标把外层弹窗滚一下,卡片就会当场消失。现在"焦点在卡片里"时豁免——用户正在这张卡片上打字,那次滚动与"离开选区"无关。

- **「自动一页」从"看起来没生效"变成真的生效了。** 模板里所有尺寸都挂在 `:root` 的 `--fs: calc(基准字号 * var(--fit-scale, 1))` 上,而自适应脚本写的是 `body.style.setProperty("--fit-scale", …)`。CSS 自定义属性只向下继承,`:root` 读不到子元素上的值,于是 `--fit-scale` 永远停在 fallback `1`——`data-scale` 在变、computed `font-size` 恒等于基准字号,整条版式自适应**静默失效**且没有任何报错。现在脚本与兜底路径都写到 `documentElement`(与 `--fs` 同一个元素);实测一份两页内容在 1 页上限下缩到 0.654、`font-size` 从 14px 变成 9.15px、`overflow` 归零。前后端各加一条守卫(模板逐模板断言、`layout.ts` 源码断言),防止有人"顺手"改回 body。(整套缩放机制随后按用户要求整体退场,见上文「预览与导出都严格按你设定的字号排版」;这条记录留作"自定义属性只向下继承"的教训。)

- **切到 2 / 3 页时字号不再突然减半。** 预览把连续流切成 N 列后 `body.scrollWidth` 是 N 页宽(1588 / 2382),模板里的自适应脚本 `document.fonts.ready` 恰好在注入 `column-count` 之后才跑,拿它和单页宽 793.7 相除必然判成"宽度超了一倍",把 `--fit-scale` 一路压到下限 0.5。现在 `layoutFrame` 注入多列样式时给 body 打 `data-preview-columns="1"` 标记,脚本见到它就退出;测量阶段会先删标记,保证页数始终在单列状态下量。(脚本随后整体退场,标记保留给溢出探针:多列状态下不参与页数判定。)

- **简历多页预览不再被页边阴影遮字。** 分页条原先向外绘制的阴影会伸进正文,压住紧贴页边的日期、照片或末尾文字;现在阴影收进页间留白,2 / 3 页仍保持 A4 横向并排,正文和页数计算不变。

- **简历预览弹窗的内容不再贴着右侧滚动条。** 弹窗正文自己就是滚动容器、左右内边距为 0,滚动条一出现内容就被顶到它边上(「3 页」「随模板」几乎压线)。现在右侧留 12px,再用等量负外边距把这 12px 从弹窗内边距里借回来——内容宽度一点没变,滚动条整条向弹窗边缘挪 12px,左右两侧看起来仍然平衡。生成简历弹窗的预览阶段同步。

- **PDF 文件在「识别附件」里点「预览」仍然提示预览不了。** 原因是运行中的后端还是旧进程:`POST /api/attachments/pdf-preview` 这条路由根本没加载(请求直接 404,前端只能显示失败提示),而识别走的是老接口所以照常可用。重启后端后,用一份 379KB 的真实 PDF 实测:接口返回第 1 页的 JPEG 位图(约 120KB)。同时确认:**更换简历字体后预览不刷新**也是同一个原因——旧后端不认识 `format_config.font`,选择了也不会有任何变化。

- **「通用简历」区块不再常驻一个没有归属的名称输入框。** 名称改在真正会用到它的地方填:点「从头手写」后,编辑器顶部有「简历名称」(留空由后端自动命名);「AI 生成」则在生成弹窗里填。页面上只留「AI 生成 / 从头手写」两个入口。

- **`Generate-Sbom.ps1` 的产物在本地与 CI 之间不再有编码差异。** 以前 Windows PowerShell 5.1 的 `Set-Content -Encoding UTF8` 会给 `THIRD-PARTY-NOTICES.txt` 写 BOM,Windows 上 Python 又写 CRLF,而 CI 的 Ubuntu + pwsh 7 是无 BOM 的 LF——同一份快照在两边跑出来字节不同,本地重跑会造出一份只差编码的假差异。现在两个文件统一按 CI 的形态写出(UTF-8 无 BOM + LF),内容不受影响。

- **设置页切换数据集不再偶发「请先停止任务」的 409。** 根因不是有任务在跑,而是页面手里的「数据集身份」过期了:它只在启动时取一次,后端重启、另一个标签页切过数据集、或上一次切换「服务端成功但页面没重载」之后,此后第一个写请求——恰好是「切换数据集」——就会撞 409。现在后端在 409 响应里也回写当前身份并记一条 `dataset scope mismatch` 警告日志;前端区分出 `code=dataset_scope_changed` 这一种 409 后,会**显式重新同步身份**再**重试一次**(仍只弹一次成功提示),「切换中」「排空超时」两类保持原文案与原行为。身份**不会**被普通响应静默改写——否则一个还开着编辑器的旧标签页会"跟着"切到另一个数据集,把草稿写进那里同 id 的记录;只有你主动切数据集时才会重新同步。切换请求的预算也从 15s 提到 30s,与服务端 10s 排空上限对齐。

- **岗位解读不再「生成到一半就报超时」,客户端断开后也不会白烧 token。** 这条链路原来是普通 POST 走 15s 默认预算,而真实生成要 10–60s;现在有独立的 180s 预算,并且服务端在生成期间每 400ms 检查一次连接,客户端断开就取消协程、返回 499、不写缓存(此前是「跑完才检查」,那次调用既没缓存也白付了 token)。

- **手写简历保存后,列表立刻出现。** 此前「我的资料」页的通用简历卡片只在挂载时请求一次,保存完要手动刷新才看得见;简历中心则是在弹窗关闭时无条件重取一次——取消也会白跑一个请求。现在保存成功会广播一次列表变更:「我的资料」与「简历中心」各自重取,简历中心同时回到第 1 页,关闭弹窗不再触发请求。

- **审计 21 项清单的本轮 13 项集中收口(2 个 P0 / 7 个 P1 / 4 个 P2)。** 逐条:
  - **存储清理不会再把最新备份清掉。** `backups/` 里按修改时间最新的 3 份永远不进清单(此前 5 份过期备份会被全部移走,含最新那份);扫描范围补上此前漏掉的 `auto-backups/`,`backups/.trash` 不再被重复列两次。清理弹窗按类别分组勾选(备份 / 备份回收站 / 自动备份 / 日志),预览与执行共用同一份清单;未知类别返回 400 并列出合法值,`categories: []` 表示什么都不清。
  - **岗位广场支持服务端排序。** 岗位 / 公司 / 发布时间三列可点,`?sort=created_desc|updated_desc|title_asc|company_asc` 加可选 `order` 覆盖方向,排序写进地址栏(刷新与分享都保留),非法值返回 422(此前静默 200 且顺序不变);「全选全部」的 id 顺序与列表一致,不再只排当前页。
  - **岗位解读先读缓存。** 已有未过期结果时 `POST /analysis` 直接返回 `cached=true`、不再调用模型,只有「重新生成」带 `force=true` 才重新计费;缓存结构失效时降级为重新生成而不是 500;生成期间连接已断开则不写缓存。
  - **页边距只剩一个旋钮。** 导出面板的「页边距(mm)」并入 `format_config.page_padding`(8–26,与预览同一个键),改它预览与 PDF 同时变化,不再出现「预览没动、PDF 动了」;导出响应新增 `X-Pdf-Engine` 与 `X-Resume-Overflow`(后者与页数是否超上限同真同假),并加入 CORS `expose_headers` 供前端读取。
  - **全局模型支持多套配置。** `global_settings.json` 升到 v2(`profiles[]` + `active_profile_id`),读到 v1 自动迁移并写回;页头新增下拉可一键切换(只配了数据集级时不显示),设置页支持新增 / 编辑 / 删除 / 设为当前,删除当前项自动切到剩下的;写入加进程内锁与随机临时文件名,并发写不会互相覆盖。数据集导出默认**包含** API Key(可取消勾选;Windows 是本机绑定密文,mac/Linux 为明文)。
  - **预览 / HTML / PDF 使用同一款自定义字体。** 上传字体按正文实际字符子集化后以内嵌 `@font-face` 写进预览用的 HTML(实测 19.5MB 中文字体子集后约 40KB,整份简历 HTML 约 160KB),8 个内置模板 CSP 补 `font-src data:`,自制模板走同一处注入;没有选中字体时不产生任何 `@font-face`,字体损坏或子集失败只降级为系统字体、不影响渲染,结果超过 1.5MB 也退回系统字体而不是把整份字体塞进 HTML。
  - **「导出全部数据集」改成嵌套整包(format 4)。** 外层 zip 是 `manifest.json` + `README.txt` + `datasets/<id>.zip`,每份子包仍是一份完整单数据集包并带 SHA-256 与体积;导入逐份校验,坏一份只跳过那一份并在界面点名「名称:原因」,其余照常恢复(至少一份成功即 200,全部失败才 400);子包名用数据集 id,活动数据集不是主数据时也不会撞名;旧 format 1/2/3 包继续可导入,旧版本会明确拒收 format 4。
  - **深色主题不再有白块/亮线。** 内联颜色守卫从 CSS 扩到 `.tsx/.ts` 的 `style={{}}`(含 antd `styles={{}}`),本轮清掉 38 处硬编码、**白名单为空**;图表色与网申分组色抽成深浅两套色板,模板画廊、ATS 面板、风险面板在深色下不再有白底;A4 纸面在深色下仍是白纸。主题选择写入 `global_settings.json`,换浏览器 / 换数据集不丢,localStorage 仅作离线兜底。
  - **「取消生成」改成「停止等待」。** 不再 abort 请求:只是停止界面等待并关闭弹窗,服务端连接不断,本次结果会正常写缓存,下次打开直接可看——不再出现「点了取消、服务端仍在跑并写缓存」的自相矛盾。
  - **全站搜索可以用键盘操作。** ↑↓ 跨分组移动(首尾循环)、Enter 打开当前项、Esc 关闭,结果项带 `aria-selected` 并自动滚入可视区,悬停与键盘共用一份高亮。
  - **快捷键 6 处「与计划不符」核实为刻意规避浏览器保留键,键位不变。** `Ctrl+E`(地址栏搜索)与 `Ctrl+,`(浏览器/系统设置)在多数浏览器里到不了页面,因此导出用 `Ctrl+Shift+E`、设置用 `Ctrl+Alt+S`;设置页给相关动作加了「?」说明,上下页标签改为「上一页(列表)/ 下一页(列表)」——它们作用于列表分页,不是预览翻页。
  - **分享包显示预计体积**(按后端返回的清单求和,不编造数字),并可勾选是否包含照片(后端新增 `include_photo`,默认包含,旧调用方行为不变);包内固定包含 HTML 与 PDF。
  - **简历列表新增「最近修改」列**,标题列固定 220px 并省略 + 悬停显示全名,1080px 宽下不再被压成约一字宽。

- **全局 AI 润色不再把模型的"拒绝"当成改写结果。** 模型因区域限制或内容策略回一句"很抱歉!改写当前对你的区域不可用"时,以前会被原样填进输入框,看起来像"润色成功了,结果是一句道歉";现在这种情况直接报错(502),提示"模型没有返回可用的改写结果,请换一段文字或换个模型",且**不把原句回显**成结果。模型按约定返回 `{"error": "原因"}` 时会把原因如实带给你(例如"内容涉及敏感信息")。提示词同时补了两条约束:错误提示/免责声明这类原文照常润色、不许作答;确实改不了只输出 `{"error": …}`。"不得新增事实"的约束没有放宽,正文里出现"无法"这类词也不会被误判成拒答。

- **出生日期与经历日期的年份下拉不再把年份截断成两位。** 「年 / 月 / 日」并排时控件被等分得很窄,下拉层跟着控件宽度走,选中项会显示成「20」而不是「2024」;现在三个下拉层的宽度按选项内容自适应、不跟随控件,年份控件本身保留 80px 最小宽度,年份下拉另有最小宽度兜底。

- **修复岗位详情「连点两次没反应 / 关掉后打不开」的问题。** 连续两次点同一个岗位标题、关闭抽屉后再点同一个岗位、以及 A→B 快速切换这三种操作现在都能正常打开详情:晚到的旧响应不会再覆盖后来的结果,请求失败后也能重新点开重试。

- **「内容来源说明」默认收起,只显示标题与条数。** 以前每份简历的预览里都会先铺开一大段来源说明;现在它是一行可展开的标题(`内容来源说明 · N 条`),点开才看得到分区与依据,没有标注的旧简历仍然什么都不显示。

- **出生日期与经历日期的年份下拉以今年为第一项。** 下拉不再从 1900 年开始、要翻很久才选到自己的年份:第一组从今年往前排,未来年份(例如"预计 2027 年毕业")单独排成第二组放在后面;空值打开时不会替你写入今天的日期。

- **修复简历预览里「让 AI 改这一栏」在长栏目名下按钮错位的问题。** 指向的栏目路径很长时(例如「实习/工作经历 · 第 2 条 · 工作内容 · 第 3 条」),提示文字自己收缩省略,按钮固定停在工具栏右侧、不再被挤到下一行或挤出可视区;窄屏时整条独占一行。

- **简历中心新增「按岗位筛选」,并写进地址栏。** 选中岗位后列表按该岗位过滤、页码回到第 1 页,地址里带上 `?job_id=`,用这个链接(书签或深链)重新打开会自动回填岗位名,岗位已被删除时降级显示 `#id` 且筛选仍生效;点清除只删掉岗位这一项,「关键词」「有无岗位」等其它筛选原样保留。

- **收藏星标恢复亮琥珀色。** 上一轮把 `#f5a623` 换成了警示文字色 `#875300`,浅色下星标变成一颗深棕点;岗位详情分区图标、助手会话旗标、助手链接 hover 也被一起换成了正文色。现在星标统一用 `--rf-favorite`(浅色 #f5a623 / 深色 #f0c04a),分区图标回到信息蓝、紫、绿、琥珀,链接 hover 用 `--rf-info-strong`;新增 `color-regression.test.ts` 按选择器钉死,防止再退化。

- **「导出全部数据集」不再因为旧数据集缺表而整包失败。** 按模型全集逐张 `COUNT(*)` 时,停在 0040 的旧数据集没有 `application_track_event`,会抛 `no such table` 让整包中止;现在先读实际表集合,缺的表按 0 记录并写入清单 `missing_tables`,错误信息也会带上数据集名称。

- **「导出当前数据集」不再误报「备份不完整」。** 压测数据集的 310 个附件在导入时就已缺失(原件在 `runtime/stress/user_files` 且 SHA-256 全部匹配),旧逻辑每次都把它们算成"这次导出丢了文件"。现在:导入时就缺的记 `*_missing_at_import`,只给说明不触发 `X-Backup-Incomplete`;响应头新增 `X-Backup-Asset-Missing / -Mismatched / -Missing-At-Import`,界面按「新缺失 / 哈希不符 / 导入时缺失」三类分开写文案。310 个附件已逐文件校验补回 `backend/data/datasets/user_files`(新增 310、跳过 1、0 个哈希不符)。

- **深色弹窗的标题/页脚不再是两条"黑条"。** 它们原先用比容器更暗的 `surface-alt`,深色下像渲染残留;现在与容器同色,只保留一条细分隔线(手动添加岗位、从头编写通用简历、近期提醒等全部弹窗一起生效)。

- **窄屏(701–767px)「我的空间」文字不再顶出圆框。** 该区间按钮被压成 48×48 但文字仍以 12px 渲染;现在方形成按钮时同时把字号设为 0、图标 18px。

- **投投的纸飞机重新抠图,暗色下没有白边。** 从 `generated-images/Cute_floating_orb_pet_characte_…png` 重新抠出主体,飞机边缘单独收紧 alpha 并去白底混合,改用 28KB 的 WebP(原 PNG 56KB);助手初始页与 Star 提示弹窗同步换新。

### Security

- **桌面版打印桥加固。** 打印 IPC 现在只接受主窗口顶层 frame、校验通道与 payload 形状(≤5MB UTF-8 字节),加载字体/图片、就绪与打印三段各有明确预算;异常与超时都会销毁隐藏窗口,不会留下挂死的后台窗口。`nodeIntegration: false` / `contextIsolation: true` / `sandbox: true` 保持不变,打印窗口拒绝导航与开新窗口,打印输入仍只来自应用生成的 HTML(不接受任意 URL)。真实 Electron 的 sender/frame 语义仍需 `desktop-shell` CI 与人工 smoke 覆盖,本地只有单测证据。

- **联网搜索正文抓取的边界写清楚。** 默认 `fetch_pages=0` 只取搜索摘要;开启后每个候选 URL 在**每一跳重定向**都做 scheme/端口/DNS 公网地址校验,但"解析检查"与"httpx 建连时再次解析"之间仍存在 TOCTOU 时间窗,因此模块与 `SECURITY.md` 都明确不宣称"完全防御 DNS rebinding";6to4/NAT64 不做解封装(已知边界)。本轮未放宽任何公网白名单、未改变事件循环语义、未把默认值改成开启。

- **消除 CodeQL 静态扫描的全部 10 个开放告警。** 字体文件名校验改用 `os.path.basename` 并先归一 `\` / `/` 两种分隔符再剥目录成分(`..\\x.ttf` 这类跨平台写法现在一律拒收,合法字体名行为不变);网申资料粘贴识别与经历头解析的两条正则改写为无多项式回溯的等价形式(超长行的最坏耗时从平方级降到线性,正常输入的解析结果逐字不变);数据集导入时「全局模型配置 / 字体未能导入」的提示与恢复问题清单不再携带异常原文(可能含本机路径),详情只进服务端日志(datasets / global_assets / asset_restore 共 14 处);导出一致性测试的 `<script>` 过滤正则兼容 `</script >` 这类带空白的结束标签;前端守卫测试里两处只替换首次出现的 `../` 改为全局替换、一处构造 `RegExp` 前的转义补齐反斜杠。

- **消除 CodeQL 静态扫描的 6 个新增开放告警(回到 0)。** 交付包归档的磁盘名构造改为纯服务端值(3 处 path-injection:用户文件名只进数据库展示列,路径只由内容摘要与白名单扩展名拼出,内容寻址去重语义不变);「采纳来源文本」409 响应的 detail 改用静态文案、异常详情只进服务端日志(stack-trace-exposure);导出一致性测试的 `<script>` 过滤正则再放宽到带属性的结束标签 `</script attr>`(bad-tag-filter 第二轮口径);静态路径越界探针脚本里的假"密钥"内容与文件改名 canary(clear-text-storage 误报,脚本语义不变)。


- **导出归档路径构造收敛(CodeQL `py/path-injection` 清零)。** 归档目标改为 `abspath` 归一化 + 归档根前缀校验(与仓库既有的附件安全校验同一范式),并新增 8 条恶意格式 / 穿越名测试;内容寻址命名、同字节复用与超限拒绝行为不变。
- **测试辅助的 script 标签过滤支持大小写混写(CodeQL `py/bad-tag-filter` 清零)。**


### 兼容性与已知限制(简历语言)

- **备份格式号仍是 6。** 本次新增的是数据库列(不是包内新成员),随整库快照一起走;`global_assets.json` / `fonts/` / `ASSET_CATEGORIES` 都没有变化,所以不升格式号,旧样本也不用重新生成。

- **旧备份(0048 及更早)可以正常导入。** 候选库在导入时会被自动迁到当前版本,`language` 补为 `zh-CN`,表集合不变,因此通过完整性校验。

- **新备份导入旧版本程序会被拒收**(提示"备份来自更新版本的 ResumeForge")。这是所有迁移的既有行为,不是本次引入的缺陷。

- **降级 0049 会丢弃语言值,但库可用。** `downgrade` 会 `drop_column`,中英文标记随之消失,简历内容与其它版式字段不受影响;回到本版后这些简历按 `zh-CN` 读。

- **已知限制一:冒号未本地化。** 英文产物的字段前缀后面仍是全角冒号,例如 `Objective:…`、`GPA / Rank:3.8`。本次只换词、分隔符与括号,标点本地化不在范围内。

- **已知限制二:Word 页眉仍只印姓名与求职意向,不含 GitHub 与个人主页。** 这是本次改动之前就存在的链路差异(PDF 与 HTML 的联系方式行有这两个字段,Markdown 与 Word 没有),本次只换文案、不新增字段,因此保持原样。

- **已知限制三:生成链路(大模型)不消费这个字段。** 选英文只改变纸面固定文案,**不会**让已经生成好的中文内容变成英文;它也不是"下次生成时用英文写"的开关。让生成链路感知语言是下一个需求。

## 1.0.2 - 2026-10-18

### Added

- **体验示例数据集覆盖全部主要页面,并支持幂等复用。** 示例数据从"一个岗位 + 一份简历"扩充到岗位(4 个不同状态/来源)、简历(挂岗 + 通用)、求职进度、资料箱、知识库、面经、历史题库与网申资料,全部为纯虚构"示例"内容(播种过程无任何网络/模型调用);重复点击「先体验示例数据」时直接回到已有示例数据集(接口新增 `reused` 标记),不再每点一次就新建一份。示例数据按 `seed_version` 管理,内容升级后重新载入会新建而不覆盖旧份。

- **桌面版新增退出桥与应用内更新的自动安装闭环。** 桌面壳向前端暴露 `window.splash.isDesktop` 与 `quitApp()`:桌面里的「退出应用」使用面向桌面应用的确认文案(后台服务一并停止、从桌面快捷方式再次启动),确认后停掉后端并退出壳进程;浏览器内行为不变。应用内更新在用户确认"将下载并安装更新,期间应用会自动重启"后,下载与校验通过即**自动退出并安装**——不再弹出第二个需要人工确认的对话框(此前用户离开电脑或窗口在托盘里时,安装就永远不会发生);弹更新提示前先把藏在托盘的窗口带回前台;安装器改为由脱离的等待进程在主进程完全退出后再静默运行,避免 NSIS 安装与仍在运行的壳争抢文件而静默失败;启动 30 秒后的首次更新检查若赶上离线,之后每 6 小时自动补查。

- **分区把手旁新增「上移 / 下移」微调按钮。** 悬停分区标题栏(或键盘聚焦)时浮现,点击逐格移动、边界自动禁用,与拖拽共用同一排序路径;长分区不想拖的时候点两下即可。

### Fixed

- **「我的资料」交互与保存流程修复(9 项用户实测问题)。** 使用指南弹窗初始焦点不再落在关闭按钮上(键盘焦点环保留);非编辑态的出生/经历日期控件不再可修改;分区里多条记录的拖拽排序恢复可用(不再整块折叠,条目保持展开);大分区拖拽时卡片全程跟住鼠标,页头「调整模块顺序」按钮移除(排序只由拖拽驱动,键盘方向键仍可用);网申资料不再在简历资料校验失败时被单独保存(两次都成功才算保存,页面才会退出编辑态);「粘贴文本识别」成功后自动关闭弹窗、进入编辑态并回到「简历资料」分页;设置页更新说明按 Markdown 渲染(标题/列表/粗体)而不是显示原始符号。

- **粘贴文本识别准确率提升(本地规则 + AI 提取)。** 补齐「项目/实践经历」「个人技能」等分区标题别名;日期写法规范化(`2025.3`→`2025-03`、`2025.6.11`→`2025-06-11`、`2025.3 至今`、`2024—2025学年`);以日期开头的多列条目(项目/实践、校园经历单列「组织+职务」)按条目拆分且不再互相吞并;教育分区内的 GPA、主修课程与荣誉奖项落到对应字段,不再被误拆成独立条目;技能分类行(语言/计算机/摄影)逐项拆分、整句技能描述整条保留;「年龄」不再混入姓名;AI 提取提示词明确按日期块拆条目、日期规范化与逐字保留描述,并对模型输出的分区别名键(practice/campus 等)做宽容映射。

- **拖拽排序稳定性重做(用户实测的三轮问题全部根治)。** 条目拖拽改为按所属列表容器采集几何,消除同页多个列表之间下标串味导致的互相抽动;条目拖到列表末位不再出现"两张卡片重叠却永远不交换"的死锁;大分区拖拽激活瞬间不再凭空跳位、卡片全程跟住鼠标(此前塌缩排序模式与滚动夹持叠加导致卡片脱手、浮出可移动范围);交换瞬间不再向上跳一下(布局提交与位移写入同帧完成);拖拽中被拖卡片背景不再出现整段绘制缺失(此前看起来像游离的"重复把手");浏览器滚动锚定与自动滚动的正反馈环路已切断,临界点来回抖动不再引发顺序翻转。

### Changed

- **拖拽跨槽大幅提速、换位更顺滑。** 换位推进改为按指针行程计算(每 48px 一槽,与分区高度解耦)——把一个分区跨过几个 1000px 级的大分区,从每个都要拖近千像素降到 48px;贴边自动滚动提速一倍,且滚动期间顺序持续跟进;每次换位其它卡片以 150ms 平滑滑动到新位置(被拖卡片仍 1:1 跟手),`prefers-reduced-motion` 下自动关闭;拖拽中的卡片边缘改为浅色描边 + 分层柔和阴影,分区拖拽时隐藏条目级把手减少视觉噪音。

## 1.0.1 - 2026-10-17

### Added

- **自动备份频率可在设置页自定义。** 此前快照间隔是环境级固定值(默认 7 天,改配置需改环境并重启);现在存为运行时可改设置(1–365 天),设置页选择后下次调度检查即生效,备份状态展示同步读取。

- **岗位/资料/进度识别支持扫描版(图片型)PDF。** 文本 PDF 仍在本机提取文字、不上传原文件;扫描页自动转为图片走 AI 视觉识别(与截图上传同一路径与体积预算),单份最多转换 8 页。

### Fixed

- **岗位批量删除改为软删除(进回收站、可恢复),与单条删除及其他实体口径一致。** 此前 `POST /api/jobs/batch-delete` 直接物理删除数据库行,是唯一绕过回收站的删除入口——用户在界面批量删岗位即不可恢复的数据丢失;现改为走 `trash.soft_delete`(与单条删除同一软删语义),删除后可在「回收站」恢复,彻底删除仍只在回收站里单独提供。

- **修复助手空态「深夜关怀语」文字对比度不足的问题。** 该文案此前用 antd 的 `type="warning"`(#faad14),在白/浅底上对比度仅约 1.9:1,过不了 WCAG AA 4.5:1;现改用站内 warning 文字 token(#875300,白/浅底均 ≥6:1),关怀/提醒的语义不变。

- **修复岗位广场空表无法键盘聚焦的横向滚动区(无障碍)。** 岗位表在无数据时仍固定横向滚动(列宽合计 1330px),`.ant-table-content` 成为"可横滚、区域内却没有任何可聚焦元素"的容器,命中 axe 的 `scrollable-region-focusable`(serious)。现在空表不再启用横向滚动,有数据时按列宽固定横滚的原行为不变。

- **关闭自动备份时不再启动空调度线程或获取数据集租约。** 避免禁用状态下的后台检查与数据集切换争抢;开启时的备份、排空和租约保护保持不变。浏览器键盘冒烟先等待初始化后的真实链接挂载,消除 Windows 冷启动时 Tab 早于 React 挂载的竞态,不降低键盘断言。

- **示例数据徽标文字加深,满足小字至少 4.5:1 的对比度。** 页头与数据集菜单共用样式,并增加实际样式的对比度回归测试。
- **DeepSeek 默认预设同步为模型列表中的 `deepseek-flash`。** `deepseek-v4-flash` 作为兼容别名继续识别,不改写已保存配置;思考能力提示同步更新。
- **模型连接测试不再把空白回复报告为成功。** 保留 64 Token 的探针上限,剥离用户思考参数,并仅对能力表确认默认开启思考的服务商发送关闭参数;其他服务商不附加专有关闭字段。
- **修复 DeepSeek 思考模式的工具续轮协议。** 官方 OpenAI 兼容接口在当次工具调用消息里保留完整 `reasoning_content`,不混入正文;其他服务商保持原行为,历史展示仍按原上限截断。
- **迁移相关界面的 Ant Design 弃用属性与列表。** 加载提示使用 `Spin.description`,纵向间距使用 `Space.orientation`,浮层样式使用 `Tooltip.styles.root`,备份、导入预览与剪贴片段复用现有 `Listy` 行组件。

- **修复知识库卡片高度不齐的问题。** 网格的等高拉伸在卡片外层的可点击包装元素处断链,补齐后同一行卡片高度一致(内容多的卡片内部摘要仍按行截断)。

### Changed

- **手动备份卡的多选操作行与说明文字同行对齐,「退出多选」改为「取消」。** 批量勾选框同步改为相对行内容垂直居中。

- **手动备份卡的文案动词统一为「移除」。** 说明文字此前写"逐份确认后移到",与按钮上的"移除"混用容易误会,现统一口径(去向说明保留:移除后进 backups/.trash/,可随时找回)。

- **修正模拟面试「历史面试」删除确认的误导文案。** 历史面试的删除是真删除、不经过回收站,确认弹窗却写着"删除后可在回收站找回";现如实提示「删除后无法恢复」。

- **修复编辑大模型配置记录时展开「高级调整」导致整个弹窗过高的问题。** 配置弹窗限制在视口高度内,只有表单正文区域滚动;标题、关闭按钮以及测试连接、取消和保存按钮保持可见。

- **修复编辑大模型配置时「获取可用模型」按钮无反馈的问题。** 查询时提供明确加载状态;无模型或请求失败时保留原因并允许重试。编辑已有记录且密钥留空时,使用该记录原有密钥引用;更改地址或密钥后,过期请求结果不会覆盖当前表单。

- **进一步收紧网申填表「浏览器设置」弹窗的行距。** 说明段落、字段换行行距(16→8)与各段间距统一压缩,卡片内容更紧凑(仅该弹窗,投递台同字段组件不受影响)。

- **修复「编辑配置记录」弹窗创意度滑杆两端文字被截断(「严谨」只显示「谨」)且弹窗底部出现横向滚动条的问题。** 三列布局偏挤时,滑杆两端的档位文字与拖动手柄以轨道端点为中心渲染、溢出列宽——左侧被弹窗内容区的横向裁剪截断、同时撑出横向滚动。弹窗加宽至 720px,并把滑杆整体内缩(包裹层留白 16px)让档位文字与手柄完整落回可视区,label 与其他列保持对齐;内容区另做了横向溢出兜底。

- **修复投投悬浮球弹出的卡片被其他弹窗盖住的问题。** 深挖后发现根因是 antd 6 的弹层层级自动偏移:抽屉内的弹窗实际层级比静态基准高出两级,恰好与剪贴板卡片同级、又因 DOM 顺序靠后而胜出。现重新规划层级阶梯:AI 求职助手卡 / 保存卡 / 剪贴板卡抬升至 3400 / 3450 / 3500(覆盖两层乃至三层容器嵌套的弹窗),悬浮球及其提示仍在卡片之上(球随时可点),卡内下拉等弹层同步抬升;并补了一组层级阶梯的防御性测试,防止未来再被静默破坏。

- **修复「编辑配置记录」弹窗展开高级调整后折叠头上下顶格的问题(第三次修复,根因挖穿)。** 前两次分别败给"运行时注入的组件样式覆盖静态样式表"与"上方说明文字自带的 12px 下边距把折叠头的 8px 上边距整个吞掉(相邻 margin 塌陷取最大值)"——后者意味着间距代码其实生效了、只是被相邻元素的边距完全吃掉。本次改为不参与塌陷的 padding 承载间距(上方 16px、下方 8px),并以测试直接钉住该内联值;机制上同时免疫样式表加载顺序与边距塌陷。

- **修复下拉框「当前选中项」底色过深的问题。** 根因是主题品牌色过深,antd 由此算出的下拉选中底色是一档中灰、在浅色界面里对比刺眼(编辑配置弹窗的快速预设/接口协议/思考强度等处均受影响);现与侧边菜单选中态同一套处理——浅色品牌底 + 深色文字,全站下拉统一生效。

- **修复网申填表「浏览器设置」弹窗内的多余空行。** 表单项默认下边距与说明行行距叠加出 40px 空隙,现收紧为紧凑节奏(仅作用于该弹窗)。

- **修复备选岗位批量工具栏与搜索框间距过大的问题。** 上一轮放宽到 28px 后留白过多,收紧为 12px。

- **修复备选岗位「全选全部」后翻页丢失选择的问题。** 此前每次翻页都会清空已选记录,跨页「全选全部」选中的条目在翻页查看时被静默清掉;现在翻页保留跨页选择(已选计数跨页累计、本页勾选框按本页口径亮起),换搜索词、清空选择、退出多选仍按原语义清空,删除记录时同步从选中集剔除。

- **修复「编辑配置记录」弹窗展开高级调整后折叠头上下顶格的问题。** 首次修复写在样式表里的间距被 antd 运行时注入的按钮样式覆盖、在部分构建下未生效;现改为直接写在元素内联样式上(上下 8px),并以测试钉住该内联值,不再依赖样式表加载顺序。若你看到的效果仍是旧的,重启一次应用(或重新打包)即可拿到新构建。

- **修复备选岗位批量选择的两处状态逻辑问题。** ①进入多选但未勾选任何记录时,右侧仍显示一排灰色禁用的「批量导入/批量删除/清空选择」按钮——现在未选中时这组按钮直接不渲染,选中即出现;②「全选本页」勾选框的选中/半选口径此前错把跨页全选算进本页判断:跨页「全选全部」后勾选框错误显示半选、再点会静默砍掉跨页选择——现改为「本页」口径(本页全中即勾选、部分即半选),勾选/取消以并集/差集合并,跨页选择在翻页勾选时不再被破坏。

- **修复网申填表「历史网址」菜单出现双滚动条且无边框的问题。** 此前限高容器与菜单自身各出一条滚动条;现在只保留外层一条(内层菜单不再自行滚动),并为浮层补齐背景、圆角、边框与阴影,与站内其他浮层观感一致。

- **修复「编辑配置记录」弹窗展开高级调整后折叠头上下顶格的问题。** 折叠头与上方说明文字、下方参数提示之间补 8px 间距(收起/展开两态一致)。

- **恢复成功与警告提示原有的浅绿/浅黄配色。** 深色文字色不再用作主题基础色,修复投递台、网申填表、简历预览及各处成功/待核对标签背景变暗、发灰的问题;相关边框与图标一并恢复原来的配色。

- **我的资料读取失败时保留明确错误,阻止进入空白编辑器。** 失败后可直接重试;原资料未读回之前,页面与投投均不提供编辑/保存入口,避免把读取失败误认为没有资料。

- **修复投投悬浮球双卡片重叠遮挡与三处浮层「被裁剪」的问题。** 剪贴板卡片与 AI 求职助手卡片同时弹出时前者被后者盖住(两卡层级对调:剪贴板 3300 > 助手卡 3200,保存卡 3250 恰好居中);「思考强度」下拉、剪贴板「···」菜单、输入框图片预览看似被边缘裁掉,实为 antd 6 弹层在全局 zIndex 基准之上只有少量偏移、整体低于卡片层级被盖住——新增「卡内弹层」基准并用局部主题抬升两张卡内的全部弹层(实落区间仍低于悬浮球,球的随时可点不受影响),顺带修复行操作删除确认框曾被简历编辑器弹窗盖住的存量隐患。

- **修复备选岗位「全选全部导出 CSV」超过 200 条必报错的问题。** 导出请求的 id 列表上限为 200,全选 420 条记录直接报错;上限放宽到 5000,岗位/简历/备选的批量操作端点同步放宽,前端超过 5000 时明确提示缩小范围而不是撞服务端校验。

- **修复求职进度模块加载时统计区布局错乱的问题。** 此前加载中渲染数值为 0 的统计卡片并与「统计加载中…」文字错位同屏;现在加载中显示与真卡同形的骨架占位,完成后一次性渲染统计网格,加载中与空数据(0 值)明确区分。

- **修复「我的资料」分区拖拽时被拖卡片脱离鼠标飘走的问题。** 上一轮重构后拖动已不卡顿,但 CSS `order` 视觉重排的瞬间,被拖卡片的 flex 布局位置会随新顺序跳变(例如从第 3 位换到第 4 位,盒子位置瞬间下移一个相邻分区的高度),而跟手用的 `translateY` 是相对自身盒子的偏移、没有补偿这个跳变,于是视觉位置=新布局位置+旧偏移 ≠ 鼠标位置,卡片「甩出」鼠标。现在实时重排时按分区高度前缀和精确计算被拖项布局位移(新增纯函数 `draggedLayoutShift`,含被拖项自身高度不再计入新前缀的修正),transform 改为「指针位移 − 累计布局位移」,同一帧内新布局与新 transform 一起绘制,被拖卡片全程钉在鼠标上;basic_info 钳位、6px 激活阈值、pointercancel 清理与零 DOM 查询的渲染成本守卫(内容子树零重渲/提交有界/DOM 同实例)全部不变。

- **修复模拟面试两个连环问题:选「自动」轮数必报 422、面试中提交回答报「请求失败(HTTP 404)」。** ①「自动(AI 判断收尾)」档此前随表单提交 `rounds_min=1` 作模式开关,被后端「自动收尾最早轮数 ≥ 3」的校验拦下("Input should be greater than or equal to 3"),面试根本开不起来;现在 `rounds_min` 语义改为「自动收尾的最早轮数下限」,提交合法值 3(AI 最早第 3 轮起可按表现收尾),`rounds` 仍提交服务端硬性保险 12,恒满足 `rounds_min < rounds`。②提交回答的流式请求(SSE `consumeSSE` 不像普通 `request()` 会自动补 `/api` 前缀)此前漏掉前缀,POST 打到前端自身源上返回 404——即使会话创建成功也答不了题;现在请求 `/api/interview/{id}/answers/stream`,后端的明确错误(如「面试不存在或已被删除」)也会原样展示给用户而不是干巴巴的状态码。另核对创建失败的处理:422 时前端已正确留在设置页并展示错误、绝不乐观进入面试界面(补了回归测试钉住这两条行为)。

- **界面打磨:三处控件间距统一 + 投投弹出过渡。** ①简历中心筛选栏:「交付存档」按钮、搜索框与「收藏状态」「岗位关联」两个筛选收进同一工具栏(统一 12px 间距,此前按钮与搜索框水平贴死、各组控件的垂直间距 12/16px 不一致);②求职助手侧栏:「新对话」按钮与搜索输入框之间补 10px 垂直间距(此前零间距贴死,投投浮窗的窄板侧栏同样生效);③简历预览:工具栏、AI 核对警告条、溢出警告与画布之间各补 12px 呼吸间距(此前三层全部紧贴),分页页码标签与画布上缘的间距同步加大。④投投悬浮球:点击弹出的助手卡片改为 160ms 淡入上浮入场、小贴士改为 140ms 淡入,替代生硬的瞬间弹出;系统开启「减少动态效果」(prefers-reduced-motion)时直接显示、不做动画。

- **求职助手回复体验修复:流式生成期间可自由向上滚动,切换页面不再中断回复。** 此前回答的每个流式增量都会把消息列表无条件拽回底部,用户上翻查看历史立即被拉回;现在采用「吸底跟随」——距底部较近时自动跟随新内容,向上翻阅时不再被打扰,并显示「回到底部」悬浮按钮(点击回到底部并恢复跟随;切换会话、加载详情时仍定位到最新消息)。此前离开助手页会中止正在生成的请求并丢弃流式状态;现在流式会话跨页面存活(助手页与投投浮窗共享同一份状态),返回后可继续查看进度,离场期间回复完整生成并刷新会话列表,回到页面能正常看到已落库的完整回复。

- **修复带照片简历运行匹配分析必然失败的问题。** 简历照片以数百 KB 的 base64 data URL 藏在提示词数据里,在 2 万字符预算的收缩迭代中会挤掉几乎全部真实简历文本,模型看到的几乎全是 base64 片段;现在构造提示词前先把 data URL 剥离为简短占位文本(个人资料与简历同理),证据核对也不再受 base64 干扰,提示词预算数值保持不变。

### Changed

- **批量操作条的按钮高度全站统一。** 「退出多选」此前是小尺寸、与各页面同一行里的「批量删除 / 删除所选 / 全选全部」等默认尺寸按钮高低不齐(简历中心、模拟面试题库等处均受影响);现统一为同一高度。

- **联网搜索设置改为「查看态 → 编辑态」交互。** 字段默认灰色只读,点「编辑搜索设置」解锁(编辑时按钮换成「保存搜索设置 + 取消」,取消丢弃改动,保存成功后回到查看态);「重新读取」移到卡片右上角,只保留环形箭头图标(悬停显示文字)。

- **简历中心多选工具栏去掉重复的「全选本页」勾选框。** 它与表格表头(收藏列左侧)自带的全选勾选框功能重复——工具栏只留已选计数、全选全部与操作按钮,全选本页统一走表格表头。

- **两个 CSV 导入弹窗的「下载空模板」按钮与「取消 / 确认导入」合并到同一行。** 此前模板按钮孤悬在内容区左下、与底部操作行基线错位;现在统一收进弹窗底部操作行左侧,左右对齐、一行清爽(导入岗位 CSV 与导入备选岗位 CSV 同步修改)。

- **编辑配置记录弹窗:「不限制」移到「最大输出 Token」标签右侧,参数区行距收紧。** 勾选框不再独占一行,字段标签、说明问号与开关同行排布;参数三列与思考模式区之间的空隙收紧 12px。

- **大模型配置记录卡「使用中」的永远排最顶部。** 当前生效的那张配置是最高频查看对象,不再淹没在列表中间;其余记录保持原有顺序不因切换而打乱。编辑弹窗中「不限制」勾选框文案精简(完整解释仍在悬停提示里),三分之一列宽内不再换行。

- **求职助手输入区的「思考强度」帮助提示改为问号图标触发。** 此前悬停整块控件就弹出长提示、容易误触;现在「思考强度」文字旁有一个问号图标,悬停问号才显示说明,提示内容不变。

- **手动备份卡操作重整。** 每份快照的「移除」按钮收进「···」菜单不再裸露;支持右键进入批量选择模式——勾选多份后「移除所选」一次确认逐份移到 backups/.trash/。

- **岗位广场多选:「退出选择」挪到「全选全部岗位」右侧。** 退出入口与选择操作同处批量操作条,不再挤在筛选行末尾。

- **联网搜索设置的操作按钮挪到卡片右下角。**

- **联网搜索卡片右上角常驻「编辑搜索设置」入口,并移除「重新读取」按钮。** 查看态在右上角点编辑进入编辑态(保存 / 取消在右下角)。

- **修复手动备份卡批量选择模式的排版与重复勾选框。** 勾选框此前在行右侧操作区与内容区各渲染了一个(一选项两框),且内容区包裹方式破坏了列表布局、把标题与说明挤成竖排;现改用列表项原生的头像槽位承载唯一勾选框,恢复正常排版。

- **精简投投剪贴板的说明文案。**

- **移除界面左下角的「检查更新」按钮。** 检查更新统一走「设置」页的更新卡片。

- **备选岗位批量选择交互重整:多选模式的进入即所见。** 卡片勾选框与整套批量工具栏(全选本页/已选计数/全选全部/导出 CSV/批量删除/清空选择/退出多选)现在只在右键卡片「批量选择」后出现——此前勾选框常驻、批量按钮要等勾选了卡片才冒出来,操作逻辑割裂;进入多选后批量按钮立即显示(未选中时禁用),退出多选整组消失。批量导入入口按需求移除(单条「导入到岗位」保留在卡片菜单,采集结果面板不受影响)。搜索框与工具栏间距同步加大。

- **操作入口整理:投递记录与备选岗位卡片。** 投递记录卡片上独立的「详 情」按钮并入行内「···」菜单(查看详情排在删除上方,整行点击进详情保留);备选岗位卡片右键菜单重排为两组(查看详情/编辑 — 分隔线 — 批量选择/删除,删除标红,新增「查看详情」与卡片点击同源);大模型 API 配置记录卡上「使用中」条目的「使用」按钮改为禁用灰态,不再显示为可点击。

- **备选岗位批量工具栏重排。** 按「选择区(全选本页三态勾选框 + 已选计数 + 全选全部)→ 批量操作区(导出/导入/删除/清空,仅在有选中时出现)→ 退出区」三组布局,统一间距、窄屏自动换行。

- **全站界面与交互基线统一。** 控件采用 8px 圆角、桌面常规 40px 高度及 150–200ms 状态过渡;语义色、次级文字和警告标签加强对比度,尊重系统「减少动态效果」。小于 768px 的窗口改用左上角抽屉导航,页头显示当前页面、保留数据集与我的空间入口;筛选全宽排列、操作区换行,宽表格和回收站分类在各自区域内滚动。
- **补全键盘与错误恢复闭环。** 增加「跳到主要内容」、页面切换播报与浏览器标题;资料卡片的详情入口与行操作改为同级原生按钮,避免交互嵌套。岗位、简历、收藏、资料箱、知识库、工作台、求职进度、事实台账、求职统计和回收站读取失败均提供持续可见的重试入口。资料箱/知识库编辑器关联字段标签,支持 Ctrl / ⌘ + S、Ctrl / ⌘ + Enter 保存,知识标题失焦校验与错误定位保留已填正文;输入法合成、按键连发和保存中不会重复提交。

- **大模型 API 配置整合为单一区块:配置记录成为主体,支持自由切换与完整编辑。** 此前页面上是「当前配置摘要卡」+「配置记录列表」两个相邻区块,且配置能力(创意度滑杆、最大输出不限制、思考模式探测、高级参数折叠区等)藏在页面级表单里;现在合并为一个「大模型 API 配置」区块——已保存的配置记录以卡片列表呈现(默认前 5 张,可展开),每张卡标明是否「使用中」(生效配置一目了然),卡上直接「使用 / 测试连接」,编辑与删除收进「···」菜单,点卡片本体可查看该配置的只读详情;「新增配置」与「编辑」共用同一张弹卡,此前页面表单的全部能力(快速预设与协议联动、创意度滑杆、超时、最大输出及不限制勾选、思考模式总开关/强度/自定义/支持探测、高级调整折叠区、密钥显隐与脱敏回填)原样迁入弹卡;「保存当前配置」按钮移除;编辑使用中的记录保存后自动同步到生效配置,新增记录不自动生效(点「使用」切换)。记录卡「更新于」时间改为悬停显示。

- **备选岗位批量导入到岗位广场:入口补全二次确认。** 勾选多条备选岗位后经批量选择模式的「批量导入」即可创建为岗位广场记录(按标题+公司+来源链接查重跳过,导入结果逐条反馈:成功/重复/在回收站/无效);本次为导入动作补上确认弹窗与超上限用例,并把导入逻辑收进批量工具 hook 统一维护。

- **修复网申填表「历史网址」菜单限高未生效且打开后整页滚动卡顿的问题。** 限高此前写在 antd 6 Dropdown 的 menu.styles.list 上,实测不透传(菜单仍占满整屏);改为 popupRender 包一层限高滚动容器。卡顿根因是几十条历史每条各挂一个删除确认气泡与一个悬浮提示触发器,页面滚动时全部同时做定位计算——删除确认改走全局确认框、网址完整提示改用浏览器原生 title,菜单内不再堆积弹层触发器。

- **岗位广场与简历中心的批量选择体验统一。** 卡片右上角的三点菜单移除(其全部条目右键菜单本已覆盖,并为键盘用户补了菜单键 / Shift+F10 触达同一份菜单);「全选本页」文字入口移除(左上角勾选框即全选本页,悬停有提示);简历中心「收藏状态」筛选移除(收藏功能保留,查看收藏走收藏夹);批量操作不再限制每次条数(超 5000 明确提示缩小范围);两页批量按钮文案口径统一(如统一为「批量删除」)。

- **列表分页与展开体验。** 投递记录每页 10 条、批次展开加平滑过渡(系统开启「减少动态效果」时直接展开);内推记录新增服务端分页(每页 10 条,顺带消除该接口的全量无界下发);知识库每页 15 条;资料箱每页 16 张(桌面 4 列 × 4 行)。

- **设置页信息架构整理。** 大模型 API 配置从整片裸露输入框改为卡片化:默认只见当前配置摘要卡与已保存配置记录列表(密钥一律脱敏),「测试连接」直接放在每张卡上,点「编辑」才展开表单,右上角单独的「编辑设置」按钮移除;数据集/手动备份/助手技能列表默认只展示前 5 条、其余一键展开;数据页操作按钮按语义分层(新建为主操作、导出同族、导入独立),降低误按。

- **界面细节清理。** 资料分区拖拽提示去掉「上下方向键」表述与「拖到此处」占位文字(落点感知保留高亮样式);网申填表「历史网址」列表限高滚动查看;工作台样式模板标题与按钮组补间距、窄屏自动换行;「下载空模板」按钮与上方输入框补间距(备选岗位与岗位广场两处);求职助手侧栏加载中显示「正在加载对话…」、仅加载完成且确无会话才显示「暂无对话」,加载指示不再压住空态插图。

- **「我的资料」分区拖拽排序性能重构:拖动全程不再触发表单全量重渲。** 根因诊断:实时重排此前走页面级 React state,拖动中每次插入位变化都会重建 antd `Form` 元素并换新 Form context,导致整个资料表单的 67 个表单项(含 12 组年月日三连选)全量重渲一次——拖一次跨越三个分区就是连续三次百毫秒级卡顿,且与组件 memo 优化无关(memo 挡不住 context 变更)。现在分区顺序改由外部 store 供分区栈自行订阅(`useSyncExternalStore`),页面级渲染链不再感知拖拽;分区栈 DOM 顺序固定、视觉顺序交给 CSS `order`(实时重排只更新两三个分区的样式,不再移动大表单的 DOM 子树);被拖分区在拖动期间提升为独立合成层(`will-change: transform` + 置顶)并带抬起投影,松手即摘。新增 Profiler 计数守卫测试:拖拽跨越 3 个分区时分区内容子树零重渲、提交次数有界、DOM 节点不重挂。已知取舍:分区的 Tab/读屏顺序固定为默认序(视觉顺序由 CSS `order` 排布),键盘用户仍经「上移/下移」按钮调整顺序,语义不变。

## 1.0.0 - 2026-10-14

### Security & Reliability

- 简历导出 GET/POST 在读取私人简历和执行保存目录探针前校验回环直连与跨站来源;非回环、非回环 Origin 或 `Sec-Fetch-Site: cross-site` 请求现在返回 403。保存位置只能由后端本机原生文件夹选择器设置、校验与持久化;HTTP 设置接口拒绝客户端提交的任意路径并只接受恢复默认操作。演示接口自检使用明确的本机测试客户端,并新增路径输入拒绝、选择器校验/持久化、导出来源与兼容性回归测试。
- 完成桌面依赖剩余风险清偿:精确替换 `@electron/get@3.1.0` 的代理依赖为 `global-agent@4.1.3`,从锁文件移除 `roarr` / `sprintf-js`,撤销 8 项已清偿豁免;通过隔离安装和真实代理下载/校验验证兼容。审计门禁提升为检查全部漏洞等级。
- Windows 桌面审计不再使用 `shell: true` 执行 npm `.cmd`:通过 Node 运行 `npm-cli.js`,避免 `--registry` 元字符被命令解释器执行。
- 修复多批次并发加载明细时互相作废的请求标识问题,改为逐批次标识与整视图作用域守卫;保留分页、缓存及旧筛选迟到响应保护。任务轮询的旧请求也不再清除新请求的 single-flight 标记。
- 清理前端组件/纯函数混合导出与动态 Hook 依赖列表警告,保留日期/资料分组 memo 行为;备份状态刷新复用带取消和迟到响应守卫的加载器,现有 API 与数据格式不变。
- 浏览器只以固定空白页启动,目标网址通过 CDP 导航,不再进入进程参数;调试入口显式使用回环地址,移除允许任意网页 Origin 的参数。导航连接总会关闭,失败不会被误报为网页已打开,也不会关闭已启动的浏览器。
- 数据集 ID 由验证后的数字值重建;附件路径先规范化再检查带分隔符的目录边界,保留跨平台文件名、链接拒绝和原子不覆盖规则。保存位置设置与原生选择器一样仅允许本机客户端,非回环请求在目录探针前返回 403。
- 完成剩余解析正则线性化,地点前缀共享唯一实现;助手搜索、租约失败与 JSON 自动修复不回传原始异常,磁盘统计改用稳定的 IO 分类提示。
- 更新状态读取采用 single-flight、取消与版本守卫,防止迟到的初始/轮询响应覆盖已完成下载;读取失败提供重试入口,未知状态下不允许下载安装。事实标签色板只读取自有键,未知或原型键回退为默认颜色。
- 扩展安全扫描补充:业务日志使用有界、脱敏、单行的元数据字段,移除问题、技能名、模板名等不必要原文;Pydantic 异常载荷脱敏改为线性扫描,避免重复未匹配标记造成回溯。提案失败/结果未知与系统文件打开失败不再把内部异常写入 HTTP 响应或可见提案状态。
- Jinja 提示词环境按文件类型自动转义 HTML/XML,同时保留 Markdown 和纯文本模型提示词原有字节语义;没有通过启用全面 HTML 转义损坏模型上下文。
- 收紧备份导入与导出边界:附件使用跨平台安全文件名、解压预算、严格清单、原子不覆盖发布和真实故障回滚;旧格式附件也不再直接写入共享目录。
- 为浏览器请求绑定进程内数据集作用域,拒绝旧标签页在切换后把写入落到另一份数据集;模型解析、面试、流式生成和日志错误路径不再回显原始简历或模型响应。
- 桌面更新器校验稳定版本、平台和发布资产来源;桌面迁移在缺少 SQLite 一致快照能力时 fail-closed。
- 新增交付存档独立分页检索和下载入口,原简历删除后仍可查找;页头数据集切换后旧表单保留草稿并拒绝串写。恢复解压预算可用 `MAX_BACKUP_UNCOMPRESSED_MB` 调整(默认 4096 MB)。
- 桌面下载 IO 错误不再挂起,子进程异步启动失败有明确终态;根进程退出但端口仍存活时阻止更新安装。启动页仅使用受控本地图标,并兼容 asar 排除构建资源时的 extraResources 路径。
- **升级安装不再丢旧版用户数据(1.0.0 发布前阻断项)。** 最早发布的 Windows 安装版(ResumeForge-Setup-0.18.0.exe)把数据库、附件与备份写在安装目录的 `resources/payload/backend/data` 里;NSIS 原地升级会先执行旧版卸载器(`/S /KEEP_APP_DATA --updated`),而它把整个安装目录搬进临时目录再删除——用户数据在"新壳的 ADR-0004 迁移流程还没机会运行"之前就已消失,首启只会在用户目录静默新建一份空库。现在安装器在触碰旧版本之前先把这份数据复制到自己的临时目录并**逐文件校验字节数**(应用仍在运行、或复制校验失败,都会中止安装并保证旧数据原样保留、给出可见说明),安装完成后再放回**新** payload 的同名位置,交给既有迁移流程弹窗确认、先备份再搬进 `%APPDATA%\ResumeForge\data`。目标位置已有数据库时绝不覆盖;旧位置带应用写入的「已迁移走」标记(`migrated-away.json`,迁移成功后由 `desktop/lib/data-migration.js` 写入旧数据目录)时整体空转。判据只用安装目录内的文件——安装器不去猜应用的 userData 路径(`%APPDATA%` 下的库可能属于同机另一套安装,桌面壳也支持 `--user-data-dir`)。实现为 `desktop/nsis/legacy-data-rescue.nsh`(整文件以 `!ifndef BUILD_UNINSTALLER` 包住、UTF-8 BOM),守卫测试 `desktop/scripts/test-nsis-rescue.js`;真实「0.18.0 安装版 → 1.0.0」升级演练为零失败。
- **投递浏览器与网申浏览器的登录态目录改为跟随数据目录(`RF_DATA_DIR`)。** 此前两处硬编码 `BACKEND_DIR/"data"`:打包形态下等于安装目录内的 payload 路径,升级安装会把浏览器登录态一并清掉;现在与数据库、附件一致地落在数据目录下(控制台/开发形态没有 `RF_DATA_DIR`,行为与从前逐字节一致),旧位置里的登录态由上面的抢救链一并带回。测试见 `backend/tests/test_data_dir_env.py` 与 `test_apply_browser_manager.py`。
- 本地构建的 Windows 安装包开始生成校验和 sidecar:`scripts/Build-Desktop.ps1 -Target win` 按 CI 与桌面更新器接受的格式(`<hex>`+两个空格+文件名,ASCII、无 coreutils 的 `*` 前缀)写 `ResumeForge-Setup-<版本>.exe.sha256`;0.18.0 发布包里 sidecar 的 `*` 格式会被 1.0.0 的更新器判为非法格式而拒绝更新。同一脚本装配 payload 的解包步骤固定用系统 bsdtar、不再信任 PATH 里的 `tar`(Git for Windows / MSYS 的 GNU tar 会把 `D:\...` 当成远端主机名直接失败),与运行时解包同一口径。

### Added

- **交付存档「下载存档」入口 + 重放语义如实说明(RR-04)。** 简历详情的每条投递记录新增下载按钮:`export` 快照按存档内容 + 实际请求参数重放导出,`apply`/`track` 快照下载冻结的内容 JSON(原简历已删也能取到证据)。重放字节与当初下载的文件可能不同(HTML 每次渲染换 CSP nonce),界面在这种情况明确说明"这是按存档重新生成的版本"并给出原始产物指纹,不再把它呈现成"原件"。

- **桌面端 payload 旧数据迁移(ADR-0004 登记的遗留项落地)。** 旧版安装(数据写在安装目录 `resources/payload/backend/data`)的用户首次启动新版桌面壳时,检测到「旧位置有数据库且新用户数据目录(`%APPDATA%\ResumeForge\data`)无库」即弹窗询问是否迁移(旧数据将保留备份);用户拒绝则本次仍按旧位置启动(不注入 `RF_DATA_DIR`,行为与旧版逐字节一致),下次启动会再次提示(不静默)。确认后按「备份 → 临时名复制 → 逐文件校验(文件集/字节/SHA-256)→ rename 发布 → 写 `migrated.json` 标记」执行:迁移前把旧 data 目录整体复制为同目录旁 `data.pre-migration-<时间戳>/`,校验失败即回滚(删除新位置半成品并弹错说明旧数据完好可重试),旧副本与备份**永不删除**(清理留给后续大版本)。标记写入新数据目录内(随更新替换 payload 也不会丢),已迁移过绝不重复复制或再次弹窗(幂等)。迁移在之后端启动之前完成,任何失败路径都不会让数据处于"两处各一半"状态;复制不跟随符号链接(文件集不符会判定失败而非拉入链接目标内容)。实现为不依赖 Electron 的独立模块 `desktop/lib/data-migration.js`(确认/报错对话框由 `main.js` 注入,模块延迟加载使开发树生命周期探针不受影响),已登记进 `electron-builder.yml` 的 `files:` 清单并通过 asar require 图门禁。配套测试 `desktop/scripts/test-data-migration.js`(8 例:检测四态、迁移成功全量比对、用户拒绝、校验失败注入、复制失败注入、幂等、非空目标保护),desktop 全量 node --test 48 例全绿。

### Changed

- **风险扫描远端增强的台账出站最小化(RR-01,P0 隐私)。** 此前台账条目的标题/主体/候选表述/个人边界/风险备注/面试细节**原文**(只做数字兜底)会随远端增强 prompt 发出去——复验实证泄漏了台账里的合作方名称、对接人姓名与内部编号(它们既不在简历结构字段里、也不在 `redact` 的 token 表里,正则无法证明已匿名化)。现在台账条目**只发结构信息**(类别/承担程度/核实状态/边界与备注"填没填、几条"/面试细节计数),整段还有字节上限;条目原文只留在本地规则里生成追问点,用户看到的仍是完整信息。出站自由文本的兜底遮罩扩展到邮箱、链接与 `data:` 内嵌资源(唯一实现 `services/privacy.mask_outbound_text`)。风险扫描提示词同步改写,明确"台账只有结构信息、不得臆测条目内容"。**并补上用户侧的显式开关**:配置了模型时面板出现「AI 增强」开关(默认开,状态记在本地),关掉后只跑本地规则、连 provider 都不创建;响应新增 `remote_enhancement_available`(有没有配置模型)与原有的 `llm_used`(这次用没用)分开回答,界面不再把"你关掉了"显示成"你没配置"。请求体是可选的新入参 `{"enhance": false}`,不带请求体的老调用方行为不变。

- **搜索关键词语义统一为「字面子串」(RR-07)。** 全局搜索与知识库检索各有 FTS(trigram)快路径与 LIKE 回退,两路此前对同一关键词给出不同结果:`%`/`_` 在 LIKE 里是通配符、在 FTS 里是字面量;前后空白在 FTS 侧被 strip(`" foo "` 会命中 `foo...`);带变音符号的查询在 trigram 下会被折叠(`café` 连 `cafe` 一起命中)。现在统一到 `services/search_matching.py`:LIKE 侧显式 `ESCAPE`,**不做 strip**(空白是子串的一部分,只判空时 strip),带变音符号的查询直接回退 LIKE 保语义。对照测试从"差异被钉住"改为"两路逐条相等",并新增 `%`/`_`/空白/变音符号/引号关键词组与"基线非空"防呆。

- **`/api/search` 与 `/api/stats` 的岗位对象改为摘要(RR-08)。** 这两个最高频入口此前回传完整 `JobOut`(含 JD 全文,单条可达 20KB;`limit=50` 时最坏接近 1MB)。现在与 `/api/jobs` 列表同用瘦身模型:不含 `description`、带 `has_description`;要全文走 `GET /api/jobs/{id}`(搜索结果点击后的深链本来就是这么做的)。前端 `SearchResult.jobs` / `Stats.latest_jobs` 同步改为 `JobList` 类型,两个消费组件(SearchCard/LatestCards)本就只读摘要字段。**兼容性说明**:这是对外可见的响应形状变化(嵌套对象少一个字段、多一个字段),本仓库内没有其它消费方;若有本地脚本读这两个端点的 `description`,请改走详情接口。

- **交付下载的哈希口径修正(RR-04)。** `X-Content-Sha256` 以前放的是**存档里**的旧产物哈希:HTML 重放每次 nonce 不同,用户永远核对不上;apply/track 路径返回的是带说明的 JSON 封装,头却按 `content` 字段算。现在 `X-Content-Sha256` 永远是**本次响应真实字节**的 sha256(下载后自己算一遍就能对上),历史指纹另开响应头:`X-Artifact-Sha256`(当初产物的 sha256)、`X-Source-Content-Sha256`(apply/track 冻结内容的规范化 sha256)、`X-Delivery-Replay: true`(这份字节是重新生成的)。ADR-0002 相应补上"内容哈希/历史产物哈希/重放字节哈希不可混用"的口径。

- **桌面旧数据迁移的可信度修复(RR-06)。** ①迁移备份从"旧数据目录旁"(安装目录内,可能只读)改到**用户数据树下的 `backups/pre-migration-<时间戳>/`**(名字冲突自动加序号,绝不覆盖旧备份);②库文件不再整份复制,改用 SQLite 一致快照(`node:sqlite` 的 `VACUUM INTO`):`-wal` 里已提交但未 checkpoint 的事务会一并进入快照,边车**不再拷贝**(把陈旧 `-wal` 回放到新快照正是数据库损坏的成因),快照以 `PRAGMA quick_check` 复核;③`migrated.json` 标记还在但数据库不见时不再静默 —— 旧位置仍有数据则重新弹窗询问,两处都没有则**fail-closed**:弹恢复对话框(先去恢复备份 / 显式选择创建全新数据库),绝不自动在"曾经有数据"的目录里新建空库;④临时目录只凭名字不再可删,必须带本次迁移写入的来源标记,重试时也不会误删同名用户目录。

- **助手批准写工具的失败语义(RR-05)。** 已注册的写工具在服务层各自 `commit()`;handler 在提交之后失败(典型是 `refresh` 或结果构造)时,端点此前按 422「执行失败」+ `mark_failed` 回报——而数据其实已经落库,用户照提示重发就会二次写入。现在用会话级 `after_commit` 监听器判定"是否已经真的提交过":提交后失败 → `unknown` 终态 + 409「结果未知,本提案不会再次执行」,不再误报"未执行";提交前失败仍如实报 422「执行失败」。

- **`/api/stats` 查询合并:14 条 SQL → 4 条(万级最慢端点治理)。** 10 个计数合并为一条聚合语句(每个子查询自带完整 WHERE 口径),待确认台账与最近投递结果合并为一条 UNION ALL(Python 侧按 kind 拆分、显式重排序等价于原 SQL ORDER),最近岗位/简历各一条;响应 JSON 的**顶层**形状逐字节不变(14 键集合+顺序+类型钉住)——注意 `latest_jobs` **列表项**的形状在本批次后续的 RR-08 改动里已改为摘要模型(去掉 `description`、新增 `has_description`),顶层不变的说法仅指该条改动当时的范围。软删除过滤逐条核对保留(唯一例外 ApplyQueueItem 无 deleted_at,与合并前一致,专项测试钉住)。新增 SQL 条数预算测试(≤4 条断言,含空库/有数据两形态;启动自检回填后台线程会干扰计数,fixture 已掐掉)。

- **全局搜索接入 FTS5(trigram),迁移 0046(万级最后两个线性增长点之一)。** job/resume_record/referral/reminder/interview_experience/claim_record/material 七域建 external content 虚拟表 + 三触发器(增/改/删同步)+ 收尾 rebuild(复用 0044 全套模式:逐表跳过、trigram 不可用整体跳过、完整 downgrade,迁移内联 spec 与 services/search_fts.SEARCH_FTS_SPECS 钉住相等);skill(至多几十行的硬删子表)保留 LIKE(虚拟表维护成本是负优化)。检索侧 `app/services/search_fts.py`:可用性按 (Engine,表) 缓存、<3 字符回退 LIKE、引号转义防注入;`/api/search` 各域 FTS 可用走 MATCH(参数绑定)否则原 LIKE,字段清单与 FTS 列一一对应。**搜索排序统一加 id 决胜(created_at DESC, id DESC)**:批量导入行常共享同一秒 created_at,平局顺序此前依赖查询计划(未定义行为),现在任何计划下确定。两遍法对照测试:无索引库 LIKE 基准 vs 0046 后逐关键词响应 JSON 完全相等(13 关键词含中文/大小写/2 字回退/FTS5 操作符注入/无命中)+ 分页/软删除/scope 逐条一致。
- **`/api/jobs` 列表响应瘦身(2026-10-13 审计观察项)。** 列表与 `/page` 分页改用 `JobListOut`:不再逐行携带完整 JD 全文(此前 page_size=20 时响应约 430KB),新增 `has_description` 布尔字段(由 description 非空白派生);**详情端点(getJob)契约不变**,JD 全文仍从详情获取。前端岗位列表的"补齐详情"过滤改用 `has_description`(语义与原 `!description.trim()` 逐字等价);从列表打开"生成/编辑/手写"前先经 getJob 取全量;收藏页(FavoritesPage)同步切换 JobList。配套:前端类型/mock(含 measure_frontend_perf 的 fail-closed mock 契约)同步、JobsPage/JobTable/FavoritesPage 测试更新(JobsPage 18 例全绿)、后端 test_api_jobs 契约改钉。

- **desktop 依赖升级:electron ^33.0.0 → ^44.7.0、electron-builder ^25.1.8 → ^26.17.0(PR-06 豁免清偿)。** 仅变更 package.json 与锁文件,由 CI desktop-shell 打包门禁 + desktop-shell-smoke 真启动作业全链路验证。`npm audit` 从 19 条(4 moderate / 14 high / 1 critical)降到 8 条(全 moderate):全部 critical/high(含 critical 的 tar)随升级清零;剩余 8 条全部源自 sprintf-js 根公告 GHSA-hp3w-g68c-fv3c(上游未发布修复),按 moderate 登记豁免至 2027-01-31(audit-baseline.json 已重建,供应链文档补升级记录)。audit-gate 测试同步:基线规模断言由硬编码 19 改为结构性校验(条目完整+到期字段),避免后续合法清偿再炸测试。


- **datasets / resume_generator / resume_risk 三文件按豁免预案纯搬迁拆分(PR-18 收尾)。** ①`backend/app/services/datasets.py`(549 行 → 450 行)把列表/重命名/删除/导出(`_describe`/`list_datasets`/`rename_dataset`/`delete_dataset`/`export_all_datasets`/`export_dataset`)拆到 `datasets_admin.py`(151 行);导入链路(`import_dataset`)与切换链路(prepare/commit/compensate、`recover_startup_pointer`)留在原文件——测试在 `datasets` 模块命名空间上 monkeypatch(`_DRAIN_TIMEOUT_SECONDS`/`extract_database`/`restore_assets_from_manifest`/`run_database_migrations` 等),落点不动。②`resume_generator.py`(538 行 → 488 行)把提示词常量与篇幅引导(`MAX_JD_CHARS`/`MAX_PROFILE_CHARS`/`ENHANCEMENT_LEVEL_GUIDES`/`_ENHANCEMENT_DISABLED_GUIDE`/`build_layout_guide`)拆到 `resume_generator_prompts.py`(65 行);`PROMPTS_DIR`、用量汇总(`usage_records`)、预算分段接线与流式主循环留在原文件。③`resume_risk.py`(663 行 → 108 行)按职责拆两份:本地扫描规则(查重/敏感词/夸大/深挖/合规 + `scan_local`)→ `resume_risk_rules.py`(373 行),出站最小化与远端响应规范化(`_build_enhancement_messages`/`_outbound_resume_text`/`_normalize_enhancement_points` 等 PR-11 隐私关键路径)→ `resume_risk_outbound.py`(234 行),原文件保留扫描编排(`scan_resume_risks`)并作为门面 re-export,`__all__` 契约与消费方 import 路径不变。④`test_file_size_budget.py` 删除上述三条 PR-18 豁免(拆分预案执行完毕),其余豁免保持不变。全部为纯搬迁,行为零变化;出站隐私断言、降级矩阵、datasets 崩溃确定性等既有测试原样全绿。

- **tracker 服务层与投递记录面板按豁免预案纯搬迁拆分(PR-18 收尾)。** ①`backend/app/services/tracker.py`(563 行 → 296 行)按读写接缝拆出 `tracker_query.py`(查询/过滤/排序/漏斗统计/惰性迭代,207 行)与 `tracker_csv.py`(CSV 行构造/流式分块/JSON 导出,107 行);写路径(`create_track`/`update_track`/`delete_track`/`record_applied`/`_freeze_linked_resume`)留在 tracker.py——测试在 `tracker` 命名空间上 monkeypatch `freeze_delivery`/`record_status_change`,落点不能动。tracker.py 仍是对外门面:re-export 与 `__all__` 逐字不变,`api/tracker.py`、`assistant_tools/tracker_tools.py` 与全部测试的 import 无需改动。②`frontend/src/components/apply/ApplyRecordsPanel.tsx`(511 行 → 432 行)抽出两个纯展示子组件:`ApplyRecordsToolbar.tsx`(关键词/结果筛选区)与 `ApplyRecordDetailDrawer.tsx`(详情抽屉),状态、hooks 与明细请求代次守卫留在主组件,props 传参、JSX 逐字搬迁不改行为。③`test_file_size_budget.py` 删除 tracker.py 与 ApplyRecordsPanel.tsx 两条 PR-18 豁免(拆分预案执行完毕),其余豁免保持不变。

- **首页激活引导判定对齐后端事实(2026-10-13 生产审计 PR-19)。** 修复「第一份交付」进度面板两处"进度说谎":①「准备事实」此前只看台账/简历计数(claim_count>0 或 resume_count>0),只认真填写「我的资料」的用户永远看不到第一步完成——现在新增基于 `/api/profile` 真实返回字段的关键字段完整度判定(**姓名非空且电话/邮箱至少一项非空**,即 `isProfileFactsReady`;不编造接口没有的数据),关键字段齐了即算有了底子,CTA 提示也会点名缺什么;②「核实并交付」此前"有简历且无待核实"就算完成——**空台账(一条主张都没核实过)也能显示已完成**。现在完成条件加上 `claim_count > 0`(空台账不等于完成事实核实),并新增真实进度文案:台账为空显示"台账为空:还没有可核实的主张"(CTA 去台账录入)、部分核实显示"已核实 N/M 条主张"、主张全核实但没简历时 CTA 转向简历中心而不是投递台。③首页数据加载失败与"真的为空"可区分:stats 或 profile 加载失败时错误横幅带**重试按钮**(点按重新拉取),不再让面板沉默消失。改动收敛在 `useActivationProgress` 纯函数与首页数据装配,不动路由/页头/视觉系统;纯函数判定由 `useActivationProgress.test.ts`(13 例)、面板渲染由 `FirstDeliveryPanel.test.tsx`、首页装配由 `HomePage.test.tsx` 钉住(含"关键字段缺失→资料步骤不显示完成""空台账→核实步骤不显示完成"两条审计断言)。

- **异常落日志前强制脱敏 + 运营手册(2026-10-13 生产审计 PR-20)。** ①**日志脱敏**:`logger.exception` 会把整条 traceback(含 `raise ... from exc` 链)渲染进日志,而业务层把模型输出的 Pydantic `ValidationError` 链在 LLMError 之后——其错误消息带 `input_value=<模型原始返回>`,等于把模型返回的岗位描述/简历原文落盘。新增 `app/runtime/log_redact.py`:`redact_sensitive_text`(文本级清洗:`input_value` 载荷、base64 照片 data URL、`sk-` API Key、`Bearer` 凭证、password/token/secret/api_key 赋值键值对——键名保留、值统一打码)与 `log_exception_redacted`(`logger.exception` 的脱敏替代:traceback 先渲染成文本、清洗后再落日志,调用栈与异常类型保留)。接入最危险的 3 个调用点:岗位匹配分析(`api/job_match.py`)、岗位需求解读(`api/jobs.py`)、简历生成(`api/resumes/generate_routes.py`);`privacy.redact`(结构化 ResumeContent 遮罩)输入形态不同无法直接复用,占位符沿用同一惯例,不新增第二套语义。守卫测试 `backend/tests/test_log_redact.py`(6 例)用**真实 Pydantic ValidationError 与真实异常对象**驱动,断言渲染后日志不含简历原文标记/身份证号/照片 base64/API Key,且 traceback 结构仍在。②**运营手册** `docs/operations.md`(新):发布清单(构建→校验和→SBOM→冒烟→发布,逐环节如实标注自动化程度——SBOM 仅第三方许可与锁定清单、校验和手动计算、**签名未做**)、四级事故分级(数据不可读/隐私外发/功能不可用/性能劣化)与响应动作、诊断包最小化指引(明确排除密钥与完整简历)、备份恢复步骤(导出→校验→空目录恢复→行数/文件复核,对应 PR-15 manifest 流程)、离线/模型不可用手册(逐功能降级行为,与 PR-11 降级契约一致)、支持边界声明(本地单用户,loopback+CORS 是本地防线不是身份认证)、数据保留与删除说明(不可变快照含个人信息:删除原简历后备份快照/导出文件里的副本仍在、如何显式清除)、威胁模型备注(page_reader DNS rebinding 已知残留、更新校验依赖 checksum 非签名)。

- **性能测量工具重做,产出三档真实性能证据(2026-10-13 生产审计 PR-17)。** 修复"测量工具不能支撑万级数据结论"的问题:①`scripts/measure_api_perf.py` 的 fixture 此前用 `create_all` 建库(缺 Alembic/FTS 派生索引,知识库 FTS 收益根本测不出来),现在复用 `stress_seed.schema` 的同一份实现走**真实 Alembic head** 建库,并新增 `--stress-run` 直接测量 `seed_stress_data.py` 造数产物(只读、测后不删);测量前校验库内 `alembic_version`==head、`knowledge_fts` 存在、搜索的 EXPLAIN QUERY PLAN 真的命中 FTS,不满足直接拒绝出数。②测点清单对齐真实路由(OpenAPI 权威路由表 + 404 检测):旧全量接口(`/api/knowledge`、`/api/tracker` 全量、`/api/candidate-jobs` 全量)全部改为 `/page` 分页端点,补 `/api/tracker/stats` 与真实搜索端点(搜索词从被测库现取,保证命中);SLO 判定 fail-closed——任何端点 p95 超阈值或测量出错即标 FAILED 并**退出非零**,不再 exit 0 装成功;结果 JSON 记录 HEAD/规模/schema head/SQLite/FTS 能力/硬件/冷热/错误率/响应体/SQL 条数。③`scripts/measure_frontend_perf.mjs` 前端 mock 改为 **fail-closed**:补齐 `/api/tracker/stats`、`/api/reminders/upcoming`、应用壳 settings 等 mock;未匹配的 `/api/` 请求显式回 500 并计数(不再默认 EMPTY_PAGE 把错误 schema 伪装成成功);每次导航断言 mock 数据真实渲染进 DOM、pageerror 为零、未命中为零,全部通过才记录 LCP 等指标;mock 形状对齐后端 schema(Stats/TrackStatsOut/Page/ReminderUpcomingOut/settings),字段清单由 `scripts/tests/test-measure-frontend-mock.mjs`(node:test 10 项)钉住。④知识库检索 FTS/LIKE 语义差异显式化(`backend/tests/test_perf_knowledge_fts_semantics.py`,真实临时库+Alembic head):短词回退 LIKE、中文子串/大小写两路一致、索引插入即同步可搜、FTS 语法字符按字面量;**含 `%`/`_` 的 ≥3 字符查询两路行为不同**(FTS 按字面量、LIKE 的通配符会与用户输入合并),"语义完全一致"的准确边界由此钉在测试里。⑤**实测证据**(进程内 TestClient,`--seed 20261013` 三档造数 500/2000/10000,产物在 `runtime/perf-20261013/`,报告 `deliverables/ResumeForge-perf-evidence-20261013.md`——只呈现事实与阈值对照,不给总体达标结论):三档全部受测端点 p95 ≤ 300ms 口径;FTS 对 LIKE 的引擎级收益随规模放大(p50:500 档 ~11×、10000 档 ~24×);**真实浏览器端到端测量本轮未做**(沙箱派生进程限制,已降级为 mock 修复+断言单测并如实留档),TestClient 数字不能当真实用户延迟引用。⑥附带修复 `stress_seed/counts.py`:scale ≥ 2000 时进度条数派生值(2304)超过公司池容量(120×12=1440),造数直接 IndexError——现在按容量封顶,配套回归测试 `test_seed_stress_safety.py::test_counts_respect_track_pool_capacity`。

- **质量门禁扩面与行数预算豁免机制(2026-10-13 生产审计 PR-18)。** ①**行数预算豁免显式化并带到期日**:`backend/tests/test_file_size_budget.py` 引入 `Exemption(reason, expires)` 数据类——豁免必须带理由与到期日,新增守卫测试解析 `expires` 并对已过期豁免直接点名失败,杜绝豁免无声续期;本轮 5 个多 PR 叠加增长的真实红(`datasets.py`/`resume_generator.py`/`resume_risk.py`/`tracker.py`/`ApplyRecordsPanel.tsx`)按此登记为声明式豁免(到期 2026-11-15,届时再评估拆分或续期,不是永久免检),既有按触发条件类豁免统一到 2027-06-30 结构化兜底;`ProfilePage.test.tsx`(844 行)**选拆分不选豁免**:抽出 `frontend/src/test/profilePageHarness.tsx`(mock 装配/渲染/复位脚手架),测试拆为主文件(393 行,通用简历/资料分页/未保存防护/分区折叠/悬浮球卡/网申输入性能)与 `ProfilePage.webform.test.tsx`(423 行,网申资料 13 项),用例数 29 项不变全绿——vi.mock 的文件级作用域决定了 mock 声明必须留在测试文件内,共享脚手架只收渲染/复位逻辑。②**门禁扩面**:行数预算覆盖 `scripts/`(阈值 800 行,豁免 `seed_demo_data.py`/`measure_api_perf.py`)与 `desktop/`(阈值 500 行,排除 `node_modules`/`out`/`payload` 等产物目录,当前零豁免),配套两条守卫测试。③**Pyright 渐进扩面**:探测 `app/services/` 与 `app/api/` 各子目录错误数后,选 0 错误的 `app/services/data_backup` 加入 `backend/pyrightconfig.json` include(探测记录在注释里:apply 8 错、browser 17 错、api/ 65 错,留待后续按最小子集逐个清零),全量 pyright 保持 0 errors,不做 mass type: ignore。④**跨模块缺口补测 3 项真实缺口**(盘点后确认 dataset-scope 流式切换/租约、proposal TTL 已有真实 TestClient 层与授权层覆盖,不重复堆覆盖率):备份导入中途数据库写入失败时 `.partial` 暂存文件清理、附件清单恢复硬故障(OSError)不拖垮整个导入(进 `asset_issues` 显式上报、数据集照常导入)、数据集切换持锁期间批准提案返回 409 且提案保持 pending、释放后可重试成功——`test_backup_asset_manifest.py` +2、`test_assistant_proposals_endpoint.py` +1,全部真实 TestClient 层。

- **桌面数据目录策略定型(ADR-0004,2026-10-13 生产审计 PR-05)。** 此前桌面打包形态下数据库/附件/导出/备份默认写在安装目录内的 `resources/payload/backend/data`——安装目录只读或更新替换时存在数据丢失/不可写风险。现在:`backend/app/config.py` 的 `DATA_DIR` 支持 `RF_DATA_DIR` 环境变量覆盖(未设置时默认值与解析顺序逐字节不变,控制台/开发流程零影响);桌面壳在打包模式下启动后端时注入 `RF_DATA_DIR=%APPDATA%\ResumeForge\data`,用户数据从此落用户目录(数据库 URL 随之推导),绝不放 payload。五类路径(程序目录/用户数据/日志/运行时/缓存)的固定策略与旧数据迁移方案(显式确认+备份+校验,留待后续版本实现)记录在 `docs/adr/ADR-0004-desktop-data-directory-policy.md`;本轮**不做**自动数据搬迁。配套测试:`desktop/scripts/test-backend-manager.js`(`buildBackendEnv` 与 spawn env 断言)。
- **bump_version 覆盖桌面版本源(2026-10-13 生产审计 PR-05)。** `scripts/bump_version.py` 此前只维护前端/后端/README/CHANGELOG 的版本号,`desktop/package.json` 与其 lockfile 漏网——桌面壳的 `app.getVersion()`(更新器比较版本号的基准)会停在旧版本。现在发版脚本同步写入 desktop/package.json 与 desktop/package-lock.json 根版本,四处版本不一致直接报错拒绝;新增自检测试 `desktop/scripts/test-bump-version.js`(最小化临时 git 仓库 + `RF_BUMP_REPO_ROOT` 跑真实脚本,断言 desktop 版本与其余位置同步;脚本不支持该环境变量时测试拒绝运行,绝不碰真实仓库)。
- **桌面依赖供应链基线与 CI 审计门禁(2026-10-13 生产审计 PR-06)。** desktop `npm audit` 报 19 条受影响包(4 moderate / 14 high / 1 critical——critical 为 tar);每条修复路径都是 semver-major 升级(electron ^33→44、electron-builder ^25→26.15.3),不存在范围内安全升级,本轮全部登记为带到期日(2027-01-31)的豁免而不升级(electron 是桌面运行内核,builder 26 改变打包行为,均需 CI 全链路复验后切换)。逐条分类/公告链接/决定见 `docs/desktop-supply-chain-audit.md`,机读基线在 `desktop/scripts/audit-baseline.json`。新增 `desktop/scripts/check-npm-audit.js` 门禁:只读运行 `npm audit --json` 与基线比对,出现基线外新 critical/high、已登记包的新公告 id、severity 升级或豁免到期即失败;CI 的 `desktop-shell` 作业新增该门禁步骤与 `node --test scripts/` 单测步骤。注意 desktop 不适用 `--omit=dev`:electron 在 devDependencies 里也是运行内核。配套测试 `desktop/scripts/test-audit-gate.js`。
- **备份附件改为按库引用收集并逐文件校验(PR-15)。** 修复两处会丢数据/泄隐私的问题:①此前导出把数据库同目录的 `user_files/`、`referral_images/` **整目录**装包——多份导入数据集共用同一个附件目录,导出数据集 A 会把**数据集 B** 的附件一起带走(跨数据集隐私泄漏);②"导出全部数据集"时随包数据集只带 `.db`,**导入库位置的附件整体缺失**。现在每份数据库的附件按**库内容收集的引用**(`user_file.disk_name` 与 `referral.note_images`)装包,包内清单(`asset_manifest` 段,自带 `manifest_version`)逐文件记录大小与 SHA-256;随包数据集的附件在 `datasets/<id>/` 前缀下命名空间隔离。恢复时按清单落盘并**实际读回**逐文件复核、按清单复核数据库关键行数——缺文件、内容不符、行数不符都会列入导入结果的 `asset_issues` 显式上报,绝不静默成功。旧备份包(无清单)照常导入;包格式号不升级(老版本导入新包仍能恢复数据库,附件按其既有能力处理)。配套:自动备份轮转淘汰的快照改为移入 `auto-backups/<数据集>/.trash/`(保留最近 5 份,可恢复),与手动备份"删除 = 移入回收"统一,不再直接删除曾经有效的好快照(校验失败的无效候选仍直接删除——它从未有效,无可恢复内容);备份调度线程改为**真正持租约**运行(`acquire_lease("auto-backup")`),数据集切换期间自动跳过、备份期间切换会等它归还,双向互斥;前端备份状态卡的"受保护"口径注明**仅数据库快照,不含附件文件**。设计记录见 `docs/adr/ADR-0003-backup-asset-manifest-and-storage-scope.md`,配套测试 `backend/tests/test_backup_asset_manifest.py`。
- **存储统计统一路径作用域,分项不重叠、总计可解释(PR-16)。** 修复 `/api/stats/storage` 的三类口径问题:①统计根此前取"当前激活库的父目录"——激活导入库时变成 `data/datasets/` 子目录,应用级目录(投递/网申浏览器用户目录、采集样例、日志等)跟着漂移,且"数据集"分项与库/附件分项**双重计数**(总计≠分项之和);②主库文件名在数据目录分支下被硬编码为 `resume_forge.db`,自定义库名被统计成默认库;③每次统计全目录走两遍、无预算无缓存。现在:统计根固定为**配置的数据目录**,整棵目录单遍扫描、按路径归类,每个文件只进一个分项(`dataset` / `shared` / `backup` / `application` 四类作用域逐项标注,不属于任何分项的进"未分类"兜底项),**总计恒等于分项之和**;数据库文件按当前连接的真实库文件(含 WAL 边车)单独统计;扫描带预算(20 万文件/24 层深度)、权限失败计入响应的 `warnings` 显式可见、不跟随符号链接逃逸、继续只 stat 不读任何文件内容;结果带 60 秒短缓存,避免设置页反复打开就反复全量扫描。

- **压力造数脚本安全重做(`scripts/seed_stress_data.py`,PR-01)。** 修复此前"一 import 就重建(清空)数据库"的严重问题:旧脚本在模块级就 drop_all 建表,连 `--help`、传错参数都会先清空目标库。现在 ①**导入与 `--help` 零副作用**——CLI 解析在最前,建目录/建引擎/写文件全部移入 `main()` 装配阶段(逻辑拆分到 `scripts/stress_seed/` 包:路径安全、规模计数、schema 初始化、各域生成器、只读校验等窄职责模块);②**默认输出到全新 run-id 目录**(`runtime/stress/stress-<时间戳>-<随机>/`,含 `stress.db`、`manifest.json`、`import_report.md`、`user_files/`),目标已存在一律拒绝退出,不提供覆盖旧库的快捷方式,也绝不自动清理旧压力产物;③**路径安全不再依赖文件名含 "stress" 子串**——输出必须严格落在沙箱根之内(默认 `runtime/stress`,可用 `--sandbox-root` 指定),规范化解析后校验包含关系、拒绝 symlink/junction 逃逸,并断言实际引擎 URL 与声明输出一致;④**只读检查与造数彻底分离**——`--check-only <run目录或db>` 以 SQLite 只读模式检查已有库(表数、行数、manifest、user_files 物理文件与 DB 引用一致性),绝不造数、绝不 drop_all;`--api-check`(可选)在造数完成后跑一遍全部只读接口自检;⑤**schema 用真实 Alembic head(`upgrade head`)初始化**,与生产 schema(含 0044 FTS)完全一致,不再用 create_all 掩盖;⑥**规模可配置**(`--scale 500/2000/10000`,默认 500)、随机种子可指定(`--seed`),manifest 记录种子、规模、schema head、实际引擎路径、数据库与用户文件哈希、运行时刻与 NOW 锚点。配套测试 `backend/tests/test_seed_stress_safety.py` 钉住全部安全性质。`scripts/measure_api_perf.py` 的 `--db` 同步补上防护:目标目录存在数据集活动指针(`active.json`)时拒绝执行,并断言真实引擎 URL 与声明输出一致。
- **万级数据下的检索与导出提速(批次 C)。** ①**知识库关键词检索接入 SQLite FTS5**(新增迁移 `0044_search_fts5`):按"三字滑窗"(trigram)对标题/正文建全文索引(含同步触发器),≥3 字符的关键词检索从全表 LIKE 扫描转为索引命中,语义与原来一致(子串匹配、ASCII 大小写折叠都相同);短于 3 字符的关键词自动回退原 LIKE 路径,SQLite 太旧(不支持 FTS5 trigram)的库在迁移时整体跳过、检索自动走原路径——历史库照常可用,产物不受影响。②**求职进度导出 CSV 改为流式生成**:不再把全部记录一次性拉进内存拼成大字符串,逐行产出、产物与原来字节级一致(BOM、表头、行序、行尾都不变),万级记录导出不再占几十 MB 内存;JSON 导出保持原实现。③**简历中心批量选择补齐「全选全部」的后端支撑**:新增 `GET /api/resumes/ids`(与简历列表同一份筛选器,不分页返回全部 id,模式与 `GET /api/jobs/ids` 相同),前端跨页全选由此取 id。④备份导入校验放行 FTS5 派生索引表(`knowledge_fts*`,可由 rebuild 重建的检索辅助结构),旧备份照常导入、新备份不误报"来自更新版本"。
- **大数据量下的交互补齐(批次 C,前端侧)。** ①**大文件上传有了进度条**:上传通道改为 XHR 进度上报,资料识别(粘贴文本弹窗的附件)、内推备注图、头像三处在上传期间显示实时百分比进度条(到 100% 后的等待是服务端处理时间),不再只有按钮干转圈;上传的取消、超时与错误口径和普通请求完全一致(超时与主动取消在界面上区分开)。②**简历中心批量选择补上「全选全部简历(共 N 个)」**:按当前筛选跨页勾选全部简历,批量删除一次完成(单次上限 500,超出如实提示先用筛选缩小范围)。③**上传类请求的预算档位纠偏**:两个 CSV 导入预览从 15 秒默认档挪到 10 分钟上传档,通知材料识别补齐 90 秒识别档(与岗位、资料识别同口径)——大附件上传不再动辄误报"请求超时"。

- **大数据量下的体验补齐(批次 B,共 7 项)。** ①知识库、资料箱、事实台账三个列表改为**服务端分页**(知识库/资料箱一页 12 张、台账一页 20 条,关键词与筛选都下发服务端、翻页即取):千级条目不再一次全量搬运;②事实台账的汇总数字(总数/已确认/待确认/各分类计数)改由数据库直接计数,不再把全部记录拉回内存数一遍;③**投递记录的批次明细改为按需加载**:列表只带"本批 N 条记录",展开某个批次才拉它的明细(批内同样服务端分页,一页 20),海投几百条后打开投递记录不再整屏硬算;④**求职助手长对话改为「加载更早」**:打开会话先取最新 50 条消息(顶部如实显示总条数),往上翻点「加载更早」继续取,几百条消息的会话秒开;⑤面经列表不再携带全文(正文只带 200 字摘要与问题条数,全文与完整问题清单走详情);⑥**六个页面补齐批量选择**:知识库、资料箱、事实台账、面经、内推、投递记录(批内明细)都支持右键进入批量选择、全选本页、批量删除(单次上限 500,任一不存在则整批不删,软删进回收站;后端配套 6 个 `batch-delete` 端点);⑦统计看板与内推统计的聚合改为只取所需列/一次批量取回,万级进度与事件下保持轻快。
- **大数据量下的性能与正确性修复(批次 A,共 7 项)。** ①全站"第 1 页 100 条当全量"的下拉改为**远程搜索**(新增 `useRemoteSearchSelect` 钩子):进度表单、日历提醒绑定目标、内推关联岗位/漏斗、模拟面试关联岗位、题库/面经/复盘的关联岗位与简历、深挖关联岗位、队列条目简历(只列该岗位关联过的)、求职助手的上下文岗位/简历——第 101 条之后的历史记录都能通过关键词搜到,编辑时已选值按 id 回查保显示;②求职助手会话列表改为**服务端搜索**:侧边栏新增搜索框(防抖 300ms,按标题/首条消息过滤,后端上限提至 500),不再硬编码"只取 100 条"把更早的会话藏掉;③知识库旧列表端点改回**摘要行**(正文只带 ≤200 字摘要),不再整库搬运 Markdown 长文;④求职进度页的漏斗/统计改走专用 `GET /api/tracker/stats`,页面展示统计不再为几个数字整表拉回全部进度;⑤内推转化率统计消除 N+1 查询(此前每条内推单独回查一次关联进度,现在一次批量取回);⑥简历批量删除改为**一次请求删完**(`POST /api/resumes/batch-delete`,单次上限 500、任一不存在则整批不删),不再前端逐条 N 连发;⑦投递队列列表封顶 500 条(按入队顺序取最新的)并如实标注——队列更长时界面提示"已显示前 N 条,请用筛选缩小范围或先投递消化",不再假装队列只有 500 条(配套:队列接口从 `apply.py` 拆出独立路由文件,响应改为 `{items, total, truncated}`)。
- **AI 调用全面提速(实测识别最快快 88%、面试首字 0.3s)。** ①岗位识别、资料识别、岗位分析、适配度、深挖、面试官轮次等**结构化输出类**调用自动跳过思考模式(严格 JSON 不需要思考,DeepSeek flash 实测关思考快 88%;对话与简历生成仍按你的思考设置);②求职助手的**写类工具按需下发**:闲聊轮只带查询类工具,消息命中写入意图(投递/修改/删除/生成…)或放宽模式才全量下发——每轮少传数千 token,首字更快;③模拟面试官回答改**流式渐进显示**:问题像打字一样逐字出现(实测首字 0.3s,总时长 1.3s),点评随后就绪,收尾/报告逻辑不变;④岗位/资料识别请求的超时预算 15s → 90s(大文本不再误报"请求超时");⑤设置页思考模式的说明同步更新(结构化任务自动跳过,无需为识别场景关闭思考)。
- **「文件副本」页签更名「我的文件」,并真正成为"数据集里的用户文件清单"。** 资料箱附件、简历照片、岗位备注图、备选截图、助手附件——用户上传内容的落盘副本集中在这里,改个一眼能懂的名字。此前这些文件**不随数据集导出/导入**(换机器就丢);现在备份包会附带 `user_files/` 目录,恢复时原样落回,切换数据集与升级也一直保留。
- **批量适配度分析整条交互重做,超时问题根治。** ①此前多岗位分析走同步请求,岗位一多必然撞前端超时预算报「请求超时,请重试」——现在**一律走后台任务**:面板里显示逐岗位进度(正在分析哪个岗位一目了然),关掉窗口/切到其他页面任务继续,完成时全局通知,回到面板自动接上进度;②点击「AI 分析适配度」不再直接开跑,先出**设置视图**(显示将分析的岗位数、是否覆盖已有结论的选项),点 **开始分析** 才启动;③进行中可随时 **取消本次分析**(取消前完成的结果仍已保存);④**分析记录**的「查看结果」改为在**独立结果卡片**中回看,不再把整个面板切到"本次结果"页签。
- **模拟面试的轮数选择合并为一个下拉框。** 原「自动轮数」开关取消,轮数下拉里直接选 **自动(AI 判断收尾)** 或具体轮数(3–12)——一个控件说完这件事。
- **所有 CSV 导入处新增「下载空模板」。** 岗位广场与备选岗位的导入弹窗都能一键拿到带表头的空模板(`岗位导入模板.csv` / `备选岗位导入模板.csv`),照着列名填就不会因为表头对不上而导入失败;备选岗位的导入弹窗文案也明确标出"导入的是备选岗位",与岗位广场的导入区分开。
- **批量选择支持「全选全部岗位(跨页)」。** 岗位广场进入选择模式后,工具条新增 **全选全部岗位(共 N 个)**:按当前筛选把所有页的岗位一键勾选,导出 CSV、批量设置状态、加入投递队列、批量删除对全部勾选生效(批量操作单次上限 500,超出会提示先用筛选缩小范围,不悄悄截断)。后端配套新增 `GET /api/jobs/ids`(与列表同一份筛选器,不分页返回全部 id)。
- **导出 CSV 更表格友好。** 列名与列序改成**手动添加岗位表单的字段名**(岗位名称 / 公司名称 / 工作地点 / 薪资范围 / 岗位类型 / 状态 / 投递链接 / 发布时间 / 岗位描述 / 任职要求 / 其他招聘信息 / 来源 / 备注 / 收藏),表格与表单之间对字段不用再翻译;文件名自带导出时间(`岗位列表-20261012-103000.csv`),多次导出不互相覆盖;**字段内换行折叠成空格**——此前 JD 里的换行会把 Excel 里一条岗位撑成十几行、整个表格看着"散架",现在每条岗位恰好一行。旧列名的文件(标题 / 公司 / 地点 / 薪资、职位描述 / 职位类型)导入时仍然全部识别。
- **全站口径统一:「职位」一律改称「岗位」。** 岗位表单(岗位名称/岗位描述)、岗位表格列头、搜索占位、CSV 表头(`职位类型`→`岗位类型`、`职位描述`→`岗位描述`,导入时旧表头文件仍可原样导入)、采集补详情与站点健康提示等用户可见文案全部统一;网申表单里对应招聘网站自身字段的「职位」、外部网站的原文标题保持原样(那是别人的页面用词,改了会影响解析与填表)。
- **岗位详情的「信息来源」更名「录入方式」,且「来源」没填时不再占一行。** 两个字段语义不同——「录入方式」是系统记录的这条岗位怎么进来的(手动填写/粘贴识别/采集等),「来源」是用户可编辑的业务来源(内推/招聘会/猎头等);原来并排两行、来源还是个"-",看着像重复。现在各自表意,空来源不渲染。
- **备选岗位卡片更透气、操作更清爽。** 卡片之间加了 16px 间距(此前一张贴一张);去掉了每张卡片上的「···」菜单(编辑/删除收进卡片右键菜单,与「批量选择」并列;待处理卡片的「导入到岗位」按钮保留;详情点卡片即可);「全选本页」工具条与上方搜索框的间距也拉开了。

### Fixed

- **备份恢复改为「先验后发布」:不再先覆盖本机附件再校验(RR-03,P0 数据安全)。** 此前 `restore_assets_from_manifest` 直接以 `open("wb")` 把包内字节写进共享的 `datasets/user_files`、`referral_images`,**写完才**核对 sha256;`import_dataset` 更是在附件校验**之前**就发布了主库与随包数据集,附件问题最终只是 200 响应里的 `asset_issues`。后果:包内同名成员被改过时,本机已有附件会被覆写成损坏内容;自洽篡改包(连清单一起改)甚至零 issue 静默替换。现在:①附件预检与库校验一起前置到"一个字节都没写"的阶段——成员存在性、大小、sha256、文件名合法性逐项核对,并与磁盘现状做**冲突检测**(同名同内容算已满足,同名不同内容**绝不覆盖**、如实报冲突),包内跨段同名但内容不同同样拦下;②通过后先原子发布库、再逐文件 `os.replace` 原子发布附件;③发布链路任何失败都会**收回本次新建的数据集文件**(只删自己刚创建的那几份,用户既有文件一律不动);④收尾用 `verify_restored_assets` 以磁盘现状对照清单独立复核。完整性问题默认让整次导入在写盘前失败并给出原因;用户显式选择"跳过有问题的附件继续导入"(`allow_partial_assets`,界面弹窗确认)时才走 DB-only 路径,问题仍逐条上报。
- **导出侧如实标记"这份备份不完整"。** 资产清单新增 `<category>_mismatched`(`user_file` 登记的 sha256 与磁盘实际字节不符);导出响应头带 `X-Backup-Incomplete` / `X-Backup-Asset-Warnings`,前端据此把提示从"已导出"改为"已导出,但这份备份不完整(附件缺失/与登记哈希不符)";导入这类包时同样在结果里转述(`backup_asset_warnings`),不再让用户以为恢复出了一套完整附件。
- **桌面退出不再对未经核验的旧 PID 强制停止(RR-02,P0)。** 启动器把后端 PID 写进 `runtime/backend.json`(macOS 写 `runtime/backend.macos.pid`),桌面壳此前只读那个数字就直接 `taskkill /T /F`(Windows)或 `process.kill`(macOS),不校验启动时间、命令行或可执行文件;`main.js` 还会在停止超时/失败后照常启动更新安装器。现在新增 `desktop/lib/process-identity.js`:接管前用启动器同一套证据复核(进程在、命令行匹配 `uvicorn app.main:app`、启动时刻与记录一致 ±2s;macOS 用 `ps -o lstart=` 原始字符串签名),**任何一项无法确证就不停进程**并报可见错误;`taskkill` 退出码不再当作成功(以进程表为准,杀完要确认真的消失);自建子进程也按 spawn 时抓取的启动指纹复核(防 PID 复用);后端停止未被确认时**阻止更新安装**并弹错误说明;顺带修掉 macOS 端永远读不到记录(只认 `backend.json`、macOS 写的是 `backend.macos.pid`)导致后端起不来也停不掉的问题;启动超时路径现在会清理自己刚拉起的进程树。桌面端新增身份核验测试(假进程表 + 受控注入),全量 node --test 69 例。
- **HTTP 查询串里的 FTS/LIKE 特殊字符**(见上方"搜索关键词语义统一")。

- **取消生成时主动中止挂起的模型调用(消除 ~5s 的租约归还延迟)。** 模型流挂在 socket read 上(对端不发数据)时,`_stop` 信号只在工作线程的 chunk 间隙检查点轮询,永远轮询不到挂起的 await——cancel 的 join(2s) 必超时,租约要等 httpx read timeout(~5s)线程退出才归还,此窗口内切换数据集会撞 409。现在 `resume_generate_runner` 改用显式事件循环管理(`_drive_event_loop` 登记 loop/主 task 句柄),cancel()/shutdown() 经 `call_soon_threadsafe` 跨线程直接取消主 task,线程亚秒级退出、租约即时归还;join(2s) 保留作兜底(线程卡死在不可中断的 C 扩展里时仍超时写终态)。取消引发的 CancelledError 沿既有异常分类映射为"用户取消",不误报内部错误;"结果未知不自动重放"纪律不变。配套测试:挂死型 Provider(`await asyncio.sleep(60)`,区别于逐毫秒轮询的 EndlessProvider)钉住"cancel 后 1s 内租约已归还",另有跨线程取消机制的单测。
- **修复实时填表"浏览器已关闭"测试在 CI 上的偶发失败(等待目标与状态写入顺序竞态)。** `LiveSession`/`MultiLiveSession` 的自动结束路径里,`stop()` 先置 `_thread=None`(`is_running` 立即变 False)、最后才写 `state["running"]=False`;测试只等 `is_running` 就断言状态,会撞进两步之间的中间态(CI run7 的 `assert True is False`)。两个用例的等待条件补上 `state["running"]` 翻转(stop 的最后一步),等到即全部落定;期望成功的等待预算(10s 安全网)与期望失败的小预算保持分档不变。
- **投递运行器停止先归还租约再公开终态 + CDP client 的 finally 全覆盖(与简历生成同款竞态的 apply 侧清偿)。** ①`stop()` 此前由 API 线程立即写 stopped 终态,而 apply-task 租约要等工作线程退出(`_run_releasing_lease` 的 finally)才归还——窗口内的数据集切换会在 `_wait_for_idle` 排空窗撞 409,stop 后立刻开始下一批也会撞"已有任务正在进行中"。现在 stop 先 join 工作线程(≤2s)再写终态;线程卡死在单次 CDP 调用上时超时照常写终态、排空等待如实 409 可重试。②`run_apply`/`run_collect` 在拿到 client 之后、原 try/finally 之前还有装配段(适配器快照、样例录制器目录创建,后者走文件系统),这段抛异常时任务会兜底置 failed 但 CDP 连接漏关——现在 try 边界上移,client 的 finally 全覆盖。回归测试 `test_stop_returns_after_lease_released`(钉"stop 返回即租约已归还")与两条装配故障注入钉住。
- **数据集切换排空预算 3s → 10s(消除连续切换的偶发 409)。** 激活会唤醒备份调度,新数据集的首份自动备份在调度线程持租约期间创建,慢磁盘上可超过原 3s 排空窗——连续切换(如 A→B→A)偶发 409「有正在进行的任务(auto-backup)」。等待有界延长到 10s 好过立即拒绝;真正卡死的租约超时后仍按原语义拒绝并点名 owner。
- **取消生成先归还租约再公开终态(数据集切换 409 竞态修复,e2e recovery 真实翻车路径)。** 取消由 API 线程立即写 cancelled 终态,而 runner 线程要退出(finally)才归还 resume-generate 租约——两者之间的窗口内切换数据集会撞上排空 409。修复:cancel 先 join 工作线程(≤2s,与取消可见收尾预算一致)再写终态;线程卡死时超时照常写终态、排空等待如实 409 可重试。回归测试 test_cancel_returns_after_lease_released 钉住"取消返回即租约已归还"。
- **求职进度更新改为原子事务 + 全局日志异常脱敏兜底(PR-10/PR-20 遗留对称项)。** ①`update_track` 此前仍分三段 commit(字段保存 → 状态事件 → 投递快照),中途失败会留下"已更新字段但缺事件/快照"的半成品。现在与 `create_track` 同一模式合并为**单事务**:先 flush 把字段改动压进事务缓冲,事件与快照任一步失败整体 rollback,最后一个 commit 一次性发布;对外 API 与返回值契约不变。配套测试 `tests/test_tracker_atomic.py` 对事件插入/快照冻结/最终 commit 三个故障点分别注入,断言无残留;盘点确认其余进度写路径无同类问题(`delete_track` 单次 commit、`apply_merges` 循环 flush 后单次 commit、`record_applied` 不自行 commit 沿用调用方事务)。②**全局日志异常脱敏**:PR-20 的 `log_exception_redacted` 只接入了 3 个最危险调用点,全仓约 30 处 `logger.exception`(drill/resume_writing/apply task_runner 等)的 traceback 仍可能携带 Pydantic `input_value`(模型原始返回=岗位描述/简历原文)原样落盘。现在 `app/runtime/log_redact.py` 新增 handler 级 `RedactExceptionFilter`,在 `application.py` 的日志装配处挂到**全部 handler**:对带 `exc_info`/`exc_text` 的 LogRecord 在格式化之前把异常文本经 `redact_sensitive_text` 清洗进 `exc_text`(标准 Formatter 与 JsonLogFormatter 都优先使用 `exc_text`,不重渲染原文;`exc_info` 保留不清,兼容 caplog 等读取方);同一条 record 流经多个 handler 只清洗一次;脱敏器自身抛错时丢弃异常文本并落「日志脱敏失败」占位,绝不因脱敏打断业务日志。既有 3 处显式调用保持不变(双保险)。配套测试 `test_log_redact.py` 扩展:handler 级清洗、单次清洗、预渲染 `exc_text` 清洗、脱敏故障兜底,以及 drill / profile / apply task_runner 三个真实调用点的端到端冒烟(敏感原文不落盘、业务行为不变)。
- **CSV 公式注入防护、解析健壮性与行号口径(2026-10-13 生产审计 PR-12)。** ①**导出公式注入防护**:以 `=`、`+`、`-`、`@`、制表符等开头的单元格会被 Excel 当公式执行(如 `=HYPERLINK(...)`),现在所有 CSV 导出(岗位、备选岗位、求职进度)统一经共享编码 `csv_io.safe_csv_cell` 加前导单引号按文本显示——负数与日期(`-5`、`2026-10-13`)虽以 `-` 开头但属用户预期字面量,不做转义;控制字符剔除。编码只实现一份,tracker 流式导出与 `records_to_csv_text` 同源(改一处两处生效)。②**解析不再"一条坏行毁整个导入"**:字段大小上限从 131072 放宽到与 2MB 文件上限一致(此前 14 万字符的正常字段会以 `_csv.Error: field larger than field limit` 崩掉整次导入),且 `_csv.Error` 转成「第 N 行有单元格超过上限/引号不闭合」类可读的逐行错误,坏行只影响自己那一行;③**行号口径修正为物理起始行**:带引号内换行的记录此前按数据序号报行号(报告 [2,3]、实际物理起始行 [2,4]),现在 preview 与 commit 共用 `reader.line_num` 推导的物理行号,与 Excel 状态栏一致;④**重复表头报错**:两列映射到同一字段时此前静默丢弃后一列,现在直接报错说明哪一列重复;⑤备选岗位 CSV 的说明文档明确其为「精简交换格式」(地点/薪资等列不在列集内、换行折叠),不是完整无损备份。
- **求职进度的统计与导出筛选一致性(2026-10-13 生产审计 PR-13)。** ①**mutation 后统计同步刷新**:新增/导入/删除进度后此前只重拉列表,漏斗数字停在旧值直到筛选变化——现在列表与统计一起失效刷新,且统计请求有独立的加载/失败状态(失败如实提示,不拿 0 冒充);②**岗位深链 `job_id` 贯穿**:`GET /api/tracker/stats` 与 `/export`(CSV/JSON)新增可选 `job_id` 参数(过滤逻辑与列表同用一份 `_filtered_query`),前端深链时列表、漏斗与导出三处同口径——此前从岗位详情进入时列表是一家公司的进度、漏斗和下载却是全部岗位。
- **分页末页困死与异步竞态(2026-10-13 生产审计 PR-14)。** ①**知识库删除后页码校正**:删除最后一页最后一条时此前停在已不存在的页码上、条目与分页一起消失,现在按 total 校正回最后一个有数据的页;批量删除失败时保留选择不退出多选,可直接重试(投递记录批内批量删除同口径);②**投递记录明细的代次守卫**:展开批次的明细请求此前直接回写状态,筛选变化后迟到的旧响应会把旧筛选/旧页的数据写进新视图——现在数据集或筛选一旦变化即作废进行中的请求,响应回来时校验代次匹配才落状态;③**「全选全部」的筛选竞态**:岗位广场与简历中心的 `useSelectAllJobs`/`useSelectAllResumes` 拉全部 id 期间切换筛选,旧筛选的 id 集合不再装进新视图(代次守卫 + loading 立即归位)。
- **可信交付账本:投递快照的目的地、同源存档与独立可达(2026-10-13 生产审计 PR-09)。** ①**目的地快照来自实际投递目标**:快照上的公司/岗位此前从原简历反推——通用简历(无公司)投给某公司时,快照目的地是空值或"Generic role"。现在 apply 路径取投递台条目的公司/岗位、track 路径取进度记录本身填写的目标,不从简历反推;**历史已存数据不改写**(旧行目的地为空时界面如实标注「目的地未记录」,不编造)。②**补投也冻结快照**:已有进度后来补上简历关联(表单 PUT 或投递台 `record_applied` 补 `resume_id`)同样是真实发生的投递动作,现在同样落一份 `source="apply"` 快照。③**导出存档与下载产物同源 + 实际参数**:脱敏导出存的是脱敏后的内容(此前存档保留原姓名、下载却是 `***`);存档新增 `source_content_sha256`(导出前源内容哈希)与 `artifact_sha256`(导出产物字节哈希)双指纹拆分,`render_context`/新增 `request_params` 记录**实际生效**的请求参数(修复"请求 page_limit=2、存档却是 1"的错位),凭存档可重放导出。④**独立存档访问**:新增 `GET /api/resumes/deliveries/{id}`(详情)、`GET /api/resumes/deliveries/{id}/download`(export 快照按存档重放导出;apply/track 快照下载冻结的内容 JSON;旧快照缺参数返回 409「不可还原」,不假装能重放)、`GET /api/tracker/{id}/deliveries`(从进度进入对应快照)——原简历删除(外键 SET NULL)后证据仍可达,旧入口 `/resumes/{id}/deliveries` 口径不变。⑤**幂等键**:快照新增 `idempotency_key` 列代表"哪一次真实投递动作"——投递台条目用 `apply_task_item:{id}`(同一任务重放不重复记账),导出用 `export:{简历id}:{产物哈希}`(重复点击同一份导出只留一份存档,改内容后重导出是新存档);真实第二次投递(不同条目)不被 company+title 合并吞掉。设计决策见 `docs/adr/ADR-0002-trust-delivery-ledger.md`。⑥`GET /export?record_delivery=true` 标注 deprecated:行为原样保留(最小破坏兼容),「导出并记录交付」统一走 `POST /export`(请求体 `record_delivery`)。⑦前端 `ResumeDeliveryInfo` 三态语义区分(「已投递给」仅 apply;进度关联注明"非投递台发送";导出存档注明"不代表已实际发送"),请求失败显示错误态 + 重试而不是误报「暂无投递记录」。数据库迁移 `0045_delivery_ledger` 只加四列(全可空,旧备份导入不受影响)。
- **求职进度创建改为原子事务(2026-10-13 生产审计 PR-10)。** `create_track` 此前分三次 commit(进度 → 首个状态事件 → 投递快照),中途任一步失败会留下"有进度没事件/没快照"的半成品,用户重试还会撞 company+title 唯一约束。现在三段写入合并为**单事务**:先 flush 拿 id,最后一个 commit 一次性发布;事件插入、快照冻结、最终提交任一步异常都整体 rollback,数据库无业务半成品,失败沿调用链返回可解释的错误响应,去障后重试直接成功。配套测试 `tests/test_tracker_atomic.py` 对三个故障点分别注入(快照冻结/事件写入/最终 commit),断言无残留。
- **AI 调用总预算硬 deadline 与多段用量汇总(2026-10-13 生产审计 PR-07/PR-08)。** ①**整请求硬墙钟**:简历生成与求职助手的模型调用此前只有"两段数据之间"的 read timeout 上限和逐事件协作检查——首字前静默、只发心跳帧的流既不会被切断也不会被取消(runner/助手在收到 delta 前是预算盲区)。现在 provider 的每次传输尝试都以所在业务段(整链预算或 child 段)的**剩余预算**用 `asyncio.timeout` 包出硬上限,到期即取消本次 await 并显式关闭 response 与异步迭代器;预算耗尽后绝不再发起新请求。②**重试退避受剩余预算约束**:429/5xx 的退避 sleep 被压进 `min(退避值, 剩余预算 - ε)`(此前 remaining=1.1s 时会按 Retry-After=8s 真睡 8 秒),剩余不足 1s 仍不重试;重试门在每次决策时重算剩余,不再用构造时的旧值。沿用的硬规则不变:超时/取消/结果未知的请求绝不自动重放。③**取消路径显式收尾**:生成 runner 与助手流按 aclosing 纪律在取消/超时/异常时逐层 aclose 生成器与 provider 流;生成 runner 的每任务新事件循环(asyncio.run)关闭前调用连接池的 per-loop 清理。④**多段用量汇总**:主生成 + 格式修复 + 质量重试 + 失败/取消轮次的 token 用量合并为一条总口径日志(token 缺失保留「未知」,不伪造 0,也不给部分和冒充完整值);助手失败/取消路径同样收集最后一轮用量。⑤**连接池按循环关闭**:池淘汰条目时在其**所属循环**内关闭客户端(当前循环直接调度,其他循环经 `run_coroutine_threadsafe` 投递,绝不跨循环 await),修复此前"只丢引用、连接悬挂"的问题;⑥**连接池 cookie 隔离**:共享客户端显式禁用 cookie 持久化(httpx 默认会把响应的 Set-Cookie 存进客户端并在后续请求自动带上——池内客户端跨 provider/模型轮换复用就是会话状态污染),测试钉住两个请求不共享 cookie 状态。⑦断开连接/超时/取消/协议错误按类别记录结构化日志(复用现有日志上下文设施)。配套测试:`test_llm_hard_deadline.py`(退避压预算、无首字流/心跳流硬切、预算耗尽不发新请求、多段 usage 汇总与取消补记)、`test_llm_client_pool.py` 生命周期与 cookie 用例、`test_resume_generate_task.py` 循环清理用例。
- **风险扫描的隐私与降级契约修正(2026-10-13 生产审计 PR-11)。** ①**身份字段不再外发给模型**:风险扫描的可选 AI 增强(深挖风险点)此前把简历完整 JSON 直接放进远端 prompt,姓名、手机号、邮箱、公司名全部出站。现在出站输入统一走一个最小化构造函数:复用 `privacy.redact`(全仓库唯一脱敏实现)遮罩姓名/电话/邮箱/公司、排除照片,姓名/公司/学校/项目名等身份 token 对序列化全文做字面兜底替换(它们常以叙事形式写在描述正文里),长数字串(手机号/证件号/银行卡号)再兜底遮罩一次;本地扫描仍用完整文本(本地要靠它发现敏感项),测试钉住出站消息不含合成姓名/手机号/邮箱。②**畸形模型返回不再把整个扫描打成 500**:模型返回的深挖点先经受限规范化(`points` 是对象则包一层数组、是字符串等不可恢复结构则整体降级;`follow_up` 只接受字符串/字符串数组,畸形抛受控错误;条数上限 20、单条文本截断),解析失败与意外内部异常都降级为本地规则结果并在 notes 里明确「AI 增强未完成」,意外异常的日志只记类型与简短描述、不落 prompt/响应原文。③**查重去重边界与预算**:完全相同的要点只报一次精确重复告警(此前同一对要点同时触发精确+相似两条),精确重复成员不再进入相似比较;相似比较的候选条数(200)、比较次数(20000)与告警条数(50)三重上限,超限截断并在 notes 注明——长简历不再把运行在 async 路由里的本地扫描放大成无界 O(n²)。④**台账条目收敛**:远端增强 prompt 只携带与当前简历关联的强责任台账条目(关联键:台账 `subject` 出现在简历正文,上限 20 条);无关联条目时明确指示模型只能以「全库通用准备建议」类别给通用建议,不与简历特定告警混淆,未关联条目内容一律不外发。配套测试 `test_resume_risk.py` 新增出站隐私断言、畸形返回降级矩阵、去重边界、长输入预算与台账关联性用例。
- **桌面安装包缺少入口必需文件,打包后无法启动(2026-10-13 生产审计 PR-04)。** `desktop/main.js` 顶层有 `require("./updater")`,但 `desktop/electron-builder.yml` 的 `files:` 正向清单没有 updater.js——2026-10-12 生成的 app.asar 里确实没有该文件,入口模块顶层 require 失败发生在业务错误处理之前,打包出的桌面版一启动就崩。修复:①清单补上 `updater.js`(updater.js 只依赖 Node 内建模块,无其他本地依赖需要一并补);②新增 `desktop/scripts/check-asar-deps.js` 完整性门禁——递归解析 main.js 的本地相对 require 图,逐一验证每个模块在 electron-builder 清单(`--source`,打包前静态检查)或 app.asar / app.asar.unpacked(`--asar` / `--unpacked`,打包后真实产物检查)中真实存在,缺失即非零退出并点名缺失模块;零新增 npm 依赖,asar 读取复用 electron-builder 自带的 `@electron/asar`;③CI 的 `desktop-shell` 作业在打包前后各加一步运行该门禁;④新增 `desktop-shell-smoke` CI 作业:真实启动 win-unpacked 的 `ResumeForge.exe`、从 desktop-backend.log 解析后端端口、取服务端页面与入口模块("端口活着不等于页面能用")、退出后断言后端端口不再监听——该作业只能在 CI 环境运行(审计约束:不操作用户本机进程)。配套测试:`desktop/scripts/test-asar-deps-check.js`(node:test,零依赖,覆盖真实仓库源码检查、目录/asar/嵌套 require 图、清单遗漏与陈旧条目、win-unpacked 布局识别、用法错误退出码)与 `backend/tests/test_desktop_asar_deps_check.py`(subprocess 冒烟)。
- **桌面退出生命周期统一停止状态机(2026-10-13 生产审计 PR-05)。** ①**托盘退出不再绕过停止**:此前托盘「退出」先置 `quitting=true` 再 `app.quit()`,`before-quit` 见标志直接 return,后端进程从不被停止(审计用 Node VM 调真实菜单回调实测 `backend.stop` 调用次数为 0,悬空的 uvicorn 会锁住文件句柄、破坏下次启动/更新)。现在所有退出入口(托盘退出、窗口关闭、更新安装、window-all-closed)统一走 `app.quit() → before-quit`:`before-quit` 是**唯一**停止后端的地方,等 stop 完成后才放行 quit(stop 带 10s 硬上限防挂死),第二次 `before-quit` 直接放行。②**更新安装顺序修正**:删除"先 spawn 安装器再 quit"的反模式(注释写"先停止后端再安装"与实现相反)——更新器校验通过后把安装器路径交给 main 的 `installAndQuit`,真实 spawn 发生在 before-quit 中 **backend.stop 完成之后**。③**崩溃重启预算**:5 分钟窗口内超过 3 次异常退出即停止自动重启并弹错误说明(窗口滚动清除,偶发崩溃照常自愈)。④**fallback 启动脚本退出不再误判崩溃**:打包兜底分支的子进程是短命的引导脚本(Start-ResumeForge.ps1 / start.sh),它启动真后端后正常退出——现在该分支下引导脚本退出时先探活后端端口,健康则视为**交接**(并从 `runtime/backend.json` 认领真实后端 pid,stop 时杀真正的进程树),不再计入崩溃重启;引导脚本在后端健康前退出仍按崩溃处理。⑤**后端日志有界**:`desktop-backend.log` 超过 5MB 轮转入 `.old`(保留一代),不再无界增长。配套测试 `desktop/scripts/test-lifecycle.js`(Node VM 加载真实 main.js、stub electron/backend-manager,断言托盘退出→stop→quit 顺序与安装器 spawn 时序,修复前红:stop 0 次)。
- **桌面更新器取消信号与下载加固(2026-10-13 生产审计 PR-05)。** ①**fetch 换 AbortSignal**:此前对 fetch 传 `timeout` 选项——它不是取消信号,无法中断已建立的响应;现在所有更新请求都带 `AbortSignal.timeout`(Electron 33 内嵌 Node 20 支持,与 backend-manager 健康探针同款),导出 `timeoutSignal()` 并测试其真实超时中止。②**下载有界流式**:删除 `arrayBuffer()` 全量入内存的实现,改为 Content-Length 预检 + 分块写入 `<目标>.part` 暂存文件 + 累计大小硬上限(512MB,超限即中止并清理暂存);**校验和验证通过后才 rename 发布**,校验失败绝不替换候选安装器。配套测试 `desktop/scripts/test-updater.js`(stub fetch 捕获 signal、超限响应中止不留残file、校验和不匹配不发布、匹配才 installAndQuit)。
- **桌面运行时 marker 版本化(2026-10-13 生产审计 PR-05)。** `.runtime-complete` 此前只写一个时间戳、无版本信息——升级依赖重新打包后,旧的解包运行时(旧 site-packages)会被静默复用。现在 marker 记录运行时 zip 的 SHA-256 摘要;启动时 marker 与期望摘要不符(或为旧版纯文本格式)即重新解压安装,崩溃中断的半安装也绝不会再留下"已完成"假 marker(先删 marker 再解压)。配套测试 `desktop/scripts/test-backend-manager.js`。
- **桌面解包内置运行时不再依赖 PATH 里 `tar` 是谁。** 打包壳用 `tar -xf` 解 `python-runtime.zip`:bsdtar 能读 zip,而 Git for Windows 的 GNU tar 不能——一个把 Git 的 `usr\bin` 排在 PATH 前面的启动环境(从 Git 终端双击 exe、或继承了该 PATH 的启动器)会让解包直接失败,壳永远起不来。现在 Windows 上显式使用系统自带的 `%SystemRoot%\System32\tar.exe`(Windows 10 1803 起随系统提供),取不到才回退 PATH;macOS 维持 PATH 的 bsdtar。配套用例改为用同一个解析器建包再解包:此前"建包"走 PATH 的 tar,GNU tar 环境下整例静默跳过(本地 1 跳过、CI 却真跑并暴露另一个用例缺陷),现在两边都真跑。

### Added

- **简历中心批量选择支持「全选本页」。** 与其他列表一致:勾选若干简历批量删除时,操作条上可一键全选当前页。
- **CSV 导入弹窗新增「下载空模板」。** 岗位广场与备选岗位两处都能拿到带表头的空模板(岗位模板不再含"来源"列——来源由系统按录入方式自动判定,无需填写)。
- **备选岗位支持岗位链接、CSV 导入导出与「全选全部」。** 添加/编辑备选岗位时可以留一条**岗位链接**(选填,导入岗位时预填投递链接,详情里可回原站);抽屉右上角新增 **导入 CSV**(预览账目 → 确认入库,列集与添加表单同字段名,旧表头别名都认),勾选候选后工具条里可 **导出 CSV**(文件名带导出时间);工具条新增 **全选全部(共 N 条)**——按当前筛选跨页勾选全部候选(批量操作一次最多 500 条)。后端配套:`GET /api/candidate-jobs/ids`(跨页全选)、`/csv/export`、`/csv/import/preview`、`/csv/import`。
- **模拟面试的「自动轮数」完全交给 AI。** 轮数下拉选「自动」后,界面与提示词都不再出现"3–9 轮"之类的范围:面试官按你的表现自主决定何时收尾(回答有料就深挖、空洞就自然收束),收尾语带整场总评;服务端仍保留 12 轮硬性保险防止对话失控(只在代码里,不向用户/模型展示)。
- **桌面安装包支持自定义安装位置。** 安装器从"一键直装"改为向导式:可以自主选择安装目录(此前 exe 一运行就直接装到默认位置);桌面端自动更新仍走静默安装,更新体验不变。
- **发布包可选注入桌面安装版(`-IncludeDesktop`)。** `scripts/Build-Release.ps1` 新增开关:出包时先跑 `Build-Desktop.ps1 -Target win` 产出 NSIS 安装器,注入压缩包的 `desktop/ResumeForge-Setup-<版本>.exe` 并纳入必需清单校验——解压便携 zip 的用户不用再去单独的下载页就能跑安装版。默认关(普通包保持纯源码 + 启动器,旧版应用内更新器解析的正是这个形态);只支持 `-Ref HEAD`,`-Platform macos` 拒绝;`desktop/package.json` 版本与后端不一致直接报错。macOS 桌面目标(dmg)仍需真 Mac,维持独立事项。
- **岗位广场支持 CSV 导入导出。** 勾选岗位后工具条里点 **导出 CSV**,即可把选中的岗位(含职位描述、任职要求、来源等全部字段)导出成 Excel 友好的 UTF-8 CSV;筛选条新增 **导入 CSV**:选文件先出**预览**——可导入行、逐行错误(带文件行号可直接定位)、与已有岗位重复自动跳过,确认后才入库;表头支持别名、UTF-8/GBK 编码都能读,导出的文件不改列名就能原样导回。
- **批量选择全面支持「全选本页」。** 题库历史、提醒、网申填充记录、求职助手会话、模拟面试历史、深挖历史的批量操作条都补上了「全选本页」复选框(半选状态可见,跨页保留其他已选)——此前这些卡片列表只能逐条手点,岗位广场的表格与备选岗位原本就有。
- **模拟面试支持「自动轮数」。** 新增迁移 `0043_interview_auto_rounds`:不想提前知道这场面试有几题的话,打开设定卡里的「自动轮数」开关——面试官按你的表现判断何时收尾,最迟第 12 轮强制结束出报告(硬性保险不出现在界面与提示词里);面试界面上只显示"第 N 轮",不再剧透"还剩几题",更接近真实面试的不可预期。固定轮数模式一切照旧。
- **AI 调用整体提速:共享连接池 + 测试连接短输出探针。** 与大模型服务商的 HTTP 连接改为进程级共享连接池(此前每次 AI 调用都重新建连——日常 AI 功能每次平白多付数百毫秒的握手开销,连续对话/批量操作里尤其明显);设置页「测试连接」的探针请求把输出上限压到 64 token(连通性检查只在乎"模型回话了",不再陪跑用户配置的完整输出长度,快模型测连接也从十几秒降到一两秒内)。
- **匹配度分析有了后台进行标志与完成提醒。** 在岗位详情发起匹配度分析后可以关掉窗口去做别的:岗位详情抽屉的「匹配度分析」按钮会转为「分析中…」转圈,后台进度一眼可见;无论你有没有守在弹窗前,分析完成或失败都会弹出统一通知并响一声(成功文案指路结果所在位置)。此前"后台进行"在界面上毫无迹象、完成也没有任何提醒——两个缺口一并补上。
- **岗位「来源」拆成独立字段。** 新增迁移 `0042_job_origin_source`:岗位表单新增「来源(选填)」一栏(如"内推 / 招聘会 / 猎头"),岗位详情抽屉同步展示;历史备注里自动追加的「来源:…」行会由迁移搬到新字段、备注正文还原为用户写的内容。来源从此**删得掉、改得动**——此前它被硬拼在备注末尾,手动删除后保存又会被补回来、编辑一条空备注的岗位也会凭空长出来源行(用户反馈的原痛点)。没填来源时仍会按录入方式自动预填(粘贴文本识别 / 图片识别 / 文档识别等),只是不再侵入备注。
- **原生桌面应用(Electron)。** 简历通现在有真正的桌面软件形态:双击秒开独立品牌窗口(不再经过浏览器,也没有命令行黑框),启动时显示品牌进度页(准备运行环境 → 启动后端服务 → 等待就绪 → 加载界面),就绪后无缝进入应用。窗口支持系统托盘常驻(关闭窗口最小化到托盘,托盘菜单可打开/退出),单实例锁(重复启动只唤起已有窗口),后端异常退出时自动按指数退避重启(至多 3 次,仍失败弹原生错误对话框并指引导日志)。全部日志收编到 `runtime/logs/`,运行期零控制台输出。
- **两种安装形态。** 「安装版」(`ResumeForge-Setup-*.exe`,NSIS):双击安装、自动创建开始菜单快捷方式,卸载走系统标准入口;「便携版」zip:解压即用,与既有使用习惯一致。两种形态共用同一用户数据目录,互相切换不丢数据。桌面快捷方式更名为「简历通」,`start.cmd` 首次启动时若桌面没有快捷方式会自动创建。
- **零下载首启。** 安装包内置预装好全部后端依赖的便携 Python 运行时(`python-runtime.zip`,约 47.5MB),首次启动只需解压到用户数据目录即可启动后端,不再现场下载 Python 解释器与依赖。
- **桌面端自动更新。** 已安装版启动约 30 秒后静默检查 GitHub 最新 Release,发现新版本会弹窗询问;下载后经 SHA-256 校验(与发布 checksum 侧车文件逐一比对,不匹配绝不安装)再退出应用并静默运行安装器。更新下载遵循逐跳域名白名单(仅 GitHub 系主机),与后端 `update_allowed_hosts` 同一安全纪律。

### Fixed

- **写入提案「批准」按钮此前从未真正生效(批准端点两处接线错误,PR-02)。** ①批准接口的路由前缀是 `/assistant` 而前端统一按 `/api/...` 发请求——所有批准/拒绝/状态查询全部 404,用户点「批准」只会看到失败提示;②后端拿到会话工厂后未实例化就直接当上下文管理器用,即便路由修好也会抛 `TypeError: 'sessionmaker' object does not support the context manager protocol`。两处都已修复,并用真实 HTTP 路径的端点测试钉住:批准一次成功落库、二次点击不重复写入、会话打开失败/执行失败/提交失败分别返回结构化错误并把提案置入可解释终态(失败/结果未知),任何终态都不会被再次执行。
- **数据集身份现在贯穿「创建提案 → 批准 → 执行」全程,数据集切换改为可补偿的提交段(PR-03)。** ①提案创建时绑定数据集 id **与代次**:A→B→A 来回切换后,旧提案(id 相同、代次已前进)批准会被拒绝,不再可能把写入串进旧库;②批准执行前先取数据集租约、冻结会话工厂改从租约身份派生——核销之后到提交之间即使有切换请求排队,写入也落在批准时刻的那份数据集上;③切换数据集改为 prepare/commit/compensate:先建好并迁移目标引擎,任一步失败都保持原数据集不变(旧引擎不再被过早销毁),杜绝「指针已写成目标库、内存还在旧库」的不一致窗口;④启动时校验数据集指针与可用库一致性,指针指向的库已丢失/损坏时记录日志并回退主数据(崩溃窗口的确定性恢复);⑤简历生成 SSE 把租约获取提前到任何业务快照读取之前(资料/岗位/事实基线与保存段同属一个数据集身份),快照阶段失败时先还租约再报错;⑥提案状态查询不再暴露其他数据集作用域的提案详情。
- **资料箱卡片的右键菜单此前从未生效。** 右键菜单包在不透传 ref/事件的组件外面,右键一直是空操作——现在垫了一层容器,右键菜单真正可用(知识库与事实台账卡片的右键同步修复)。
- **岗位详情的「录入方式」不再显示"未记录"。** 老数据或恢复数据集里没有录入方式的岗位,现在如实显示"手动",与列表角标口径一致。
- **知识库卡片右上角不再有「查看」字样。** 点卡片本身就打开详情,重复入口已移除。
- **「下载空模板」不再弹出文件选择框。** 按钮此前被包在上传拖拽区内,点击会冒泡成"选择文件"——已移出。
- **实习岗位不再被标成「校招」。** 采集卡片不带类型字段,此前导入备选/岗位时默认回落「校招」——BOSS 直聘上满屏的"实习生"全被错标。现在导入时先按标题推断类型("实习 / Intern"字样优先,其次校招/社招字样),推断不出才用采集任务的标注,最后才回落默认。
- **备选岗位卡片底部的多余留白。** 已导入状态的卡片不再渲染空的操作区容器,卡片高度随内容收紧。
- **浏览器标签页图标(favicon)补齐多尺寸。** 此前只有一张 331KB 的大图做图标(浏览器缓存后常显示成默认地球);现在提供 16/32/48px 小尺寸与 256px 收藏夹图,标签页图标加载更快也更稳(若仍显示旧图标,强刷或清一次站点数据即可)。
- **岗位广场「导出 CSV」点击即报错。** 导出请求走的是原始响应通道,缺了 `Content-Type: application/json` 头——后端把请求体当空表单解析,Pydantic 报「Input should be a valid dictionary or object to extract fields from」,一个岗位都导不出来。补上该头并加回归测试钉住。
- **应用内更新「重启并安装」此前从未走通——点了安装,应用既不退出也不重启。** 根因是 install 端点是同步 `def`,被 FastAPI 丢进线程池执行,而线程池线程没有运行中的事件循环,`schedule_install` 里"等更新器接手再退出后端"的 `asyncio.create_task` 必然抛 `RuntimeError: no running event loop`;端点把它转成 409,退出逻辑永不执行,更新器等不到后端退出、超时后自我了断,用户再次点击只会看到「更新包还没有下载完成」。现在端点改为 `async def`,协程在事件循环线程上被正常调度;并补上「下载完成 → 重启安装」成功路径的端点级回归测试(修复前该用例必然 409)。
- **更新「已下载完成」状态不再因后端重启而丢失。** 下载状态此前只活在进程内存里,升级流程中用户关掉应用再打开极常见——磁盘上的安装包还在,界面却显示"没有更新包"、安装按钮消失。现在空闲时会扫描下载目录,认出比当前版本更新、命名合规且校验通过的安装包并恢复为「可安装」(多个取最新;等于/低于当前版本、校验不过、命名不符的一律不恢复),`download-status` 查询与点击安装两处入口都会兜底恢复。
- **更新的错误提示不再张冠李戴。** 「状态不对」与「文件丢了」此前共用同一句「更新包还没有下载完成」——文件其实被清理或安全软件移除时,这句话会把用户引入错误的排查方向。现在文件不存在会如实说明「更新包文件不在了(可能被清理或安全软件移除),请重新下载」,并在真正安装前对安装包再校验一次(下载与安装可能相隔数天,防文件被替换/损坏,也再挡一次路径穿越),校验失败给清晰错误并置为失败态。
- **安装/下载失败后界面会重新拉取真实状态。** 此前失败只弹出红色错误、不刷新状态,陈旧的 `ready` 会让绿色「更新包已下载完成」与红色错误同屏并存(用户截图里的现场)。现在失败后立即向后端要一次真实状态,绿色成功框与安装按钮会随之消失。

## 0.18.0 - 2026-10-09

### Added

- **文件副本库。** 资料箱新增「文件副本」页签:所有存入简历通的文件(资料箱附件、简历照片、岗位备注图、备选岗位截图、助手消息附件)都会自动留一份副本,集中在这里查看——图片与 PDF 可直接预览,其它类型可一键调用系统程序打开。副本统一保存在应用数据目录的 `user_files/` 文件夹、按内容指纹去重;启动与切换数据集时后台幂等回填,随数据集切换 / 迁移自动重建。求职助手同步支持查询副本清单(`list_user_files`,只读元数据,不返回文件内容),能力目录补齐「批量设置状态」等条目。
- **自定义生成内容的保存位置。** 设置 → 「数据」页签新增「保存位置」卡片,**只支持点选**:点「选择文件夹」在本机弹出系统原生选择对话框(后端拿到真实绝对路径,中文路径不乱码),选完自动保存;默认留空 = 只保存到浏览器的默认下载目录,设置后导出简历会在下载的同时落盘一份到指定文件夹,目录不存在 / 不可写在保存设置时即以中文说明原因。
- **求职助手「放宽模式」。** 设置 → 「AI 模型」页签新增开关(默认关):开启前明确告知解锁范围——助手可读取完整资料(姓名、电话、邮箱等身份字段)、网申填表的真实填写值与历史对话,这些内容会随提问发送给所配置的大模型服务商;关闭时一切照旧(脱敏视图、四个敏感工具不下发)。**API 密钥等凭据与全局配置、简历照片的图片内容任何模式下都不会发送给模型**,助手也永远没有删除类工具。
- **投投悬浮球更顺手。** 悬停悬浮球会提示「单击:打开求职助手;双击:打开投递剪贴板」;双击打开的投递剪贴板卡片改为出现在悬浮球旁(不再跑到屏幕左上角);页面弹出任何弹窗/抽屉时悬浮球仍然可点可用(层级整体提到弹层之上:球 3400 > 助手卡 3300 > 剪贴板卡 3200);在「我的资料」编辑表单处于未保存状态时,悬浮球上方会出现「保存资料 / 取消」浮动按钮,保存、取消或离开编辑后自动收起——不用再滚回顶部找保存;停在「我的资料」时球旁还会升起一张「想补充或修改资料?」提示卡,点「去编辑」等同于点页头的「编辑资料」,右上角的 × 关掉后本次停在这一页期间不再出现(离开再回来会重新提醒一次),并同样受「投投提示标语」开关控制。
- **模拟面试有了面试官形象「简」。** 「投-简-历」家族补齐中间一员:简是模拟面试模块的面试官,浅蓝光泽球猫造型(金丝圆镜 + 深蓝领结,与投投/历历同款渲染语言),在「面试官设定」卡头自我介绍;面试对话里每条面试官消息都带头像,等待下一问时切到扶镜思考的表情;四种面试官风格(严谨专业/温和引导/持续追问/压力质询)各自映射默认表情。素材位于 `frontend/src/assets/jane/`,映射逻辑见 `frontend/src/pages/interview/janeFaces.ts`。

### Changed

- **「我的资料 → 网申资料」编辑流畅度第二轮优化 + 搜索修复。** 重复区块的每张记录卡进一步 memo 化——编辑某一条记录时,同组其余记录整卡跳过(上一轮只做到"组级",组内其它记录仍会重渲);「搜索字段名或内容」加 300ms 防抖(此前每敲一个字符就重挂载一批控件);**搜索现在对重复区块也生效**(此前只过滤上面的字段表单):在记录的组名 / 字段标签 / 已填内容里匹配,没有匹配的组整组隐藏,且只在**已填的内容**里找——搜一个字段名不会再把所有空记录都带出来。
- **「我的资料 → 网申资料」编辑卡顿治理(按排查报告 `deliverables/profile-input-perf-audit-2026-10-09.md` 实施)。** 日期三连选(PartialDateSelect)加 memo 并把月/年/日选项改为常量与按需缓存(此前每次渲染重建约 185 个选项对象,页面同时存在十几个三连选);教育/实习等重复区块的每组卡片加 memo——此前任何一条记录打字都会让**所有记录全部重渲**,现在只有被编辑的那一组重渲。
- **「只看已填写」现在对重复区块也生效。** 该开关此前只过滤上面的字段表单,教育 / 实习等重复区块照旧全量显示(用户反馈"似乎没有生效"的真实原因);打开后整组没填过的记录不再渲染,关掉即恢复。
- **一批长列表补上了翻页。** 按全站排查(`deliverables/pagination-audit-2026-10-09.md`)给持续增长的列表补齐分页:网申填充记录、本次采集结果、采集记录、题库历史、知识库、备选岗位、历史面试、深挖历史、提醒列表、求职助手会话列表——每页 10–20 条、只有一页时不显示分页器,筛选 / 搜索后自动回第 1 页;分页形态与岗位广场一致(总数显示,表格另带条数切换)。
- **投递队列支持筛选与批量管理。** 顶部新增「按状态 / 按准入」筛选(筛选只影响列表显示,准入告警与投递范围仍按全部数据判断);新增跨页「全选队列」(勾选当前筛选结果里全部可投岗位,与每页的表头全选并存)与「移出所选」批量移出(逐条容错,完成后给出成功 / 失败账目);计数条随时显示队列总数、筛选数与已选数。
- **投递队列分页显示。** 队列改为每页 10 条、超过一页自动出现分页器(此前 50 条一页且单页隐藏,队列一长整屏塞满),分页形态与岗位广场 / 简历中心一致(条数切换 + 总数显示);上移 / 下移按钮下线——顺序由加入先后决定(最新在最上),逐条操作收敛到行末「更多操作」菜单。
- **投递队列「刷新」按钮有了点击反馈。** 刷新期间按钮转圈(此前点击毫无变化,用户会以为没点上而连点)。
- **设置页每页签内按主题分组。** 「AI 模型」分为「模型服务 / 助手能力」两组,「应用」分为「界面与提醒 / 网申填表 / 更新与帮助」三组,每组带一句说明——功能多了之后不再是一列卡片从头滚到尾、分类一眼可辨。
- **海投提效:批量直接加入投递队列。** 岗位广场选择模式新增「加入投递队列」按钮:勾选多个岗位一步送进投递队列(未分析 / 真实缺口的确认一并带上,不再逐条弹窗;来源不支持的岗位自动跳过,结果里给出成功、已在队列、来源不支持、失败的聚合账目);「自动采集 → 本次采集结果」同步新增「导入并加入投递队列」,导入岗位广场与入队一步完成。
- **投递队列最新导入的岗位排最前。** 队列默认按导入时间倒序展示(刚导入的在最上面,与"采集完马上处理最新一批"的顺序一致),队列内排序的保存逻辑同步适配。
- **站点筛选进页即加载。** 「按招聘网站的条件筛」在清单读取期间显示占位行(此前整个区块消失,用户会误以为没有筛选功能);「自动采集」页签改为应用启动即预渲染并预读采集配置与筛选清单,切过来时筛选项已就位。
- **采集状态卡与结果面板融合。** 采集批次的状态卡(进行中/已完成,含暂停/继续/停止)从页面顶部移进「自动采集」页签,摆在筛选账目与「本次采集结果」之间;原表单下方的动态提示条删除(信息与状态卡重复)。
- **「采集后筛选」整体下线。** 自动采集不再有"采回来再按岗位字段筛"的那一层(期望薪资 / 经验 / 学历与岗位类型标注全部移除,连同依赖它的「校招」档)——条件过滤全权交给「按招聘网站的条件筛」。历史配置里的旧值不再参与筛选,导入口径不变。后续收口:求职助手能力目录与「使用指南」改为只讲站点筛选这一层口径,本地筛选相关的界面死分支、样式、测试断言与代码注释全部清理;后端筛选实现(collect_filters)保留并标注「当前未启用」,供未来站点接入时复用。
- **站点筛选新增「测试是否生效」。** 「按招聘网站的条件筛」标题行新增按钮:在真实招聘网站页面上逐项检查你当前选的条件现在能不能选到,并逐项给出「已生效 / 未生效 / 不限」的结论;浏览器未启动时明确提示先启动。
- **冗余悬停提示清理。** 移除一批"提示内容与可见文字重复"的悬停气泡:助手页标题、左下角「使用指南」、页头的当前数据集与头像、「资料箱 → 文件副本」的文件名、个人资料分区标题、求职进度卡片里的来源标签、识别入口的文件名标签。图标按钮与截断溢出场景的提示(有真实信息增量)全部保留。
- **批量适配度分析评分样式重做。** 圆环按参考分分档配色(≥70 绿 / 40–69 蓝 / <40 琥珀,纯展示口径),圆环内直接显示「N 分」,右侧改为"匹配度参考分 + 准入结论标签 + 模型"的纵向元信息,窄卡片整行换行而不再截断文字。
- **投递设置弹窗不再顶格。** 表单限高滚动(与全站弹窗口径一致),底部「取消 / 保存设置」与表单之间加分隔线。
- **月历溢出修复。** 求职进度的「日历提醒」月历里,过长的提醒标题会把整块栅格撑出弹窗——根因是网格单元缺少最小宽度约束,已修复;标题在格内省略号截断,悬停仍可看全称。
- **自动采集界面重排。** 「采集后筛选」(期望薪资 / 经验 / 学历)只在成功采集到岗位后才显示(字段保持挂载,保存与开始采集的提交内容不变);「求职类型」的超长分工说明缩成一句短提示并约束表单项宽度,不再把选框顶出排版。
- **BOSS 采集翻页方式修复 + 翻页饱和提前停止(修复"卡在某一页、一个岗位都采不到")。** 用户实测定位根因:BOSS 搜索页**不吃 URL 的 page 参数**,靠向下滑动列表加载更多——旧逻辑换 page 值导航,读到的永远是同一批推荐位,全部重复却仍按"有下一页"翻满 30 页。现在第 1 页导航进入搜索页,第 2 页起**滚动列表加载下一批**(不再导航),并把采集改为两阶段:先滚动读完整列表,再统一抓岗位详情;连续两页没有新结果也会提前停止翻页。账目与收尾文案写明停止位置与下一步建议。
- **采集过程动态提示与账目间距。** 采集进行中在结果区上方显示"正在采集岗位…"提示条(实时计数 + 进度条 + 当前页码);账目 Alert 与表单按钮之间增加留白。
- **投递台提示配色区分与选框对齐。** 首次扫码登录、站点筛选完整选项两处提示改为琥珀色(与流程说明的蓝色、采集成功的绿色形成层次);采集条件各行改为顶边对齐,「求职类型」的说明文字不再把旁边的选框挤得高低不齐。
- **批量选择入口全站改为右键菜单。** 11 处页面(简历中心、岗位广场、备选岗位、助手会话与消息、历史面试、深挖历史、历史题库、日历提醒、网申填充记录、回收站等)的条目右键菜单里新增「批量选择」进多选;全卡片支持右键操作。回收站条目右键含批量选择 / 恢复 / 彻底删除。
- **全站弹窗统一新皮肤。** 弹窗垂直居中、统一圆角 / 阴影 / 内距,body 高度收敛,长内容改为弹窗内部滚动。
- **全站加载动效统一。** 引入 LoadingBlock 与 PageSkeleton 两套通用加载组件并逐页替换原有 Spin / Skeleton 用法。
- **界面细节打磨。** 求职进度卡片信息层次与阶段色彩条间距优化;「我的空间」菜单选中态改为浅色;近期提醒弹窗与月历视觉精致化。
- **表单输入更跟手、更干净。** 网申填表页与「我的资料」大表单输入卡顿优化:草稿持久化改为防抖落盘(离开页面强制落盘一次)、浏览器状态与实时会话轮询在内容无变化时不再触发重渲染、预览表与资料行组件 memo 化,长表单打字不再整页重绘。「我的资料」→「网申资料」的草稿改由该分页自己持有,打字只重渲这一块,不再连带动「简历资料」的表单字段与日期三连选(实测每次击键的阻塞时间从 76~178ms 降到不再出现长任务,生产构建同样无长任务)。全部文本输入框移除「0/4000」式字数计数,达到上限时以一行红字提示「已达 N 字上限」。设置页「界面与导航」改为「核心入口 / 我的空间」两组两列网格;求职进度卡片左侧改为与漏斗一致的阶段色圆角色条;回收站的复选框列默认隐藏,点「批量操作」进入多选模式。

### Fixed

- **本机后端为状态变更请求增加浏览器跨站防线。** 后端只监听 `127.0.0.1`,但恶意网页仍可借用户浏览器向本机后端发跨站请求(如调用退出接口、批量改数据)。现在 POST/PUT/PATCH/DELETE 请求会校验 `Origin` 来源与 `Sec-Fetch-Site` 头:来源不是本机回环(dev 的 Vite 代理 `localhost:5173`、生产的同源 `127.0.0.1:8005` 均不受影响)或浏览器明确宣告跨站的一律拒绝;不带 Origin 的本地工具、脚本与启动器调用照常放行。同时「选择文件夹」弹窗端点补上与本机密钥查看一致的回环校验,避免被任意网页反复弹出模态对话框骚扰。
- **更新器的「更新前数据快照」此前从未真正生成。** Windows 与 macOS 两份更新器的快照函数都读项目根下的 `data/`,而真实用户数据在 `backend/data/`(该目录下不存在 `data/`,函数静默提前返回)——数据库迁移一旦出问题就没有这份最后的回滚保险。现已修复为对 `backend/data` 快照(数据库 + `-wal` / `-shm` / `-journal` 边车 + 指针 json + datasets 子目录,保留最近 3 份),macOS 侧 datasets 子目录此前完全没拷边车也一并补齐;文档中的 `auto-backups` / `pre-update` 路径措辞同步写准。
- **手动添加岗位截图识别报错「Cannot destructure property 'warnings'」。** 纯截图(没粘贴文字)识别时,如果 AI 识别不可用(未配置模型 / 模型不支持图片输入 / 调用失败),本地兜底没有文字可切,接口返回了空 `items`,前端解构第一份草稿时直接崩出英文错误。现在后端保证 `items` 至少返回一份空内容草稿——界面会提示「没有识别到内容」并说明原因,用户手填的内容也不会被抹掉;前端对空列表另加了兜底,不再崩溃。
- **截图识别不再「一会儿行一会儿不行」。** 视觉模型有偶发抖动:同一张截图,这一次没抄录图片文字、没输出识别结果,紧接着再调一次往往就能成功;此前一次失败就直接回退本地规则,对纯截图等于直接失败。现在岗位识别(单份与多份拆分)失败后会立即自动原样重试一次,仍失败才回退并说明原因;重试只发生在失败路径上,正常识别不多花时间与调用。
- **「去投递队列查看」点击无效。** 投递台的页签此前只存在内存里,从采集结果的导入成功提示点「去投递队列查看」(以及任何带 `?tab=` 的链接)都无法切换页签、毫无反应;页签已改为从 URL 解析(与岗位广场的筛选/分页同模式),这类入口现在直接生效,页签链接可分享、刷新后停留的页签不丢。
- **工作台「导入 HTML」按钮点击无响应。** 按钮的事件绑定在样式重构后失效,点击不再没有任何反应。
- **编辑岗位等长弹窗触底贴边。** 内容较长的弹窗改为居中显示、内部滚动,底部不再抵住屏幕下缘。
- **网申填表辅助卡片与弹窗一批修复。** 辅助卡片「保存到哪里」列表不再整列深蓝(仅真实选中行高亮)、右上角关闭键恢复低调样式;「记住这条」新增可编辑的「字段内容」输入框,默认带入用户在表单里填的值;自定义字段(如「项目链接」)有值时,AI 建议不再误报「资料里还没填这一项」;资料箱/知识库点「编辑」不再同时弹出详情卡(只弹编辑窗);两处编辑窗的「分类」下拉恢复可选;简历预览的「手动调整」编辑器不再被宿主弹窗压住;首页「近期提醒」关闭钮不再自带焦点边框;岗位批量适配度分析与事实台账等超高弹窗改为居中显示、卡片内部滚动,底部不再抵住屏幕下缘;数据集切换等 5 处悬停提示在点击展开后立即消失。
- **「网申资料」两处会丢数据的修复。** 其一:新增自定义字段时会重新取一次字段目录,而那次带回的多条补充记录(教育经历/实习和工作/项目经历等)是进入页面时的旧值,原先会拿它覆盖用户刚编辑、还没保存的内容;其二:在「简历资料」分页点「编辑资料」、之后才打开「网申资料」时,取消一次编辑会把这一页刚加载好的字段目录清掉——之后再点「保存全部资料」,整份网申资料都不会提交,界面上却毫无提示。
- **文档统一「匹配度参考分」口径。** 匹配度分析已有 0–100 的参考分(本地规则估算、仅展示、不参与投递准入、带免责文案),但 README、使用指南、面试指南、架构文档与助手系统提示词里仍残留「不给百分比评分 / 不存在分数」的旧表述,已一并更正为「五类结论不带百分比,参考分仅作展示辅助」;助手提示词同步改为如实告知界面有参考分并引导用户到岗位详情查看,不再声称"匹配度没有分数"(工具返回剔除分数的红线不变)。本轮收口最后一批残留:README FAQ 标题(原「匹配度为什么不给分数?」)、应用内使用指南「匹配度分析」条、`what-it-does.svg` 与 `architecture.svg` 图内文案(PNG 已同步重导出)、官网 `demo.html` 与 `docs.html` 两处。

## 0.17.0 - 2026-10-07

### Added

- **网申填表准确率增强。** 放宽模式的受约束文本匹配现可用于批量预览、实时逐框建议和悬浮球快捷填充;使用已有字段标签/别名及页面上下文匹配,重复记录按可判定的区块/顺序配对,歧义或安全否决时不猜。只靠弱上下文的放宽文本匹配标为「需确认」且默认不勾,精确标签与可完整配对的重复记录仍可自动填。自定义字段以用户保存的标签参与匹配,未新增“我的资料”字段。移除过宽的「工具」项目技术栈别名,避免误匹配 AI 工具问题。教育经历补充移除「是否境外教育」「是否统招」「是否最高学历」「是否辅修」,历史值保留但不再参与新目录。

- **新用户可一键载入「体验示例」数据集。** 首启动引导弹窗新增「载入示例数据体验」:应用自动新建独立的「体验示例」数据集(主数据不受影响,随时可整份删除),写入一个纯虚构的市场运营岗位与一份示例简历,载入后刷新即有内容可看。后端对应 `POST /api/settings/datasets/sample/load`。
- **设置页新增「帮助与诊断」。** 一键导出脱敏诊断包(zip:脱敏运行事件 + 系统信息 + 近期日志尾部),不含简历数据与密钥;同卡片给出 GitHub Issues 与用户群反馈入口(群号可复制)。
- **设置页数据分页新增「隐私与数据主权」卡片。** 把"数据只存本机、无账号无埋点、AI 只在主动调用时发送必要内容、导出与彻底删除路径"写成用户可读的条款,与官网新增的隐私专页同一套叙事。
- **自动备份。** 启动后在后台检查"距上次自动备份是否已到间隔",到期自动快照当前数据库并只保留最近 5 份(默认每周一次;`AUTO_BACKUP_ENABLED` / `AUTO_BACKUP_INTERVAL_DAYS` / `AUTO_BACKUP_KEEP` 可调)。快照存放在数据目录的 `auto-backups/`,与手动导出互不干扰。
- **更新前数据快照。** 两条更新器(Windows / macOS)在覆盖程序文件之前先把数据目录的数据库与数据集快照到 `data/pre-update/`(保留最近 3 份)——新版本的迁移万一在旧数据上出问题,还有回头路。
- **文件日志轮转。** 除启动器重定向的 `runtime/*.log` 外,后端在数据目录 `logs/backend.log` 追加一份 5MB×5 的滚动日志(`LOG_FILE_ENABLED=false` 可关;`LOG_LEVEL` 可调)。
- **SQLite 启动维护。** 每次启动迁移完成后执行一次 WAL 截断 checkpoint 与 `PRAGMA optimize`,防止长期运行下 `-wal` 文件无限膨胀;失败只记日志不阻断启动。
- **Playwright 浏览器冒烟。** 三条真实 Chromium 用例(启动入口齐全 / 载入示例后岗位简历可见 / 示例简历导出 PDF)接入 Windows 与 macOS 的干净检出 e2e 作业,补上 jsdom 捕不到的布局与浏览器 API 层。
- **CI 新增 CodeQL 与 Nightly。** CodeQL 每周对 Python 与 JS/TS 跑静态安全扫描;Nightly 每日全量后端 + 前端回归,失败用例自动重跑两次并在汇总中标记 flaky(只标记不掩盖)。

### Changed

- **Python 下限从 3.10 升到 3.11。** 3.10 已于 2026-10 EOL;随之解锁 `sqlalchemy 2.1.3` 与 `fonttools 4.66.1`(Dependabot 对应 ignore 已移除)。sqlalchemy 2.1 的 URL 百分号编码与 alembic configparser 的冲突已在迁移装配处转义修复,并有"带百分号与空格的库路径"守卫用例。CI 矩阵调整为 3.11 / 3.13(ubuntu)+ 3.12(windows)。
- **CI 前端作业升 Node 22**,与本地开发一致;解锁 `vitest 5.0.3` 与 `jsdom 30`(此前 5.0.3 在 Node 20 下的确定性失败未再复现,全量 146 文件通过)。
- **ruff 启用 I / UP / B / SIM 规则集**(B008 与个别"非法输入必须被拒"的测试用例按书面理由豁免),全仓一次性清零。
- **后端接入 pyright(basic)**,先钉住 `app/models` 与 `app/schemas` 两层类型边界(0 错误);其余子目录随注解补齐渐进纳入。
- **前端覆盖率门禁从 0 到 1。** 基线实测(146 文件)语句 66.5% / 分支 61.2% / 函数 59.1% / 行 68.2%,CI 阈值取基线 −3pt 且只升不降。
- **依赖审计分级门禁。** 生产依赖(`requirements.txt`)出现已知漏洞即失败、npm 生产依赖 high 及以上失败;开发工具链维持 advisory 不阻断。
- **CI 增加版本号单源核对**(`scripts/check_version_sync.py`):`config.py` 的 `app_version` 与 `frontend/package.json` 不一致时合并失败。
- **备份快照剥离 `sqlite_stat*` 统计表**,导入白名单按 `sqlite_` 前缀放行 SQLite 引擎内部表——`PRAGMA optimize` 留下的统计表不再导致合法备份被旧版本拒收。
- **求职进度列表瘦身。** 列表接口的备注与依据字段改为截断预览(超出上限加省略号,全文走详情接口),求职看板的主读路径不再随列表拖带大文本;编辑表单打开时自动从详情接口取全文回填,保存不会把预览文本写回库。
- **性能优化批(第二批)。** 简历模板引擎匹配缓存——修复日期控件每轮渲染被重建导致实例缓存失效的真元凶,长表单匹配基准提速约 2.1-2.5 倍;助手历史消息气泡 memo 化,流式输出期间历史气泡不再整树重渲染;实时填表空闲退避——页面静止约 3 秒后轮询间隔自动放大到 1.5 秒,任何事件立即恢复快档,交互手感不变;求职进度合并预览的逐条点查改为一次批量查询。

## 0.16.0 - 2026-10-06

### Added

- **求职助手新增三个工具(P3 工具批)。** ①「匹配分析记录」(只读):助手能读「岗位广场 → 分析记录」的历史批量适配度分析——批次时间、岗位、完成/失败数与每个岗位的硬性门槛、准入结论、建议要点;与界面一致,返回里**不含任何分数、百分比或按分数派生的名次**,匹配度只有五类定性结论。②「新增面经」:用户说「记个面经」时,助手可直接把公司、岗位、来源、被问到的真实问题、难度、轮次与日期录入面经知识库。③「新增内推」:用户说「记一条内推」时,助手可记录公司、岗位、内推人、关系、渠道等(状态默认进行中);内推码与备注图片的完整编辑仍在「投递台」页进行。
- **简历模板工作台增强。** 内置样式新增「社论」「分栏」两套;「导入参考模板」支持多张图片 / PDF / DOCX,识别结果不再直接落库,而是先生成可解释的视觉草稿,用户可在真实 A4 预览旁微调颜色、字体、页头布局、分栏、照片形状、技能标签和自定义标签图片后保存。原有 HTML 导入、格式模板、旧模板数据与直接导入格式模板接口继续兼容。
- **列表与历史记录支持批量选择。** 简历中心(表格勾选)、助手历史对话、历史面试、深挖历史、  
  日历提醒、历史题库、网申填充记录都能点「批量选择」进入勾选模式:勾选若干条后「删除所选」  
  统一二次确认,逐条删除、全部进回收站可恢复。
- **投投有了时间感知。** 求职助手(整页、悬浮卡与专用浏览器里的「问历历」共用链路)每次回答前都会  
  拿到你的本地时间与星期,能自然地按时段给建议——例如深夜/凌晨提醒你早点休息,不会生硬重复;  
  主界面与悬浮卡的新对话问候同样分时段:深夜打开会多一句「改完这一份就早点休息吧」,欢迎对话同理。
- **点击页头 GitHub 图标会弹出 Star 提示。** 直跳仓库的行为不变,同时轻提示一次:如果简历通对你  
  有帮助,欢迎去给项目点个 Star(弹窗内带直达按钮)。每次点击都会提醒。
- **网申一键填充展示成功率。** 悬浮球的填充完成文案与整页「填充结果」卡都会给出本次成功率  
  (成功填写数 / 页面可读取表单框总数),并同时展示实际识别并尝试填写的数量。
- **网申填表增加脱敏诊断导出。** 用户可以导出版本、请求编号、控件数量、填充结果和前端错误等排障信息,  
  不包含简历值、表单值、密码、Cookie 或 API Key,适合连同截图提交问题。
- **日期转换更完整,但只写给文本框。** 简历资料中的日期写进「YYYY-MM-DD」这类文本框时按页面的写法转换  
  (原样、带分隔符、只有年月就只写年月,缺的部分不编);页面明确要完整日期时才把缺的日补成 1 号,并在  
  预览那一行注明让你核对。日期控件与年月下拉本身不再自动填——见下面的「只填不点」。
- **网申浏览器打开即自动开启智能逐项填表。** 启动浏览器或打开目标页面时会随首个页面注入历历球,  
  不用再等一会儿、也不用手动开;同时去掉了注入时把浏览器窗口强行唤到前台的动作——那正是  
  "球一出来浏览器又弹一次"的来源。
- **关闭智能逐项填表后悬浮球继续保留。** 单击悬浮球或主界面按钮只切换功能启用状态:关闭时悬浮球变灰、  
  输入框聚焦不再弹提示卡,双击菜单和重新开启仍可用。
- **简历预览新增「生成说明」与结构化的「未收录清单」。** 折叠的「为什么是这样一份简历」解释岗位  
  信号、候选资料组成与省略原因,全部由确定性数据拼装、不经模型之手;「资料里有但没写进这份简历」  
  的内容从"疑似虚构"红墙里分出来,以中性清单分组展示,每组可一&#x952E;**「AI 补上这段」**(预填修订指令,  
  只新增不改其余)或打开「手动调整」。
- **生成简历现在会读取 GitHub 主页与个人主页。** 此前这两个字段不进生成上下文,模型看不到自然也  
  写不进简历;现在它们随候选资料提供给模型,并作为简历的独立字段渲染在页眉联系方式区(简历编辑器  
  里也可手动填写),只作展示、不参与防虚构核对。
- **简历预览新增「AI 修改 / 重新生成」。** 简历详情底部新增入口:填修改要求则**只改你提出的部分**  
  (其余内容逐字保留),留空则整体重新生成(事实口径不变、表达与结构重新组织);修订直接更新当前  
  记录并刷新预览。姓名、照片与联系方式不进模型、由代码强制回填,模型改不了也看不见。
- **岗位优化建议可以逐条采纳。** 「岗位优化建议」弹窗里每条建议新增「采纳并修改」:按这条建议修订  
  简历(同样只改它涉及的内容)并立即刷新预览。
- **新增「投投剪贴板」。** **双击**任意页面的投投悬浮球打开(单击仍是打开求职助手):把要反复粘贴的  
  自我介绍、项目描述、联系方式等存成片段,每条一键复制到系统剪贴板,可新增 / 编辑 / 删除(最多 50  
  条);片段只保存在本机浏览器 localStorage,不上传。
- **网申填表新增「放宽模式(代点下拉与确认勾选)」,默认关闭。** 「只填不点」的边界保持为默认:  
  用户在设置页或网申填表页**显式开启**后,点选类控件(自定义下拉 / 弹层选择器、原生下拉、事实类  
  单选 / 复选)若能匹配到资料值,程序才尝试代点——选项按严格口径解析(精确 → 别名 → 包含,  
  纯数字不猜),点选后回读比对,一致才算填上,失败如实报「代选失败:+原因」;依赖下拉按 DOM  
  顺序写入(父级先于城市)。同意 / 声明类勾选**绝不静默代勾**:批量预览里以「需你确认」行出现  
  (默认不勾、淡警示底色,勾选后才代点),实时面板给出「帮我勾选:{字段名}」按钮,点按即视为  
  用户本人确认。日期选择器与文件上传在任何情况下仍不代做。开关保存在本地设置中,开关开启前  
  两处界面都有风险提示(存在选错可能,填完请核对)。
- **网申「放宽模式」第二轮定向放宽(2026-10-06 字节校招页实测后)。** ①「没有实习经历」这类  
  「没有…」开头的声明勾选与「无…」同等待遇:以「需你确认」行 / 实时「帮我勾选」出现,绝不静默  
  代勾;② 开启放宽模式后,规则认不出字段的点选类控件(弹层 / 下拉)会交给 AI 兜底识别**字段名**  
  (沿用全部既有纪律:只发字段名不发资料值、目录外丢弃、采纳前守门、调用额度不变):命中且资料  
  有值 → 放宽代选行(标 AI 来源、默认不勾),命中无值 → 如实进「资料里还没有」,不识别 / 调用  
  失败 / 额度用完 → 保持原 blocked 文案;③ 弹层代选支持在一个弹层里选多个值(如意向城市最多  
  3 个):按资料值逐个严格解析、**全部命中才动手**,任一不命中整体放弃并如实报缺哪一项,回读  
  验证选中的值;④ 语言弹层的页面措辞(「语言」「精通程度」)补进字段同义词,竞赛名称、社交平台、  
  意向城市目录已有对应字段。
- **网申弹层代选支持字节自研 UD Design 页面(真机 2026-10-06 实测后)。** 此前弹层代选只认
  Semi/antd/aria 声明形态,在字节校招页 7 个弹层全部读不到选项。现在:① 通用 portal 根发现
  覆盖 UD 的 `ud__select__dropdown`(挂 body、选项行无 role="option")与树形多选
  `ud__tree__node`(城市弹层);② 关闭后仍 display:block 残留在页面上的弹层(atsx-select)
  按「点击前已在页面上且文档坐标没变」时序排除,不再被当成当前控件的选项;③ 树形行的点选
  目标是行内 checkbox(点行中心只是展开);④ rc-virtual-list 长列表优先输入过滤、再逐屏滚动
  加载;⑤ 同名重复选项去重,不让严格解析卡在 ambiguous。真机逐控件验证:证件类型(居民身份证)、
  学历(本科)、学历类型(全日制)、手机区号(+86)、期望工作地点与意向城市(上海)全部代选
  成功且回读一致。
- **求职助手新增 4 个工具,能力地图同步纠偏。** ① `list_format_templates`:列出自制格式模板清单
  (id / 名称 / 说明 / 版式参数),改模板前先看清单;② `list_apply_records`:按批次查看投递记录,
  含每个岗位的结果、失败分类与可操作诊断——「上次为什么投失败」助手终于答得上来了;
  ③ `update_resume`:代为收藏 / 取消收藏、写备注(只动这两个标记,正文与版式不受影响);
  ④ `update_reminder`:把提醒标记为已完成或已忽略。另:`update_job` 支持 `favorite` 参数
  (与 list_jobs 的收藏筛选对称);助手对简历的收藏 / 备注改动与投递记录、格式模板清单、
  提醒标记都会在「助手做了什么」卡里显示中文名称。

### Changed

- **侧栏导航重组:主流程留在侧栏,其余入口收进页头「我的空间」。** 左侧栏保留高频主流程
  九项(首页 / 岗位广场 / 简历中心 / 投递台 / 网申填表 / 求职进度 / 模拟面试 / 求职助手 / 设置),
  我的资料、收藏夹、资料箱、知识库、工作台、求职统计、事实台账与回收站改到页头「我的空间」
  下拉菜单(当前所在页会在菜单里高亮);「设置 → 应用 → 界面与导航」里的隐藏开关照旧生效。
- **网申弹层控件的字段识别修正(真机 2026-10-06 取证后)。** 区号弹层的可见标签只剩触发框
  显示值(value 属性为空),此前被认成 phone、证件类型弹层被认成 id_number——值根本装不进;
  现在这两类弹层按「显示值自证」识别(旁文出现独立 +数字 token → 区号;出现证件枚举项
  → 证件类型),与实时面板同一判据。「学历类型」(统招/自考类选项)不再被 degree 抢走,
  归入学习形式(study_mode)。「全日制」的选项别名补「统招全日制」。UD 自定义下拉的显示值
  画在触发框容器里(input.value 恒空),回读验证会读容器显示文本做包含比对,点选成功不再
  误报「可能未生效」;已选中的期望值不再重复点选(树形 checkbox 重复点会取消勾选)。

- **放宽模式下「认得出字段但资料没值」的点选控件改入「资料里还没有」。** 此前这类控件留在  
  blocked、文案是「需要你自己点选」,用户看不出该去补哪一项;现在如实落到 missing_data 并标出  
  字段名(与文本框同一措辞)。开关关闭时行为与文案完全不变。
- **网申填表:关闭浏览器后本次填写会话自动结束。** 此前浏览器整个被关掉后,实时填表  
  会话仍永远显示「运行中」,只能手动停止;现在连续几轮通信失败且确认调试端口不可达后  
  会自动结束本次填写,界面如实给出一行中性提示(正常流程,不按故障处理),日志保留一条  
  说明。页面跳转、关闭单个标签页这类情况不受影响。
- **网申引擎匹配提速约一个数量级。** 控件文本指纹与字段证据计算改为按控件缓存(快照  
  frozen 不可变,纯派生值算一次终身受用):此前一次预览 / 批量投递逐页匹配会对  
  「字段 × 控件」反复重算同义词扫描,60 控件 30 字段的页面从约 760ms 降到约 66ms。  
  匹配结果与全部守卫行为不变(webform 全量测试通过)。
- **求职进度列表排序下推 SQL 并限量 500 条。** 列表不再把全部记录拉回内存用 Python  
  排序(`MAX_LIST_LIMIT` 开始生效,更早的记录不再随列表返回);漏斗统计改从 SQL  
  聚合取**全量口径**,列表截断后数字仍准确;CSV / JSON 导出与助手工具保持全量不受限。
- **实时填表空闲降频。** 「点哪个填哪个」的页面轮询在连续约 2.8 秒无任何事件后从  
  0.35 秒放缓到 1.5 秒,任何交互、请求、页面跳转或出错立刻恢复快档——静止页面不再  
  持续消耗前后端 CPU,交互手感不变。
- **求职助手流式输出时历史消息不再整页重渲染。** 此前回复的每个增量片段都会让整棵助手页  
  重渲染,历史消息气泡与会话列表跟着陪跑(长对话下逐条重新解析全文 Markdown,流式明显卡顿)。  
  现在历史消息内容与会话列表都做了缓存短路:只有正在流式的那条回复在动,其余部分不重画。
- **投递记录面板翻页换成标准分页器。** 「下一页 / 上一页」文字链换成 antd 分页组件(可直跳页码,  
  单页时自动隐藏);批内记录表从一次全量渲染改为每页 20 条分页(不满一页不显示),投递队列  
  右键菜单支持 Esc 关闭、关闭后焦点归还触发行。
- **SQLite 启用 WAL 与 busy_timeout。** 后台任务(如批量投递)持续写库时,前端轮询读不再被写锁阻塞,  
  偶发锁库告警也改为最多等 5 秒再重试报错。
- **重活移出事件循环。** 网申表单预览的规则匹配、备份包导入(zip 校验、解压与迁移)、技能包导入,  
  以及岗位 / 资料 / 台账四处文档(PDF/DOCX)解析改为在线程池执行——大表单预览或大备份导入期间,  
  健康检查与前端轮询等其余接口不再跟着卡顿。
- **悬浮球表情图转 WebP 并按需加载。** 6 张表情 PNG(约 1.4MB)转成 WebP(合计约 0.18MB,透明通道  
  保留),并且只加载当前表情那一张、切换表情时换 `src`——首屏约省 1.3MB,视觉与交互不变。
- **回收站与投递队列的大列表不再全量渲染。** 回收站按每页 20 条分页、投递队列按每页 50 条分页  
  (条数不满一页时不显示分页条);批量选择跨页保留,队列的上移 / 下移跨页也仍然正确。
- **首屏路由预热改为空闲分批。** 打开应用 800ms 后预热各页面代码块的节奏从「一次发起 18 个 chunk」  
  改为空闲时分批(每批 3 个,优先用 `requestIdleCallback`,不支持的环境回退定时器),不再一次性  
  挤占带宽与编译资源。
- **网申填表收窄为「只填不点」:只往文本类控件里写值。** 下拉、单选、复选、日期控件,以及点开才是  
  弹层的自定义选择器(**可搜索下拉**——学校 / 专业 / 意向城市这类看着像输入框、必须点开列表选  
  一个才算数的组件;只读的省 / 市 / 区级联框、日期选择器)一律不再自动填,会如实列进  
  「不会自动填」并写明"需要你自己点选"——不假装。这类组件库下拉靠**祖先类名**识别  
  (`select` / `dropdown` / `picker` 一类):MokaHR 用的 Semi Design 在 `<input>` 上不带任何  
  aria 标注,此前会被当普通文本框填掉。旧边界是"事实类照填、只挡表态类"(性别、学历、  
  意向城市照填),现在两类合成一条:这些框的值是**你的选择**,不是资料里的一行字;而且程序往只读  
  展示框 / 组件状态里写值,常常是"看着填上了、交上去还是空的"。同意条款与声明类勾选的拦截不变;  
  AI 兜底同样拿不到这类控件(连问都不问);预览里**手动**确认过的值仍然可以填。  
  随之变化:预览不再为下拉列出可改挑的选项(它不再出现在「将填入」里),实时面板点到日期控件会  
  提示"需要你自己点选"。
- **数据集导出可以选择是否包含大模型 API Key。** 设置 → 数据页新增「备份中包含大模型 API Key」  
  勾选项(默认关闭,行为与以往一致:密钥剥离出包)。勾选后,导出当前 / 全部数据集时密钥会按  
  **本机存储形态**随包带走——Windows 上是 DPAPI 加密形态(绑定当前用户与这台电脑,换电脑恢复  
  解不开时按未配置处理),不会被解密成明文;含密钥的备份包格式号升到 3,旧版本导入时会明确  
  提示"请先升级应用"而不是含糊拒收,导入端则要求清单显式声明密钥状态(缺失仍拒收)。导入  
  勾选过该选项的包成功后,会如实提示"密钥已一并恢复"。
- **工作台「简历模板」整合重复入口。** 独立的「模板市场」卡片并入「样式模板」卡,成为其中的  
  「按场景选:模板市场建议」折叠区(四套预设、建议组合的文案与行为不变);模板预览弹窗统一为  
  一个组件(工作台行内预览与市场预览共用:前者可切字号/版式,后者按预设组合直预);A4 画布  
  尺寸常量收敛为单一份。能力无删减。
- **岗位、简历、求职进度、收藏夹的筛选与分页进 URL。** 关键词、类型 / 状态 / 来源、收藏筛选、  
  页码与每页条数改动后以 replace 语义写回地址栏(不堆历史栈),刷新、后退与分享链接都能恢复  
  原来的筛选状态;岗位详情(`?job_id=`)同样进 URL,打开 / 关闭详情抽屉同步写回。
- **「AI 分析适配度」未进选择模式时不再给死路提示。** 点击后直接进入选择模式并提示  
  「已进入选择模式,勾选岗位后点分析」,替代原先「请先选择岗位」的无效警告。
- **采集导入与投递完成补上下一步入口。** 采集结果导入成功的提示带「去岗位广场查看」动作;  
  投递批次结束后的进度面板与投递记录面板头部都提供「去求职进度跟进」链接。
- **「开始投递」增加浏览器前置引导。** 浏览器未启动时点击开始投递不再发出必然失败的请求,  
  而是提示「请先启动浏览器再开始投递」;按钮保持可点、由点击引导。
- **进站弹窗改为先后顺序弹出。** 首次使用的欢迎引导与「近期提醒」弹窗此前可能同时叠出,  
  现在欢迎引导关闭后再弹近期提醒。
- **职位描述(JD)输入增加 20000 字上限并显示字数。** 与全站表单风格一致,超长粘贴不再  
  无提示地溢出。
- **网申填表:「需要你自己点选」的分桶在放宽模式下让出一部分给预览行。** 开关关闭时  
  blocked 分桶与文案一条不变;开启后,点选类控件里能匹配到字段 + 值的那部分改以  
  「放宽代选」(默认勾选)或「需你确认」(同意 / 声明类,默认不勾)进预览行,仍无法  
  匹配的(含日期选择器、文件上传、密码 / 验证码类终局判定)留在 blocked 且理由原文不变。  
  批量填充按 DOM 顺序写入,使依赖下拉(省份 → 城市)在父级选中后再解析选项。

### Removed

- **移除「按场景选:模板市场建议」折叠区。** 工作台「简历模板」的「样式模板」卡恢复为  
  只有内置样式画廊与自制样式模板列表,不再提供模板市场建议入口;随之清理了只为该折叠区  
  服务的预设直预通道(预览弹窗的预设预览分支)。模板预览弹窗本身保留,行内预览切字号 /  
  版式的能力不变。

### Fixed

- **助手能力地图两处文字纠偏。** ① 「网申填表」条目原先只写「下拉、单选、复选、日期控件一律
  不自动填」,未提放宽模式例外——现在写清:放宽模式默认关闭、在网申填表页顶部或「设置 → 应用」
  开启、开启后代点下拉与弹层(严格匹配 + 回读验证)、同意 / 声明类勾选仍需逐条确认、日期与文件
  上传仍不代做,且助手没有开关它的工具;README 功能特性表同步补「放宽模式」一行(与目录映射
  双向锁定)。② 「数据集」条目原先写「导入导出不含大模型 API Key」,与实际行为(备份默认不含、
  导出前可勾选包含,密文绑定本机)不符,已改为与 README 一致的措辞。
- **网申弹层控件判定不再被样式类名与字面 `aria-haspopup="false"` 误触发。** 页面注入脚本此前对祖上三层的类名做裸子串匹配,`selection` / `selected` 这类普通词也会把普通文本框判成弹层、被「只填不点」白白跳过;现在 Tailwind 的 user-select 工具族(`select-none` 一族)与选中态词只在**整个类名恰好是**它时排除,`semi-select` / `ant-picker` / `sd-Dropdown-container` 等组件外壳照常识别;`aria-haspopup="false"`(规范里的「明确声明无弹层」)不再因字符串真值而被当成弹层。日期库选择器、自定义下拉等全部「只填不点」边界不变。
- **紧急联系人等亲属栏字段在占位符是通用词时不再漏填。** 紧急联系人的框占位符常写「请输入姓名 / 请输入手机号码」——占位符权威规则把 `name` / `phone` 当唯一主人,而它们又被「他人信息」负向词拦下,亲属字段两头落空、整框进「没认出来」。现在占位符点名的字段全部被负向词拦下时,占位符权威落空、回退到 label 档(**不退旁文**:旁文被整表标签污染的本人姓名框不会错收他人的值)。同时把逐条资料 `contact_*` 组与主目录 `emergency_contact_*` 的同义词对齐,「请输入与本人关系」这类占位符不再被两个并列最高分判成说不清。
- **「页面还要求这些」面板补上「换个资料…」出口。** 批量预览的「需要你自己动手」「没认出来」分区此前没有任何后续路径,现在指向智能逐项填表的「换个资料…」。浏览器里的实时面板本身对所有状态(含「不会自动填」)都已有该入口、没有匹配项也能展开完整资料清单,行为不变。

- **实时填表的自定义字段别名取值在标准字段标签与同义词重复时失效。** 实时链路把「标签与标准网申
  字段标签精确同名且唯一」的自定义字段值映射到标准字段(如「期望薪资」→ salary),但全部 51 个
  网申补充字段的标签同时也出现在自己的同义词表里,唯一性判定把同一字段的重复命中误判成歧义,
  这条别名支线对所有字段都没有生效过。现在判定按字段键去重后再看是否恰好一个字段;同名不同的
  两条自定义字段仍视为歧义、已有规范字段值仍优先。
- **悬浮球逐框建议在控件带 name 属性时无法命中同名自定义字段。** 精确同名匹配此前把控件的
  label / placeholder / aria_label / name 拼成一串再归一比对,而真实网站的控件几乎都带 name
  (如 Google 表单的 entry.123456),拼接结果永远不等于自定义标签的归一结果,建议全部落空。
  现在改为逐属性精确匹配——任一属性归一后与某自定义字段标签完全相等即命中;同名两条仍放弃、
  无值仍提示「换个资料…」。
- **后台任务完成后刷新页面不再丢失完成提醒。** 此前生成任务跑一半时刷新页面,完成弹窗与  
  提示音就永远不出现了(登记表只存在内存里)。现在任务元数据随登记写入本地存储,刷新后  
  应用启动时自动恢复:已完成的补发「简历已生成 / 生成失败」提醒,仍在进行的恢复进度追踪,  
  任务记录已不存在的静默清理;隐私模式下存储不可用时行为退回原样,不影响使用。
- **岗位 / 投递记录搜索框点「×」清除按钮后筛选条件不更新。** 搜索框此前是非受控的(只监听回车搜索),  
  点 × 清空输入框后列表仍按旧关键词过滤;现在改为受控输入,清空立即按空关键词重新加载,外部带入的  
  关键词(如 URL 带参)也会回填输入框。岗位广场与投递记录两处同修。
- **网申填表:表单整页展开后的第二轮真机复验(字节 / 美团 / 京东 / 腾讯 / 鹰角)。** 五处修复:
  - **弹层类控件不再抢单**:字节页实测,+86 区号框抢走了手机号、只读的「证件类型」抢走了  
    证件号码,两个真输入框双双落空。修法最后收成了下面那条「只填不点」——这类控件干脆不再  
    参与自动匹配,连"抢"的机会都没有。
  - **手机号被页面清空时改用纯数字重试**:资料里的 `138-0000-0000` 在字节页被清空(那个框  
    只收 11 位数字),第二次换 `13800000000` 写成功;腾讯页则被页面自己规范化成同一串数字  
    ——现在两种情况都算填对。
  - **整页填充收尾复读一次**:"当场回读有值、组件随后自己还原"的伪已填(字节的意向城市框)  
    如实降级为"待确认",不再报成功。
  - **超过框长度上限的值不写**:美团内推码框限 9 个字、资料里的码 12 个字,此前写进去被  
    页面截成残码(`RF-BOSS-`);现在直接跳过并说明"请精简后自己填"。
- **网申填表:四个真实站点实测发现的识别问题一批修复(腾讯 / 美团 / 京东 / 鹰角校招页)。**
  - **光杆「请输入」不再当成字段陈述**:京东校招页的姓名 / 手机号码 / 电子邮箱三个框占位符都  
    只是"请输入",真标签在旁文("*姓名:")里——回退到旁文**开头那一段**后三个最基础的字段  
    不再集体"认不出";长说明文(多行、带"您可以…建议…")同样不算陈述,腾讯的「补充信息」框  
    因此恢复识别(此前占位符里揉着自我评价 / 爱好特长 / 补充信息 三个概念,被判并列放弃)。
  - **区块限定的短词没有区块名不再认领控件**:鹰角的「游戏经历」框不再被认成「实习描述  
    (第一条)」——批量匹配一直有这道闸,实时面板补上了同一条判据(两处统一到  
    `block_hint_satisfied`)。
  - **整表标签串从旁文里减掉**:mokahr 类页面把整张表的标签串进每个控件的旁文(同一段  
    ≥60 字的文本出现在 ≥3 个控件时),它让每个框都"看起来"命中所有字段,现在只保留各自  
    的前缀。
  - **「作品网盘链接」框不再被"密码"误拦**:占位符里的提示「(如有密码,用分号分隔)」是  
    提示不是控件名,安全词判定先去掉括号内容;"网盘链接"收进作品链接词表。
  - **教育背景里连排的两对年 / 月(起止)现在填得全**:两对上下文一模一样、都只写"就读  
    时间",此前第二对(止)永远填不上;紧挨着的同父第二组按结构补成对应的结束字段。
- **网申填表:填了不生效会被如实报出来,并自动重试。** 填充完成后逐个回读页面值:读不到控件、  
  值被框架改回、勾选没生效都会如实标成"未验证"(此前回读调用出错会被记成"已填",成功率虚高);  
  不一致时先等 200ms 复读一次,仍不一致再按原因分类重试,最多两轮——重试前先回看页面现状:  
  值已经在就判成功、页面已被别的值占住就不覆盖、单选已勾上就不再点(再点会反选)。写值前先把  
  控件滚进视口并聚焦,重试路径补发 blur(部分站点把同步/校验挂在 onblur 上);富文本框回读  
  改读 textContent(此前读 value 恒为空串,填完一律被判"未验证")。填充结果新增失败原因分布,  
  一键填充的"成功率"由此变成诚实数字。
- **网申填表:AI 兜底的一次抖动不再让整页识别落空。** 模型调用失败或返回无法解析的内容时  
  自动重试一次("合法 JSON 但一个都不像"是模型的真实回答,不重试);重试与缓存命中都进脱敏  
  诊断,便于看出哪些页面在反复花钱。AI 给出的候选在采纳前还会再过一遍规则侧的否决  
  (亲属信息、区号等跨族冲突),模型不再能绕过这些闸门。
- **网申填表:被整表标签污染的普通日期框不再漏填,跨字段并列的框改标"需确认"。** mokahr 类页面  
  会把整张表的标签串累积进每个控件的邻近文字(其中含"手机号码"),此前日期族的电话负向词按  
  "标签 + 邻近文字"的签名匹配,会把同页正常的「毕业时间」文本框一并漏掉;现在改为按控件**自述**  
  (标签 / 占位符 / 属性)做跨族否决——区号框仍被拦下,普通日期框照常填,保护范围同时从教育 /  
  工作的四个字段扩到整个日期族(出生日期、经历 / 项目起止、获奖、入党、到岗时间)。另外,同一  
  控件被两个不同字段同档同分争抢时(旁文并列的「学校名称 专业名称」),此前按字段表顺序静默  
  选一个,现在如实标"需确认"让用户看一眼。AI 兜底的答案与规则引擎共用同一组否决(亲属信息、  
  区号等),模型不再能绕过这些闸门。
- **网申填表认得出"标签写在占位符里"的基础字段,年月分开的下拉不再连着填。** 鹰角 apply 页实测  
  两类问题:站点把标签直接写进占位符("姓名"/"邮箱"/"国籍",不带"请输入"前缀)时,这些占位符  
  不参与字段识别,框掉进邻近文字档又被整表标签列表("姓名手机号码+86邮箱…"挤在同一段)触发  
  高风险消歧——连姓名、邮箱都没认出来;现在裸占位符一对一命中即按占位符档计,没命中也不截断  
  邻近文字。另一个是目标表单的年、月各自是独立的输入框,此前年/月组件槽位不检查字段是否日期  
  语义(学校/专业的值会被塞进年月框),且匹配与 AI 兜底都把"2023-06"整串喂给年框和月框;现在  
  组件槽位只收日期形状的值,匹配、预览改值与 AI 兜底三条路径都会把完整日期拆成组件所属的部分  
  (年框收 "2023"、月框收 "06")。年月组件同时豁免电话负向词与高风险消歧的误伤(组件旁的整表  
  标签对组件是噪声不是信号),真区号框的保护不受影响。
- **网申填表不再认错相邻的相似控件。** 修复六站点真机验证发现的三类错位:教育经历的  
  「入学时间」与「毕业时间」在证据同分时会互相写反(同义词定义顺序所致,现已让开始时间  
  优先对应靠前的日期框);日期不再被写进邻近带电话语义的控件(如「区号」框旁的报错文案  
  里恰好出现「毕业时间」);占位符同时点名两个字段且得分并列时(如「请输入毕业学校专业」  
  同时像学校和专业),现在如实留空不再挑一个猜——宁可漏填,不可错填。start/end 配对、  
  区块限定字段与逐条资料变体仍按原有机制区分,不受影响。
- **一批弹窗不再超出视口,内容改为在卡片内部滚动。** 查看简历大图、使用指南、岗位匹配度分析、  
  岗位需求解读、批量适配分析、岗位化修改建议、版本对比、离线分享包、备选岗位详情、网申学习/记忆、  
  通知导入、模板导入微调、日历当日详情此前内容过长时弹窗整体被推出屏幕,需要在弹窗外的整页滚动  
  才能看全;现在这些弹窗都限制了最大高度、超长内容在弹窗内部滚动,卡片始终完整呈现在视口内。  
  「手动调整」编辑器弹窗同时改为垂直居中,修掉底部保存按钮被视口下缘挡住的问题;「生成简历」  
  弹窗的预览阶段宽度与布局对齐简历中心的预览弹窗。
- **生成简历预览阶段的「AI 补上这段」可以点了。** 此前该入口在生成弹窗的预览里点了没反应  
  (修订弹窗被误随「AI 修改 / 重新生成」按钮一起隐藏);现在只要预览能被刷新,修订弹窗就会挂载,  
  底部按钮的显隐逻辑保持不变。
- **修复网申分离年月控件与两条填充入口的口径不一致。** 年、月输入框现在分别写入对应组件值;专用浏览器悬浮球的「自动填写当前页面」复用主界面的匹配、冲突保护和日期转换逻辑;邻近标签同时出现姓名、手机号等候选时不再猜测,避免把姓名错识别成手机号。
- **网申表单不再被页面说明中的“密码”误拦截。** 此前把控件周围文字和共享的  
  `aria-describedby` 也用于安全黑名单判断,像“如有密码,用分号分隔”这样的作品链接说明会让姓名、邮箱、  
  手机号、性别等普通字段一起显示为“永不自动填写”;现在密码/验证码拦截只依据控件自身语义,明确的密码控件仍保持禁止自动填写。
- **多页简历预览改为并排的「两张纸」。** 此前选择 2/3 页时预览是一张拉通的长纸加一条贯穿虚线,  
  看不出页与页的界限;现在分页处显示一条「裁切间隙」(露出预览底色 + 两侧纸边阴影),观感是  
  两张并排的独立 A4 纸。分页位置、缩放、点击编辑、打印与导出 PDF 均不受影响。
- **历历悬浮球去掉白底白圈,星星与投影也不再被裁。** 那圈白来自注入脚本的  
  `background:#fff` 与 1px 白描边;右上角的金星被切、球下的投影不见了,则是因为  
  `.rf-live-avatar` 上写着 `border-radius:50%` + `object-fit:cover`——把方形素材裁成了圆,  
  而素材里金星正好探出球体、球下还有一层投影。现在**整颗球就是形象本身**:透明底、不裁圆、  
  也不额外加阴影(素材自带)。素材按内容重新切版留边(`backend/scripts/build_lili_orb.py`  
  可重跑),尺寸提到 128px 供 2 倍屏。**素材同时纳入版本控制并加了发布守卫**:发行包走  
  `git archive`,只带被跟踪的文件,漏提交会让历历回落成"白圆底 + 填字"——正是要去掉的那圈白底;  
  现在 `Build-Release.ps1` 的必检清单会对着归档条目校验它,另有一条测试盯着"文件存在且被跟踪"。
- **鼠标放到历历球上不再多出一圈深蓝底。** 那个蓝不是球的样式,而是**同一 shadow root 里  
  面板那条通配按钮规则**(`button:not(.navchip)…:hover:not(:disabled){background:#3475ad}`)——  
  它的特异性 (0,5,1) 压得过球自己的 `.rf-live-switch.has-avatar:hover`(0,3,0),于是鼠标一沾上  
  球就被刷成深蓝。按该文件已有的写法把球也排除掉(基础 `button` 规则一并排除)。真浏览器里  
  悬停实测:改前计算样式 `rgb(52, 117, 173)`,改后透明。
- **重复注入会连样式一起刷新。** 球的注入脚本原来发现"球已经在了"就直接返回,于是页面里那颗球  
  永远停在**注入那一刻**的样子:应用升级后不重开浏览器,老样式会一直留着。现在重复注入会把  
  样式表与形象一起刷新成最新的(不动 DOM、不重建球)。
- **逐项填表的卡片不再"先亮再灭"。** 浏览器路由已经在后端顺手开启了实时会话,前端原来还会再发  
  一次 `/live/start`,那份迟到的响应会把刚读到的"已开启"覆盖回"未开启"。改成**先读状态、没开才  
  启动**,顺带省掉一次多余往返。
- **资料里真实存在的技能不再被误报成"疑似虚构"。** 技能候选按岗位筛选后,模型从项目描述里带出的  
  工具(如 Excel)会拿去和技能候选名单比对,从而被扣上"候选资料未包含"的帽子;现在会先在候选资料  
  全文里找一遍,找到了就不再提示。
- **技能候选过少时自动按分数补足。** 技能候选只剩一两条时模型只能自拟技能区,又会触发上面的误报;  
  现在匹配数低于下限时按相关性分数补足(补的仍是用户资料里的技能)。
- **警告文案指向的入口找得到。** 覆盖度提醒里说的「微调内容」在界面上其实叫「手动调整」,已统一  
  措辞;同时"没写进简历"与"疑似虚构"两类提示在界面上分组呈现,不再共用一面红色警告墙。
- **网申专用浏览器的悬浮球定名「历历」,并按反馈重做交互与外观。** 注入球换成「简历球」形象(静态、  
  单图,无表情动画),去掉了球外圈的绿色圆环;右上角的「+」展开入口改为**双击悬浮球**触发,  
  拖动从"长按 280ms"改为**按住左键即可拖**;单击切换实时填表的语义保留(单击/双击自动判别);  
  菜单里的「问投投」同步更名「问历历」,  
  填表时可以直接向求职助手提问,数秒内收到简短回答(保留最近 5 轮上下文,页面跳转即重置)。问答一切  
  数据经本地 Python 中转,注入脚本不含任何对本地 API 的请求;未配置模型服务时会明确提示。匹配度类问题  
  只给定性结论,不输出百分比或分数。
- **投投的助手卡片可以拖动了。** 按住卡片顶部的窄条把手即可把卡片拖到屏幕任意位置(不会拖出视口,  
  把手永远可达,也支持聚焦后方向键微调);再次从悬浮球打开时,卡片会重新出现在悬浮球旁边  
  (按球的当前位置与贴边方向计算),而不是停在上一次拖动的位置。
- **「提示标语」开关进设置。** **设置 → 应用 → 投投悬浮球** 新增与悬浮球开关并列的「提示标语」开关  
  (默认开启,兼容老数据):关闭后悬浮球不再弹出轮换提示语,已显示的立即收起,重新开启后自动恢复;  
  不影响球的其它功能。
- **后台任务在后端重启 / 记录被清理后不再永久卡住。** 此前生成任务轮询遇 404 或持续失败时  
  会永远转圈、取消也每次 404;现在连续 3 次轮询失败(或 404 一次)即把任务标为「状态未知」  
  (不谎报成败),页头给出「移除」按钮;取消遇 404 时移除条目并提示「任务记录已不存在」。  
  之后某轮轮询恢复成功会自动回到正常显示。投递台的批次详情 404 同样停止轮询,显示  
  「任务记录不存在,可能已被清理」并提供「重试」。
- **网络不可达的报错中文化。** 后端服务未启动或网络不可达时,请求错误此前原样抛出英文的  
  "TypeError: Failed to fetch";现在统一转为「无法连接本地服务,请确认应用已启动」  
  (原始错误保留在 cause 上,不丢诊断信息)。
- **批量删除的确认文案与实际行为一致,部分失败如实报告。** 岗位广场批量删除的确认文案由  
  「删除后无法恢复」改为「将移入回收站,可随时恢复」;删除成功文案带「可在回收站恢复」,  
  部分岗位未能删除时如实报「成功 X 个、Y 个未能删除」。回收站批量恢复 / 批量彻底删除改为  
  逐条执行并汇总,部分失败时明确报出成功数与失败数。批量删除后仅当当前页被删空且还有上一页时  
  才回退一页,不再无脑跳回首页。助手会话批量删除的同类失真文案一并修正。

### Fixed

- **悬浮球不再被弹窗、抽屉等覆盖层挡住。** 球与助手卡片提升到应用内所有已知覆盖层(含 antd Modal/Drawer  
  与预览遮罩)之上,任何时刻都能点到投投;球吸附顶边时,助手卡片自动下移让出球的位置,不再把球盖住一半。
- **投投浮窗的历史抽屉不再贴边。** 「新对话」按钮、筛选组与会话列表项与抽屉边框之间有了统一内边距,  
  悬停与选中态不再和边框重叠。
- **修复助手输入框的 104px 高度规则失效。** 旧规则选择器串着一层 antd 5 已不再渲染的包装元素,从未生效;  
  现在选择器直接落在输入框上并上下锁高,粘贴超长文本时输入框内部滚动(整页 104px / 浮窗 72px),  
  不再把布局撑坏。
- **浮窗附件按钮的提示不再被裁切或遮挡。** 浮窗内改用短文案并限宽换行,贴近视口边缘时自动翻转;  
  卡片层级提升后提示也不再被卡片盖住。

### Changed

- **投投更有点击网页小动物的味道了。** 悬浮球被收进屏幕任意一边时不再只露一条细边,而是露出近一半并周期性  
  朝屏幕内"探头张望"(鼠标移上去完整滑出);用鼠标在它身上连续快速划过,它会眯起眼睛满足地摇摆一小会儿  
  (助手思考/出错时不抢表情,开启"减弱动态效果"时只换表情不摇摆)。
- **对话里可以直接粘贴截图。** 求职助手与投投浮窗的输入框都支持 Ctrl+V 贴图(浮窗此前把附件入口整个关掉了,  
  现在连同缩略图与「附件」按钮一起恢复,手机上也有可点的入口)。贴图那一轮的**紧接一句追问**会再带上这张图  
  ——"这个岗位匹配吗 → 那我简历该怎么改"能接得上;再往后的追问不再重复发送,避免用量随轮数上涨。  
  **行为变化**:此前历史图片在任何后续轮次都不再发送。粘贴图片不计入字符预算(体积上限仍在入库时校验)。
- **投投悬浮球升级为独立助手卡片。** 去掉纸飞机与星星环绕图标,悬浮球支持上 / 下 / 左 / 右四边吸附,  
  在简历预览弹窗中仍保持可用;点击后可直接与投投对话,支持联网搜索和思考强度。浮窗与侧栏助手复用同一套  
  助手能力与安全边界,但聊天记录按入口分开保存;助手新增本地产品文档检索,能回答简历通使用问题。
- **新增「投投」求职助手悬浮球。** 任意页面都能点击它进入求职助手;悬浮球支持六态表情、拖拽吸附、贴边收纳、键盘操作和减弱动态效果,助手请求中会显示思考 / 完成 / 出错状态,并会偶尔给出简短的求职与简历技巧。可在 **设置 → 应用 → 投投悬浮球** 中关闭,设置会持久保存。

### Fixed

- **拖拽悬浮球不再顶掉助手的表情。** 此前流式回答期间拖动一下球,思考脸就被换成"好奇"且不会恢复;  
  现在助手正在思考 / 完成 / 出错时,拖拽与松手回弹都不会改变表情。久置睡着时收纳比例也放宽到 62%  
  并飘出 Zzz(原来的 30% 细边根本看不出它睡着了)。
- **悬浮球在窄屏上的吸附位置不再差 8px。** 拖拽的边界夹取与贴边吸附改用元素实测尺寸,  
  与 CSS 在 ≤767px 切换的 56px / 16px 保持一致(此前按 64px / 24px 硬算)。
- **投投浮窗的输入区不再被卡片裁掉。** 浮窗页容器沿用了全站的 `content-box`,`height: 100%`  
  再加上内边距就比卡片高出一截,输入区一旦长高(例如贴了几张图),底部那行发送按钮会被卡片的  
  `overflow: hidden` 切掉。改成 `border-box` 后高度真正受卡片约束。

### Changed

- **投投浮窗按卡片尺度重排。** 整页助手的尺度(24px 的大标题、104px 高的输入框、消息区 4px 的横向留白、  
  320px 的消息区下限)在 760×680 的浮窗里既挤又糙:现在正文离面板边框 24px、行宽上限 600px,  
  空态四层的间距拉开,输入框收成 72px,历史抽屉改为带背板并可点空白或按 Esc 收起。  
  只作用于浮窗,整页助手观感不变。

### Dependencies

- **前端三大件连跳大版本:antd 6、React 19、Vite 8(Rolldown 内核)。** UI 库迁移到 antd 6.6.5  
  (图标、弹层、表单、通知与分隔线等 API 按官方指南全面更新,List 系列表改用 Listy 并沉淀共享组件),  
  运行时升到 React 19.3.0,构建链换用 Vite 8 的 Rolldown;静态检查升到 ESLint 10 +  
  TypeScript 6.0.3(配套 typescript-eslint 8.71、react-hooks 7.1.1),测试链升到 Vitest 5 +  
  jsdom 29 + jest-dom 7,@ant-design/icons 升到 6.3.4。行为以"等价迁移"为准:全部前端用例与  
  后端测试通过;顺带修复了生成简历完成提醒可能在预览阶段被误升级为居中弹窗的一处竞态。
- **后端依赖同步升级。** pypdf 6.19、uvicorn 0.54、pydantic 2.13、python-multipart 0.0.32、  
  python-docx 1.2、websocket-client 1.9.2、alembic 1.20、starlette 1.7、ruff 0.16.9,  
  以及 fpdf2 2.8.9(2.8.8 起对 CID 字体的嵌入方式变化已在测试侧适配,导出内容与外观不变)。

## 0.15.0 - 2026-10-01

### Added

- **思考强度支持自定义档位。** 求职助手与设置页的「思考强度」都多了一项「自定义…」,可以自己填  
  服务商支持的档位词(`xhigh`、`max`、`minimal`…)——各家词汇不一样,固定几档总会漏。自定义值原样  
  发给 OpenAI 兼容接口;Claude 原生协议下**填数字=思考预算 tokens**,填其他词按默认档处理(不会  
  报错,也不会静默变成"不思考")。同时修掉一处会让自定义值失效的行为:后端此前会把"不在候选表  
  里的档位"直接丢掉,那正好把用户自己填的值吃掉了。
- **大模型配置新增「思考模式」,并能检测当前服务商支持哪种写法与档位。** 开关默认关闭,关着时  
  请求体与之前逐字节一致(各家对不认识的参数处理不同:有的忽略、有的直接 400);开启后作用于  
  **除求职助手以外**的 AI 调用,求职助手保留它自己的「思考强度」,两者刻意分开。上游没有"查询  
  思考能力"的接口,所以「检测思考支持」按内置厂商表给出候选档位,并**实发一次最小请求**分辨  
  "真的生效 / 被静默忽略 / 被上游拒绝"三种结果(按钮旁写明会计入用量);上游拒绝思考参数时会  
  去掉它重试一次并记日志,不会让其它 AI 功能跟着失败。高级调整里另有「思考参数形态」用于中转站  
  与自建网关。新增 `llm_config_record` 的 `thinking_enabled` / `thinking_effort` / `thinking_style`  
  三列(只加列、有默认值,旧库自动补齐)。**行为变化**:求职助手不再继承设置里的「思考预算」,  
  它的思考完全由自己的强度决定。
- **网申专用浏览器悬浮球支持当前页面自动填写。** 仅处理已识别且为空的字段,按字段逐项写入并显示进度,不自动提交表单;关闭辅助填写后会立即移除页面侧边提示。
- **简历预览版式调整更可控。** 两页或三页简历改为横向 A4 分页预览,字号可调范围扩展到 11–18px;版式调整先作为草稿,可选择保存修改或取消修改。
- **网申资料统一按通用资料使用。** 历史场景 / 本次档位仍兼容读取但会归一为通用;性别及“是否”类资料改为选项字段,填表匹配可处理对应单选或下拉选项。
- **网申辅助操作名称与专用浏览器标签管理更清晰。** 统一使用“智能逐项填表”和“未识别字段智能辅助”等名称;页面标签可展开查看,超过可见数量时会提示剩余页面数。
- **专用浏览器当前页自动填写减少等待。** 页面填写通过一次批量操作完成,进度仍按字段顺序展示,避免每个字段重复执行完整浏览器调用。
- **网申资料支持逐条补充。** 教育、实习和工作、项目、校园和社会实践、  
  学术成果、获奖、语言、证书、技能、紧急联系人、作品和社交账号可新增多条记录;空记录不保存。  
  网申清单还补充曾用名、户口所在地、生源地、婚姻及健康状况、面试与笔试城市等单值字段。  
  附件说明只存文字,不替用户上传文件。网申填表合并读取两区资料,生成简历仍只读简历资料。新增  
  `web_form_profile_record` 表,旧版客户端不传  
  `repeated` 时保留该表数据;升级前建议备份数据库,回退迁移将移除新表及其中的补充记录。
- **岗位广场支持批量 AI 适配度分析。** 选择多个岗位后,应用结合当前个人资料与简历逐一分析,  
  按匹配度参考分排序展示,并将完整结果保存到「分析记录」供历史回看;模型未配置时明确显示本地降级,  
  单个岗位失败不会吞掉同批次的其它结果;还可选择后台运行,跨页面显示进度并在完成、失败或取消时通知。  
  结果快照落在新增的 `job_match_batch` 表(只加表、带时间索引,旧备份可直接导入)。
- **专用浏览器新增地址栏与网址记录。** 在「网申填表」页输入网址即可在专用浏览器里打开新标签页  
  (不再先加载 `blank`,也不会覆盖你已经打开的页面);用过的网址按最近使用排序保留 50 条,带页面  
  标题,可一键复用、也可单条删除。只接受完整的 `http` / `https` 地址。新增 `web_form_url_history`  
  表(只加表,旧备份可直接导入)。
- **侧栏模块可以按需隐藏。** 设置 → 应用 → 「界面与导航」:把用不到的模块关掉,侧栏只留你实际用的  
  那几个入口,一键「恢复默认显示」还能全部还原。**隐藏只影响入口**——不删数据、也不禁止直接访问  
  对应页面(隐藏的页面在你正浏览它时仍会出现在侧栏)。首页、岗位广场、简历中心、我的资料、投递台  
  与设置**始终保留**,所以不会把自己锁在外面;这条约束在后端也过滤一次,不只是界面上的灰按钮。  
  隐藏偏好随数据集一起导出与备份。新增 `GET/PUT /api/settings/navigation`。

### Fixed

- **点「退出」现在真的会把前端进程也停掉。** 原先后端是用"分离进程 + 输出丢弃"的方式去调停止  
  脚本的:分离进程没有控制台,而 Windows PowerShell 是控制台宿主程序,脚本很可能压根没跑起来,  
  于是退出后 vite/node 仍在跑、5173 仍被占用(刷新页面会回到一个"半死"的界面),而后端日志里  
  一行线索都没有。现在改用隐藏控制台(`CREATE_NO_WINDOW`)启动并**同步等它跑完**,把退出码与  
  脚本输出写进日志;任何失败都被吞掉并记警告——退出路径绝不允许抛异常,否则会变成"前端收到  
  202、后端却不退"。**macOS 也一并补齐**:那条分支此前直接返回,点退出根本不会停前端。  
  浏览器不允许脚本关闭"不是自己打开的"标签页,所以退出后仍需手动关掉标签页(界面会给出提示)。
- **按 2026-10 的现状更新模型预设与思考能力表。** 快速预设里有两个模型名**已经下线**  
  (DeepSeek 的 `deepseek-chat` 于 2026-07-24、月之暗面的 `kimi-k2-0711-preview` 于 2026-05-25,  
  继续调用会直接 404),另有一批停在上一代(豆包 1.6、智谱 glm-4-plus、MiniMax-Text-01、  
  `gpt-4o-mini`、`gemini-2.5-flash`、`grok-3-mini`、Claude sonnet-5)——都已换成各家当前在售的  
  型号;通义千问继续用官方长期保留的 `qwen-plus` 档位别名。思考能力表同步重写:DeepSeek V4 与  
  通义 Qwen3.5+ 改成"用 thinking / enable_thinking 开关,**默认就思考**",OpenAI 的  
  `reasoning_effort` 按代次分开(GPT-5.2 起多了 `none` / `xhigh`),并补上 MiniMax 与 kimi-k3  
  (思考常开、关不掉)的说明——原先那条"DeepSeek 不接受思考参数"与实测结果相反。  
  随之而来的行为变化:对**默认就思考**的模型,「思考模式」关闭时会**显式发送关闭参数**;此前  
  "关闭"只是不发参数,而这类模型不发就是开着,界面等于在说假话。
- “智能逐项填表”的资料选择侧栏按分组稳定着色并重新排版。只有包含多条子项的大类会获得专属颜色,同一大类的分组标题、跳转标签和资料标签保持一致,不同多子项大类不会复用颜色;单条大类统一使用蓝色。资料条目改为“字段名/分组 + 值”的两层布局,长字段名最多两行、长值最多三行,“填入”按钮固定在右侧,搜索和折叠后颜色不会漂移。跳转标签进一步收敛为低饱和底色、色点和数量胶囊,卡片按钮、搜索框、资料行与焦点态统一了间距和反馈。
- **网申表单的联动原生下拉不再因首次读取只有占位项而直接放弃。** 读取到明确的联动标记时,  
  填充阶段会在父级选中后重新读取当前选项,并按目标页面实际的 `value` 严格匹配;普通的  
  「请选择」仍不会被误当成可填写项,自定义弹层/级联组件仍要求用户手动选择。
- **「换个资料…」与「记住这条」的查找层级更清楚。** 分组标题和跳转胶囊使用稳定的颜色区分,  
  「记住这条」默认只展开「网申资料 · 自定义」,其他分组折叠;搜索时临时展开命中分组,  
  清空搜索后恢复用户原来的折叠状态。
- **分组跳转条不再盖住下面的资料,同类分区聚成一张卡片。** 分区一多(真实资料库约 50 个分区),  
  跳转条会换行铺到十几行,而它挂在吸顶的搜索块里,于是把「可能是这几个」和完整资料清单**整片盖住**、  
  滚也滚不出来。现在整条可以折起来(分区多时默认折成一行,写明「多少组 · 多少条」),展开后的胶囊区  
  高度按资料区可见高度倒推、**保证资料区至少剩一半**;「荣誉奖项 1 / 2 / 3」这类同类分区聚成一张卡片,  
  点开才是各个成员,当前所在的那一族会高亮并自动滚进可见范围。面板资料区改为按面板剩余高度布局,  
  窗口偏矮时最后几行不会再被面板裁掉。
- **专用浏览器进入流程更顺畅。** 有目标网址时直接打开目标页,不再先加载 `blank`;同一目标的并发打开  
  会复用已有页面并受到打开锁保护,避免岗位详情点「前往投递」时反复弹出多个浏览器窗口。
- **网申悬浮球与投递台状态栏修复。** 悬浮球只注入专用浏览器页面,支持长按拖动并在刷新 / 跳转后恢复,  
  去掉多余的设置图标;投递台保留「调试端口 9333」,加载时显示稳定的「正在刷新」,不再与「读取中……」反复交换。
- **2026-09-30:专用浏览器当前页面自动填写不再卡在「正在填写中」。** 页面刷新或跳转后会重置自动填写请求序号,  
  新页面第一次点击可以正常派发;实时轮询与后台写入会串行使用 CDP 通道,避免响应串线;后台读取或写入失败  
  会明确回显为失败状态,不会永久停留在进行中。
- **发行版多挂一个全平台包,让**已经装在旧版本上的用户**也能从应用内更新。**  
  应用内更新挑包的那段逻辑跑在用户机器上那份**旧代码**里,而它按文件名排序取第一个 `.zip`  
  ——`-macos.zip` 排在 `-windows.zip` 前面,所以 0.14.2 及更早的用户点「重启并安装」必然  
  下到 macOS 包、必然报「更新包缺少 start.cmd」。这段代码在旧版本上修不了(用户手上就  
  是它),但**改发行版的资产名可以**:多挂一个 `ResumeForge-<版本>-all.zip`,它排在最前,  
  老用户的旧代码会选中它,而它确实装得上。新版本自己优先选带平台词的包,不受影响。
- **应用内更新在 Windows 上从「必定失败」修成能用。** 发行版挂的是  
  `ResumeForge-<版本>-windows.zip` 与 `…-macos.zip` 两个包,而挑包逻辑是"按文件名排序取第一个 `.zip`"  
  ——`macos` 排在 `windows` 前面,于是 Windows 用户永远下到 **macOS 包**,那个包里只有 `start.command`、  
  没有 `start.cmd`,校验直接报「更新包缺少 start.cmd」。界面上按钮摆着,一点就错。现在按**当前平台**挑包  
  (不依赖附件顺序),并优先选带平台词的包、其次是不带平台词的通用包;真的没有适用于本机的包时如实说明  
  「这个版本只上传了 macOS 安装包」,而不是给一个必然失败的按钮。**顺带核对发布时旁边的 `.sha256`**:  
  下载完先比摘要再安装,挡住下载被截断或被改动的情况。
- **更新终于会把 `backend/app/data/` 一并更新。** 排除规则原本是"任意层级下名为 `data` 的目录",  
  于是受跟踪的程序资源(`skills.json`、`ats_keywords.json`)被静默跳过——**新版本往那里加的文件永远  
  到不了老用户机器上**,随后 preflight 会以"缺文件"拒绝启动。现在排除规则按**相对项目根的路径**判断:  
  `backend/data`(用户数据)绝不动,`backend/app/data`(程序资源)正常更新。macOS 的 `update.sh`  
  是同一处 bug,一并修掉。
- **更新程序文件之前先停掉正在运行的应用。** 手动 / 压缩包那条路径原本是**先覆盖再停**:文件被运行中的  
  实例锁住时,`Copy-Item` 会在中途抛错,留下一棵"一半新一半旧"的树,而且没有回滚。现在改成先停再覆盖  
  (仍排在下载与解压之后——下载失败不该把你正在用的应用关掉);解压出来的目录必须真的含 `start.cmd`,  
  否则直接报错,不会"更新完成"地什么都没覆盖。
- **应用内更新不再谎报成功,失败也留得下线索。** 原先更新器以隐藏窗口启动、输出丢进 DEVNULL,后端  
  1 秒后**无条件退出**;前端更是在**点击安装时**就写下"已完成"的标记,于是安装崩了下次打开照样显示  
  「已更新」。现在:更新器把输出写进 `runtime/update.log`,把这次安装的状态(进行中 / 成功 / 失败 +  
  原因)写进 `runtime/update-status.json`;**只有 `/api/health` 报出来的版本号等于目标版本才算成功**;  
  更新器 30 秒内没起来握手,后端就**不退出**,界面如实告诉你"更新器没有启动"以及日志在哪。  
  另外修掉一处会让 `-StopPids` 永远传不出去的编码问题(进程记录是带 BOM 的 UTF-8,按普通 utf-8 读会  
  静默失败),它导致应用内安装时 Vite 还在跑着就被 `npm ci` 覆盖依赖目录,以 EPERM 失败。

### Changed

- **移除简历资料条目的参考文件功能。** 教育、实习/工作、校园经历和项目经历不再显示参考文件上传或文本入口,  
  新增记录也不会再创建这类字段;旧数据库列和历史数据暂时保留为兼容,不主动删除或覆盖。
- **「网申专用资料」并入统一的「网申资料」模块。** 原有的国家和地区、证件、出生日期、家庭信息、  
  期望薪资、导师、研究方向等字段不再显示在简历资料的基本信息分区,旧数据兼容回读;  
  字段 key 与网申填表取值保持兼容。
- **「我的资料」拆成两个横向板块。** 「简历资料」与「网申资料」不再纵向堆在同一页,默认先打开简历资料,切换标签即可填写网申资料;两块仍共用编辑、取消和保存流程,切换时不会丢失未保存的内容。简历生成只读简历资料,网申填表合并读取简历与网申补充资料。
- **网申填表的日期与选项继续以目标页面写法为准。** 文本日期会参考占位符/当前值的格式,  
  原生日期下拉会匹配日期语义后写入页面自己的选项值;记忆选择器的通用 / 场景 / 本次档位  
  也使用颜色区分,并保持可键盘操作。
- **全局导航与首屏加载优化。** 点击左上角品牌图标回到首页,头像直达「我的资料」,页头数据集可直接切换;  
  侧栏页面改为按需加载、悬停预加载并在空闲时预热,首次进入未访问页面无需再长时间等待代码块。
- **再次启动时终端会说明"正在载入",不再像卡住。** 依赖装好之后再次双击 `start.cmd`,终端在  
  「探测依赖 → 等后端就绪(最长 90 秒)→ 等前端就绪(最长 120 秒)→ 预热前端页面」这几段里原本  
  **一行字都没有**——最容易让人以为卡死或启动失败。现在会先说明"依赖已经装好、这次不用重新装",  
  再在每段等待前报一句"正在等待后端/前端就绪(最多 N 秒)"。另外「正在准备前端页面…」原本排在  
  取页面**之后**,最该被解释的那段等待反而是静默的,已挪到前面。macOS 侧补上同样的两行等待提示  
  (它已经有"启动后端/前端"的行)。

## 0.14.2 - 2026-09-29

### Fixed

- **新解压的一份代码不会再"沿用"别的目录里正在运行的简历通。** 启动器原来只看端口上的  
  健康检查:**任何一个**合格实例应答就算"已经在运行",于是刚解压的这份会直接跳过自己的依赖  
  安装,浏览器打开的是别人(或上一次运行留下的残影)——用户看到的是"页面一片空白、依赖好像  
  没装"(2026-09-29 一台虚拟机上的反馈,本机也复现过:另一个目录的实例占着 8005/5173,  
  新目录一份代码都没跑起来、却提示"已就绪")。

  现在"是不是自己人"按本目录 `runtime\*` 里的进程记录判断,PID、启动时间、命令行三者都对得上  
  才沿用;对不上就如实说清当前目录是什么、端口上那个是谁,并给出两条出路(关掉它 / 换端口启动)。

- **打开浏览器之前先把前端页面准备好。** Vite 的 dev server"健康"只说明它在监听:真正让页面  
  渲染出来的是随后取入口模块(`index.html` 里的 `/src/main.tsx`)这一步,而它要现做一次依赖  
  预打包——本机冷启动实测单次取入口 **>10 秒**(几百个包,慢机器与开着杀毒软件时会更久)。  
  启动器原本在这一步之前就把浏览器打开了,那段等待全部落在用户眼前:一片空白,看着像启动失败。

  现在开浏览器前会先取一次首页与入口,把等待挪到「正在准备前端页面(第一次要把几百个依赖  
  打包好,慢机器上要等一会儿)」下面;取不到不算失败,只提示一句,浏览器打开时还会再取。  
  入口路径按 `index.html` 里的 module 脚本取(Vite 注入的 `/@vite/client` 会被跳过,  
  它不是应用入口)。

- **CI 增加 `windows-end-to-end`:从干净检出走完整启动链。** 之前只有 macOS 有端到端作业,  
  Windows 侧只有启动器的单元测试——"包本身能不能在新机器上跑起来"一直没人自动验证,  
  上面两个问题都没被 CI 拦下。新作业建 venv、装前后端依赖、起 uvicorn 与 vite、查两个健康  
  端点,并且**真的取一次前端入口模块**(端口活着不等于页面能用)。

## 0.14.1 - 2026-09-29

### Fixed

- **手动更新不再因为"应用还开着"而失败。** 双击 `update.cmd` 时如果简历通还在运行,  
  `npm ci` 会删不掉被前端占着的 `node_modules\@esbuild\win32-x64\esbuild.exe`,以  
  `EPERM: operation not permitted, unlink ...` 中断。用户看到的是一页 npm 日志,  
  看不出"关掉应用就能好"(2026-09-28 用户反馈复现)。

  应用内「检查更新」本来就会先等应用退出、再按传进来的 PID 覆盖文件,手动双击那条路径  
  却完全没有这类机制。现在手动更新在同步依赖前会先停掉记录在案的实例——走的是和  
  `stop.cmd` 完全相同的一份实现(`ResumeForge.Common.ps1` 里按 PID + 启动时间 + 命令行  
  三重校验),记录对不上就跳过,绝不误杀;`-ArchivePath` 的应用内路径不做这一步,  
  否则 `taskkill /T` 会把正在运行更新器的那个后端连进程树一起杀掉。万一仍被别的东西  
  占着(杀毒软件也会占同一批文件),失败信息会直接写明"先双击 stop.cmd 关掉应用再运行一次"。

  macOS 侧不受影响:Unix 允许删除正在使用的文件,`update.command` 不会遇到这个错。

## 0.14.0 - 2026-09-28

### Added

- **不再往"只能点选"的框里写值。** `readonly` 与"点开会弹层"两个信号以前**根本没采**,  
  于是「当前所处地」这类**级联选择器**(省 / 市 / 区)看起来就是个普通输入框,程序会往里  
  写一个值——框里出现一行字、看着像填上了,而组件内部状态一点没变,表单交上去还是空的。  
  **那比"没填"更糟**:用户以为这项已经好了。

  现在两个信号都采回来,这类控件归进「永不自动填」并写明"这是个只能点选的选择控件  
  (值不能直接写进去),需要你在页面上自己点选"。与"下拉里只有占位项"那条是同一类处置  
  ——宁可如实说要你自己点,也不假装填上了。原生 `<select>` 不受影响。

  **顺带说清一件本来就成立的事**:下拉框一直是**照着页面自己的写法**填的。资料里存「天津」、  
  表单那一项写「天津市」或「天津 」,程序按 **精确 → 别名 → 包含** 三级去对,对上哪个填哪个  
  (省/市/区这种多级,每一级各自照自己那一级对);两个选项都沾边时报「有 N 个选项都对应…」  
  交给用户选,不猜。

- **点单选框/复选框时,上一个框的面板会被收掉。** 注入脚本遇到"不填"的类型只 `return`,  
  而 `return` 不会关掉**已经显示**的面板。实际用起来是这样:先点「姓名」弹出"将填入:张三",  
  再点「性别」的单选框,**那条提示还挂在屏幕上**,看起来就像程序要把姓名填进性别里。  
  现在收到不填的控件会收面板,并把焦点序号 +1,让 ResumeForge 页面上的状态一起清掉。

  顺带清掉了两处**按控件类型一刀切**的拦截(`build_preview` 里新增的那条、以及 `suggest_for`  
  里原有的那条)。它们与 `engine.skip_reason()` 的判据重复且更宽——后者已经精确挡住了  
  同意项与声明项并给出各自的理由,前者只会把性别这种**能填的事实类单选**也一并挡掉。  
  「挡哪些」这件事现在只有 `skip_reason` 一处说了算。

- **网申页的浏览器状态栏修了三处**,每一处都会让人以为"这功能坏了":
  - **状态永远停在「未启动」、加载图标一直转**——根因是**状态接口比轮询间隔还慢**:  
    实测接口要 **2.0s**,而前端每 **1.5s** 问一次。于是 `useApi` 里那个防乱序的版本号  
    守卫把**每一个响应都判成过期丢掉**:数据永远不落地(界面停在初始的「未启动」)、  
    `setLoading(false)` 永远不执行(转圈不停)。**接口越慢越稳定复现**。

    两头都改:前端改成**自定步**(这次问完再排下一次,接口慢只会让刷新变疏);  
    后端把探测里的 `trust_env` 关掉(原先会去读环境代理,而**本机回环不该走代理**——  
    用户机器上真配了代理时它还会直接探失败)并把超时从 1.0s 收到 0.6s,单体实测  
    0.44s → **0.15s**。

  - **跳转条点几次之后"失灵",点了不跳**——`jumpTo` 原先用 `scrollIntoView` 对齐之后  
    再减一次 sticky 高度,而那一步在**靠后的分组**上会把已经夹到最大值的滚位往回挪,  
    那一组反而被推到可视区下方。实测点「分组13」停在距顶 **530px**、容器才 560px,  
    组头下面的条目**全在折叠线以下**,看着就是没跳。改成按视口相对位移算,且只改  
    `.pick` 自己的 `scrollTop`(`scrollIntoView` 还能滚到根滚动容器上去)。
  - **「专业技能 1 2」被读成「专业技能12」**——分组名以数字结尾是常态(「专业技能 1」  
    「教育经历 1」),条数拼进同一串文本必然歧义。条数改成独立的小胶囊元素,  
    并在 `title` 里写全「跳到「专业技能 1」(2 条)」。
- **浏览器那一栏的三个按钮:职责写清楚了,也不再能并行点。** 三件事各管各的——  
  **起停**(启动/关闭浏览器)· **配置**(浏览器设置)· **重查**(刷新状态)。

  原先设置弹窗里写着「回到页面点『**重启浏览器**』才会生效」,而页面上**从来没有这个  
  按钮**(那个位置叫「关闭浏览器」)——照着找必然找不到,这正是"职责说不清"的来源。  
  现在点名真正的两个按钮,三个按钮各带一条说明自己管什么的悬停提示。

  另外三个按钮**共用一个 busy**:任何一个在跑,其余的都禁用。原先只是各自转圈,  
  可以"关闭还没回来就点启动"。投递台那栏(`BrowserStatusBar`)有同样的毛病,一并修了。

- **「换个资料…」再快一步:按你此刻在填的框,把最像的几条顶到最上面。** 上一版加了跳转条  
  与折叠之后,实测还是不够快——扁平分组就有 **11 个**,加上按条分组的「教育经历 1」  
  「实习/工作 2」…**十几个**,而面板只露得下八九行。更根本的是:**分组是错的主轴**。  
  用户想的是「我要找手机号」,不是「我要找联系方式组」——先选组再扫条目是**两次决策**。

  现在面板顶上多一栏 **「可能是这几个」**:按**当前这个框的文字**(标签、占位符、旁文)  
  从完整清单里挑出最像的几条。判据**复用 `engine.evidence_key` 与同一份 `FIELD_SYNONYMS`**,  
  没有另写一套文本相似度——这个仓库吃过"两份匹配实现各自分叉"的亏(`recognize_field` 与  
  `_rank_controls` 曾经各写一份,同一个框在批量预览和实时面板里认成两个字段)。  
  子表里有几个字段(工作内容、项目描述、担任角色)没有同义词表,退回按标签本身比。

  **它只排顺序、不做取舍**:没进前几名的条目仍然在下面的完整清单里,一条都没少。  
  所以判错一条的代价只是"多看一眼",不会让人找不到东西——这也是这一栏能被接受的前提。

  顺带修了两处版面:**跳转条改成换行铺开**(原先横向溢出,用户得先横向滚找到分区再点,  
  和纵向滚着找条目是同一种累),以及**按钮被挤成竖排两行**(提示文字与按钮挤在同一个  
  flex 行里,而按钮默认可被压缩;`.n` 的 `margin-top` 本来就写明提示该独占一行,  
  是 markup 把它放进了 `.r`)。

- **「换个资料…」那一屏重做:能跳、能折、能搜到、长名字不再撑乱排版。** 起因是实际用下来  
  的反馈——资料一多,只能在面板里一路往下滚着翻找;单条字段名过长时没有约束,一行会被撑乱。

  改法是给这一屏加几个帮手:
  - **分组跳转条**:搜索框下面横排每个分区一个按钮,带这一区的条数,点一下直接跳过去。  
    (面板最宽 620px,仍装不下左侧目录,所以跳转条是横着的。)
  - **每组可折叠**:点分区标题折起来;折叠状态**跨重绘保留**,不会因为你搜了个字就全展开。
  - **命中数**:搜索时写上"命中 N 条";一条都没有时直说,不留空白。
  - **长内容单行截断**:字段名与值都截断,完整的那条挂在 `title` 上。
  - **每条一个「填入」按钮**(整行点击照旧),字段来源是「网申资料」的另带一个小标记。
  - **键盘可用**:整行与分区标题都可聚焦,回车即填入/折叠。
    面板最宽 620px,所以这一屏没有做左侧目录(那是原始设计里写的形态),改成了顶部横向跳转条——  
    目标(一键跳转)一样,但不吃列表宽度。

- **「记住这条」的落点收成「网申资料」一种,改不到简历资料了。** 上一版这个按钮可以按  
  识别到的字段直接把值存进「我的资料」(基本信息、教育、实习、项目…),默认落点也是那里。  
  现在**只写「网申资料」**:网申资料里已有该字段就更新它,没有就按当前字段名新增一条  
  自定义字段;面板内用小字提示实际保存位置。

  收掉的理由有两条,都不是"少个功能":
  1. **简历资料会被生成进简历正文。** 在别人的网申页面上顺手按一下,就改掉了自己简历里的  
     内容——而简历是要投出去的。用户要的是"下次填表能自动填",不是"我的简历被顺手改了"。
  2. **触发者不完全可信。** 落库的触发是页面里的 `window.__rfRemember`,而那个全局变量  
     **页面上的任何脚本都能写**。目标集里一旦包含 `profile:*`,就等于给每个网申页面开了一条  
     改你简历资料的通道。
     这是**结构性**的收口,不是界面上藏起来:`services/webform/profile_targets.py` 里已经  
     没有能改 `UserProfile` 的代码,伪造的 `target_id` 也落不下去。测试从服务层和接口层各钉了  
     一条,并把"整份简历资料逐字未变"一起断言。

- **网申填表的浏览器状态与逐框填表体验完善**:网申页和投递台现在都会在打开后立即读取浏览器状态并持续轮询,浏览器被直接关闭后会自动显示为未启动,也提供「刷新状态」按钮。浏览器启动后「点哪个填哪个」默认开启,只在文本、日期等可输入控件上出现;单选、复选、下拉和附件控件不会弹面板。面板加宽、分组、模糊搜索、拖动和关闭按钮;「记住这条」默认落到「网申资料」里对应的字段上,未识别的字段按当前字段名新增到网申资料自定义里,并在面板内用小字说明保存位置,不再默认弹出大型确认框。
- **我的资料中的网申资料整理**:移除「只存本机」逐条标签、驾照类型及所有是否类目录字段;保留统一的本机隐私说明。网申资料支持在编辑态手动新增自定义字段;「换个资料…」会展示基本信息、教育、工作、校园、项目、专业技能、荣誉、网申资料及已有自定义字段。基本信息模块底部空白和经历项目的补充总结模块一并移除。
- **网申资料自定义字段可维护**:在「我的资料 → 网申资料」编辑态可以直接修改自定义字段名或删除字段;修改只更新显示标签、保留原有 key 和已记住的值,删除会在保存后同步移除实时识别目标,避免旧字段继续被推荐。新增自定义字段不会清空同一轮编辑中尚未保存的其他输入。
- **「网申填表」:每个框旁边都能「记住这条」,还能记清单元外的自定义字段。** 上一版的学习  
  是**批量模式、填完统一弹窗**:填完才算候选、弹一条汇总提示、勾选后保存。这一版加了一个  
  **逐框**的入口,并把**范围**放开到清单外。

  **时机**:「点哪个填哪个」的面板上,「填入」旁边多了一个「记住这条」——用户正看着哪个框  
  就记哪个,不用等填完。**认得出、但资料里没值的框也能记**(`missing_data` 那类,上一版  
  刻意排除在外):就地按一下就把这个值补进「网申资料」,比跳去另一屏录一遍顺手得多。  
  这是用户明确要的——「认得出的资料里没值的就填到那个值里面」。

  **范围**:允许记**清单元外的自定义字段**(某家特有的「导师姓名」「实验室」)。这**推翻  
  了上一版一条刻意立的边界**(`learnable` 只收 `EXTRA_FIELD_KEYS`),所以改的时候是当  
  "打开一个明确命名的口子"来做的,不是顺手放开。

  **一个必须先说清的边界:能存,但大多不能自动填。** 自定义字段**只有标签本身一个信号**  
  (内置字段每个有五到十个同义词 + autocomplete 档位),所以不做模糊或子串猜测,避免把值  
  **悄悄写进别的框**。实时逐框建议只有一个严格例外:

  | 学到的                                                                                     | 下次自动填           | 怎么做到                                    |
  | ------------------------------------------------------------------------------------------ | -------------------- | ------------------------------------------- |
  | 标签规范化后与唯一预设字段显示名完全相同                                                   | **能(仅逐框建议)** | 忽略空白和标点后精确相等                    |
  | 真正目录外的(某家特有的「意向事业部」)                                                   | **不能**             | 存得下、看得见、能手填、「换个资料…」搜得到 |
  | **不新建"运行时同义词表"**:一份在 `fields.py`、一份在库里,两处都叫同义词,日后必然       |
  | 分叉(这个仓库反复踩过:`recognize_field` 与 `_rank_controls` 各写一份,同一个框在批量     |
  | 预览和实时面板里认成两个字段)。除上面的唯一标签精确对应外,自定义字段不参与通用自动匹配。 |

  **数据**:`web_form_profile_entry` 加一列 `label`(迁移 `0031_extra_profile_label`),  
  存**用户看到的字段名**。目录内的 key 它的值等于 `FIELD_LABELS[key]`;自定义 key 靠它显示  
  ——否则界面上是一串 `CUSTOM_导师姓名`。**空串 = 从目录取名**(存量行都是这样),所以  
  **不加数据迁移**。自定义 key 用 `CUSTOM_` + 标签规范化的前缀(不是随机 id),因为这个  
  仓库的标准是"库里能看得出含义"。

  **写入与读取的口径必须一起放宽。** `save_entries` / `list_entries` / `list_details`  
  现在共用同一个判断 `_accepts()`(目录内的,或 `CUSTOM_` 前缀)。改一处忘另一处的失败  
  方式是**静默的**——存进去了、读不出来、没有任何报错——所以这两半**各配了一条测试**。

  **`remember()` 与 `save_entries()` 是刻意的分工**:后者是"这一屏就是全部"的整份覆盖,  
  填表过程中用它会把用户这一屏之外的资料全删掉;前者只动一条。

  **顺带修了一个真机 bug**:腾讯校招那类页面相邻四个框(导师 / 实验室 / 研究方向 / 论文)  
  的旁文里全是这四个词,最长同义词比较让「研究方向」偷走了「导师」。补了  
  `laboratory` / `paper` 两个目录字段与同义词(**刻意不收「研究」「发表」**——它们太泛,  
  会把别的框也吸过来),并用一条回归测试钉住那四个框各自的归属。

  **边界不变**:学来的和手动录的一样只存本机、不进简历、不进分享包、助手读不到。  
  **自定义字段也补了"不进简历生成"的断言**——它和目录字段住在同一张表里、同一条边界管着。

- **「网申填表」:填过的、简历通里没有的字段,主动问你要不要记下来。** 以前每换一家公司  
  都要重新填「招聘信息来源」「内推码」「是否服从调剂」这类只在填表时才想得起来的栏目。  
  现在填完一次表,如果这一轮填进去的字段里有简历通里本来没有的,页面弹一条汇总提示  
  「本次填的 N 项简历通里没有,要记住下次自动填吗?」——**默认全勾、默认「通用」**,  
  可在弹窗里逐条取消、逐条换档位。

  **三档**(`general` / `scenario` / `once`,落在 `web_form_profile_entry` 新增的  
  `reuse` 列上):**通用**与**场景**下次自动预填,**「本次」只记不填**——内推码、某家的  
  申请编号这类一次性的值,第二次自动填就是错的。档位在「网申资料」里随时可改,  
  清空那一格等于删掉,删掉后下次会重新问。

  **来源落在一列 `source` 上**(`manual` / `learned`):学到的那些在「网申资料」里带  
  「填表时学到的」标记,**AI 认出来的另标「AI 认的」**——它比规则更可能认错字段,  
  用户核对时的怀疑程度不同。这两样是功能的安全阀,不是装饰:值会下一次自动填进去,  
  所以「看得见、改得掉、删得掉」必须成立。

  **判据只有三条**,且都**结构上成立**、不靠额外判断:字段被程序认出来  
  (`source` 是 `rule` 或 `ai`)、字段**不在** `build_form_data(db)` 的返回值里、  
  值非空。第三条同时挡住两类东西——`source="profile"` 的简历资料字段永不会被学走  
  (学下来会让"学到的"冒充简历资料字段名),以及 `save_entries` 对目录外 key 的静默丢弃  
  (在**提案阶段**就过滤掉,而不是让它悄悄消失)。

  **"认得出但资料里没值"那类字段不进这批候选**,这不是靠额外判断,而是结构使然:它们落在  
  `preview` 的 `missing_data` 桶、不在 `items` 里,而学习只看 `items`。  
  (**2026-09-27 更新**:逐框的「记住这条」补上了这条路——见上面那条;两条链各自成立,  
  没有在 `learnable` 里混。)

  **同样结构上成立的是"不重复提案"**:一个字段一旦被记住,它下次就在  
  `build_form_data` 的返回值里了 → 不再进这个循环 → 提示不会变成每次填表都弹的骚扰。

  **边界不变**:学来的和手动录的一样只存本机、不进简历、不进分享包、助手读不到;  
  密码/验证码/他人信息走 `blocked`,根本进不了学习这一步。唯一不同的是这张表会**自动增长**,  
  所以三条边界这次都补了**真的断言**(简历生成取数入口、助手工具源码、分享包取数入口)。

- **「网申填表」:把资料填进公司自建官网的网申表单。** 腾讯校招那类系统的个人信息页  
  (姓名 / 性别 / 国家地区 / 证件类型与号码 / 手机区号 / 邮箱 / 当前所处地 / 微信号 /  
  教育经历…)不用每换一家公司就重填一遍:启动浏览器 → 在窗口里打开网申页 → 点「读取当前  
  表单」→ 逐条核对映射(可改值、可取消勾选)→「填充到页面」。

  **四条边界写死在实现里,不是提示语**:
  - **只填不交**。程序不碰任何提交类控件,填完由用户自己在页面上点提交;  
    `tests/test_webform_no_submit.py` 把这条钉成不变量(源码里出现 `.submit(` /  
    `requestSubmit` 直接红)。
  - **密码与验证码永不自动填**,连值都不读进快照。
  - **页面上已有的值默认不覆盖**,冲突行在预览里默认不勾选——那可能是用户上一轮填了一半  
    的草稿,覆盖在"点提交之前"是用户看不见的破坏。
  - **填之前先让用户核对**。字段匹配是启发式的("姓名"与"紧急联系人姓名"只差几个字),  
    负向词挡得住一部分但挡不全,所以这一步不是装饰。
    匹配是通用启发式(字段名/标签/占位符/邻近文本 + 别名表),**不写死任何一家公司**;  
    认不出的控件如实列出来。`docs/user-guide.md` 的隐私边界一节写明了它不做什么。

  **多段经历按页面区块序号填充**:实习、项目、校园、教育和获奖区块会读取标题中的类型与序号,  
  例如「实习经历-2」严格使用资料里的第 2 条,不再把第一条误称为第二条;成对日期还会结合  
  页面顺序区分开始与结束。程序仍**不点页面上的「添加」按钮**——点按钮是在改页面而不只是填值,  
  需要更多条时由用户先添加后重新读取即可。

  **三类永不自动填的东西**(都不是"还没做",是刻意的):密码与验证码连值都不读;  
  「我已阅读并同意」这类**同意项**——代勾等于替你做出法律意义上的同意;「无实习经历」  
  「至今」这类**声明项**——勾上是在替你陈述事实。

- **「点哪个填哪个」:点到哪个框,就在框旁边给出资料里对应的值。** 与上面的整页读取是  
  **两种并存的模式**,不是替代。

  它**不做预先快照**——点哪个框才在现场描述那个框、现场匹配。所以不存在"页面一联动、  
  序号就失效"的问题,而那正是整页模式最大的风险点(选了国家、省份下拉重渲染,后面所有  
  控件的序号都会错位)。另外它还能帮到整页模式填不了的框:自定义下拉会告诉你**该选什么**,  
  只是不替你点开。

  页面里只装一个焦点监听与一个提示面板(shadow DOM 隔离),**按钮不写值**——它只把"用户  
  点了填入"记下来,真正的写入走 Python 侧与整页模式**同一条**路径(同样的哑执行器、  
  同样的回读校验)。这样写入的实现只有一份,不会分叉。停用会把监听、面板、标记与全局  
  变量**全部撤掉**,不在别人的页面上留东西。

  面板同样遵守上面那三类"永不自动填":验证码、同意项、声明项、他人信息只说明原因,  
  **不给「填入」按钮**。

- **「换个资料…」:认不出来也不用卡住,你自己从全部资料里挑。** 面板上多了一个入口,  
  点开就是你填过的**全部**资料——按分区列出(身份信息 / 联系方式 / 教育经历 1 / 实习工作 2…),  
  带搜索框,点哪条就填哪条。

  这一步的意义不只是"兜底":**它把整个"猜这个框想要什么"的问题绕开了**。前面那些  
  同义词表、区块限定、`autocomplete`、往上找标签,全都是在猜;猜错就会填错(本项目实测  
  过三次)。让用户自己挑,就不存在猜错——**匹配器从"决定者"降级成"排序器"**,  
  它的失败代价从"悄悄填错"变成"排序不够准",后者一眼就能发现。

  顺带解决两件此前明确没做的事:
  - **多段经历的第二条**。清单里逐条列出("实习/工作 1"、"实习/工作 2"…),点到第二个  
    区块时直接挑第二条即可——不需要识别页面上的「添加」按钮,也不需要判断区块。
  - **认不出标签的框**。这条路只需要知道"用户点了哪个元素、它是什么类型",**不依赖标签  
    能否提取**,所以字节那种把输入框埋六层的组件库也照样能用。
    展开状态**跨框保留**(连着几个框都想自己挑时不用每次再点一下),搜索词则随换框清空。

- **识别时优先读 `autocomplete` 属性**。它是 HTML 规范里的字段类型声明  
  (`name` / `tel` / `email` / `bday` / `organization` …),也是**唯一一个不需要猜的信号**  
  ——Chrome 的自动填充同样把它当第一优先级,没有才退回启发式。现在它的权重高于"控件自己  
  说的"和"旁边提到的":规范写得规整的表单,识别准确率会明显高一档;没写的站点完全不受影响。

  两处顺带的行为值得说明:
  - **站点自己声明"别自动填"的(`current-password` / `new-password` / `one-time-code` /  
    `cc-number` 等)直接拦下**,并在提示里写明是站点标注的——这比我们从标签猜更可信。
  - **`autocomplete="off"` 既不匹配、也不拦**,当"没有信息"。实测依据:字节简历页给 6 个  
    控件标了 `off`,其中就有「期望工作地点」「学历类型」——若把 `off` 当禁令,这几个框会  
    直接变得填不了。它绝大多数时候是对**浏览器自带填充**的表态,不是针对用户用自己工具填  
    自己的资料;Chrome 也把它当提示而非禁令。

- **网申专用资料字段**:`user_profile` 增加国家/地区、证件类型、证件号码、出生日期、  
  手机区号、微信号、QQ 号、政治面貌、籍贯、家庭信息、期望薪资、导师、研究方向、意向行业;  
  `education` 增加院系、学习形式与学位类型(网申表单把"学历"拆成三个独立下拉)。  
  **只存本机、只用于填表**——不进简历导出、不进发给模型的资料提示词(助手读资料走  
  `_LLM_PROFILE_FIELDS` 白名单,这几个键不在其中)。
- **网申填表:认不出的字段交给 AI 兜底。** 规则匹配是四档证据的启发式,遇到这张表特有的  
  栏目(导师、实验室、研究方向)就束手无策——实测腾讯校招那一页有 18 个控件落在「没认出来」。  
  现在这些控件会**用一次调用**交给模型,让它从字段目录里挑一个。

  这个功能的开关在「网申填表」页,**默认开**;没在「设置」里配模型时开关不可用并写明原因  
  (开了却毫无效果是最糟的,用户会以为坏了)。实时与批量两条链路都覆盖:批量是**一次调用  
  处理全部认不出的控件**,不是一个控件一次。

  **三条边界写死在实现里,不是提示语**:
  - **模型只决定"这个框是哪个字段",绝不决定"写进去什么值"。** 它的输出被硬约束在  
    `FORM_FIELDS` 的 key 集合里(目录外的 key 一律丢弃),取值、下拉项解析、日期格式化  
    全部复用规则模式那套纯函数。推论是**模型造不出一个值**——它能做错的最坏情况是选错了  
    字段,而那在预览表里一眼可见。
  - **模型看不到任何资料值。** 发出去的只有两样:页面上本来就有的控件文字  
    (label / placeholder / aria / autocomplete / name / options / nearby_text),  
    以及字段目录的 `key + 标签`。`identify_fields()` 的签名**不接受 `data` 参数**,  
    从类型上就堵死了这条路;`tests/test_webform_ai.py` 再钉一道(喂一份特征串很显眼的  
    假资料,逐个值去模型收到的消息里搜)。
  - **模型不进自动写入路径。** 密码 / 验证码 / 他人信息 / 同意项 / 声明类是**终局判定**,  
    `skip_reason` 先跑,**模型没有投票权**——顺序反了等于让模型有机会说服我们填验证码。  
    实时模式下每条候选都要用户自己点「填入」;批量模式下 AI 命中的行标「AI 建议」  
    且**默认不勾选**(另给一个「全选 AI 建议」)。
    **为什么 AI 行默认不勾**:规则命中至少证明页面上有字对上了,AI 命中可能纯粹是上下文推的。  
    默认不勾的失败形态是"我忘了勾",默认勾的失败形态是"悄悄写了个错值"——**后者正是这个功能  
    一直在防的那类**。这与既有的"冲突行默认不勾"同源。

  几处顺带的行为:
  - **认得出字段、只是资料里没填**的框**不会**问模型(`suggest_for` 原本把这种情况和  
    "压根认不出"都报成 `unmatched`,现在分开了)——去「我的资料」补一下就好,问了是白花钱。
  - 模型拿不准时可以对同一个控件给**最多 3 个候选**,实时面板上逐条列出、**每条自带  
    「填入」按钮**,就在输入框旁边。
  - 有**语义缓存**(同一段控件文字问过一次就不再问,跨会话保留)与**每会话 30 次的配额**;  
    到顶会如实说"次数已用完",不静默变成"没认出来"。
  - **模型挂了不影响可用性**:回落成今天的行为,预览照常打得开,面板上永远留着  
    「换个资料…」那条路。
    **已知限制(没有解决,如实记下)**:模型只在**规则认不出**时出手。规则"自信但认错"时  
    (例如「导师姓名」被同义词「姓名」抢走,于是建议填**本人**姓名)AI 不会来纠正——  
    `test_webform_live_ai.py::test_a_confidently_wrong_rule_match_is_not_rechecked_by_the_model`  
    把这个现状钉着,哪天改了触发策略它会红。

- **「点哪个填哪个」的面板:定位加固与拖动。** 面板本来就已经锚定在输入框旁  
  (读 `getBoundingClientRect()` 贴左下),这次补三件事:
  - **优先贴右侧**——用户刚点完输入框,手就在框附近;右、左、下、上依次退让,  
    且**任何情况下都不与输入框重叠**(挡住用户正要输入的框是最糟的位置)。
  - **跟得住页面的重排**。级联下拉选了国家才加载省份、校验错误提示插在字段上方、  
    折叠区展开——输入框位置变了,而页面**既没滚也没缩**,`scroll` / `resize` 一个都不触发。  
    只挂事件的话面板会停在陈旧的坐标上,看起来就是"按钮跑偏了"。现在显示期间每 200ms  
    对一次坐标,**坐标没变就什么都不写**(否则那点无谓的样式写入会让页面一直在重排)。
  - **可以拖走**。表单窄的时候贴边会盖住相邻字段,而"盖住了哪一块"只有用户自己知道。  
    拖动记的是**相对输入框的偏移**而不是绝对坐标——这样"挪开一点"和"换个框还贴着"能同时  
    成立;记绝对坐标的话拖过一次之后就再也不跟了。
- **「我的资料」里新增「网申资料」一区:专门给网申填表用的补充资料。** 用户提的:  
  "再单独列一项'网申资料',专门给用户记录一些网申时会用到的资料,越多越好……这一项要跟简历  
  资料分开,用于补充简历资料里没有的内容,专门供网申填表模块读取,**生成简历模块默认不读  
  这里的信息**"。

  校招网申表单问的东西**比简历多得多**:学号、培养方式、专业排名与总人数、档案所在地与  
  报到证抬头、紧急联系人与父母工作单位、入党时间、身高视力、可到岗时间、是否服从调剂……  
  这些在简历上**从不出现**,  
  以前没有地方录,只能每次填表时现找。现在分七组 43 个字段(学籍与档案 / 语言与证书 /  
  家庭与紧急联系人 / 党团信息 / 身体情况 / 意向与到岗 / 其他补充),收录判据是  
  "**至少两类企业反复问**"——只有个别企业问的定制项不收,否则一屏空框。

  **边界由结构保证,不靠约定**:新建独立的表 `web_form_profile_entry`(键值对,  
  **加字段不需要迁移**——清单在 `services/webform/fields.py`,加一行即可)。  
  简历生成读的是 `get_profile_detail()` → `UserProfile`,**完全不经过**这张表;网申填表读的  
  `build_form_data()` 才做合并。两条守卫真的去断言这件事  
  (`test_webform_extra_profile.py::test_the_resume_generation_path_never_sees_this_table`  
  与 `test_webform_api.py::test_extra_profile_is_invisible_to_the_resume_generation_path`)——  
  失败方式是**静默的**:一旦有人"顺手"把它并进 `to_profile_out`,简历正文里会多出四六级分数、  
  身高、父母工作单位,而没有任何报错。

  **助手读不到它**(理由记在 `test_assistant_coverage.py` 的 `COVERAGE` 里):里面含证件、  
  家庭与健康类敏感值,暴露给助手会让它们随提问进入对话上下文并发往模型服务商,与既有的  
  "只发字段名、不发值"边界直接冲突;而且工具化的读写在网申表单那条链路上已经有了。

  **填写跟随「我的资料」现有的编辑/保存流程**(同一个「保存全部资料」按钮),但数据是另一次  
  请求,所以**两次都成功才算成功**——第二次失败时明确说"个人资料已保存,但「网申资料」没存上",  
  不允许"第二次失败却提示保存成功"。

- **「填充记录」:每次填完留一条痕,可回看、可删、可恢复。** 网申填表此前"填完即忘"——  
  页面快照只在进程内存里活 900 秒,关掉就没了。用户填完一张长表(往往含证件号、手机号、  
  亲属信息),事后想确认"我当时到底填了什么、有几个没成、为什么"时无处可查。现在每次  
  填充落一条记录:填了哪些框、认成什么字段、**最终写了什么值**、成功/未验证/失败各几项,  
  外加填完后页面的可读快照。

  写入时机按模式分开:批量在拿到 `outcomes` 后落一条;实时模式在 `stop_live()` 时  
  **按会话汇总落一条**(不是每填一个框落一条,否则一次填表就是几十条噪声)。  
  记录失败绝不导致填充失败——`_record_fill()` 吞掉全部异常。

  **记录里含用户填进别人页面的真实值,所以它的隐私边界比别处更紧**:只存本机、  
  不进分享包、不进简历导出;那 5 秒快照进回收站而不是随手丢弃。助手**刻意读不到它**  
  (`tests/test_assistant_coverage.py` 里写明理由)——一旦暴露,证件号与手机号会随用户的  
  提问进入对话上下文并被发往模型服务商,与「只发字段名、不发值」的既有边界直接冲突。

- **「网申填表」页可换浏览器了。** 后端一直就支持 `自动 / Chrome / Edge / 自定义路径`  
  (`browser_manager`),但入口只在「投递台 → 投递设置」——网申页的浏览器是跟着投递台  
  走的,用户看不到、也改不了。现在网申页页头有「浏览器设置」,与投递台**共用同一个  
  受控窗口**(含登录态),所以这里改了对两边都生效。选择 UI 抽成  
  `components/apply/BrowserChoiceFields.tsx` 一份,投递设置与网申页共用,**不写第二份  
  选择逻辑**。换过浏览器要**重启浏览器才生效**,界面上写明了。指定某一个就只找那一个,  
  没装会直接提示,**不会静默换一个**。
- **投递记录也能删了**(单条删 + 整批删),删掉进回收站、可恢复。`apply_task_item`  
  加一列 `deleted_at` 走既有的回收站机制。**删了就真没了:不再计入统计,也不再计入  
  每日上限**——这条语义带来的连带改动比看上去大,因为投递记录现在是  
  `ApplyTask` 的子表、所有查询都 `join(ApplyTask)`,必须逐处补 `live_only` 过滤:
  - `_records.daily_success_count`(每日上限的已用量)——**漏了则删记录会悄悄放宽上限**;
  - `_records.list_records` / `list_record_batches` / 批次内取记录;
  - `api/stats.py` 首页统计的成功投递数。
    这些失败方式都是**静默的**(不报错,只给出偏大的数字),所以每条都有测试钉住  
    (`tests/test_apply_record_delete.py`,含"删一条成功记录后每日上限已用量确实减少")。  
    另外 `POST /records/{item_id}/retry` 对已软删的记录返回 **404** 而不是复活它。  
    批删只软删该批次下的**记录**,不动 `ApplyTask` 本身,否则批次列表会缺组。

### Changed

- **QQ 号、微信号归位到「基本信息」,四六级分数归位到「教育经历」。** 用户提的:"我希望把  
  qq 号、微信号放入我的资料中的简历资料的基本信息里面,把四六级分数放到教育经历里面"。

  它们本来就**属于简历资料**,之前却在「网申资料」里另存了一份,于是同一个值有两个来源——  
  改哪边都不对,而且填表时哪一份生效全看巧合。现在:
  - **QQ 号 / 微信号**:`UserProfile.qq` / `.wechat` 一直就在,网申填表本来就从 `UserProfile`  
    取这两个值,所以把「网申资料」里那份重复的删掉了(能力不变)。  
    **但界面这一半当时没做完**——它们仍然待在下面那张「网申专用资料」卡片里,而不是「基本信息」,  
    见下面那条修正;
  - **四六级分数**:新增 `education.cet4_score` / `.cet6_score` 两列(迁移 `0029`),录在**每一条**  
    教育经历上——成绩是某段学历期间考出来的。界面上就在「绩点/排名」旁边,网申填表取**最高学历  
    那一条**(与其它教育字段同一条规则)。
    ⚠️ **已经在「网申资料」里填过这几个字段的值会失效**(那是重复录入的一份)。四六级分数请改到  
    「教育经历」里去填。这是有意的:留着旧值就意味着两个来源同时存在,而那正是这次要消掉的东西。

  自由文本而非整数存储:`""`(没填)/ `"512"` / `"未考"` 都可能,不做规范化——规范化只会让  
  填表时多一层猜测,而站点要的是原样写进去。

  **顺带补上一个静默丢弃的缺口**:粘贴识别("粘贴文本识别")的结果要穿过  
  `_bound_parse_result` 的**白名单**收窄——只按名单里的键构造结果,没列进去的会被**丢掉**,  
  表现为"认不出这一项"且没有任何报错。四六级两列起初就漏在名单外,这次补上了。

  同一个坑里还躺着**三个早于本次改动**的字段:`department` / `study_mode` / `degree_type`  
  在更早的迁移里加到了模型与界面,白名单与提示词却一直没跟上——粘一段带院系的简历,  
  院系、全日制、学位类型都不会被认出来,同样不报错。这次一并补上,并把守卫改成**双向**:  
  `EducationIn` 与收窄白名单必须互相覆盖,以后往教育经历加字段忘了同步会直接变红。

- **「导师 / 实验室 / 研究方向 / 论文」几个紧挨着的框互相串台,已修。** 用户实测报的:  
  腾讯校招页上「导师」被认成「研究方向」,「实验室」「研究方向」「论文」三个**全**认成  
  「研究方向」。根因是**证据强度判据在两处各写了一份**:引擎的 `_rank_controls` 与  
  实时面板的 `service.recognize_field`。修法是把判据提成一个共享函数 `evidence_key()`  
  (`services/webform/engine.py`),两边都调它,分档如下:
  - **3** = `autocomplete`(浏览器自己标注的字段类型,最可信);
  - **2** = **占位符**,但仅在它**在陈述这个框要什么**时才算("请输入导师" /  
    "请选择学历" / "请填写证件号码" → `_states_its_field()`)——输入框自己写着要什么,  
    比周围的文字更权威;
  - **1** = `label` / `aria-label` / `name`(控件自己说的);
  - **0** = 只在 `nearby_text`(旁文)里命中——**最弱**,因为那可能只是段说明文字。
    同档内按命中的**最长同义词**排名。修复前「实验室」的旁文里含"研究方向"而又没有更强的  
    证据,于是和真正写着"研究方向"的框打平、靠 DOM 序号决胜,谁在前面谁赢——这正是用户  
    看到的"三个全认成研究方向"。

  修完所有兼容值都走同一条路——不再有第二份判据,新增字段不会再出现"引擎认得出、面板  
  认不出"的分叉。

- **删掉官网采集遗留的三张空表**(`official_site` / `official_collect_run` /  
  `official_discovery_search`)。功能移除时故意留着它们,理由是"删表要发迁移,为空表单独付  
  这个代价不划算"——本次迁移 `0025` 就是那