完美像素请求账本移出项目布局
账本记的是「本机发出过哪一次 POST」,属于对账凭据而非画布内容。它此前写在 用户的画布布局里,为此派生出一条严格布局保存通道:发 POST 前必须拿到布局 保存的 revision ack。任何布局校验失败因此都会升级成完美像素的硬阻断。 改为存在本机 localStorage,按 owner + project 双键隔离;布局里只留 perfectPixelOperationId 标记,用来把这类占位与队列型占位区分开。本机写入是 同步的、不过网络、不受服务端校验影响,因此它能提供严格保存想提供的那个保证 ——请求可被追溯——却不引入阻断点。严格保存通道整体删除,只保留一个不改变 失败语义的 preferLatestGenerationDialogs。 由此新出现的「账本有、占位没写进布局」窗口,由恢复 effect 覆盖:它同时遍历 内存占位与孤儿账本条目,对后者照常 GET 对账,终态给出 asset-only 提示并清 账本。 本机账本是明确设计,缺失只降级、不得构成阻断:换设备、清缓存、隐私模式、 配额写满都会读不到账本,此时带标记的占位一律收口成可删除的失败占位,用户 删掉重来即可。跨设备不再自动收口是已知且接受的代价。 布局内联账本作为 legacy 形状继续被读取,滚动部署期间的在途操作不会被一次性 判死;写入侧不再产生新的内联账本。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -6381,3 +6381,14 @@
|
||||
- 影响范围:`src/components/image-editor/useCanvasGenerationDialogs.ts`(删除 `isUnsettledPerfectPixelOperationDialog`,新增 `requiresGenerationDeleteConfirmation`)、`ImageCanvasEditorView.tsx` 的 `requestRemoveCanvasGenerationDialog`、`useImageCanvasLayerCommands.ts` 的 `deleteSelectedLayer`、`useImageCanvasGenerationWorkflow.ts` 的恢复失效分支。不修改服务端、契约或数据。
|
||||
- 验证方式:覆盖三种状态下按 id 删除与随源图层删除均真正移除、完美像素占位任何状态都不要求确认而普通 `generating` 占位仍要求、快捷键与混合选择删除会写入历史并触发副作用、在途删除后已知失败退回全局提示、删除后 applied verdict 走 asset-only 提示且不回填画布、标记失效时快照被丢弃。运行 `npx vitest run src/components/image-editor`、`npx vitest run src/components/platform-entry`、`npm run typecheck`、`npm run check:encoding`。
|
||||
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
|
||||
|
||||
## 2026-08-05 完美像素请求账本移出项目布局,严格布局保存整体删除
|
||||
|
||||
- 背景:完美像素是唯一没有 durable job 的生成路径——免费、同步、不走 `enqueue_editor_generation_job`,服务端没有任何一行记录「这次请求发出过」。为了让刷新后还能 GET-only 对账,请求账本 `perfectPixelOperation` 被写进了**用户的画布布局**,并由此派生出一条严格布局保存通道:发 POST 前必须拿到布局保存的 revision ack,否则整条链路中止。该耦合直接造成两类缺陷:一是账本寄生在用户数据上,占位一度被禁止删除(已由同日「完美像素占位恢复为可删除」作废);二是任何布局校验失败都会升级成完美像素的硬阻断,「画布图层元数据以资源行为准」那条缺陷正是因为严格保存才从静默重试变成用户可见的死锁。
|
||||
- 决策:账本改由 `src/components/image-editor/perfectPixelOperationStore.ts` 存在本机 localStorage,按 owner + project 双键隔离;布局里只留 `perfectPixelOperationId` 标记,用来把这类占位与队列型占位区分开。发 POST 前先同步写本机账本,再**尽力而为**地保存布局;布局保存失败不再拦 POST,只是把结果降级为「只进素材库」。严格布局保存通道(`strictCompletion` 全套机制、`flushProjectPersistence` 的 `requireSuccess` / `requiredDialogId` / `deadlineAt` 选项、`PERFECT_PIXEL_STRICT_LAYOUT_SAVE_BUDGET_MS`)整体删除,只保留一个不改变失败语义的 `preferLatestGenerationDialogs`,用于取到刚创建、尚未回流到 ref 的占位。
|
||||
- 为什么这样仍然安全:本机写入是同步的、不过网络、不受服务端校验影响,因此它能提供严格布局保存想提供的那个保证——「请求可被追溯」——却不引入任何阻断点。由此新出现的「账本有、占位没写进布局」窗口,由恢复 effect 覆盖:它同时遍历内存占位与孤儿账本条目,对后者照常 GET 对账,终态给出 asset-only 提示并清账本。
|
||||
- **本机账本是明确设计,缺失只降级、不得构成阻断**:换设备、换浏览器、清缓存、隐私模式、配额写满都会读不到账本。这些情况下带标记的占位一律 hydrate 成 `failed + perfectPixelOperationInvalid` 的**可删除**占位,用户删掉重来即可。任何代码路径都不得因为「读不到账本」而拒绝用户发起、重试或删除,也不得把占位停在无从收口的处理中态。跨设备不再自动收口是本决策已知且接受的代价:完美像素免费且同步,重做成本极低,用它换掉「用户数据里混着系统对账状态」的耦合是划算的。后续复查发现该行为时,应对照本条确认为设计,不得判定为回归。
|
||||
- 兼容与生命周期:布局内联账本是 legacy 形状,hydrate 侧继续认,滚动部署期间的在途操作不会被一次性判死;写入侧不再产生新的内联账本。本机账本按 7 天保留期与 32 条上限裁剪,终态(applied / dialog-missing / 快照与项目不匹配 / 无占位可挂错误)立即清除。读取沿用与布局快照相同的 v1 白名单校验,任何字段漂移失败关闭,绝不据一份可疑账本重放 POST。
|
||||
- 影响范围:新增 `perfectPixelOperationStore.ts`;`ImageCanvasEditorTypes.ts` 新增 `perfectPixelOperationId`;`ImageCanvasEditorModel.ts` 的 `serializeDialogReferences` / `hydrateCanvasGenerationDialog` / `splitCanvasLayoutItems` / `dropDeadInlineGenerationPlaceholders`;`useImageCanvasProjectPersistence.ts` 删除严格保存机制并在 hydrate 时读账本;`useImageCanvasGenerationWorkflow.ts` 的提交、重试与恢复 effect;`useImageCanvasGenerationSurface.tsx` 的 props 类型。不修改服务端、SpacetimeDB schema 或对外契约——服务端从来不认识这个字段。
|
||||
- 验证方式:账本单测覆盖往返、owner / project 隔离、跨账号整条丢弃、被篡改条目失败关闭、保留期与条数裁剪、终态清除、以及存储不可用时静默降级;模型层覆盖「布局只留标记且不含源图地址」「标记在而账本缺失时收口为可删除失败态」「账本 id 与占位不符时失败关闭」;工作流覆盖「布局保存失败仍照发 POST 并保留可重试的 operation」与「孤儿账本条目照常对账并在终态清账本」;持久化层覆盖「布局保存 400 / 403 与缺 authority 时 flush 均不抛、下游照常执行」。运行 `npx vitest run src/components/image-editor`、`npm run typecheck`、`npm run lint:eslint`、`npm run check:encoding`。
|
||||
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
|
||||
|
||||
Reference in New Issue
Block a user