Repository navigation
Releases
dsh-notes v0.8.0 — 网页演示:笔记里就地跑可交互 HTML
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 — 历史回退 + 外部改动实时同步 + 图题公式
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,浜庢槸璁烘枃鍥炬敞鐨勫吀鍨嬪啓娉?,鍔犵矖/琛屽唴鐮佷篃璺熺潃娓叉煋;
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 + 打包自查
见 CHANGELOG.md
v0.5.2 — Windows / macOS 平台兼容:路径方言归一 + 保留设备名 + 跨平台靶子
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:/…vsC:\…);修完插件侧 + 期望值按插件约定归一之后 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 张真实界面截图 + 市场截图声明
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 test238 条全绿、node scripts/build-graph.mjscycles: 0、
node --check通过;npm run build重建后lib/client.js逐字节不变(注释进不了产物)。 - 仍然保留(有意):
LICENSE的作者署名Phy-D、仓库/包名DynaMyhome/dsh-notes、
$DSH_HOME这类公开约定的环境变量。
v0.5.0 — 卡片终于显示「笔记工作区」+ 删掉从不生效的 meta 字段 + 规范化
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(clientSlots, rootsidebar.right.pane.tab)
的 occupants 里dsh-notesactive: true。 - 宿主半实地核对:
/dsh-notes/tree返回 401(路由在、自建鉴权拦下;未知路径是 404)。 npm test(238 条)与node scripts/build-graph.mjs(cycles: 0)保持全绿。
v0.4.0 — 可点选目录 / 排序与时间修正 / 分类改名删除 / CRLF 笔记与表格渲染修复 / 索引改相对路径
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()—— 它要nativecapability,
而本 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.mjscycles 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 注释:root只能给很小的子目录。给插件根会让 chokidar(默认无限递归 + 跟随软链)把整个工作区
递归进去 —— 实测 6530 个 inotify watch 且持续增长、永不 ready,把 WSL 的 drvfs/9p 打满,
表现是端口在听但永不应答(systemd 还显示 active)。只给lib/是 13 个 watch。- 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/)
插件自己的数据现在跟着工作区走了。 这是本插件的一次存储契约变更(带自动迁移,升级零操作):
把工作区备份走、或整份拷到另一台机器,装上同一个插件就能原样读出来 —— 分类树、笔记归属、忽略规则、
置顶、跨工作区映射、回收站(可恢复的删除),全都在工作区里。
破坏性存储变更,带自动迁移。 插件的语义数据(分类树、笔记归属、忽略规则、置顶、
跨工作区映射、回收站)从用户主目录搬进工作区自己的 .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工具新增migrateop(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-graphcycles 0。
怎么用
- 什么都不用做:第一次打开工作区时,旧
$DSH_HOME/knowledge/registry.json里属于它的那部分
会自动迁进<工作区>/.dsh-notes/index.json,旧文件原样保留作备份。 - 搬机器/搬路径:整份拷过去、用那个目录开会话即可(索引里记的绝对路径会按新根自动重定位)。
- 不想要工作区里这个目录:配置
storeScope: home回到旧行为;或用knowledge工具的
migrateop 在两个位置之间显式搬运。 - 要备份"人工组织":带上
.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 条缺陷修复)
首个带 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 条)
- 软换行长段落"点哪光标都在行首" —— 命中判定把"不在同一视觉行"当成"点在更前面",二分一路塌到行首;
现在按视觉行夹区间 + 行内二分,并优先用浏览器自身的文本命中。 - 切「预览/源码」丢未保存的编辑 —— 载入 effect 的依赖带着模式,切模式即销毁重建并重读磁盘;
现在切模式用当前文档重建,不重读。 - 关标签 / 收分屏 / 切工作区不落盘 —— 卸载前没有 flush;现在 cleanup 先取文本再销毁并保存,
关页面 / 切后台走navigator.sendBeacon尽力落盘(并处理 beacon 后版本号过期导致的下一次误报冲突)。 - 表格「删行」删错行 —— 行号语义统一为「数据行 = tbody 下标(0 起),表头 = -1」,
修掉"首行删不掉、其余删的是上一行"。 - 改名后标签失联 ——
refresh依赖写漏导致一直用旧闭包,改名后标题/路径不更新、继续编辑保存报NOT_FOUND;
现在改名即时生效,新建笔记也会自动打开。 - 索引每 ~8s 整份重写、一次写失败永久失效 ——
persist()内容没变就不写盘、失败不毒化后续;
lastUsedAt只在会话换工作区时更新。 - 浮层半透明穿帮 —— 面板/菜单/弹层改用与宿主菜单同一套材质(
--dsw-specific-menu+backdrop-filter)。 - 关键按钮被挤出屏幕 —— 「预览/源码」与「保存」移到编辑器条右侧固定簇(不参与横向滚动),标题不再被压成 30px。
- 英文界面混中文 —— 代码卡「复制」、元数据 chip、快速打开占位符等全部走字典,并加守卫测试
(双字典键必须一致、英文值不许含中文、DOM 文案不许硬编码中文)。
另外修掉了几处更早的编辑期缺陷:wrapSelection 用普通对象冒充选区导致"设一次格式就卡死"、
表格重影与吞掉紧跟段落的行、点击正文时光标漂一行、菜单项点击无响应、拖拽高亮卡住、分屏切源码后另一栏行内装饰全丢。
安全与可靠性
- 四条硬不变量写进
AGENTS.md:永不删除、永不移动用户的.md(只有"改名"与"移入回收站"两处显式例外,
且彻底删除要两次确认);Markdown 权威、索引是可重建的派生态;只加一个 Agent 工具;不改 DSH 核心。 - 保存是守卫式的(带
expectedVersion),并有身份自愈:frontmatter 的dsh-note-id被删/被改时,
Host 按索引写回并在响应里带restoredId。
验证
npm test:164 条单测全绿(含落点、表格、索引落盘与双语文案的守卫用例);build-graphcycles: 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 字段。