Repository navigation
TB-272 TB-293: 保存服务器列表并绑定连接与阅读来源 - #139
hehetoshang wants to merge 101 commits into
Conversation
feat: add Preview device entitlement client
fix: harden Preview service URL and window resizing
feat: establish and harden Preview distribution
chore: sync talebook main into fork
| function sourceForReaderEvent(windowLabel: string, bookId: string): ReaderSource | null { | ||
| const known = windows.get(windowLabel); | ||
| if (known) return known.bookId === bookId ? known : null; | ||
| if (retired.has(windowLabel) || pending?.ambiguous || pending?.source.bookId !== bookId || pending.before.has(windowLabel)) return null; | ||
| observeWindow(windowLabel); | ||
| return windows.get(windowLabel) ?? null; | ||
| } |
There was a problem hiding this comment.
(高)单 WebView 平台(OHOS / Android / iOS)的阅读进度会被这里静默丢弃,属于行为回归。
src-tauri/src/extensions/api_server.rs:271-275 明确写着这些平台「阅读器运行在唯一 WebView(label 为 main)里,而不是独立的 reader-* 窗口」,因此 is_reader_window_label 把 main 也算作阅读器窗口(label.starts_with("reader-") || (is_single_webview_runtime() && label == "main"))。但前端的来源绑定只接受 reader-*:
- 单 WebView 平台走
openEmbeddedReaderBook(src/lib/moke-reader.ts:460),它既不设置pending,也不绑定任何窗口; - 于是
sourceForReaderEvent('main', bookId)在本行短路:pending为null⇒pending?.source.bookId !== bookId恒为真 ⇒ 返回null; - 即使
pending存在,第 117 行的observeWindow('main')也会被第 73 行的!label.startsWith('reader-')拒绝。
结果 ReaderProgressProvider.tsx:38-46 永远拿不到 source → saveReadingProgress 不会被调用,book:closed 的收尾 flush 也一并失效。改动前监听全局 reader:page:changed + store 里的 serverUrl,这些平台是可用的。
注意 isReaderWindow(第 193-198 行)不把 main 计入是正确的(否则移动端 requireClosedReaders 会因为主窗口始终存在而永远无法关闭),所以修复不能简单地把 main 加进 isReaderWindow;可行方向是让嵌入式打开这条路径显式登记来源(例如 openEmbeddedReaderBook 在 single-WebView 运行时绑定当前 source,返回/切换会话时清除),或按 docs 的约定与 Reader 仓库协调一个显式来源契约(事件回带启动实例 id)。
另外:docs/reader-development.md 记录了 moke-home-*「没有 Moke 来源绑定」这个已知缺口,但没有覆盖 main;新增的 reader-progress 测试也只用 reader-* label,建议补一个 single-WebView 用例。
| err: data.err || `http.${response.status}`, | ||
| // A success-shaped body cannot turn a failed HTTP request into an open | ||
| // connection. Invite-state compatibility applies only after HTTP success. | ||
| err: typeof data.err === 'string' && data.err !== 'ok' && data.err !== 'free' ? data.err : `http.${response.status}`, |
There was a problem hiding this comment.
not_invited 的语义在这里取决于 HTTP 状态码,两处判定互相矛盾,建议确认后再定稿。
readConnectionWelcome(src/lib/server-connection.ts:8)把not_invited视为「需要访问码」的成功状态;- 但本分支在
!response.ok时把它当作错误返回(err: 'not_invited'),checkSavedServer会据此抛出「访问码状态检查失败」,用户永远到不了/access。
问题在于同一文件第 282-285 行已经显式记录 ['not_invited', 403]——即该状态在 /api/user/info 上就是 403。如果 Talebook 的 invite gate 对 /api/welcome 同样返回非 2xx(403),那么「需要访问码」的服务器会直接变成不可连接;而如果返回 200,本分支永远不生效。
另外 tests/helpers/server-list-fixture.mjs:31 的 route.fulfill({json}) 固定是 200,tests/server-validation.test.mjs:87,105 只断言 503 的情况,所以现有测试无法覆盖 403 这一真实分支。
建议:明确 /api/welcome 在 invite gate 下的真实状态码,并让两个入口对同一个 not_invited 给出一致结论(要么都拒绝,要么都路由到访问码页),同时补一条 403 的回归。
Merge PR head abd6995 onto hehetoshang/moke main f770eae. Integrate saved-server connection and Reader source/session guards with personal Preview entitlement, OHOS vendor support, and offline reconnect. Preserve personal version 1.1.5-10 and all four pinned submodules. Resolve integration boundaries around Chrome 96 abort compatibility, access confirmation after registry replacement, stale continue-reading results, and userinfo deadline cancellation. Adapt browser fixtures to explicit connection, responsive navigation, IPC, and real BFCache restore. Validation: 955 Node tests; 33 web, 95 normal static, and 91 Preview browser cases passed in groups; Rust transport 13 tests; lint, typecheck, normal/Preview frontend builds, and locked Rust Preview cargo check. Browser coverage uses Chromium and mocked native IPC; no device validation.
启动页展示持久保存的服务器列表。加入 A/B 只校验和保存地址;点击条目才连接,失败、超时或取消保留原连接和列表。冷启动只加载列表,在线连接和离线入口由开发者显式选择。
历史评审处理(自动化启用前)
历史状态与验证(自动化启用前)
固定 head:
46b23a49696bdc408adb7716f7a3425ecf8bd0f6;包含 main05f5d145096b6fa1c015a87333d71ae89e5e1a44。本轮 7 条意见已修复并 resolved;最后回读累计 78 条 resolved、未解决 0。保持草稿,无合并冲突,未合并或批准评审。pnpm build、pnpm typecheck、ESLint 和差异检查通过;ESLint 0 error、21 既有 warning。构建前移走开发服务遗留的损坏生成类型,未修改源码规避类型检查。依赖、lockfile、Rust、原生配置及 Reader gitlink 均未改。原读取永久挂起时可继续离线书库或当前连接;列表修改和重新读取仍需等待原操作结束或完全退出重启。读取与写入不是 CAS,末次检查后的跨进程竞争仍有边界。忽略 AbortSignal 的自定义 callback 无法强制终止;取消也不能撤回服务器已接受的 POST。新推送触发 CI,交付不等待外部 CI 完成。
原生文件与 Provider 部分验证使用 synthetic IPC/effects。Tauri/ArkWeb 实机重启迁移、Reader 真窗口/EPUB、Cookie/CORS、自签名仍未验收;浏览器和模拟 IPC 通过不能作为实机证据。
后续 review 自动化
开发者已确认:剩余 review 全部有证据证明是误报、没有需要修改的内容时才删除自动化。已启用 Multica 自动化“ Moke PR #139:每10分钟检查并修复新review”(
cd2985c6-e4ec-40c0-8510-d60f7807096c),每 10 分钟检查,Asia/Shanghai,run_only,指派 Moke 开发工程师。没有新意见时继续监测;有效问题修复、验证后非 force 推送原 PR 并 resolve 对应线程。误报结论须独立复核并记录证据,不能因没有新 review 或未解决为 0 而删除。PR 关闭/合并时暂停,不继续改代码。未手动触发试跑;首次调度为 2026-10-11 22:10(Asia/Shanghai),执行情况以自动化运行历史为准。自动维护记录:2026-10-11(run ee74798e)
最终提交:
2e5ce49c5c536184abed3a4d6904649cfd878926,已非 force 推送至原 headagent/moke/3fa392e4c2cd;保持草稿与 Reader gitlink4b3db91bd1d94e5e9063639964f95a407334e81f。本轮读取全部 84 个 reviewThreads、84 个 reviews(含其全部 comments)与 2 个普通 comments,并再次核对 head 和评审更新时间。普通评论中的历史要求按现有代码/文档核对,不重复归类为误报。/api/book/{id}/progress、debounce、关闭/隐藏 flush。独立只读代理复核;新增 13 项执行实际 Reader bootstrap/bridge 的行为回归,覆盖 OHOS/Android/iOS、A/B 来源与桌面不重复直存。补齐 docs 解释。IPC/HTTP/DOM 模拟不等于设备验收。/library查看本地下载(包括未入架书籍),关闭单 WebView Reader 后按既有离线契约返回/shelf;示例书的首次 offline-library intent 到达即消费,后续新 Reader 凭据不继承。保留入口与现有返回行为,reader-return、sample-offline-navigation 和生产 handler 回归通过。验证:Node 24.21.0 全量单测 767/767;
pnpm lint0 error、21 既有 warning;pnpm typecheck、pnpm build、git diff --check通过。生产 Tauri 静态前端使用系统 Chromium 与 synthetic IPC,server-storage-timeout / offline-document / connection-reader-guard 三组 14/14 通过;预览进程已收齐停止。构建生成的 next-env 变化已还原,无依赖/lockfile/Rust/原生配置/gitlink 修改。分工:4 个子代理分别核实并实现 API、Welcome/备份 UI、Repository/store 备份支持、Reader 协议回归与 CI;另 1 个独立只读代理复核整合,未发现阻断。主代理核实普通评论、完成文档、审阅整合并执行最终检查/推送。
本轮有有效修复,不满足“当前尚未解决意见全部误报”的自动删除条件。自动化
cd2985c6-e4ec-40c0-8510-d60f7807096c保持 active;不 merge/approve、不发送 review 讨论消息。仍保留跨进程非原子存储竞争及原生 WebView/EPUB/Cookie/CORS 实机验收边界。推送后一次 CI 快照
快照 head
2e5ce49c5c536184abed3a4d6904649cfd878926;状态计数:{'IN_PROGRESS': 17, 'SUCCESS': 3, 'NEUTRAL': 1}。只记录现场状态,不等待外部 CI;新推送的后续结果由下一次调度核对。自动维护记录:2026-10-11(run 4909a0a2)
提交
abd69959b0e97c63e3180f22b9f5ab907a655156已非 force 推送至原 headagent/moke/3fa392e4c2cd,保持 Draft、Reader gitlink4b3db91bd1d94e5e9063639964f95a407334e81f。开始写入与推送前均重读实时自动化条件、runs、活动 agent/tasks 和 PR head;没有其他修复占用。完整分页读取 88 个 threads、88 个 reviews(含全部线程 comments/回复)及 2 个普通 comments;旧已处理要求不重复实现。推送后回读:86 条 resolved,2 条保留未解决。offline-books.ts格式回退与无格式查询一致,按 serverUrl、bookId、format 匹配;空来源不能读取或删除其他服务器记录,跨服可见的示例书也不能误命中。7 项新增 Web/Tauri 行为回归覆盖 A/B 同 ID、读取/删除、规范化 origin、示例碰撞与显式本地记录;旧实现复现 6 项失败,修复后通过。activateCandidate默认未确认,读取 needsAccessCode,并在 Reader 关闭/进度等待后回读;仅 Access 的当前请求收到服务端err=ok后传确认。新 store 回归及生产 handler 回归验证普通/陈旧/错误/验证码响应不能授权。此 boolean 约束内部调用,服务端访问认证仍由接口和会话负责。验证:Node 24.21.0 全量单测 781/781;
pnpm lint0 error、21 既有 warning;pnpm typecheck、pnpm build、git diff --check通过。生产 Tauri 静态前端的 offline-document 与 connection-reader-guard 6/6;Web 的桌面/移动 A/B 连接与认证、Access 重载和 3 个访问码流程 6/6。浏览器预览进程全部停止,4 个子代理结果全部收齐;构建生成的 next-env 差异已还原,运行时自动注入的 AGENTS 内容及测试产物未纳入提交。依赖/lockfile/Rust/原生配置/gitlink 未改。分工:3 个实现代理分别负责 Welcome HTTP 协议、离线来源、候选访问码;1 个只读代理独立复核 Reader 意见及最终整合,未发现阻断问题。主代理修复 Settings、复核整合并完成全量检查、浏览器验证、推送与线程回读。
边界:DOM、IPC、HTTP/计时器部分使用模拟,不等于原生设备、真实 Cookie/CORS 或 Talebook 部署验收。离线列表到详情仅传 bookId 的原有初始选书歧义(
src/app/detail/page.tsx:199)未扩展修改;后续开书/删除使用所选记录的实际来源,本轮修复的是函数直接查询/格式回退的跨来源错误。自动化保持 active。开发者实时条件允许 A(剩余意见全部证实误报且独立复核)、B(当前 head 的 Claude 实际完成且无任何评审输出)或 C(当前 head 明确因额度耗尽停止)删除。上一 head
2e5ce49对应 Claude run 38147841177 attempt 1 的 DeepSeek 审查正常完成(result success、is_error=false),但确实发布了本轮 4 条新评审,因此不满足 B/C;本轮有有效修复,不能刚推送就用 A 删除。新 head 的 Claude 仍执行中,后续调度继续核实。不 merge/approve,不发送讨论回复。本轮推送后一次 CI 快照
head
abd69959b0e97c63e3180f22b9f5ab907a655156:{'IN_PROGRESS': 18, 'SUCCESS': 2, 'NEUTRAL': 1},无失败状态;记录现场,不等待外部 CI。