From 62f5168dfe32ce8cfd7967a3997d910c8e2389c3 Mon Sep 17 00:00:00 2001 From: wangheng Date: Mon, 2 Nov 2026 23:16:49 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20PATCH.md=20=E8=A1=A5=E4=B8=A4=E6=9D=A1?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=AE=89=E5=85=A8=E7=BA=A6=E6=9D=9F=E3=80=81?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E5=8E=86=E5=8F=B2=E4=B8=8E=E5=8D=87=E7=BA=A7?= =?UTF-8?q?=E9=A1=BA=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PATCH.md | 56 ++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/PATCH.md b/PATCH.md index 50ba92c..33a435f 100644 --- a/PATCH.md +++ b/PATCH.md @@ -1,6 +1,6 @@ # 本地补丁:工作台状态镜像到宿主侧 -基于上游 `Aisland-SJL/dsh-worktable` **v0.5.1**,本地版本号改成 `0.5.2-local.1`。 +基于上游 `Aisland-SJL/dsh-worktable` **v0.5.1**,本地版本号 `0.5.2-local.3`。 ## 为什么打这个补丁 @@ -13,35 +13,71 @@ ## 改了什么 1. `01_content/src/index.ts`:新增路由 `GET/POST /api/worktable/state` - - 文件位置:`/worktable-state.json`(走)`resolveDshHomeSafe()`) - - GET 读回,POST 原子写入(先写 `.tmp--` 再 rename) - - 走宿主既有鉴权(`requestRejection`),和 `/api/worktable/file` 等私有路由一致 -2. `01_content/src/client/hostState.ts`(新增):宿主镜像的读/写与 400ms 防抖合并 + - 文件位置:`/worktable-state.json`(走 `resolveDshHomeSafe()`) + - GET 读回;POST **先读旧文件、按 key 合并、再原子写入**(`.tmp--` → rename) + - 走宿主既有鉴权(`requestRejection`),与 `/api/worktable/file` 等私有路由一致 +2. `01_content/src/client/hostState.ts`(新增):宿主镜像的读/写、400ms 防抖合并、退避重试 3. `01_content/src/client/index.tsx`: - `loadView` / `loadProjects` 抽出 `normalizeView` / `normalizeProjects`,本地存档与宿主镜像共用校验 - `persistView` / `persistProjects` 额外写 `dsh.worktable.savedAt.v1` 并把状态排进宿主镜像 - 挂载时拉宿主镜像,只有 `updatedAt > 本地 savedAt` 才回填,避免旧镜像盖掉刚改的内容 - - `markHydrated()` 之前的写入一律不发,防止首屏默认值把镜像冲掉 - `pagehide` / `beforeunload` 立即冲刷 +## 两条数据安全约束(0.5.2-local.3 补的,来自真实事故) + +事故:拖了一下面板(只改了 `view`),客户端 POST 的半截状态把整个文件替换,`projects` 被抹掉; +紧接着重启,客户端读宿主失败又用空状态覆盖了一次。 + +1. **写入按 key 合并**:服务端读旧文件再 `{ ...previous, ...incoming }`,只带 `view` 的写入不会伤到 `projects`。 +2. **读不到就不写**:`loadHostState()` 带退避重试(0/300/700/1200/2000/3000ms)。 + 返回 `{ reachable: false }` 时调用 `staysReadOnly()`,**整场会话都不写镜像** —— + 宁可这次只在本地,也不拿空状态覆盖。 +3. hydration 成功后 `markHydrated({ projects, view })` 把读到的内容留在内存, + 后续半截写入也会带上另一半。 + +改动这三条时注意:`hydrated` 为 false 期间 `queueHostSave` 只累积不发送,这是首屏默认值的闸门,别去掉。 + ## 覆盖范围 -- 覆盖:项目、布局、绑定、文件夹、视图/外观设置 +- 覆盖:项目、布局、绑定、项目文件夹、视图/外观设置 - **不覆盖**:图库媒体(照片)仍在 IndexedDB,换端口照样丢 +## 兼容声明 + +`0.2.0-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.5 || 0.2.2-alpha.2` + +宿主不在这个范围里时,`dsh plugin add` 会直接拒绝并回滚。换宿主版本要同步改 +`package.json` 的 `engines.dsh`**和**`dsh.compatibility.dsh` / `dshReleases`,三处一起改。 + ## 构建 / 安装 ```powershell cd 01_content npm install npm run build # lib/index.js + lib/client.js -npm pack # 产出 dsh-worktable-0.5.2-local.1.tgz(7 文件,无 .map) -dsh plugin --profile web add "file:./dsh-plugins/dsh-worktable-0.5.2-local.1.tgz" +npm pack # 产出 dsh-worktable-.tgz(7 文件,无 .map) +``` + +发到 Gitea Release 后(`xiaoqu/dsh-worktable`): + +```powershell +dsh plugin --profile web add "https://gitea.ququclean.com/xiaoqu/dsh-worktable/releases/download/v/dsh-worktable.tgz" ``` 装完必须**完全退出宿主再重开**。 +**注意**:升级到新版本时,别先删旧 Release —— profile 里还指着旧地址,pnpm 解析依赖会 404。 +正确顺序是:先装新版(profile 依赖换成新地址)→ 再把旧 Release 删掉。 + ## 升级官方版时的注意 -上游出 0.5.2 及以上时,本地补丁会被覆盖,需要把上面的三处改动重新合并到新版本再打包。 +上游出 0.5.2 及以上时,本地补丁会被覆盖,需要把上面的改动重新合并到新版本再打包。 如果上游自己解决了「桌面端随机端口 + localStorage」,这个补丁就可以直接丢掉。 + +## 版本历史 + +| 版本 | 内容 | +| --- | --- | +| 0.5.2-local.1 | 状态镜像首版;只声明兼容到 0.2.1-rc.5 | +| 0.5.2-local.2 | 兼容声明加入 DSH 0.2.2-alpha.2(宿主升级后安装被拒) | +| 0.5.2-local.3 | 修数据安全:写入按 key 合并 + 读不到不写,修复空状态覆盖镜像 |