合并 master 到跳一跳分支

合入 origin/master 最新变更

解决跳一跳提示词测试与图集去背冲突
This commit is contained in:
2026-06-26 21:30:26 +08:00
943 changed files with 266938 additions and 7298 deletions
+20 -3
View File
@@ -1,9 +1,10 @@
# 文档总览
`docs/` 现在按主题拆成了 6 类;旧后端路线文档开始聚合和删除,后续实现以 Rust / SpacetimeDB 当前基线为准。
`docs/` 现在按主题维护项目当前口径;旧后端路线文档开始聚合和删除,后续实现以 Rust / SpacetimeDB 当前基线为准。
## 快速入口
- [Agent 工作入口与执行准则](./%E3%80%90%E5%8D%8F%E4%BD%9C%E8%A7%84%E8%8C%83%E3%80%91Agent%E5%B7%A5%E4%BD%9C%E5%85%A5%E5%8F%A3%E4%B8%8E%E6%89%A7%E8%A1%8C%E5%87%86%E5%88%99-2026-06-22.md):复杂任务前的 Agent 阅读顺序、执行边界、技能路由、文档规则和验证口径。
- [经验沉淀](./experience/README.md):项目开发经验、UI 交接、历史实现经验。
- [审计与复盘](./audits/README.md):工程审查、文本/乱码审计、专项落地审计。
- [系统设计](./design/README.md):玩法、关系、物品与对话设计。
@@ -21,9 +22,25 @@
微信小程序虚拟支付接入、`wechat_mp_virtual` 渠道、`wx.requestVirtualPayment` 承接页和后端签名配置见 [【技术方案】微信虚拟支付接入-2026-05-26.md](./%E3%80%90%E6%8A%80%E6%9C%AF%E6%96%B9%E6%A1%88%E3%80%91%E5%BE%AE%E4%BF%A1%E8%99%9A%E6%8B%9F%E6%94%AF%E4%BB%98%E6%8E%A5%E5%85%A5-2026-05-26.md)。
微信小程序、Expo React Native 移动壳与 Tauri 桌面壳共用的宿主能力契约、method / capability 白名单、H5 facade 和三端边界见 [【前端架构】宿主壳能力统一协议-2026-06-17.md](./%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91%E5%AE%BF%E4%B8%BB%E5%A3%B3%E8%83%BD%E5%8A%9B%E7%BB%9F%E4%B8%80%E5%8D%8F%E8%AE%AE-2026-06-17.md)。
Expo React Native 移动壳和 Tauri 桌面壳的工程结构、同源 WebView 安全边界、原生能力实现、构建配置和门禁要求见 [【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md](./%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91ExpoReactNative%E4%B8%8ETauri%E5%AE%BF%E4%B8%BB%E5%A3%B3%E6%96%B9%E6%A1%88-2026-06-17.md)。
原生壳本地开发与验证命令统一从根工程进入:移动壳使用 `npm run mobile-shell:dev`、`npm run mobile-shell:typecheck`、`npm run mobile-shell:test`,桌面壳使用 `npm run desktop-shell:dev`、`npm run desktop-shell:typecheck`、`npm run desktop-shell:test`,跨端结构和能力门禁使用 `npm run check:native-shells`。
`/editor/agent` 浏览器内 AI Web 工程编辑器的静态 SPA 沙箱预览 MVP,采用“平台编辑器壳 + api-server 控制面 + 独立 runner worker + 独立预览域”四层结构;技术方案、威胁模型和验收清单见 [【技术方案】浏览器内AIWeb工程沙箱预览方案-2026-06-13.md](./technical/【技术方案】浏览器内AIWeb工程沙箱预览方案-2026-06-13.md)、[【安全模型】AIWeb工程Runner与预览隔离威胁模型-2026-06-13.md](./technical/【安全模型】AIWeb工程Runner与预览隔离威胁模型-2026-06-13.md) 和 [【测试用例】AIWeb工程静态预览MVP验收清单-2026-06-13.md](./technical/【测试用例】AIWeb工程静态预览MVP验收清单-2026-06-13.md)。P1 先用确定性 mock Agent 生成结构化 patch、真实打通项目 / 快照 / 构建 / artifact / 预览闭环,落地拆分见 [【技术方案】EditorAgentMockAgentP1落地计划-2026-06-15.md](./technical/【技术方案】EditorAgentMockAgentP1落地计划-2026-06-15.md)。
`/editor/canvas` 图片画布编辑器的画布素材 ZIP 导出能力,入口放在右上角标题栏下载图标内,采用前端 JSZip 打包画布中有效图层引用的上传图、生成图、修改结果和角色动作序列帧;动作图层右键“导出为”提供序列帧 ZIP(含前端生成的 `preview.gif`)与 Spine JSON ZIP 两个二级选项,方案见 [【前端架构】图片画布素材导出方案-2026-06-15.md](./technical/【前端架构】图片画布素材导出方案-2026-06-15.md)。
图片画布生成类面板的模型泥点默认 JSON、运行时 override、后台“模型定价”页面和主站动态下发口径见 [【编辑器】模型定价配置管理方案-2026-06-22.md](./%E3%80%90%E7%BC%96%E8%BE%91%E5%99%A8%E3%80%91%E6%A8%A1%E5%9E%8B%E5%AE%9A%E4%BB%B7%E9%85%8D%E7%BD%AE%E7%AE%A1%E7%90%86%E6%96%B9%E6%A1%88-2026-06-22.md)。
桌面端全站客服悬浮入口的全局接入点、固定层级、拖拽边界和移动端隐藏口径见 [【前端架构】全站客服悬浮入口接入约定-2026-06-23.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91%E5%85%A8%E7%AB%99%E5%AE%A2%E6%9C%8D%E6%82%AC%E6%B5%AE%E5%85%A5%E5%8F%A3%E6%8E%A5%E5%85%A5%E7%BA%A6%E5%AE%9A-2026-06-23.md)。
桌面端 `/creation` 创作工具主页、顶级“草稿”入口替换为“项目”、最近项目、新建项目和陶泥儿精选素材瀑布流的落地计划见 [【玩法创作】创作主页与项目入口改版计划-2026-06-18.md](./%E3%80%90%E7%8E%A9%E6%B3%95%E5%88%9B%E4%BD%9C%E3%80%91%E5%88%9B%E4%BD%9C%E4%B8%BB%E9%A1%B5%E4%B8%8E%E9%A1%B9%E7%9B%AE%E5%85%A5%E5%8F%A3%E6%94%B9%E7%89%88%E8%AE%A1%E5%88%92-2026-06-18.md)。
本地通过 SSH alias 管理多台服务器、查看硬件 / systemd / HTTP 健康状态并执行受控服务启停的 egui 桌面工具见 [【开发运维】本地SSH服务器管理面板技术方案-2026-06-11.md](./technical/【开发运维】本地SSH服务器管理面板技术方案-2026-06-11.md)。
生产部署切换到 systemd + Nginx + SpacetimeDB 自托管的总方案见 [PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md](./technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md),该文档也是当前生产 Jenkinsfile 的唯一入口。SpacetimeDB 表结构变更、自动迁移边界和保留旧数据的分阶段迁移流程见 [SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md](./technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md);private 表迁移 JSON 导入导出、HTTP 413 分片导入和旧数据库迁移流水线经验见 [SPACETIMEDB_JSON_STRING_MIGRATION_PROCEDURE_2026-04-27.md](./technical/SPACETIMEDB_JSON_STRING_MIGRATION_PROCEDURE_2026-04-27.md) 与 [JENKINS_SPACETIMEDB_DATABASE_MIGRATION_PIPELINES_2026-04-29.md](./technical/JENKINS_SPACETIMEDB_DATABASE_MIGRATION_PIPELINES_2026-04-29.md);后台管理独立前端工程技术方案见 [ADMIN_WEB_CONSOLE_TECHNICAL_SOLUTION_2026-04-30.md](./technical/ADMIN_WEB_CONSOLE_TECHNICAL_SOLUTION_2026-04-30.md)。
生产部署切换到 systemd + Nginx + SpacetimeDB 自托管的总方案见 [PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md](./technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md),该文档也是当前生产 Jenkinsfile 的唯一入口。Pingora 只作为独立二进制影子网关试点时,边界、路由口径与替换前验收见 [【开发运维】Pingora独立网关试点-2026-06-11.md](./technical/【开发运维】Pingora独立网关试点-2026-06-11.md)。SpacetimeDB 表结构变更、自动迁移边界和保留旧数据的分阶段迁移流程见 [SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md](./technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md);private 表迁移 JSON 导入导出、HTTP 413 分片导入和旧数据库迁移流水线经验见 [SPACETIMEDB_JSON_STRING_MIGRATION_PROCEDURE_2026-04-27.md](./technical/SPACETIMEDB_JSON_STRING_MIGRATION_PROCEDURE_2026-04-27.md) 与 [JENKINS_SPACETIMEDB_DATABASE_MIGRATION_PIPELINES_2026-04-29.md](./technical/JENKINS_SPACETIMEDB_DATABASE_MIGRATION_PIPELINES_2026-04-29.md);后台管理独立前端工程技术方案见 [ADMIN_WEB_CONSOLE_TECHNICAL_SOLUTION_2026-04-30.md](./technical/ADMIN_WEB_CONSOLE_TECHNICAL_SOLUTION_2026-04-30.md),Dashboard 默认入口、运营指标和统计口径见 [【后台管理】Dashboard运营看板方案-2026-06-23.md](./technical/【后台管理】Dashboard运营看板方案-2026-06-23.md)。
SpacetimeDB 表结构变更、自动迁移边界和保留旧数据的分阶段迁移流程见 [SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md](./technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md)。
@@ -85,7 +102,7 @@ RPG Agent 结果页发布门禁展示和预览来源 label 收口到 `src/compon
平台入口错误 / 完成弹窗的文案归一、来源格式、候选择一、dismiss key 与任务完成文案收口到 `src/components/platform-entry/platformDialogStateModel.ts`,规则见 [【前端架构】PlatformDialogStateModel收口计划-2026-06-03.md](./technical/【前端架构】PlatformDialogStateModel收口计划-2026-06-03.md)。
平台 UI Kit 的提示 / 确认弹窗收口到 `src/components/common/UnifiedConfirmDialog.tsx`,复制反馈收口到 `src/components/common/useCopyFeedback.ts`、`src/components/common/CopyFeedbackButton.tsx`、`src/components/common/CopyCodeButton.tsx` 与 `src/components/common/CopyFeedbackMessage.tsx`,基础状态提示收口到 `src/components/common/PlatformStatusMessage.tsx`,运行态短错误 / 成功 / 反馈 toast 收口到 `src/components/common/PlatformRuntimeStatusToast.tsx`,平台空态 / 轻量加载态收口到 `src/components/common/PlatformEmptyState.tsx`,平台动作按钮收口到 `src/components/common/PlatformActionButton.tsx`,平台白底子面板 / 小型列表卡片收口到 `src/components/common/PlatformSubpanel.tsx`,平台输入框 / 文本域收口到 `src/components/common/PlatformTextField.tsx`,平台字段标题收口到 `src/components/common/PlatformFieldLabel.tsx`,平台媒体预览框收口到 `src/components/common/PlatformMediaFrame.tsx`,平台胶囊状态标签收口到 `src/components/common/PlatformPillBadge.tsx`,平台 / 个人中心弹窗关闭按钮收口到 `src/components/common/PlatformModalCloseButton.tsx`,底层继续复用 `UnifiedModal`;普通提示、确认 / 取消、危险确认、复制状态机、短代码复制 chip、复制按钮表现、白底 / 个人中心 / 认证入口 token 状态条、运行态状态 toast、无操作空态、主动作按钮、白底子面板、白底交互列表卡片、普通输入字段、字段标题、图片源 / fallback / 固定比例媒体预览、单个状态 / 标签 chip 和圆形关闭按钮优先使用公共 Module,规则见 [【前端架构】PlatformUiKit弹窗组件收口计划-2026-06-08.md](./technical/【前端架构】PlatformUiKit弹窗组件收口计划-2026-06-08.md)。
平台 UI Kit 的提示 / 确认弹窗收口到 `src/components/common/UnifiedConfirmDialog.tsx`,复制反馈收口到 `src/components/common/useCopyFeedback.ts`、`src/components/common/CopyFeedbackButton.tsx`、`src/components/common/CopyCodeButton.tsx` 与 `src/components/common/CopyFeedbackMessage.tsx`,基础状态提示收口到 `src/components/common/PlatformStatusMessage.tsx`,运行态短错误 / 成功 / 反馈 toast 收口到 `src/components/common/PlatformRuntimeStatusToast.tsx`,平台空态 / 轻量加载态收口到 `src/components/common/PlatformEmptyState.tsx`,平台动作按钮收口到 `src/components/common/PlatformActionButton.tsx`,平台白底子面板 / 小型列表卡片收口到 `src/components/common/PlatformSubpanel.tsx`,平台输入框 / 文本域收口到 `src/components/common/PlatformTextField.tsx`,平台字段标题收口到 `src/components/common/PlatformFieldLabel.tsx`,平台媒体预览框收口到 `src/components/common/PlatformMediaFrame.tsx`,平台胶囊状态标签收口到 `src/components/common/PlatformPillBadge.tsx`,平台图片全屏预览收口到 `src/components/common/PlatformImagePreviewModal.tsx`,平台 / 个人中心弹窗关闭按钮收口到 `src/components/common/PlatformModalCloseButton.tsx`,底层继续复用 `UnifiedModal`;普通提示、确认 / 取消、危险确认、复制状态机、短代码复制 chip、复制按钮表现、白底 / 个人中心 / 认证入口 token 状态条、运行态状态 toast、无操作空态、主动作按钮、白底子面板、白底交互列表卡片、普通输入字段、字段标题、图片源 / fallback / 固定比例媒体预览、全屏黑底图片查看、单个状态 / 标签 chip 和圆形关闭按钮优先使用公共 Module,规则见 [【前端架构】PlatformUiKit弹窗组件收口计划-2026-06-08.md](./technical/【前端架构】PlatformUiKit弹窗组件收口计划-2026-06-08.md)。
平台入口受保护数据失效后的 stage 去留判定,以及缺失草稿 / 作品 / run 时的阶段回退,收口到 `src/components/platform-entry/platformSelectionStageModel.ts`,壳层只执行缓存清空、布尔事实汇总和必要跳转,规则见 [【前端架构】PlatformSelectionStageModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformSelectionStageModel收口计划-2026-06-04.md)。
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -9,5 +9,5 @@
## 维护规则
- 计划文档只记录可执行阶段、负责人切分、验收门禁和当前状态。
- 已经稳定为长期约定的内容,应同步沉淀到 `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md` 或 `.hermes/shared-memory/`。
- 已经稳定为长期约定的内容,应同步沉淀到 `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md` 或 `docs/project-memory/shared-memory/`。
- 若代码事实与计划冲突,以代码和当前融合文档为准,并回写更新本目录。
@@ -27,7 +27,7 @@
| 阶段 | 状态 | 说明 |
| --- | --- | --- |
| Phase 0 总计划与门禁 | 已完成 | 本文档、`docs/planning/README.md`、`docs/README.md` 和 `.hermes/shared-memory/document-map.md` 已补齐入口;后续按 phase 扩展门禁。 |
| Phase 0 总计划与门禁 | 已完成 | 本文档、`docs/planning/README.md`、`docs/README.md` 和 `docs/project-memory/shared-memory/document-map.md` 已补齐入口;后续按 phase 扩展门禁。 |
| Phase 1 首批统一壳 | 已收口 | `puzzle`、`match3d`、`jump-hop`、`wooden-fish` 已接入 `UnifiedCreationPage` / `UnifiedGenerationPage`,竖屏滚动和字段契约已回归。 |
| Phase 1 补充统一壳 | 已收口 | `jump-hop` 也已接入 `UnifiedCreationPage` / `UnifiedGenerationPage`,统一创作页现在接管拼图、抓大鹅、跳一跳和敲木鱼四条入口的可见外壳与滚动。 |
| Phase 2 契约与配置治理 | 已完成 | `creationTypes[].unifiedCreationSpec`、前端 fallback、后台配置校验和文档门禁已按现有测试与 schema 检查收口。 |
@@ -60,7 +60,7 @@
状态:已完成。
- 新增本计划文档和 `docs/planning/README.md`,并在 `docs/README.md`、`.hermes/shared-memory/document-map.md` 中补上规划入口。
- 新增本计划文档和 `docs/planning/README.md`,并在 `docs/README.md`、`docs/project-memory/shared-memory/document-map.md` 中补上规划入口。
- 补齐 `当前进度`、`执行轮次` 和可并行任务表,后续每个 phase 完成后更新本文档的状态、验收命令和风险。
退出条件:
@@ -151,15 +151,15 @@ WF-*
1. 调用 VectorEngine `/v1/images/edits`,模型固定为 `gpt-image-2`;
2. multipart 参考图固定包含默认木鱼图 `/wooden-fish/default-hit-object.png`,作为基础结构和画风参考;
3. 若用户上传参考图,该图只作为新主题参考追加到同一次 image2 edits 请求,不直接进入运行态;
4. 尺寸固定 `1:1`,必须输出绿色背景主体图(纯绿色绿幕),背景为单一纯绿色 `#00FF00`,并显式禁止黑底、白底、棋盘格、纸板底或任何其它实底背景;
4. 尺寸固定 `1:1`,必须输出单一纯绿色 `#00FF00 / RGB(0,255,0)` 绿幕背景主体图,并显式禁止黑底、白底、棋盘格、纸板底或任何其它实底背景;
5. 提示词严格使用:
```text
生成敲木鱼新样式,要求结构,画风与参考图保持高度一致,新样式颜色搭配使用新主题对应的颜色。尺寸1:1,先输出绿色背景主体图(纯绿色绿幕),背景必须是单一纯绿色 #00FF00 且平整无纹理、无渐变、无阴影、无道具,主体完整居中,主体边缘必须干净,不要直接输出透明底。随后由服务端对绿色背景主体图做抠图去除绿色背景。最终结果只保留单个敲击物图案,禁止黑底、白底、棋盘格、纸板底或任何实底背景;主体本身不要使用与绿幕接近的纯绿色,若新主题天然包含绿色,请改用偏深、偏黄或偏蓝的绿色并与绿幕清晰区分。
生成敲木鱼新样式,要求结构,画风与参考图保持高度一致,新样式颜色搭配使用新主题对应的颜色。尺寸1:1,先输出单一纯绿色 #00FF00 / RGB(0,255,0) 绿幕背景主体图,背景必须严格使用 #00FF00 / RGB(0,255,0) 且平整无纹理、无渐变、无阴影、无道具,主体完整居中,主体边缘必须干净,不要直接输出透明底。随后由服务端对绿色背景主体图做抠图去除绿色背景。最终结果只保留单个敲击物图案,禁止黑底、白底、棋盘格、纸板底或任何实底背景;主体本身不要使用与绿幕接近的纯绿色,若新主题天然包含绿色,请改用偏深、偏黄或偏蓝的绿色并与绿幕清晰区分。
新主题为:(用户提供参考图或用户输入关键词)
```
敲击物图案落盘前,`api-server` 必须只对第一步生成的纯绿色绿幕背景执行去绿处理,把绿色背景转成真实透明 alpha PNG;不得对黑底、白底或其它未知实底执行泛抠图,避免误伤玉米等主体像素。去绿处理必须保留主体内部深色结构和主题细节。
敲击物图案落盘前,`api-server` 必须只对第一步生成的单一纯绿色 `#00FF00 / RGB(0,255,0)` 绿幕背景执行去绿处理,把绿色背景转成真实透明 alpha PNG;不得对黑底、白底或其它未知实底执行泛抠图,避免误伤玉米等主体像素。去绿处理必须保留主体内部深色结构和主题细节。
背景环境图生成流程固定为:
@@ -178,12 +178,12 @@ WF-*
1. 调用 VectorEngine `/v1/images/edits`,模型固定为 `gpt-image-2`;
2. multipart 参考图固定包含第一步去除绿色背景后的敲击物主体图,以及第二步生成的背景环境图;
3. 尺寸固定 `1:1`,必须输出绿色背景主体图(纯绿色绿幕),后端落库前执行同一套去绿背景处理;
3. 尺寸固定 `1:1`,必须输出单一纯绿色 `#00FF00 / RGB(0,255,0)` 绿幕背景主体图,后端落库前执行同一套去绿背景处理;
4. 按主题、画风、材质和配色生成左上角返回按钮图,但参考图只用于约束圆形底色和中央左箭头的颜色搭配,不得借鉴复杂造型、花纹、浮雕边、异形外框或装饰图案;按钮必须始终是标准圆形,主体视觉尺寸比当前模板再放大约 50%,圆形外沿必须有与主题色搭配的干净外描边,中央只保留单个清晰左箭头或返回箭头,不得包含文字、数字、水印、额外 UI 面板、木槌或敲击道具;
5. 提示词严格使用:
```text
生成敲木鱼左上角返回按钮图。要求以参考图-去除绿色背景后的敲击物主体和背景环境图为主题、画风、材质和配色参考,但参考图只用来约束圆形底色和中央左箭头的颜色搭配,不要继承复杂造型、花纹、浮雕边、异形外框或装饰图案。按钮必须始终是标准圆形,整体像单个圆形图标,按钮主体在画布中的视觉尺寸比当前模板再放大约 50%,圆心居中,圆形外沿加一圈和主题色搭配的干净外描边,让它更像一个按钮,但仍然只保留一个清晰、简洁、居中的向左返回箭头,不要出现文字、数字、水印、按钮外标签、额外 UI 面板、木槌或敲击道具。尺寸1:1,输出绿色背景主体图(纯绿色绿幕),背景必须是单一纯绿色 #00FF00 且平整无纹理、无渐变、无阴影。按钮主体边缘干净,后续由服务端扣除绿色背景;按钮底色不要使用与绿幕接近的纯绿色,若主题天然包含绿色,请仅在圆形底色上使用偏深、偏黄或偏蓝的主题绿色,并用更高对比的箭头颜色区分。
生成敲木鱼左上角返回按钮图。要求以参考图-去除绿色背景后的敲击物主体和背景环境图为主题、画风、材质和配色参考,但参考图只用来约束圆形底色和中央左箭头的颜色搭配,不要继承复杂造型、花纹、浮雕边、异形外框或装饰图案。按钮必须始终是标准圆形,整体像单个圆形图标,按钮主体在画布中的视觉尺寸比当前模板再放大约 50%,圆心居中,圆形外沿加一圈和主题色搭配的干净外描边,让它更像一个按钮,但仍然只保留一个清晰、简洁、居中的向左返回箭头,不要出现文字、数字、水印、按钮外标签、额外 UI 面板、木槌或敲击道具。尺寸1:1,输出单一纯绿色 #00FF00 / RGB(0,255,0) 绿幕背景主体图,背景必须严格使用 #00FF00 / RGB(0,255,0) 且平整无纹理、无渐变、无阴影。按钮主体边缘干净,后续由服务端扣除绿色背景;按钮底色不要使用与绿幕接近的纯绿色,若主题天然包含绿色,请仅在圆形底色上使用偏深、偏黄或偏蓝的主题绿色,并用更高对比的箭头颜色区分。
主题为:(用户提供参考图或用户输入关键词)
```
+32
View File
@@ -0,0 +1,32 @@
# 项目记忆目录
本目录保存可以进入 Git 的项目级长期知识,供开发者和 Agent 读取。`.hermes/` 只保留 Hermes 工具专用资源,不再作为项目知识库。
## 目录结构
```text
docs/project-memory/
├─ README.md
├─ shared-memory/
│ ├─ project-overview.md
│ ├─ team-conventions.md
│ ├─ development-workflow.md
│ ├─ document-map.md
│ ├─ decision-log.md
│ ├─ pitfalls.md
│ └─ handoff-template.md
├─ plans/
└─ todos/
```
## 使用原则
- 开发前先读 `AGENTS.md`;复杂任务继续读取 `docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md`,再按任务读取 `docs/project-memory/shared-memory/` 和当前 `docs/` 文档。
- 长期有效的架构约定、接口变化、排障经验、开发流程和协作规则写入 `shared-memory/`。
- 阶段性计划写入 `plans/`,已确定但暂未实施的共享 TODO 写入 `todos/`。
- 如果本目录内容与代码或最新 `docs/` 冲突,以代码和最新 `docs/` 为准,并同步修正过期记忆。
- 禁止写入个人配置、API Key、Token、Cookie、会话记录、认证文件、本地私密路径、构建产物、日志、缓存和数据库 dump。
## RAG 索引
本目录是 Agent 本地 RAG 的高权重索引源。RAG 主要用于 Agent 检索上下文,不替代人工阅读入口或正式文档地图。索引脚本位于 `scripts/rag/`,本地生成的 `.rag/` 数据不提交 Git。
@@ -0,0 +1,399 @@
# Genarrative(陶泥儿)项目架构优化计划书
**文档版本**:v1.0
**编制日期**:2026-05-26
**项目名称**:Genarrative(陶泥儿)
**文档密级**:内部
---
## 一、项目概况
| 维度 | 详情 |
|---|---|
| **项目名** | Genarrative(陶泥儿) |
| **项目定位** | AI Native 互动视觉 RPG 平台——支持多种玩法模板的 AI 创作、运行与分享("玩法类型平台") |
| **核心玩法** | 拼图、视觉小说、Match3D、Bark Battle、Big Fish、Jump-Hop、Square Hole、木鱼、教娱等 10+ 种玩法模板 |
| **代码规模** | 前端 ~823 个 TS/TSX 文件,后端 ~1532 个 Rust 文件,属于大型项目 |
### 1.1 计划目的
本计划书基于对 Genarrative 项目当前架构的全面分析,识别架构层面的关键问题,并提出分阶段、可落地的优化方案。旨在:
- 统一前端架构模式,降低团队认知成本和新人上手门槛
- 提升模块内聚性,减少不必要的耦合与依赖
- 建立自动化契约保障机制,降低跨语言同步出错风险
- 优化工程基础设施,提高开发效率和运维可观测性
---
## 二、技术栈总览
| 层级 | 技术选型 | 版本 |
|---|---|---|
| **前端框架** | React + TypeScript + Vite | React 19 / TS 5.8 / Vite 6 |
| **样式** | TailwindCSS | v4 |
| **3D / 动画** | Three.js、Motion、cannon-es | - |
| **后端 HTTP** | Rust + Axum(BFF 门面) | Axum 0.8 |
| **游戏状态 DB** | SpacetimeDB(实时反应式数据库) | v2.2 |
| **AI / LLM** | LangChain-Rust + LLM Proxy | - |
| **小程序** | 微信小程序(含微信支付) | - |
| **容器化** | Docker Compose(Nginx + API Server + OTel Collector) | - |
| **运维** | systemd、Nginx、Jenkins CI/CD、k6 压测 | - |
| **可观测性** | OpenTelemetry(OTLP → Grafana) | - |
---
## 三、当前架构分层图
```
┌──────────────────────────────────────────────────────────────┐
│ 入口层 │
│ index.html → main.tsx → resolveAppRoute() → RouteComponent │
│ (多入口路由:平台主页 / 拼图 / BigFish / Match3D / ...) │
└──────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ 前端应用层 (src/) │
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────────────┐│
│ │ components/ │ │ services/ │ │ games/ ││
│ │ *-creation │ │ *-creation │ │ bark-battle/ ││
│ │ *-result │ │ *-runtime │ │ domain/ ││
│ │ *-runtime │ │ *-works │ │ application/ ││
│ │ common/ │ │ storyEngine │ │ infrastructure/ ││
│ │ auth/ │ │ payment │ │ ui/ ││
│ └─────────────┘ └──────────────┘ └──────────────────────┘│
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────────────┐│
│ │ hooks/ │ │ data/ │ │ routing/ ││
│ │ persistence/│ │ functionCat. │ │ config/ editor/ ││
│ └─────────────┘ └──────────────┘ └──────────────────────┘│
└──────────────────────────────────────────────────────────────┘
│ Vite Proxy (/api/*)
▼
┌──────────────────────────────────────────────────────────────┐
│ Rust 后端 (server-rs/) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ api-server (Axum HTTP / SSE / BFF 门面) │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────────────┐ ┌──────────────┐ │
│ │ platform │ │ module-* │ │ shared │ │
│ │ -auth │ │ -puzzle │ │ -contracts │ │
│ │ -llm │ │ -visual-novel │ │ -kernel │ │
│ │ -image │ │ -match3d │ │ -logging │ │
│ │ -oss │ │ -bark-battle │ └──────────────┘ │
│ │ -speech │ │ -big-fish ... │ │
│ │ -agent │ │ -runtime │ │
│ └──────────┘ │ -combat/npc │ │
│ └──────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ spacetime-module + spacetime-client → SpacetimeDB │ │
│ └──────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ 辅助子系统 │
│ apps/admin-web (React 后台管理) │
│ miniprogram/ (微信小程序) │
│ packages/shared (前后端共享契约/LLM工具) │
│ deploy/ (Docker/Nginx/systemd/OTel) │
│ scripts/ (40+ 构建/部署/检查脚本) │
│ jenkins/ (CI/CD Pipeline) │
└──────────────────────────────────────────────────────────────┘
```
---
## 四、架构亮点
1. **清晰的"玩法模板"平台模式**
每种玩法遵循统一的 `creation → result → runtime` 三段式生命周期,前端 `components/` 和 `services/` 均按此模式组织。新增玩法可快速套用模板,极大降低了横向扩展成本。
2. **后端严格的分层约束**
`api-server`(门面)→ `module-*`(领域)→ `spacetime-module`(持久化),`module-*` 不直接依赖 Axum、HTTP、SpacetimeDB table、LLM、文件系统,保证了领域纯净性和可测试性。
3. **SpacetimeDB 作为游戏状态核心**
采用反应式实时数据库替代传统 Redis + PostgreSQL 组合,天然适合多人实时游戏状态同步,减少了中间层复杂度和延迟。
4. **前端 DDD 探索**
`src/games/bark-battle/` 采用 `domain / application / infrastructure / ui` 四层 DDD 结构,为复杂玩法的前端架构提供了良好范本。
5. **完善的多入口路由体系**
通过 `resolveAppRoute()` 按 URL path 分发到不同的 lazy-loaded 组件,实现了按需加载和良好的首屏性能。
6. **运维体系完备**
Docker Compose + systemd + Nginx + OpenTelemetry + k6 压测 + Jenkins CI/CD,覆盖了构建、部署、监控、压测全链路。
---
## 五、问题诊断与改进方案
### 问题 1:前端 services/ 与 components/ 的耦合不统一
**现状**
大部分玩法遵循 `services/` + `components/` 分离模式,但部分玩法运行时直接放在 `components/` 下,services 层职责模糊——有的承担了业务逻辑编排,有的仅作简单 API 调用。
**影响**
- 新人阅读代码时无法预判某个逻辑应位于哪个目录
- 单元测试困难:services 与组件耦合的业务逻辑无法独立测试
- 跨玩法复用时需要额外的迁移成本
**改进方案**
1. 制定规范:`services/` 仅负责纯 API 调用和数据转换,不包含业务判断逻辑
2. 业务逻辑统一归到 `hooks/` 或 `games/<play-type>/application/`
3. 以 puzzle 玩法为样板,先行重构并形成迁移指南,再推广至其余玩法
4. 在 CI 中增加 ESLint 规则,禁止 `components/` 直接 import `services/` 之外的外部模块
**预期收益**
- 职责边界清晰,降低认知成本约 30%
- services 层可独立单测覆盖率达到 80%+
- 新玩法开发上手时间从 2 天缩短至 0.5 天
---
### 问题 2:前端 DDD 与模板模式并存,架构不统一
**现状**
`bark-battle` 采用 DDD 四层结构(`domain/application/infrastructure/ui`),其余玩法散落在 `components/` + `services/` 下,存在两种截然不同的组织范式。
**影响**
- 团队内部对"正确"的代码组织方式缺乏共识
- 代码审查时需要切换判断标准
- DDD 玩法的优势无法在全局范围内发挥
**改进方案**
1. 所有玩法统一迁移到 `src/games/<play-type>/` 下,采用 `domain / application / ui` 三层结构(infrastructure 按需保留)
2. 以 puzzle 为样板完成首例迁移,产出迁移 Checklist 和模板生成脚本
3. 新增玩法脚手架直接生成 DDD 结构目录
4. 旧玩法分批次迁移,每批次 2-3 个玩法,在 4 个迭代内完成
**预期收益**
- 架构一致性提升至 100%
- 跨玩法逻辑复用变得可能(domain 层可共享)
- 为后续 monorepo 改造打下基础
---
### 问题 3:后端 module-* 粒度偏细,存在潜在循环依赖风险
**现状**
后端共 35 个 crate。`module-runtime` 被拆分为 3 个独立 crate,`module-combat / npc / inventory` 各自独立。部分紧密协作的模块之间可能存在隐式耦合。
**影响**
- 编译时间增长(35 个 crate 独立编译)
- 跨 crate 重构时需要同时修改多处
- 循环依赖风险增加,可能在特定组合下触发编译失败
**改进方案**
1. 评估合并方案:
- `module-runtime-*` 系列合并为单 crate `module-runtime`,内部用 `mod` 做逻辑隔离
- `module-combat / npc / inventory` 评估合并为 `module-combat`,npc 和 inventory 作为子模块
2. 保留 trait/interface 抽象层,确保 module 之间不直接依赖具体实现
3. 在 CI 中引入 `cargo-deny` 或自定义脚本,自动检测 module 间的依赖方向是否违反分层约束
4. 目标:将 35 个 crate 精简至 25 个以内
**预期收益**
- 全量编译时间预计缩短 15%-20%
- 循环依赖风险归零
- module 内部重构成本降低
---
### 问题 4:根目录 env 文件过多且混乱
**现状**
根目录存在 4 个 env 文件(`.env`、`.env.example`、`.env.production` 等),且 `deploy/` 下另有多个 env 文件。配置分散在多处,部分文件之间字段不一致。
**影响**
- 排查配置问题时需要翻阅多个文件
- 新人无法快速确定本地开发需要哪些环境变量
- 部署时可能遗漏或错误覆盖某项配置
**改进方案**
1. 收敛为三层配置体系:
- `.env.example`:包含所有可配置项的说明和默认值(唯一提交到仓库的 env 文件)
- `.env.local`:本地开发覆盖(加入 .gitignore)
- `deploy/env/<env-name>.env`:各部署环境专用配置
2. 引入 config crate,支持层次覆盖(default → local → env-specific),启动时自动校验必填字段
3. 在 CI 中加入 env 校验步骤:对比 `.env.example` 与部署环境的 env 文件,标记缺失或多余字段
**预期收益**
- 配置查找时间从分钟级降至秒级
- 部署配置遗漏导致的线上事故减少 90%+
- 新成员本地环境搭建时间从 30 分钟缩短至 10 分钟
---
### 问题 5:scripts/ 目录膨胀为"万能工具箱"
**现状**
`scripts/` 目录共 42 个文件,涵盖构建、部署、检查、迁移、生成、压测等,全部平铺在同一层级,缺乏分类。
**影响**
- 难以快速定位所需脚本
- 同类脚本缺乏命名规范
- 新增脚本时不知道放在何处
**改进方案**
1. 按功能域分类重组:
```
scripts/
├── build/ # 构建相关(vite、cargo、wasm 等)
├── deploy/ # 部署相关(docker、systemd、rsync)
├── check/ # 检查/校验(lint、format、type-check)
├── spacetime/ # SpacetimeDB 相关(migration、seed)
├── generate/ # 代码生成(scaffold、proto、types)
└── loadtest/ # 压测脚本(k6 配置及辅助)
```
2. 为每个子目录添加 README.md,说明各脚本用途和调用方式
3. 将重复逻辑抽取为共享函数库
**预期收益**
- 脚本查找效率提升 60%+
- 降低脚本重复概率
- 便于 CI Pipeline 直接引用标准化路径
---
### 问题 6:前端路由系统缺乏统一的"玩法注册"机制
**现状**
`appRoutes.tsx` 中硬编码 `switch-case` 逻辑,每新增一种玩法需手动修改路由文件、入口组件、资源加载等多个位置。
**影响**
- 新增玩法的接入点分散,容易遗漏
- 路由文件随玩法增多持续膨胀
- 无法实现"按需注册"——即使某环境不包含某玩法,路由代码仍然存在
**改进方案**
1. 建立 `PlayTypeRegistry` 模式:每个玩法导出一个注册项对象,包含 `path`、`lazyComponent`、`preload` 等字段
2. `resolveAppRoute()` 改为动态聚合所有注册项,替代硬编码 switch-case
3. 支持环境级玩法开关:通过配置控制某环境启用哪些玩法,路由系统自动忽略未启用的
**预期收益**
- 新增玩法零侵入路由系统,只需在玩法目录内添加注册文件
- 路由文件体积与玩法数量解耦
- 灰度发布和 A/B 测试变得可能
---
### 问题 7:前端缺少统一的状态管理层
**现状**
前端状态管理依赖 React hooks + props drilling + services 层手动管理。未使用任何状态管理库(如 Zustand、Jotai、Redux)。
**影响**
- 全局状态(用户认证、会话、通知)通过多层 props 传递,组件耦合度高
- 跨页面状态无法优雅共享
- 状态变更难以追踪和调试
**改进方案**
1. 引入 Zustand(轻量、无 boilerplate、TS 友好)管理全局状态:
- `useAuthStore`:认证状态
- `useSessionStore`:当前会话/游戏状态
- `useNotificationStore`:全局通知
2. 玩法内状态继续使用 React hooks + `useReducer`,保持局部自治
3. 全局 store 与玩法内 state 通过事件总线松耦合通信
**预期收益**
- props drilling 层级从 5+ 层降至 1-2 层
- 全局状态可追溯,支持 Redux DevTools 调试
- 跨玩法状态共享(如用户余额、道具)变得自然
---
### 问题 8:shared-contracts 的实际复用程度待验证
**现状**
前后端通过 `packages/shared` 共享 DTO 类型定义,但依赖手动同步 TypeScript 类型。可能存在前后端契约不一致但编译期无法检出的情况。
**影响**
- 后端修改 DTO 字段后,前端可能遗漏更新导致运行时错误
- 手动同步耗时且易出错
- Code Review 时难以判断契约一致性
**改进方案**
1. 引入 `ts-rs`:从 Rust 结构体自动生成 TypeScript 类型定义
2. 将生成步骤集成到 CI Pipeline:
- 每次 Rust PR 触发 `ts-rs` 重新生成 TS 类型
- 对比生成的类型与仓库中的类型是否一致,不一致则 CI 失败
3. 长期考虑引入 Protobuf / OpenAPI 作为跨语言契约的单一事实来源
**预期收益**
- 前后端契约不一致导致的线上 bug 减少 95%+
- 手动同步工作量归零
- PR Review 时契约一致性问题自动拦截
---
## 六、改进优先级路线图
| 优先级 | 改进项 | 涉及层 | 建议时间 | 预期收益 |
|---|---|---|---|---|
| **P0** | 统一前端 services/hooks/components 职责边界 | 前端 | 第 1-2 周 | 降低认知成本,提升可测试性 |
| **P1** | 建立 PlayType 注册机制 | 前端 | 第 2-3 周 | 新增玩法零侵入路由 |
| **P1** | 评估 module-* 合并方案并执行 | 后端 | 第 3-4 周 | 减少编译时间 15%-20% |
| **P1** | 引入 Zustand 全局状态管理 | 前端 | 第 4-5 周 | 改善状态追踪与跨组件共享 |
| **P2** | 清理 scripts/ 目录结构 | 工程 | 第 5-6 周 | 提高可发现性 |
| **P2** | 前端玩法统一迁移至 DDD 结构 | 前端 | 第 5-8 周 | 架构一致性 100% |
| **P2** | env 配置收敛 | 工程 | 第 6-8 周 | 减少部署配置事故 |
| **P3** | 前后端共享 DTO 自动化(ts-rs) | 全栈 | 第 6-8 周 | 消除契约不一致风险 |
| **P3** | CI 分层约束检查(cargo-deny) | 后端 | 第 8-10 周 | 循环依赖归零 |
> **说明**:P0 为阻塞项,必须最先完成。P1 项可部分并行推进(PlayType 注册与 module 合并互不依赖)。P2/P3 为优化项,可在日常迭代中穿插推进。
---
## 七、架构健康度评分卡
| 维度 | 评分 | 当前状态 | 目标状态 |
|---|---|---|---|
| **分层清晰度** | ★★★★☆ | 后端分层严格,前端分层存在不一致 | ★★★★★ 前后端均严格分层 |
| **模块化程度** | ★★★★☆ | 后端 35 crate 粒度偏细,前端结构化较好 | ★★★★☆ 后端精简至 25 crate |
| **可扩展性** | ★★★★★ | 玩法模板模式使新增玩法成本低 | ★★★★★ 维持 |
| **代码复用** | ★★★☆☆ | shared 层作用有限,services 层有重复 | ★★★★☆ DDD 统一后 domain 可复用 |
| **DevOps 成熟度** | ★★★★★ | Docker + k6 + OTel + Jenkins 覆盖完整 | ★★★★★ 维持 |
| **文档完备性** | ★★★★★ | docs/ 分类清晰,基线文档齐全 | ★★★★★ 维持 |
| **技术债务管控** | ★★★★☆ | 有明确的"历史残留"标记和废弃策略 | ★★★★★ 增加自动化检测 |
**综合评级:A-(优秀,存在可优化空间)**
---
## 八、附录:代码规模统计
| 维度 | 数量 |
|---|---|
| **前端 TypeScript/TSX 文件** | ~823 个 |
| **后端 Rust 源文件** | ~1532 个 |
| **后端 Crate 数量** | 35 个 |
| **核心玩法类型** | 10+ 种 |
| **scripts/ 脚本数量** | 42 个 |
| **根目录 env 文件** | 4 个 + deploy 下多个 |
### 模块规模明细(后端)
| Crate | 职责 | 建议 |
|---|---|---|
| `api-server` | Axum HTTP 门面,路由聚合 | 保持 |
| `platform-*` (auth/llm/image/oss/speech/agent) | 平台级跨玩法能力 | 保持 |
| `module-puzzle` | 拼图玩法 | 作为 DDD 迁移样板 |
| `module-visual-novel` | 视觉小说 | 后续迁移 |
| `module-match3d` | Match3D 三消 | 后续迁移 |
| `module-bark-battle` | 犬吠对战 | 已对接前端 DDD |
| `module-big-fish` | Big Fish | 后续迁移 |
| `module-runtime*` (3 crates) | 通用运行时 | **建议合并为单 crate** |
| `module-combat / npc / inventory` | 战斗系统 | **建议合并** |
| `spacetime-module` + `spacetime-client` | SpacetimeDB 接入 | 保持 |
| `shared-contracts / kernel / logging` | 共享基础设施 | 保持 |
---
> **文档结束**
> 本计划书由 Genarrative 架构分析报告衍生,所有改进项均基于对当前项目代码库的实际分析。执行过程中如遇阻力或新发现,应及时更新本计划书并同步相关方。
File diff suppressed because one or more lines are too long
@@ -0,0 +1,327 @@
# 开发工作流
> 用途:给本地 Agent 和开发人员提供统一的开发、测试、提交流程。具体命令以 `package.json`、`server-rs/Cargo.toml`、`AGENTS.md` 和相关 `docs/` 最新文档为准。
## 标准任务流程
```text
同步代码 → 读取 AGENTS.md → 复杂任务读取 Agent 执行准则 → 读取 docs/project-memory/shared-memory → 查找/完善 docs → 制定计划 → 小步实现 → 本地验证 → 更新文档/记忆 → 提交
```
## 建议启动方式
在项目根目录启动本地 Agent:
```bash
cd /path/to/Genarrative
hermes
```
在本机当前常见路径为:
```bash
/home/dsk/workspace/Genarrative
```
其他开发者以自己本地实际路径为准,不要把个人绝对路径写入共享文档作为通用规则。
## 开发前检查清单
- [ ] 当前分支是否正确
- [ ] 是否已拉取最新代码
- [ ] 是否阅读 `AGENTS.md`
- [ ] 复杂任务是否阅读 `docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md`
- [ ] 是否阅读 `docs/project-memory/shared-memory/` 相关文件
- [ ] 是否阅读 `README.md` 中的运行和检查命令
- [ ] 是否阅读 `docs/README.md` 及任务相关分类 README
- [ ] 是否存在足够具体的 PRD / 设计 / 技术文档
- [ ] 是否明确测试、验收和文档更新方式
## 本地运行命令
安装依赖:
```bash
npm install
```
完整联调开发环境:
```bash
npm run dev
```
Linux 多用户共享同一台机器开发时,本地 dev 脚本会为当前 Linux 用户分配一个固定端口段并写入系统级注册表 `/var/tmp/genarrative-dev-port-ranges/registry.json`,自动分配从 `10000-10099` 开始,每段 100 个端口,四个 dev 服务依次使用 `start` 到 `start + 3`。可用 `GENARRATIVE_DEV_PORT_RANGE` 或 `npm run dev -- --port-range` 手动指定端口段用于特殊场景;注册表会阻止不同用户使用相同或重叠段,并让同一用户后续启动继续复用自己已占用的固定段。该机制只在 Linux 生效,Windows 仍沿用原有端口探测与漂移逻辑。
本地 `npm run dev`、`npm run dev:spacetime` 和 `npm run dev:api-server` 会在 Rust 子进程环境中绕过项目默认 `sccache` wrapper,避免损坏的本机 cache daemon 阻断 `spacetime publish` 或 `api-server` 启动;显式设置的非 sccache 自定义 wrapper 会被保留。生产 / Jenkins 构建仍按流水线自身的 sccache 策略执行。
该命令会启动:
- SpacetimeDB standalone
- Rust `api-server`
- 主站 Vite
- 后台 Vite
`npm run dev` 和单模块 `dev:*` 命令会更新根目录 `.app/dev-stack.json`,记录四个本地服务的 pid、端口、URL、启动状态和当前命令。该目录只作本机运行态观测,不提交 Git。
开启自动刷新:
```bash
npm run dev -- --watch
```
watch 模式只由外层调度器自动处理后端侧刷新:`spacetime-module` 改动后重新发布模块但不重启 standalone 宿主,`api-server` 改动后重启 Rust 进程。主站 Vite 与后台 Vite 的源码变化交给 Vite 自身 HMR,避免外层 watcher 监听到依赖缓存或临时文件后循环重启。
非 watch 模式下,`npm run dev` 终端支持输入 `rs spacetime`、`rs api-server`、`rs web`、`rs admin-web` 或 `rs all`。其中 `rs spacetime` 只会重新发布 `spacetime-module`,不会重启 standalone 宿主;其他模块仍按进程重启。
单独启动 SpacetimeDB:
```bash
npm run dev:spacetime
```
单独启动 Rust API server:
```bash
npm run dev:api-server
```
单独启动前端:
```bash
npm run dev:web
```
单独启动后台管理前端:
```bash
npm run dev:admin-web
```
本地 SSH 服务器管理面板:
```bash
npm run server-manager:panel
```
该命令启动 `server-rs/crates/server-manager-panel` 的 egui 桌面工具,从本机 `~/.ssh/config` 读取可用 `Host` alias,支持多服务器健康巡检、可折叠侧边栏和受控 systemd 服务启停。服务操作通过远端 `sudo -n systemctl start|stop|restart <unit>` 执行,目标服务器需要提前配置对应 unit 的免交互 sudo 权限。
面板启动时会自动注入本机中文字体;如开发机中文仍显示为方块,可设置 `GENARRATIVE_SERVER_PANEL_CJK_FONT=/path/to/font.ttc|index` 指向本机 CJK 字体。
`npm run dev:api-server` 会保留终端实时输出,并把同一份输出持久化到 `logs/api-server/api-server-<timestamp>.log`。完整联调入口 `npm run dev` 启动的 Rust `api-server` 使用同一套日志规则。如需改写路径,可设置 `GENARRATIVE_API_SERVER_LOG_FILE`;如只改目录,可设置 `GENARRATIVE_API_SERVER_LOG_DIR`。
开发态 `npm run dev` / `npm run dev:api-server` 默认打开 `GENARRATIVE_DEV_PASSWORD_ENTRY_AUTO_REGISTER_ENABLED=true`,密码入口可以直接注册未知手机号账号;生产默认仍关闭该开关。
生产 `Genarrative-Stdb-Module-Publish` 的备份默认使用 `DATABASE_BACKUP_MODE=async`:流水线在 publish 前先生成本地冷备份,随后继续 publish,并把同一份发布前备份交给后台 Node 进程上传 OSS,避免低带宽 OSS 上传长时间占住部署窗口。需要强制在 publish 前等待打包和上传并让失败阻断发布时,手动选择 `DATABASE_BACKUP_MODE=sync`;已有其他备份窗口且明确接受风险时才选择 `skip`。
生产 API / Web / Stdb 发布流水线不在目标机器 checkout Git。对应 Build 流水线必须把发布产物、校验文件、`release-manifest.json` 和部署 / 发布脚本一起归档;Deploy / Publish 流水线只通过 `copyArtifacts` 复制上游构建归档并执行随产物归档的脚本,避免目标机器 Git 访问和产物 commit 与部署脚本 commit 漂移。
查看本地 Rust/SpacetimeDB 日志:
```bash
npm run dev:spacetime:logs
```
本机隔离验证外部生成 worker 队列、API-only 更新和 worker 动态扩缩容时,优先使用:
```bash
npm run container:worker-smoke -- smoke
```
该命令生成 `deploy/container/worker-smoke/` 下的 gitignored env 与端口 state,启动独立 compose project 和独立 SpacetimeDB,用 unsupported job 验证 worker claim / fail 回写;排查时用 `api-update` 确认 API 重建不触碰 worker,用 `scale <n>` 调整 worker 数量。
`external_generation_job` 是 private table,worker-smoke 通过 worker 日志里的 job_id 和 unsupported 记录确认消费,不通过 CLI SQL 查询队列表。
worker-smoke 默认把本机 `spacetime` CLI 打成轻量 SpacetimeDB 镜像,避免首次 smoke 依赖官方大镜像下载。若容器内 Cargo 下载依赖不稳定,追加 `--local-binary`,让容器内 Cargo 复用本机 Cargo 缓存构建当前 `api-server` 二进制,并把产物放进 Debian bookworm smoke runtime;可用 `GENARRATIVE_WORKER_SMOKE_LOCAL_BASE_IMAGE` 覆盖运行时基础镜像;隔离端口或库数据需要重建时追加 `--force`。
后台管理前端:
```bash
npm run admin-web:build
npm run admin-web:typecheck
```
SpacetimeDB bindings 生成:
```bash
npm run spacetime:generate
```
CodeGraph 本地语义索引:
```bash
npm run codegraph:init
npm run codegraph:status
npm run codegraph:sync
npm run codegraph:index
```
`.codegraph/config.json` 可随仓库共享;`.codegraph/codegraph.db`、缓存和日志为本机生成物,不提交。
Codex 项目级 hook 保存在 `.codex/config.toml` 与 `.codex/hooks/`:
- `PreToolUse` hook:`node .codex/hooks/pre-submit-compile-check.mjs`,Codex 准备执行 `git commit` 前检查 `npm run typecheck`、`npm run admin-web:typecheck`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`。
- `PostToolUse` hook:`node .codex/hooks/post-edit-codegraph-sync.mjs`,工具修改文件后执行 `npm run codegraph:sync`。
个人 token、模型路由、MCP server 仍属于个人环境;需要时由成员本机执行 `codegraph install` 或查看 `codegraph install --print-config codex`,不要提交个人全局配置。
Agent 本地 RAG 文档索引:
```bash
npm run rag:index
npm run rag:search -- --query "搜索内容"
```
RAG 主要供 Agent 检索项目上下文,开发者仍按 `AGENTS.md`、`docs/README.md` 和 `docs/project-memory/` 阅读正式文档。RAG 仅索引项目文档和项目共享记忆,默认不把 LanceDB、Transformers.js 或本地 embedding 模型装入根 `package.json`。需要启用 RAG 时,Agent 必须先询问用户是否安装本地运行时依赖;用户确认后只安装到 gitignored 的 `.rag/runtime/`,模型缓存和向量库也留在 `.rag/`。具体命令见 `scripts/rag/README.md`。
## 常用检查命令
- 后端通用用户行为埋点统一通过 `record_tracking_event_and_return` procedure、`SpacetimeRuntimeClient::record_tracking_event(...)` 与 api-server `tracking` 中间件写入 `tracking_event` / `tracking_daily_stat`;后台、RPG、大鱼吃小鱼、Visual Novel、Story、Combat 默认排除;作品级游玩埋点统一使用 `work_play_start`,详细事件清单见 `docs/technical/BACKEND_TRACKING_EVENT_COVERAGE_2026-05-09.md`。
编码检查:
```bash
npm run check:encoding
```
ESLint:
```bash
npm run lint:eslint
```
类型检查:
```bash
npm run typecheck
```
综合 lint:
```bash
npm run lint
```
测试:
```bash
npm run test
```
生产构建:
```bash
npm run build
```
原生壳验收:
```bash
npm run check:native-shells
```
该命令会覆盖 H5 HostBridge 关键测试、微信 / Expo / Tauri 三端桥接层文件结构门禁、完整相对路径文档反查、微信 capability 到真实 WebView / 支付 / 分享页面流程和测试清单的映射门禁、H5 HostBridge 事件订阅双能力门控反查、H5 `navigation.canGoBack` 消费 hook 与直达二级页返回锚点测试、移动端和桌面端单端源码清单门禁、Expo 壳 typecheck / test / EAS build config smoke / config smoke / Metro export smoke、Tauri 壳 typecheck / cargo test、桌面壳 release `--no-bundle` 构建烟测,以及可分发壳与 H5 HostBridge 真实调用链的临时替身词扫描,确认 Expo managed config、移动端 EAS 原生包构建 profile、移动端 iOS / Android production bundle、打包 H5 资产、Tauri release 入口、H5 页面内导航保留完整原生宿主上下文和 H5 HostBridge 真实调用链没有漂移;扫描范围包含微信小程序壳生产 `.js`、Tauri `Info.plist`、共享 HostBridge 契约、H5 native transport,并自动覆盖已接入真实宿主能力 facade 的 H5 生产调用链文件,但不扫描 Expo export、Tauri `target/`、Cargo / Metro 缓存或 release 构建产物。移动壳配置检查必须反查 EAS 生产 profile、文本 / 文档 / 图片 / 音频导入边界都来自共享 HostBridge 契约。登录与支付外链跳转必须保持在该调用链扫描内,`src/services/authService.ts` 和 `src/services/payment/paymentRedirect.ts` 是必扫文件;`AuthGate` 的登录成功、退出登录、身份边界刷新和登录状态异常重试都必须通过 `app.reloadWebView` 优先路径,并由 `src/components/auth/AuthGate.test.tsx` 进入该门禁。壳源码和配置继续严格禁止 mock / fake / placeholder / stub / TODO / FIXME / 占位 / 模拟 / 伪造 / 未实现 / 临时;H5 业务调用链允许正常表单 `placeholder` 属性、业务占位图文案和真实兼容 / 故障语义中的“未实现”“临时”表述,但仍禁止 mock / fake / stub / TODO / FIXME / 模拟 / 伪造等替身痕迹。
创作 Agent 原生壳文档导入优先走 `file.importDocument`,旧壳只声明 `file.importText` 时才回退文本导入;相关变更必须让根级和单端门禁覆盖共享 method、capability profile、文档 MIME / 5 MiB 上限、读取前 size 校验,以及 H5 base64 转 `File` 后继续走后端文档解析的链路。
创作 Agent 参考图上传在原生壳声明 `file.importImage` 时必须优先走宿主图片导入,并把 H5 base64 转 `File` 后继续交给既有 `onReferenceImageChange` 校验链路;用户取消原生选择不应再连带弹出浏览器文件输入,普通浏览器、小程序和未声明能力的裁剪壳才使用原隐藏文件输入。
创作 Agent 轻输入 composer 的参考图按钮在原生壳声明 `file.importImage` 时必须优先走宿主图片导入;移动壳声明 `file.captureImage` 时才显示拍摄参考图入口,并把宿主图片同样转为 `File` 后复用 `readPuzzleReferenceImageAsDataUrl` 的类型、大小、压缩和预览链路。
反馈页上传凭证在原生壳声明 `file.importImage` 时必须优先走宿主图片导入;移动壳声明 `file.captureImage` 时才显示拍摄凭证入口,并把拍摄图片同样转为 `File` 后复用反馈页原有数量、大小、MIME、data URL 预览和提交 payload 校验。
固定内置 H5 体验入口在原生壳声明 `navigation.openNativePage` 时必须优先走 `navigateHostNativePage()`;例如儿童动作热身 Demo 从平台首页进入 `/child-motion-demo` 时应由 HostBridge 发出 `navigation.openNativePage`,宿主不可用时才回退浏览器跳转。
H5 支付链接跳转在原生壳声明 `app.openExternalUrl` 时必须优先走宿主系统浏览器;原生壳未接真实支付 SDK 前不得声明 `payment.request`,也不得把外部 H5 支付跳转伪装成原生支付成功。
微信 OAuth 登录授权 URL 在原生壳声明 `app.openExternalUrl` 时必须优先走宿主系统浏览器;原生壳未接真实登录 SDK 前不得声明 `auth.requestLogin`,也不得把网页登录跳转伪装成原生登录成功。
汪汪声浪结果页玩家 / 对手 / UI 背景三图槽位上传在原生壳声明 `file.importImage` 时必须优先走宿主图片导入,并把 H5 base64 转 `File` 后继续交给 `uploadBarkBattleAsset` 与当前槽位写回链路;用户取消原生选择不应再连带弹出浏览器文件输入,普通浏览器、小程序和未声明能力的裁剪壳才使用原隐藏文件输入。
抓大鹅结果页发布封面图和封面参考图上传在原生壳声明 `file.importImage` 时必须优先走宿主图片导入,并把 H5 base64 转 `File` 后复用现有封面 data URL 读取、AI 重绘开关、参考图集合和封面生成 payload 链路;用户取消原生选择不应再连带弹出浏览器文件输入。
RPG 角色资产工作室的角色参考图上传在原生壳声明 `file.importImage` 时必须优先走宿主图片导入,并把 H5 base64 转 `File` 后复用现有 `readFileAsDataUrl` 参考图集合和角色形象生成 payload 链路;用户取消原生选择不应再连带弹出浏览器文件输入。
RPG 作品封面上传在原生壳声明 `file.importImage` 时必须优先走宿主图片导入,并把 H5 base64 转 `File` 后复用现有 10 MiB 校验、图片尺寸读取、16:9 裁剪和 `uploadCustomWorldCoverImage` 保存链路;用户取消原生选择不应再连带弹出浏览器文件输入。
RPG 作品封面参考图上传在原生壳声明 `file.importImage` 时必须优先走宿主图片导入,并把 H5 base64 转 `File` 后复用现有 `readImageFileAsDataUrl` 读取、预览和 `generateCustomWorldCoverImage` payload 链路;用户取消原生选择不应再连带弹出浏览器文件输入。
RPG 场景图片参考图上传在原生壳声明 `file.importImage` 时必须优先走宿主图片导入,并把 H5 base64 转 `File` 后复用现有 `readImageFileAsDataUrl` 读取、预览和 `rpgCreationAssetClient.generateSceneImage` payload 链路;用户取消原生选择不应再连带弹出浏览器文件输入。
视觉小说结果页封面 / 角色 / 场景图片和音乐 / 环境音上传在原生壳声明 `file.importImage` / `file.importAudio` 时必须优先走宿主受控导入,并把 H5 base64 转 `File` 后继续交给 `uploadVisualNovelAsset` 与当前素材字段写回链路;用户取消原生选择不应再连带弹出浏览器文件输入,历史素材选择和 AI 图片生成保持原链路。
该命令会反查微信小程序 `WECHAT_HOST_CAPABILITIES` 与共享 `HOST_BRIDGE_WECHAT_MINI_PROGRAM_CAPABILITIES` 一致;小程序生产代码继续保留 CommonJS 运行时镜像,不直接 import TypeScript shared 包。
该命令同时会运行微信小程序 `miniprogram/host-bridge/`、`miniprogram/shell/`、`pages/web-view` 样式和 `scripts/miniprogram-web-view-auth.test.ts` 的壳层测试,保证微信桥接层拆分后的支付、订阅消息、九宫切图、分享目标和 WebView 登录 / 分享入口行为与 Expo、Tauri 壳一起验收。
该命令还会反查微信小程序 `app.json.pages` 与 `host-bridge/protocol.js` 页面 URL、H5 小程序页面常量、H5 订阅授权页面常量、WebView 分享入口、分享目标消息类型、`WEB_VIEW_SOURCE_QUERY`、微信请求头运行时标记、H5 runtime parser、H5 路由保留字段和 H5 / API base URL 格式,避免页面路由、来源标记、宿主上下文 query 或域名配置在微信壳、H5 HostBridge 与运行时配置之间分叉。生产 / 开发 H5 与 API 域名都必须显式配置为纯 HTTPS domain,运行时开发域名回退生产域名只作为异常兜底。
内容检查:
```bash
npm run check:data
npm run check:overrides
npm run check:smoke
npm run check:content
```
全量检查:
```bash
npm run check
```
DDD 边界检查:
```bash
npm run check:server-rs-ddd
```
## 后端相关默认验证
后端修改后,按 DDD 文档中的验收命令执行。涉及 API smoke 时:
- 使用 `npm run dev:api-server` 重新拉起后端。
- 禁止使用 `npm run api-server:maincloud`、`npm.cmd run api-server:maincloud` 或任何 `GENARRATIVE_SPACETIME_MAINCLOUD_*` 口径;这些只属于历史残留。
- 本地 smoke 检查 `/healthz`;发布后或确认实例可接生产流量时检查 `/readyz`。
- 执行对应自动测试。
- 涉及 SpacetimeDB 表、reducer、procedure、row shape 或绑定变化时,同步更新 `migration.rs`、表目录和生成绑定。
- SpacetimeDB 已有表新增字段必须放在 Rust 表结构体最后,并设置明确默认值;需要修改字段名时,先询问用户并确认迁移计划,再同步更新 `server-rs/crates/spacetime-module/src/migration.rs`、表目录和生成绑定。
- 修改 SpacetimeDB schema 后运行 `npm run check:spacetime-schema`,用自动检查拦截缺 default、插入中间、字段删除/改名/重排/改类型,以及漏改迁移、表目录或绑定。
关键文档:
- `docs/technical/CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md`
- `docs/technical/SERVER_RS_DDD_FULL_REFACTOR_2026-04-28.md`
- `docs/technical/SERVER_RS_DDD_PARALLEL_TASKLIST_2026-04-29.md`
- `docs/technical/SERVER_RS_DDD_G1_CONTRACT_AND_ROUTE_MATRIX_2026-04-29.md`
- `docs/technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md`
- `docs/technical/SPACETIMEDB_TABLE_CATALOG.md`
- `docs/technical/MAINCLOUD_REFERENCE_REMOVAL_POLICY_2026-05-06.md`
## 生产压测与观测默认口径
- 作品列表 50 HTTP req/s 压测使用 `scripts/loadtest/README.md` 中的 K6 命令;当前脚本一次 iteration 请求两个公开列表接口,因此目标 50 HTTP req/s 对应 `PEAK_RPS=25`。
- 生产 `api-server` 默认 backlog、worker threads、HTTP 并发背压、`/readyz` 接流检查、systemd 优雅停机窗口、Nginx upstream timing log 和 OTLP 开关以 `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md` 为准。
- OpenTelemetry 现阶段可选发送 traces / metrics / logs,但不会取代本地 `journalctl -u genarrative-api.service`、`logs/api-server/` 与 `/var/log/nginx/genarrative.*.log`。
- 指标 label 不写 raw URI、userId、profileId 或 request_id;request_id 只用于 trace/log 串联。
## 前端相关默认验证
前端修改后,应根据修改范围选择:
- `npm run check:encoding`
- `npm run lint:eslint`
- `npm run typecheck`
- `npm run test`
- 页面交互 smoke
- 移动端视口检查
前端原则:
- 移动端优先,再兼容网页端。
- 页面只展示后端返回的状态,不自行计算结论型业务状态。
- 创作中心入口配置事实源在 SpacetimeDB,通过 `GET /api/creation-entry/config` 下发;前端只在 `platformEntryCreationTypes.ts` 做展示派生,api-server 路由熔断也使用同一份配置,禁止恢复前端硬编码入口配置文件。底部加号创作入口页公告位也跟随后端 `eventBanners` 配置,前端只做展示和轮播;后台公告用表单维护标题与 HTML 内容,保存时再序列化为后端 `eventBannersJson` 传输字段。`最近创作` 不属于模板分类,不能作为分类缺失兜底;生成中和生成失败的真实草稿摘要都应进入最近创作。
- 一期统一创作页字段 spec 同样跟随 `GET /api/creation-entry/config`,由 `creationTypes[].unifiedCreationSpec` 下发;拼图、抓大鹅、敲木鱼之外的模板不接入该扩展位,前端只保留旧后端缺字段时的兜底默认。
- 优先复用现有面板、抽屉、弹窗,不新建独立大系统。
- 不在 UI 中默认写功能说明类文本。
- 弹出独立面板的交互不要实现成在当前面板下方追加内容。
## 文档更新规则
- 工程修改要同步更新对应文档。
- 如果没有现成文档,新文档统一放入 `docs/` 下合适分类。
- `docs/project-memory/shared-memory/` 只记录高频、长期、团队共享的摘要和索引,不替代完整 PRD/技术文档。
- 如果 `docs/project-memory/shared-memory/` 与代码或 `docs/` 冲突,以代码和最新 `docs/` 为准,并同步修正共享记忆。
## 提交前建议让 Agent 执行
涉及拼图、抓大鹅、敲木鱼统一创作 / 生成链路、Phase 2 之后的跨玩法回归或本地 dev 栈时,先按 `quality-gates/README.md`、`quality-gates/【玩法创作】跨玩法回归与冒烟门禁-2026-05-30.md` 和对应单项门禁文档执行自动脚本与体验检查。
```text
请检查当前 git diff,指出:
1. 是否违反 AGENTS.md、Agent 执行准则或 docs/project-memory/shared-memory 约定;
2. 是否需要补充 docs;
3. 是否有长期知识需要写入 docs/project-memory/shared-memory;
4. 建议的测试命令和提交信息。
```
@@ -0,0 +1,59 @@
# 文档地图与阅读索引
更新时间:`2026-06-22`
## 当前文档入口
| 场景 | 优先阅读 |
| --- | --- |
| 建立项目背景 | `README.md`、`AGENTS.md`、`docs/project-memory/shared-memory/project-overview.md` |
| Agent 复杂任务执行规则 | `AGENTS.md`、`docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md` |
| 找当前文档 | `docs/README.md` |
| 产品、命名、UI、协作和废弃路线 | `docs/【项目基线】当前产品与工程约束-2026-05-15.md` |
| 后端、DDD、API、SpacetimeDB schema 和表目录 | `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md` |
| 创作入口、草稿架和玩法链路 | `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md` |
| 创作流程统一阶段计划 | `docs/planning/【玩法创作】创作流程统一总计划-2026-05-30.md` |
| 宿主壳、移动 App、桌面 App 与 AI H5 沙箱边界 | `docs/【前端架构】宿主壳能力统一协议-2026-06-17.md`、`docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md` |
| 本地启动、验证、部署、埋点和运营查询 | `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md` |
| 微信小程序虚拟支付 | `docs/【技术方案】微信虚拟支付接入-2026-05-26.md` |
| UI 像素资产与 9-slice 规范 | `UI_CODING_STANDARD.md` |
| 图片画布生成面板与模型定价 | `docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md`、`docs/【编辑器】模型定价配置管理方案-2026-06-22.md` |
## 阅读顺序
通用复杂任务:
1. `AGENTS.md`
2. `docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md`
3. `docs/project-memory/shared-memory/`
4. `docs/README.md`
5. 与任务匹配的当前融合文档
后端 / 数据真相 / SpacetimeDB:
1. `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
2. `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
3. 对应 crate README 或源码
玩法 / 创作入口 / 运行态:
1. `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
2. 若任务涉及跨玩法创作流程统一,读取 `docs/planning/【玩法创作】创作流程统一总计划-2026-05-30.md`
3. `docs/【项目基线】当前产品与工程约束-2026-05-15.md`
4. 相关前端组件、service、shared contract 和后端 module
生产部署 / 服务器 / Jenkins:
1. `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
2. `deploy/env/api-server.env.example`
3. `deploy/nginx/README.md`
## 维护规则
- 当前 `docs/` 只保留少量融合文档。
- `AGENTS.md` 只保留最高优先级入口;Agent 执行细则优先沉到 `docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md` 或对应专题文档。
- 新增工程实现时,如果已有对应当前文档,必须同步更新。
- 如果没有合适位置,新文档文件名必须使用 `【标签名】中文标题-YYYY-MM-DD.md`。
- 阶段性流水账、一次性修复记录和已关闭实验不要再新增为长期文档。
- 阶段性计划和一次性 TODO 不再作为长期文档目录;需要保留的决策、流程和坑点应进入 `docs/` 当前文档或 `docs/project-memory/shared-memory/`。
- 如果文档与代码冲突,先确认代码事实,再更新过期文档和共享记忆。
@@ -0,0 +1,53 @@
# 任务交接模板
> 用途:当一名开发者把任务交给另一名开发者,或让 Hermes 接续上下文时,复制本模板并填写。
## 基本信息
- 任务名称:
- 负责人:
- 当前分支:
- 相关需求/Issue:
- 相关文档:
## 背景
简要说明为什么做这个任务,以及业务/技术目标。
## 已完成
- [ ]
- [ ]
- [ ]
## 未完成
- [ ]
- [ ]
- [ ]
## 关键文件
- `path/to/file`:说明
- `path/to/file`:说明
## 当前问题/风险
-
## 已执行验证
```bash
# 粘贴已执行命令和结果摘要
```
## 建议下一步
1.
2.
3.
## 是否需要更新团队记忆
- [ ] 不需要
- [ ] 需要,建议更新:`docs/project-memory/shared-memory/...`
File diff suppressed because one or more lines are too long
@@ -0,0 +1,62 @@
# Genarrative 项目共享概览
更新时间:`2026-06-23`
## 一句话定位
Genarrative / 陶泥儿是一个 AI 原生互动内容与小游戏平台,把 AI 创作、作品草稿、公开分发、运行态、用户账号、钱包任务、后台管理和小程序外壳收在同一套工程中。
产品 IP / 产品形象统一为 `public/branding/taonier-product-ip.png`:陶罐中探出的橙色耳朵形象。后续提到“产品 IP”“产品形象”或左上角品牌旁形象时默认指该资产。
## 当前主要能力
- RPG / 自定义世界创作与运行时。
- 拼图玩法创作、草稿、发布、运行态和排行榜。
- 拼消消玩法创作、素材图集生成、结果页、发布、统一作品详情、正式运行态和基础统计。
- 敲木鱼玩法创作、草稿、发布、运行态、公开详情和分享码。
- 抓大鹅 Match3D 创作、2D 多视角素材生成、发布和运行态。
- 大鱼吃小鱼、方洞挑战、视觉小说、汪汪声浪和儿童向寓教于乐玩法。
- 账号、短信 / 密码 / 微信登录、个人资料、任务、钱包、邀请码、充值、反馈、法律信息和后台管理。
## 当前入口
- 主站:`http://127.0.0.1:3000`。
- 后台:`http://127.0.0.1:3000/admin/` 或 `http://127.0.0.1:3102`。
- 后台前端工程:`apps/admin-web`。
- 小程序 WebView 外壳:`miniprogram/`。
- 法律文本:`media/files/user_agreement.md`、`media/files/privacy_policy.md`、`media/files/disclaimer.md`。
移动端一级 Tab:`推荐 / 发现 / 我的`。桌面端导航保留 `创作` 并新增 `项目`,其中 `/creation` 是独立创作工具主页,`/project` 是画布项目入口。
## 当前后端路线
唯一有效后端路线:
```text
server-rs + Axum + SpacetimeDB
```
当前 SpacetimeDB crate、SDK、CLI / standalone、生成 bindings 和容器压测镜像统一按 `2.5.0` 对齐;遇到版本不匹配时先升级到 `server-rs/Cargo.toml` 锁定版本,升级后重启对应 SpacetimeDB 进程再重试。
职责边界:
- `api-server`:HTTP / SSE / BFF 门面和外部副作用编排。
- `spacetime-module`:SpacetimeDB 表、reducer、procedure、事务 adapter 和 row mapper。
- `spacetime-client`:后端访问 SpacetimeDB 的 typed facade。
- `module-*`:纯领域模型、命令、应用规则、领域事件和领域错误。
- `platform-*`:OSS、LLM、认证、语音等外部平台能力。
- `shared-contracts` / `packages/shared`:前后端 DTO 和公开契约。
- 前端:表现、交互、临时 UI 状态和后端结果渲染。
明确废弃:旧 `server-node`、Express、PostgreSQL、Go 服务端、`maincloud`、人工 `spacetime --root-dir` 口径,以及前端承接正式业务真相的路线。
## 当前文档入口
- `docs/README.md`
- `docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md`
- `docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
- `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
- `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
旧 PRD、设计、审计、阶段计划和技术流水账已融合进上述文档;没有融合的旧材料不再作为实现依据。
@@ -0,0 +1,97 @@
# 团队协作约定
> 用途:约定 3 名开发人员在各自本地开发环境和 Agent 中协作开发、共享项目记忆的方式。
## 基本模式
- 每位开发人员在自己的电脑上使用本地 Agent。
- 每位开发人员本地拉取同一个项目仓库,独立修改代码、运行测试、提交分支。
- 团队共享内容优先放在本仓库 `docs/project-memory/` 与 `docs/` 中,通过 Git 同步。
- 不共享个人 `~/.hermes` 目录。
## 共享与禁止共享
推荐共享:
- `docs/project-memory/shared-memory/` 团队级长期记忆
- `docs/project-memory/plans/` 阶段性实施计划
- `docs/project-memory/todos/` 已确定需要执行、但尚未进入实施的共享 TODO 计划
- `.hermes/skills/` Hermes 专用仓库级 skills
- `docs/` 中 PRD、设计、技术、经验、审计、查询手册
- `AGENTS.md` 项目级 Agent 约束
- `docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md` Agent 执行细则
禁止提交:
- 个人 `~/.hermes/config.yaml`
- 个人 `~/.hermes/.env`
- 个人 `~/.hermes/sessions/`
- API Key、Token、Cookie、认证文件
- 个人本地私密路径和个人隐私信息
- 构建产物、日志、缓存、数据库 dump
## 开发前
1. 拉取最新代码。
2. 阅读 `AGENTS.md`。
3. 复杂任务阅读 `docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md`。
4. 阅读 `docs/project-memory/shared-memory/` 中与任务相关的文件。
5. 阅读 `docs/README.md` 和任务相关分类 README。
6. 阅读对应 PRD、设计、技术、经验或审计文档。
7. 如果文档不足以指导编码,先补充或修正文档。
## 开发中
- 保持修改范围聚焦,不做无关重构。
- 复用、修改、扩展现有系统优先,避免新建重复系统或页面。
- 新增 Markdown 文档时,文件名必须以分类标签开头,格式为 `【标签名】中文标题-日期.md`;只在任务需要时重命名历史文档,避免无关大 diff。
- 涉及中文文本时注意 UTF-8 编码和乱码排查。
- 涉及后端时遵循 DDD 分层,不把业务真相下沉到前端或临时兼容层。
- `maincloud` / `Maincloud` / `MAINCLOUD` 相关代码、脚本、测试、环境变量、命令和文档要求均视为历史残留,禁止新增、运行或引用;API smoke 统一使用 `npm run dev:api-server` 与 `/healthz`。
- 涉及 SpacetimeDB 表结构、发布或迁移时,先看 `SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md` 和 `SPACETIMEDB_TABLE_CATALOG.md`。
- 涉及生产发布、服务器配置、Jenkins Job 重建或回滚时,先看 `PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`。
## 开发后
1. 运行与修改范围匹配的测试或验证命令。
2. 更新相关 `docs/` 文档。
3. 新增或沉淀 Markdown 文档时,确认文件名已使用 `【标签名】` 前缀。
4. 若产生长期有效知识,更新 `docs/project-memory/shared-memory/`。
5. 若形成 Hermes 专用可复用流程,考虑沉淀到 `.hermes/skills/`。
6. 提交代码时,提交标题使用中文;标题后逐行写明本次提交修改了什么,每条变更单独一行。
## 文档阅读顺序
通用任务建议:
1. `README.md`
2. `AGENTS.md`
3. `docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md`
4. `docs/project-memory/shared-memory/`
5. `docs/README.md`
6. 任务所属分类:`docs/design/`、`docs/technical/`、`docs/planning/`、`docs/prd/`、`docs/reference/`、`docs/tracking/`、`docs/operations/`
后端任务建议:
1. `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
2. `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
3. 任务相关 crate README、源码和当前专题文档
## 共享记忆更新准则
适合更新:
- 新增稳定架构约定
- 新增长期开发流程
- 已验证的踩坑和排障步骤
- 重要接口契约变化
- 团队协作规范变化
- 文档索引或阅读顺序变化
不适合更新:
- 一次性临时计划
- 未验证猜测
- 个人偏好和个人路径
- 敏感信息
- 大段聊天记录
@@ -0,0 +1,27 @@
# 前端直订阅公开作品列表准入待办
## 背景
未来可以考虑让前端直接订阅公开作品列表,以减少列表读取链路中的 HTTP 往返,并复用 SpacetimeDB 的实时同步能力。
## 当前结论
短期仍由 `api-server` / BFF 订阅 SpacetimeDB public read model,并从本地 cache 读取后对外提供 HTTP 列表接口。前端不直接订阅作品源表,也不把正式列表排序、分页、权限裁剪逻辑下放到 UI。
## 落地前置条件
- 建立专用、稳定、低基数的 public read model,例如 `public_work_gallery_entry`。
- 明确权限边界,只暴露公开列表所需字段,不泄露作者私有信息、审核内部状态或运营字段。
- 固化字段契约,明确字段含义、默认值、兼容策略和生成绑定更新流程。
- 明确排序与分页语义,避免依赖自增 ID 顺序,优先使用时间戳或显式排序字段。
- 补齐埋点方案,能区分直订阅首屏、增量更新、分页加载和 fallback 命中。
- 保留 BFF HTTP fallback,用于低版本客户端、订阅失败、权限策略调整和灰度回滚。
- 禁止前端订阅 `puzzle_work_profile`、`custom_world_profile` 等作品源表。
## 建议验收
- 文档确认直订阅只面向专用 public read model,不绕过 BFF 读取源表。
- schema、绑定、字段契约、排序分页和权限说明同步更新。
- 前端具备订阅失败后的 BFF HTTP fallback。
- 自动测试覆盖公开字段裁剪、排序分页稳定性和 fallback 路径。
- 监控可观察直订阅成功率、首屏耗时、增量更新延迟和 fallback 比例。
@@ -0,0 +1,146 @@
# Editor Image Model Options Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 让图片画布的“生成角色形象”和“生成图标素材”支持 `nanobanana2` 与 `gpt-image-2`,并按模型提供合法的尺寸比例与大小尺寸选项。
**Architecture:** 前端抽出编辑器图片模型配置,生成面板只保存模型、比例、大小三个轻量状态;后端集中归一模型和尺寸组合,角色图与图标 spritesheet 继续走现有 VectorEngine、去背、OSS 和拆分链路。用户模型偏好用 localStorage 记住,默认 `nanobanana2`。
**Tech Stack:** React + TypeScript + Vitest;Rust Axum api-server;platform-image VectorEngine provider;Markdown 项目文档。
---
## File Structure
- Modify `C:\Genarrative\src\services\image-editor\editorProjectClient.ts`
- 扩展图片生成和图标 spritesheet 请求类型,加入 `aspectRatio` 与 `imageSize`。
- 默认图标模型改为 `gemini-3.1-flash-image-preview` 对应的 `nanobanana2`。
- Modify `C:\Genarrative\src\services\image-editor\editorProjectClient.test.ts`
- 先补失败测试:角色 / 图标请求会带模型、比例、大小。
- Modify `C:\Genarrative\src\components\image-editor\ImageCanvasEditorView.tsx`
- 增加模型配置、选项归一、localStorage 偏好、角色 / 图标面板字段和提交 payload。
- Modify `C:\Genarrative\src\components\image-editor\ImageCanvasEditorView.test.tsx`
- 先补失败测试:默认显示 nanobanana2,切换模型后比例 / 大小选项变更,并在请求中传递。
- Modify `C:\Genarrative\server-rs\crates\platform-image\src\vector_engine\request.rs`
- 让 `512` 不被 normalize 成非法尺寸,并保留 gpt-image-2 尺寸。
- Modify `C:\Genarrative\server-rs\crates\platform-image\tests\vector_engine.rs`
- 先补失败测试:nanobanana2 0.5K 请求 body 保留 `512`。
- Modify `C:\Genarrative\server-rs\crates\api-server\src\editor_project.rs`
- 扩展请求 DTO,集中校验 `nanobanana2 / gpt-image-2` 与尺寸组合。
- 角色生成按模型走 with_model 调用;图标生成按模型和组合选择尺寸。
- Modify docs:
- `C:\Genarrative\docs\【编辑器】画板角色形象生成入口设计-2026-06-15.md`
- `C:\Genarrative\docs\【编辑器】画板图标素材生成入口设计-2026-06-15.md`
- `C:\Genarrative\docs\technical\【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`
---
### Task 1: Client request contract
**Files:**
- Modify: `C:\Genarrative\src\services\image-editor\editorProjectClient.ts`
- Test: `C:\Genarrative\src\services\image-editor\editorProjectClient.test.ts`
- [ ] **Step 1: Write failing tests**
Add tests asserting `generateEditorImage` and `generateEditorIconSpritesheet` serialize `model`, `aspectRatio`, and `imageSize` when supplied.
- [ ] **Step 2: Run test to verify failure**
Run: `npm run test -- src/services/image-editor/editorProjectClient.test.ts -t "image model options"`
Expected: FAIL because payloads do not include `aspectRatio` / `imageSize`.
- [ ] **Step 3: Minimal implementation**
Extend input types and JSON body builders to include optional `aspectRatio` and `imageSize`; change icon default model constant to `gemini-3.1-flash-image-preview` if not already.
- [ ] **Step 4: Verify green**
Run same test command. Expected: PASS.
### Task 2: Frontend panel state and local preference
**Files:**
- Modify: `C:\Genarrative\src\components\image-editor\ImageCanvasEditorView.tsx`
- Test: `C:\Genarrative\src\components\image-editor\ImageCanvasEditorView.test.tsx`
- [ ] **Step 1: Write failing tests**
Add tests for:
1. opening `生成角色形象` defaults to model `nanobanana2` and shows `尺寸比例` / `大小尺寸`;
2. switching to `gpt-image-2` limits visible combinations and submits model + mapped size metadata;
3. icon spritesheet defaults to `nanobanana2` and submits the chosen model.
- [ ] **Step 2: Run test to verify failure**
Run: `npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx -t "模型|尺寸比例|大小尺寸"`
Expected: FAIL because current UI has placeholder model button only.
- [ ] **Step 3: Minimal implementation**
Add model option config, dialog fields `imageModel/aspectRatio/imageSize`, localStorage helpers, option buttons/selects, and submit payload wiring.
- [ ] **Step 4: Verify green**
Run same test command. Expected: PASS.
### Task 3: Backend model and dimension normalization
**Files:**
- Modify: `C:\Genarrative\server-rs\crates\platform-image\src\vector_engine\request.rs`
- Modify: `C:\Genarrative\server-rs\crates\api-server\src\editor_project.rs`
- Test: `C:\Genarrative\server-rs\crates\platform-image\tests\vector_engine.rs`
- Test: existing unit tests inside `editor_project.rs`
- [ ] **Step 1: Write failing tests**
Add tests covering:
1. `build_vector_engine_image_request_body_with_model("gemini-3.1-flash-image-preview", ..., "512", ...)` keeps `size = "512"`.
2. editor character model normalization defaults to nanobanana2 and maps `gpt-image-2 + 2:3 + 1K` to `1024x1536`.
3. icon spritesheet model normalization accepts both models.
- [ ] **Step 2: Run backend tests to verify failure**
Run:
`cargo test -p platform-image --manifest-path server-rs/Cargo.toml vector_engine_request_body_can_use_nanobanana2_half_k -- --nocapture`
`cargo test -p api-server --manifest-path server-rs/Cargo.toml editor_project -- --nocapture`
Expected: FAIL until normalization functions exist.
- [ ] **Step 3: Minimal implementation**
Add constants and helpers:
- `EDITOR_IMAGE_MODEL_NANOBANANA2 = "gemini-3.1-flash-image-preview"`
- `EDITOR_IMAGE_MODEL_GPT_IMAGE_2 = "gpt-image-2"`
- `normalize_editor_image_model`
- `normalize_editor_generation_dimensions`
Use with_model calls for character and icon generation responses.
- [ ] **Step 4: Verify green**
Run the same backend tests. Expected: PASS.
### Task 4: Documentation and final checks
**Files:**
- Modify docs listed above.
- [ ] **Step 1: Update docs**
Document defaults, user preference, model-specific options, and Apifox source URLs.
- [ ] **Step 2: Run focused verification**
Run:
`npm run test -- src/services/image-editor/editorProjectClient.test.ts src/components/image-editor/ImageCanvasEditorView.test.tsx`
`cargo test -p platform-image --manifest-path server-rs/Cargo.toml vector_engine_request_body_can_use_nanobanana2_half_k -- --nocapture`
`cargo test -p api-server --manifest-path server-rs/Cargo.toml editor_project -- --nocapture`
`npm run typecheck -- --pretty false`
`npm run check:encoding`
---
## Self-Review
- Spec coverage: covers role generation, icon spritesheet generation, default model, user preference, model-specific dimensions, docs.
- Placeholder scan: no unresolved placeholders.
- Type consistency: frontend uses `model/aspectRatio/imageSize`; backend DTO mirrors camelCase fields.
@@ -0,0 +1,84 @@
# 图片信息生成输入快照 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 图片画布编辑器的图片信息页删除生图 Prompt 字段,改为展示图片生成时的面板输入快照,包括文本字段和参考图。
**Architecture:** 在 `CanvasLayer` 上新增轻量 `generationInputs` 前端快照,只保存用户可见输入和参考图摘要;生成成功时从对应面板状态构建快照,随画布 layout JSON 持久化。图片信息弹窗只读取该快照渲染,不回退显示后端组装 Prompt。
**Tech Stack:** React + TypeScript + Vitest + Testing Library;现有 `UnifiedModal`、平台按钮和图片画布 layout snapshot。
---
### Task 1: 信息弹窗行为测试
**Files:**
- Test: `C:/Genarrative/src/components/image-editor/ImageCanvasEditorView.test.tsx`
- [ ] **Step 1: Write the failing tests**
- 修改已有图片信息测试,断言弹窗不出现 `Prompt` 和 `复制Prompt`。
- 新增普通生成图片测试:生成后打开信息页,应显示 `生成输入`、`生成提示词` 和用户输入值。
- 新增角色生成图片测试:绑定角色规范参考图后生成,信息页应显示 `角色设定`、`角色规范` 与参考图名称。
- 新增图标素材生成测试:绑定图标规范后生成,信息页应显示 `素材描述`、具体描述和 `图标规范` 参考图。
- [ ] **Step 2: Run test to verify it fails**
- Run: `npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx`
- Expected: FAIL because `generationInputs` is not yet implemented and old `Prompt` field still exists.
### Task 2: 生成输入快照模型与持久化
**Files:**
- Modify: `C:/Genarrative/src/components/image-editor/ImageCanvasEditorView.tsx`
- [ ] **Step 1: Add minimal types**
- Add `CanvasGenerationInputField`, `CanvasGenerationInputReference`, `CanvasGenerationInputs`.
- Add optional `generationInputs` to `CanvasLayer`.
- [ ] **Step 2: Serialize and hydrate**
- Include `generationInputs` in `serializeLayer`.
- Hydrate only trusted shapes: string `title`, string `value`, string `label`, string `src`.
### Task 3: Build snapshots when creating generated layers
**Files:**
- Modify: `C:/Genarrative/src/components/image-editor/ImageCanvasEditorView.tsx`
- [ ] **Step 1: Add builders**
- `buildGenerationInputsForImagePrompt(prompt)`.
- `buildGenerationInputsForSpec(specType, specValues)`.
- `buildGenerationInputsForCharacter(prompt, specRef, references)`.
- `buildGenerationInputsForIcon(iconDescriptions, iconSpecRef)`.
- `buildGenerationInputsForEdit(prompt, sourceLayer)`.
- [ ] **Step 2: Attach snapshots**
- Pass `generationInputs` into `addGeneratedResultLayer`, `addQuickEditResultLayer`, and `addIconSpritesheetResultLayers`.
- Keep old `prompt/actualPrompt` for backend metadata and backward compatibility, but do not render them in UI.
### Task 4: Render image info without生图 Prompt
**Files:**
- Modify: `C:/Genarrative/src/components/image-editor/ImageCanvasEditorView.tsx`
- Modify: `C:/Genarrative/src/index.css` if existing metadata styles need a reference grid helper.
- [ ] **Step 1: Replace Prompt row**
- Remove `Prompt` dt/dd and `复制Prompt` button.
- Render `生成输入` row.
- [ ] **Step 2: Render fields and references**
- If `generationInputs` has fields, render each field title/value.
- If it has references, render thumbnail cards with title and image.
- If both empty or absent, render `-`.
### Task 5: Documentation and verification
**Files:**
- Modify: `C:/Genarrative/docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`
- [ ] **Step 1: Update documentation**
- Add note: image info displays panel input snapshot and references, never assembled generation Prompt.
- [ ] **Step 2: Run verification**
- Run: `npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx`
- Run: `npm run typecheck`
- Run: `npm run check:encoding`
- Run: `git diff --check`
@@ -0,0 +1,106 @@
# 图片画布生成对象独立化修复 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [x]`) syntax for tracking.
**Goal:** 修复图片画布底部生成按钮复用单一状态导致后创建对象销毁前一个对象的问题。
**Architecture:** 保留现有图片画布组件结构,先以回归测试锁定“规范占位 + 角色占位可并存”。实现上把生成占位状态从单个 `generateDialog` 扩展为 active dialog + inactive dialog 列表;每次新建生成对象只新增一个 dialog 实例,旧实例保留占位和逻辑状态,只有当前 active 实例渲染编辑面板。
**Tech Stack:** React、TypeScript、Vitest、Testing Library。
---
### Task 1: 补充失败回归测试
**Files:**
- Modify: `C:/Genarrative/src/components/image-editor/ImageCanvasEditorView.test.tsx`
- [x] **Step 1: Write the failing test**
在 `keeps the bottom AI toolbar visible while generation panels are open` 附近新增测试:
```tsx
it('keeps existing generation placeholders when another bottom generation object is created', () => {
render(<ImageCanvasEditorView />);
const bottomToolbar = screen.getByRole('toolbar', { name: 'AI画布工具栏' });
fireEvent.click(
within(bottomToolbar).getByRole('button', { name: '生成规范' }),
);
fireEvent.click(screen.getByRole('menuitem', { name: '角色规范' }));
expect(screen.getByLabelText('规范生成占位图')).toBeTruthy();
expect(screen.getByRole('dialog', { name: '生成规范' })).toBeTruthy();
fireEvent.click(screen.getByRole('button', { name: '生成角色形象' }));
expect(screen.getByLabelText('规范生成占位图')).toBeTruthy();
expect(screen.getByLabelText('角色生成占位图')).toBeTruthy();
expect(screen.getByRole('dialog', { name: '生成角色形象' })).toBeTruthy();
});
```
- [x] **Step 2: Run test to verify it fails**
Run: `npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx -t "keeps existing generation placeholders"`
Expected: FAIL because only the latest placeholder remains.
### Task 2: 实现最小独立生成对象状态
**Files:**
- Modify: `C:/Genarrative/src/components/image-editor/ImageCanvasEditorView.tsx`
- [x] **Step 1: Add stable dialog ids and inactive dialog state**
Add `id: string` to `GenerateDialogState`; add `generationDialogCounterRef`; add `inactiveGenerateDialogs` state. Provide helpers to create ids, archive current active dialog before replacing it, update active/inactive dialogs by id, and list all canvas generation dialogs.
- [x] **Step 2: Update bottom generation openers**
Change `openGenerateDialog`、`openSpecDialog`、`openCharacterGenerationDialog`、`openIconGenerationDialog` so each call archives the current active canvas generation dialog and sets a newly created active dialog. Edit modal remains single active dialog and does not archive.
- [x] **Step 3: Render all placeholders**
Replace the single placeholder render block with a map over inactive dialogs plus active dialog. Only active dialog shows composer; inactive dialogs remain visible and can be clicked to reactivate their own panel.
- [x] **Step 4: Keep actions scoped to active dialog**
Keep submit/update/upload/pick actions operating on active dialog only. Adjust delete, drag, blur, and generated-layer cleanup so they update or remove only the matching active/inactive dialog.
- [x] **Step 5: Keep archived async generation writeback scoped by dialog id**
当一个生成对象已经进入 `generating`,随后用户再创建第二个生成对象并把第一个对象归档为 inactive 时,第一个对象仍可能继续被拖拽或等待异步完成。完成回写必须按 `dialog.id` 从 active + inactive 的最新状态读取占位图,不能使用提交瞬间的旧 `placeholder` 快照。
回归测试:
```bash
npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx -t "keeps archived generation logic"
```
### Task 3: 验证并更新文档
**Files:**
- Modify: `C:/Genarrative/docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`
- Optional Modify: `C:/Genarrative/.hermes/shared-memory/pitfalls.md`
- [x] **Step 1: Run focused tests**
Run: `npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx -t "keeps existing generation placeholders|opens character spec generation form|opens icon asset generation panel|removes the active character generation placeholder"`
Expected: PASS.
- [x] **Step 2: Run full image editor test**
Run: `npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx`
Expected: PASS.
- [x] **Step 3: Run encoding check**
Run: `npm run check:encoding`
Expected: PASS.
- [x] **Step 4: Document behavior**
Add one sentence to the image canvas editor technical plan: bottom generation buttons create independent canvas generation objects; creating a new one must not destroy previous placeholders or generated-object logic.
@@ -0,0 +1,128 @@
# 画板角色动画生成 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 在图片画布编辑器中,仅对角色图片提供角色动画生成入口,并通过后端 seedance2.0-fast 链路生成视频、抽帧、去绿幕并持久化到 OSS。
**Architecture:** 前端在 `ImageCanvasEditorView` 中基于图层 `assetKind === "character"` 控制悬浮按钮和右键菜单,打开锚定到图片右侧的独立动画生成面板。前端 service 调用新增编辑器角色动画 API,后端复用 `character_animation_assets.rs` 中现有视频生成、抽帧、绿幕去背、OSS 写入能力,避免新建平行资产系统。
**Tech Stack:** React + TypeScript + Vitest;Rust Axum `api-server`;现有 `shared-contracts` 资产 DTO;Aliyun OSS 资产持久化;VectorEngine/Ark seedance2.0-fast 角色动画链路。
---
### Task 1: 文档补充
**Files:**
- Modify: `C:/Genarrative/docs/【编辑器】画板角色形象生成入口设计-2026-06-15.md`
- [ ] **Step 1: 补充角色动画生成章节**
- 明确仅 `assetKind: "character"` 图层展示入口。
- 明确右侧独立面板字段、预设动作、价格、模型、抽帧和 OSS 存储口径。
- 明确非角色图层不展示按钮。
- [ ] **Step 2: 运行编码检查**
- Run: `npm run check:encoding`
- Expected: PASS 或仅与本任务无关的既有问题。
### Task 2: 前端失败测试
**Files:**
- Modify: `C:/Genarrative/src/components/image-editor/ImageCanvasEditorView.test.tsx`
- Modify: `C:/Genarrative/src/services/image-editor/editorProjectClient.test.ts`
- [ ] **Step 1: 写失败测试**
- 测试角色图层显示悬浮 / 右键 `生成动画`。
- 测试非角色图层不显示 `生成动画`。
- 测试面板提交请求包含 `sourceLayerId`、`sourceImageSrc`、prompt、resolution、ratio、frameCount、durationSeconds、priceMudPoints、model。
- [ ] **Step 2: 验证 RED**
- Run: `npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx src/services/image-editor/editorProjectClient.test.ts`
- Expected: FAIL,失败原因是功能/API 尚未实现。
### Task 3: 前端实现
**Files:**
- Modify: `C:/Genarrative/src/services/image-editor/editorProjectClient.ts`
- Modify: `C:/Genarrative/src/components/image-editor/ImageCanvasEditorView.tsx`
- Modify: `C:/Genarrative/src/index.css`
- [ ] **Step 1: 新增 service 类型和请求函数**
- `generateEditorCharacterAnimation(input)` 调用 `/api/editor/character-animations/generations`。
- 限定 model 固定为 `seedance2.0-fast` 的回包展示字段。
- [ ] **Step 2: 扩展图层 assetKind**
- `CanvasLayer.assetKind` 支持 `'character' | 'spec' | null`。
- hydrate / serialize / 图片类型展示跟随扩展。
- [ ] **Step 3: 加入口和面板**
- 角色图层悬浮工具条和右键菜单显示 `生成动画`。
- 面板锚定图片右侧,字段按设计实现,文本框 maxLength=4000。
- 价格通过 `resolution * durationSeconds` 计算。
- [ ] **Step 4: 验证 GREEN**
- Run: `npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx src/services/image-editor/editorProjectClient.test.ts`
- Expected: PASS。
### Task 4: 后端失败测试
**Files:**
- Modify: `C:/Genarrative/server-rs/crates/shared-contracts/src/assets.rs`
- Modify: `C:/Genarrative/server-rs/crates/api-server/src/character_animation_assets.rs`
- Modify: `C:/Genarrative/server-rs/crates/api-server/src/modules/play_flow.rs` 或现有 editor router 文件(按现有路由事实选择)
- [ ] **Step 1: 写 DTO / prompt / plan 单测**
- 验证请求 480p/720p、32/40/48 帧、比例枚举、模型固定 seedance2.0-fast。
- 验证构造 prompt 包含用户给定固定骨架与动作描述。
- 验证价格计算:480p 每秒 10,720p 每秒 20。
- [ ] **Step 2: 验证 RED**
- Run: `cargo test -p api-server editor_character_animation --manifest-path server-rs/Cargo.toml`
- Expected: FAIL,失败原因是 helper 或 handler 尚未实现。
### Task 5: 后端实现
**Files:**
- Modify: `C:/Genarrative/server-rs/crates/shared-contracts/src/assets.rs`
- Modify: `C:/Genarrative/server-rs/crates/api-server/src/character_animation_assets.rs`
- Modify: `C:/Genarrative/server-rs/crates/api-server/src/modules/play_flow.rs` 或现有 editor router 文件
- [ ] **Step 1: 新增编辑器角色动画 DTO**
- 请求字段:sourceLayerId/sourceImageSrc/promptText/resolution/ratio/frameCount/durationSeconds/sourceWidth/sourceHeight。
- 响应字段:taskId/model/prompt/previewVideoPath/frames/priceMudPoints。
- [ ] **Step 2: 新增 handler**
- 校验 prompt 1..4000、resolution、ratio、frameCount 与 durationSeconds 组合。
- sourceImageSrc 作为首尾帧参考。
- 调用现有 seedance image-to-video 逻辑生成预览视频。
- 调用现有抽帧 + 绿幕去背 + OSS 持久化逻辑输出帧。
- [ ] **Step 3: 路由接入**
- `POST /api/editor/character-animations/generations`。
- 保持走 play_flow 创作/游玩支撑主干或 editor 路由现有聚合,不回到 `app.rs` 平行挂载。
- [ ] **Step 4: 验证 GREEN**
- Run: `cargo test -p api-server editor_character_animation --manifest-path server-rs/Cargo.toml`
- Expected: PASS。
### Task 6: 总验证与收口
**Files:**
- All modified files.
- [ ] **Step 1: 定向前端测试**
- Run: `npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx src/services/image-editor/editorProjectClient.test.ts`
- Expected: PASS。
- [ ] **Step 2: 定向后端测试 / check**
- Run: `cargo test -p api-server editor_character_animation --manifest-path server-rs/Cargo.toml`
- Run: `cargo check -p api-server --manifest-path server-rs/Cargo.toml`
- Expected: PASS。
- [ ] **Step 3: 类型与编码**
- Run: `npm run typecheck`
- Run: `npm run check:encoding`
- Expected: PASS。
- [ ] **Step 4: 检查 diff**
- Run: `git diff --check`
- Expected: PASS。
@@ -4,7 +4,7 @@
`PlatformEntryFlowShellImpl.tsx` 曾内联维护拼图生成完成后刷新恢复的两个纯函数:`normalizeRecoveredPuzzleDraftSession` 与 `hasRecoverableGeneratedPuzzleDraft`。旧逻辑只要草稿有 `coverImageSrc`、首关 cover 或候选图,就会把恢复会话的 draft 和首关 `generationStatus` 抬成 `ready`,再进入结果页。
`.hermes/shared-memory/pitfalls.md` 已记录:拼图待发布判定偏弱时,只有首图但缺关卡画面、UI spritesheet 或关卡背景的半成品会被误当完成,用户进入结果页后仍可能空图或无法发布。
`docs/project-memory/shared-memory/pitfalls.md` 已记录:拼图待发布判定偏弱时,只有首图但缺关卡画面、UI spritesheet 或关卡背景的半成品会被误当完成,用户进入结果页后仍可能空图或无法发布。
本切片先修前端恢复链路:只有完整首关资产包存在时,恢复流程才视为可完成。后端 `build_result_preview` / `validate_publish_requirements` / `is_puzzle_session_snapshot_publish_ready` 的发布门槛收紧另作后续切片,不混入本次前端模型收口。
@@ -277,7 +277,7 @@
19.3.47. `PlatformDarkModalFooter` 继续从标准双按钮 footer 扩展到 detail / confirm 收尾:`NpcModals.tsx` 的交易详情单按钮 footer 与 `MapModal.tsx` 的场景切换确认 footer 已接入共享 dark footer frame,分别保留“关闭”单 CTA 和“取消 / 确认前往”双 CTA 的业务语义、按钮 tone 与禁用态。后续 dark / pixel modal 里若只是标准底部分隔线 + 常规动作区排布,优先直接复用 `PlatformDarkModalFooter`,即使只有单个按钮也不再手写 `flex justify-end`;但像 `SquareImageCropModal.tsx` 这类白底弹窗 footer、sticky 工作台 footer 和运行态 HUD 工具条继续留在各自语义壳层,不强行混到 dark footer 抽象里。验证命令:`npx vitest run src/components/NpcModals.test.tsx src/components/MapModal.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
19.3.48. `RpgEntryHomeView.tsx` 里的分类筛选工具条继续从页面内重复 JSX 收口到 `src/components/common/PlatformFilterToolbar.tsx`;该 Module 只承接“筛选按钮 + 横向 tabs + 排序按钮”的结构排布,暴露 `mobile / desktop` 两种 layout 以覆盖移动端 divider + 独立排序行和桌面端同排布局差异,但不持有分类列表、筛选状态、空态或排序逻辑。当前 RPG 首页分类区已接入,后续若其它白底列表页也出现同构的筛选壳层,可直接复用这套薄结构组件;若场景只是在单页内局部重复、接口会为了兼容业务差异不断膨胀,则优先退回文件内 helper,不把 `common` 扩成假的“万能筛选条”。验证命令:`npx vitest run src/components/common/PlatformFilterToolbar.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
19.3.49. `SquareImageCropModal.tsx` 的白底 modal 壳层与 footer 已收口到 `src/components/common/UnifiedModal.tsx`;`UnifiedModal` 为此只薄补了 `titleId` 与 `closeIcon` 透传,继续由调用方决定 `closeOnBackdrop`、`closeOnEscape`、`portal`、header/footer 样式和按钮内容,不额外掺入 profile 业务语义,也不让 `common/` 反向依赖 `platform-entry/`。`SquareImageCropModal.tsx` 继续保留裁剪拖拽、pointer capture、保存禁用态与两列等宽 footer 行为,只把 header / body / footer 外壳交给共享 modal 承接。后续 `common` 级白底工具弹窗若只是标准标题栏 + 内容区 + footer 按钮排布,优先先看 `UnifiedModal` 是否够用,再决定是否需要新的薄壳;不要为了一个弹窗把 `PlatformProfileModalShell` 之类带页面语义的壳层倒灌回 `common`。验证命令:`npx vitest run src/components/common/SquareImageCropModal.test.tsx src/components/common/UnifiedModal.test.tsx src/components/unified-creation/workspaces/PuzzleCreationWorkspace.interaction.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
19.3.50. `CreativeImageInputPanel.tsx` 里内嵌的 white tool modal 继续并回 `UnifiedModal` 体系:参考图预览与主图预览都改成直接复用 `src/components/common/UnifiedModal.tsx`,继续保留各自 `max-w` / `max-h` 节奏、点击遮罩关闭与紧凑 header;移除图片确认改成复用 `src/components/common/UnifiedConfirmDialog.tsx`,不再在 panel 内手写 `platform-modal-backdrop + platform-modal-shell + 两列按钮`。这次没有新增 `PlatformImagePreviewModal`,因为当前预览弹窗差异还只在尺寸和文案层,继续直接组合 `UnifiedModal` 更深、更稳。后续 `common` 级图片面板若出现同类“预览大图 + 单标题栏 + 关闭按钮”弹窗,优先先复用 `UnifiedModal` 并把尺寸/文案留在调用方;只有当至少两到三个调用点开始重复同一套 preview body/header adapter 时,再考虑补新的薄壳。验证命令:`npx vitest run src/components/common/CreativeImageInputPanel.test.tsx src/components/common/UnifiedModal.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
19.3.50. `CreativeImageInputPanel.tsx` 里内嵌的图片查看器改为 `src/components/common/PlatformImagePreviewModal.tsx`:参考图预览与主图预览都使用黑底全屏查看器,底层继续委托 `UnifiedModal size="fullscreen"` 承接 dialog / portal / Escape 语义,但 overlay、panel 和 body 必须强制全屏黑底,避免透出原页面或白底工具面板。查看器固定提供缩小、重置、放大和关闭图标按钮,缩放范围夹在 `1x-4x`;图片先按视口完整 contain,放大后拖拽位移按缩放后的图片边界夹取,不能把图片拖到露出背景。移除图片确认继续复用 `src/components/common/UnifiedConfirmDialog.tsx`,不和全屏查看器混同。后续 `common` 级图片大图预览优先复用 `PlatformImagePreviewModal`,若只是裁剪、选择或编辑工具弹窗,再回到 `UnifiedModal` / `PlatformToolModalShell` 的白底工具语义。验证命令:`npm run test -- src/components/common/PlatformImagePreviewModal.test.tsx src/components/common/CreativeImageInputPanel.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
19.3.51. `PlatformReportDialog.tsx` 与 `PublishShareModal.tsx` 共同的工具信息弹窗壳层继续收口到 `src/components/common/PlatformUtilityInfoModal.tsx`;该 Module 只承接平台主题 overlay、白底 panel,以及 body / footer 的基础间距与标准 footer frame,底层继续委托 `UnifiedModal.tsx`,不吸收报告字段列表、分享正文、复制逻辑、渠道按钮或品牌图标这些业务内容。`PlatformReportDialog.tsx` 继续保留 `PlatformInfoBlock` 字段列表与 joined report copy 行为,`PublishShareModal.tsx` 继续保留分享文案、主复制动作和渠道按钮网格;后续 `common` 级白底工具信息弹窗若只是重复这套“共享 modal 外壳 + 业务正文 / footer 内容”的骨架,优先复用 `PlatformUtilityInfoModal`,只有当正文编排或 footer 交互明显偏离时才回退到直接组合 `UnifiedModal`。验证命令:`npx vitest run src/components/common/PlatformUtilityInfoModal.test.tsx src/components/common/PlatformReportDialog.test.tsx src/components/common/PublishShareModal.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
19.3.52. profile 白底 modal 里的摘要头、列表骨架和内容行继续沉到 `src/components/common/PlatformProfileSummaryHeader.tsx`、`src/components/common/PlatformProfileSkeletonList.tsx` 与 `src/components/common/PlatformProfileContentRow.tsx`;这三个 Module 只承接 `kicker + title + badge` 的摘要层次、重复 skeleton 列表行,以及 `PlatformSubpanel` 上的 `div / button` 内容行语义,不持有账单金额、任务进度、邀请用户信息、充值商品结构或 modal 状态切换逻辑。`PlatformProfileWalletLedgerModal.tsx`、`PlatformProfileTaskCenterModal.tsx`、`PlatformProfilePlayedWorksModal.tsx`、`PlatformProfileReferralModal.tsx` 与 `PlatformProfileRechargeModal.tsx` 已接入;后续 profile 副弹层若只是重复这三类白底内容骨架,优先继续复用这组薄组件,不再把 skeleton、摘要头和 row chrome 写回各自 modal。验证命令:`npx vitest run src/components/common/PlatformProfileModalContent.shared.test.tsx src/components/platform-entry/PlatformProfileTaskCenterModal.test.tsx src/components/platform-entry/PlatformProfileWalletLedgerModal.test.tsx src/components/platform-entry/PlatformProfilePlayedWorksModal.test.tsx src/components/platform-entry/PlatformProfileReferralModal.test.tsx src/components/platform-entry/PlatformProfileRechargeModal.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
19.3.53. 认证入口白底弹窗壳层收口到 `src/components/auth/PlatformAuthModalShell.tsx`;该 Module 只承接平台主题 overlay、`platform-auth-card`、标准标题栏、关闭按钮、点击遮罩关闭和禁用 Escape 的认证弹窗策略,不持有短信 / 密码登录、重置密码、邀请码规范化、法律协议或错误状态。`LoginScreen.tsx` 与 `RegistrationInviteModal.tsx` 已接入,业务组件只保留表单状态与提交流程。后续认证域新增同形态白底弹窗时优先复用该壳层;账号安全详情和绑定手机号这类布局差异较大的卡片先独立评估,不把 auth shell 扩成万能认证容器。验证命令:`npx vitest run src/components/auth/PlatformAuthModalShell.test.tsx src/components/auth/AuthGate.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
@@ -0,0 +1,36 @@
# 全站客服悬浮入口接入约定
日期:2026-06-23
## 背景
主站需要在桌面端每个页面提供稳定可见的客服入口,包括 `/editor/canvas` 这类全屏画布页面。入口使用陶泥儿客服形象图片,不进入具体业务页布局,避免被滚动、画布缩放或面板层级影响。
## 接入口
- 全局 React 入口为 `src/components/common/FloatingFeedbackEntry.tsx`。
- 在 `src/main.tsx` 的 `RouteImageReadyGate` 内、当前路由组件之后渲染,覆盖主站 `resolveAppRoute` 管理的所有页面。
- 图片资产放在 `public/branding/taonier-feedback-entry.png`,并通过 `BRAND_ASSETS.taonierFeedbackEntry` 引用。
- 悬浮层样式使用 `.floating-feedback-entry`,通过 `position: fixed` 和最高层级保持在页面最上方。
## 交互约定
- 仅桌面端展示,媒体查询为 `(min-width: 1024px)`;移动端不渲染入口。
- 初始位置靠近页面左下角,随窗口滚动保持固定。
- 用户可以按住拖动,拖拽过程中实时夹在当前 viewport 内,保证图片完整可见,不被窗口边界遮挡。
- 窗口尺寸变化后重新夹取当前位置,避免缩小窗口后入口跑出视口。
- 点击入口使用新页面打开客服表单:
```text
https://kcnz41bksl1c.feishu.cn/share/base/form/shrcn1p9WpuZMDxrI5ysLLJhw7k
```
- 拖拽释放后短时间内的补发 click 会被吞掉,避免用户拖动入口时误打开表单。
## 验收口径
- 桌面端主站任意路由均能看到客服入口,画布页也可见。
- 滚动页面时入口位置相对视口不变。
- 拖到窗口四边时入口完整留在可见区域内。
- 移动端视口不渲染入口。
- 单击入口新开页面跳转到飞书表单。
@@ -0,0 +1,232 @@
# 图片画布素材导出方案
日期:2026-06-15
## 背景
`/editor/canvas` 已承载项目画布、账号级素材库、生成图片、图层、分组、隐藏、锁定、翻转、缩放和右键菜单等编辑能力。用户需要一次性下载当前画布中使用到的全部素材,用于本地归档、外部编辑或跨工具交接。
素材导出应作为画布级能力,而不是单个图层的“导出为”。单图导出仍保留在目标右键菜单;全部素材导出放在画布标题栏,入口稳定、可发现且不打断画布操作。
## 入口设计
- 在画布右上角标题栏内增加下载图标按钮。
- 图标使用 `lucide-react` 的 `Download`。
- 按钮 `aria-label` 使用 `下载画布素材`。
- 入口与返回项目页、项目名称同属标题栏,但视觉上靠右,不进入左下角画布工具组,也不进入底部 AI 工具栏。
- 标题栏空间不足时,下载图标保持固定尺寸,项目名称使用省略号收缩。
## 第一版范围
第一版实现“导出画布素材 ZIP”:
- 导出当前画布中有效图层引用的图片。
- 包含上传图、生成图、修改生成结果。
- 角色动作 `image-sequence` 图层导出完整透明 PNG 序列帧。
- 默认包含隐藏图层。
- 跳过已被素材库删除且已判定无效的上传图层。
- 锁定、分组、翻转等状态不影响图片文件导出,但写入元数据。
- 空画布时按钮置灰,或点击后显示轻提示。
暂不实现:
- 画布整体截图 PNG。
- PSD / Figma / 工程包导出。
- 后端异步打包任务。
- 导入导出包恢复画布。
## ZIP 结构
导出文件名:
```text
项目名-画布素材-YYYYMMDD.zip
```
包内结构:
```text
项目名-画布素材/
├─ images/
│ ├─ 001-拼图素材.png
│ ├─ 002-生成图片.png
│ └─ 003-修改结果.png
├─ sequences/
│ └─ 004-角色动作/
│ ├─ frames/
│ │ ├─ frame-01.png
│ │ └─ frame-02.png
│ ├─ skeleton.json
│ ├─ README.md
│ ├─ metadata.json
│ └─ manifest.txt
├─ metadata.json
└─ manifest.txt
```
角色动作单图层右键“导出为”展开二级菜单:
- `序列帧导出(zip)` 生成普通序列帧 ZIP,保留完整 `frames/`,并由前端把成功读取的帧拼成 `preview.gif`。
- `Spine 导出(zip)` 生成 Spine JSON ZIP,保留 `frames/`,并附带 `skeleton.json`。
普通序列帧 ZIP 结构:
```text
角色动作-Sequence.zip
├─ frames/
│ ├─ frame-01.png
│ └─ frame-02.png
├─ preview.gif
├─ metadata.json
└─ manifest.txt
```
Spine JSON ZIP 结构:
```text
角色动作-SpineJSON.zip
├─ frames/
│ ├─ frame-01.png
│ └─ frame-02.png
├─ skeleton.json
├─ README.md
├─ metadata.json
└─ manifest.txt
```
`skeleton.json` 使用 Spine JSON 4.2 结构表达序列帧:一个 `root` bone、一个 `character` slot、每帧一个 region attachment,`animations.animation.slots.character.attachment` 按时间切换 attachment。它不是 `.spine` 编辑器工程,也不包含 mesh、权重、IK 或拆件绑定。
## 元数据结构
`metadata.json` 记录项目、导出时间、图层和图片文件映射:
```json
{
"projectId": "editor-project-default",
"projectTitle": "默认项目",
"exportedAt": "2026-06-15T00:00:00.000Z",
"layers": [
{
"layerId": "layer-generated-1",
"title": "生成图片 1",
"file": "images/002-生成图片.png",
"width": 1024,
"height": 1024,
"canvas": {
"x": 120,
"y": 80,
"width": 420,
"height": 420,
"zIndex": 12,
"hidden": false,
"locked": false,
"flipX": false,
"flipY": false,
"groupId": null
},
"sourceType": "generated",
"prompt": "一张明亮的拼图主视觉",
"actualPrompt": "一张明亮的拼图主视觉",
"model": "gpt-image-2",
"provider": "VectorEngine",
"taskId": "editor-real-task-1"
}
]
}
```
`manifest.txt` 用于人工快速查看:
```text
项目:默认项目
导出时间:2026-06-15T00:00:00.000Z
素材数量:3
图层数量:5
```
## 去重规则
同一张图片被多个图层引用时,只导出一份图片,所有图层在 `metadata.json` 中指向同一个 `file`。
图片去重 key 优先级:
```text
assetObjectId > objectKey > sourceAssetId > src
```
文件命名按图层 zIndex 从低到高排序,遇到重复名称追加序号。
## 前端实现
第一版优先使用客户端 ZIP:
1. 从当前 `layers` 取目标图层。
2. 过滤无效图层,保留隐藏图层。
3. 按去重 key 合并图片源或序列帧源。
4. 对每个素材源读取 Blob:
- `data:image/...` 直接转换为 Blob。
- 同源或可访问 URL 使用 `fetch` 拉取 Blob。
- 私有 generated / OSS 素材先走 `/api/assets/read-url` 换签并由浏览器直接 `fetch` OSS 签名 URL;只有换签或 OSS 字节读取失败时,才 fallback 到同源 `/api/assets/read-bytes`。
- `mediaType="image-sequence"` 逐帧读取 `imageSequenceFrames`,写入 `sequences/<编号-标题>/frames/`。
- 画布素材 ZIP 内的 `mediaType="image-sequence"` 同步写入 `skeleton.json`,供 Spine Editor 或 runtime 以 slot attachment timeline 方式播放序列帧。
- 单图层普通序列帧导出写入 `preview.gif`,由前端基于成功读取的帧生成动画预览,不依赖压缩包读取时再临时播放 PNG。
- 拉取失败时记录失败项,不中断整个导出。
5. 使用 `JSZip` 写入 `images/`、`metadata.json` 和 `manifest.txt`。
6. `zip.generateAsync({ type: 'blob' })` 生成文件。
7. 用临时 `<a download>` 触发下载。
若当前仓库未安装 `jszip`,新增依赖时应只引入 `jszip`,不引入额外下载库。
## 错误处理
- 空画布:按钮置灰或显示短提示。
- 部分图片下载失败:ZIP 仍生成,`metadata.json` 记录失败图片,UI 显示“部分素材未能导出”。
- 全部图片失败:不下载 ZIP,显示失败提示。
- 浏览器不支持 Blob 下载:显示失败提示。
## 测试计划
- 标题栏右上角显示 `下载画布素材` 图标入口。
- 空画布时入口不可执行或提示为空。
- 上传图和生成图都写入 ZIP。
- 多图层引用同一素材时,图片文件只写一份。
- 隐藏图层默认写入 `metadata.json`。
- 图层的锁定、翻转、分组状态写入元数据。
- `data:image` 图片不经过网络请求即可导出。
- URL 图片 fetch 失败时不中断其他素材导出。
- 动作图层右键菜单把 `导出为` 作为一级入口,二级菜单提供 `序列帧导出(zip)` 和 `Spine 导出(zip)`。
- 单图层普通序列帧 ZIP 包含 `frames/`、`preview.gif`、`metadata.json` 和 `manifest.txt`,且不包含 `skeleton.json`。
- 序列帧导出的 `skeleton.json` 可被独立验证器解析并预览。
## Spine JSON 验证器
导出产物验证项目位于 `tools/spine-json-export-validator/`,独立于主站构建。它用 `@esotericsoftware/spine-webgl` 的 `SkeletonJson`、自定义 `AttachmentLoader`、`GLTexture` 和 `SceneRenderer` 直接验证导出的 `skeleton.json + frames/`,不要求 atlas。
首次使用安装验证器自己的依赖:
```bash
npm --prefix tools/spine-json-export-validator install
```
本地启动:
```bash
npm run spine-export-validator:dev
```
检查命令:
```bash
npm run spine-export-validator:typecheck
npm run spine-export-validator:build
```
验证器支持根目录 `skeleton.json`,也支持画布素材 ZIP 内 `sequences/<编号-标题>/skeleton.json`。当前只验证 region attachment;如果未来导出 mesh、sequence attachment 或多动画命名,需要同步扩展验证器的 loader 和 UI 摘要。
## 后续扩展
- 导出当前选中素材。
- 导出画布快照 PNG。
- 导出可恢复工程包。
- 导入工程包恢复画布。
- 后端异步打包大项目,前端只轮询下载结果。
File diff suppressed because one or more lines are too long
@@ -0,0 +1,253 @@
# 图片画布编辑器前端拆分计划
日期:2026-06-17
## 背景
`src/components/image-editor/ImageCanvasEditorView.tsx` 已承载图片画布的素材库、图层、生成对象、导出、右键菜单、拖拽、缩放、吸附、小地图和登录恢复等能力。文件体量超过八千行,阅读一个局部交互时需要先穿过大量无关类型、常量和纯函数,后续继续补齐 Lovart 式画布能力会持续放大维护成本。
本轮拆分目标不是把界面拆成很多浅模块,而是先把稳定模型和纯逻辑放到有深度的模块中。主视图继续保留 React 状态编排和 JSX 工作面,避免第一轮把复杂交互拆成大量 props 透传。
## 拆分原则
- 类型、常量和纯函数优先拆出,React 状态闭环暂不拆散。
- 模块接口要承载真实规则,不建立只转发一两个属性的浅模块。
- 画布坐标、素材快照、生成输入快照和导出元数据分别放在相近规则的模块内,提升局部修改能力。
- `ImageCanvasEditorView.tsx` 不再反向作为模型模块的依赖;依赖方向固定为 `View -> model/types/services`。
- 任何拆分必须保持现有交互、持久化和测试断言不变。
## 第一阶段模块
- `ImageCanvasEditorTypes.ts`
- 承载编辑器前端共享类型:素材、图层、视口、工具、生成对象、历史快照、剪贴板、右键菜单、拖拽状态等。
- 只暴露类型,不承载运行时逻辑。
- `ImageCanvasEditorModel.ts`
- 承载画布基础模型:尺寸、缩放、背景色、素材默认文件夹、快照序列化 / 水合、素材库快照映射、吸附、右键菜单定位、DataTransfer 工具和通用数值格式化。
- 保留“图片显示尺寸跟随 Resolution”“只保留一个默认素材文件夹”“右键菜单不滚动而是限制到视口内”等规则。
- `ImageCanvasGenerationModel.ts`
- 承载生成相关模型:生成占位尺寸、默认模型、规范表单默认值、角色动画选项、生成输入快照、规范 prompt 构建、生成对象识别和错误文案。
- 保留角色动画优先用 `objectKey` 的体积保护规则。
- `ImageCanvasExportModel.ts`
- 承载画布素材导出的底层规则:文件名清理、日期格式、图片去重 key、Data URL 转 Blob、Blob 读取和图层导出元数据。
- ZIP 组包和下载触发仍留在主视图,作为 UI 状态编排的一部分。
## 第二阶段模块
- `ImageCanvasSidebarView.tsx`
- 承载素材 / 图层共用左侧整合面板的 JSX,包括素材文件夹、新建 / 折叠 / 重命名 / 删除、上传入口、素材选择模式、框选层、批量删除、素材拖到文件夹和图层列表。
- 继续通过 props 调用主视图状态机,不接管上传、登录弹窗、持久化、拖到画布的坐标换算和图层历史记录。
- 保持“素材”和“图层”同一侧栏切换的 Lovart 式布局,不恢复右侧独立图层栏或左侧竖向工具栏。
## 第三阶段模块
- `ImageCanvasStageView.tsx`
- 承载中央画布工作区的视觉树:viewport / world DOM、图层渲染、生成占位框、选中图片浮动工具栏、空白和图片右键菜单、左下 dock、缩放菜单、背景设置面板、小地图和底部 AI 工具栏。
- 继续通过 props 调用主视图状态机,不接管拖拽 / 平移 / 缩放、画布坐标换算、历史 undo / redo、上传、登录、生成提交、素材持久化和右键命令实现。
- 保持 `canvasViewportRef` 由主视图传入,确保 pointer capture、drop 坐标、滚轮缩放和小地图拖拽仍使用同一套坐标源。
- 图片图层渲染必须和音频、视频一样走统一资源读取解析;图层存在 `objectKey` 时优先用它换签,避免重进项目后私有上传 / 生成资源只剩旧路径而无法显示。
第三阶段以后,主视图仍是画布编排入口。继续拆分前应优先选择能形成稳定边界的深模块,避免把上传链路、DataTransfer、画布坐标和历史快照拆成互相回调的小碎片。
## 第四阶段模块
- `ImageCanvasGenerationComposerView.tsx`
- 承载生成图片、生成规范、生成角色形象、生成图标素材、快速编辑图片、角色动画和修改图片弹窗的视觉表单。
- 保留生成对象状态机、提交 API、上传文件 input、引用选择、生成结果回写、图层历史和坐标锚定在主视图内,避免把 Lovart 式生成对象拆成不可追踪的远程状态。
- 该组件可以管理局部字段输入和菜单展示,但所有会影响画布事实的动作都通过主视图回调落回原有状态机。
## 第五阶段模块
- `ImageCanvasLayerCommandModel.ts`
- 承载图层命令的纯数据规则:右键目标解析、复制快照、粘贴 / 创建副本定位、层级移动、分组 / 解组、显示 / 隐藏、锁定 / 解锁、水平 / 垂直翻转和删除。
- 主视图继续负责命令触发时机、历史快照、选中态、菜单关闭、元数据清理和导出下载等 UI / 浏览器副作用。
- 该模块有独立单测锁定当前右键菜单语义,避免后续调整 UI 时顺手改变图层数据规则。
## 第六阶段模块
- `ImageCanvasInteractionModel.ts`
- 承载画布交互纯计算:适合视图、中心缩放、普通滚轮纵向滚动、Ctrl / Cmd 滚轮缩放、画布坐标换算、框选命中、平移、生成占位框拖拽、图层拖拽吸附、小地图投影、小地图点击定位和小地图拖拽视图移动。
- 主视图继续负责 React 事件对象、pointer capture、history 快照、生成对象回写、选中态和 `setState`。
- 该模块用独立单测覆盖小地图灵敏度、吸附、多选拖拽和滚轮缩放等之前容易回退的交互规则。
## 第七阶段模块
- `useCanvasHistory.ts`
- 承载画布历史栈:快照创建、快照恢复、撤销、重做、历史栈长度限制和 `canUndo` / `canRedo` 派生状态。
- 主视图继续负责在具体用户动作前调用 `captureCanvasHistory`,并通过 hook 注入恢复快照后需要清理的 hover、元数据、框选、吸附、右键菜单和平移拖拽状态。
- 该 hook 有独立测试覆盖图层、视口、active / archived 生成对话框和选中态的撤销 / 重做恢复,避免后续把 history 逻辑继续埋回主视图。
## 第八阶段模块
- `useImageCanvasProjectPersistence.ts`
- 承载图片画布工程持久化协调:项目加载、`projectId` 维护、未就绪资源队列、工程资源创建、资源创建后即时 layout 保存、450ms 自动保存和鉴权失败登录弹窗。
- 该 hook 以“项目持久化协调器”整体抽出,避免把加载、保存和资源创建拆成多个小 hook 后打散 `projectIdRef`、`pendingProjectResourceLayersRef`、`isProjectReady` 和 `saveTimerRef` 的时序约束。
- 主视图继续负责项目重命名 UI、素材库管理、上传流程和用户动作触发;新增图层仍通过 `appendCanvasLayersWithResources` 先写本地图层快照,再创建 project resource 并保存带真实 `resourceId` 的 layout。
- 项目加载 hydrate 时必须从 project resource 回填 `objectKey`、`assetObjectId`、`sourceResourceId` 等资源元数据;layout 快照保持轻量,但重进项目后的画布图层仍要保留换签和后续编辑所需线索。
## 第九阶段模块
- `useCanvasGenerationDialogs.ts`
- 承载画布生成 dialog 注册表:active / inactive dialog 状态、id 分配、归档、激活、按 id 更新 / 删除、按关联图层清理,以及生成中异步回写时获取最新占位框。
- 该 hook 只处理 `generate/spec/character/icon` 这类画布生成对象,继续保留 `GenerateDialogState.mode === 'edit'` 的直接 `setGenerateDialog` 能力用于低风险迁移;真实生成请求、quick edit、角色动画、结果落图和资源持久化仍留在主视图。
- 主视图通过 `onActivate` 处理激活生成对象后的清空图层选择和关闭图片菜单等跨状态副作用,避免 dialog hook 反向依赖画布图层状态。
## 第十阶段模块
- `useImageCanvasAssetLibrary.ts`
- 承载账号级素材库状态模型:素材文件夹、素材列表、文件夹折叠 / 新建 / 重命名 / 删除、素材重命名 / 删除、素材选择模式、框选、多选删除、素材拖到文件夹和鉴权失败登录弹窗。
- 主视图继续保留上传文件读取、上传占位卡片进度、拖到画布坐标、创建画布图层、工程资源持久化和画布图层清理;素材删除通过 `onDeleteAssets` 回调通知主视图清理关联图层。
- 该 hook 有独立单测覆盖素材库加载归一化、401 登录、新建文件夹临时 id 替换、素材移动、删除回调和多选删除,避免后续整理侧栏 JSX 时丢失素材库能力。
## 第十一阶段模块
- `ImageCanvasFileModel.ts`
- 承载图片文件判定和 `FileReader` Data URL 读取工具,供素材上传、生成参考图上传和后续导入能力复用。
- 该模块不依赖素材库状态,避免把通用文件读取继续挂在素材库 hook 上。
- `useImageCanvasUploadWorkflow.ts`
- 承载图片画布上传工作流:隐藏文件 input、上传目标分发、未登录拦截和登录后续传、上传占位卡片、文件读取、素材落库、拖到画布建层、选中新图层、打开图层侧栏,以及角色 / 图标生成参考图上传。
- 主视图继续负责画布 drop 外层事件判断、素材库已有素材加入画布、项目资源持久化 hook 注入和画布历史捕获,避免上传 hook 反向成为画布全局状态真相。
- 该 hook 用独立单测覆盖登录续传、上传占位 / 成功回写、上传到画布建层、鉴权失败和生成参考图分发,主视图保留 DOM 级 smoke 覆盖侧栏上传、画布 drop 上传和文件夹定向上传。
- 普通素材上传入口必须在打开系统文件选择器前检查登录态;未登录时先弹 `账号入口`,登录后由用户再次点击上传入口打开选择器,避免浏览器拦截异步触发的系统文件选择器。角色 / 图标生成参考图只作为本地引用进入生成表单,不强制登录。
## 第十二阶段模块
- `ImageCanvasGenerationLayerModel.ts`
- 承载生成结果落画布的纯数据规则:普通生图、修改图片、快速编辑和图标素材批量结果如何生成图层 id、临时 resourceId、标题、位置、原始分辨率尺寸、zIndex、source metadata、`assetKind`、源图关联和 `generationInputs`。
- 主视图继续负责生成提交 API、生成对象 active / archived 状态、资源持久化、图层选择、侧栏切换、对话框收起和适合视图等副作用,避免把多个画布生成对象的生命周期拆成浅 wrapper。
- 该模块用独立单测锁定“图片显示尺寸跟随原始 Resolution”“生成占位框只作为定位参考”“图标素材沿用当前行宽换行规则”和“快速编辑保留源图分组 / 类型”的规则。
## 第十三阶段模块
- `useImageCanvasAssetExportWorkflow.ts`
- 承载画布素材导出工作流:导出状态、空画布提示、单图右键导出、整包 ZIP 组包、图片去重、读取失败记录、`metadata.json`、`manifest.txt`、下载链接创建和浏览器不支持时的错误提示。
- 主视图继续负责右键菜单目标解析、下载按钮渲染和状态提示展示;导出 hook 不接管图层选择、右键菜单生命周期或画布状态。
- 该 hook 用独立单测覆盖 ZIP 内容、重复图层复用同一图片文件、失败图片 metadata、manifest 失败数量、空画布提示和单图文件名清理。
## 第十四阶段模块
- `useImageCanvasLayerCommands.ts`
- 承载图层命令工作流:画布剪贴板、右键目标解析、复制 / 剪切 / 粘贴、创建副本、层级移动、分组 / 解组、显示 / 隐藏、锁定 / 解锁、翻转、删除选中图层、按 id 删除图层和单图导出委托。
- 主视图继续负责右键菜单定位、画布事件、生成提交、素材上传、项目持久化和实际下载实现;hook 只接收必要 setter 与副作用回调,不反向读取 DOM 或路由。
- 该 hook 用独立单测覆盖菜单关闭、历史捕获、选中态更新、删除副作用、剪贴板和导出委托,避免主视图后续继续堆积右键菜单 orchestration。
## 第十五阶段模块
- `useImageCanvasGenerationWorkflow.ts`
- 承载图片画布生成工作流:打开普通生图 / 规范 / 角色 / 图标生成对象、打开修改图片 / 快速编辑 / 角色动画面板、选择画布规范参考、提交真实生成 API、失败恢复、生成结果落图、侧栏切换、适合视图和删除图层后的生成态清理。
- 主视图继续负责画布事件、生成对话框定位样式、占位框拖拽、DOM 渲染、上传入口、工程资源持久化和历史捕获;生成 hook 只接收状态 setter 与落图 / 选中 / 适合视图回调。
- 该 hook 用独立单测覆盖打开生成占位、普通生图落图、快速编辑落图并适配源图、删除源图时清理 quick edit / edit dialog、角色动画入口过滤和隐藏生成面板不删除占位框,避免后续拆分再次导致生成工具或底部工具栏状态回退。
## 第十六阶段模块
- `useImageCanvasEditorChrome.ts`
- 承载编辑器 chrome 状态:项目标题 / 重命名、左侧栏开关、当前工具、缩放菜单、背景设置面板、小地图开关、画布背景色和 HEX 输入。
- 主视图继续负责真正跨工作流的动作编排,例如上传工具触发上传工作流、生成工具触发生成工作流、项目加载后注入标题、键盘 Escape 同时关闭生成 / 快速编辑 / 图片菜单等非 chrome 面板。
- 该 hook 用独立单测覆盖项目重命名、鉴权失败登录、背景色合法 / 非法 HEX、侧栏切换、缩放 / 背景面板关闭、小地图和工具状态,避免后续改顶部栏或左下 dock 时把这些状态重新散回主视图。
## 第十七阶段模块
- `useImageCanvasViewportControls.ts`
- 承载画布视口控制:`viewport`、`canvasSize`、小地图投影、适合视图、中心缩放、普通滚轮纵向滚动、Ctrl / Cmd 滚轮缩放、屏幕点到画布 / 世界坐标换算和小地图点击 / 拖拽移动视图。
- 主视图继续负责图层拖拽、生成占位框拖拽、框选、多选、历史触发时机、上传 drop 分流和小地图 pointer down 事件;该 hook 只作为视口控制协调器,不接管画布完整 pointer 状态机。
- 该 hook 用独立单测覆盖尺寸同步、适合视图、中心缩放、坐标换算、滚轮语义和小地图移动,为后续抽 `useImageCanvasStageInteractions` 预留更清晰的视口接口。
## 第十八阶段模块
- `useImageCanvasStageInteractions.ts`
- 承载画布舞台 pointer 状态机:选择 / 框选、多选图层拖拽、生成占位框拖拽、抓手 / Space 临时抓手 / 中键平移、小地图 click / drag 分流和吸附线状态。
- 主视图继续保留原生文件 / 素材 drop、右键菜单定位、上传工作流、生成提交、项目持久化和工具栏动作分流;舞台 hook 只接收这些能力需要的回调,不反向读取路由、API 或素材库状态。
- 该 hook 用独立单测覆盖多选拖拽、框选、临时抓手、生成占位拖拽和小地图 click / drag 分流;主视图 DOM 测试继续覆盖真实组件路径和历史上容易回退的浏览器级交互。
## 第十九阶段模块
- `useImageCanvasCanvasDropWorkflow.ts`
- 承载画布区域 drag over / leave / drop 分流:识别素材库拖拽 MIME、本地文件拖拽、画布遮罩状态、默认文件夹选择、素材入画布和文件上传到画布参数组装。
- 主视图继续提供已有能力注入:账号级素材列表、默认素材文件夹、屏幕点转画布 drop 点、素材建层、文件上传、素材移动高亮清理;drop hook 不直接创建资源、不访问 API,也不读取项目持久化状态。
- 该 hook 用独立单测覆盖素材库图片拖入画布、文件拖入画布、无关拖拽不拦截和 drag leave 清理遮罩;主视图集成测试继续覆盖真实 DOM 中的“素材库拖到画布”和“文件拖到画布”路径。
## 第二十阶段模块
- `ImageCanvasMetadataModalView.tsx`
- 承载图片信息弹窗渲染:图片类型、生成输入字段、参考图、模型、分辨率、Provider、Task 和 Object 信息。
- 主视图只保留 `metadataLayer` 状态和打开 / 关闭动作;图片信息的视觉结构、fallback 文案和 `UnifiedModal` 接入不再散在主视图底部。
- 该组件用独立单测覆盖生成图 metadata、上传图 fallback、参考图渲染和关闭回调;主视图集成测试继续覆盖从画布图层点击右上角信息按钮打开弹窗。
- 本阶段同步把项目 / 素材初始加载挂到 `AuthGate` 的受保护数据可访问状态之后;未登录进入编辑器只拉起 `账号入口`,不再抢跑 `/api/editor/*` 造成素材或工程读取 401 噪声。
- `重置画布视图` 按钮必须显式调用 `onFitLayers()`,不能把 React click event 作为目标图层数组透传给适合视图逻辑。
## 第二十一阶段模块
- `useImageCanvasKeyboardShortcuts.ts`
- 承载图片画布全局键盘快捷键:Windows 下支持 Ctrl+Z / Ctrl+Shift+Z 撤销重做、Ctrl+A/C/V/X/D 全选 / 复制 / 粘贴 / 剪切 / 复制一份、Ctrl+0/1/+/- 视图控制、V/H/U/G/Shift+V/M 工具切换、Alt+1/Alt+2/Alt+M 面板切换、Ctrl+] / Ctrl+[ / Ctrl+Shift+] / Ctrl+Shift+[ 层级调整、方向键微移、Backspace / Delete 删除选中元素、Escape 关闭临时面板并取消选择,以及 Space 临时抓手。
- 主视图继续保留各工作流状态和具体副作用,例如图层删除、生成对话框、规格菜单、快速编辑面板和 chrome 面板状态;快捷键 hook 只接收 ref、setter 与回调,不直接读写素材库、路由或 API。
- 该 hook 用独立单测覆盖输入框忽略快捷键、撤销重做、编辑命令、视图 / 层级命令、工具 / 面板命令、方向键移动、选中元素删除、生成占位删除、Escape 保留生成中面板、Space 临时抓手和 Shift 状态;主视图 DOM 测试继续覆盖真实编辑器里的 Backspace、Escape、Space 和 undo / redo 集成路径。
- `ImageCanvasShortcutDialogView.tsx` / `ImageCanvasShortcutModel.ts`
- 承载右上角快捷键入口打开后的独立弹窗和 Windows 快捷键表;快捷键文案、键帽和“新增”状态集中在模型中维护,避免 UI 表格和 hook 行为漂移。
## 第二十二阶段模块
- `useImageCanvasAssetPointerDragBridge.ts`
- 承载素材库卡片 pointer 拖拽桥接:全局 `pointermove` / `pointerup` / `pointercancel` 监听、拖拽激活阈值、画布 drop 提示、文件夹移动高亮、拖到文件夹移动素材、拖到画布创建图层,以及拖拽结束后的点击抑制。
- 主视图继续保留素材库事实、画布建层、历史捕获、工程资源持久化和素材移动 API 编排;该 hook 只作为“侧栏素材拖拽到画布或文件夹”的事件桥,不直接读写路由、API 或图层持久化。
- 该 hook 用独立单测覆盖拖拽激活、文件夹 drop、画布 drop、非激活拖拽清理和 pointer cancel 完成路径;主视图 DOM 测试继续覆盖真实素材库拖到画布和拖到文件夹的集成链路。
- 本阶段同步恢复认证弹窗使用 portal 渲染,避免 `/editor/canvas` 这类全屏画布内容把 `账号入口` 遮住;同时把画布背景设置面板调整为独立白色浮层,包含当前色、色域、色相、自定义颜色、预设网格、HEX 输入和恢复默认。
## 第二十三阶段模块
- `ImageCanvasOverlayModel.ts`
- 承载画布浮层定位纯规则:生成输入框锚定、图标素材生成面板横向扩宽、选中图片工具栏边界限制、快速编辑面板定位和角色动画面板定位。
- 主视图继续负责生成对象、quick edit、角色动画、视口和图层状态编排,只把可纯函数验证的屏幕坐标计算移出,避免后续调整 Lovart 式浮层时继续在主视图里散落坐标公式。
- 该模块用独立单测覆盖生成结果优先锚定、生成中 / 手动关闭隐藏输入框、icon 描述项宽度、图片工具栏 clamp、quick edit 和角色动画面板边界,以及画布生成 dialog 模式识别。
- 本阶段主视图从 1182 行降至 1133 行;下一步若继续拆分,可在该模型基础上抽更大的 `useImageCanvasStageController`,承接舞台派生状态、右键菜单和工具切换胶水。
## 第二十四阶段模块
- `useImageCanvasAssetCanvasBridge.ts`
- 承载素材库与画布之间的桥接工作流:删除素材时清理关联画布图层、素材加入画布创建图层、素材 pointer 拖入画布 / 文件夹、画布区域 drag over / leave / drop 分流,以及拖拽文件上传到画布的参数组装。
- 主视图继续掌握素材库事实、上传文件读取、工程资源持久化、历史捕获触发时机和实际 API 副作用;该 hook 只负责把已有素材 / 文件 drop 转成画布动作,不反向读取路由、登录态或项目数据。
- 该 hook 用独立单测覆盖素材建层、pointer drop 入画布和删除素材清理关联图层 / 选中态;原有 pointer drag 和 canvas drop hook 单测继续保留,主视图 DOM 测试继续覆盖真实素材库拖入画布路径。
- 本阶段主视图从 1133 行降至 1086 行;下一步可继续抽顶栏视图或更高内聚的舞台控制层,但应优先选择能收敛真实状态规则的深边界。
## 第二十五阶段模块
- `ImageCanvasStageControllerModel.ts`
- 承载舞台派生状态和右键菜单模型:选中图层、选中浮动工具栏位置、图片菜单图层、右键菜单目标图层,以及显示 / 解锁菜单文案判断。
- 该模型复用既有图层命令模型与浮层定位模型,不重新实现右键目标和选中工具栏坐标规则;生成 Composer 锚点不属于舞台控制器,后续由生成表面编排统一负责。
- 新增单测覆盖选中工具栏位置、右键目标集合、显示 / 解锁判断和菜单位置限制。
- `useImageCanvasStageController.ts`
- 承载舞台控制胶水:清空画布焦点、空白画布右键菜单和图层右键菜单处理。
- 主视图继续保留工具切换、上传 / 生成入口、图层命令、项目持久化和舞台 pointer 状态机,避免把跨工作流动作塞进单个 hook。
- 本阶段主视图从 1086 行降至 993 行;后续若继续拆分,应优先考虑顶栏 / 项目标题区域或把侧栏图层右键入口并入同一舞台菜单控制层。
## 第二十六阶段模块
- `ImageCanvasTopbarView.tsx`
- 承载编辑器顶部栏视觉结构:返回项目入口、项目标题展示、项目标题重命名表单、下载画布素材按钮和导出状态提示。
- 主视图继续保留项目标题 / 重命名状态所属的 chrome hook、项目持久化、导出工作流和实际导出副作用;顶栏只负责表单事件和显示,不接管业务状态。
- 新增组件单测覆盖返回入口、标题编辑入口、重命名提交 / 取消、导出按钮禁用 / 启用和导出状态提示。
- 本阶段主视图从 993 行降至 905 行;后续可继续评估隐藏上传 input / 侧栏拖拽预览这类根级浮层是否值得抽为编辑器 shell 视图。
## 第二十七阶段模块
- `useImageCanvasGenerationSurface.tsx`
- 承载 Lovart 式生成表面编排:组合 `useImageCanvasGenerationWorkflow`,统一计算普通生图、图标生成、快速编辑和角色动画的浮层位置,并构建 `ImageCanvasGenerationComposerView` 节点。
- `useImageCanvasGenerationWorkflow` 继续作为生成状态机和真实 API 提交入口;生成表面 hook 只收口 Composer JSX、工具切换分流和浮层定位,避免主视图继续维护大段生成 props 胶水。
- 本阶段同步把生成 Composer 锚点从 `ImageCanvasStageControllerModel` 移出,避免舞台控制器和生成表面各自计算一套生成浮层状态。
- 新增 hook 单测覆盖生成工具切换、Composer 位置、关闭生成输入框和规范菜单;主视图从 905 行降至 793 行。
## 后续阶段
- 后续可继续选择更高内聚的交互 workflow 或持久化边界,不再把生成链路继续拆成浅层 wrapper。
- 工程资源持久化、工具切换和历史捕获仍在主视图编排,拆分前需要先确认不会破坏多生成对象同时存在、完成时读取最新占位框、素材拖拽上传位置和角色动画优先传 `objectKey` 的历史保护规则。
## 验证计划
- `npm run test -- src/components/image-editor/useImageCanvasGenerationSurface.test.tsx src/components/image-editor/ImageCanvasStageControllerModel.test.ts src/components/image-editor/useImageCanvasStageController.test.tsx src/components/image-editor/useImageCanvasGenerationWorkflow.test.tsx src/components/image-editor/ImageCanvasEditorView.test.tsx`
- `npm run typecheck`
- `npm run check:encoding`
- `git diff --check`
- 浏览器回归 `/editor/canvas`:确认登录弹窗、素材上传、背景设置面板、底部工具栏、Space 临时抓手、撤销 / 重做和画布基础渲染仍正常。

Some files were not shown because too many files have changed in this diff Show More