Skip to content

Releases

dsh-notes v0.8.0 — 网页演示:笔记里就地跑可交互 HTML

Choose a tag to compare

@Phy-D Phy-D released this 03 Nov 14:45

v0.8.0 — 网页演示:笔记里就地跑可交互 HTML(2026-11-03)

动机

笔记里一直只能指向一个 HTML 演示(写个链接,点开去浏览器看),而这类东西的意义就在
"拖一下、看它变" —— 离开笔记、切窗口、再切回来,理解成本全在切换上。用户原话:
「就是插入 html 的网页演示……让网页能够在笔记里面直接显示,而不仅仅是插入路径」。

新功能

  • ```embed <路径> 围栏 → 就地沙箱 iframe:脚本真的执行(用户在 Analog IC 的
    三电平 SAR 演示上验收:拖 ΔV 滑块,图跟着变)。路径相对笔记目录解析;也可写
    title: / height: 两行配置(path: / src: 同义),高度 160–2400、默认 560。
  • 底边拖拽调高度,按文件记在 localStorage['dsh-notes:embed-size'](最近 200 条),
    重开笔记仍是上次的高度;拖完不会因为"点了下别处"就把 iframe 重载。
  • 条上三枚按钮:「源码」(光标回到围栏)、「刷新」(带 nonce 重载)、「在新标签打开」
    (仅 http(s) 载体;桌面壳的自定义 scheme 下不显示)。
  • 工具栏 21 → 22 键:插入组新增「插入网页演示」;右键「插入」子菜单同步加一条。
  • 光标进入围栏 → 显示原始 markdown(与代码卡一致);移开 → 恢复 iframe。

安全模型(这是本版最需要知道的)

  • 服务端是插件自己的 GET /dsh-notes/embed,不是官方 /api/file:后者响应头是
    Content-Security-Policy: sandbox; default-src 'none',HTML 进 iframe 后脚本/样式全被禁,
    做不出交互。
  • 响应头 EMBED_CSP:sandbox(不带 allow-same-origin)+ 放行内联脚本/样式 + 断网
    (default-src 'none' / connect-src 'none')+ nosniff / no-referrer / no-store。
    于是演示页拿不到 parent.document、document.cookie、localStorage,也发不出网络请求 ——
    演示页必须自包含(内联 CSS/JS、data: 图片;CDN 与相对资源都不行)。
  • 读取边界 = 当前工作区(笔记面板可切到别的已登记工作区,所以 workspaceKey 一路从面板传到
    路由);两边都用 fs.processPath 的 realpath 派生路径比对,挡 .. 与符号链接逃逸;
    只认 .html/.htm,单文件上限 8MB。越界/非 HTML/缺失/超限在 iframe 里显示可读的错误页,
    而不是一坨 JSON。
  • 配置 htmlEmbed(默认 true):关掉 = 该路由一律 403 错误页,其他功能不变。

实现要点(给后来改这块的人)

  • 块级替换只能在 table.ts 的 StateField 里做,且围栏要独占整行(被 > 引用/列表包住的
    退回代码卡)。EmbedWidget.eq() 刻意不比 from(否则在块上方打字就重载 iframe)、也
    不比"算出来的初始高度"(拖拽只改 localStorage),位置用 view.posAtDOM(root, 0) 现取。
  • 纯逻辑独立成 embed-model.ts(解析/路径/URL/高度夹取)与 embed-size.ts(高度记忆),
    在 Node 里可测;Host 侧 service.readEmbed(只读,readBytes 缺失时退化 readText)。
  • 新增单测:317 条全绿(新增 embed-model / embed-size / service-embed,并扩了 routes
    与 locale-guard)。

已知边界(不是 bug)

  • 演示页滚出视口再滚回来,iframe 会被 CM6 重建、演示状态重置(没有做 keep-alive);
  • iframe 获得焦点时编辑器快捷键不生效(点回正文即恢复);滚轮在 iframe 上滚的是演示页;
  • 只支持自包含页面;需要 CDN / 相对资源 / 联网的演示,请用「在新标签打开」或直接在浏览器里看。

本次 Release 同时纳入此前未单独发版的 v0.7.1 / v0.7.2 / v0.7.3(公式字体、外部改动不丢位置 + 图片预留尺寸、历史兜底覆盖 notes/ 之外)——对应 CHANGELOG 里的同名段。

dsh-notes v0.7.0 — 历史回退 + 外部改动实时同步 + 图题公式

Choose a tag to compare

@Phy-D Phy-D released this 04 Oct 06:54

v0.7.0 鈥?鍘嗗彶鍥為€€ + 澶栭儴鏀瑰姩瀹炴椂鍚屾 + 鍥鹃鍏紡(2026-10-04)

涓変欢浜?agent 鏀瑰潖浜嗚兘绮剧‘鍥炲埌鏀逛箣鍓?*銆乤gent 鏀瑰畬鐢ㄦ埛杩欒竟绔嬪埢鐪嬪埌**銆佸浘棰橀噷鐨勫叕寮?涓嶅啀鏄剧ず鎴?$A$ 鍘熸枃銆?

1. 鍘嗗彶鍥為€€:agent 鍐欏墠蹇収(鏍稿績)

*涓轰粈涔堜笉鑳藉彧闈犳壂鎻?*:reconcile 鏈?3s 鍐峰嵈銆乣markDirty400ms 鍘绘姈銆乀TL 8s 鈥斺€?agent 鍦ㄤ竴娆℃壂鎻忓懆鏈熼噷杩炲啓V1鈫扸2鈫扸3鈫扸4,鍙潬浜嬪悗鎵弿鍙兘鐪嬪埌棣栧熬,涓棿鐘舵€佷細涓€? **鍋氭硶**:娉ㄥ唽 tools/execute 鍐欏墠閽╁瓙(lib/index.js 鈫?service.captureBeforeToolWrite)銆?DSH 鐨?tools/execute 鏄?*鐜粫鍒嗗彂**鐨?waterfall (ctx.waterfall(carrier, 'tools/execute', exec, () => dispatchToolBody(exec))),鐩戝惉鑰呭湪 next() 涔嬪墠璺?璇诲埌鐨勬鏄?鍗冲皢琚鐩栨帀鐨勯偅涓€浠藉唴瀹?銆傛彃浠剁骇(鏈墦 scope 鏍囩)鐨勭洃鍚€?鎸?@deepseek-ai/dsh-scope` 鐨勮鍒?鍏ㄥ眬鏀捐,鎵€浠ヤ富 agent / 瀛?agent / workflow 鐨勫啓鍏?閮芥敹寰楀埌銆?

  • 鏁版嵁鍦?<宸ヤ綔鍖?/.dsh-notes/.history/(璺熺潃宸ヤ綔鍖鸿蛋銆佷笉杩?git status銆佷笉琚壂鎻?:
    history.json + <noteId>/<鏃堕棿鎴?.md;historyMaxPerNote(榛樿 50)鏉?瓒呭嚭浠庢渶鏃ц銆?- 鎹曡幏鐐?鍙湁涓や釜* + 涓€涓敤鎴峰姩浣?鍐欏墠閽╁瓙(agent)銆佹壂鎻忚瀵?external/baseline,
    瑕嗙洊 Obsidian / vim / bash / 鍒殑绐楀彛)銆佹仮澶嶅墠鐨勫綋鍓嶅唴瀹?restore)銆?- 鍐呭 hash 鍘婚噸:鍚屼竴娆℃敼鍔ㄨ涓や釜鐐圭湅鍒颁篃鍙暀涓€鏉?agent 鍐欏墠 鈫?鎵弿 鈫?淇濆瓨 鈫?鎵弿
    涓嶄細闀垮嚭閲嶅鏉$洰銆?- 銆屾挙閿€杩欐澶栭儴鏀瑰姩銆嶇簿纭搴?杩欐淇敼涔嬪墠":predecessorOf 鎸?entry 鐨?version
    鎵?浠庢柊鍒版棫绗竴鏉?version !== 褰撳墠纾佺洏鐗堟湰"鈥斺€斾笉鏄?鍊掓暟绗簩鏉?銆傚啓鍓嶉挬瀛愯鐨勬鏄? 鍐欎箣鍓嶇殑鐗堟湰,鎵€浠?agent 杩炲啓涔熶笉浼氶敊浣嶃€?- 鐣岄潰:缂栬緫鍣ㄥ彸渚у浐瀹氱皣鐨勩€屽巻鍙茬増鏈€嶆寜閽?+ 绗旇鏍戝彸閿€屽巻鍙茬増鏈€︺€?鍒楄〃(鏃堕棿/鏉ユ簮/澶у皬)
    • 鍘熸枃棰勮 + 浜屾纭鎭㈠;鍐茬獊妯箙閲屽涓€涓€屾挙閿€杩欐澶栭儴鏀瑰姩銆?鏈夊彲鍥為€€鐨勫巻鍙叉墠鏄剧ず)銆?- 鍐欏墠閽╁瓙姘镐笉鎶?*:浠讳綍寮傚父閮藉彧鏄?杩欐娌¤",骞朵笖鏃犺鎴愯触閮?return next()** 鈥斺€? 鍦?next() 涔嬪墠鎶涢敊浼氳 tools 鐨勮皟搴﹀眰鍙樻垚宸ュ叿澶辫触,绛変簬鎵撴柇 agent 鐨勫啓鍏ャ€?

2. 澶栭儴鏀瑰姩瀹炴椂鍚屾(鐓?Typora)

  • 鏂拌矾鐢?POST /dsh-notes/stat:鍙?stat銆佷笉璇绘鏂?涓€娆¢棶瀹屾湰甯冨眬鎵€鏈夋墦寮€鏍囩銆?- 瀹㈡埛绔瘡 1.5s 鎺竴娆?NotesPane.probeExternal,鎸?椤甸潰鍙 + 杩欎釜 tab 鍦ㄦ樉绀?闂ㄦ帶):
    • 缂栬緫鍣?骞插噣* 鈫?闈欓粯閲嶈浇(淇濈暀鍏夋爣涓庢粴鍔ㄤ綅缃?,杩欏氨鏄?绔嬪埢鐪嬪埌";
    • 鏈?鏈繚瀛樼殑缂栬緫* 鈫?鍙脊鏃㈡湁鍐茬獊妯箙(閲嶆柊杞藉叆 / 鐢ㄦ垜鐨勮鐩?/ 鎾ら攢杩欐澶栭儴鏀瑰姩),
      缁濅笉闈欓粯瑕嗙洊鐢ㄦ埛杈撳叆;
    • 鏂囦欢**涓嶅湪浜?* 鈫?甯搁┗涓€鏉℃彁绀?涓嶉攢姣佺紪杈戝櫒銆?- 涓や釜"涓嶈璇姤"鐨勬姢鏍?淇濆瓨椋炶涓?savingRef)璺宠繃;杩樻湁涓€娆?beacon 鍐欏叆娌¤璁ら
      (beaconTextRef)鏃惰烦杩囥€?- 鏄庣‘涓嶅仛 SSE / WebSocket:閽╁瓙鍙鐩?宸ュ叿 鍐欏叆(Obsidian / vim / bash 閮借鍙︽兂鍔炴硶),
      鏈彃浠朵篃娌℃湁鎺ㄩ€侀€氶亾,鑰?fs.watch 鍦?WSL/drvfs 涓婂疄娴嬫敹涓嶅埌浜嬩欢 鈥斺€?杞鏄繖閲屽敮涓€
      鏃㈢畝鍗曞張瀵规墍鏈夊啓鍏ヨ€呬竴鑷寸殑鏂规銆?

3. 鍥鹃(鍥剧墖 alt)閲岀殑鍏紡

ImageWidget 鐨?caption 浠ュ墠鏄?caption.textContent = alt,浜庢槸璁烘枃鍥炬敞鐨勫吀鍨嬪啓娉?![鍥?4 绾崇背瀛旀簮 + 杩愭斁 $A$ + 鍗曚綅澧炵泭 buffer $A_1$](鈥? 閲岀殑 $A$ 姘歌繙鏄剧ず鎴愬師鏂囥€?鐜板湪鍥鹃涓庤〃鏍煎崟鍏冩牸璧?*鍚屼竴濂?*鍐崇瓥灞?+ 娓叉煋鍣?鍏紡璧?Temml),鍔犵矖/琛屽唴鐮佷篃璺熺潃娓叉煋;
img.alt 浠嶄繚鐣欏師鏂?鏃犻殰纰?銆傝鍐呰В鏋愬櫒鏀舵暃鎴愬崟渚?editor/inline-parse.ts)銆?

鏂板閰嶇疆

閰嶇疆 榛樿 璇存槑
history true 鍏虫帀 = 瀹屽叏涓嶅仛棰濆璇诲啓(閫冪敓寮€鍏?
historyMaxPerNote 50 姣忕瘒淇濈暀澶氬皯鏉″巻鍙?瓒呭嚭浠庢渶鏃ц)

娴嬭瘯

274 鏉?鏂板 history(绾€昏緫)/ service-history(鐪熸枃浠?V1鈫扸2鈫扸3鈫扸4 杩炲啓銆?鍘婚噸銆佹壂鎻忓厹搴曘€佽秺鐣屾枃浠跺悕銆佽秴澶ф枃浠?/ index-hook(瑁呴厤濂戠害:閽╁瓙蹇呴』鏀捐 next()銆?璇荤洏澶辫触涔熶笉鑳芥墦鏂啓鍏?/ service-stat / 璺敱 + 鍥鹃鍏紡闈跺瓙)銆?

dsh-notes v0.6.0 — 从零安装可用:漏声明 peer + 打包自查

Choose a tag to compare

@Phy-D Phy-D released this 02 Oct 13:28

见 CHANGELOG.md

v0.5.2 — Windows / macOS 平台兼容:路径方言归一 + 保留设备名 + 跨平台靶子

Choose a tag to compare

@Phy-D Phy-D released this 02 Oct 09:11

v0.5.2 — Windows / macOS 平台兼容:路径方言归一 + 保留设备名 + 跨平台靶子(2026-10-02)

修(都只在 Windows 上才会犯,Linux 上测不出来)

  • 路径方言不再混用。node:path 的 join() 在 Windows 上产出 \,而插件内部约定一律 /
    (lib/notes.js 的 toPosix)——lib/store.js 的路径助手与 lib/service.js 里 3 处漏归一的
    join()(回收站落盘路径、扫描根展开、.dsh-notes/.gitignore)现在全部过 normalizePath()。
    不归一的话,同一个目录会同时存在两种写法,字符串比较与集合键随即静默失配。
    实测依据:在 Windows node(v22.14)上跑那 32 个不依赖 harness 依赖的测试文件,原本 12 条红
    (C:/… vs C:\…);修完插件侧 + 期望值按插件约定归一之后 208/208 全绿,Linux 侧 250/250 全绿
    (Windows 那轮跑的是真实 Windows 文件系统:真建/真读/真改名/真删除,回收站与 CRLF 行尾保真都在里面)。
  • Windows 保留设备名:CON / PRN / AUX / NUL / COM1–COM9 / LPT1–LPT9
    (含 CON.md 这种带后缀也保留的写法)→ sanitizeFileName() 现在会避开(CON → CON-,
    CON.md → CON-.md)。以前在 Windows 上新建一篇标题叫 CON 的笔记会直接失败。
  • 目录选择器的面包屑:官方给的 crumbs[].path 与我们会话 cwd 的大小写可能不同,原来按字符串
    相等找工作区根 → Windows/macOS 上可能整条面包屑不显示;改成「互相都能相对化」判定。

新增

  • test/platform-paths.test.mjs(12 条):喂 Win32 输入的字符串,把「内部只有一种路径写法」
    钉成可执行约定 —— 在 Linux 上跑也照样抓得到回归(注入一次原生 join,实测立刻 1 条红)。
    同时覆盖盘符大小写不敏感、跨盘不相对化、保留设备名、尾随点与空格、单组件长度上限、面包屑匹配。
  • README 新增 ## 平台支持:三平台状态与证据、上面这些差异、以及没有真机的 macOS 边界
    (HFS+ 的 NFD 归一化、Windows 260 字符路径上限)。AGENTS.md 加了「平台约定」一节。

变更

  • 版本 0.5.1 → 0.5.2;lib/client.js 因 browse-path.ts 改动而重建;单测 238 → 250 条。

v0.5.1 — README 图文版:8 张真实界面截图 + 市场截图声明

Choose a tag to compare

@Phy-D Phy-D released this 02 Oct 09:11

v0.5.1 — README 有图了:8 张真实界面截图 + 市场截图声明(2026-10-02)

文档

  • README 从纯文字变成"看图就懂":页首一张区域全貌,新增 ## 界面预览 一节,按功能段落排 7 张截图
    (预览模式 / 表格公式代码卡 / 标签与分屏 / 大纲与快速打开 / 纳入管理 / 分类右键 / 回收站)。
    截图取自运行中的插件本体(DSH 中文界面),示例内容是一套虚构的「星图 Aster」知识库 ——
    不含任何真实笔记、会话、路径或机器信息(回收站面板里那行绝对路径已抹掉)。
  • 新增仓库根的 screenshots.json:8 条相对路径,顺序即画廊顺序。插件市场(dsh-market)按它展示
    App Store 风格截图;不声明时市场只能"从 README 里抓图",声明后能控制顺序与取舍。
    路径相对该文件本身、不以 / 开头、不含 ..,符合
    awesome-dsh-plugin 的截图约定。

变更

  • 版本 0.5.0 → 0.5.1:只有文档与截图声明,没有代码改动(lib/ 与 src/ 一行未动,lib/client.js 未重建)。
  • 图片放 docs/images/,8 张 PNG、1217×1028、合计约 1.1 MB;package.json.files 白名单不变
    (private: true,本轮不发 npm)。

隐私清理(同日补,2026-10-02)

仓库转 public 之后复查了全部公开文件,把与本机布局相关的字符串清掉(只改字符串,不改行为):

  • 本机绝对路径 → 中立写法:/mnt/<盘>/.../dsh-notes → <你克隆 dsh-notes 的目录>;
    /home/<user>/.dsh/profiles/web/node_modules → $DSH_HOME/profiles/<profile>/node_modules;
    /mnt/<盘>/... 这类举例 → 「Windows 盘(/mnt/*)」/「很长的磁盘路径」。
  • 涉及:AGENTS.md、README.md、CHANGELOG.md、src/client/editor/setup.ts 的注释、
    lib/service.js 的注释,以及 test/*.mjs 里当假数据用的绝对路径(改成 /home/user、
    /data/... 这类中性值;断言只看"是不是绝对路径",不依赖具体值)。
  • 复核:npm test 238 条全绿、node scripts/build-graph.mjs cycles: 0、
    node --check 通过;npm run build 重建后 lib/client.js 逐字节不变(注释进不了产物)。
  • 仍然保留(有意):LICENSE 的作者署名 Phy-D、仓库/包名 DynaMyhome/dsh-notes、
    $DSH_HOME 这类公开约定的环境变量。

v0.5.0 — 卡片终于显示「笔记工作区」+ 删掉从不生效的 meta 字段 + 规范化

Choose a tag to compare

@Phy-D Phy-D released this 01 Oct 15:36

v0.5.0 — 插件卡片终于显示「笔记工作区」/ 删掉从不生效的 meta 字段 / 补齐许可证与规范化(2026-10-01)

修

  • 设置 → 插件 卡片标题一直显示包名 dsh-notes,本地化从未生效。
    根因是两处形状都不对:
    • package.json 里的 meta: {title, description} 根本不是 DSH 读的字段 ——
      dsh-app-boot 的 readPluginMeta()(lib/index.js:1969)只读 manifest.icon /
      manifest.name / manifest.description 与 locale/<lang>.json;已在 0.1.7-alpha.1 与
      0.2.0-rc.2 两个 runtime
      里 grep 确认没有任何代码读 manifest.meta。
    • locale/{en,zh}.json 写成了根级 title / description,而读取端(dictionariesOf())
      读的是 parsed.meta.title / parsed.meta.description —— 于是整个字典被当成"没有 title",
      标题退回 manifest.name(包名),描述退回 manifest.description(中文描述就这样"看起来是对的",
      掩盖了问题)。
      修法:locale 两份改成规范形状 {"meta":{"title","description"}};删除 package.json.meta
      (内容已搬进 locale,不留一个从来没人读的字段误导后来人)。

变更

  • 补 license / repository / homepage / bugs 字段与 LICENSE 正文(MIT © 2026 Phy-D):
    此前 package.json 既没有 license 字段、仓库里也没有许可证正文。
  • 新增 .gitattributes(* text=auto eol=lf + 二进制保护):本插件的开发目录在
    /mnt/d/...,工作树被 Windows 侧工具改成 CRLF 时,git diff 会整文件重写、真实改动被淹没
    (dsh-restart-manager 上实测过 719 增 / 719 删而真实差异为 0)。
  • files 白名单:locale → locale/*.json(与其他插件一致),补 LICENSE。
  • 版本 0.4.0 → 0.5.0。private: true 保留(只挡 npm publish,不影响 git/link: 安装,
    本项目也从不走 npm 发布)。

文档更正(重要,与本插件代码无关但会误导人)

  • dsh.client.inject 不是"信息性字段"。 早先 README 沿用了 dsh-device-center 的说法
    ("框架不校验"),那是错的:@deepseek-ai/dsh-client-modules 会校验它
    (optionalStringArray(pkgName, "dsh.client.inject", decl.inject)),并把它同时用作
    ①客户端模块图的到达前置(arriveGraphRow 先 await 完 row.inject 里的依赖)与
    ②客户端插件 fiber 的 inject(即要等这些客户端服务就位才激活)。
    所以这份清单必须只列这个 bundle 真正必需的服务;凡是"有就读、没有就算了"的可选服务
    (本插件的 uiWorkspace 就是)绝不能写进去,否则最小组合里整个插件起不来 ——
    这正是 AGENTS.md「服务经 ctx.get('uiWorkspace') 可选读取(不写进 dsh.client.inject)」
    那条注释的由来。本插件现有三项
    (dsh-client-locale / dsh-client-ui-slots / dsh-client-ui-sidebar-right)
    恰好等于 lib/client.js 自己的 exports.inject,保持不变。

实测

  • 设置 → 插件 卡片显示「笔记工作区 / Notes workspace」与自有图标(此前是包名 + 通用占位图标)。
  • 客户端槽位实地核对:cordis_inspect_query(client Slots, root sidebar.right.pane.tab)
    的 occupants 里 dsh-notes active: true。
  • 宿主半实地核对:/dsh-notes/tree 返回 401(路由在、自建鉴权拦下;未知路径是 404)。
  • npm test(238 条)与 node scripts/build-graph.mjs(cycles: 0)保持全绿。

v0.4.0 — 可点选目录 / 排序与时间修正 / 分类改名删除 / CRLF 笔记与表格渲染修复 / 索引改相对路径

Choose a tag to compare

@Phy-D Phy-D released this 01 Oct 11:09

v0.4.0 — 可点选目录 / 排序与时间修正 / 分类可改名可删除 / CRLF 笔记打不开 / 索引改记相对路径(2026-10-01)

修

  • 新建分类立刻报「分类不存在」 —— 行身份取错了字段:TreePane 里行内改名用的是
    row.id,而 Row 上从来没有这个字段(取出来是 undefined),于是
    renameCollection(ws, undefined, name) 返回 false,Host 抛「分类不存在」。
    新建分类走的是"先建默认名、再立刻进 行内改名",所以看起来是一建就报错。
    改成从行 key 上取 id(c:<id> / n:<id>,笔记那条分支本来就是这么写的)。
    旁证:用户树里那 5 个还叫「新分类」的分类,就是这条的症状。
  • 「最近」其实不是按时间排 —— Host 用 Number(entry.version) 当 mtime,而真 provider
    (@deepseek-ai/dsh-fs-local)的 version 是 `${dev}:${ino}:${size}:${mtimeNs}:${ctimeNs}`,
    Number(...) 是 NaN → at 恒为 0 → 全部并列 → 实际按路径排。现在按严格形状解析第 4 段
    (mtimeNs),认不出的形状一律 0(回落路径序,绝不瞎猜)。测试替身一直给
    String(mtimeMs),所以这条在单测里永远测不出来
    —— 新测试改用真形状,与运行时同构。
  • 打开 CRLF 笔记报 Selection points outside of document —— CM6 的
    EditorState.create({ doc }) 用 /\r\n?|\n/ 切分重建,CRLF 文件的 doc.length
    比磁盘原文少掉「CR 的个数」;initialAnchor 拿原文长度当坐标,而那一处
    dispatch({selection:{anchor}}) 又是全项目唯一没夹取的。用户那篇 123KB / 1244 个 CR
    的笔记正中:状态栏以前显示 75404,现在是 74160(= 75404 − 1244)。
  • 保存会静默把 CRLF 改成 LF —— fs.writeText 是原样写(不像官方 editText 会
    restoreLineEndings)。现在保存前采样文件头判断行尾并按原风格写回;
    FS_STALE_VERSION 交出去的 currentText 也归一成 LF —— 否则 CRLF 笔记上
    "磁盘内容 == 我正要写的内容"那条自愈分支永远不成立,会误报外部修改。
  • 超过 64KB 的笔记根本存不下去 —— 路由的 JSON 请求体上限是 64KB,而 save 走的正是它:
    用户那篇 123,530 字节的笔记每次保存都被挡住,界面只显示「Save failed」,
    真正的原因(请求体过大)藏在响应里。上限提到 8MB(本地同源路由、自带鉴权,
    笔记是纯文本;资产那条本来就是 16MB)。靶子用例:一条 >128KB 的 body 必须 200。
  • 表格里的 markdown 完全没渲染 —— 单元格以前是 td.textContent = cell.text,
    于是 **加粗**、$公式$、==高亮==、`代码`、[[双链]] 在表格里全是原文。
    现在每格单独跑决策层再落成 DOM(见 editor/cell-inline.ts / cell-render.ts)——
    规则不在第二处重写,"该藏还是该露"仍然只有决策层说了算。公式走 Temml(与正文同一个渲染器)。
  • 表格列一多就把整篇顶宽(分屏时正文也得左右拖) —— 两个原因是叠加的:
    ① 单元格里的长文本不换行,把列撑到几千像素;
    ② .cm-content 是 CM6 的 flex item(flex-grow:2; flex-shrink:0),
    它的 min-width:auto 等于min-content 宽度 —— 宽表格的 min-content 把内容区撑到比滚动区还宽
    (实测:滚动区 384px、内容区 687px),于是连正文都在 687px 上换行。
    修法:表格自己滚(外壳 overflow-x:auto; max-width:100%)+
    内容区 min-width:0(让正文老老实实等于滚动区宽度)+ 单元格换行。
    现在照 Typora:正文一律自适应、没有横向滚动条;只有表格在列多时出横向滚动条。
  • 渲染出来的图片不跟着横向宽度自适应 —— inline-flex 外壳的宽度是由图片本身决定的,
    子元素再写 max-width:100% 只是"相对自己"的空转,一张 2000px 宽的图照样把正文顶宽。
    现在外壳也带 max-width:100%(百分比相对行/内容区解析,这才是真约束),
    图片 max-width:100% + height:auto(只缩不放、保持长宽比)。

新增

  • 「选择目录…」:扫描范围不再只能手打。 面板内浏览器(面包屑 + 子目录列表,
    种子 = 工作区根,且不许走出工作区;点目录/SKIP_DIRS 不给,免得选了也是空的)。
    数据来自官方 uiWorkspace.listDirectory;拿不到该服务时按钮不出现(官方口径:
    藏起入口而不是失败)。特意没用 pickDirectory() —— 它要 native capability,
    而本 profile 组合的是 browse 后端,调用会被 directory-picker/unavailable 拒绝(实测)。
  • 排序:候选/杂项两段支持 时间 / 名称 / 路径 / 大小 × 升降序;「最近」默认时间倒序;
    每行显示修改时间(认不出时间的文件不显示那列 —— 不拿假时间骗人)。
  • 分类右键:重命名 + 删除。删除只动树(索引里的归属),磁盘上一个文件都不碰:
    「笔记移到上级分类」或「笔记留为未归类」。二级菜单本身就是二次动作,不再弹窗确认。
  • 扫描范围接受绝对路径:Host 侧换算成工作区相对,并拒绝工作区外的目录
    (否则"多看一个目录"就变成越界读用户主目录)。

存储:索引改记相对路径

<工作区>/.dsh-notes/index.json 里,笔记与笔记根从绝对路径改成工作区相对
(notes[].rel / workspaces[].notesRootRel);只有 workspace.root 保持绝对 —— 它是锚点,
也是工作区键的来源。

  • 为什么:这个文件就躺在工作区里,它记的每一篇笔记必然在工作区内,记绝对路径等于把
    "这台机器的盘符和目录名"抄进用户数据。v0.3.0 的搬迁能力其实是靠装载时那一趟
    rebasePath 硬扳回来的;改成相对之后搬迁 = 零操作(同一个文件在新根下直接就是对的),
    文件也不再泄漏机器路径。
  • 迁移:旧文件(绝对 path)装载时自动补出 rel,下次落盘即迁移完;键与 root 的认领逻辑不变。
  • 例外:storeScope: 'home'(机器本地一份整体索引,里面装着多个工作区与跨工作区映射)
    仍然用绝对路径 —— 那里只有绝对路径说得清"是哪个文件"。
  • 一处刻意的区分:notesRootOverrideRel 用 '.' 表示"显式设成工作区根",因为 '' 在
    这个字段上的含义是"没设过";不区分的话往返一趟就丢(用户显式选过的笔记根会变回默认)。

工程

  • 单测 184 → 238 条;node scripts/build-graph.mjs cycles 0。
  • 新增 src/client/scan-sort.ts(排序纯逻辑)、src/client/browse-path.ts(目录浏览器纯逻辑)、
    src/client/DirPicker.tsx;src/client/editor/cell-inline.ts(单元格行内节点模型,纯函数)、
    cell-render.ts(落成 DOM);lib/notes.js 新增 mtimeOfVersion / detectEol / applyEol /
    normalizeEol / relativeToWorkspace;lib/store.js 新增 relativizeState。
  • Host 半热重载打通(本轮实测,profile 侧配置):给 hmr 行的 root 加上本插件的
    lib/ 目录,改 Host 代码约 1 秒后自动热替换插件条目,不用重启;客户端半一直走
    dsh-client-hmr 的 500ms bundle 轮询,改完 npm run build 已打开的页面自动换新(不用刷)。
    两个坑都写进了 profile 注释:
    1. root 只能给很小的子目录。给插件根会让 chokidar(默认无限递归 + 跟随软链)把整个工作区
      递归进去 —— 实测 6530 个 inotify watch 且持续增长、永不 ready,把 WSL 的 drvfs/9p 打满,
      表现是端口在听但永不应答(systemd 还显示 active)。只给 lib/ 是 13 个 watch。
    2. WSL 的 inotify 看不见 /mnt/d(drvfs):独立进程实测,改 ext4(/tmp、/home)能收到
      change,改 /mnt/d/... 收不到任何事件。所以必须开 usePolling
      (chokidar 选项能经 config 透传:schemastery 会保留未声明的键,不用给
      @deepseek-ai/dsh-hmr 打补丁);interval: 1000,实测代价 +0.5% 单核。

v0.3.0 — 插件数据跟着工作区走(索引与回收站进 <工作区>/.dsh-notes/)

Choose a tag to compare

@Phy-D Phy-D released this 01 Oct 07:00

插件自己的数据现在跟着工作区走了。 这是本插件的一次存储契约变更(带自动迁移,升级零操作):
把工作区备份走、或整份拷到另一台机器,装上同一个插件就能原样读出来 —— 分类树、笔记归属、忽略规则、
置顶、跨工作区映射、回收站(可恢复的删除),全都在工作区里。

破坏性存储变更,带自动迁移。 插件的语义数据(分类树、笔记归属、忽略规则、置顶、
跨工作区映射、回收站)从用户主目录搬进工作区自己的 .dsh-notes/:

<工作区>/
├── notes/                  # 笔记(不变)
├── .dsh-assets/            # 图片资产(不变)
└── .dsh-notes/             # 新增:插件数据(自动写 .gitignore,不进 git status)
    ├── index.json          #   薄索引
    └── .trash/             #   回收站(文件本体 + trash.json)

为什么:以前这些数据在 $DSH_HOME/knowledge/registry.json —— 用户主目录。备份工作区、
或把工作区搬到另一台机器,数据不跟着走,得从头组织一遍。现在整份拷过去、装上同一个插件就能原样读出来。

变化

  • 跟着工作区走:备份 / 迁机 / 改路径之后,分类树、归属、忽略规则、置顶、跨工作区映射、
    回收站(可恢复的删除)全都在。
  • 路径自动重定位:索引里记的是绝对路径;工作区换路径后,装载时按旧根的相对位置重定位到新根,
    不会留下一堆指向老机器的死路径(这条不做的话,"搬走的工作区"是打不开的)。
  • 升级零操作:第一次打开工作区时,把旧 registry.json 里属于它的切片自动迁进
    .dsh-notes/index.json,旧文件原样保留作备份;已知工作区列表改存 $DSH_HOME/knowledge/workspaces.json。
  • 机器本地只剩扫描缓存(index/<key>.json,派生数据)与"这台机器见过哪些工作区"。
  • storeScope: home 逃生开关:完全回到旧行为(只读挂载、共享仓库、应急回退时用);
    knowledge 工具新增 migrate op(toWorkspace / toHome),在两个位置之间显式搬运
    (移动语义,不留两半)。
  • 只读工作区:读路径(对账/扫描)只记一次日志、不抛,界面仍能用内存里的索引;
    用户显式写操作才如实报错。
  • .dsh-notes/ 永不被扫描:走目录时跳过所有点目录,所以回收站里的 .md 不会被当成候选笔记
    重新登记回树(回收站搬进工作区的前提)。
  • 标签/分屏布局、字号刻意留在浏览器 localStorage(设备形状的偏好,不跟着工作区跑)。

工程

  • 新增 lib/store.js(存储位置决策 + 数据切片,纯函数)与 test/store.test.mjs(9 条)、
    test/store-scope.test.mjs(8 条:迁移 / 搬走工作区 / 只读 / 回收站 / 点目录不被扫 / 两种形态等价)。
  • 单测 184 条(v0.2.0 是 164 条),build-graph cycles 0。

怎么用

  • 什么都不用做:第一次打开工作区时,旧 $DSH_HOME/knowledge/registry.json 里属于它的那部分
    会自动迁进 <工作区>/.dsh-notes/index.json,旧文件原样保留作备份。
  • 搬机器/搬路径:整份拷过去、用那个目录开会话即可(索引里记的绝对路径会按新根自动重定位)。
  • 不想要工作区里这个目录:配置 storeScope: home 回到旧行为;或用 knowledge 工具的
    migrate op 在两个位置之间显式搬运。
  • 要备份"人工组织":带上 .dsh-notes/(它自带 .gitignore,不会污染你的 git status)。
    只删掉它的话,笔记文件不丢、rescan 能把"哪些是笔记"认回来,但分类树/归属/忽略/置顶会退化成平铺树。

验证

190 条单测全绿(test/store.test.mjs 10 条 + test/store-scope.test.mjs 9 条是新加的),
build-graph cycles 0;真机实测:迁移后树结构与迁移前逐项一致、旧索引未被改写、
工作区 git status 看不到 .dsh-notes/、切到别的工作区不会在那里留下空目录。

v0.2.0 — 首个正式版本(常驻笔记区 + 字号缩放 + 9 条缺陷修复)

Choose a tag to compare

@Phy-D Phy-D released this 30 Sep 17:45

首个带 tag 的正式发布(0.1.0 是开发期初始版本,当时没有打 tag、也没有 release)。
自上次推送累计 40 个提交:P0–P6 全部落地,并修完两个只读审计会话里核实过的 9 条缺陷。

亮点

  • 笔记区常驻:侧栏切到别的 tab、或把整栏收起,笔记区只是被藏起来(注册时 keepMounted: true)——
    笔记树、打开着的标签、光标与滚动都在原处,切回来不再重新加载「读取中…」;隐藏期间不轮询,回来立刻对一次账。
  • 字号 / 图标大小可调:标题栏 Aa(五档预设 + 滑块 + 复位),只作用于笔记区;基准字号跟随宿主
    「设置 → 通用 → 字体大小」。统一公式 cssSize() = calc(Npx × 系数 + 增量),不用 zoom(那会把浮层坐标推偏)。
  • 标签栏 + 左右分屏:每栏一条自己的标签栏;点=替换、Ctrl/Cmd+点击或中键=新标签、拖标签或 ⇥ 分屏;
    跨栏拖动、栏内按中线重排、空栏自动收;布局按工作区记住。
  • 笔记树 + 纳入管理面板:工作区里的 md 分三类(笔记 / 候选 / 杂项);面板按最近改动或文件夹分组,
    可搜索、多选、批量纳入或忽略,并显式管理扫描范围(默认只扫 notes/)。
  • 编辑器:预览/源码按栏独立;工具栏 21 键(6 组 + 4 弹层:标题 / 链接 / 表格 / 公式);
    右键菜单(引用此处 / 文本格式 / 段落设置 / 插入,表内换成插删行列);[[双链]] 补全;
    图片粘贴与拖入;真表格就地编辑;代码卡片(语言可改、一键复制);块级公式;frontmatter 收成一行元数据 chip;
    Ctrl/Cmd+S/F/B/I/E、Ctrl+1…6/0、表格内 Tab 跳单元格。
  • 大纲面板(标题树,可拖拽重排整节)、快速打开(Ctrl/Cmd+P)、回收站(可恢复 / 彻底删除)。
  • 工作区切换:能切到别的已登记工作区,各自独立的树与布局;缺 notes/ 的工作区给空态卡片。
  • Agent 侧:新增 knowledge 工具(登记 / 注销 / 归类 / 分类树 / 未归类 / 重扫 / 候选与忽略 / 回收站);
    内容读写仍然只用 DSH 既有的文件工具。

修复(用户实测的 9 条)

  1. 软换行长段落"点哪光标都在行首" —— 命中判定把"不在同一视觉行"当成"点在更前面",二分一路塌到行首;
    现在按视觉行夹区间 + 行内二分,并优先用浏览器自身的文本命中。
  2. 切「预览/源码」丢未保存的编辑 —— 载入 effect 的依赖带着模式,切模式即销毁重建并重读磁盘;
    现在切模式用当前文档重建,不重读。
  3. 关标签 / 收分屏 / 切工作区不落盘 —— 卸载前没有 flush;现在 cleanup 先取文本再销毁并保存,
    关页面 / 切后台走 navigator.sendBeacon 尽力落盘(并处理 beacon 后版本号过期导致的下一次误报冲突)。
  4. 表格「删行」删错行 —— 行号语义统一为「数据行 = tbody 下标(0 起),表头 = -1」,
    修掉"首行删不掉、其余删的是上一行"。
  5. 改名后标签失联 —— refresh 依赖写漏导致一直用旧闭包,改名后标题/路径不更新、继续编辑保存报 NOT_FOUND;
    现在改名即时生效,新建笔记也会自动打开。
  6. 索引每 ~8s 整份重写、一次写失败永久失效 —— persist() 内容没变就不写盘、失败不毒化后续;
    lastUsedAt 只在会话换工作区时更新。
  7. 浮层半透明穿帮 —— 面板/菜单/弹层改用与宿主菜单同一套材质(--dsw-specific-menu + backdrop-filter)。
  8. 关键按钮被挤出屏幕 —— 「预览/源码」与「保存」移到编辑器条右侧固定簇(不参与横向滚动),标题不再被压成 30px。
  9. 英文界面混中文 —— 代码卡「复制」、元数据 chip、快速打开占位符等全部走字典,并加守卫测试
    (双字典键必须一致、英文值不许含中文、DOM 文案不许硬编码中文)。

另外修掉了几处更早的编辑期缺陷:wrapSelection 用普通对象冒充选区导致"设一次格式就卡死"、
表格重影与吞掉紧跟段落的行、点击正文时光标漂一行、菜单项点击无响应、拖拽高亮卡住、分屏切源码后另一栏行内装饰全丢。

安全与可靠性

  • 四条硬不变量写进 AGENTS.md:永不删除、永不移动用户的 .md(只有"改名"与"移入回收站"两处显式例外,
    且彻底删除要两次确认);Markdown 权威、索引是可重建的派生态;只加一个 Agent 工具;不改 DSH 核心。
  • 保存是守卫式的(带 expectedVersion),并有身份自愈:frontmatter 的 dsh-note-id 被删/被改时,
    Host 按索引写回并在响应里带 restoredId。

验证

  • npm test:164 条单测全绿(含落点、表格、索引落盘与双语文案的守卫用例);build-graph cycles: 0;
    npm run build 复现字节一致的 lib/client.js。
  • 浏览器里逐条实测(其中涉及写操作的项目用页内探针拦 POST /dsh-notes/save,保证验证期间不写用户笔记;
    结束后把 notes/ 逐字节还原并用 md5 复核)。

安装

DSH 插件,以 link: 装进 profile(步骤见 README.md)。
本版本在本机 DSH 0.2.0-rc.2 上验证,依赖官方右侧栏扩展点 ctx.sidebarRightTabs 与 keepMounted 字段。