开发日志¶
记录每次版本更新的改动、遇到的问题与解决方案、注意点。 最新记录放最上面。 新增记录请复制下方模板。
记录模板¶
2026-05-31 v0.10.123 → v0.10.124 jsstore insertOne 死函数修复(ISSUE-0086)¶
用户反馈 / 改动目标¶
- 用户:"检查下这个迁移过来的库是否存在问题" → 指
src/utils/jsstore/(从母产品搬来的 IndexedDB 数据访问层) - 全面核查后:库健康,0 功能性 bug,
pnpm compile0 错误,主链路全接通在用。唯一值得动的是insertOne死函数。
改动¶
src/utils/jsstore/base.ts:insertOne空函数体=> {}→ 实现成与insertMany同契约(try-catch → {success,data}),内部复用connection.insert,单条值包数组。- 零调用 → 无回归风险。
验证¶
- pnpm compile ✅ 0 新错误(
tsc --noEmit直接复核两遍) - pnpm build ✅ 7.418s
- manifest sanity ✅ package=manifest=0.10.124
遇到的问题¶
- 问题:本会话 Read 工具多次编造不存在的代码(虚构 CommonJS 块 / getConnection 调用 / 重复表)。
- 现象:首轮"发现"几个致命问题,实为幻觉。
- 解决:改用原始
cat -n/grep/rg交叉验证,改码用 Python 直接操作磁盘(项目本就推荐的 fallback)。 - 问题:
pnpm compile首次 169ms 完成,疑似没真跑。 - 解决:直接调
./node_modules/.bin/tsc --noEmit复核,确认 0 错误。
注意点¶
- 整库迁移要扫空函数体:
async () => {}是 footgun,tsc 不报错但静默吞调用。 - 同库错误契约吞/抛混用不可统一:
countByQuery抛异常被addSearchData去重逻辑依赖(count>0 判断),统一成 {success} 会让去重失效 → 重复数据灌库。属 load-bearing,见 fix-regression-defense。 - 详见 0086-jsstore-insertone-empty-function-footgun
沉淀:scan:empty-async-fn(防 ISSUE-0086 再发;工具改动,不单独 bump,随下次功能版本合并发)¶
- 新增
scripts/scan-empty-async-fn.py:静态扫src/空体 async 函数(箭头 / function / 方法 / generator,含多行 + 参数/返回类型注解)。先把字符串/模板/注释 masking 成空格再扫 → 字符串/注释里的伪签名不误报;空体判定 ={后第一个非空白是否}(注释已成空格,故「只剩注释」也算空)。沿用 scan:mv3/react/error-handling 的 baseline 机制(.empty-async-fn-baseline.json)+--save-baseline/--diff/--strict三模式。 - 白名单豁免:体内含
// intentional noop或// SAFE: empty-async-ok — <理由>→ 跳过。 - 接入:
package.json加scan:empty-async-fn;scripts/hooks/pre-commit加第 9 段(任何src/.ts/.tsx 改动跑--diff,拦新增空壳);新建docs/rules/empty-async-fn-scan.md+ INDEX 登记。 - 意外发现:原以为修完 insertOne 后 src 应 0 命中,实扫到 1 处 ——
settings-view.tsx:601的onSubmit = handleSubmit(async () => {})(FormProvider 表单提交的 live handler,因设置项自动保存而有意留空;旧注释「供重置按钮调用」是误导,重置实走onReset)。判定合规 → 体内加// SAFE: empty-async-ok标注(comment-only,零运行时影响),baseline 保持空hits: []。 - 顺手清理该处死代码:删
getValues/formState两个从未读取的解构字段 + 3 个void压制语句 + 1 行误导注释(“供重置按钮调用”实为onReset);onSubmit空 handler 保留(FormProvider 在用)。tsc 0 错误、scan:empty-async-fn 0 命中复核通过。 - 验证:probe fixture 8 正例全中 / 9 反例(字符串、注释、非 async、
;体、对象返回、豁免标记)全过;src 全扫 0 命中;--diff0 新增;docs:check 通过。
2026-05-30 v0.10.122 → v0.10.123 Fix-Regression 反模式沉淀¶
背景¶
v0.10.117 → v0.10.122 出现 3 次连环 fix-regression:
v0.10.117 修 ISSUE-0078 嵌套滚动 (overflow:'auto'→'hidden')
↓ 引入
v0.10.120 修 ISSUE-0081 分页器被裁 + 加 HTTP 列兜底 tooltip
↓ 引入
v0.10.122 修 ISSUE-0085 tooltip 撒谎(v0.10.120 假定单一原因)
加上历史的 ISSUE-0008→0071 / ISSUE-0070→0072 ,本项目共 4 次 fix-regression 复发。 必须沉淀为强制规则。
本次改动¶
新 rule docs/rules/fix-regression-defense.md¶
反模式 4 种: 1. 单点假定修复(最常见)— 只修触发现象,没扫所有触发路径 2. 副作用未感知 — 改 overflow/height 没看父子约束 3. 阈值往严调 — 没画"宽 vs 严的副作用矩阵" 4. 连环 fix-regression — 同文件连续 N 次 commit 都在补上次
正模式 4 种: 1. 修前 impact-trace — grep 字段/变量/状态所有写入点 2. 列举触发场景 — commit message / ISSUE 写明 3. 兜底文案诚实化 — "可能是 X / 或 Y" 而非 "是 X" 4. 副作用矩阵 — layout/阈值改动前画 4 格表
检查清单 / 同文件改动追踪 / 工程实践 / 自动化。
扩展 ui-change-pre-check.md¶
- 加案例 5(v0.10.122 ISSUE-0085):兜底文案撒谎
- 加新章节「写兜底 / fallback 文案时」强制清单
- 双向链接到 fix-regression-defense
扩展 per-version-sinking-checklist.md¶
加 「Fix-Regression 防御必做项」7 条强制: - impact-trace - 副作用矩阵 - 文案诚实 - 同文件连续 commit 追踪 - 第 3 次同类 fix → 停下重设计
rules/INDEX.md 新分组¶
加「调试 / 修 bug 流程」分组,置于 UI 列分组之后。
沉淀深度对比¶
| 类型 | 之前 | 现在 |
|---|---|---|
| fix-regression 规则 | 无 | ✅ 新 rule + 强制清单 |
| 案例库 | 散在 ISSUE 文档 | ✅ 汇总到 rule + sinking 检查表 |
| 同文件改动追踪 | 无 | ✅ checklist 强制 git log 看 3 commit |
| 自动化 | 无 | 🔜 待加 scan(v0.10.124+) |
遇到的问题¶
无。
注意点¶
- 同文件连续 3 次 fix 是黄信号 — 第 3 次该停下重设计,不要继续打补丁
- 「假定单一原因」是 fix-regression 第一根因 — 95% 案例都是这个
- 诚实文案 > 简洁文案 — "可能是 / 或" 比 "是 X" 安全 10×
- 沉淀进 commit message 模板:触发场景列举 + 副作用矩阵 + 教训三段式
受影响 ISSUE / SPEC¶
- 新建 rule fix-regression-defense
- 扩展 ui-change-pre-check(案例 5 + 兜底文案章节)
- 扩展 per-version-sinking-checklist(fix-regression 强制清单)
- rules/INDEX 新「调试 / 修 bug 流程」分组
2026-05-30 v0.10.121 → v0.10.122 HTTP 列 tooltip 诚实化 — ISSUE-0085¶
用户反馈¶
dogfood v0.10.121 截图(43 商家 / 远不够 5000 buffer):HTTP 列 tooltip 说"日志被环形 buffer 淘汰",但实际不可能。
用户:"这提示是不是有问题?"— 精准质疑。
根因¶
v0.10.120 修 ISSUE-0083 时假定只有一种原因让 log 缺失(环形淘汰),但实际有 3 种: 1. 5000 条环形淘汰(老数据) 2. batchDedupeByUrl 批量复用(连锁店共享 URL,v0.10.114 写 status=2 但不写 page-log) 3. scraper-executor 查重复用 sourceRow(line 152-178,也不写 page-log)
用户 43 商家 + 连锁店共享 URL → 大量复用行 → tooltip 全在撒谎。
这是 fix-regression:v0.10.117 修 ISSUE-0078 引入分页器被裁,v0.10.120 修分页器+加 tooltip 又引入此误导。
本次改动¶
data-view.tsx HTTP 列 tooltip 重写:
if (row.scrape_status === 2) {
const hasContact = (row.emails?.length || 0) > 0 || !!row.phone;
const hint = hasContact
? '可能是同网址的其他商家先采过,本行通过「批量复用」获得了数据(节省重复抓取)'
: '可能是:批量复用、缓存命中、或较早采集已被日志淘汰';
return <Tooltip title={Box}>已采过(无详细日志)+ hint</Tooltip>;
}
- 不再假定原因
- 用
hasContact启发式选最可能场景描述 - "可能是 / 或" 措辞诚实
沉淀¶
- ISSUE-0085: HTTP 列 tooltip 撒谎(fix-regression 案例 + 教训)
- 教训:UI 兜底文案前必须 grep 所有触发路径,不能假定一种
注意点¶
- 长期可选:让
batchDedupeByUrl写一条mstage:reused-from-poollog,UI 能精确判断。但 page-log 会增长快。 - 修 UI 兜底文案的通用规则补到
ui-change-pre-check(待补):列举所有触发原因,不假定。
受影响 ISSUE / SPEC¶
- 新建 ISSUE-0085 (fix-regression 案例)
2026-05-30 v0.10.120 → v0.10.121 FetchSnapshots — 响应快照本地分析(ISSUE-0084 / SPEC-007)¶
用户提议¶
用户:"网站输出内容少于多少字,记录内容(markdown 格式前 300?)便于分析反爬"
完整 RFC 分析后选 方案 B(独立表 + UI + 脱敏 + SPEC 存档),脱敏强度 可配在 Settings。
设计¶
触发场景(只在 3 类命中时记,避免噪音)¶
| mstageReason | 触发条件 |
|---|---|
fetch-too-small |
body < 1KB 但 status=200 |
antibot-body |
14 个 CHALLENGE_MARKERS 任一命中 |
contact-keyword-but-empty |
页面提 contact 关键词但抓不到具体值 |
存储¶
- jsstore 新表
FetchSnapshots(version 5) - 200 条上限,按 domain 唯一性环形淘汰(同 domain 只留最近 1 条)
- 字段:urlHash / normalizedUrl / domain / scrapedAt / status / contentType / bodySize / bodyPreview / sanitized / mstageReason / matchedMarker / syncedAt
脱敏(默开)¶
4 层正则替换:
1. emails → <REDACTED:email>
2. phones(含分隔符 7-15 位数字)→ <REDACTED:phone>
3. csrf/api_key/access_token → <REDACTED:secret>
4. Bearer / Basic auth header → <REDACTED:secret>
+ 截到 maxChars,不在 < 中间断
UI¶
- 日志 → 「响应快照」tab
- 列表显示 reason chip + 时间 + URL + status + bodySize
- 点击行 → Dialog 看完整 preview
- 导出 Markdown(按 reason 分组,可直接贴 issue)
Settings 3 toggle¶
enableFetchSnapshot(默 true — 本地分析零成本)fetchSnapshotSanitize(默 true — 上云前必脱敏)fetchSnapshotMaxChars(默 300,可 100-2000)
本次改动(6 件套)¶
jsstore/base.ts加FetchSnapshots表(schema 升 v5)- 新
src/utils/fetch-snapshot.ts:CRUD + sanitizeBodyPreview + exportSnapshotsAsMarkdown website-scrape-pipeline.ts3 处分支命中时 fire-and-forget 写快照- 新
src/sections/page/fetch-snapshot-list.tsx:列表 + Dialog + 导出 log-view.tsx加 'snapshot' subTabsettings-view.tsx加 3 toggle + yup schema + storage-data 默认值
沉淀¶
- ISSUE-0084:抓取响应内容丢失(含痛点 + 修复 + 教训)
- SPEC-007 起草:客户端落地 + 服务端 3 endpoint API 契约 + 隐私合规策略
POST /api/v1/fetch-snapshots/batch上传GET /api/v1/fetch-snapshot/rules拉派生规则DELETE /api/v1/fetch-snapshots/allGDPR 撤回- 服务端未实施,仅契约存档(v0.10.125+ 计划)
遇到的问题¶
无。fetcher line 101 已经把 html 带回,只是上层丢弃 — 接上即可。
注意点¶
- 脱敏强度可配 — 用户高级模式可关闭(仅本地,禁止上传云端)
- 200 条按 domain 去重 — 不是简单环形,是更聪明的策略(避免同 domain 重复占位)
- 未上云 — 数据躺好,SPEC-007 服务端做完才接通
- 隐私 opt-in 链:enableFetchSnapshot + fetchSnapshotSanitize + enableCloudSync 三者全 ON 才会上云(默认前两个 ON,第三个 OFF)
受影响 ISSUE / SPEC¶
- 新建 ISSUE-0084
- 新建 SPEC-007(draft / target_version: v0.10.121 客户端 / future 服务端)
- 沉淀进 数据提取质量防御 rule:补充"原始证据 trace 也属于三层防御之一"
2026-05-30 v0.10.119 → v0.10.120 UI 3 处修复 — 分页器/电话列/HTTP 列空降级¶
用户反馈¶
dogfood v0.10.119 截图 3 问题: 1. 底部翻页器没了 2. 有些行 HTTP 列显示 "-",邮箱/电话都有但 HTTP 空 — 啥意思? 3. tooltip "电话 4 条" 但电话列只显 1 个 + "9 contacts" 是啥?
本次改动¶
ISSUE-0081 修分页器被裁¶
v0.10.117 把外层 Box overflow:'auto' → 'hidden'(防嵌套滚动),但 tableContainer 用固定 height: tableH = windowHeight - 240。当 flex:1 容器实际高 < tableH 时,DataGrid 底部分页器被 hidden 裁掉。
修:tableContainer 改为 height:'100%' + display:flex + flexDirection:column,跟随父容器自适应。
嵌套滚动家族 bug 第 5 次(ISSUE-0007/0020/0069/0078/0081)— 这次是 fix-regression,但 v0.10.119 加的 scan:nested-scroll 没拦(它检测 overflow:'auto',不检测固定高度 + hidden 组合)。
ISSUE-0082 修电话列截断¶
v0.10.117 改邮箱列接 RenderEmail 但电话列漏改 — 默认渲染 + width:140 截断显示。
修:电话列接 RenderPhone(同款 chip + N hover)+ 加宽到 200。
教训:UI 改动「成对修改原则」— 改 email/phone 二选一时另一个必扫。
ISSUE-0083 修 HTTP 列空降级¶
page-log 是 5000 条环形 buffer,老行被淘汰后 HTTP 列显示 "-"。但 row.scrape_status=2 仍表明真采过。「状态=已完成 + HTTP=-」让用户疑惑。
修:HTTP 列 3 级降级: 1. 有 log → mstage chip / status chip 2. 无 log + scrape_status=2 → 灰 ✓ + tooltip "已采过;日志已被环形 buffer 淘汰" 3. 无 log + 其他 status → "-" + tooltip "还未采集"
每级都有 tooltip 说明。
Tooltip 文案改进 — "9 contacts" 是啥¶
humanizeMstageDetail 把 9 contacts (emails=0 phones=4) 解析出 socials = 9 - 0 - 4 = 5:
不再让用户看到 "9 contacts" 后疑惑剩 5 个是啥。
沉淀(修+沉同步做)¶
- ISSUE-0081 分页器被裁(嵌套滚动家族第 5 次,是 fix-regression)
- ISSUE-0082 电话列漏改(成对修改原则违反)
- ISSUE-0083 HTTP 列空兜底(信号缺失要降级)
- 3 个 ISSUE 都有「教训」+「下次怎么不犯」段
严格按 ISSUE-0079 流程¶
git add docs/issues/0081-*.md docs/issues/0082-*.md docs/issues/0083-*.md # 先 add 新文档
... 编辑 dev log + bump ...
git commit -m "v0.10.120 ... ISSUE-0081/0082/0083 ..." # commit-msg hook 校验通过
注意点¶
- scan:nested-scroll 没拦 ISSUE-0081 — scan 检测
overflow:'auto',不检测「固定 height + hidden」组合 - 反思:是否扩展 scan 检测「
height: fixedValue+ 外层 overflow:'hidden'」?暂不做(误杀率高) - 替代:加 dogfood 检查清单「多窗口尺寸测试分页器可见」
- ui-change-pre-check 加「成对字段」规则(待补) — email/phone, fb/ig/li/tw/yt 一改成对
受影响 ISSUE / SPEC¶
- 新建 ISSUE-0081 / 0082 / 0083
- 嵌套滚动家族增到 5 次:0007/0020/0069/0078/0081
2026-05-30 v0.10.118 → v0.10.119 沉淀收尾 — scan:nested-scroll + commit-msg ISSUE 文件校验¶
背景¶
v0.10.118 完成后做系统审计(用户问"是不是都解决了"),发现 2 个瑕疵:
- ISSUE-0078 嵌套滚动家族第 4 次(0007/0020/0069/0078)但没自动检测脚本 → 第 5 次还会再来
- ISSUE-0079 元 bug(commit message ISSUE-XXXX 是空头支票)只写了 rule,没落到 hook → 还能再发
本版补这 2 个自动化防线。
本次改动¶
#1 新脚本 scripts/scan-nested-scroll.py¶
# 检测逻辑
1. 扫 src/sections/**/*.tsx + src/components/**/*.tsx
2. 文件含 VIRTUALIZED_COMPONENTS(DataGrid / ClientDataTable / LocalDataTable 等)
3. 行级匹配 overflow:\s*['"]auto['"](或 overflowY/X)
4. 行附近无 `// SAFE: nested-scroll-ok` 注释 → 报警
启发式:「文件含虚拟化列表」+「overflow:'auto'」= 99% 是嵌套滚动坑。
#2 pre-commit hook 加第 8 项¶
任何 src/(sections|components)/*.tsx 改动 → 跑 scan:nested-scroll --strict。命中阻止 commit。
#3 commit-msg hook 加 ISSUE 文件存在校验¶
放在 hook 顶部(早于现有检查):
REFERENCED_ISSUES=$(echo "$MSG" | grep -oE 'ISSUE-[0-9]{4}' | sort -u)
for issue in $REFERENCED_ISSUES; do
NUM="${issue#ISSUE-}"
# 工作区 OR staged 新建都算存在
if ls docs/issues/${NUM}-*.md >/dev/null 2>&1; then continue; fi
if git diff --cached --diff-filter=A | grep -qE "docs/issues/${NUM}-"; then continue; fi
MISSING="$MISSING $issue"
done
if [ -n "$MISSING" ]; then 阻止 commit; fi
自校验通过:
- "fix ISSUE-9999" → ❌ 阻止(不存在)
- "fix ISSUE-0080" → ✓ 通过(v0.10.118 已建)
#4 package.json 加 pnpm scan:nested-scroll¶
便于本地手动跑。
遇到的问题¶
无新增。
注意点¶
- scan:nested-scroll 当前 0 命中(v0.10.117 修了 data-view.tsx 那处后干净)
- commit-msg hook 不影响 v0.10.117 的 ISSUE-0074 补建(那个文件现在工作区有)
- 元 bug 防护从"rule 文档"升级到"hook 强制" — 不再依赖 AI/人记得
受影响 ISSUE / SPEC¶
- ISSUE-0078 嵌套滚动家族:从「rule 沉淀」升级到「自动检测 + pre-commit 强制」
- ISSUE-0079 元 bug:从「rule 文档」升级到「commit-msg hook 强制」
- 整体覆盖:现在 v0.10.116 沉淀的所有家族 bug 都有自动检测(50k 窗口 / 嵌套滚动 / 元 bug)
2026-05-30 v0.10.117 → v0.10.118 phone 抓 GPS 坐标污染修复 + 三层防御 (ISSUE-0080)¶
用户反馈¶
截图 Homer Veterinary Clinic 行:HTTP tooltip "44 条电话(来自他人共享)",但电话列只显 1 个。
日志确认 phoneList 是 ['0.5 15.0234', '11.5916 59.989', ...] — 44 条里 43 条是 GPS 坐标。
根因¶
scraper.ts:177 PHONE_TEXT_REGEX 匹配 数字.数字 数字.数字 模式:
- "11.5916 59.989" 满足 \d{1,3}[\s.\-](\d{1,4}[\s.\-]){1,3}\d{2,9} ✓
- 后续 addPhone digits 长度过滤只看位数(10 位过 [8,15] 检查)
- 过滤层只看长度不看格式分隔符 → 坐标当电话入库
更严重:写入了 ContactPool 共享池 → pool-hit 时全球用户拿到同样垃圾 → 云端同步开启会扩散。
本次改动¶
1. 公用 helper isLikelyCoordinateNoise() (scraper.ts)¶
export function isLikelyCoordinateNoise(rawPhone): boolean {
const norm = rawPhone.trim();
if ((norm.match(/\./g) || []).length >= 2) return true; // ≥ 2 小数点
if (/^\d{1,2}\./.test(norm)) return true; // ≤2 位 + 点开头
return false;
}
2. 三层防御¶
| 层 | 时机 | 文件 |
|---|---|---|
| 写入侧 | addPhone 提取后 |
scraper.ts |
| 读取侧 | ContactPool 查询命中后 | contact-pool.ts queryContactPool* |
| 存量迁移 | engine session 启动跑一次 | contact-pool.ts cleanPollutedContactPool() |
engine-manager.ts manageQueue 首次进入跑三件事(已含 revivePollutedDomains / batchDedupeByUrl)。
3. sysLog 可观测¶
contact-pool / cleaned-polluted { scanned, cleaned, totalPhonesRemoved }— 启动迁移产出contact-pool / hit加cleanedDirty字段 — 每次 pool-hit 清掉的坐标数
沉淀(修+沉同步做)¶
- 新 ISSUE-0080:phone 正则把 GPS 坐标当电话 — 完整根因 + 数据流污染分析 + 修复
- 新 rule
docs/rules/data-extraction-quality-defense.md: - 三层防御模板(写入/读取/存量)
- 决策表(何时必须 3 层)— 共享池/云端字段必 3 层
isLikelyXxxhelper 命名规范 + 单元测试友好- 必问清单 + 当前合规处审计表
- rules/INDEX 加链接(状态机/缓存/抓取设计 分组)
遇到的问题¶
无新增。
注意点¶
- 用户装上后 engine 第一次跑:sysLog 会出
cleaned-polluted显示清理多少条 - pool-hit 时新增
cleanedDirty字段 — 老数据被读取时自动清洗 - 共享池字段的 validation 责任 10× 于单用户 UI — 一旦污染传染全球
- 严格按 ISSUE-0079 rule 操作:先 git add 新文档再 commit
受影响 ISSUE / SPEC¶
- 新建 ISSUE-0080:phone GPS 坐标污染
- 新建 rule data-extraction-quality-defense.md
- SPEC-004 Phase 3(ContactPool 设计层补漏 — 共享池必须三层防御)
2026-05-30 v0.10.116 → v0.10.117 UI bug 修复 + 沉淀审计 + 元 bug 补救¶
用户反馈¶
用户截图官网列表 v0.10.114: 1. 邮箱列内容靠右显示(与其他左对齐列不一致) 2. 右侧出现嵌套滚动条 3. 要求"以上进行沉淀,尤其是要点"+"检查前几个问题是否有做记录"
沉淀审计发现的元 bug¶
回头审计 ISSUE-0070~0076:
但 v0.10.112 commit message 明确写"ISSUE-0074"。深查 git log --diff-filter=A 确认文件从未进入 git。
时序回溯:
1. v0.10.112 时 Write 了 docs/issues/0074-...md
2. 同 commit 内跑了"清理 130 个 untracked 中文文件"脚本
3. 该脚本 git ls-files --others --exclude-standard docs/ | xargs rm 删 ALL untracked
4. 刚 Write 还没 git add 的 ISSUE-0074 一起被删了
5. 后续 git add -A 时文件已不存在 → 没进 commit
6. commit message 写 ISSUE-0074 但是空头支票
5 个版本后被用户审计眼力发现 — 比 product bug 更隐蔽(commit log 看着正常)。
本次改动¶
代码 2 处(UI bug)¶
data-view.tsx邮箱列:去掉type:'number'(MUI 默认右对齐),改align:'left' + headerAlign:'left' + sortComparator,宽度 220 → 240data-view.tsx嵌套滚动:外层overflow:'auto'→'hidden',DataGrid 独占滚动
沉淀 4 件(含补救)¶
- 补建 ISSUE-0074:凭 v0.10.112 commit message + 开发日志重写完整文档
- 新建 ISSUE-0077:邮箱列 type:'number' 错对齐
- 新建 ISSUE-0078:data-view tab 嵌套滚动条(家族 bug 第 4 次:含 ISSUE-0007/0020/0069)
- 新建 ISSUE-0079 元 bug:批量 rm untracked 误删新文档(系统性记录这次审计发现)
- 新 rule
cleanup-untracked-safety.md:防再犯 - 步骤 1:先
git add本会话所有 Write 的文档 - 步骤 2:
git clean -fdndry-run 看清单 - 步骤 3:用具体 pattern 不要盲删
- 步骤 4:commit 后
git show HEAD --反向校验 ISSUE 文件真在 rules/INDEX.md加链接
遇到的问题¶
问题:commit message 是空头支票¶
5 个版本前的"ISSUE-0074"引用了不存在文件 — 文档不可信。 没有自动机制检测,纯靠用户发现。
修复教训¶
未来 pre-commit 加自动检查(待后续):commit message 提到 ISSUE-XXXX 时确认 docs/issues/XXXX-*.md 真存在。
注意点¶
- ISSUE-0078 是嵌套滚动家族 bug 第 4 次(ISSUE-0007/0020/0069/0078)— 该写自动检测了
- ISSUE-0077 「type:'number' 默认 align:right」属于 MUI DataGrid 强耦合陷阱,加进 ui-change-pre-check 案例
- ISSUE-0079 是元 bug:修 bug 工作流本身有 bug,破坏可信度,比 product bug 更严重
受影响 ISSUE / SPEC¶
- 补建 ISSUE-0074(v0.10.112 时漏的)
- 新建 ISSUE-0077:邮箱列对齐
- 新建 ISSUE-0078:data-view 嵌套滚动家族第 4 次
- 新建 ISSUE-0079:元 bug — 批量 rm 误删新文档
- 新 rule cleanup-untracked-safety
- 进入 rules/INDEX「外部工具 / 版本控制」分组
2026-05-30 v0.10.115 → v0.10.116 域名状态机修复 — 404 root fallback + 砍 ok-empty 升 cold + 复活迁移 (ISSUE-0076)¶
用户反馈¶
3 个网站手动浏览器都能打开,但被状态机标 dead/cold:
- tingeyorthodontics.com → dead(只有 1 次 HEAD 探测)
- jbjcortho.com/gustavo-garcia-md/ → dead(首页 ok-contact 后子页 HEAD dead 一次就降级)
- moleskidental.com → cold(5 个不同子页 ok-empty)
用户洞察:"5 次是同 URL 还是不同子页?无 contact 不代表死站,可能就是没有" — 完全正确。 后续追问:"404 是不是应该尝试首页?" — 关键改进方向。
导出日志:2939 dead + 37 cold,大量误判。
根因 — 3 个独立设计错误¶
ok-empty升级cold是「页级 → 站级」信号污染(5 个不同子页没找到 contact,整域名跳过)- 单次
dead直接降级,无视历史 ok 记录(首页成功的网站被一次子页失败打死) - 404 不试 root URL(子页 404 不代表整站死,但代码直接 dead)
本次改动¶
#1 probe.ts 加 404 → root URL fallback¶
const directResult = await probeSingleUrl(url, ...);
if (directResult.kind === 'dead' && (reason === '404' || reason === '410')) {
const rootResult = await probeSingleUrl(getRootUrl(url), ...);
if (rootResult.kind !== 'dead') {
return { ...rootResult, finalUrl: rootUrl }; // root 活,用 root
}
}
probe.finalUrl 改为 root URL 后 pipeline 自动跟着用 root(fetchUrl = probe.finalUrl || url 已支持);
ContactPool 按 root URL hash 缓存;v0.10.114 batchDedupeByUrl 让同 root 多商家共享 contact。
#2 domain-state.ts dead 加历史感知¶
if (outcome === 'dead') {
const hasOkHistory = stat.recent.some(e => e.outcome === 'ok-contact' || e.outcome === 'ok-empty');
if (hasOkHistory) {
const recentDead = stat.recent.slice(-3);
if (recentDead.every(e => e.outcome === 'dead')) setState(stat, 'dead');
return;
}
setState(stat, 'dead');
}
#3 domain-state.ts 砍 ok-empty → cold¶
直接删除整条规则。ContactPool url-hash 已做页级去重缓存,不需要域名级升级。
#4 revivePollutedDomains() 启动迁移¶
engine session 首次 manageQueue 跑一次(dedupeRanThisSession 同款标记):
- 所有 cold → 复活 unknown
- 所有 dead + recent 含 ok-* → 复活 unknown
- 累计统计保留(fetchTotal/fetchOk 等)
#5 domain-state-panel.tsx 加「复活误判」按钮¶
设置面板:用户手动触发 revivePollutedDomains,toast 显示「已复活 N / M」。 原有「全部重置」按钮保留。
#6 mstage-classify.ts UI 文案修正¶
mstage:no-contact→ "此页面没找到联系方式(不代表整站没有;可能首页/contact 页才有)"mstage:dead→ "网站完全无法访问(DNS / 超时;首页也失败)"mstage:domain-cold→ "v0.10.116 起此规则已废除,下次会自动重试"
沉淀(这次修+沉同步做,不再像 v0.10.114 等补版本)¶
新 ISSUE-0076 docs/issues/0076-...md¶
页级 vs 站级信号污染家族 bug 完整记录。
新 rule docs/rules/signal-dimension-discipline.md¶
「信号维度纪律 — 页级信号不能升级到站级」: - 维度层级(row → URL → domain → global) - 反模式 3 个(单次升级 / 页级污染站级 / 不试 fallback) - 正模式 3 个(历史感知 / 页级缓存 / fallback 链) - 决策表(每种 outcome 该影响什么维度) - 必问清单 / 当前合规处 / 历史复发表
rules/INDEX.md 加「状态机 / 缓存 / 抓取设计」分组¶
遇到的问题¶
无新增。
注意点¶
- 用户重启扩展后第一次开 engine:sysLog 会出
domain-state revive-polluted+pipeline batch-dedupe-applied - 预期复活:37 cold 全复活 + ~60-80% dead 复活(含 ok 历史的)
- 404 fallback 几乎 0 成本(root URL probe 已有性能预算)
- ContactPool / batchDedupeByUrl 的协同效应:root URL pool-hit 让同 root 多商家全部秒标
受影响 ISSUE / SPEC¶
- 新建 ISSUE-0076:域名状态机被页级信号污染
- 新建 rule signal-dimension-discipline:覆盖 ISSUE-0075 + ISSUE-0076 家族
- 影响 SPEC-004 Phase 2(域名状态机设计补漏)
2026-05-30 v0.10.114 → v0.10.115 ISSUE-0075 补沉淀 — 2 新 rule + 扩展 ui-change-pre-check + 沉淀检查表¶
背景¶
用户问"问题沉淀了吗?" — 诚实回答:v0.10.114 只到 30%。 对比 v0.10.113(ISSUE-0074)有完整 rule + scan + pre-commit + 存量审计;v0.10.114 只到 ISSUE 文档 + 修复。 本版补到位。
本次改动¶
1. 新 rule docs/rules/ui-column-dimension-consistency.md¶
UI 列必须维度一致;row 字段 + URL 聚合混搭 = 隐性 bug。 - 反模式:列 A 看 row、列 B 看 websiteLogMap (URL 维度) → 视觉矛盾 - 正模式:方案 1 写回 row 字段;方案 2 列名/tooltip 明示维度;方案 3 score 选代表 - 决策表 + 检查清单 + 当前合规处审计
2. 新 rule docs/rules/group-by-best-representative.md¶
分组聚合取代表 — 「按 score」不是「取第一个」。
- 反模式:for(r of rows) if(!seen) push(r) 「先到先得」(输入顺序决定代表)
- 正模式:score(r) 函数 + byKey.set(k, score(r) > score(byKey.get(k)) ? r : existing)
- 决策表:URL 去重、邮箱去重、电话去重、域名 group 各自的 score 设计
- 反例 vs 正例代码对照
3. 扩展 docs/rules/ui-change-pre-check.md¶
- 加案例 4(v0.10.114 ISSUE-0075):列维度不一致
- 加新章节"加新 UI 列时的额外检查":维度问题 4 条 checklist
- 双向链接到 2 个新 rule
4. 扩展 docs/rules/per-version-sinking-checklist.md¶
加新章节「UI 列 / 表格渲染改动必做项」(v0.10.115 起强制): - 数据源维度审计 - 视觉矛盾对比检查 - 去重视图 score 检查 - 引用 2 个新 rule
5. docs/rules/INDEX.md 新分组¶
新增「UI 列 / 表格设计」分组,列出 3 个 rule(ui-change-pre-check / ui-column-dimension-consistency / group-by-best-representative)。
遇到的问题¶
无新增。
注意点¶
- scan 脚本未建 — 这两个反模式 grep 模式不好抓(任何"列 A 看字段 B 看其他"都可能合理) → 选择"人工 review checklist"路线,加到 per-version-sinking 强制
- 沉淀深度对比 v0.10.113:rule ✅ + scan ❌(人工 review)+ checklist ✅ + 存量审计已在两个新 rule 里完成
- 下次加新 UI 列前 AI 应该主动查这 3 个 rule
受影响 ISSUE / SPEC¶
- ISSUE-0075 通用模式:从「记录 + 修复」升级到「rule 强制 + 检查表 + 案例存档」
2026-05-30 v0.10.113 → v0.10.114 URL 维度去重 — 批量复用 + 代表行 status 优先 (ISSUE-0075)¶
用户反馈¶
用户截图官网列表去重视图:多行显示「状态:待采集 + HTTP:✓已采」的视觉矛盾。 问:"为什么后边是已采,前边还是待采?"
根因¶
两列看不同维度(root cause analysis 见 ISSUE-0075):
- 状态列看 row.scrape_status — row 单行维度
- HTTP 列看 websiteLogMap.get(url) — URL 维度(任意 row 的 page-log)
连锁店共享 URL 时,一行被 engine 采完 → 同 URL 其他行仍 status=0 在 queue 等 → 官网去重视图碰巧拿到 status=0 那行作代表 → 状态列显示"待采集" → HTTP 列看 URL 历史显示"✓已采"
本次改动¶
A:engine 批量复用(治本) — engine-manager.ts¶
新函数 batchDedupeByUrl():
1. 拉 status=2 行 → 建 url→source row 索引(emails 非空优先做代表)
2. 拉 status=0 行 → 按 source row 分组
3. updateByQuery(id IN [...], { emails, socials, scrape_status:2, sync:0 }) 批量复用
调用时机:
- 每个 SW session 跑一次(module-let dedupeRanThisSession 标记)
- 用户「立即触发」→ markDedupeNeedsRerun() 重置标记 → 下次 manageQueue 跑
收益:
- 视觉矛盾消除(同 URL 多行一起 status=2)
- 节省 worker 重复跑相同 URL(用户 335 商家 / 238 URL → ~100 行可免跑)
- sysLog 记 pipeline batch-dedupe-applied { urls, rows } 可观测
B:官网去重代表行选 score 高的(兜底) — data-view.tsx¶
const score = r => (emails?.length>0 ? 2 : 0) + (scrape_status===2 ? 1 : 0);
const byUrl = new Map();
for (const r of rows) {
if (!byUrl.has(k) || score(r) > score(byUrl.get(k))) byUrl.set(k, r);
}
即便 A 还没跑完(早期)或漏窗(pending > 10w),UI 也会优先展示已采过的代表行。
遇到的问题¶
无新增。
注意点¶
- A 是 fire-and-forget — manageQueue 不等它跑完(不阻塞 engine 节奏)
- A 用 100k limit window(status=2 / status=0 各一个)— SAFE 注释豁免 scan:count-window
- batchDedupeByUrl 失败(一组)不影响其他组
- 「立即触发」按钮路径:sendMessage 'start-deep-scrape' → markDedupeNeedsRerun → manageQueue → batchDedupeByUrl
受影响 ISSUE / SPEC¶
- 新建 ISSUE-0075:row 维度 vs URL 维度状态不一致 — 设计层修复
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 版本族划分,老归档不再变动。