Oncin 图库存储调查(2026-09-28)
结论与范围
反馈真实。电脑、手机和分享回放中的图片不一致,由目前的存储实现直接导致。 useYumina().storage 是按世界隔离的浏览器 localStorage,并非服务器会话存储。 这是图库使用了不适合的持久化方式,以及平台缺少明确约束的问题,不是一次云同步请求失败。
本次在本地修复了同一保存路径里的数据丢失缺陷:空间不足时不再删除其他存储项,失败写入会抛错,让 SDK Promise 拒绝。 这项补丁没有实现跨设备图库,也没有迁移用户已有图片。 未提交、未部署,未修改生产数据或作者卡片。
来源
- 原帖:custom ui image storage bug
- 作者:
@oncin;数据库记录时间:2026-09-29 00:41:38 UTC(洛杉矶 9 月 28 日 17:41)。 - 只读查询了用户提供的数据库中的
threads、posts、worlds。帖子查询时没有回复。 - 最初检查的开发库 Discord 记录仅到 2026-09-22 05:26 UTC,不能代表最新社区反馈;最终结论基于站内原帖与实际卡片代码。
- 核对的卡片:
27483dff-e14f-49ec-864c-37bd85d7d9c4,Oncin : Xianxia Cultivation Simulator。 - 没有读取私人聊天内容、下载玩家图片或打开浏览器。没有保存数据库凭据。
根因证据
- 卡片
rootComponent.files.index.tsx:- 第 9069–9072 行:图库键由固定前缀加
sessionId构成;没有 ID 则返回 null。 - 第 9245 行:图库从
api.storage.get(storageKey)加载。 - 第 9273–9275 行:调用
storage.set后,只要 Promise 完成就标记已保存;仅 catch 分支提示保存失败。 - 第 10242 行:通过
FileReader.readAsDataURL(file)读取上传图片。 - 这些行号是查询时数据库里的卡片源代码位置,不是仓库文件位置。
- 第 9069–9072 行:图库键由固定前缀加
packages/app/src/features/chat/world-renderer.tsx的storage.*分支直接读写 localStorage,没有网络保存请求。packages/server/src/routes/shared-playthroughs.ts分享时保存消息、session.state、摘要、会话记忆,未保存浏览器 localStorage。packages/app/src/features/chat/playthrough-replay-view.tsx用空sessionId挂载回放。 因此即使作者在原设备观看回放,卡片也不会取到原来的图库键。
给本地键拼接 sessionId 只能区分本地会话,不能使图片跨设备同步。 仅把回放 sessionId 改成源会话 ID 也不能修复:其他设备仍没有图片,而且会把只读快照错误地绑回可变的本地数据。
本次实现
- 新增
packages/app/src/features/chat/world-storage.ts,保留原来的世界命名空间规则。 WorldRenderer写入走该模块;删除原先按大小清除 25%yumina:local:*键的重试逻辑。 那个清除逻辑跨越世界、会话边界,甚至在新数据最终仍写不下时也已删除其他数据。- 不再返回
{ error: "quota_exceeded" }作为正常完成的返回值。 保留浏览器原始异常,由现有 SandboxBridge 转成 SDK Promise rejection。 - 更新生成卡片所用的 TSX 指引,明确本地 storage 不同步、不进入分享快照,禁止把 base64 图库当作本地/变量存档。
- 没有修改界面布局、上传控件或已发布卡片。
完整修复路径(尚未接入)
推荐图片字节进入素材服务,图库索引进入会话状态,保持 api.storage 的本地语义,避免将偏好/缓存意外公开。
- 为卡片声明一个 JSON 变量,例如 ID
gallery-data,存储图库项目、@asset:id或稳定 CDN URL、人物/地点映射等小型元数据;不要存 base64。 - 从平台素材选择器上传/选取图片,再把引用写入该变量,等待服务器确认后显示保存成功。 当前工作区的
pickChatImage()已返回assetId和url,patchVariables()可确认状态持久化。 两者包含已有未提交工作,必须确认目标发布版本支持后再让卡片调用,不能宣称线上已经支持。 - 普通会话及只读回放统一从
api.variables加载图库,不依赖 sessionId 创建本地键。 尊重api.readOnly。现有分享finalState可携带引用,但旧分享快照不会自动更新,需要重新分享。 - 旧图片只能在保存它们的原浏览器上迁移:读取本地图库、上传图片、合并云端索引,确认全部保存成功后再考虑清理本地副本。 电脑和手机分别有图片时必须逐项合并,不能用整库覆盖。迁移需幂等、可重试;不可在普通读取时偷偷上传所有本地缓存。
- 若要提供真正属于 playthrough 的通用素材 API,还需会话归属校验、容量限制、服务器端并发合并/版本检查、分享引用保留和删除生命周期。 当前普通用户素材的 CDN URL 可访问,不等价于私有会话素材;已分享的图片不能因原会话或用户素材被清理而失效。
- 接入作者卡片的上传、迁移和分享流程前,按用户的 UI 工作流提供具体预览并取得批准。
验收:PC 上传→手机重开可见;手机新增→PC 重开两张都在;不同会话隔离;不同账号无权修改;分享在其他账号可读且只读;新分享包含图库;旧快照不被后续编辑修改;上传/保存失败不假报成功;双设备迁移无丢失/重复;源素材删除不破坏已分享快照。
验证
- 新增 6 项存储测试;从原实现提取的旧逻辑运行时 3 失败、3 通过,确认误删、假成功、错误吞掉均可复现。
- 修复后存储与现有 SandboxBridge 测试 12/12 通过。
- App TypeScript 检查通过;Vite 生产构建通过(已有大 chunk 警告)。
- App 全量测试第一次:700 项,693 通过,7 失败。 其中 3 个编辑器测试文件因沙箱禁止读取构建依赖失败,获准重跑后其中 9 项全部通过。 仍有 4 个断言失败,来自未由本次修改的移动端 Library/导航布局:
src/lib/library-detail-mobile-boundary.test.ts(1 项)、src/lib/mobile-nav-drawer-layout.test.ts(3 项)。 它们读取界面源文件/CSS,不依赖本次存储模块。不能宣称全量测试全绿。 - 日志:
artifacts/oncin-app-tests.log、artifacts/oncin-editor-tests.log、artifacts/oncin-app-build.log。 - 环境里的 pnpm 自动尝试重新安装依赖,因非交互模式中止;改用已经安装的 tsx/TypeScript/Vite 执行验证,没有重装依赖。
状态:DONE_WITH_CONCERNS。调查与本地数据丢失修复完成;跨设备/分享图库需要上述接入和迁移,仍未实现。
