dsh-tiddlywiki 0.25.1 → 0.26.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/lib/client.js +18 -15
- package/lib/index.js +125 -0
- package/lib/index.js.map +1 -1
- package/package.json +2 -2
- package/src/client/settings-page.ts +127 -11
- package/src/client/styles.ts +3 -0
- package/src/host/admin.ts +151 -0
package/README.md
CHANGED
|
@@ -55,6 +55,8 @@
|
|
|
55
55
|
| 💾 **数据即备份** | wiki 文件夹本身就是 git 仓库,自动 commit(60s 防抖) |
|
|
56
56
|
| ⚙️ **设置页不再「假装生效」** | **v0.25.0** 把一批「保存了却不变」的项收敛掉:`git.autoCommit`/`git.debounceMs`/`git.remote` 保存后**对运行中的插件立即生效**(重建防抖提交器 + 指向新 remote;`git.branch` 只对首次初始化的仓库有意义,标签里已写明,状态行显示的是真实分支);数值项(端口 / 就绪窗口 / 分页条数 / 防抖毫秒)带 `min`/`max` 与字段旁红字,非法值**拒绝保存**而不是静默夹取;保存被拒(409,例如配置 tiddler 残留冲突标记)时错误**持久显示**而不是一闪而过的 toast;「没有改动」不再发请求、宿主也不重写配置 tiddler(每次空点保存都给 git 造 diff / 两台机器在 `.meta` 上冲突);插件/主题/语言的未应用勾选能活过「同步 / 重启」触发的重刷;表单有未保存内容时若配置在别处被改过,**保留你的输入**并给出显式提示而不是重建表单;微信发布的 `command`/`adapter`/`token`/`dsn` 补齐 UI(token 是密码型 + 显隐切换,掩码回填照旧) |
|
|
57
57
|
|
|
58
|
+
| 🔍 **插件状态不说谎** | **v0.26.0**:设置页「插件管理」的勾选只代表 `tiddlywiki.info` 启动清单,而 TW 运行时集合还要并上**经 TW 原生插件库装进 wiki 的插件 tiddler**、减去**在 TW 面板里被禁用的**。两边不一致时(TW 里启用着、设置页没有——或反过来),行内会显示「**wiki 内已装**」「**TW 内已禁用**」徽标,并单列只读小节「**wiki 内插件(经 TW 原生安装)**」(名称/版本/描述),启停与卸载引导回 TW 控制面板。第三方插件的安装与管理仍然完全交给 TW 原生界面 |
|
|
59
|
+
|
|
58
60
|
---
|
|
59
61
|
|
|
60
62
|
## 📦 安装
|
|
@@ -152,7 +154,7 @@ dsh plugin --profile web add link:/path/to/dsh-tiddlywiki
|
|
|
152
154
|
- **📚 会话知识库 Tab**:会话顶部 Tab(`ui.tabLabel` 改名、`ui.showSessionTab` 关闭),自动汇总本会话读写过的 wiki 笔记(写入 volatile `$:/temp`,不落盘不进 git),**TW 原生渲染**(v0.16.19 起 `/tw/render` 片段管线,与回复流工具卡同链路),链接点击直达中央 TW 面板,不可编辑。
|
|
153
155
|
- **🌗 跟随 DSH 主题**:内嵌 TW 随 DSH 深浅切换 palette,纯内存不写回 wiki(`ui.followDshTheme`/`ui.darkPalette`)。
|
|
154
156
|
- **🔧 知识库 FAB**:统一入口(TW 面板开关/重载、快速笔记、同步、TW 服务状态悬停 tip)。同步拉取到新内容会自动重启 TW(同端口)。
|
|
155
|
-
- **⚙️ 设置页**:DSH 设置 →「TiddlyWiki
|
|
157
|
+
- **⚙️ 设置页**:DSH 设置 →「TiddlyWiki 知识库」:状态/重启、**知识库位置(可切换)**、常规配置、插件/主题/语言管理(**自带官方插件**的离线启停;经 TW 面板装的第三方插件以只读小节列出,见 v0.26.0)、**初始化**(seed 状态与重新初始化)。配置写入 `$:/plugins/dsh-tiddlywiki/config` tiddler,覆盖 cordis `config:` 块(tiddler 优先)。
|
|
156
158
|
|
|
157
159
|
### 📍 知识库位置(可切换,v0.22.0)
|
|
158
160
|
|
|
@@ -414,6 +416,8 @@ lib/ # 预构建产物(发布含 lib/**,提交入库;
|
|
|
414
416
|
|
|
415
417
|
> 最近几个主要版本的一句话记录(完整变更见 [Releases](https://github.com/bbqisbbq/dsh-tiddlywiki/releases) / git log)。
|
|
416
418
|
|
|
419
|
+
- **v0.26.1**(2026-09-22):**修 P0:设置页插件/主题/语言的勾选框全部显示为「未勾选」,且误点「应用」会把启动清单整个清空**。回归由 v0.25.0 的 `CatalogPending` 引入(commit 883196f):契约是「`undefined` = 用户还没碰过、完全跟随服务器」,而挂载时把它初始化成了 `{ plugins: new Set(), themes: new Set(), languages: new Set() }`——`desiredPlugins()` 用 `??` 回退,而**空 Set 不是 `undefined`**,于是「没碰过」被读成「用户期望集合为空」:① 所有勾选框全空(用户看到「TW 里明明装着的插件,这里一个都没勾」——本次由作者实测报障);② 更严重的是,什么都没改就点「应用插件」会 POST 一个**空数组**,把 `tiddlywiki.info` 的 `plugins` 清单**整个清空**(主题清单、语言同理:一次误点等于卸载 codemirror/menubar/全部主题/中文语言,TW 重启后只剩内核 + 自动补装的 markdown)。**修复**:初始化改为空对象 `{}`;并给三个「应用」按钮加闸门——`pending.* === undefined`(未碰过)时按钮禁用、点击直接返回,只有真正改过勾选才允许提交(「用户刻意取消全部勾选」仍是合法提交,host 的 no-op 守卫照旧兜底)。顺带把「wiki 内已装」徽标的判据从 `desiredPlugins()` 改为服务器集合 `serverPlugins`——徽标描述当前事实,不该随未应用的勾选抖动。守门:`scripts/verify-plugin-runtime.mjs` 新增 4 条(空对象初始化 + 三个闸门 + 三个 sync 函数必须在 change 回调里重算按钮 + 徽标判据),**已反向验证**(改回空 Set 即红)。
|
|
420
|
+
- **v0.26.0**(2026-09-22):**修「设置页插件管理与 TW 实际状态对不上」——两套真相源不再互相说谎**。现象(作者本机实测):TW 控制面板里明明启用着的插件(TiddlyFlex、mermaid、pinboard 等经 TW 原生插件库装的),设置页勾选清单里**根本不出现**;反过来 menubar / comments / help 在 tiddlywiki.info 里(勾选着),但用户早在 TW 面板里禁用了它们。根因:**设置页只读 `tiddlywiki.info` 的 `plugins` 数组(启动安装清单),而 TW 运行时集合 = tiddlywiki.info ∪ 以 tiddler 形式装进 wiki 的插件 − `$:/config/Plugins/Disabled/<title>`(正文为 `yes`,TW 核心自己的判定)**——两个面板读的不是同一份真相,谁也没错但谁也不全。**修复(方向「修真」)**:勾选语义保持「是否写入 tiddlywiki.info」不变(它只管引擎自带插件的离线启停,这是 TW 原生界面做不到的唯一能力),但把缺失的另一半真相显示出来——① 每个插件行加状态徽标:「TW 内已禁用」(勾选着但被 TW 禁用)/「wiki 内已装」(不在启动清单但 wiki 里有同名 tiddler 版本,TW 运行时已在用);② 新增只读小节「**wiki 内插件(经 TW 原生安装)**」,列出第三方插件 tiddler(名称/版本/描述/禁用状态),启停/卸载引导去 TW 控制面板;③ 区块说明文字写明两边分工。**实现**:`GET /admin/state` 新增 `runtimePlugins` 字段,由 `scanWikiRuntimePlugins()`(`src/host/admin.ts`)扫 tiddlers 目录得出——插件库安装的插件是 `$__plugins_*.json` + `.json.meta`(只读几十字节的 meta,不碰几 MB 的插件 payload;无 meta 的裸 `.json` 还可能是拖拽导入的**多 tiddler 数组**,一并解析);禁用标记 `$__config_Plugins_Disabled_*.tid` 按文件自身 `title:` 头还原插件标题(不做有损的文件名反解码)。扫描失败返回 `null`(读失败 ≠ 不存在),客户端隐藏徽标与小节而不是显示空的假象;`dsh/*`、`dsh-tiddlywiki/*` 命名空间(插件自己的种子 bundle)排除在外。产品哲学不变:插件对 wiki 的强制定制依旧只有 `--init server` 基线 + 自动补装 markdown + 3 个核心种子,官方自带插件之外的安装/管理全部留给 TW 原生界面。
|
|
417
421
|
- **v0.25.1**(2026-09-21):**修「回复流里的 wiki 链接点了打不开」**——根因在 **DSH web 的 markdown 渲染器**把 `[标题](/dsh-tiddlywiki/tw/#标题)` 这类相对链接**解析成绝对 http(s) URL**,再按 protocol 判为「外链」(`target="_blank"` + 自己的 `openExternalLink` onClick)。旧 `installWikiLinkInterceptor` 只匹配相对路径正则 `^/dsh-tiddlywiki/tw/#(.+)$`,绝对 href 匹配不上 → 拦截器不接管 → 点击落入 DSH 外链逻辑,在新标签页打开 TW 编辑器而不是中央面板(用户感知「打不开」)。**修复**:拦截器改用 `new URL(href, window.location.origin)` 归一化后判**同源 + `pathname === TW_PROXY_BASE` + hash 非空**(相对/绝对两种形态都接),在 capture 阶段 `preventDefault + stopPropagation` 抢在 DSH 外链 onClick 之前打开中央 TW 面板;`scripts/verify-tool-views.mjs` 新增源码级断言(必须用 `new URL` + 同源校验 + 代理 pathname,且旧相对正则已移除)。headless Chrome 实测:TW 面板 hash 导航(含 TiddlyFlex 的 `$:/StoryList-1`)本身正常,问题确在拦截器漏匹配绝对 href。
|
|
418
422
|
- **v0.25.0**(2026-09-20):**第五轮代码审计的修复版**(审计那天被用户判定为「本地单机可接受、不修」的只有一条:`POST /render` 的 **title 分支**可以经被渲染条目正文里的 `{{…}}` 转写把插件配置读出来——本机实测复现,`text` 分支早已 403;不修的理由是单机自用、且真正的边界本就不在这条路由(任何笔记正文的 `{{$:/plugins/dsh-tiddlywiki/config}}` 在嵌入式 TW 面板里同样会被转写渲染)。其余全部修掉,按性质分四组。**① 工具契约(数据安全)**:(a) **`tags: []` 现在真的清空标签**——此前 `normalizeTagArg()` 把空数组归一成 `undefined`(=「未传 = 保留原标签」),而 `tags` 又是 `RESERVED_TIDDLER_FIELDS` 成员(`fields.tags` 会被拒),于是**没有任何途径**把一篇笔记的标签改成空:模型照 `tiddlywiki_lint` 的 junk-tags 建议去清标签、传 `[]`,什么都不会发生,回执还把旧标签原样列出来(本机实测)。(b) `tiddlywiki_append` / `tiddlywiki_rename` / `tiddlywiki_batch_put`(逐条目 `expectedModified`)补上**乐观并发令牌**:这三条与 put/delete/attach 一样是「读全文 → 整篇回写」(append 读旧基底再加一段),而可传令牌的只有后三条 —— 注入提示词让模型回传 `expectedModified`,append 却**没有这个参数**,人类在 TW 编辑器里的并发修改会被旧基底静默回滚。(c) `tiddlywiki_rename` 更新引用时**逐个重读引用者**(列表只是快照),遍历期间被改动过的跳过并在回执里报出数量——跳过一条链接可以手工补,被覆盖掉的一段正文不可恢复。(d) `tiddlywiki_attach` 的 `noteTitle` 指向二进制附件时明确拒绝(会把 base64 写坏而回执看着正常)。(e) `tiddlywiki_lint` 的 `checks` **拼错不再谎报干净**:本机实测 `checks:["broken-link"]`(少了 s)在 2933 条笔记的知识库上**一个检查都没跑**,回执却是「扫描 2933 条文本笔记,发现 0 个问题 / 没有发现问题」;现在回执列出**实际运行**的检查、显式报出无法识别的名字,且「一个检查都没跑」不再等于「没问题」。(f) `valid-until` / `review-after` 的纯日期(`YYYY-MM-DD`)改按**当地日终**判过期,此前按 UTC 0 点算,东八区当天 08:00 就报「已过期」。**② 设置项(都是「保存了却不变」)**:`git.autoCommit`/`git.debounceMs`/`git.remote` 保存后**对运行中的插件立即生效**(`AutoCommitter` 在构造时把 enabled/debounceMs 快照进不可变 options,remote 只在启动 bootstrap 时应用——此前关掉自动提交仍会 commit、换远端仍推老地址,直到重启 dsh web);「重启 TW」按钮的 15s 超时改为 120s(`/admin/restart` 要等 TW 就绪,软窗口默认 60s、大知识库自述 44s+,此前会弹「重启失败」而宿主其实成功);数值项(`bridge.port` / `startup.readyTimeoutMs` / `ui.allArticles.pageSize` / `git.debounceMs`)加 `min`/`max` 与字段旁红字,非法值**拒绝保存**(宿主对越界值是静默夹取/回落,提交只会继续制造「回显 ≠ 生效」);**空补丁不再发请求**、宿主在序列化文本逐字节相同时**跳过 PUT**(每次空点「保存配置」都会重写配置 tiddler 并刷新 `.meta` 的 `modified` → 白造 git diff、两台机器各点一次就冲突在 `.meta` 上,与 v0.24.2 刚修的 `$__language.txt.meta` 同类);保存被拒(409,如配置 tiddler 残留冲突标记)时错误**持久显示**在配置区而不是一闪而过的 toast;微信发布的 `command`/`adapter`/`token`/`dsn` 补齐 UI(token 密码型 + 👁 显隐切换,掩码回填语义照旧);插件/主题/语言的未应用勾选提升为页面级状态,能活过「同步 / 重启」触发的重刷;表单有未保存内容时若配置在**别处**被改过,保留你的输入并显式提示,而不是重建表单把输入清掉;首屏加「加载中…」、加载失败不再清空已填表单。宿主侧 `/admin/config` 的补丁现在必须过新的 `normalizeConfigPatch()`:**已知数值夹取到真实边界、已知布尔/字符串类型错误返回 400 并点名字段、`__proto__`/`constructor`/`prototype` 一律拒绝、未知键透传**(此前任意 JSON 都会落进配置 tiddler——数组体变成 `{"0":1,"1":2}` 这种永久垃圾键,`git.debounceMs: "abc"` 会经 `??` 进 `setTimeout` 被当 0 → 每次写入都 commit)。**③ 会话「知识库」Tab**:`NOTE_TOOL_NAMES` 补 `append`(「增量写入」)/`attach`(「附件」)/`delete`/`trash`(`restore` 计产生),新增「🗑 删除 / 回收站」一节——此前**用 append 写的笔记在这个 Tab 里完全不可见**,而注入提示词恰好推荐「纯增量内容优先用 append」,于是一整轮工作会被显示成「本会话暂时没有产生、读取或检索过任何知识库笔记」;事件日志读不出来的会话**在页面上明说**(不再静默吞掉、把「读不出来」显示成「没碰过笔记」),`counts.sessions` 只统计成功读取的会话;读日志从串行改 **4 路有界并发**、探测整段加 **12s 墙钟预算**(最坏 ~375s → 有界,客户端 25s abort 之后不再继续压着 TW 打),生成加 **3s 单飞复用**;客户端自愈改为**三态探测**(在 / 被清 / 状态未知——未知既不计失败也不清零,此前 `catch { return true }` 把「探测失败」当「一切正常」),自愈 interval 与 `phase` 解耦(进 error 态后不再永久失去重试),并新增**按 3 分钟节拍的静默刷新**(tab 长期开着也能跟上会话,不闪 loading 不砸内容);`escapeInline()` 中和 `{}`(会话日志里出现的 `{{…}}` 会被 `/render` 转写展开)并改为**先截断再转义**(否则 `&` 会被截成 `&am`);`scanRefs` 不再把标题里的 `)`/`]` 一律截断;`TitleState.snippet` 从死字段改为真的渲染。**④ 其余**:检索卡片的范围与工具结果对齐(卡片把模型可见回执里读到的 `workspace` 回传 `/search`,v0.24.0 只改了工具侧——卡片会列出**更多**的结果且不显示范围);`/status` 的 git 冲突内容扫描加上限(200 个文件 / 2MB,批量导入期间不再每 30s 同步读几千个文件阻塞事件循环);`tiddlywiki_delete` 的描述写明 `$:/` 系统条目实为永久删除;`TiddlywikiConfig.note` 补上漏掉的 `workspaceMark`(三处形状 lockstep);`tiddlywiki_attach` 有专属卡片。**守门**:新增 `scripts/verify-config-patch.mjs`(6 组纯函数断言:非对象拒绝 / 越界夹取 / 类型错误点名字段 / 危险键拒绝 / 未知键透传)与 `scripts/verify-tool-views.mjs`(5 条源码级断言:`TOOL_VIEW_KEYS` 与 host 工具名完全一致 / `TOOL_LABELS` 全覆盖 / 工作区前缀两处一致 / 搜索卡片仍回传 `workspace`),两者进 `verify:unit`;`verify-tools.mjs` +4 条真实 TW 断言(`tags:[]` 清空且「不传」仍保留 / append 令牌不匹配拒绝、匹配放行 / batch 逐条失败 / lint 未知检查名不得谎报);`verify-write-policy.mjs` +1 条(空数组 vs 不传的语义边界)。**已反向验证的路径**:把 `normalizeTagArg` 还原成「空数组 → undefined」、把 lint 的 `checks` 还原成自由集合、把 append 的令牌检查删掉,对应断言分别变红。
|
|
419
423
|
- **v0.24.2**(2026-09-20):**修「`$:/language` 每次 dsh web 启动都被无条件重写 → 两台机器每次 pull 都冲突」**。现象:`git pull` 稳定卡在 `tiddlers/$__language.txt.meta` 上,而**正文文件根本不冲突、diff 出来只有 `created`/`modified` 两行**;本仓库的 git 历史里甚至已有一条专门清理该冲突残留的提交,而 2026-09-20 一次会话内又撞了两次。根因定位到行:启动自举应用 `uiLanguage` 时、以及设置页改动语言时,两处都**无条件 PUT** `$:/language`(正文每次都一样,但 TW 会给 `.meta` 盖新时间戳)——两台机器各自启动过就都改了同几行。现在抽出 `pinLanguageTiddler()`(`src/host/admin.ts`):**先读,内容一致就不写**,内容不同/条目不存在才写。⚠️ 关键边界:**读失败绝不等于「内容相同」**(铁律第三条)——`client.get()` 只有 404 返回 undefined、其余抛错,抛错时**照写并记一条日志**,否则用户的语言会永久卡在错的值上且重启也修不回来。**守门**:新增 `scripts/verify-language-pin.mjs`(进 `verify:unit`)——4 条桩 client 行为断言(一致不写 / 不存在要写 / 不同要写 / 读失败照写并报警)+ 源码级断言(`index.ts` 与 `admin.ts` 除原语自身外**一处裸 PUT 都不许有**,且两处都必须经 `pinLanguageTiddler`)。**已反向验证**:还原启动路径为无条件 PUT → 红 1 条;把读失败分支改成「直接 return false」→ 红 1 条。(附带一条方法论教训:第一次做这条反向验证时**构建因我的改写而失败**,而我把构建输出丢进了 `$null`,于是守门测的是旧 `lib/`、假绿了一轮;重做时把构建输出放回台面才抓到真红。反向验证必须确认「改动真的被编译进去了」。)
|