跳转至

开发日志

记录每次版本更新的改动、遇到的问题与解决方案、注意点。 最新记录放最上面。 新增记录请复制下方模板。

记录模板

## YYYY-MM-DD v旧版本 → v新版本

### 本次改动
-

### 遇到的问题
- 问题:
  - 现象:
  - 原因:
  - 解决方案:

### 注意点
-

2026-05-30 v0.10.112 → v0.10.113 50k 窗口家族 bug 沉淀 — 新 rule + scan 脚本 + pre-commit + 修 merchant-stats

背景

v0.10.112 修了 ISSUE-0074(KPI 0 邮箱)后,意识到这是同一类 bug 第 4 次复发(ISSUE-0018/0050/0051/0074)。 每次都修单点,下次仍能再发。本版做沉淀,让以后自动避开。

本次改动

1. 新规则 docs/rules/count-vs-scan-window.md

定义反模式(红信号)/ 正模式(绿信号)/ 决策表 / 必问清单。

反模式

selectByQuery(T, { limit: 50000, order: { by: 'id', type: 'desc' } })
// 循环计数
正模式优先级: 1. countByQuery(T, { where: 索引字段 }) — 毫秒级精确 2. 必须扫行时先 where:状态字段 缩集合 3. 多状态都要算 → 分窗扫拼接

2. 新脚本 scripts/scan-count-window.py

  • 自动扫 src/ 找 selectByQuery + limit + order:id desc 跨多行模式
  • 行附近有 // SAFE: count-window-ok — <理由> 注释即豁免
  • 不豁免命中 → pre-commit --strict 阻止

3. 集成 pre-commit hook

scripts/hooks/pre-commit 加 7 号检查:任何 src/ .ts/.tsx 改动跑 scan:count-window --strict。 + package.jsonpnpm scan:count-window

4. 修一处真 bug + 标注存量

扫描发现存量 7 处命中:

  • 修真 bugsrc/utils/merchant-stats.ts:80 同款窗口截断
  • 旧:selectByQuery + limit 50k + order id desc → 用户超 5w 时 withEmail 失真
  • 新:拆 where:scrape_status=2 + where:scrape_status=0 双窗
  • 🏷 加 SAFE 注释(已合理设计的):
  • data-counts.ts:56, 65 (v0.10.112 已修)
  • data-view.tsx:138, 151, 156 (v0.10.112 已修)
  • merchant-stats.ts:113 (taskId 路径,受 ISSUE-0008 制约)

扫描后 scan:count-window 0 命中通过 ✅。

5. rules/INDEX 加链接

新增「性能 / 数据查询」分组,指向新 rule。

遇到的问题

无新增。

注意点

  • 该 rule 是面向 AI 协作者的强制规则 — 新代码写 selectByQuery + limit + order id desc 必被拦
  • SAFE 注释必须带具体理由 — 「我觉得没事」不算理由;得写"行数 ≤ N 因为 X / where 字段 indexed"等
  • 存量代码逐步治理 — 5 处加注释 + 1 处修真 bug;以后改动这些地方时由 pre-commit 提醒重审

受影响 ISSUE / SPEC

  • ISSUE-0074 + 历史 ISSUE-0018/0050/0051:沉淀进规则 + 自动检测,根治家族 bug 再发

2026-05-30 v0.10.111 → v0.10.112 KPI 0 邮箱 bug 修复 + mstage 文案通俗化 + 邮箱 chip

用户反馈

用户截图:187k 商家、电话 18.7k,但邮箱 0。用户问"为什么没有邮箱?"

导出日志分析发现: - 抓取真在跑 7.3 小时,1105 个 mstage:success - 660 个商家采到了邮箱(共 877 个独立邮箱) - ContactPool 14,812 条 / 贡献度账本 15,136 笔 - 但 KPI 显示 0 — 是计数 bug,不是抓取 bug

#1 根因:50k 窗口家族 bug 复发 (ISSUE-0074)

data-counts.ts + data-view.tsx 都用:

selectByQuery('MapTaskData', { limit: 50000, order: { by: 'id', type: 'desc' } })

用户 187k 商家里,最新 50k 是用户最近创建任务时一次性 push 入的(scrape_status=0/emails=[]), 已采过邮箱的 660 行 id 较小、被排在窗口外 → 邮箱去重 0、scrapeDone 0。

v0.9.35 当时注释写"50000 可覆盖中量级用户全量",但 187k 远超中量级。 ISSUE-0018/0050/0051 都是同款家族 bug,不彻底。

本次改动

data-counts.ts 重写

  1. 三个总数改用 countByQuery(原生 count,瞬间精确):
  2. merchant = countByQuery('MapTaskData', {})
  3. scrapeDone = countByQuery({ scrape_status: 2 })
  4. scrapePending = countByQuery({ scrape_status: 0 })

  5. 去重计数按 status 分窗扫

  6. 已采(status=2)扫 50k → 贡献 emails + phones + websites
  7. 待采(status=0)扫 50k → 补充 phone(地图带的,status=0 也有)+ websites

base.ts scrape_status: enableSearch:true 有索引,where 查询走索引。

data-view.tsx 顶层 rows 拉取

把单查询的 limit 50000 + order by id desc 拆成"status=2 + status=0"两次扫描拼接,最多 10w 行。 顺带消除商家列表卡顿主因(旧版每 30s 拉 5w 行 + 全表 desc 排序 IO 放大)。

#2 mstage 文案通俗化(用户:跳过 / 命中看不懂)

用户截图:"跳过抓取:mstage:domain-dead@domain-state — domain-state=dead" / "多阶段命中(未开 tab):mstage:success — success: 53 contacts (emails=0 phones=50) (pool-hit)"

新增 humanizeMstageDetail(error: string): string: - mstage:domain-dead → "此网站此前多次失败(已标记死站),自动跳过节省时间" - mstage:success — ... (pool-hit) → "已采到:邮箱 N 个、电话 M 条(来自他人共享)" - mstage:fallback/domain-antibot → "该域名为强反爬(Cloudflare 等),改用浏览器打开方式" - mstage:no-contact → "网站可访问,但页面上没有邮箱 / 电话 / 社媒" - 等等 12 个映射

classifyMstageshortLabel 加 emoji: - ✓ 已采 / ⏭ 跳过 / 🛡 防爬 / 🔄 重试 / 🔁 重试

Tooltip 改成 3 行卡片: 1. 前缀(带 emoji,通俗) 2. humanize detail(中文一句话说人话) 3. 原始 mstage:... 标签(monospace,给开发者排查)

#3 官网 tab 邮箱数列改 chip

data-view.tsx websiteColumnsemailsCount 列: - 旧:valueGetter: row => row.emails.length(显示 0/1/2 纯数字) - 新:renderCell: <RenderEmail row={row} />(主邮箱直显 + +N chip hover 展开全部) - valueGetter 保留(sort/filter 仍按数量工作)

宽度 88 → 220,列名 "邮箱数" → "邮箱"。

商家列表 local-table.tsx 早已是 RenderEmail chip(line 151),不需改。

遇到的问题

无新增。

注意点

  • KPI 数字立即恢复真实值(无需用户操作)
  • 商家列表卡顿主因消除:30s 拉 10w 行(分两个 5w 窗口)比旧版 5w + 全表 desc 排序快
  • humanize 映射文案要随 mstage 类型增加同步扩展,写进 mstage-classify.ts
  • 主面板邮箱列表(去重 tab)—— 仍依赖 rows 派生(emails useMemo),这次顶层 rows 改了之后跟着恢复

受影响 ISSUE / SPEC

  • 新建 ISSUE-0074:50k 窗口家族 bug 复发
  • 影响历史 ISSUE-0018 / -0050 / -0051:同一类 bug 终于治本

2026-05-29 v0.10.110 → v0.10.111 v0.10.110 审计修复(INDEX display + CF Pages 依赖固化)

背景

v0.10.110 完成 129 文件英文化后做了一次专业 + 使用维度审计,发现 2 个真实回退。本版收尾。

本次改动

1. 修 INDEX 手写段 display 文本(16 处)

v0.10.110 的 rename 脚本只替换 .md 文件名后缀,未处理 markdown link 的 display 文本。 导致手写 INDEX 段从 [共享队列架构](./共享队列架构.md) 变成 [shared-queue-architecture.md](./shared-queue-architecture.md) — display 失去中文可读性。

/tmp/fix-index-display.py 自动从目标文件的 frontmatter title: 读取中文标题, 替换 display。覆盖范围: - docs/wiki/INDEX.md — 8 处 - docs/rules/INDEX.md — 6 处 - docs/changelog/development-log-archive-*.md — 2 处

排除_overview.md / _todo.md / issues/INDEX.md 这些是 rebuild-docs.py 自动生成区, 格式是 [english-stem.md](path) — 中文 title(dash 后有中文 fallback),保留。

2. 建 requirements-docs.txt 固化 CF Pages 依赖

v0.10.110 后 137 个 〚english|中文〛 wikilink 强依赖 mkdocs-roamlinks-plugin。 之前 build command 把 plugin 名写在 CF dashboard 外部,配置易丢。

新文件 requirements-docs.txt

mkdocs>=1.6,<2
mkdocs-material>=9.5,<10
mkdocs-roamlinks-plugin>=0.4,<1

CF Pages build command 更新为:pip install -r requirements-docs.txt && cp -f CHANGELOG.md docs/ 2>/dev/null; mkdocs build

3. cf-pages-deploy rule 加故障排查项

新增故障:「站点上所有 〚english|中文〛 显示为字面文本」→ plugin 没装 → 用 requirements-docs.txt

遇到的问题

无新增。

注意点

  • rebuild-docs.py 自动区不要手改 display:会被覆盖
  • 手写 INDEX 段维护成本:未来加新文件要手工补 display 中文标题(rebuild 不管手写段)
  • 依赖版本固化策略:minor 边界(如 <10)防大版本破坏;patch 自动跟进

受影响

无新 ISSUE / SPEC。属审计修复。


背景

用户审计文档规范:「文档名由英文和数字以及"_-"组成,但是文章名称可以是中文」。 v0.10.105 已清理过 +/() 等 URL 不友好字符,但 150+ 中文文件名仍未追溯。 本版本一次性完成全部追溯(B 方案)。

本次改动

1. 文件名英文化(129 个 git mv)

类别 文件数 示例
changelog 3 开发日志.mddevelopment-log.md
issues 70 0070-共享窗口孤儿累积...md0070-shared-window-orphan-storage-race-sw-kill.md
specs 8 SPEC-006-task删除时商家数据二选一确认.mdSPEC-006-task-delete-data-cascade-confirm.md
wiki 11 共享队列架构.mdshared-queue-architecture.md
rules 18 文档目录规范.mddocs-directory-spec.md
raw 19 2026-05-26-watchdog重启后自动继续.md2026-05-26-watchdog-auto-resume-after-restart.md

文件名规则: - kebab-case(小写 + - 分词) - ≤ 60 字符 - 保留语义 + 4 位 ID / 日期前缀 - 文章 title: frontmatter 保持中文(rebuild-docs.py 用此显示)

/tmp/do-rename-v0.10.110.py 一次性全局更新:

wikilink 处理(保留中文显示)(下用 〚〛 代替双方括号防 docs:check 误报): - 〚开发日志〛〚development-log|开发日志〛(无 alias 加中文 alias) - 〚开发日志|某文字〛〚development-log|某文字〛(已有 alias 保留) - 〚开发日志#章节〛〚development-log#章节|开发日志〛(含锚点) - 路径前缀型:〚../wiki/Tab生命周期与看门狗〛〚../wiki/tab-lifecycle-and-watchdog|Tab生命周期与看门狗〛

markdown link 处理: - (开发日志.md)(development-log.md)(只改路径,display 不动) - 替换顺序:basename 长度 DESC(避免短 stem 误吞长 stem 子串)

扫描范围:docs/**/*.md + scripts/*.py + mkdocs.yml + 根级 *.md

3. 自动化脚本更新

  • scripts/archive-log.py:归档命名模板 开发日志-archive-vMIN-vMAX.mddevelopment-log-archive-vMIN-vMAX.md
  • mkdocs.yml:导航路径英文化(display 名仍中文)
  • scripts/rebuild-docs.py:自动读 frontmatter title: 渲染 INDEX,已兼容(无需改)

4. 文档规范更新

docs/rules/docs-directory-spec.md: - 命名约定明确"v0.10.110 起统一英文文件名" - ❌ 不用中文 / 大写 / 空格 - ✅ kebab-case + - 分词

遇到的问题

  • 现象pnpm docs:check 报 4 个 broken wikilinks
  • 原因:初版 regex \[\[<stem>...\]\] 只匹配 bare stem,没考虑 〚../wiki/<stem>〛 路径前缀型
  • 解决:写 /tmp/fix-path-wikilinks.py 二次扫描修 5 个文件 / 10 个 wikilinks
  • 教训:obsidian wikilink 支持相对路径,下次先 grep 路径前缀型再写脚本

注意点

  • rebuild-docs.py 已兼容:用 frontmatter title: 渲染 INDEX 显示文字,文件名英文不影响 UI 体验
  • archive-log.py 修正未来:以后自动归档生成的文件名直接英文,无需再手工
  • wikilink alias 显式中文:所有 〚english|中文〛 让 Obsidian + mkdocs 都能正常显示
  • CHANGELOG.md 自动重生成:跑 pnpm docs:changelog 即可

受影响 ISSUE / SPEC

无具体 ISSUE(无新 bug),属文档治理。


2026-05-28 v0.10.102 → v0.10.103 code review 修 4 项(2 严重 + 2 优化)

agent code review 在 v0.10.86-102 改动中发现 10 项问题,确认 4 项需修:

🔴 严重 1: SPEC-006 cascade 删命中 v0.10.2 ISSUE-0008 老 DB 坑 - task-manager.ts 用 removeByQuery({taskId: id}) + Dialog 用 countByQuery({taskId}) - 升级过的 DB 上 jsstore where:{taskId} 给 0 行(search-data.ts:67-91 已注释) - 后果:Dialog 显示 0 商家 / cascade 删 0 行 - 修:task-manager 改 select 全表 + JS filter + remove by id;Dialog 改用 countAllByTaskIds

🔴 严重 2: cleanupOrphanScrapeWindows 误杀用户新标签页 - 之前 680-840 × 480-640 范围 + tab 全 about:blank/chrome:newtab 就关 - 用户调过的小窗口容易落进 → 浏览器启动时误杀用户新标签页 - 修:尺寸阈值 ±80 → ±20;只关 about:blank(不关 chrome://newtab/)

🟡 优化 3: log-view exportedFromVersion 写死 '0.10.96' - 改 browser.runtime.getManifest().version 动态读

🟡 优化 4: log-view 导出 settings 未脱敏 - 9 个敏感字段 → <redacted:Nchars> - bundle 加 redactedFields 字段列出脱敏 keys


2026-05-28 v0.10.108 → v0.10.109 MkDocs 主题 + 日志归档脚本 + 子目录 INDEX 补全

#1 主题:全宽 + 蓝色

  • mkdocs.yml palette: green → blue / accent: indigo
  • docs/assets/extra.css 自定义全宽 + 表格 + 代码块 + wikilink 高亮
  • .md-grid { max-width: none; } 去掉 1220px 限制

#3 日志自动归档脚本

scripts/archive-log.py: - 阈值 1500 行 + 保留最新 30 个版本 - 超阈值 → 自动归档老版本到 development-log-archive-vMIN-vMAX.md - 自动加 frontmatter + 主文件加索引尾 - pnpm docs:archive-log (dry run) / --apply 真执行

#4 子目录 INDEX 补全

9 个新 INDEX.md(覆盖之前缺索引的子目录): - docs/changelog/INDEX.md - docs/specs/{active,done,parked}/INDEX.md - docs/raw/{inbox,feedback,conversations,ideas}/INDEX.md - docs/assets/INDEX.md

frontmatter description 已全部 md 含(_template.md 除外,是模板)。

下一步:v0.10.110 文件名英文化

150+ 中文 md + 350 wikilink 全部追溯改 — 工程量大,单独 commit 便于 review。


2026-05-28 v0.10.107 → v0.10.108 文档目录整改 — 根从 8 → 4,全部归档到 docs/

用户问"为什么开发日志/问题记录/改版说明在一级目录?SOP 流程规则?" 诚实审计:根目录散乱破坏了 CLAUDE.md 五层漏斗规则。

移动(git mv)

旧位置 新位置
development-log.md docs/changelog/development-log.md
development-log-archive-v0.10.0-84.md docs/changelog/development-log-archive-v0.10.0-84.md
development-log-archive-v0.8-v0.9.md docs/changelog/development-log-archive-v0.8-v0.9.md
更新规则.md docs/rules/version-release-iron-rules.md
v2-UI改版说明.md docs/specs/done/SPEC-000-v2-ui-redesign.md

引用同步(脚本自动 + 手工)

  • CHANGELOG.md / CLAUDE.md / README.md / docs/README.md / docs/index.md
  • docs/issues/INDEX.md (4 处)
  • mkdocs.yml (nav 路径)
  • scripts/gen-changelog.py (脚本路径)
  • scripts/rebuild-docs.py (扫描路径)
  • docs/rules/cf-pages-deploy.md (build command 简化)

13 个文件 / 20+ 处引用更新。docs:check 0 broken link。

新规则文档

docs/rules/docs-directory-spec.md — 决策树 + 根白名单 + docs/ 角色表 + 命名约定 + 反例 + 工具支持。

明确根只允许 4 个 .md:README / CHANGELOG / CLAUDE / LICENSE。 其他全部 docs/ 五层漏斗分类。

结果

repo 根 .md:8 个 → 4 个(README / CHANGELOG / CLAUDE / LICENSE-tbd) docs/ 结构清晰:raw / specs / wiki / issues / rules / changelog / _topics + 自动文件

元教训

"SOP 写在文档""SOP 被遵守"。CLAUDE.md 早规定五层漏斗,但执行中根目录还是散乱。 v0.10.108 起: - docs-directory-spec.md 明确白名单 - 移动需脚本化(git mv + 引用同步) - pre-commit 可加 "根 .md 必白名单" 检查(v0.10.X 加)


2026-05-28 v0.10.106 → v0.10.107 沉淀机制强化:补 4 ISSUE + 2 rules + commit-msg hook(ISSUE-0070~0073)

用户问"每次更新都进行沉淀吗?问题回顾历史吗?" 诚实审计:v0.10.97~106 有 4 个真 bug 修漏建 ISSUE,多次"直接读代码不查 INDEX"。 本版把"机制有"变成"强制执行"。

#1 补 ISSUE-0070~0073(漏建的)

  • ISSUE-0070: 共享窗口孤儿累积(v0.10.101 修,含元教训 + audit_grep)
  • ISSUE-0071: SPEC-006 cascade 删命中 ISSUE-0008 历史回归(v0.10.103 修)
  • ISSUE-0072: cleanupOrphan 误杀新标签页(v0.10.103 修,关键 UX 事故)
  • ISSUE-0073: 导出 JSON 版本号写死 + settings 未脱敏(v0.10.103 修)

每个都含 audit_grep 字段,scan:issue-coverage 自动防退化。

#2 新 docs/rules/per-version-sinking-checklist.md

完成 commit 前的强制自查清单: - 何时必建 ISSUE(修 bug / 历史回归 / agent code review 找到) - 何时必更 wiki(架构概念 / 字段语义) - 何时必动 SPEC(≥ 数据模型 / API / UI 流程) - 决策树 + 各类型必做项详表 + 反例(本会话漏的 4 个 ISSUE)

#3 新 docs/rules/user-feedback-pre-lookup.md

用户报问题时的强制流程:

用户反馈 → grep 关键词查 docs/issues/INDEX → 看 _todo.md → 才读源码

按反馈类型给具体 grep 命令: - UI / 显示 - 抓取相关 - SW kill / 持久化 - jsstore / 数据库 - 设置字段 - 调度 / 并发 - 隐私 / 安全 / 导出 - MV3 / alarms

反例:本会话 4 次"直接读源码"漏查的反思。

#4 commit-msg hook 强制

scripts/hooks/commit-msg 新增 — src/ 改动时 commit message 必含: - ISSUE-XXXXSPEC-XXX 关键词(推荐) - 或 vX.Y.Z 版本号格式(已是版本 commit) - 或 meta:/chore:/docs:/refactor:/style:/build:/test:/ci: 意图前缀(仅小修) - 或本次同时改 docs/issues/ 或 docs/specs/(算"已沉淀")

都不满足 → 阻止 commit,详细提示如何修。 scripts/setup-hooks.sh 更新装 commit-msg + 现有 pre-commit。

元教训

"机制有 ≠ 执行做到":CLAUDE.md 早就规定"先查 INDEX"、"每 bug 一 ISSUE",但我多次不遵守。 v0.10.107 起强制硬约束(pre-commit + commit-msg + 必看清单),不再靠记忆。


2026-05-28 v0.10.105 → v0.10.106 部署目标切换:GH Pages → Cloudflare Pages

用户决定发布到 Cloudflare Pages 而非 GitHub Pages。

改动

  • .github/workflows/docs.yml (GH Pages workflow 不再用)
  • docs/rules/cf-pages-deploy.md 一次性配置指引(5 分钟在 CF dashboard 完成)
  • 更新 docs/index.md 提到 CF Pages 部署

为什么选 CF Pages 直连方式(不用 wrangler)

CF Pages 自带 GitHub 集成 — push 后 webhook 自动 build。不需要: - GH Actions workflow - wrangler / CF_API_TOKEN secret - 任何手动操作

只需在 CF dashboard 配一次 build command + output dir 即可。

用户需做的一次性配置

docs/rules/cf-pages-deploy.md 操作: 1. CF dashboard → Workers & Pages → Create application → Pages → Connect to Git 2. 选 repo + 配 build command + 输出目录 site + 环境变量 PYTHON_VERSION=3.11 3. Save and Deploy

完成后 push 即自动部署。


2026-05-28 v0.10.104 → v0.10.105 文档治理 D+A:文件名清理 + MkDocs site

D 文件名清理

19 个文件含 + / ( / ) → MkDocs URL 编码后丑陋。 脚本批量重命名 + 全局替换: - 文件 19 个 git mv - 139 处 [[wikilink]] / markdown link / 路径引用全局替换 - 涉及 40 个 md 文件 - docs:check 验证 0 broken link

替换规则:+-, -, → 删除, 连续 ---

例: - 0067-email占位符+phone从未写库+probe-403+UI-toggle.md0067-email-placeholder-phone-probe-403-toggle.md - SPEC-004-网站采集多阶段优化+云端协同.mdSPEC-004-multi-stage-scrape-cloud-sync.md

A MkDocs site 发布

  • mkdocs.yml: mkdocs-material 主题 + roamlinks plugin(处理 350 个 [[wikilink]])
  • 中文支持 + 深浅色切换 + 代码高亮 + mermaid + 搜索高亮
  • nav 树覆盖五层漏斗:Wiki / SPEC / ISSUE / Rule / Raw + 版本变更
  • docs/index.md: 首页(五层漏斗说明 + 快速入口 + 关键架构)
  • .github/workflows/docs.yml: 自动部署 GH Pages
  • push 到 main 含 docs/ 或 mkdocs.yml 改动 → 自动 build + publish
  • cp CHANGELOG / development-log.md 进 docs/ 供 mkdocs 引用
  • peaceiris/actions-gh-pages@v4 部署到 gh-pages 分支

site/ 加入 .gitignore。

部署后访问

push 后 1-2 分钟 → https://suxuemi.github.io/laifaxin-chajian-ditu/

注意点

  • mkdocs build 用 --strict || mkdocs build — 严格模式失败时降级(warning 不阻塞首次部署)
  • roamlinks plugin 把双方括号 wikilink 解析为相对 markdown link — 不依赖前缀路径
  • 首次部署需在 GitHub 仓库 Settings → Pages 选 "Deploy from a branch" → gh-pages

2026-05-28 v0.10.103 → v0.10.104 文档治理:开发日志拆分 + CHANGELOG 生成

用户要求"检查规范是否便于持续迭代 / MkDocs 友好",体检发现: - 开发日志膨胀至 10060 行单文件 - 没有 CHANGELOG.md / 没有"按版本聚合"视图 - 350 个 wikilink 阻碍 MkDocs

本版做 B + C(低风险,下版 v0.10.105 做 D + A)。

C: 开发日志按版本族拆

development-log.md (645 行) — v0.10.85+ 当前活跃 + 索引尾
├─ development-log-archive-v0.10.0-84.md (8675 行)
└─ development-log-archive-v0.8-v0.9.md (765 行)

158 条版本 entry 跨 v0.8.37 ~ v0.10.103,原单文件 10060 行查找性差。

B: CHANGELOG.md(按版本聚合)

keep-a-changelog 格式,扫 docs/issues/.md frontmatter (fixed_version) + specs/done/.md (target_version) + 开发日志 (## 版本号 标题) 自动生成。

每个版本含: - 主要改动(来自开发日志 ## 标题) - 修复的 ISSUE 链接 - 落地的 SPEC 链接


2026-05-28 v0.10.101 → v0.10.102 SPEC-006 — task 删除时商家数据二选一确认

本次改动

实施 SPEC-006(之前 approved 已久),用户决策: - 默认"保留商家数据"(安全默认) - TaskFilterPicker 加"已删任务"分组(孤儿数据可筛) - 批量删一并实现

1. task-manager.ts controlTask 加 deleteData payload

if (action === 'delete') {
  ...
  if (payload?.deleteData === true) {
    await removeByQuery('MapTaskData', { taskId: id });  // 级联删
  }
}

2. 新组件 task-delete-confirm-dialog.tsx (~200 行)

  • 单删 + 批量两种模式(按 tasks.length 自动切换)
  • mount 时 countByQuery 拿商家数据数(串行,N=10 也只 100ms)
  • RadioGroup 2 选项:"仅删任务,保留数据(推荐)" / "同时删除全部 N 条"
  • 按钮文案动态:选 cascade 时显示 "删任务 + N 条数据"
  • 0 商家场景跳过选项直接确认

3. task-view.tsx 替换原 ConfirmDialog 调用

  • handleDelete / handleBatchDelete 都改打开 TaskDeleteConfirmDialog
  • 提公用 performDeleteTasks(ids, deleteData) 走 task-control message + 收集结果
  • toast 文案加 "(含商家数据)" / "(保留商家数据)" 区分

4. TaskFilterPicker 已删任务分组

  • 加 useRequest 60s 扫 MapTaskData distinct taskId
  • 计算 orphanIds = distinctTaskIds - liveTaskIds
  • Autocomplete groupBy 渲染两组:"现存任务" / "已删任务(数据仍存)"
  • 孤儿项加 DeleteSweepIcon + 名字 "已删任务 {id前8}"
  • placeholder 加 "/ 已删任务" 提示

UI 效果

单删流程:

点删除 → Dialog "删除任务「店铺名」"
  → "该任务采集到 1,234 条商家数据,请选择..."
  → ⚪ 仅删任务(推荐)/ ⚪ 同时删 1234 条
  → [取消] [删任务 + 1234 条数据]

批量删流程:

选 5 个 → 批量删 → Dialog "批量删除 5 个任务"
  → "该批任务共采集到 12,345 条..."
  → 同款 RadioGroup

已删任务筛选:

TaskFilterPicker 下拉:
  ━ 现存任务 ━━━━━━━━
  📋 dentist · United States
  📋 plumber · CA
  ━ 已删任务(数据仍存)━
  🗑 已删任务 q3vbyman (8 字符前缀)

注意点

  • deleteData 失败不阻塞任务删除(catch + console.warn,任务已删数据成孤儿但仍可见)
  • 孤儿扫描 60s 一次 + selectByQuery limit 100000 — 大数据量略慢但用户感知不到(picker 关时不查)
  • TaskFilterPicker getOptionLabel 含 t.name,孤儿名是 "已删任务 xxx" 防重名

2026-05-28 v0.10.96 → v0.10.97 命名澄清 + sys-log 空状态引导

本次改动

用户 v0.10.96 dogfood 截图反馈:跑了 19 个商家但系统事件 0 条。诊断后:

  • 商家全部抓到(19 个)但没进入官网抓取阶段(官网/社媒 0 条)
  • 左下 sidebar「提取邮箱/社媒」toggle 关闭 → 官网抓取不启动 → pipeline 不被调用 → 无 sys log
  • 同时用户看到左下「云端同步」(laifaxin 备份)开着,可能误以为 SPEC-004 Phase 3 也启用了

命名混淆 + 空状态无引导 = 用户不知道为啥日志空。

修复

  1. settings-view.tsx — settings 中 toggle 改名:
  2. "启用云端同步(实验 · 服务端待上线)" → "启用社区共享(实验 · 服务端待上线)"
  3. helperText 明确说明"⚠️ 注意:与左侧 sidebar 的「云端同步」(laifaxin 备份) 不是一回事"

  4. sys-log-list.tsx 空状态从单行文字升级为完整引导:

  5. 列出 3 个相关 toggle(提取邮箱/社媒 / 多阶段抓取 / 启用社区共享)
  6. 解释各自作用 + 哪个开关与 sys log 关联
  7. 提醒"云端同步" vs "启用社区共享" 是两回事

元教训

新功能加 toggle 时要看现有 UI 是否有同名 toggle — sidebar 早就有"云端同步"(laifaxin 备份),我 v0.10.95 又加了 SPEC-004 的"启用云端同步"。复用同名词 = 用户必困惑。

空状态文案要有引导而非单"暂无"——0 条 ≠ 一定是 bug,可能只是用户没开开关。

注意点

  • 数据库 enableCloudSync 字段名不改(避免迁移),仅 UI label 改
  • sys-log 空状态 4 行引导直接渲染在列表区,不需新组件
  • 这是本次会话最后一次"小修",重要的开发已落地(Phase 1+2+3 全做完 + 日志导出)

2026-05-28 v0.10.95 → v0.10.96 全环节 logging 补全 + JSON 日志导出

本次改动

用户问"日志可以导出给你分析吗?所有环节都加上日志了吗?"

审计发现 Phase 2/3 新代码大量用 try/catch + console.warn — SW kill 后日志全丢

1. 新文件 src/utils/system-log.ts(~150 行)

  • 6 category: pipeline / domain-state / contact-pool / cloud-sync / contribution / general
  • 4 level: debug / info / warn / error
  • chrome.storage.local 持久化 + 5000 条环形 + 串行写防并发
  • detail 字段限 2KB(超长截断)
  • API: appendSysLog / getSysLogs / getSysLogsBrief / clearSysLogs / exportSysLogsRaw

2. 补全 ~25 个事件点

位置 事件
domain-state setState transition 含 from/to
domain-state getDomainState TTL ttl-reset
domain-state resetDomainStats reset-all / reset-failed
contact-pool writeContactRecord write / write-failed
contact-pool queryContactPoolByUrl hit
contact-pool writeCloudRecords pull-cloud / pull-cloud-failed
contribution-ledger recordContribution action 名 / record-failed
cloud-domain writeCloudDomainStates pull-domain-state / pull-domain-state-failed
cloud-sync-orchestrator flush 3 路径 upload-start / upload-ok / upload-error
cloud-sync-orchestrator backoff/auth skip-backoff / auth-failed
cloud-sync-orchestrator runStartupTasks startup-begin/health-ok/health-fail/auth-fail/done
pipeline ContactPool 命中 pool-hit
pipeline domain-state 非 continue domain-advice

每条日志含语义结构化 detail(不是 free text),便于 grep / 统计。

3. UI(log-view + sys-log-list)

  • log-view.tsx:tab 加第 4 个"系统事件" + 顶部"导出全部"按钮
  • 新文件 src/sections/page/sys-log-list.tsx(~180 行)
  • category chip 过滤(pipeline / 域名状态 / Contact池 / 云端同步 / 贡献度 / 通用)
  • error/warn 计数 chip
  • monospace 表格行:时间 / category chip / level chip / event / detail JSON
  • 自带"清空系统日志"按钮(ConfirmDialog 防误删)

4. 导出 JSON Bundle

handleExport 合并 8 个 source 写成 laifaxin-logs-{iso-ts}.json

{
  exportedAt, exportedAtIso, exportedFromVersion,
  summary: { sysLog, domain, contactPool, ledger, cloudDomain, pageLogCount },
  sysLogs: [...],
  pageLogs: [...],
  domainStats: [...],
  settings: {...}
}

用户截图 dogfood 时可下载 + 发给开发者,一次性给出完整事件流 + 数据快照

5. 文档

docs/rules/scrape-pipeline-decision-table.md §6 新增: - 完整事件清单表(25+ 条) - 加新事件类型流程 - 导出 bundle 结构

元洞察

try/catch 吞错 + console.warn 是技术债: - SW kill 后看不到 - 用户复现 bug 时无法事后分析 - "在所有环节都加上日志" 应该是 Phase 2/3 完成时就做的(事后补是 2x 工作)

v0.10.97+ 默认做法:所有新代码加 appendSysLog(默认 info / 失败 error),不再用 console.warn 吞错。

注意点

  • system-log 写入是 fire-and-forget — 业务路径不阻塞
  • 5000 条上限,超出 FIFO 淘汰
  • detail > 2KB 自动截断(防超大对象塞爆 storage)
  • 导出文件含 settings — 用户可能不想分享 emailRegex / 黑名单等。当前不脱敏(用户主动导出给开发者,假设知情)

2026-05-28 v0.10.94 → v0.10.95 SPEC-004 Phase 3 — 一次性客户端完整(云端 API 待实现)

本次改动

用户要求一次性完成 Phase 3 客户端 + API 契约文档化。默认 feature flag 关闭(服务端未上线)。

1. API 契约文档(最重要)

docs/specs/done/SPEC-004-phase3-api-contract.md — 完整 endpoint 契约: - 通用约定(base URL / Authorization / 响应包装 / 速率限制 / Idempotency-Key) - 匿名注册 (POST /anonymous/register) + GET /me - ContactPool 3 个 endpoint (upload/query/since) - DomainState 2 个 endpoint (upload/list) - ContributionLedger 3 个 endpoint (log/balance/history) - Leaderboard / Health - 错误码总览 + 客户端处理策略 - 验收清单 + 数据流图

服务端按此契约实现即可,客户端 v0.10.95 已对接。

2. jsstore 加 3 个新表(version 3 → 4)

  • ContactPool: urlHash 主键 + 完整 contact + sync 字段
  • CloudDomainState: 云端权威 domain state 缓存
  • ContributionLedger: append-only 账本

3. 客户端代码(8 个新文件)

文件 行数 职责
src/utils/url-hash.ts ~95 URL 归一化(去 utm/fbclid + lowercase + sort 等)+ sha256
src/utils/contact-pool.ts ~210 ContactPool CRUD + queryByUrl/Hashes + 待上传队列
src/utils/cloud-domain.ts ~80 云端 domain state 缓存
src/utils/contribution-ledger.ts ~120 append-only 账本 + ack 机制
src/utils/cloud-sync-client.ts ~270 通用 fetch wrapper + 9 个具体 API 客户端
src/utils/cloud-sync-orchestrator.ts ~250 4 类任务 + 指数退避 + 手动触发
src/sections/settings/view/cloud-sync-panel.tsx ~160 settings 面板

4. pipeline + executor 集成

  • runWebsiteScrapePipeline 入口加 ContactPool 命中检查(stage: 'contact-pool'
  • scraper-executor 两个 success 路径(pipeline + tab)都写 ContactPool + record contribution

新 PipelineOutcome stage: contact-pool;success 加 poolHit: boolean 区分

5. SW alarms

background/index.ts 加: - cloud-sync-tick periodInMinutes: 1(上传) - cloud-sync-pull periodInMinutes: 60(拉取) - SW 启动时一次 runStartupTasks(health + 拉云端)

6. Settings UI

  • enableCloudSync toggle(默认 false)+ 隐私说明
  • cloudSyncBaseUrl 输入(自定义服务端地址)
  • 概览面板:ContactPool / Ledger / CloudDomain 数 + 立即同步 + 各自清空

关键约束(用户原话)

默认是关闭状态,不同步(因为现在没云端对接)

enableCloudSync: false 默认值 ✅ apiFetch 第一行 if (!isFeatureEnabled()) return { error: 'DISABLED' } — 不发任何请求 ✅ orchestrator 每个 flush 函数都先检查 isEnabled

服务端实现指南

参考 docs/specs/done/SPEC-004-phase3-api-contract.md: - 共 10 个 endpoint - 通用 Bearer + Idempotency-Key - 错误码 11 种 - Rate limit 三档(upload 60/min, query 600/min, pull 30/h)

注意点

  • jsstore version 3 → 4:旧用户首次启动会触发 schema upgrade 加 3 张表(不影响现有数据)
  • 抓取性能影响:每次抓取额外多 1-2 个 jsstore upsert(μs 级),可忽略
  • 服务端未上线时:client logic 完全静默(feature flag 关 + try/catch 兜底,零错误污染 console)
  • 用户开 enableCloudSync 时若服务端真挂:apiHealth 失败 → 跳过 startup pull → upload 队列累积,下次重试

元教训

契约先行:API 契约文档(~600 行)是这次最重的产出。服务端实现者拿到这份文档就能独立工作 — 不需要再问客户端。


2026-05-28 v0.10.93 → v0.10.94 domain-state 迁 jsstore(为 Phase 3 云端协同准备)

本次改动

用户问"哪个方案便于后续云端协同(不仅域名状态)"。重新评估 chrome.storage.local vs jsstore: - chrome.storage.local: 单 key 5MB 上限 / 无索引 / 增量 sync 要 diff 整 Object - jsstore (IndexedDB): GB 级 / 索引快 / 单条 upsert / 增量 sync 友好

结论:所有 sync-related 数据必须用 jsstore。v0.10.93 的 domain-state 用了 storage.local 是技术债,现在迁移(Phase 3 启动前付,避免后期累积更难迁)。

改动

  1. src/utils/jsstore/base.ts 加新表 DomainStats
  2. domain 为 primaryKey
  3. state / lastSeen / stateExpiresAt / syncedAt 启用 enableSearch(批量查询 + 增量 sync 用)
  4. Phase 3 预留字段syncedAt(增量 sync 时间戳)、cloudConsensus(云端共识强度 0-1)
  5. db version 2 → 3(自动加表,不影响 MapTaskData)

  6. src/utils/domain-state.ts 改写

  7. 保留外部 API(getDomainState/recordPipelineEvent/getDomainAdvice/getAllDomainStats/getDomainStatsBrief/resetDomainStats)不变
  8. 内部:删 Map cache + debounce timer,单条 upsert(jsstore 索引扫描足够快)
  9. 加迁移逻辑 ensureMigrated():模块首次加载时 check 旧 local:domainStats:v1,有就一次性 import 到 jsstore + 删 storage 旧 key + 置 flag

  10. docs/wiki/cloud-sync-architecture.md 新建

  11. 三方案对比(storage.local vs jsstore vs Dexie)— 选 jsstore 理由
  12. Phase 3 四个 store 设计预览(DomainStats / ContactPool / CloudDomainState / ContributionLedger)
  13. 通用增量 sync 流程
  14. 关键约束:"所有 sync-related 必须 jsstore"

用户决策影响

用户拍板"现在迁"(vs Phase 3 启动时迁): - ✅ v0.10.93 用户还没积累数据,迁移成本最低 - ✅ Phase 3 启动时可以直接加 ContactPool 等新 store,架构一致 - ✅ 避免技术债累积

风险

  • jsstore version 从 2 升到 3 — 旧用户首次启动会触发 schema upgrade。实测如有报错需关注(v0.9.8 升 version 1→2 时有过 jsstore where:taskId logError 历史,ISSUE-0008 修过)
  • 迁移失败不阻塞 pipeline — ensureMigrated() 内 catch 后继续,大不了重头学习

注意点

  • domain-state API 完全兼容 — pipeline / settings panel 不动
  • DomainStat 类型加可选字段 syncedAt? / cloudConsensus?(Phase 3 启用)
  • ALL_STATES 数组 + 6 个 count 并发,settings panel 性能好于旧 Map filter

2026-05-28 v0.10.92 → v0.10.93 SPEC-004 Phase 2 — 域名状态机

本次改动

实施 SPEC-004 Phase 2 全栈:

1. src/utils/domain-state.ts(~280 行)

  • 类型DomainState(6 状态)/ Outcome(6 种事件结果)/ Method(head/fetch/tab)
  • 持久化:chrome.storage.local local:domainStats:v1,内存 Map cache + debounce 500ms 写回
  • LRU 5000 域名:超出按 lastSeen 升序淘汰最旧
  • TTL 自动重置:dead 30d / antibot-soft 7d / antibot-hard 90d / cold 90d / unknown 7d / friendly 永久(仅行为降级)
  • 转换规则
  • dead 事件 → state=dead
  • antibot × 3 连续 → antibot-soft → antibot-hard
  • fetch ok-contact 占比 ≥ 70% 且 ≥ 5 样本 → friendly
  • friendly fetch 失败 × 5 → unknown
  • ok-empty × 5 连续 → cold
  • antibot-soft + 2 次 ok-contact → 恢复 unknown

2. pipeline 集成 (website-scrape-pipeline.ts)

入口短路 + 出口事件记录:

URL → getDomainAdvice → {
  skip(dead/cold)         → outcome.kind='skip' reason='domain-dead'|'domain-cold'
  force-tab(antibot-hard) → outcome.kind='fallback' reason='domain-antibot-hard'
  fast-fetch(friendly)    → 跳过 HEAD 直接 GET(节省 ~3s/URL)
  continue                → 完整 pipeline
} → 每阶段 recordPipelineEvent 更新状态

新 PipelineOutcome reason: domain-dead / domain-cold / domain-antibot-hard,stage 加 domain-state

3. UI

  • src/sections/settings/view/domain-state-panel.tsx — settings 内显示概览
  • 总域名数 + 各状态 chip 计数(按状态着色:dead 红 / antibot 橙 / friendly 绿 / cold 灰)
  • 30s 自动刷新
  • "重置" 按钮 + ConfirmDialog(清空所有学习数据)
  • 集成到 settings-view "⚡ 多阶段抓取" 段下方

4. mstage-classify 扩展

加 Phase 2 三个新标签的分类: - mstage:domain-dead / mstage:domain-cold → skip 灰色 - mstage:fallback/domain-antibot-hard → antibot 橙色

5. 文档同步

  • docs/wiki/domain-state-machine.md status: draft → stable(implemented_in: v0.10.93)
  • docs/rules/scrape-pipeline-decision-table.md 加 3 行新 mstage 标签
  • docs/specs/active/SPEC-004 phased_status: phase_1=done@v0.10.87 / phase_2=done@v0.10.93 / phase_3=parked

同版顺手(A 反馈)

嵌套滚动条问题审了 layout 代码(LocalDataView height:100% + overflow:hidden 是合理设计,DataGrid 自带 internal scroll 是正常),未找到明显双层 overflow 问题。待用户具体截图位置后再处理

性能预期

站类型 占比 旧 Phase 1 新 Phase 2 优化点
已知 dead 域名(重复抓) ~5% 3s HEAD 0ms 短路
已知 friendly 域名 ~20% 3s HEAD + 5s GET 5s GET(跳 HEAD) 1.6x
已知 antibot-hard ~3% 3s HEAD + 5s GET 浪费 直接 tab 8s 节省
已知 cold 域名 ~10% 3s + 5s + 解析 0ms 短路

整体期望额外加速 ≈ 1.3-1.5x(叠加 Phase 1 的 1.8x)。

注意点

  • domain-state 完全无需开关 — 跟随 enableMultiStageScrape flag 自动启用(pipeline 内调)
  • 重置后从零学习(适合用户怀疑 dead 域名活了 / 误判 antibot 后用)
  • 5k 域名上限 + LRU 淘汰,存储 ~2.5MB 内
  • pipeline 内每次调 recordPipelineEvent 是异步但非阻塞用户路径(pipeline 已 await)

2026-05-28 v0.10.91 → v0.10.92 data-view mstage 漏改 + 规则文档化(ISSUE-0069)

本次改动

用户截图 matsudental.com "失败"实为 v0.10.90 漏改 — data-view.tsx HTTP 列还在用老逻辑(一律红色 + "抓取失败")。

代码

  1. 新文件 src/utils/mstage-classify.ts — 公用 helper,返回 { kind, color, chipColor, tooltipPrefix, shortLabel }
  2. src/sections/page/log-view.tsx — 删 local classifyMstage(dead code),改 import
  3. src/sections/data/data-view.tsx HTTP 列 — 用 classifyMstage 分类
  4. success → 🟢 "✓ 命中" + "多阶段命中(未开 tab)"
  5. skip → ⚪ "跳过" + "跳过抓取"
  6. antibot → 🟠 "反爬" + "反爬拦截 / 落 tab"
  7. fallback → 🔵 "落tab" + "降级到 tab 抓取"
  8. 真错误 → 🔴 截断 + "抓取失败"

规则文档(用户反馈 3:可持续迭代)

  1. docs/rules/scrape-pipeline-decision-table.md — mstage 完整速查
  2. 决策树 / 标签 → UI 映射表 / PipelineOutcome 编码规则 / 加新类型 6 步流程
  3. docs/rules/ui-change-pre-check.md — 改 UI 前 4 步清单
  4. 高频字段速查表(e.error / row.phone / scrape_status / ...)
  5. 抽公用 helper 信号 + 模板
  6. 反面教材 3 例

其他

  • ISSUE-0069 归档(Bug 1 ✅ / Bug 2 嵌套滚动条 ⏸ 待复现)
  • raw inbox 收纳"嵌套滚动条待复现"等用户给位置后处理

元教训

抽公用要趁早:v0.10.90 在 log-view.tsx 内 inline classifyMstage 是技术债 — data-view.tsx 自然漏了同款逻辑。dogfood 暴露后才补抽公用 + 写规则文档。

dogfood 价值:用户截图 matsudental.com "失败" 是最好的 QA — 自动测试不会察觉视觉文案问题。

待办(v0.10.93+)

  • 嵌套滚动条 — 需用户标注具体位置(已记 raw/inbox)
  • SPEC-004 Phase 2 域名状态机(dogfood 数据足够后启动)

2026-05-28 v0.10.90 → v0.10.91 任务 id 保证含数字 + chip 改 "id" 文本

本次改动

用户截图反馈 task id 'qvlbymansizu' 全字母看起来像单词不像 ID + chip 内 'QVLBYM' 看不懂意思。

  1. generateTaskId:retry 直到生成的 id 既含数字又含字母(之前 base36 均匀分布纯字母 12 字符概率 ≈ 2.7%,用户撞上)。极端兜底(10 次重试都纯字母)在第 5 位强塞数字
  2. task-view.tsx renderIdChip:chip label 改成固定 "id"(小写 monospace + letterSpacing),保留 hover tooltip "任务 ID:xxx(点击复制)"
  3. task-detail-dialog.tsx:dialog 标题里直接显示完整 id(dialog 空间足够,更直观)
  4. 清理 dead code:两处 shortId 函数删除(不再被引用)

设计权衡

chip 文字选择: - 用户原文 'id' 小写 — 尊重原意,monospace 字体下 letterSpacing 增强可读性 - 大写 'ID' 更醒目但偏离用户意图 - 之前 6 字符前缀(QVLBYM)— 用户反馈"看不懂",移除

注意点

  • id 生成的 retry 期望迭代 < 1.04 次,性能影响可忽略
  • 旧 id(不含数字的)不会自动迁移,仅新建 task 走新逻辑
  • shortId 函数删后 src/sections/task/ 两个文件清爽了

2026-05-28 v0.10.89 → v0.10.90 dogfood v0.10.89 暴露 4 项(ISSUE-0068)

本次改动

用户开启 enableMultiStageScrape 后跑大批量任务,截图反馈 4 项:

  1. 清空数据后列表不刷新 — local-toolbar.tsx doClearAll 缺 flushData() 调用,client mode rowsSnapshot 不更新。1 行修
  2. mstage:success 标签 hover 显示"抓取失败" — log-view.tsx 新 helper classifyMstage,按前缀分类 color + tooltipPrefix:success 绿 / skip 灰 / retry 橙暗 / antibot 橙 / fallback 蓝 / 其他 mstage 灰 / 真错误红
  3. 电话去重未去国家码 — scraper-executor.ts normalize 取后 10 位("+1 907-522-1341" 和 "9075221341" 现在被去重为同号)
  4. 创建任务卡顿无反馈 — task-create-dialog.tsx 加 submitting state + try-finally,按钮 LoadingButton 风格(CircularProgress + "创建中…")

元教训

dogfood 大数据集才暴露:单跑 10 商家这 4 个全看不出来,需要 50k+ 行 + 含国家码的真实电话 + 大量 mstage 事件。

注意点

  • normalize 取后 10 位对中国 +86 / 北美 +1 都安全(中国手机 11 位也取后 10 位,与"无国家码"格式去重一致)
  • classifyMstage 用 startsWith — 未来 mstage 加新类型时补 case
  • handleCreate try-finally 包整个 async 块,所有 return 路径都重置 submitting

2026-05-28 v0.10.88 → v0.10.89 电话列与邮箱列对齐 — 多号码 chip + tooltip

本次改动

v0.10.88 phone 合并存进字段(逗号分隔),但 UI 还按单值字符串渲染 → 用户截图看不到 +N chip。

src/sections/page/cell-renderers.tsx: - 新 helper splitPhones(phone) — 按 ,;| 分隔为数组 - RenderPhone(独立列)— 首号主显 + 多号 +N chip + tooltip 弹全部(与 RenderEmail 一致样式) - RenderContact(复合列)— 同款改造

视觉

单号码场景(90%+):UI 与 v0.10.88 完全一致(不加 chip,向后兼容) 多号码场景:+1 847-382-6579 (地图) [+1],hover chip 弹出 +1 847-382-6579 (地图) 224-848-4453

注意点

  • 已有数据不会自动重抓 — 新 UI 在新抓的数据(v0.10.88+ 写入)上生效
  • v0.10.88 之前抓的 phone 字段是单号码字符串,splitPhones 返回 [phone],无 chip
  • 复合列 RenderContact 内联 splitPhones 调用,避免多次解析

2026-05-28 v0.10.87 → v0.10.88 dogfood v0.10.87 暴露 4 项小修(ISSUE-0067)

本次改动

用户截图 advocatehealth.com 医生页 dogfood v0.10.87 暴露 4 个真问题:

  1. email 占位符 your@email.com 抓到污染 → BLACKLIST/NOISE 扩充 23 条(覆盖 your/me/info/contact/admin/noreply + @example.com/@yoursite.com 等)
  2. 网页 regex 提取的 phone 从未写库(长期 bug)→ 新 helper mergePhonesWithMapsLabel 合并 Google Maps 原 phone + 网站 extractedPhones,normalize 纯数字去重,多号码时给地图来源加 (地图) 标记。tab 路径 + pipeline success 路径两处都改
  3. 缺 settings UI toggle → settings-view.tsx 加 "⚡ 多阶段抓取(实验性 · 默认关)" 段 + RHFSwitch + Yup schema
  4. probe 403/451 未归 antibot → website-probe.ts pickStatusReason 加分支,让 tab 兜底试 cookie/UA

元洞察

  • dogfood 第一张截图就翻出 2 真 bug + 1 长期 bug + 1 体验:没装真浏览器永远查不出 phone 字段从未写库
  • WXT lazy storage 陷阱:storage.defineItem getValue 默认值不落盘;chrome.storage.local 改 storage 不安全 → 优先做 UI

注意点

  • phone 单号码 + 地图来源场景(90%+)不加标记,兼容旧 UI / CSV 导出
  • email 黑名单扩充:精确 14 条 + 模糊 9 条;不影响真实邮箱(example/yoursite/yourdomain 域名极少是真实公司域)
  • probe 403 归 antibot 是"宁可错杀不放过"策略,未来 dogfood 可校准

2026-05-28 v0.10.86 → v0.10.87 SPEC-004 Phase 1 — 多阶段抓取 pipeline(默认关)

本次改动

实现 SPEC-004 Phase 1 的客户端多阶段抓取 pipeline。在 processWebsiteScrape 开 chrome.tabs 实抓前,加入 HEAD → GET → regex → tab fallback 决策树。

新增文件(5 个): - src/utils/website-probe.ts — HEAD 探测(3s timeout)+ 响应头分类(dead/antibot/retry-later/ok),GET 兜底 405/501 - src/utils/website-fetcher.ts — GET fetch(5s timeout)+ Content-Type/长度/14 个 challenge markers 三层判断,stream cancel cap 2MB - src/utils/contact-extractor.ts — 包 extractDataFromHtml,自动加载 settings,附 contact / social 关键词扫描(区分"真无"vs"JS 渲染") - src/utils/website-scrape-pipeline.ts — 决策树组装 + 统一 PipelineOutcome { success | skip | fallback } - docs/wiki/multi-stage-scrape-pipeline.md — 架构文档 + 调用约定

修改文件: - src/utils/storage-data.tsSettingParamsenableMultiStageScrape: boolean(默认 false) - src/utils/scraper-executor.ts — feature flag 入口 + applyPipelineOutcome 帮助函数

Feature flag

默认 关闭,需 chrome devtools 改 storage 字段 local:settingParams.enableMultiStageScrape = true 启用。下版本(v0.10.88)加 UI toggle + dogfood。

anti-bot 双层判断

  • HEAD 强信号:cf-mitigated: challenge|block / 403/503 + cf-ray → antibot
  • GET body 关键词:14 个 challenge markers(cf-browser-verification / just a moment / incapsula / awswafcaptchacdk / aws-waf-token / ...)
  • Server: cloudflare 单独 NOT 路由 — 70% CF 站纯 CDN,fetch 正常

决策树映射 → DB / 日志

  • success → 写 emails/phones/socials 到 DB + scrape_status: 2 + page-log opened=false + error='mstage:success'
  • skip/dead/no-contact/non-html/retry-later → 标 scrape_status: 2 + page-log error='mstage:reason@stage'
  • fallback/* → 不动 DB,写一条 fallback 标签的 page-log,然后继续走原 scrapeWithTab

性能预期

死站 3x、静态友好站 2x、整体加权 ≈ 1.8x(dogfood 后校准)。

注意点

  • pipeline 写 DB / 日志 — 由 scraper-executor.applyPipelineOutcome 统一处理(解耦)
  • HEAD 网络层失败不直接判 dead,落 GET 兜底(部分站防火墙吞 HEAD)
  • fetch body 用 stream reader + cancel,防超大 HTML 撑爆 SW heap
  • retry-later (5xx/429) 本期标 scrape_status: 2(已抓),Phase 2 域名状态机会加 24h retry queue
  • 旧代码路径完全保留 — feature flag 关时行为 100% 与 v0.10.86 一致

状态

代码就绪 + tsc 通过 + build 出 v0.10.87 manifest。dogfood 待 v0.10.88 加 UI toggle 后进行。



历史版本归档

v0.10.104 起按版本族拆分。完整历史见:

拆分规则:主文件保留当前活跃迭代(最近 ~30 条)+ 索引。归档文件按 major 版本族划分,老归档不再变动。