开发日志¶
记录每次版本更新的改动、遇到的问题与解决方案、注意点。 最新记录放最上面。 新增记录请复制下方模板。
记录模板¶
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¶
定义反模式(红信号)/ 正模式(绿信号)/ 决策表 / 必问清单。
反模式:
正模式优先级: 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.json 加 pnpm scan:count-window。
4. 修一处真 bug + 标注存量¶
扫描发现存量 7 处命中:
- ✅ 修真 bug:
src/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 都用:
用户 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 重写¶
- 三个总数改用
countByQuery(原生 count,瞬间精确): merchant = countByQuery('MapTaskData', {})scrapeDone = countByQuery({ scrape_status: 2 })-
scrapePending = countByQuery({ scrape_status: 0 }) -
去重计数按 status 分窗扫:
- 已采(status=2)扫 50k → 贡献 emails + phones + websites
- 待采(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 个映射
classifyMstage 的 shortLabel 加 emoji:
- ✓ 已采 / ⏭ 跳过 / 🛡 防爬 / 🔄 重试 / 🔁 重试
Tooltip 改成 3 行卡片:
1. 前缀(带 emoji,通俗)
2. humanize detail(中文一句话说人话)
3. 原始 mstage:... 标签(monospace,给开发者排查)
#3 官网 tab 邮箱数列改 chip¶
data-view.tsx websiteColumns 的 emailsCount 列:
- 旧: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:
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。属审计修复。
2026-05-29 v0.10.109 → v0.10.110 文件名英文化(129 文件 git mv + 全 wikilink 同步)¶
背景¶
用户审计文档规范:「文档名由英文和数字以及"_-"组成,但是文章名称可以是中文」。
v0.10.105 已清理过 +/() 等 URL 不友好字符,但 150+ 中文文件名仍未追溯。
本版本一次性完成全部追溯(B 方案)。
本次改动¶
1. 文件名英文化(129 个 git mv)¶
| 类别 | 文件数 | 示例 |
|---|---|---|
| changelog | 3 | 开发日志.md → development-log.md |
| issues | 70 | 0070-共享窗口孤儿累积...md → 0070-shared-window-orphan-storage-race-sw-kill.md |
| specs | 8 | SPEC-006-task删除时商家数据二选一确认.md → SPEC-006-task-delete-data-cascade-confirm.md |
| wiki | 11 | 共享队列架构.md → shared-queue-architecture.md |
| rules | 18 | 文档目录规范.md → docs-directory-spec.md |
| raw | 19 | 2026-05-26-watchdog重启后自动继续.md → 2026-05-26-watchdog-auto-resume-after-restart.md |
文件名规则:
- kebab-case(小写 + - 分词)
- ≤ 60 字符
- 保留语义 + 4 位 ID / 日期前缀
- 文章 title: frontmatter 保持中文(rebuild-docs.py 用此显示)
2. 引用同步(350+ wikilinks + markdown links)¶
写 /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.md→development-log-archive-vMIN-vMAX.mdmkdocs.yml:导航路径英文化(display 名仍中文)scripts/rebuild-docs.py:自动读 frontmattertitle:渲染 INDEX,已兼容(无需改)
4. 文档规范更新¶
docs/rules/docs-directory-spec.md:
- 命名约定明确"v0.10.110 起统一英文文件名"
- ❌ 不用中文 / 大写 / 空格
- ✅ kebab-case + - 分词
遇到的问题¶
问题 1:path-prefixed wikilinks 第一轮漏改¶
- 现象:
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 命令: - UI / 显示 - 抓取相关 - SW kill / 持久化 - jsstore / 数据库 - 设置字段 - 调度 / 并发 - 隐私 / 安全 / 导出 - MV3 / alarms
反例:本会话 4 次"直接读源码"漏查的反思。
#4 commit-msg hook 强制¶
scripts/hooks/commit-msg 新增 — src/ 改动时 commit message 必含:
- ISSUE-XXXX 或 SPEC-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.md
→ 0067-email-placeholder-phone-probe-403-toggle.md
- SPEC-004-网站采集多阶段优化+云端协同.md
→ SPEC-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 条数据]
批量删流程:
已删任务筛选:
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 也启用了
命名混淆 + 空状态无引导 = 用户不知道为啥日志空。
修复¶
settings-view.tsx— settings 中 toggle 改名:- "启用云端同步(实验 · 服务端待上线)" → "启用社区共享(实验 · 服务端待上线)"
-
helperText 明确说明"⚠️ 注意:与左侧 sidebar 的「云端同步」(laifaxin 备份) 不是一回事"
-
sys-log-list.tsx空状态从单行文字升级为完整引导: - 列出 3 个相关 toggle(提取邮箱/社媒 / 多阶段抓取 / 启用社区共享)
- 解释各自作用 + 哪个开关与 sys log 关联
- 提醒"云端同步" 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¶
enableCloudSynctoggle(默认 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 启动前付,避免后期累积更难迁)。
改动¶
src/utils/jsstore/base.ts加新表DomainStats- domain 为 primaryKey
- state / lastSeen / stateExpiresAt / syncedAt 启用 enableSearch(批量查询 + 增量 sync 用)
- Phase 3 预留字段:
syncedAt(增量 sync 时间戳)、cloudConsensus(云端共识强度 0-1) -
db version 2 → 3(自动加表,不影响 MapTaskData)
-
src/utils/domain-state.ts改写 - 保留外部 API(getDomainState/recordPipelineEvent/getDomainAdvice/getAllDomainStats/getDomainStatsBrief/resetDomainStats)不变
- 内部:删 Map cache + debounce timer,单条 upsert(jsstore 索引扫描足够快)
-
加迁移逻辑
ensureMigrated():模块首次加载时 check 旧local:domainStats:v1,有就一次性 import 到 jsstore + 删 storage 旧 key + 置 flag -
docs/wiki/cloud-sync-architecture.md新建 - 三方案对比(storage.local vs jsstore vs Dexie)— 选 jsstore 理由
- Phase 3 四个 store 设计预览(DomainStats / ContactPool / CloudDomainState / ContributionLedger)
- 通用增量 sync 流程
- 关键约束:"所有 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.mdstatus: draft → stable(implemented_in: v0.10.93)docs/rules/scrape-pipeline-decision-table.md加 3 行新 mstage 标签docs/specs/active/SPEC-004phased_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 列还在用老逻辑(一律红色 + "抓取失败")。
代码¶
- 新文件
src/utils/mstage-classify.ts— 公用 helper,返回{ kind, color, chipColor, tooltipPrefix, shortLabel } src/sections/page/log-view.tsx— 删 local classifyMstage(dead code),改 importsrc/sections/data/data-view.tsxHTTP 列 — 用 classifyMstage 分类- success → 🟢 "✓ 命中" + "多阶段命中(未开 tab)"
- skip → ⚪ "跳过" + "跳过抓取"
- antibot → 🟠 "反爬" + "反爬拦截 / 落 tab"
- fallback → 🔵 "落tab" + "降级到 tab 抓取"
- 真错误 → 🔴 截断 + "抓取失败"
规则文档(用户反馈 3:可持续迭代)¶
docs/rules/scrape-pipeline-decision-table.md— mstage 完整速查- 决策树 / 标签 → UI 映射表 / PipelineOutcome 编码规则 / 加新类型 6 步流程
docs/rules/ui-change-pre-check.md— 改 UI 前 4 步清单- 高频字段速查表(e.error / row.phone / scrape_status / ...)
- 抽公用 helper 信号 + 模板
- 反面教材 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' 看不懂意思。
- generateTaskId:retry 直到生成的 id 既含数字又含字母(之前 base36 均匀分布纯字母 12 字符概率 ≈ 2.7%,用户撞上)。极端兜底(10 次重试都纯字母)在第 5 位强塞数字
- task-view.tsx renderIdChip:chip label 改成固定 "id"(小写 monospace + letterSpacing),保留 hover tooltip "任务 ID:xxx(点击复制)"
- task-detail-dialog.tsx:dialog 标题里直接显示完整 id(dialog 空间足够,更直观)
- 清理 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 项:
- 清空数据后列表不刷新 — local-toolbar.tsx doClearAll 缺 flushData() 调用,client mode rowsSnapshot 不更新。1 行修
- mstage:success 标签 hover 显示"抓取失败" — log-view.tsx 新 helper
classifyMstage,按前缀分类 color + tooltipPrefix:success 绿 / skip 灰 / retry 橙暗 / antibot 橙 / fallback 蓝 / 其他 mstage 灰 / 真错误红 - 电话去重未去国家码 — scraper-executor.ts normalize 取后 10 位("+1 907-522-1341" 和 "9075221341" 现在被去重为同号)
- 创建任务卡顿无反馈 — 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 个真问题:
- email 占位符
your@email.com抓到污染 → BLACKLIST/NOISE 扩充 23 条(覆盖 your/me/info/contact/admin/noreply + @example.com/@yoursite.com 等) - 网页 regex 提取的 phone 从未写库(长期 bug)→ 新 helper
mergePhonesWithMapsLabel合并 Google Maps 原 phone + 网站 extractedPhones,normalize 纯数字去重,多号码时给地图来源加(地图)标记。tab 路径 + pipeline success 路径两处都改 - 缺 settings UI toggle → settings-view.tsx 加 "⚡ 多阶段抓取(实验性 · 默认关)" 段 + RHFSwitch + Yup schema
- 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.ts — SettingParams 加 enableMultiStageScrape: 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 起按版本族拆分。完整历史见:
- ./development-log-archive-v0.10.0-84.md — v0.10 早期 (2026-05-25 ~ 28,~85 条)
- ./development-log-archive-v0.8-v0.9.md — v0.8.37 ~ v0.9.x 早期 (2026-05-21 ~ 22)
拆分规则:主文件保留当前活跃迭代(最近 ~30 条)+ 索引。归档文件按 major 版本族划分,老归档不再变动。