| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
fix(ui): 移动端自动收起不该污染桌面端的侧栏偏好 用真实 Chromium 在 390x844 / 1280x800 两种视口下实测发现:移动端一进入就 自动把 sidebarOpen 写成 localStorage "0",于是同一个浏览器里打开桌面端 (或把窗口拉宽)时侧栏是收起的——而用户从没在桌面收过它。 区分两类改动: - **显式操作**(点活动图标 toggleSidebar):是用户对侧栏的偏好,照常持久化 - **布局驱动**(移动端自动收起、点遮罩、选完笔记自动收起):由屏幕尺寸决定, 只改当前状态,不写 localStorage setSidebarOpen 增加 { persist?: boolean };上面三处布局驱动的调用改成 persist:false。新增 activity.test.ts 钉住这个语义(109 tests)。 实测复验:全新 profile 下移动端初始 localStorage 由 "0" 变为未写入, 点遮罩后保持 "1";桌面端侧栏恢复展开。 | 1 个月前 | |
refactor(nav): 模板中心进活动栏的 ITEMS —— 与其余 6 项完全同形(c-2) 接 547adeb6(c-1:store 层并进 view)。这一笔把竖条上那套"两套机制"收成一套: · ACTIVITIES / Activity 加 "templates"(末尾,与 view 的顺序一致); · ActivityBar 的 ITEMS 加一条(TemplateIcon,labelKey 用已存在的 templateCenter, 不为整齐再多两个 locale 条目 —— 同一个词一个键); · 删掉竖条底部那颗**独立按钮** —— 它原来只 setOpen(true)、不参与高亮,与统一体系是两套。 ⭐ 至此 owner 的 a + c 都落地:命令面板 7 个视图齐全(a),模板中心并进统一体系(c), 竖条上它和其余 6 项走同一个 pick()、同一套高亮。 读数:tsc exit 0;check:overlays / check:hook-order / check:store-subscriptions 三条 exit 0; pnpm verify 59 条全过。 | 18 天前 | |
feat(ai): P4 收官 —— 跨库总结接进应用(取材 + 强制回链 + 真 SQL/真模型读数) 第 5 笔:把跨库总结从库里的模块接到按钮上。 取材(librarySummaryRun.ts):只从**已索引的块**取(与检索面同一份文本),页面→[[标题]]、PDF 逐页→pdf://att#0基页、其余附件→att://id、未整理附件也算。没索引/超预算/标题不可用一律**如实进 skipped**,不静默少一段。 两条最容易漏的性质,各配一条承重判据: 1. **发出去的回链必须能被 extractRefs 认出来**(认不出 ⇒ 模型照抄也会被判「没出处」整行丢 ⇒ 整批结论全灭)。pageRefOf 对空标题/含 ]]/超 120 字直接拒绝并说明。 2. **PDF 页码 1 基→0 基只在这一处换算**(差一个就是点回链跳错页)。 接应用:useAiStore.summarizeLibrary(进度进气泡、stop 后结果作废、失败落成人话)+ AI 助手页脚「跨库总结」按钮;结果正文进对话,另给一条 append_block **草稿**(与 AI 工具面同一套 key 约定,用户点「应用」才落库)。没有打开的页面就不给草稿——总结不替用户挑落点。 读数:真 SQL(sql.js 真表真 store)+ 真本机模型全链跑通,30.4s: 取材 5 个来源(1 页 / 3 个附件,84 字)→ 输出 2 条结论,回链 [[季度总结]] 与 pdf://a1#2(p.3 → 0 基 2),全部来自输入。 变异证明 5/5(.tools/p4-wiring-mutation.mjs):改掉页码换算 / 不校验回链形状 / 超预算静默丢 / 去掉 stop 守卫 / 改成往第一个页面写 —— 逐个当场红,还原后基线全绿、源码哈希一致。 判据:librarySummaryRun 18 + store 8 + e2e 3(其中 1 条真模型,无服务则跳过)。 | 1 个月前 | |
feat(ai): LLM Wiki 草稿可「采用为页面」—— 只新增一页、自带派生标记与来源;生成/采用计数上本机 口径:采用是唯一写库的一次点击(createPage,不 update/delete 任何已有页);页脚「派生,非出处 | 模型 | 时间」、覆盖度、来源清单随正文落库;打 llm-wiki 标签。失败如实报错且草稿不动。计数落在 localStorage(只本机、不上报),面板显示采用率 —— 需求 §8「采纳率」那行从此可量。 | 4 天前 | |
feat(ai): LLM Wiki 草稿可「采用为页面」—— 只新增一页、自带派生标记与来源;生成/采用计数上本机 口径:采用是唯一写库的一次点击(createPage,不 update/delete 任何已有页);页脚「派生,非出处 | 模型 | 时间」、覆盖度、来源清单随正文落库;打 llm-wiki 标签。失败如实报错且草稿不动。计数落在 localStorage(只本机、不上报),面板显示采用率 —— 需求 §8「采纳率」那行从此可量。 | 4 天前 | |
feat: 附件不再占用固定显示空间 + /附件 斜杠入口 - AttachmentPanel:页面无附件时隐藏面板(return null,不再显示"附件 + 暂无附件"空块), 仅在有关附件或导入中时显示。 - 新增 useAttachmentsStore(revision):外部导入后 bump,让隐藏的面板刷新。 - SlashMenuPlugin 新增「/附件」命令:多选文件导入并 bump,作为无面板时的添加入口。 | 1 个月前 | |
feat(ui): 账户页显示当前登录邮箱 + 登录切换时清理旧空间 token 回应「B 登录 A 的连接怎么处理」第一层: 1. 显示当前登录邮箱:auth store 加 email 字段;init 时调 /auth/me 恢复 email, 登录时 setSession(base, token, email)。账户页「当前团队账号」卡显示邮箱 (而非只显示服务器地址),让人知道是哪个身份。 2. 登录/注册(换账号)后,cleanOtherServerTokens 把同服务器其它空间的 sync token 覆盖为当前 token——清掉旧账号残留,避免 401/数据错乱(之前 401 同类根源)。 - sync.rs team_get_me(调 /auth/me);lib.rs 注册;契约+web+api.ts。 | 1 个月前 | |
feat(blocks): 关系图块级图层 + 块引用/嵌入目标缓存实时刷新 - get_graph 扩展返回 blocks(参与块级引用的块)+ block_edges(块→块 link/embed + 块→页 belongs) - GraphView 新增「块级」开关:块节点(青色小圆)+ 所属虚线边 + 块级引用边,点击块节点跳转定位 - 新增 blockCache store(revision 失效信号),保存后 bump - BlockEmbedView 订阅 revision 自动刷新镜像 - 新增 BlockRefSyncPlugin:保存后重拉 ((blockId)) 目标文案并更新(blockref-sync tag 跳过保存,避免循环) - tsc + cargo check 通过 | 1 个月前 | |
fix(store): 退出多选模式时清空已选 —— 修"已选 1 块"的残留工具条 - 现象(owner 截图逐字):工具条上 count 显示「已选 1 块」而按钮显示「多选模式」⇒ 说明 selectMode 已关、而 keys 还留着 1 个 ✗。 - 因果链:在多选模式下拖表格列宽时,那次 mousedown 被 BlockSelectionPlugin 当成"点块" (该插件是唯一写入者:s.setAnchor/s.toggleKey,只在 selectMode 下生效); 随后用户退出多选 ⇒ setSelectMode 只改 selectMode、**不清 keys** ⇒ 残影常驻 ✗。 - 改法:setSelectMode(false) 顺带清 keys 与 anchor(进入时不清,用户可能想接着选)。 - 判据:进入多选 ⇒ 选 2 块 ⇒ 退出 ⇒ 工具条应消失(且再进多选时是干净状态 ✓); 反例:把清空去掉 ⇒ 残影复现 ✓。 - 另:同轮的 BlockSelectionPlugin 已把手柄类 .table-resize-handle 加进 mousedown 排除清单 (28e4b735);本条修的是**残留显示**这一半。若"在多选模式下拖列宽仍会选中"仍复现, 说明按下目标不是手柄本身,需要 owner 再给一次现场(见信箱)。 - 读数:tsc --noEmit 0;markdownTransformers 3 条通过;smoke-web 363/363。 - 范围:src/store/blockSelection.ts;同一分支;未碰 dev。 | 20 天前 | |
| 2 个月前 | ||
feat(p0): 深链的"接入缝" openWithLink —— 预填并预览,但绝不自动保存 Windows 侧接完 OS 层后只需要一行: useCommunitySave.getState().openWithLink(url)。 语义上关键的一点(也写进了代码注释):**预填 ≠ 自动保存**——网页发出的链接不该比用户自己 粘贴多出任何权限,所以照样走"抓取 → 预览 → 人确认",取消一样零痕迹。 - store 加 pendingLink + openWithLink(link);openDialog()/close() 都会清掉它; - 对话框打开时若带 pendingLink → 预填并自动读一次(只读,不写); - 新增 3 条渲染级测试:合法深链出预览且不落库 / 非法深链(指向回环)说清原因且**根本不发请求** / 深链进来后取消仍零痕迹; - 顺手修一处测试卫生问题:setState({ open: true }) 没清 pendingLink,上一个用例的链接会漏到 下一个(表现为"fetch 被调用了一次"这种莫名其妙的失败)。 | 1 个月前 | |
feat: 删除等确认对话框改为应用窗口内居中 - 新增应用内 ConfirmDialog(position:fixed 覆盖整个应用窗口,居中), 替代系统原生 confirm()(会随 OS 居中到屏幕而非窗口)。 - 新增 useConfirmStore + confirmDialog(options):Promise<boolean>。 - 替换 5 处 confirm() 调用:导入备份覆盖 / 恢复版本 / 删除页面 / 删除标签 / 彻底删除, 均传入 title/message/danger。 | 1 个月前 | |
refactor(sync): make the sync panel the only home for the sync settings -- the settings dialog no longer offers a sync tab, so its tab entry, render gate, import and the now stale comment are gone, the settings tab union drops the sync member that only that gate used, and the pane component is deleted rather than left as an empty shell; the criteria that pinned the settings side as deliberately untouched are retargeted to the panel instead of being deleted, so the same needle still proves the three device level fields are reachable, and a new source level check asserts that neither the dialog nor the union mentions the removed pane again; four comments that had come to say the opposite of the truth are corrected while the three that remain accurate are left alone; typescript is clean and the whole component directory is green with four hundred and seventy two tests | 8 天前 | |
fix(email): 设置里加了账号,聚合邮箱不及时更新——账号列表收敛为单一数据源 ## 报障 「客户端设置添加了新的邮箱账户,聚合邮箱不能及时更新账户」。 ## 根因:状态归属错了 设置( SettingsDialog 邮箱区)与邮箱面板(EmailPanel)**各持一份 useState 账号副本, 各自只在自己挂载时读一次后端**: // EmailPanel 旧写法(注释自己写着"挂载时读一次") useEffect(() => { api.emailListAccounts().then(...) }, []); 于是「在设置里加账号」只改了后端与设置自己那份,**已经挂载的面板并不知情**。 受影响的不止"少一个账号",而是一整串: - 账号下拉、来源账号小标、多账号筛选(selectedAccountKeys 的合法性判断) - 未读角标与定时收取(都按 accounts.filter(a => a.auto_fetch) 算) - **活动账号**——emailSaveAccount 是 upsert 到首位即成为活动账号,面板那份还是旧的 - 最扎眼的一种:**面板挂载时一个账号都没有,用户去设置里加完回来,它仍显示「去配置邮箱账号」** (accounts.length === 0 那条空态分支永远不会自己走出来) ## 改动 **账号列表收敛到 store/emailPanel 作为单一数据源**(accounts / accountsLoaded / reloadAccounts / patchAccount),并把「写后端 + 重读列表」合成 store 的两个动作 saveAccount / removeAccount。 为什么要把"保存"也放进 store:**只要这个动作还散在组件里,就总有人写成"只改自己那份 state"**, 而跨组件同步正是靠它完成的。合成在 store,设置与面板就不可能各改各的。 设置侧三个变更入口(保存 / 删除 / 设为活动账号)全部改走 store。 顺带两处: - **新增账号后立刻重拉一次聚合流**(按 accountsSig 变化触发)。 否则"账号加上了但看不到它的信"同样属于没及时更新。首次不拉,避免刚开面板就拉两遍。 - **收敛 accountKey**:它原先在 EmailPanel 与 SettingsDialog 各写了一份**完全相同**的实现, 而 store 又需要第三份。三份同逻辑的键函数只会静默分叉,统一到 src/lib/emailAccount.ts。 ## 测试(邮箱功能此前零测试) 新建 src/components/EmailPanel.test.ts,6 例: 1. store 契约:reloadAccounts 后任何订阅者读到的都是新列表 2. store 契约:patchAccount 只改 key 匹配的那一条 3. saveAccount 保存后列表立刻含新账号 4. removeAccount 删除后列表立刻不含该账号 5. **回归主体**:面板挂载时没有账号 → 加完账号**不重挂载**,空态必须消失 6. **回归主体**:已有 1 个账号再加 1 个 → 多账号筛选器出现(说明面板拿到的是 2 个) **变异自证(关键)**:把面板的账号列表冻结成"挂载时那一份"(等价于旧写法) ⇒ 第 5、6 例立刻红,报错正是用户看到的那句「去配置邮箱账号」; 其余 4 例不受影响(它们不依赖面板渲染),说明测试盯的确实是这个 bug 而不是别的。 ## 校验 - 全量 **69 文件 / 647 例全过**(新增 6 例) - tsc --noEmit 通过 - 门禁 7 项全过:check:versions / check:changelog / check:web-commands / check:doc-links / check:ps1-ascii / check:capabilities / check:deep-link - 变更记入 CHANGELOG.md 的 [Unreleased] / 修复(按本仓库惯例:攒在 Unreleased,发版时统一 bump) | 1 个月前 | |
fix(ai): 取消自带密钥下的 AI 额度门槛 原实现有个硬矛盾:AI 推理成本**不在我们这边**(用户自带本地 Ollama 或自己的 OpenAI 兼容密钥),却在用完免费额度后拦截,并提示「升级 Pro」——而 Pro 既无 支付入口也无校验(plan 只存 localStorage,改一行即为 Pro)。等于既不代付算力、 又限制用户使用自己的钥匙,还把用户指向一个不存在的购买路径。 - entitlements.ts 增加 ENFORCE_QUOTA 开关(当前 false),isCapAllowed 直接放行; 额度表与结构保留,将来真提供托管推理时改一处即可恢复门槛。 - gate.ts / store/ai.ts 去掉「升级 Pro」文案,改为中性提示。 - 用量记录保留(对用户有价值:能看到自己用了多少),只是不再作为门槛。 | 1 个月前 | |
feat(export): C 弹窗合并 —— 两个新入口(「导出当前页…」「导出本空间…」)+ 一个带预估的导出对话框 owner 的 C 方案:命令面板里原有四条导出命令,用户得先想"我要哪种形态"。现在另给**两个统一入口**, 点开同一个对话框:先看**预估**,再勾三件事,由执行层一把导完。 ⛔ 四条旧命令**一条没删**(owner 真机验过新入口再收 —— 不做"先删后补" ✓)。 - 新浮层 components/MarkdownExportDialog.tsx: · ☑附件(打包 zip)/不勾 ⇒ 只产出 .md;☐元数据 YAML(title/id/kind front matter); ☐纯 .md 平铺(不按页面树建目录) · 预估行用现成的 estimateBundleText(新增 estimateExportScope 供数据:不读附件字节、只问列表; 大小未知的照实另说,⛔ 不当 0 ✗) · **登记进返回栈** useOverlayLayer("markdownExport", …) + 滚动锁 —— check-overlay-registry 28/28 ✓ (容器类名复用 .confirm-overlay / .confirm-box 的既有几何,⛔ 不新增未验的 -overlay/-popover ✗) - 新执行层 lib/exportMarkdownDialog.ts:读哪些页 → 弹保存对话框 → 写 zip / 写 .md → 说人话; 不带附件时正文里的 attachment:// **原样留着**并在消息里报数(⛔ 不改成坏链接 ✗) - lib/exportMarkdownBundle.ts 加三个**可选**能力(缺省 ⇒ 老行为逐字不变 ✓): planBundle({flat, yaml}) / flatPaths() / yamlFrontMatter() + pageFileHeader(); prepareBundle 导出(返回 files);estimateExportScope;startExportProgress / bundleMessage 对外开放 - 两个入口在 plugins/builtinCommands.ts(只打开浮层,⛔ 不碰执行逻辑 ✗);App.tsx 常驻渲染浮层 - 判据:lib/exportMarkdownDialog.test.ts(zip/纯 .md/平铺/YAML/取消/失败 共 11 条)/ lib/exportMarkdownBundle.test.ts +8 条(flatPaths、yaml 头、预估去重与未知大小、清单列不出来)/ store/exportDialog.test.ts 4 条 / components/markdownExportDialog.test.tsx 5 条 (真渲染:**返回栈里登记了**、预估行、勾选项原样传给执行层、关闭后不留幽灵层) ⚠️ 移动端几何:本弹窗复用 .confirm-* 那套已量过的几何,但**验收脚本没有单独打开它** ⇒ 属未验证项(不是"验过了"✓)。 | 5 天前 | |
feat(export): C 弹窗合并 —— 两个新入口(「导出当前页…」「导出本空间…」)+ 一个带预估的导出对话框 owner 的 C 方案:命令面板里原有四条导出命令,用户得先想"我要哪种形态"。现在另给**两个统一入口**, 点开同一个对话框:先看**预估**,再勾三件事,由执行层一把导完。 ⛔ 四条旧命令**一条没删**(owner 真机验过新入口再收 —— 不做"先删后补" ✓)。 - 新浮层 components/MarkdownExportDialog.tsx: · ☑附件(打包 zip)/不勾 ⇒ 只产出 .md;☐元数据 YAML(title/id/kind front matter); ☐纯 .md 平铺(不按页面树建目录) · 预估行用现成的 estimateBundleText(新增 estimateExportScope 供数据:不读附件字节、只问列表; 大小未知的照实另说,⛔ 不当 0 ✗) · **登记进返回栈** useOverlayLayer("markdownExport", …) + 滚动锁 —— check-overlay-registry 28/28 ✓ (容器类名复用 .confirm-overlay / .confirm-box 的既有几何,⛔ 不新增未验的 -overlay/-popover ✗) - 新执行层 lib/exportMarkdownDialog.ts:读哪些页 → 弹保存对话框 → 写 zip / 写 .md → 说人话; 不带附件时正文里的 attachment:// **原样留着**并在消息里报数(⛔ 不改成坏链接 ✗) - lib/exportMarkdownBundle.ts 加三个**可选**能力(缺省 ⇒ 老行为逐字不变 ✓): planBundle({flat, yaml}) / flatPaths() / yamlFrontMatter() + pageFileHeader(); prepareBundle 导出(返回 files);estimateExportScope;startExportProgress / bundleMessage 对外开放 - 两个入口在 plugins/builtinCommands.ts(只打开浮层,⛔ 不碰执行逻辑 ✗);App.tsx 常驻渲染浮层 - 判据:lib/exportMarkdownDialog.test.ts(zip/纯 .md/平铺/YAML/取消/失败 共 11 条)/ lib/exportMarkdownBundle.test.ts +8 条(flatPaths、yaml 头、预估去重与未知大小、清单列不出来)/ store/exportDialog.test.ts 4 条 / components/markdownExportDialog.test.tsx 5 条 (真渲染:**返回栈里登记了**、预估行、勾选项原样传给执行层、关闭后不留幽灵层) ⚠️ 移动端几何:本弹窗复用 .confirm-* 那套已量过的几何,但**验收脚本没有单独打开它** ⇒ 属未验证项(不是"验过了"✓)。 | 5 天前 | |
fix(view): close MD/PDF viewers when opening folder/files view or navigating folders | 1 个月前 | |
feat(sync): P6.3 续 —— 把「可操作」补到所有应用内打开路径(PDF/图片/音视频/markdown) 上一轮只在文件管理器给了入口(「未下载」标记 + ☁ 按钮),但用户不一定从那儿打开文件: 页面树、附件面板、内联附件引用、PDF 引用、插件命令都各有一条打开路径,原来仍落到 一句死胡同文案上("文件内容缺失(可能未同步到本机,或已被删除)"——文案没错, 但没有任何动作,而字节完好地躺在服务器上)。 关键判断:**修在必经之路上,不修在 10 个调用点上。** - 新增 src/lib/attachmentBytes.ts:ensureAttachmentBytes(hash) —— 判据与取字节的 **唯一实现**(先 list_attachment_hashes 走附件目录判断在不在本机,不在就 download_attachment 取回来)。放这一层的理由写在文件头:开 PDF 有 10 个调用点, 各写一遍=同一件事抄十遍,改一处忘一处是必然。 - store/pdfReader.ts:openPdf 是所有 PDF 入口的必经之路。改成 读 → 失败则按需取回 → 再读一次;取不回来抛**最初那个错**(离线/服务端也没这份字节才是真原因)。 - store/filePreview.ts:markdown 预览同样加"读→取回→再读"。 - components/FilePreviewDialog.tsx:图片/视频/音频/PDF 四个分支原先各有一句一模一样的 死胡同文案,现在 ① 先自动按需取回再读一次;② 真失败才落到统一的 missingBlock, 并给「从服务器下载」按钮(点它重取并重跑读取)。配 .fm-preview-fetch 样式。 顺手发现并修掉一处死状态:FilePreviewDialog 的 asset.missing **写了 3 处、一处都没读** (grep 只有赋值没有使用)⇒ 那个"缺失"状态在界面上从没被区分过;现在它真的被用来 渲染上面那个可操作状态。 按需下载**不受 P6.1 每空间开关与 C1 预算闸门限制**(与 download_attachment 的既有注释 一致:显式操作照做)。 仍未覆盖(记在计划书里,别当成已修):页面内嵌图片/视频(ImageNode/VideoNode 的 src 解析)不走这条必经之路,字节不在本机时那里还是一张坏图——修它要动编辑器节点, 属另一类改动。 验证:tsc 0;pnpm test 656 全过;smoke-web 350 通过;check-panel-layout 25 通过 (真实 Chromium);check:web-commands 通过。⚠️ 未做真机验收。 | 1 个月前 | |
feat: 公式(块级/行内) + Notion风格公式编辑器 + KaTeX 0.18.4 - 公式块: FormulaNode (DecoratorNode, /公式或\$\$..\$\$), KaTeX懒加载(独立chunk), 无边框/无空行/居中/contentEditable=false(块内不可输入); 实时预览同步 - 行内公式: InlineFormulaNode (TextNode子类, \$..\$), registerNodeTransform 文本插件, 保留字面进content_text; 防\/\ - Notion风格公式编辑器弹窗: FormulaEditorDialog + formulaEditor store (分类tab下拉 αβ/÷×/≤≥/xₐ/↑↓/H₂O, 图标/手写SVG按钮, 紧贴公式块下方, 主题/个性化配色var(--accent)/--bg); /公式与+入口 - KaTeX 0.16->0.18.4 + 补引katex.min.css - 文档体系: docs/plans/2026-08-30-formula-plan.md + formula-recognition-plan.md(方案), docs/README + roadmap M26(公式+识别扩展) - tsc/smoke(347)/build 全绿 | 1 个月前 | |
feat(ui): 更换页面图标改为 emoji 选择器(可选可输入) - 新增 src/lib/emojis.ts 内置分组 emoji 数据集(表情/手势/动物/植物/食物/活动/旅行/物品/符号/旗帜)。 - 新增 EmojiPicker 组件:左侧分组 tab + 顶部搜索框(可输入任意 emoji 直接使用) + 最近使用(持久化) + emoji 网格 + 底部分组栏 + 清除图标/随机。照参考图(Notion 式)布局。 - 页面图标按钮(图标上/操作行)改为一键打开 emoji picker。 - replace inputDialog 旧入口。 | 1 个月前 | |
feat(隐私边界): A1 建空间时选个人/团队 + A2 开启加密前必须确认「口令丢了就没了」 owner 2026-09-25 对「待拍 5 件」的拍板落地(信箱 2026-09-25-owner-decisions-five-open-items.md)。 本笔只做其中**要动代码的两件**(A1/A2);A3=确认走 ①(不做 6 位码)、A4=扫码先不做: 两条都是"不做事",已在方案文档里记为结论;A5 是文档口径,同批改掉。 ## A1(拍板 B):**分类由入口决定** —— 建空间那一刻就选「个人 / 团队」 - **前端**:PageTree.createSpace 先问名字(既有 inputDialog),再问类型 (新 chooseDialog —— store/input.ts 的单选模式,与 confirm.ts 同一手法: resolver 放 store)。**只有桌面端问**:按空间加密是桌面专属,Web 上的分类没有下游。 - **为什么不能"先建了再回头去隐私面板里改"**:sync_gate 对**未分类**空间**一律放行** (AllowedUnclassified)⇒ 中间那段时间里,一个本该加密的个人空间可以被绑上同步(明文上云)。 唯一没有窗口的时刻就是**创建这一刻**。 - **Rust**:create_workspace 多收 kind: Option<String>;不给 ⇒ 走原来的默认(personal, 行为逐字不变)。给错 ⇒ **报错**(parse_new_space_kind 窄进,与读库那侧 SpaceKind::parse 的宽进刻意相反:把 "tem" 静默当成"未分类"=闸门松开)。 - **契约**:create_workspace 的 args 加 kind?: "personal" | "team" | null;api.createWorkspace 与 useSpaceStore.create 跟着透传。 ## A2(拍板「补回来:一个勾选框 + 一句真话」) - SpacePrivacySection 里,**没加密的**空间那行多一句真话(常量 PASSPHRASE_NO_RECOVERY): 「主口令忘了就**真的打不开了**:钥匙只在本机派生,我们没有它,也没有第二把备份钥匙 ——没有找回、没有重置、没有客服。」+ 一个勾选框「我已保管好主口令,知道它丢了就打不开」。 - **没勾上 ⇒「开启加密」按钮是灰的**(disabled,不是弹窗——弹窗会被一路点掉)。 - **为什么值得做**:零知识=零恢复,口令丢了**数据永久打不开**。这句真话此前只在**锁定屏** (连错 3 次之后)说 —— 那时候用户已经记不住了,等于事后通知。 docs/identity-privacy-roadmap.md:34 早就写着"开启前必须勾选确认",而代码里一直没有 (随被删的应用级加密那节一起丢了)。 ## A5(拍板「以 9-22 的单一口味为准」) docs/SM-CRYPTO-DELIVERY.md 两处 **2026-09-19「macOS 不切 CommonCrypto、国密版另发」** 标注为**【历史口径 · 已被 2026-09-22 取代】**,并写清现行口径(所有平台发出去的包一律 SM4 页, 已落进 release.yml)与**证据**(v1.91.24/25/26 三次 tag run 的产物断言)。 ⚠️ 理由不只是"哪份新":**SQLCipher 文件里不写自己用的是哪套算法**,混用会产生 "同一文件在 A 机器能开、B 机器打不开,而报错一字不差"的最难查形态。 ## 判据与读数 - Rust 新增 **3 条**(workspaces::tests):选的那一类真的落库(个人/团队各一条 + 大小写空白)/ **认不出的类型报错且一行都不写**/不传类型时默认仍是 personal(老调用方行为不变)。 - 前端新增 **6 条**(src/store/input.test.ts):chooseDialog 选了要兑现、**取消也要兑现**、 只兑现一次;+ PageTree 的**接线判据**(真的调 chooseDialog、真的把用户选的传进 create、 只在桌面端问、问名字那一步不能只挂 onSubmit)。 - SpacePrivacySection.test.ts 从 16 → **18 条**:没勾 ⇒ 点不动(且那句真话在屏幕上)/ 勾上才点得动、且**按空间记**(勾了 A 不算勾了 B)。既有两条"开启加密"的用例补上前置勾选。 - 全量:Rust **645 passed / 0 failed / 20 ignored**(332.87 s,exit code 0); vitest **2339 passed / 12 skipped(0 failed)**,退出码 0; tsc 0;check-web-commands ✓(Rust 258 / web 248 / CommandMap 260);文档三闸 ✓; check-overlay-registry ✓(单选对话框复用 input 那一层,不新增浮层)。 ⚠️ 如实记一次**负载假红**:Rust 全量与 vitest 全量**并发跑**时,communitySaveDialog.test.ts 有一条红;**隔离复跑 16/16 全过**,随后 vitest 单独再跑一遍 **0 failed** ⇒ 判据是 "隔离绿 + 负载证据"(本仓 TESTING 里那条纪律),不是"重试到绿了算数"。 ## 哪些没做 - **A3/A4 是"不做事"**:不实现 6 位短码、不实现扫码(两条都要加依赖);LanAnnounce.fp 仍只透传、pairing_export 侧填 device_id 那一半已有。 - **团队空间的自动来源仍未接上**:本笔只做了"建的时候能选",团队流程(在另一个仓) 上线前,团队空间要么用户手选、要么走隐私面板里那个下拉。 - **没做真机验收**:新勾选框与新建流程都只在本机(happy-dom)验过。 | 27 天前 | |
feat(隐私边界): A1 建空间时选个人/团队 + A2 开启加密前必须确认「口令丢了就没了」 owner 2026-09-25 对「待拍 5 件」的拍板落地(信箱 2026-09-25-owner-decisions-five-open-items.md)。 本笔只做其中**要动代码的两件**(A1/A2);A3=确认走 ①(不做 6 位码)、A4=扫码先不做: 两条都是"不做事",已在方案文档里记为结论;A5 是文档口径,同批改掉。 ## A1(拍板 B):**分类由入口决定** —— 建空间那一刻就选「个人 / 团队」 - **前端**:PageTree.createSpace 先问名字(既有 inputDialog),再问类型 (新 chooseDialog —— store/input.ts 的单选模式,与 confirm.ts 同一手法: resolver 放 store)。**只有桌面端问**:按空间加密是桌面专属,Web 上的分类没有下游。 - **为什么不能"先建了再回头去隐私面板里改"**:sync_gate 对**未分类**空间**一律放行** (AllowedUnclassified)⇒ 中间那段时间里,一个本该加密的个人空间可以被绑上同步(明文上云)。 唯一没有窗口的时刻就是**创建这一刻**。 - **Rust**:create_workspace 多收 kind: Option<String>;不给 ⇒ 走原来的默认(personal, 行为逐字不变)。给错 ⇒ **报错**(parse_new_space_kind 窄进,与读库那侧 SpaceKind::parse 的宽进刻意相反:把 "tem" 静默当成"未分类"=闸门松开)。 - **契约**:create_workspace 的 args 加 kind?: "personal" | "team" | null;api.createWorkspace 与 useSpaceStore.create 跟着透传。 ## A2(拍板「补回来:一个勾选框 + 一句真话」) - SpacePrivacySection 里,**没加密的**空间那行多一句真话(常量 PASSPHRASE_NO_RECOVERY): 「主口令忘了就**真的打不开了**:钥匙只在本机派生,我们没有它,也没有第二把备份钥匙 ——没有找回、没有重置、没有客服。」+ 一个勾选框「我已保管好主口令,知道它丢了就打不开」。 - **没勾上 ⇒「开启加密」按钮是灰的**(disabled,不是弹窗——弹窗会被一路点掉)。 - **为什么值得做**:零知识=零恢复,口令丢了**数据永久打不开**。这句真话此前只在**锁定屏** (连错 3 次之后)说 —— 那时候用户已经记不住了,等于事后通知。 docs/identity-privacy-roadmap.md:34 早就写着"开启前必须勾选确认",而代码里一直没有 (随被删的应用级加密那节一起丢了)。 ## A5(拍板「以 9-22 的单一口味为准」) docs/SM-CRYPTO-DELIVERY.md 两处 **2026-09-19「macOS 不切 CommonCrypto、国密版另发」** 标注为**【历史口径 · 已被 2026-09-22 取代】**,并写清现行口径(所有平台发出去的包一律 SM4 页, 已落进 release.yml)与**证据**(v1.91.24/25/26 三次 tag run 的产物断言)。 ⚠️ 理由不只是"哪份新":**SQLCipher 文件里不写自己用的是哪套算法**,混用会产生 "同一文件在 A 机器能开、B 机器打不开,而报错一字不差"的最难查形态。 ## 判据与读数 - Rust 新增 **3 条**(workspaces::tests):选的那一类真的落库(个人/团队各一条 + 大小写空白)/ **认不出的类型报错且一行都不写**/不传类型时默认仍是 personal(老调用方行为不变)。 - 前端新增 **6 条**(src/store/input.test.ts):chooseDialog 选了要兑现、**取消也要兑现**、 只兑现一次;+ PageTree 的**接线判据**(真的调 chooseDialog、真的把用户选的传进 create、 只在桌面端问、问名字那一步不能只挂 onSubmit)。 - SpacePrivacySection.test.ts 从 16 → **18 条**:没勾 ⇒ 点不动(且那句真话在屏幕上)/ 勾上才点得动、且**按空间记**(勾了 A 不算勾了 B)。既有两条"开启加密"的用例补上前置勾选。 - 全量:Rust **645 passed / 0 failed / 20 ignored**(332.87 s,exit code 0); vitest **2339 passed / 12 skipped(0 failed)**,退出码 0; tsc 0;check-web-commands ✓(Rust 258 / web 248 / CommandMap 260);文档三闸 ✓; check-overlay-registry ✓(单选对话框复用 input 那一层,不新增浮层)。 ⚠️ 如实记一次**负载假红**:Rust 全量与 vitest 全量**并发跑**时,communitySaveDialog.test.ts 有一条红;**隔离复跑 16/16 全过**,随后 vitest 单独再跑一遍 **0 failed** ⇒ 判据是 "隔离绿 + 负载证据"(本仓 TESTING 里那条纪律),不是"重试到绿了算数"。 ## 哪些没做 - **A3/A4 是"不做事"**:不实现 6 位短码、不实现扫码(两条都要加依赖);LanAnnounce.fp 仍只透传、pairing_export 侧填 device_id 那一半已有。 - **团队空间的自动来源仍未接上**:本笔只做了"建的时候能选",团队流程(在另一个仓) 上线前,团队空间要么用户手选、要么走隐私面板里那个下拉。 - **没做真机验收**:新勾选框与新建流程都只在本机(happy-dom)验过。 | 27 天前 | |
feat(mobile): 「首次配对」屏做完整 —— 20 格 · 5×4 + 后端算的码 + 人确认(d2;⛔ 确认按钮暂禁用,未接) 来由 ✓:owner 2026-10-17 追加「**完成配对相关功能**」✓ ⇒ d2 把 d1 的骨架填满 ✓。 一、做了什么(【1】【2】【3】✓;【4】**明标未接** ✗) `` 【1】⭐ **20 格 · 5 组 × 4 位 · 竖排 5 行** ✓(.mpair-code-row × 5 ✓,每格 56×56 ✓,类名 mpair-code-* ✓) +「20 位 · 5 组 × 4 位 | 两台必须一模一样」✓ ⭐ 码**只来自** api.pairingCodeReading({ peerDeviceId, peerPubkey }) ✓(api.ts:524 ✓ —— **后端算** ✓,⛔ 前端不算 ✗) ⭐ code: null ⇒ **如实显示 reason** ✓(data-testid="mpair-code-unavailable" ✓); reason 也空 ⇒ 「读不到对码(后端没给原因)」✓(⛔ 不编 ✗) ⭐ 顺带打通 pubkey ✓(pairingCodeReading 需要它 ✓ ⇒ pairPeer 现在带 {deviceId, deviceName, shortId, pubkey} ✓) 【2】⭐ 警示框**照抄** ✓(逐字 ✓):「⚠ 对不上一律不建立信任(**本机一个字都不收**)。两串不一样 ⇒ 多半是中间有人在替它回答, 或者两台不在同一个网络 ⇒ 取消、查网络。」✓ —— 样式用既有 --cat-yellow/--cat-yellow-soft ✓(⛔ 不写死色值 ✗) 【3】✅ 【取消】**是真的** ✓(回 cameFrom ?? "pair" ✓);⛔【码对得上,确认】**禁用** + title 写原因 ✓ (照 08 屏 v1 老规矩 ✓,⛔ 不做假按钮 ✗);★「这一步没有"自动通过":第一次必须由人点一下。(…PAKE…)」✓ 照抄 ✓ 【4】⚠️ **没接** ✗ —— 见下"卡在哪" ✓ 二、⚠️【4】卡在哪(逐字 ✓,⛔ 没绕过守卫 ✗) SyncPanel.tsx:1200-1202 逐字: const trustDevice = async (spaceIds: string[], peerInRow?: NearbyPeer) => { if (!isDesktopPlatform()) { toast("信任设备要在桌面端操作", "info"); return; } ⇒ ⭐ **守卫在第一行** ✗ ⇒ 移动端**调不到**它 ✓ 但它真正干的活**只有一条命令** ✓(:1237 附近逐字): await platform.executor.invoke(MESH_TRUST_BEGIN_COMMAND, { spaceId, peerDeviceId: peer.device_id, peerPubkey: peer.pubkey }); ⚠️ 所以移动端技术上**能直接调** ✓(spaceId + peer 的 id/pubkey 我们都有 ✓,不依赖面板私有状态 ✓) ⚠️⭐ 但那等于**绕过那道"只给桌面"的守卫** ✗ ⇒ ⛔ 实现者**没擅自绕** ✓ ⇒ ⭐ **等 owner 拍** ✓ ` 三、判据(Lead 亲跑 ✓) · tsc 干净 ✓ | check-mobile-class-namespace ⇒ ✓ 干净 ✓ | check-mobile-screen-reachable ⇒ ✓ 干净 ✓ (**登记 10** ✓/**可达 8** ✓/**到不了 0** ✓ —— ⛔ 没破 ✓) · mobile-layout/mobile-views 见提交输出 ⚠️ 窄档几何**量不到** ✗(dev 壳里附近设备为空 ✓ ⇒ 两颗入口不渲染 ⇒ 探针到不了新屏 ✓) ⇒ ⛔ 不声称 20 格/警示框的几何观感 ✓(只经 tsc + 两道门禁 ✓) ⛔ 版本/CHANGELOG/gen/** 未碰 ✓;⛔ **没碰 SyncPanel.tsx`** ✓(只**读**了它 ✓) | 5 天前 | |
| 5 天前 | ||
| 5 天前 | ||
feat(plugin): M11.8 触发面 v1——编辑器 / 菜单 + 命令执行链路共用 插件命令此前只能在命令面板里靠 Ctrl+K 搜索才发现。现在 register({ menus: ["slash"] }) 就能出现在编辑器里输入 / 弹出的菜单里(分组「插件」)。 **合法入口进注册表单一事实源**:capabilities/capabilities.json 新增 menus(含 hosted 标记),由此生成 Rust 表、类型包 PluginMenuName、作者文档 §4.7。两个刻意的选择: - **类型包只广告已实现的入口**——现在只有 slash,写 "page.context" 会编译报错, 而不是运行期静默消失; - JS 侧的值**原样透传**给宿主,认不出来的由**校验器**如实指出(menu_not_hosted / menu_unknown)。静默过滤等于让作者白写一行声明却毫无反馈。 **带参数的命令转交命令面板**:参数表单只实现一份(宿主按 schema 渲染的就在面板里), 所以 / 菜单选中这类命令时把面板打开并用标题预填查询。为此把面板的开关与查询词从 组件内部 useState 提升到 src/store/palette.ts(顺带让快捷键/其它入口也能这么做)。 **执行与结果处理抽成共用链路**(src/lib/pluginRun.ts):结果消息、__toast、插入文本、 **待确认草稿**。触发方式(面板 / / / 将来别的入口)不该改变行为,尤其是草稿确认—— 任何一个入口自己实现一遍,都会出现「某入口忘了处理草稿」,那就等于绕过写中介。 验证:cargo test 127(+2:触发面透传与去重限量、未实现入口被指出)、vitest 194 (+4:/ 菜单筛选的三条规则)、tsc、示例 tsc、作者 CLI、四个门禁、smoke-web 350 全绿。 | 1 个月前 | |
| 18 天前 | ||
feat(plugin): 声明式视图的两种落点(M11.9 收尾:面板的更多形态) 零代码插件此前只有一种显示方式:命令面板点开一个**浮层**。但浮层是"看完了就关"的形态, 而"待整理清单 / 周回顾 / 写作时的参照表"这类东西**本来就该常驻**——每次都要开一次、关一次, 那不是插件的错,是宿主只给了一种形态。加 views[].placement: - overlay(默认,省略即此)/ rail(右侧常驻面板); - rail 与正文并排、右侧竖条上占一个按钮、与 AI / 目录 / 评论**互斥**、桌面主区让位 (窄屏整屏叠加,与其它抽屉同一条规则:body.is-plugin-panel-open); - 两条行为契约各有渲染级测试钉住:**点行不关面板**(常驻面板的用途就是连续查看)、 **数据跟着页面走**(在别处改完一篇,表格跟着更新,不用关掉再开)。 两种形态**共用同一张表**:把浮层里那份渲染逻辑抽成 PluginViewTable(查询/列/汇总/点行 打开那一页都在里面),宿主外壳各自留在 PluginViewOverlay / PluginViewPanel。不抽的话 同一个插件会因为落点不同而看到不同的表——落点不该改变内容。 口径与既有白名单一致(query.kind / sort / columns 那一套):落点不认识**不拒载** (按 overlay 处理,视图永远打得开),校验器给提醒并说明按什么处理;形态写错(写成数字/ 对象)会让整份 manifest 解析失败,所以是**错误**并直指是哪一项。白名单三处同名同值: Rust VIEW_PLACEMENTS、作者 CLI knownPlacements、前端 VIEW_PLACEMENTS——作者 CLI 是 另一份实现,不跟上就会出现"CLI 说没问题、应用却报错"。 作者/用户可见性做全三处:作者 CLI 的视图清单印「→ 浮层 / 右侧常驻面板」、应用内插件管理 列出「会加上这些视图」(rail 会往界面右侧加一个按钮,不说明的话用户只看到一个不明来历的 图标)、命令面板入口文案区分「插件视图:/ 插件面板:」——文案与真正决定落点的是同一个 viewPlacement(),不会各写一份。 入口只有一个:usePluginViewStore.open() 按落点顺手把右栏抽屉摆对。命令面板走浮层、右栏 按钮走抽屉这种"两条路径各写一遍"的写法迟早会漂(一个入口忘了关别的抽屉,右栏就并排两个面板)。 测试: - Rust 3 个(168 总数):落点白名单 + 不认识按 overlay 提醒、形态写错报 view_field_shape 而不是笼统的"加载器会拒载"、声明真的流到 PluginMeta(否则前端永远按默认渲染,且不报错); - 前端 6 个渲染级(src/components/pluginViewHosts.test.ts):右栏只给 rail 视图按钮、 点开画表+让位+再点收起、**点行不关面板**、与 AI 双向互斥、overlay 视图仍走浮层且点行会关、 停用插件不留入口;外加 4 个落点纯函数测试; - 变异验证:把 viewPlacement 改成恒返回 overlay、把 openPlugin 的互斥去掉 → 5 条测试转红。 示例 reading-board 两种落点各示范一个(「最近更新」浮层 + 「本月新增」常驻面板); 文档生成器 §4.9 补了落点表(两种形态各什么时候用)、更新了示例说明,docs/plugin-api.md 已重新生成。 顺带修掉状态文档里一句过期的话(那处信任缺口早已闭合)。 验证:cargo 168、vitest 304(38 文件)、tsc、smoke-web 350、pnpm build、check:examples、 16 个示例逐个 CLI 校验、布局验收 43/0(本机装了 Chrome for Testing 才跑得动这一档)全绿。 | 1 个月前 | |
feat(plugin): 装完之后被改过吗——安装时记内容指纹,事实清单里能对 下载时我们验 sha256、验发布者签名,但装到盘上之后那就是用户自己的目录了。此前没有任何 东西能回答"它还是当初装下来的那一份吗":plugin_install.content_hash 这一列建在那儿 **从来没人写过**(死字段),而"完整性"这条线在落盘那一刻就断了。 现在:安装完成时算一次目录内容指纹,写进那一列;事实清单里多一条,三种情况分开说—— - 内容与安装时一致(指纹 xxxxxxxx) - 内容与安装时不同(安装时 xxxxxxxx,现在 yyyyyyyy) - **没有安装时的内容指纹可对(这一版之前装的),所以无法判断** 最后那条必须说出来:不说的话用户会把它读成"没被改过"。作者自己改插件也会显示成"不同", 那同样是真的——它确实不是当初装下来的那一份。 指纹本身有个设计约束:**只由相对路径与字节决定**。mtime、inode、目录遍历顺序都不掺进来, 否则每次读都会"不一致",这条事实就退化成噪音;而路径要进哈希,否则"把 a.js 改名成 b.js" 看起来像没变过。这两条都有测试钉着(同样的内容隔一会儿再读必须一样、改一个字节就变、 改名也是变化、改回去能回到原指纹)。 测试:Rust 三条(指纹稳定性与三类真实变化、三种文案各自说对话且都带可核对前缀、 事实清单里"内容一致性"排在体积之前)。变异验证:把相对路径从哈希里去掉 → 那条转红。 cargo 229+12、vitest 396、smoke 350、四个门禁全绿。 | 1 个月前 | |
feat(plugin): 多源订阅——一组索引,可增删、可一次检查全部 规范 §3.3 写了"应用维护一组订阅的索引 URL(类似 Homebrew taps / Scoop buckets),用户可增删", 这一版把它做出来。此前每换一个索引都要重新粘地址与公钥,而且应用不记得你订阅过谁—— "协议"是通的,"日常用它"还不通。 面板顶部现在是订阅列表:备注/域名、签名公钥状态、上次检查结果(成功时写「N 个插件,其中 M 个可更新」),点一条即切换过去拉取;「检查更新」一次把所有订阅拉一遍。订阅存后端 ( plugin_index_subscription),不是 localStorage——它是应用的既有事实,该跟着数据走。 **几条刻意的取舍**: - **一条失败不影响其它**:网络本来就会断,用户要看的是"哪几条还活着、哪条为什么挂了"。 所以逐条记结果,失败只写进那条自己的 last_error;前端在有失败时报"1 个索引检查失败: 无法连接到 …",而不是含混的"检查完成"——**后者是误导**。 - **失败时不显示可更新数**:那是上次成功时的旧数字,混在一起会让人以为刚查过。这条有测试钉着。 - **可更新数与安装判定同源**(共用 install_action):界面说"可更新"、点下去却报"拒绝降级" 这种不一致比不显示更糟——所以同版本(重装)与降级都不计入。 - **加订阅时先校验 URL 与公钥形状**:存错了会让之后每次检查都失败在验签那一步,而错误看起来 像"索引有问题",其实是这里存错了。 - 检查时顺带刷新撤回记忆(订阅的意义之一就是"它说了什么我就记住什么")。 - **它仍然不是商店**:没有推荐、没有排序、没有评分、不内置任何官方索引;取消订阅不会卸载 任何东西(订阅只回答"从哪里找插件")。 测试:Rust 三条(增删查 + 同 URL 重复添加只更新、只有更新的版本算"可更新"、失败的订阅记下 自己的错误且不覆盖上次成功的数字)+ 前端 6 条(三种状态分开说、失败时不混进上次的可更新数、 标题备注优先、加载/添加/取消都刷新、检查有失败必须点名、全部成功报总数)。 变异验证:把"更新的版本"放宽成"非降级" → 那条转红。 cargo 227+12、vitest 396、smoke 350、四个门禁全绿。 | 1 个月前 | |
feat(near-realtime): P0.2 presence 在线/谁在编辑——hook + store + TitleBar 指示 - store/presence.ts: 在线成员快照(spaceId/online/refresh)。 - hooks/usePresence.ts: 15s 心跳(teamPresenceBeat)+拉在线(teamOnline),仅当 激活空间绑定 server+space+token。挂在 App.tsx。 - components/PresenceBar.tsx: TitleBar 显示『N 人在线 · M 人在此页』。 - App.css: presence-bar 样式。 校验: tsc 0 错 / vitest 88 passed。 | 1 个月前 | |
fix(community): 社区帖存成笔记后属性区不及时刷新(要重开这一页才看到) 用户实测:「社区文章存进笔记后,属性区不能及时看到,需重新打开才有」。 根因:属性区 PropertiesPanel 是**自己拉** api.getPageProps 存进本地 state 的 (useEffect(load, [pageId, tick])),而这条路是**先建页、后写属性** —— savePostAsNote 建完页才用 set_page_prop 这类**命令**写 来源/作者/发布于/存于,不经过属性区那条写路径 ⇒ 面板挂载时拉到的是"还没属性"的那一份,之后再没人通知它重拉。 (标签没这个问题:那边本来就有 useTagManagerStore.bump(),属性这边一直缺同款信号。) 改法(三处): - store/propertyUi.ts:加 propsRev + bumpProps(),并把约定写进注释 —— **任何绕过属性区写属性的地方,写完都要 bump**; - components/PropertiesPanel.tsx:propsRev 进加载 effect 的依赖(并注明它必须声明在 effect 之前,否则依赖数组在渲染时求值会 TDZ); - lib/communitySaveNote.ts:属性**真写成了**才 bump(一个都没写成不通知,别让面板白拉)。 判据(都对修前状态红过): - communitySaveNote.test.ts:写完属性要 bump / 一个都没成**不** bump(10/10); - PropertiesPanel.test.tsx(新增):bump 后 getPageProps 由 1 次 → 2 次 / propsRev 没变 不白拉(2/2)。A/B 验证:把 propsRev 从依赖里摘掉 ⇒ 第一条**确实红**(1 failed)。 | 29 天前 | |
feat(ui): 顶端工具栏抽成 TopTools 一份实现两处渲染(桌面在 TitleBar、手机在 App 顶部,owner 要求两端都有);旧 rail 仍原样保留,下一笔才撤 | 21 天前 | |
feat: 侧边栏折叠/展开 - 收起为窄图标栏(56px),隐藏文字/操作/搜索 - 折叠状态 localStorage 持久化 - 过渡动画 | 2 个月前 | |
| 5 天前 | ||
| 5 天前 | ||
refactor(ui): 空间管理与账户中心按「高频/低频」归位到设置中心 判据:高频 + 与当前上下文绑定 + 需要状态常驻可见 → 侧栏;低频 + 全局 + 不可逆 → 设置。据此: - 设置中心新增「空间」页:全部空间列表(当前标记)+ 配色 + 删除(二次确认、 当前空间与仅剩一个时禁用)+ 单空间导出/导入。侧栏空间弹层相应瘦身, 只留「切换 / 重命名 / 新建」+ 一个「管理空间」入口。 - 设置中心新增「账户」页:当前团队账号与登出、按服务器分组的同步身份与 分组同步。AccountCenter 组件下线,侧栏图标保留为过渡入口指向该页 (避免老用户按肌肉记忆点过来发现图标消失)。 - 空间导出/导入逻辑抽到 lib/spaceTransfer.ts,进度写入 store/spaceTransfer.ts, 由 App 级 SpaceTransferProgress 渲染——关掉设置也能看到进度。 - 清理随迁移失效的样式(.acct-* / .space-color-* / .space-switcher-io / .space-item-del)。 - SHUYONOTE_STATE 记录该 IA 判据,供后续新入口按同一标准放置。 | 1 个月前 | |
refactor(sync): 配对 begin/end 收成一处 withSyncStatus —— 并补上设置页那条漏掉的同步路径 | 1 个月前 | |
feat: add tag management Add a global tag manager (sidebar trigger): list all tags with page counts, create a tag, rename (merging into an existing same-name tag and cleaning up the orphan), and delete a tag globally (removes it from all pages). Backend adds create_tag/rename_tag/delete_tag and list_tags now returns a page_count; a tag revision keeps the sidebar tag filter in sync. | 1 个月前 | |
fix(template): 模板 load 过滤 built_in,内置不再误入我的模板(避免删不掉/刷新重现) | 1 个月前 | |
Sync app theme with website via shuyo-theme key | 1 个月前 | |
feat(export): 导出加**进度反馈** —— 持久 toast「正在导出…(取附件 3/32)」⇒ 同一条替换成结果 owner 报的缺口:点了一键导出**全程没有任何反馈**,不知道在跑、也不知道跑完没 ( zipSync 又是同步的,大空间看起来就像卡死 ✗)。 - bundleFor 加 onProgress(done, total, phase):取附件循环里每取一件报一次 ⇒ done 单调递增、**总次数 = 附件数** - 两个导出入口(单页 zip / 空间 zip)先弹一条**持久** toast(duration: 0,不自动消失), 取附件时就地更新成「正在导出…(取附件 3/32)」,结束**同一条**替换成结果 - 取消 / 超限 / 失败都**如实**更新那条 toast(⛔ 不假装成功 ✗);失败抛 ExportReportedError, 调用方据此**不重复弹**同一句(EditorToolbar 已接上;"还没走到导出就失败"的仍由它补报) - store/toast:show 支持 duration 并返回 id、新增 update(id,…)(都是新增,老行为不变) - 判据(exportMarkdownBundle.test.ts):回调单调递增且次数=附件数 / 传了 onProgress 不弹默认 toast / 默认持久 toast 跟着进度改且结束时**只有一条**(内容=返回值)/ 取消和失败都如实变 | 5 天前 | |
feat(sidebar): auto-expand + auto-scroll while dragging (P1-1) Hovering a folder's before/inside zone ~0.45s auto-expands it so you can drop into nested children without pre-expanding; dragging near the tree container's top/bottom auto-scrolls. Sticky target retained. - treeDrag store gains expandId; coordinator requests expand on hover. - Bump 1.59.27. | 1 个月前 | |
feat(sidebar): multi-select tree nodes for batch move/delete The sidebar tree was single-select only (click opens the node). Add Ctrl/⌘+click to toggle nodes into/out of a multi-select set, with a batch toolbar (move-to-folder / trash / cancel) at the tree bottom. - New useTreeSelection store shared across the recursive TreeItem. - BatchToolbar moves N nodes into a folder or root, or deletes them. - web move_page now guards against moving a node under itself or its own descendant (prevents tree corruption from batch moves). - Smoke test +2 (cycle guard): 82 passed, 0 failed. - Bump 1.59.6. | 1 个月前 | |
feat(sync): mount the sync panel into the page content area (P-2a-1) -- add AppView sync, render SyncPanel contentOnly in the content area branch, and split the panel with a contentOnly flag so the same JSX is reused by both the drawer and the page; red-first criteria: contentOnly renders no trigger and the popover anchor holds both the sync button and the route text with the button first; the drawer branch is byte-for-byte unchanged; no protocol change, no Rust change | 9 天前 | |
fix(chrome): 默认改成系统标题栏(owner 拍 A)—— lib.rs 以 decorations(true) 建窗,那条不可达的路(运行时 setDecorations(true) 从无到有)实测不报错也不生效;判据含源码扫描 + 真变异红读数 | 12 天前 | |
fix(chrome): 默认改成系统标题栏(owner 拍 A)—— lib.rs 以 decorations(true) 建窗,那条不可达的路(运行时 setDecorations(true) 从无到有)实测不报错也不生效;判据含源码扫描 + 真变异红读数 | 12 天前 |
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 1 个月前 | ||
| 18 天前 | ||
| 1 个月前 | ||
| 4 天前 | ||
| 4 天前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 20 天前 | ||
| 2 个月前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 8 天前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 5 天前 | ||
| 5 天前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 27 天前 | ||
| 27 天前 | ||
| 5 天前 | ||
| 5 天前 | ||
| 5 天前 | ||
| 1 个月前 | ||
| 18 天前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 29 天前 | ||
| 21 天前 | ||
| 2 个月前 | ||
| 5 天前 | ||
| 5 天前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 5 天前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 9 天前 | ||
| 12 天前 | ||
| 12 天前 |