game agent无限画布开发 #136
Reference in New Issue
Block a user
Delete Branch "codex/infinite-canvas"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
主要工作是共享同一套“画布内核与通用 UI 源码”,网站和 Tauri 只分别实现宿主适配层。
当前 head 的四组 CI 绑定正确且全绿,但仍有 4 个 P1,暂不能合并:
此外,master 已前进到
6c53eda71,当前 merge-tree 在docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md冲突;请融合最新 master、处理以上问题并在新 head 上重跑完整 CI。提交历史中的c24e3010f、b95da3072为英文空正文,0b6be155d为空正文,也请按仓库规范整理。@@ -0,0 +542,4 @@draft.status = AssetCanvasDraftStatus::Generating;}draft.updated_at = now;write_asset_canvas_draft_locked(root, &draft)?;[P1] 生成状态写入必须推进草稿 revision
upsert_public_generation_record会修改draft.generations、draft.status和updatedAt后直接落盘,却没有推进draft.revision。恢复或异步轮询写入新状态后,仍持有同一 revision 的旧 UI 快照可以继续通过 CAS,并把 accepted/running/reconciliation 状态覆盖掉。请让每次公开生成状态落账都严格推进 revision,并同步更新 ledger 后续 staging/commit 的 expected revision;前端也需要消费新 revision 或重新加载草稿。@@ -0,0 +1637,4 @@fn classify_canvas_generation_error(error: &str) -> (bool, &'static str) {if error.contains("authentication-required") {return (false, "authentication-required");[P1] 已受理任务的鉴权失效不能永久终结为 Failed
这里把轮询阶段的 401/403 归为非 reconciliation,后续会写成
Failed,而恢复扫描又跳过所有 Failed ledger。此时远端 operation 可能已经受理且仍在执行;用户重新登录后只能用新的 generation/idempotency/commit 身份重试,可能产生第二个任务和第二次扣费。请保留原 operation 身份,进入可恢复的等待鉴权状态,并允许使用刷新后的登录态继续轮询同一 operation。@@ -0,0 +1063,4 @@return;}const epoch = epochRef.current;void host.project[P1] 返回不能无确认丢弃 dirty 草稿
cancelCanvas对除 saving 外的状态直接调用discardDraft,没有检查 dirty,也没有“保留草稿并退出 / 确认放弃”的独立面板。用户在 180ms 自动保存窗口内点击返回会丢失内存修改;已经持久化的草稿也会被直接标为 cancelled。请按取消合同增加独立确认流程,并在选择保留时先 flush 或保留可恢复草稿。@@ -0,0 +1794,4 @@layer={layer}selected={selected}aria-label={`图层 ${layer.title}`}onPointerDown={(event: ReactPointerEvent<HTMLElement>) => {[P1] 核心画布交互缺少键盘路径
图层虽然使用原生
button,但这里只绑定onPointerDown;Enter/Space 产生的 click 不会选中图层。缩放句柄同样只有 pointer handler,并以role="button"、tabIndex=0嵌套在外层 button 中。请提供键盘选择和可操作的缩放替代入口,避免嵌套交互元素,并补 Enter/Space、焦点与辅助技术回归测试。请求修改。当前 head 已融合最新 master,merge-tree 无冲突,run 953 四项 CI 均绑定当前提交且全绿;旧 review 中的草稿 revision 推进、基础退出确认和键盘可访问性已有针对性修复,但当前仍存在七条会阻断正常创作或造成重复生成/扣费的 P1:任务图片正规化后把 task:* 误作 External 稳定引用;API Key 修正后无法恢复原 operation;非图片资源编辑没有跨重启恢复入口;长任务期间项目 revision 前进会让已付费结果永久无法提交;失败态返回仍会丢失 dirty 内存修改;Shift 单击多选被 pointerdown/click 双重切换抵消;生成遮罩只禁 pointer,键盘仍可修改画布并破坏生命周期。另有旧 generation 快照覆盖权威记录、派生视频无法再次编辑、版本派生缺少崩溃 journal、Tauri 滚轮语义漂移等 P2。请先修复这些恢复、CAS 和交互边界并补对应回归,再重新请求 review。正式门禁还需整理 145 文件的超大范围,以及 c24e3010f、b95da3072 的英文空正文标题和
0b6be155d的空正文。@@ -0,0 +838,4 @@.resource_id.as_deref().map(str::trim).filter(|value| {[P1]
task:*不能直接作为 External 稳定引用任务产物正规化会把
task:<taskId>:<path>写入 manifest 的source.resourceId,这里却只排除local-asset:和draft-media:,随后把task:*原样作为sourceImageSrc。External 服务只解析当前账号的 objectKey、正式项目 resource ID 或素材 ID,因此“已完成任务图片 → 编辑资源 → refine”会在生成前稳定返回 400。请把task:*视为本地身份,走上传、confirm 后的 objectKey,并补该完整路径的端到端回归。@@ -0,0 +1569,4 @@ledger.error_code.as_deref().unwrap_or("generation-failed"),));}let configuration_fingerprint = canvas_api_identity_fingerprint(api_base_url, api_mode);[P1] 修正 API Key 后必须能恢复原 operation
当前配置指纹同时包含 base URL 和 API Key。已受理 operation 在轮询阶段遇到 401/403 后虽然进入
ReconciliationRequired,但用户按提示修正或轮换 Key 时指纹必然变化,这里会在查询原 operation 前改写为configuration-changed。原付费任务因此无法对账,只能新建 identity,仍有重复生成和重复扣费风险。请把凭据轮换与服务/账号身份分开,并验证新凭据仍由服务端 owner 边界授权后继续轮询同一 operation。@@ -0,0 +1695,4 @@None,);}if current_revision.revision != input.expected_project_revision {[P1] 项目 revision 前进后不能永久丢弃已付费结果
前端在远端调用前冻结一次
expectedProjectRevision,同一 operation 的所有重试继续使用旧值。最长 35 分钟的生成期间,只要 Agent 或另一个窗口推进项目 revision,即使媒体已经远端完成、下载并进入MediaDownloaded,这里也会永远报冲突;“使用原请求重试”仍带旧 revision,取消重开则会创建新 operation。请在保持源资源摘要/身份不变的前提下为已完成结果重新取得提交 CAS,或提供正式 reconciliation,而不是逼用户再次生成。@@ -0,0 +1153,4 @@});}, [exitActionPending, onCancel, persistDraft, stableScope.draftId]);const requestCanvasExit = useCallback(() => {[P1] 失败态退出也必须保护未持久化修改
退出确认只在
canvas.editing && dirty时打开。自动保存发生 revision conflict、IPC 或磁盘错误后,persistDraft会把 lifecycle 改为canvas.failed,但当前内存图层仍可能是 dirty;失败卡的“返回资源总览”会直接走到onCancel并卸载页面。请独立追踪未持久化版本,所有非生成中的退出路径都按该事实弹出保留/放弃面板。@@ -0,0 +1935,4 @@className="asset-canvas-surface__layer-hit-target"aria-label={`选择图层 ${layer.title}`}aria-pressed={selected}onClick={(event) => {[P1] Shift 多选不能在 pointerdown 和 click 各切换一次
鼠标操作先在
onPointerDown按 Shift 切换选择,React 刷新后原生click又按新状态执行这里的同一切换:未选图层刚加入就被移除,已选图层刚移除又被加回,因此 Shift 单击无法增删多选。请让 pointer/click 只有一路负责选择,或显式区分键盘 click,并补真实 pointer 序列的多选回归。@@ -0,0 +133,4 @@color: var(--platform-button-primary-text);}.asset-canvas-surface[data-state='canvas.generating'][P1] 生成期间不能只屏蔽 pointer 交互
pointer-events: none不会把底层原生按钮移出键盘焦点顺序。用户仍可 Tab 到图层或缩放按钮,用 Enter/Space/方向键调用markDirty(),把 lifecycle 从canvas.generating改回 editing,生成遮罩随即消失;后续 progress 又会反复覆盖状态。请让生成期画布真正 disabled/inert,并在事件处理器内增加生命周期门禁。@@ -1089,0 +1388,4 @@pendingResourceFocusRef.current = null;setAssetCanvasRoute(null);setAssetCanvasNotice('');setResourceEditorRoute({[P1] 未完成资源编辑必须具备跨重启恢复入口
这里每次进入编辑器都无条件生成新的
operationId/idempotencyKey;.agent/resource-edits中已进入 202/Accepted 的账本只有再次提交同一 ID 才会被读取,但当前没有扫描和恢复入口。视频、音频或图片编辑在应用退出/崩溃后,重开只能发起新任务并再次扣费,原 operation 永远不会继续轮询。请恢复现有未完成账本或明确进入可见的对账流程。请求修改。当前 head
9ff16393e0ccc65f3e14244a86cf8516723c3e37的 run 977 四项 required CI 均成功,但代码复审仍确认 3 组恢复/并发 P1:异步草稿刷新可被旧回包降级;远端终态失败仍留在 pending 且 UI 只允许恢复首项,会永久阻塞后续 operation;manifest 已写而 project revision 尚未推进的崩溃窗会在重启后被误判 committed。对应位置已写 4 条行内意见。另当前 master 已前进到755bf808587a7cb663c458160e1f102a4a676ff8,本分支 merge-base 仍为4c0fdf18065eb631bc8bb012f42c32e0b53c4be6,merge-tree 在 Cargo.lock、工作台 PRD 和 AI 游戏创作技术方案产生冲突,且当前 head 无有效审批。提交历史中 9ff16393e0、08a96e251c、c24e3010f8、b95da30721 的英文标题/空正文以及0b6be155dd的空正文也不符合仓库提交规范。请修复并融合最新 master 后重新请求审查。@@ -0,0 +1500,4 @@return Ok(result);}Some("failed") => {return Err("资源编辑生成失败".to_string());[P1] 远端明确返回
failed已是终态,但这里只返回普通错误,没有把账本推进为可识别的 terminal failed/skip 状态。该账本随后仍会被list_pending_local_project_resource_edits列出;请持久化明确终态并从可恢复队列排除,或提供跳过/选择状态机。@@ -0,0 +1944,4 @@}if let Some(existing) = manifest.assets.iter().find(|asset| asset.id == asset_id) {ledger.result_asset_id = Some(existing.id.clone());update_resource_edit_phase(root, ledger, ResourceEditLedgerPhase::Committed)?;[P1] 这里把“manifest 已存在 asset”直接当作 committed,但上一次执行可能恰好崩溃在
write_manifest之后、advance_agent_runtime_project_revision_locked之前。重启走到此分支会标记账本 committed,却永远不补 project revision,manifest 与 CAS 真相分叉。manifest 写入、revision 推进和账本提交需要 journal/可恢复事务覆盖这个崩溃窗。@@ -0,0 +459,4 @@) {return;}draftRef.current = loaded.value;[P1] 这里仍可能被乱序回包降级:假设 r5 的 loadDraft 先发、r6 后发但先返回,r6 已写入 draftRef 后,r5 回包仍满足
loaded.value.revision >= revision(它比较的是各自捕获的 revision),随后会把当前草稿从 r6 覆盖回 r5,下一次保存就会触发 revision conflict。回包落地前还需与draftRef.current.revision做单调比较,并补 r5/r6 逆序完成的回归。@@ -1192,0 +1969,4 @@type="button"disabled={resumingResourceEditId !== null}onClick={() => {const pending = pendingResourceEdits[0];[P1] UI 始终只取排序后的第一条 pending。结合远端终态失败仍被列为 pending 的路径,一个不可恢复的旧 operation 会永久占住这个按钮,后续已受理、可能已扣费的 operation 没有选择或跳过入口。请渲染可选择的恢复队列,并对终态失败提供明确处置。
补充 review #210 的当前 head 可定位意见:另有 1 个 Key 轮换恢复 P1 和 3 个交互/错误呈现 P2。
@@ -0,0 +1575,4 @@if ledger.api_identity_fingerprint.as_ref().is_some_and(|value| value != &configuration_fingerprint)[P1] 旧版本账本使用
H(baseUrl, H(oldKey)),当前只计算H(baseUrl)并在查询原 operation 前直接判 configuration-changed。若旧 Key 已失效,用户无法先用旧凭据迁移,已受理/已付费任务无法对账,只能建立新 identity,仍有重复生成与扣费风险。请提供历史 fingerprint schema 的显式迁移/确认路径,并补“旧 Key 已失效、升级后以新 Key 恢复旧 operation”的回归;只覆盖旧 Key 仍可用时的迁移不足以证明恢复。@@ -0,0 +1899,4 @@<sectionclassName="asset-canvas-surface__operation-overlay"role="alert"aria-label="图片生成失败"[P2]
canvas.failed也承载草稿保存冲突、素材 commit 和取消失败,但这里统一宣告“图片生成失败 / AI 图片生成”,并继续提供生成重试动作。保存或 revision 故障会误导用户再次生成。请让失败态携带操作类别:只有生成故障显示生成重试,保存/CAS/取消故障提供重载、保留草稿或对账动作,并补可访问名称回归。@@ -0,0 +2024,4 @@onPointerDown={(event: ReactPointerEvent<HTMLButtonElement>) => {if (lifecycleRef.current.kind !== 'canvas.editing') return;event.stopPropagation();const targetIds = event.shiftKey[P2] 仅选中一个图层时,Shift 点击该图层会在这里删掉最后一个选择,得到空 targetIds;共享 core 的 selection helper 明确保留最后一项。同一画布 core 在 Web/Tauri 因此产生不同选择和拖拽语义。请复用共享选择逻辑,并补单选后 Shift 点击自身的回归。
@@ -0,0 +2043,4 @@}return;}captureHistory({[P2] pointerdown 立即写 history,而后续 pointerup 会标脏;仅单击已选图层或零位移序列也会生成空撤销项并触发保存。用户第一次撤销只会消费无效操作,同时扩大 revision 冲突窗口。请仅在首次有效位移时 capture,并只在最终快照确实变化时 mark dirty,补零位移回归。
未处理英文或空标题内容。以后注意