[Bug] 从 0.1.7-rc.2 回退到 0.1.5-rc.2 后左侧会话列表全空:两版共用 persist key dsh.workspace.view.v5,0.1.7 删掉了 sessionUpdatedAtByAccount / Empty session list after downgrading 0.1.7-rc.2 → 0.1.5-rc.2 (shared persist key, field deleted)
#7983
liualiez-os
started this conversation in
General
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Summary
在同一个 DSH Web 服务(
http://127.0.0.1:3080)上先后跑过 0.1.7-rc.2 和 0.1.5-rc.2,回到 0.1.5 后:左侧会话栏还在,但会话列表是空的(没有任何会话条目),F12 Console 抛出TypeError: Cannot convert undefined or null to object,被错误边界捕获为slot entry crashed in 'sidebar.workspaces'。根因是两个版本共用同一个持久化键
dsh.workspace.view.v5,却在这个键下使用了不同的字段集:0.1.7 在retainAccountKeys里delete d.sessionUpdatedAtByAccount,而 0.1.5 的retainAccountKeys无条件对d.sessionUpdatedAtByAccount调Object.entries()。回退后 hydration 用水合数据整体替换了 init 默认值 → 该字段为undefined→ 抛错 → 整个列表槽位渲染为空。会话数据本身完好无损,用户看到的是「列表空了」,很容易误判成会话丢失。English TL;DR — 0.1.7-rc.2 and 0.1.5-rc.2 share the persisted store key
dsh.workspace.view.v5but not its schema: 0.1.7deletessessionUpdatedAtByAccount, while 0.1.5 unconditionally callsObject.entries()on it. Downgrading in the same browser origin hydrates the 0.1.7 payload over 0.1.5's defaults, the store action throws insideproduce, and the error boundary blanks the wholesidebar.workspacesslot. No session data is lost. Reproducible without any third-party launcher: rundsh web0.1.7, then 0.1.5, on the same port.Environment
C:\Program Files\nodejs\node.exe)http://127.0.0.1:3080127.0.0.1:3080→ one browser localStorage bucket@deepseek-ai/dsh-client-ui-workspace本机的具体形态是「用启动器装了两个整合包,各带一份 DSH 本体」:
homePath(DSH_HOME,互相隔离)pack-test(官方默认整合包 0.1.5-rc.2.1)…\dsh-packs\pack-testpack-test-2(官方默认整合包 0.1.7-rc.2.1)…\dsh-packs\pack-test-2两者的
webPort都是 3080。本 issue 不依赖这个启动器——下面给出去掉启动器的最小复现路径。Reproduction
A. 最小复现(不依赖任何启动器/整合包)
dsh web),打开页面并正常操作一次(切换工作区、点开一个会话),让 store 持久化写入localStorage['dsh.workspace.view.v5']。B. 我实际遇到的路径(两个整合包,服务端环境互相隔离)
sessionUpdatedAtByAccount的 v5)。retainAccountKeys只delete、不读那个字段),但它把archivedFilter: "default"写进同一个键。一行自证(在 0.1.5 的页面 Console)
如果把 0.1.7 与 0.1.5 两个页面的
location.origin和上面那份 JSON 并排截一张图,就是现场证据。Expected / Actual
storages/sessions里的数据是完整的)。Console output (0.1.5-rc.2, verbatim)
Root cause
packages/client/ui-workspace/src/client/(发布产物lib/client.js)。0.1.5-rc.2
调用点在 workspacePhase 变 ready 后的 effect 里,所以每次挂载都会走一遍:
0.1.7-rc.2(同一个包里)
两个辅助细节
sessionUpdatedAtByAccount),而 216/217 两行正常通过 → 说明 hydration 是整体替换(不是与 init 默认值浅合并),并且持久化载荷里恰好只缺这一个字段,与 0.1.7 的 payload 形状完全吻合。delete、不读该字段);只有 0.1.7 → 0.1.5 会崩。也就是说这个键被写成新 schema 之后,旧版本就再也起不来了。隔离边界(与第三方启动器/多整合包的关系)
本机用的是一个社区启动器(DSH Launcher 0.1.3 portable),它把 DSH 本体和插件路径都搬到了 per-pack 目录下,并且声称各整合包环境隔离。实测的隔离边界是:
…\dsh-packs\pack-testvs…\dsh-packs\pack-test-2profiles\web → profiles\pack-test)homePath内dsh-runtime\versions\0.1.5-rc.2/0.1.7-rc.2,每次启动指向对应版本http://127.0.0.1:3080+ 同一浏览器 profiledsh.workspace.view.v5存在 localStorage 里,作用域是协议+主机+端口(origin),与 DSH_HOME、插件路径、runtime 目录毫无关系。而启动器的settings.json里只有一个全局"webPort": 3080,packs.json里没有任何 per-pack 端口字段 —— 所以两个 pack 用的是同一个 origin、同一份存储。所谓「环境隔离」只覆盖磁盘侧服务端状态,反而制造了错觉:每个 pack 看起来都是干净独立的环境,用户不会怀疑浏览器里那份共享状态。这解释了 bug 为什么是单向的,也说明复现只需要两个条件:
影响面交叉验证(已做全量 sweep)
枚举两个版本
.pnpm下所有@deepseek-ai/dsh-client-*\lib\client.js,提取persist:字符串与delete d.X:dsh-client-ui-workspacedsh.workspace.view.v5dsh-client-ui-workspacedsh.workspace.view.v5delete d.sessionUpdatedAtByAccountdsh-client-ui-sidebar-browserdsh.sidebar-browser.v1(新包新键)即:两版唯一共用的持久化键就是
dsh.workspace.view.v5;0.1.7 全部客户端包里唯一的字段删除就是delete d.sessionUpdatedAtByAccount。该冲突在客户端包里只有这一处,不是范围性问题(其余包的persist:没有字符串键)。Suggested fix
dsh.workspace.view.v6并附带一次性迁移,把旧 v5 载荷归一化成新形状;不要用delete表达跨版本字段下线。Object.entries(x ?? {})兜底——这样任何来源的旧/新载荷都不会把 render 打崩。Workaround(用户侧,二选一)
localStorage.removeItem('dsh.workspace.view.v5')(只重置分组/排序偏好,不影响会话数据)。?? EMPTY_RECORD/if (d.x === void 0) d.x = {}的守卫补丁验证通过:列表恢复,且会把缺字段补{}回写持久化数据,自愈);需要的话我可以把它整理成幂等、锚点不匹配即拒绝的补丁脚本再附链接。Additional context
webPort。All reactions