# FlareDrive 回归测试清单 > 最近一轮:2026-09-06 0a-16(筛选预设选中后 ✕ 同步,issue #84) > 前几轮:2026-09-06 0a-15(筛选预设 + 可移植性文档,issue #63 #64 P2 收尾)、2026-09-06 0a-14(书签库批量操作 + 置顶/文件夹管理 + 写回浏览器书签栏 + HamHome 往返,issue #63 #64)、2026-09-06 0a-13(库页面实时同步外部收藏写入 + 显式刷新入口,issue #77)、2026-09-06 0a-12(工具栏收藏弹窗 + 快捷键 + omnibox,issue #62)、2026-09-06 0a-11(扩展书签导入导出改为文件 + 收编 HamHome 一级入口,issue #65)、2026-09-06 0a-10(扩展设置并入主页 + 首次使用强制配置 + 删除 newtab 变体;书签库 UI 打磨 #57)、2026-09-05 0a-8(扩展网盘视图去 iframe,原生挂载 Web 端 React App)、2026-08-30 第三轮四批(Sites 管理面板 + MCP 深化 + davflare-cli)、2026-08-29 第六批(缺陷修复 + B5/B9/C9 收尾 + i18n 第二阶段收编 + 界面美化 + 单测/e2e 落地)、第五批(A9 i18n 第一阶段 + scripts/api-e2e.sh 回归套件沉淀)、第四批(多选拖拽/面包屑下拉/图标扩展)、第三批(目录分享/回收站清理/搜索高亮/动效 11 项)、第二批(目录级 API 等)、首批(暗色模式等) > 约束:所有测试数据操作仅在自己创建的目录内进行,测试后清理。 ## 0a-16. 筛选预设选中后 ✕ / 下拉同步(issue #84,2026-09-06) ### 改动 | 项 | 内容 | |----|------| | 根因 | `applyPreset` 只改 filter/since 并 `renderAll()`,未刷新预设下拉/✕;`renderAll` 与 `sinceSelect` 也不回写 preset chrome,导致选中已有预设后筛选已生效但 ✕ 偶发不出现 | | 修复 | `BookmarksView.findActivePreset` 抽出匹配逻辑;`renderAll` 与 `since` 变更均调用 `renderPresetSelect`,选中预设后立即同步下拉选中态与 ✕ | | 版本 | extension manifest 1.3.7 → 1.3.8 | ### 待手动回归(Chrome 加载扩展) 1. 存预设 → 清空筛选 → 下拉选中该预设:标签+时间生效,且 ✕ **立即**出现(无需再保存)。 2. 手动改 `since` 使不再匹配 → 下拉回占位、✕ 隐藏;再改回匹配值 → 下拉选中且 ✕ 再现。 3. 点侧栏其它标签/全部后预设 chrome 同步清空;再点回匹配标签+时间后恢复。 ## 0a-15. 筛选预设 + 可移植性文档(issue #63 #64 P2 收尾,2026-09-06) ### 改动 | 项 | 内容 | |----|------| | 筛选预设 | 顶栏新增「筛选预设」下拉 + +/✕:把当前「标签+时间」筛选组合存为预设(chrome.storage.sync `bookmarkPresets`,`normalizePresets` 规范化、上限 12、同名覆盖),选中即应用;当前筛选恰为某预设时显示 ✕ 删除(带确认);非标签筛选时点 + 提示 | | 可移植性文档 | 新增 `docs/bookmarks-portability.md` / `.zh-CN.md`:三种格式权威面、导入嗅探规则、字段映射表(Davflare ↔ HamHome)、冲突策略(If-Match/412、HamHome 写回永不删除、分类签名合并幂等、Netscape 成员权威) | ### 待手动回归(Chrome 加载扩展) 1. 点某个标签进入筛选 → 选好时间范围 → 顶栏 + 输入名称保存 → 预设出现在下拉;切换到其它筛选再选回该预设,标签+时间组合即时生效。 2. 当前筛选恰好等于某预设内容时下拉自动选中且 ✕ 出现;点 ✕ 确认后预设消失。 3. 未选标签筛选时点 + → 顶部横幅提示先选标签;同名保存会覆盖旧预设。 4. 中英文案与暗色主题下预设控件样式正常;顶栏在窄窗口不溢出(预设下拉限宽 170px)。 ## 0a-14. 书签库批量操作 + 置顶/文件夹管理 + 可移植性写回(issue #63 #64,2026-09-06) 对照 HamHome 非 AI 库页补齐 Davflare 书签库的交互能力(#63 P0/P1),并把可移植性从单向导入扩展为双向(#64 P1)。 ### 改动 | 项 | 内容 | |----|------| | 多选 | 网格/列表每条书签带复选框;Shift+点击区间选择;顶栏「全选/取消全选」作用于当前筛选结果 | | 批量栏 | 选中后顶部出现批量栏:已选计数 + 移动(datalist 可输入新路径)/ 标签(批量加/减)/ 置顶·取消置顶 / 删除(带确认)+ 清除选择;一次操作处理全部选中项,单次 If-Match PUT 收敛 | | 置顶 | 模型新增 `pinned`/`pinnedAt`(sanitize/normalize 保字段,JSON sidecar 持久化,`adoptRichFields` 回填);列表置顶区按 pinnedAt 倒序在前;卡片/行有 📌 标记与行内按钮、⋯ 菜单项;侧栏新增「置顶」入口(含空态) | | 文件夹 | 模型新增声明式 `folders` 数组(空文件夹可存在,随 JSON/HTML 往返);侧栏「分类」标题旁+新建;文件夹行 hover ⋯ 菜单支持重命名(含子路径级联改写)与空文件夹删除;空文件夹与筛选空态一致展示 | | 写回浏览器书签栏 | 导出对话框新增「浏览器」区:目标文件夹下拉(来自 chrome.bookmarks.getTree)、跳过重复(urlKey 对比)、可选先清空目标夹(确认);按 buildChromeWritePlan 计划递归创建;bookmarks 权限仍为 optional,写回时按需申请 | | HamHome 往返 | `HamHome.exportTo(model, remoteMeta, remoteCats, now)` 把当前库合并写入 /HamHomeSync/bookmarks/meta.json + categories.json(categories.json 先写、meta.json 后写,与 HamHome 自身安全写序一致);合并语义:同 URL 复用 HamHome 原条目 id 原地更新,新条目用 dav- 确定性 id,HamHome 已有条目永不删除;分类按 name_parentId 签名复用、缺省以路径为 id 声明 | | 顺手修复 | `applyCopy` 中 `t.settingsUrlHint` 引用了不存在的 key,设置页「实例地址」提示文案从未显示;改为 `t.urlHint` | ### 单元测试 `extensionBatch.test.ts`(pinned 字段/批量移动/加减标签/批量删除/文件夹增删重命名/HTML 往返含空文件夹/buildChromeWritePlan 去重)、`extensionHamhome.test.ts`(exportTo 往返、分类签名合并、isDeleted 不吞更新、hash URL 等价)、`extensionBookmarksView.test.ts`(空文件夹列表、pinned 筛选、置顶排序);`npm test` 901 例全绿,`tsc --noEmit` 通过。 ### 待手动回归(Chrome 加载扩展) 1. 多选:列表页勾选若干书签 → 批量栏出现;Shift+点击做区间选择;「全选」后批量删除 ≥10 条 → 一次确认后远端 bookmarks.html/json 同步更新。 2. 批量移动/标签:选中多条 → 移动到新输入的路径(如 qa/tmp)→ 库页与 JSON sidecar 均出现新分类;批量加减标签后搜索可命中。 3. 置顶:单条/批量置顶 → 列表置顶区按最近置顶在前,侧栏「置顶」计数正确;刷新页面、重新打开库页(缓存回放)后置顶仍在;导出 JSON 再导入,置顶保留。 4. 文件夹:+新建空文件夹 → 侧栏出现且空态展示与筛选空态一致;重命名含子层的分类 → 卡片分类 chip 与计数跟随;删除空文件夹后侧栏消失。 5. 写回浏览器书签栏:导出对话框选择目标文件夹 → 写回后 Chrome 书签管理器可见(含空文件夹结构);再次写回勾选「跳过重复」不产生重复项;勾选「先清空」并确认后目标夹旧内容被移除。 6. HamHome 往返:已用 HamHome 同步过的实例上执行「写回 HamHomeSync」→ HamHome 端下一次同步能把 Davflare 新增条目拉进本地库,且 HamHome 原有条目无丢失;重复执行写回幂等(不新增重复分类/条目)。 7. 中英文案、明暗主题下新增批量栏/菜单/对话框样式与整体一致。 ## 0a-13. 库页面实时同步外部收藏写入 + 显式刷新入口(issue #77,2026-09-06) 修复「右键/弹窗收藏成功(远端已写入)后,已打开的库页面搜索仍搜不到新条目」的偶发陈旧:popup / quickSave 与库页面之间此前没有任何通知通道,库页面只在整页刷新时才重读缓存。 ### 改动 | 项 | 内容 | |----|------| | 实时应用外部写入 | `bookmarksApp.js` 新增 `chrome.storage.onChanged` 监听(仅 local 区 `bookmarksCache`):popup / 右键快藏成功后写回的最新 model+etag 即时并入内存态并重渲染,搜索词/筛选保持不变,命中新收藏 | | 自写回声过滤 | `applyExternalCache` 对 `syncedAt === state.syncedAt` 的变化(refresh/persist 自身 `saveCache` 的回声)直接忽略,避免重复渲染闪烁 | | 在途同步守卫 | `refresh()`/`persist()` 以 `inflightSync` 计数标记在途;在途时暂缓应用外部缓存,等 PUT/GET 收敛(成功写回或 412→refresh),避免中途替换内存 model/etag 吞掉本地变更或让条件请求失效(If-Match 保证最终一致) | | 显式刷新入口 | 书签视图顶栏新增「刷新」按钮(`#libRefresh`,中英文案齐全):走既有 `refresh()` 条件 GET,远端未变时 304 秒回;外部缓存写入失败(如 quota)等漏报场景用它兜底 | ### 待手动回归(Chrome 加载扩展) 1. 打开书签库页签并停留在搜索结果页 → 另一标签页右键「收藏此页到 Davflare」→ 不刷新库页签,直接搜索刚收藏的关键词即可命中;列表计数/分类/标签同步更新。 2. 同场景改用工具栏弹窗收藏(填分类/标签)→ 库页签同样即时可见。 3. 库页签保持打开,点顶栏「刷新」→ 304 秒回(列表不闪、无网络大流量);远端被其它端改动后点刷新 → 新内容立刻出现。 4. 库页面内增删改书签 → 行为与之前一致(无重复渲染闪烁、412 冲突仍走重载提示)。 5. 「刷新」按钮中英文案随浏览器语言切换,明暗主题下样式与顶栏其它控件一致。 ## 0a-12. 工具栏收藏弹窗 + 快捷键 + omnibox(issue #62,HamHome 式;2026-09-06) 工具栏左键从「直开主页」改为弹出收藏弹窗;主页入口移入弹窗 footer。P1 补齐可配置快捷键与 omnibox 检索。 ### 改动 | 项 | 内容 | |----|------| | 弹窗 | 新增 `popup.html/css/js`:页头(favicon+标题+URL)、标题/分类(datalist 选现有分类,含祖先前缀)/标签三个输入、收藏按钮 + 行内状态(保存中/已收藏/已存在/错误);右键一键收藏(contextMenus)保留 | | 弹窗状态机 | loading → ready;非 http(s) 页显示「只能收藏」;未配置实例显示引导 + 「去设置」按钮;加载失败显示错误 + 重试;412 冲突自动重载远端并保留用户输入;成功后禁用按钮并记忆 `popupLastFolder` | | 主页入口 | 弹窗 footer「插件主页 / 设置」:优先聚焦已开的 bookmarks.html 标签页,否则新开(?view= 指定视图);复用已开标签时若指定 view 会导航过去(settings 修复) | | manifest | `action.default_popup: "popup.html"`;描述同步更新 | | 快捷键(P1) | manifest `commands.save-current-page`(建议 Alt+Shift+S):优先 `chrome.action.openPopup()` 弹出收藏弹窗(与左键同一 UI);openPopup 不可用/失败(旧 Chrome)退化为后台静默收藏(复用右键菜单的 savePage 路径) | | omnibox(P1) | manifest `omnibox.keyword: "df"`:地址栏输入 `df <词>` 用 `Bookmarks.searchBookmarks`(title/url/标签/分类大小写不敏感子串 AND 匹配,不引拼音词典)检索 `bookmarksCache` 缓存;首条设为默认建议,回车/下拉选择按 disposition 打开;无命中回退书签库页 | | background | 删 `chrome.action.onClicked` 直开逻辑(`handleToolbarClick`/`openLibraryPage`);右键「切换工具栏默认模式」改为「切换插件主页默认视图」,通知文案同步 | | 主页默认视图 | shell 初始视图改由 `resolveToolbarTarget` 决定(显式 `?view=` 优先;未配置实例落 settings 保持先配置后使用);设置页文案改为「插件主页默认视图」 | | Bookmarks API | 新增 `folderPaths(model)`:排序去重的分类路径(含祖先前缀),供弹窗 datalist;新增 `searchBookmarks(model, query, limit)` 供 omnibox | | 设置页 | modeLabel/modeHint/settingsCleared 文案更新为「插件主页默认视图」语义 | ### 待手动回归(Chrome 加载扩展) 1. 任意网页点工具栏图标 → 弹出收藏弹窗,favicon/标题/URL 正确;填分类与标签 → 收藏 → 提示「已收藏」,书签库中可见且分类/标签正确。 2. 同一页面再点图标 → 显示「该页面已在书签库中」且收藏按钮禁用。 3. 弹窗「插件主页」→ 落在设置的默认视图;弹窗「设置」→ 落在设置视图;已开主页标签页时聚焦复用并导航到目标 view(非仅聚焦)。 4. chrome:// 页面点图标 → 显示「只能收藏 http(s) 页面」,表单隐藏。 5. 未配置实例时点图标 → 引导文案 + 「去设置」;配置保存后弹窗可正常收藏。 6. 断网/错误凭据点图标 → 对应错误文案 + 重试按钮;412 冲突 → 自动重载并提示再点一次。 7. 右键工具栏图标「切换插件主页默认视图」→ 通知正确、主页初始视图随之切换;右键页面「收藏此页到 Davflare」一键收藏不受影响。 8. 明暗主题:弹窗跟随系统/主页主题设置。 9. 快捷键(chrome://extensions/shortcuts 可改)按 Alt+Shift+S → 弹出收藏弹窗且预填当前页;在 openPopup 不可用的环境退化为通知式静默收藏。 10. 地址栏输入 `df` + 空格 → 提示「搜索 Davflare 书签…」;输入已藏站点关键词 → 下拉出标题+标签+URL 建议,回车打开;`df 不存在的词` 回车 → 打开书签库页。 ## 0a-11. 扩展书签导入导出改为文件 + 收编 HamHome 入口(issue #65,2026-09-06) 「导入」从 Chrome bookmarks API 改为文件优先(JSON/HTML),导出补 JSON 完整备份,HamHome 独立一级入口收进导入对话框。 ### 改动 | 项 | 内容 | |----|------| | 导入对话框 | 「⋯ 更多 → 导入」改为弹导入面板:主按钮「选择文件…(JSON / HTML)」走系统文件选择器(点不动问题闭环);「从浏览器书签导入」(原一级行为,仍走 bookmarks 可选权限)与「从本实例 HamHomeSync 目录导入」(原独立 HamHome 一级按钮)收编为面板内次要选项;空态按钮从三收二(添加 / 导入) | | 格式识别 | `Bookmarks.sniffJsonImport` 按条目字段嗅探 JSON 归属(Davflare: folder/note/added/id;HamHome: categoryId/description/createdAt,裸数组也算 HamHome),`Bookmarks.importBackup` 统一入口吃 Davflare JSON / HamHome JSON(含内联 categories)/ Netscape HTML;`HamHome.importFrom` 支持直接传已解析对象 | | 导出对话框 | 「⋯ 更多 → 导出」弹导出面板:HTML(Netscape,浏览器可再导入)+ JSON(完整备份 davflare-bookmarks.json,含 folder/tags/note);下载逻辑抽 `downloadText` 共用 | | 去重与文案 | 导入一律按 URL 合并去重(`mergeModels`),成功提示新增条数;解析失败/文件无书签分别有明确文案(importInvalid / importEmpty,显示在面板内) | ### 待手动回归(Chrome 加载扩展) 1. 「⋯ 更多 → 导入」弹面板;选 HamHome 导出的 meta.json(或含 categories 的合并 JSON)→ 提示导入条数,文件夹路径正确;选浏览器导出的 HTML → 导入成功。 2. 重复导入同一文件 → 提示「没有需要导入的新书签」;选无关文件(乱码 JSON/无书签 HTML)→ 面板内明确报错。 3. 「⋯ 更多 → 导出」分别下载 bookmarks.html 与 davflare-bookmarks.json;HTML 可被 Chrome 书签管理器再导入;JSON 重新导入后标签/备注/文件夹不丢。 4. 侧栏「⋯ 更多」只剩导入/导出两项;空态只剩「添加 / 导入」;全 UI 无一级 HamHome 入口;导入面板内两个次要选项可用。 ## 0a-10. 扩展书签库 UI 打磨(issue #57,借鉴 HamHome 观感;2026-09-06) 只动 UI/交互呈现,不动 WebDAV 同步协议。 ### 改动 | 项 | 内容 | |----|------| | 空态(P0) | 结构化空态组件 `renderEmptyState`:内联 SVG 插画 + 标题/描述 + 按钮组。书签库空态带「添加 / 导入浏览器书签 / 从 HamHome 迁入」三按钮;筛选无结果带「清除筛选」(同时清空搜索词与时间范围);工作区空态带「保存当前窗口」、Tab 分组空态带「按规则分组当前窗口」 | | 侧栏主次(P0) | 底部重排:主按钮「添加」独占一行 → 「⋯ 更多」菜单收纳导入/HamHome/导出 → 分隔线下「网盘 / 设置」弱化行(透明底灰字,hover 浅橙) | | 卡片(P0) | favicon 块 34→40px(_favicon 改请求 size=64 保 retina 清晰)、note 两行截断、标签芯片收窄(padding 1px 7px);✎/✕ 换成右下角 ⋯ 菜单(编辑/快照/删除,快照项打开编辑弹窗并滚动到快照区) | | 导航选中态(P0) | 侧栏与顶部切换的 active 从整块实心橙改为浅橙底 + 深橙字,侧栏另加左侧 3px 指示条;count 徽标改纸底 | | 顶栏对齐(P0) | 搜索/两个 select/视图切换/主题按钮统一 36px 高、0.875rem 字号、10px 圆角 | | 列表密度(P1) | 行改为 favicon | 标题+域名(两行堆叠)| 分类+标签芯片 | 时间 | 操作,并补上标签列 | | 空占位(P1) | 空分类/空标签有专属标题 + 清除筛选引导 | | 暗色对比(P1) | 暗色 --bg 加深(#14110c)、--line 提亮(0.13),减少灰橙糊 | | 菜单基建 | `popMenu`/`menuToggle` 委托:document 级开合、点外关闭、aria-expanded;卡片 ⋯ 不经 iconButton(其 stopPropagation 会挡委托) | ### 待手动回归(Chrome 加载扩展) 1. 空库 → 空态插画 + 三个入口都能完成对应动作;筛选空态「清除筛选」复位搜索/分类/标签/时间。 2. 侧栏:「添加」主按钮、「⋯ 更多」展开收纳项且点外收起、网盘/设置弱化行可切换视图。 3. 卡片:大 favicon、有 note 的书签两行截断、⋯ 菜单三动作(编辑/快照/删除)正确;列表视图为列式布局且 hover 显示 ✎/✕。 4. 导航选中态为浅橙+指示条;顶栏控件等高对齐;暗色模式下背景/卡片层次分明。 ## 0a-9. 扩展设置并入主页 + 删除 newtab 变体(2026-09-06) ### 改动 | 项 | 内容 | 验证 | |----|------|------| | 设置视图 | 主页第 5 视图:options 表单(实例地址/书签目录/工具栏默认视图/凭据/测试连接)整体移植进 `#viewSettings`,样式作用域化适配明暗主题;`openOptions()`→`openSettings()`=`switchView("settings")`,全部 banner「打开设置」与 settingsBtn 统一入口 | ✅ extension.test 断言 shell 含 viewSettings/switchSettings/settingsForm 且无 openOptionsPage | | 首次配置 | boot 时无 instanceUrl → 强制 `switchView("settings")` + bannerSettings 引导;保存并授权后自动切到工具栏默认视图并 refresh | ✅ 全量 vitest 通过;待 GUI 手测 | | 删选项页 | manifest 删 `options_ui`;删 options.html/css/js | ✅ 测试断言文件不存在 + manifest 无 options_ui | | 删 newtab | 删 `extension-newtab/`、url.js 的 `resolveNewTabTarget`/`DEFAULT_NTP`;打包脚本单 zip(并断言 manifest 无 chrome_url_overrides/options_ui);CI 只附一个 zip | ✅ zip 列表无 options/newtab 文件、含 drive/drive.js | | 路由 | `resolveToolbarTarget` 未配置 → `{action:"settings"}`;background 未配置 → `openLibraryPage("settings")`;ERROR_COPY.unauthorized 文案改指书签库设置 | ✅ toolbar helper 测试 options→settings | ### 待手动回归(需要 Chrome 加载扩展) 1. 全新配置(清空 storage):打开扩展页直接落设置视图 → 填地址/凭据 → 保存 → 授权弹窗 → 自动进入网盘视图并正常列目录。 2. 侧栏「设置」随时进入,改地址/凭据/默认视图,保存后行为立即生效;测试连接四种结果(ok/disabled/401/网络)文案正确。 3. 未配置时点工具栏 → 打开书签库页设置视图(复用已开标签页逻辑);各视图 banner「打开设置」跳转正常。 4. 书签保存:右键「收藏此页」在配置完成后正常工作;412 冲突提示文案指向设置。 5. newtab 变体移除后:Chrome 新标签页保持 Chrome 默认;旧的 newtab zip 用户需改装单包。 ### 已知影响 - `chrome://extensions` 详情页的「扩展选项」入口消失(设置并入主页侧栏,属预期)。 - 曾安装 newtab 变体的用户升级后新标签页覆盖自然消失(需重新加载单包)。 ## 0a-8. 扩展网盘视图:去 iframe,直接复用 Web 端 React 组件(2026-09-05) ### 背景与方案 实例 `_headers` 对全站下发 `X-Frame-Options: DENY`,扩展网盘视图的 iframe 被 Chrome 拒绝渲染(「拒绝了我们的连接请求」)。方案:不再嵌网页,把 Web 端 `` 整棵 React 树打进扩展,挂载到 `bookmarks.html` 的 `#driveRoot`(用户选定「改动最小」路线:整 App 挂载,不新写组合层)。 ### 改动 | 项 | 内容 | 验证 | |----|------|------| | authFetch apiBase | `src/app/auth.tsx` 新增 `setApiBase()`(默认空串=现状),`authFetch` 对 `/` 开头字符串拼接 base;全站 API 与 AuthThumbnail 都走它,一处改动全通 | ✅ 新增 authApiBase.test 3 用例(拼接/绝对 URL/空 base 不变) | | 凭据镜像 | `src/extensionDrive/credentials.ts` 纯函数 + main.tsx 订阅:chrome.storage.local(davUsername/davPassword)↔ auth 模块 localStorage 双向镜像,挂载时 chrome.storage 权威覆盖(含清空) | ✅ credentials.test 4 用例;main.test 播种用例 | | 权限门 | `DriveGate`:`chrome.permissions.contains` 检查实例 origin,未授权显示授权卡片(`request` 需用户手势,放在按钮里);授权后渲染 ``。原因:`/api/*` 无 CORS,必须 host 权限 | ✅ main.test 权限门 2 用例 | | 挂载桥 | `src/extensionDrive/main.tsx` 暴露 `window.DavflareDrive = { mount, reload }`;换实例强制重挂(清残留路由/列表);reload=整树重挂 | ✅ main.test 4 用例(jsdom + chrome mock) | | 扩展壳 | `bookmarks.html` iframe→`#driveRoot` + `