Prune stale docs and update .hermes content

Delete a large set of outdated documentation (many files under docs/ and .hermes/plans/, including audits, design, prd, technical, planning, assets, and todos). Update and consolidate .hermes content: refresh shared-memory pages (decision-log, development-workflow, document-map, pitfalls, project-overview, team-conventions) and several skills/references under .hermes/skills. Also modify AGENTS.md, README.md, UI_CODING_STANDARD.md, docs/README.md and .encoding-check-ignore. Purpose: clean up stale planning/audit material and keep current hermes documentation and related top-level docs in sync.
This commit is contained in:
2026-05-15 06:24:07 +08:00
parent 2eded08bc7
commit 3cb3efb4d0
708 changed files with 4033 additions and 142328 deletions

View File

@@ -10,6 +10,7 @@
- 开发前先阅读本目录下与任务相关的记忆文件;开发后如产生稳定知识,更新对应文档。
- 后续新增的 Markdown 文档文件名必须以分类标签开头,格式为 `【标签名】中文标题-日期.md`,便于团队跨目录检索。
- 若本目录内容与 `docs/` 或代码事实冲突,以当前代码和最新 `docs/` 为准,并同步修正过期记忆。
- 阶段性计划、一次性 TODO 和已关闭实验不再长期沉淀为仓库文档;仍有效内容应合并进 `docs/` 当前融合文档或 `.hermes/shared-memory/`
## 目录结构
@@ -24,8 +25,6 @@
│ ├─ decision-log.md # 长期决策记录
│ ├─ pitfalls.md # 踩坑与排障记录
│ └─ handoff-template.md # 任务交接模板
├─ plans/ # 阶段性计划与实施方案
├─ todos/ # 已定稿但尚未执行的共享 TODO 计划
├─ skills/ # 仓库级 Hermes skills
└─ plugins/ # 仓库级 Hermes plugins需显式启用项目 plugin
```

View File

@@ -1,724 +0,0 @@
# 埋点系统新增周、月、季、年维度映射表计划
## 目标
在 Genarrative 的埋点/统计系统中新增“周、月、季、年”维度映射表,让后续统计查询可以按不同时间粒度稳定聚合,而不是只依赖运行时临时计算日期范围。
本计划只做设计与落地步骤,不直接修改业务代码。
## 当前上下文与初步发现
1. 当前仓库根目录为 `/home/dsk/workspace/Genarrative`
2. 远端更新后已定位到当前真实埋点/任务系统:
- 原始埋点表:`tracking_event`
- 日聚合投影表:`tracking_daily_stat`
- 任务配置表:`profile_task_config`
- 任务进度表:`profile_task_progress`
- 领奖记录表:`profile_task_reward_claim`
3. 相关文件:
- `server-rs/crates/spacetime-module/src/runtime/profile.rs`
- `server-rs/crates/module-runtime/src/domain.rs`
- `server-rs/crates/module-runtime/src/application.rs`
- `server-rs/crates/api-server/src/runtime_profile.rs`
- `apps/admin-web/src/pages/AdminTaskConfigPage.tsx`
- `apps/admin-web/src/api/adminApiTypes.ts`
- `apps/admin-web/src/config/trackingEventDefinitions.ts`
- `docs/technical/PROFILE_TASK_AND_TRACKING_SYSTEM_2026-05-03.md`
- `docs/tracking/TRACKING_QUERY_PLAYBOOK_2026-05-03.md`
4. 当前 `tracking_event` 是明细表,`tracking_daily_stat` 是统一日汇总表,不按范围拆表;它通过 `event_key + scope_kind + scope_id + day_key` 区分不同聚合桶。
5. 当前任务系统是“个人任务系统”,首版任务配置均面向用户维度;后台暴露“埋点范围”选择会导致运营误配。
6. 已决定采用任务配置方案 B
- 后台任务配置不再让运营手动选择埋点范围。
- 后端个人任务配置统一限制为 `RuntimeTrackingScopeKind::User`
- 若 API 收到非 `user``scopeKind`,应拒绝或兼容忽略但最终落库必须为 `User`;推荐直接拒绝并返回清晰错误。
7. 已发现一个需要纳入本计划修复的语义问题:`profile_task_tracking_scope_id``RuntimeTrackingScopeKind::Work` 当前返回 `user_id`,这不符合 work 维度语义。即使个人任务暂不支持 work也应避免错误映射继续存在。
8. 当前日期桶使用北京时间自然日:`day_key = floor((occurred_at_micros + 8h) / 1d)`;周/月/季/年映射表应优先沿用这一业务日口径,除非产品另行确认。
9. 如果新增正式后端表,需要同步:
- 表定义
- reducer/procedure
- migration.rs
- 生成绑定
- spacetime-client facade
- shared-contracts / API DTO如有接口暴露
## 关键假设
在未定位现有埋点模块前,先按以下假设规划:
1. 当前已有某种事件明细表或统计事实表,例如:
- `telemetry_event`
- `analytics_event`
- `metric_event`
- `narrative_telemetry`
- 或类似命名
2. 新增的“映射表”用于把具体日期或事件时间映射到时间维度 bucket。
3. 映射维度包括:
-week
-month
-quarter
-year
4. 已明确选择“一张通用日期维度映射表”方案:`analytics_date_dimension`
5. 统计口径需要明确:
- 周从周一还是周日开始
- 是否使用 ISO week
- 季度是自然季度还是财务季度
- 时区使用 UTC 还是业务本地时区
## 推荐设计方向
### 方案 A单一时间维度映射表推荐
新增一张日历维度表,每一行对应一个自然日,并包含它归属的周、月、季、年。
表概念:
```text
analytics_date_dimension
```
建议字段:
```text
date_key string 例如 2026-05-04
calendar_date string 真实日期,按 YYYY-MM-DD 存储
weekday u8 1-7 或 0-6需要统一约定
iso_week_key string 例如 2026-W19
week_start_date_key string 例如 2026-05-04
week_end_date_key string 例如 2026-05-10
month_key string 例如 2026-05
month_start_date_key string
month_end_date_key string
quarter_key string 例如 2026-Q2
year_key string 例如 2026
created_at timestamp/string
updated_at timestamp/string
```
优点:
- 一张表即可支持日、周、月、季、年映射。
- 便于后续新增半月、财年、节假日、自然周等维度。
- 查询逻辑简单:事件日期 join/date_key 映射到目标粒度。
- 数据量很小,按 20 年也只有约 7300 行。
缺点:
- 需要在事件时间写入或统计查询时把 timestamp 归一为 date_key。
- 如果要支持多时区,可能需要增加 timezone 字段或多套 calendar。
### 方案 B四张独立映射表
分别新增:
```text
analytics_week_dimension
analytics_month_dimension
analytics_quarter_dimension
analytics_year_dimension
```
优点:
- 每个粒度表结构更纯粹。
- 查询时可以直接针对目标粒度表。
缺点:
- 表更多,维护复杂。
- 日期归属关系仍然需要额外处理。
- 容易出现周/月/季/年口径漂移。
### 最终选择
本计划采用方案 A单一 `analytics_date_dimension` 日期维表,而不是四张独立映射表。
如业务未来明确要求“周、月、季、年各自有独立映射表”,也应优先在日期维表基础上派生视图或物化派生表,而不是一开始拆成四张重复表。
## 后端设计建议
### 1. 明确埋点领域归属
先定位现有埋点模块。如果没有独立模块,建议新增或归入:
```text
server-rs/crates/module-analytics/
```
或如果当前项目已有 telemetry 命名,则保持已有命名,例如:
```text
server-rs/crates/module-telemetry/
```
领域层职责:
- 时间粒度定义
- date_key/week_key/month_key/quarter_key/year_key 生成规则
- 时间维度校验
- 事件聚合查询输入的纯规则
不应包含:
- SpacetimeDB 表读写
- Axum handler
- HTTP response
### 2. SpacetimeDB 表设计
`spacetime-module` 中新增时间维度表。
建议表名:
```text
analytics_date_dimension
```
建议主键:
```text
date_key
```
建议索引:
```text
iso_week_key
month_key
quarter_key
year_key
```
如果 SpacetimeDB 表定义已有统一命名规范,应按现有规范命名。
### 3. 初始化/补全 reducer
新增 reducer 或内部 procedure用于生成指定日期范围内的维度数据。
建议能力:
```text
seed_analytics_date_dimensions(start_date, end_date)
ensure_analytics_date_dimension_for_date(date_key)
ensure_analytics_date_dimensions_for_range(start_date, end_date)
```
设计原则:
- 可幂等执行。
- 已存在 date_key 时不重复插入。
- 支持一次补一段日期。
- 避免一次补太大范围导致事务过重。
- 生产环境建议按年份或月份分批。
### 4. 事件表和映射表关系
如果事件表目前只有 timestamp建议新增或计算出
```text
event_date_key
```
可选策略:
1. 写入事件时同步写 `event_date_key`
2. 查询统计时从 timestamp 临时计算 date_key。
3. 后台迁移为历史事件补 `event_date_key`
推荐:
- 新事件写入时保存 `event_date_key`
- 历史事件通过批量迁移 reducer 分批补齐。
### 5. 聚合查询设计
支持按粒度查询时API 或 facade 可以接收:
```text
granularity = day | week | month | quarter | year
start_date
end_date
metric/event_name
filters
```
内部根据粒度选择 bucket key
```text
day -> date_key
week -> iso_week_key 或 week_key
month -> month_key
quarter -> quarter_key
year -> year_key
```
返回结构建议统一:
```text
bucket_key
bucket_label
bucket_start_date
bucket_end_date
value
```
## 可能涉及的文件
由于当前尚未定位明确埋点模块,以下是预计文件范围。
### 必查文件/目录
```text
./Genarrative/server-rs/crates/
./Genarrative/server-rs/crates/spacetime-module/src/
./Genarrative/server-rs/crates/spacetime-client/src/
./Genarrative/server-rs/crates/shared-contracts/src/
./Genarrative/server-rs/crates/api-server/src/
./Genarrative/packages/shared/src/contracts/
./Genarrative/src/services/
```
### 可能新增文件
如果采用 analytics 命名:
```text
server-rs/crates/module-analytics/src/domain.rs
server-rs/crates/module-analytics/src/commands.rs
server-rs/crates/module-analytics/src/application.rs
server-rs/crates/module-analytics/src/errors.rs
server-rs/crates/module-analytics/src/events.rs
server-rs/crates/shared-contracts/src/analytics.rs
server-rs/crates/spacetime-client/src/analytics.rs
server-rs/crates/api-server/src/analytics.rs
packages/shared/src/contracts/analytics.ts
```
如果只是新增 SpacetimeDB 映射表且暂不暴露 API则可能只需
```text
server-rs/crates/spacetime-module/src/**
server-rs/crates/spacetime-module/src/migration.rs
server-rs/crates/spacetime-client/src/** # 如果查询会被 api-server 使用
```
## 详细实施步骤
### Step 1复核现有埋点系统与任务配置链路
当前已定位真实链路,实施前再做一次只读复核,确认远端最新代码没有继续变化。
已知核心表:
```text
tracking_event # 原始埋点明细
tracking_daily_stat # 日聚合投影
profile_task_config # 个人任务配置
profile_task_progress # 个人任务进度
profile_task_reward_claim # 领奖记录
```
已知核心文件:
```text
server-rs/crates/spacetime-module/src/runtime/profile.rs
server-rs/crates/module-runtime/src/domain.rs
server-rs/crates/module-runtime/src/application.rs
server-rs/crates/api-server/src/runtime_profile.rs
apps/admin-web/src/pages/AdminTaskConfigPage.tsx
apps/admin-web/src/api/adminApiTypes.ts
apps/admin-web/src/config/trackingEventDefinitions.ts
docs/technical/PROFILE_TASK_AND_TRACKING_SYSTEM_2026-05-03.md
docs/tracking/TRACKING_QUERY_PLAYBOOK_2026-05-03.md
```
重点确认:
1. `tracking_event` 是否仍包含 `event_key/scope_kind/scope_id/day_key/user_id/occurred_at`
2. `tracking_daily_stat` 是否仍按 `event_key + scope_kind + scope_id + day_key` 生成 `stat_id`
3. `profile_task_config` 是否仍包含 `scope_kind``sort_order`
4. 后台 `AdminTaskConfigPage` 是否仍暴露“埋点范围”下拉。
5. `profile_task_tracking_scope_id``Work => user_id` 的错误映射是否仍存在。
### Step 1.5:先收紧个人任务配置的埋点范围,采用方案 B
在做周/月/季/年维度映射前,先修正个人任务配置边界,避免后续在错误配置模型上继续扩展。
目标行为:
```text
个人任务配置只支持用户维度埋点。
后台页面不再展示“埋点范围”。
后端不允许 profile_task_config 被写入 site/work/module 维度。
```
建议实现:
1. 前端隐藏 `AdminTaskConfigPage` 的“埋点范围”选择。
- 文件:`apps/admin-web/src/pages/AdminTaskConfigPage.tsx`
- 移除或隐藏:`scopeKinds` 下拉 UI。
- 保存请求仍可兼容传 `scopeKind: 'user'`,避免一次性改动 API contract。
2. 后端 upsert 校验 `scopeKind` 必须为 `RuntimeTrackingScopeKind::User`
- 文件:`server-rs/crates/api-server/src/runtime_profile.rs`
- 或更底层:`server-rs/crates/module-runtime/src/domain.rs` / `server-rs/crates/spacetime-module/src/runtime/profile.rs` 的输入构造函数。
- 推荐在领域输入构造处兜底校验API 层返回清晰错误。
3. 若暂不改 API DTO则保持字段存在但限定值只能是 `user`
- 文件:`apps/admin-web/src/api/adminApiTypes.ts`
- `AdminUpsertProfileTaskConfigRequest.scopeKind` 可保留,前端固定传 `user`
4. 更新后台埋点定义注册表的语义:
- 文件:`apps/admin-web/src/config/trackingEventDefinitions.ts`
- 当前每个 event definition 包含 `scopeKind`,如果个人任务统一 `user`,可以保留为只读内部默认值;但不要让运营在页面改。
5. 更新技术文档:
- `docs/technical/PROFILE_TASK_AND_TRACKING_SYSTEM_2026-05-03.md`
- 明确“个人任务首版只支持用户维度埋点,后台不开放 scope_kind 配置”。
验收:
```text
后台任务配置页不再出现“埋点范围”选择。
保存 daily_login 后落库 scope_kind 仍为 User。
直接调用后台 upsert 接口传 site/work/module 时被拒绝,或最终不会落库为非 User推荐拒绝。
```
### Step 1.6:修复 Work 范围错误返回 user_id 的语义问题
当前函数:
```text
server-rs/crates/spacetime-module/src/runtime/profile.rs
profile_task_tracking_scope_id(user_id, config)
```
当前问题:
```rust
RuntimeTrackingScopeKind::Work => user_id.to_string()
```
这会把 work 维度错误映射为用户 ID。虽然个人任务将限制为 User但保留这个分支会误导后续扩展。
推荐修复策略:
1. 对个人任务进度计算来说,`Work` 不应进入该函数。
2.`profile_task_tracking_scope_id` 改为返回 `Result<String, String>``Option<String>`
3. 对不支持的范围返回错误,而不是伪造 scope_id
```text
Site -> "site",如果个人任务仍不允许 site则上游先拒绝
Module -> "profile",如果个人任务仍不允许 module则上游先拒绝
User -> user_id
Work -> error: personal task progress does not support work scope without work_id
```
更严格的推荐:
```text
个人任务链路只接受 User。
Work/Site/Module 在 profile_task_progress_count 前就被拒绝。
profile_task_tracking_scope_id 只保留 User 分支,或者非 User 返回错误。
```
需要同步调整调用点:
```text
profile_task_progress_count
refresh_profile_task_progress
build_profile_task_center_snapshot
claim_profile_task_reward
```
避免因为函数返回 `Result` 后调用链未处理错误。
验收:
```text
不存在 Work => user_id 的映射。
个人任务配置非 User 时不会静默算出错误进度。
相关测试覆盖User 正常Work/Site/Module 被拒绝。
```
### Step 2确定时间口径
必须先确认:
1. 周维度是否使用 ISO week。
2. 周开始日是周一还是周日。
3. 月/季/年是否按自然日历。
4. 统计时区是 UTC、服务器时区还是用户本地时区。
5. 跨年周如何命名,例如 `2025-W01` 可能开始于 2024 年末。
推荐默认:
```text
时区UTC除非产品明确要求中国时区
ISO week周一开始
月:自然月
季:自然季度
年:自然年
```
如果业务面向国内用户,建议考虑:
```text
时区Asia/Shanghai
周:周一开始
```
### Step 3设计 date dimension 表
设计字段和 key 格式,写入技术文档。
建议 key 格式:
```text
date_key: 2026-05-04
week_key: 2026-W19
month_key: 2026-05
quarter_key: 2026-Q2
year_key: 2026
```
注意:
- `week_key` 建议使用 ISO week-year不一定等于 calendar year。
- `quarter_key` 使用 calendar year。
### Step 4新增领域纯函数
在领域层或 shared-kernel 中实现纯函数:
```text
resolve_date_dimension(date, timezone) -> AnalyticsDateDimension
resolve_bucket_key(date_dimension, granularity) -> String
resolve_bucket_range(bucket_key, granularity) -> start/end date
```
要求:
- 有单元测试。
- 覆盖跨年周。
- 覆盖闰年 2 月。
- 覆盖季度边界。
### Step 5新增 SpacetimeDB 表
`spacetime-module` 中新增表。
遵守约束:
- 新增表通常安全。
- 不修改已有表字段。
- 如果必须给已有事件表加 `event_date_key`,必须加在表定义末尾并提供 default。
- 若要补历史数据,使用 reducer 分批迁移。
### Step 6新增 seed/ensure reducer
新增幂等 reducer
```text
seed_analytics_date_dimensions(start_date, end_date)
ensure_analytics_date_dimension_for_date(date_key)
```
验证点:
- 重复执行不会重复插入。
- 日期范围非法时返回稳定错误。
- 单次范围过大时拒绝或分页。
### Step 7接入事件写入链路
如果现有事件写入链路存在,新增:
```text
event_date_key
```
策略:
- 新事件写入时同步计算并保存。
- 写入前确保对应 date dimension 存在。
- 历史事件通过迁移 reducer 补齐。
如果暂不改事件表,也可以在查询阶段临时映射,但性能和一致性较差。
### Step 8接入聚合查询
如已有统计接口,扩展请求参数:
```text
granularity: day | week | month | quarter | year
```
查询逻辑改为:
```text
事件/事实表
→ event_date_key
→ analytics_date_dimension
→ 取对应 bucket key
→ group by bucket key
```
返回 bucket 时包含:
```text
bucket_key
bucket_start_date
bucket_end_date
value
```
### Step 9补 shared contracts 和前端 contracts
如果有 API 暴露,需要补:
```text
server-rs/crates/shared-contracts/src/analytics.rs
packages/shared/src/contracts/analytics.ts
```
建议 DTO
```text
AnalyticsGranularity = day | week | month | quarter | year
AnalyticsBucketMetric
AnalyticsMetricQueryRequest
AnalyticsMetricQueryResponse
```
### Step 10补测试
测试范围:
1. 领域日期映射测试
2. SpacetimeDB reducer 幂等测试
3. API 查询维度测试
4. 历史事件迁移测试,如涉及
5. 跨边界日期测试
6. 个人任务配置 scope 限制测试
7. `Work => user_id` 错误映射回归测试
重点用例:
```text
2024-02-29 闰年
2025-12-29 ISO week 可能属于 2026-W01
2026-01-01 跨年周
2026-03-31 Q1 结束
2026-04-01 Q2 开始
2026-12-31 年末
```
任务配置重点用例:
```text
admin upsert daily_login + scopeKind=user -> 成功
admin upsert daily_login + scopeKind=site -> 失败,错误信息说明个人任务仅支持 user
admin upsert daily_login + scopeKind=module -> 失败
admin upsert daily_login + scopeKind=work -> 失败
任务中心读取 daily_login -> 按 User + 当前 user_id 查询进度
代码中不存在 Work => user_id 的静默映射
```
## 测试与验证命令
具体命令需在定位模块后确认。初步建议:
```text
npm run typecheck
npm test
```
后端如涉及 Rust
```text
cargo test -p module-analytics
cargo test -p spacetime-module
cargo test -p api-server
```
涉及 API smoke
```text
npm run api-server
```
然后验证:
```text
GET /healthz
```
涉及 SpacetimeDB schema
- 需要生成绑定。
- 需要确认 migration.rs 对齐。
- 需要确认 publish 不触发不安全 schema 变更。
## 风险与权衡
### 风险 1个人任务 scope_kind 被误配置导致进度异常
当前个人任务系统本质上按用户维度计算进度。如果允许运营配置 `site/work/module`,可能导致任务进度查错 `tracking_daily_stat` 聚合桶,出现任务永远不可领取或错误可领取。
缓解:
```text
采用方案 B后台隐藏埋点范围后端限制个人任务配置只能写入 User。
```
### 风险 2Work 维度缺少 work_id上游却静默用 user_id 代替
当前 `profile_task_tracking_scope_id``Work => user_id` 是错误语义。若后续扩展作品任务,会把作品维度统计错误映射到用户维度。
缓解:
```text
移除 Work => user_id 映射;非 User 的个人任务配置应被拒绝。未来做作品任务时新增明确 work_id 来源和任务类型。
```
### 风险 3时区口径影响统计结果
周/月/季/年映射对时区敏感。当前日桶使用北京时间自然日:`floor((occurred_at_micros + 8h) / 1d)`。新增映射表应明确沿用北京时间业务日,还是切换为 UTC/用户本地时区。
### 风险 4ISO week 跨年
ISO week-year 与自然年不同。若前端展示按自然年理解,可能产生认知差异。
### 风险 5修改已有事件表可能触发 SpacetimeDB 迁移限制
如果已有事件表需要新增字段:
- 字段必须加末尾。
- 必须提供 default。
- 历史数据要分批迁移。
### 风险 5表设计过早绑定单一业务
建议用通用 date dimension而不是为某个单一埋点写死周/月/季/年表,避免后续复用困难。
## 待确认问题
1. 周维度使用 ISO week 还是自然周?周一开始还是周日开始?
2. 周/月/季/年映射是否沿用当前北京时间业务日口径?
3. 这个映射表服务的是所有埋点,还是只服务个人任务/运营后台统计?
4. 是否需要 API 暴露这些映射关系,还是只用于后端聚合?
5. 是否需要回填历史事件?历史数据规模多大?
6. 未来是否会存在非个人任务,例如整站任务、模块任务、作品任务?如果会,应另行设计任务类型和 `scope_id` 来源,不应复用当前个人任务配置页直接开放 scope。
## 建议结论
优先采用“一张通用日期维度映射表”的设计:
```text
analytics_date_dimension
```
通过字段同时提供:
```text
day / week / month / quarter / year
```
后续统计按 `granularity` 选择 bucket key 聚合。这样比直接新增四张独立映射表更稳定、更容易复用,也更容易处理跨年周、季度边界和历史回填。

View File

@@ -1,271 +0,0 @@
# 邀请码有效期与后台二次确认实施计划
> **For Hermes:** 按 plan 模式,仅输出并保存实施计划,不直接改业务代码。
**Goal:** 为邀请码新增开始日期与截止日期,并让后台所有会修改数据的操作在提交前增加二次确认,降低误操作风险。
**Architecture:**
邀请码仍作为“用户稳定邀请身份码”保留,不做停用删除;在数据层增加 `starts_at` / `expires_at`,前台填写邀请码时按时间窗校验,后台列表与编辑页展示状态。后台所有写操作统一先弹二次确认,再真正调用 API避免对兑换码、邀请码、任务配置等管理动作误触发。
**Tech Stack:**
Rust / SpacetimeDB / Axum / shared-contracts / TS + React 的 admin-web。
---
## 当前上下文
- 邀请码当前只有 `user_id``invite_code``metadata_json``created_at``updated_at`,没有状态字段。
- 目前后台存在邀请码管理入口,但没有停用能力,也没有有效期概念。
- 邀请码用于 `redeem_profile_referral_invite_code` 时的实时校验,适合增加“时间窗”而不是“禁用删除”。
- 后台已存在兑换码、任务配置等可写操作;本次要求把所有后台操作统一加二次确认,包括新增、编辑、禁用、删除等写入口。
---
## 设计原则
1. **邀请码不做软删除**:保留历史记录和邀请链路。
2. **有效期由时间窗推导**
- `starts_at` 为空表示立即生效。
- `expires_at` 为空表示长期有效。
3. **前台只拒绝新绑定**:已绑定关系不回溯修改。
4. **后台写操作统一确认**:所有会触发 POST / PATCH / DELETE 的管理动作,在真正提交前必须弹出二次确认。
5. **尽量少改接口语义**:优先在现有 admin upsert/list 体系内扩展字段,而不是新增一套并行 API。
---
## 方案概要
### 邀请码时间窗
新增字段:
- `starts_at: Option<Timestamp>`
- `expires_at: Option<Timestamp>`
校验规则:
- 当前时间 `< starts_at`:返回“邀请码未生效”
- 当前时间 `>= expires_at`:返回“邀请码已过期”
- 其他情况允许填写
建议把状态展示为:
- 未生效
- 有效
- 已过期
- 长期有效(两个字段都为空或仅无截止)
### 后台二次确认
对 admin-web 所有管理动作统一加确认弹窗/对话框,至少覆盖:
- 兑换码新增/更新
- 兑换码停用
- 邀请码新增/更新
- 任务配置新增/更新
- 任务配置停用
- 其他后续新增的后台写操作
确认文案要求:
- 显示对象标识(如 code / inviteCode / taskId
- 显示操作类型(新增 / 更新 / 停用)
- 明确提醒“该操作会立即影响线上数据”
- 允许取消返回,不调用 API
---
## 预期修改文件
### 1. 服务端领域与契约
- `server-rs/crates/spacetime-module/src/runtime/profile.rs`
- `ProfileInviteCode` 表结构新增开始/截止字段
- 邀请码 upsert 逻辑写入时间窗
- 邀请码 redeem 逻辑增加时间窗校验
- 邀请中心快照补充时间窗/状态
- `server-rs/crates/spacetime-module/src/migration.rs`
- 兼容旧表数据,给旧邀请码补默认空值
- `server-rs/crates/shared-contracts/src/runtime*.rs` 或对应生成/手写契约文件
- `AdminUpsertProfileInviteCodeRequest` 扩展字段
- `ProfileInviteCodeAdminResponse` 扩展字段
- 如需要,增加时间窗相关状态枚举或派生字段
- `server-rs/crates/spacetime-client/src/module_bindings/*`
- 重新生成 bindings
- mapper 补齐新字段
### 2. API Server
- `server-rs/crates/api-server/src/runtime_profile.rs`
- 接收/转发邀请码时间窗参数
- 返回新增字段给后台
- 如需要,调整校验错误文案
- `server-rs/crates/api-server/src/app.rs`
- 若有新路由或错误码需挂接,在此统一登记
### 3. Admin Web
- `apps/admin-web/src/api/adminApiTypes.ts`
- 增加邀请码时间窗字段
- 如有需要,增加后台操作请求结构字段
- `apps/admin-web/src/api/adminApiClient.ts`
- 透传新的请求/响应字段
- `apps/admin-web/src/app/adminRoutes.ts`
- 不一定需要改,但如果新增独立页面/子面板,需要在此登记
- `apps/admin-web/src/styles/admin.css`
- 确认弹窗与时间窗展示样式
- `apps/admin-web/src/**` 实际管理页面组件
- 邀请码编辑表单
- 邀请码列表状态展示
- 所有写操作前的二次确认弹窗
### 4. 文档
- `docs/technical/``docs/design/`
- 补一份邀请码时间窗与后台确认交互说明
- 如现有文档已经覆盖后台管理规范,则优先补充现有文档,不重复造新说明页
---
## 分步实施计划
### Task 1: 明确数据模型与契约扩展
**Objective:** 定义邀请码开始/截止日期字段及其在响应中的展示方式。
**要点:**
- 确认字段名采用 `starts_at` / `expires_at`,避免与现有字段语义冲突。
- 确认时间类型统一用 `Timestamp` / 毫秒微秒整数转换策略。
- 明确返回给后台的字段是否需要附带派生状态(如 `status`)。
**产出:**
- 契约字段定义
- 状态枚举/派生规则
---
### Task 2: 更新 SpacetimeDB 表与迁移
**Objective:** 让邀请码表可保存有效期,并兼容旧数据。
**要点:**
- 修改 `ProfileInviteCode` 表结构。
- 更新迁移逻辑,旧记录默认无开始/截止。
- 检查是否需要补充索引或查询辅助字段。
**验证:**
- 旧数据能正常读取。
- 新数据能写入开始/截止。
---
### Task 3: 实现邀请填写时的时间窗校验
**Objective:** 在邀请码被填写时正确拒绝未生效或已过期的邀请码。
**要点:**
-`redeem_profile_referral_invite_code_record` 内增加开始/截止校验。
- 保持“自己的邀请码不能填”“邀请码不存在”等原有错误优先级清晰。
- 保留历史绑定关系不受影响。
**验证:**
- 未到开始时间时返回明确错误。
- 超过截止时间时返回明确错误。
- 正常区间可绑定成功。
---
### Task 4: 扩展后台邀请码管理接口
**Objective:** 让后台可以创建/修改邀请码时间窗,并在列表中查看状态。
**要点:**
- 扩展 `AdminUpsertProfileInviteCodeRequest`
- 扩展 `ProfileInviteCodeAdminResponse`
- `api-server` 接口负责接收新字段并转发。
- 列表接口返回可读时间字段与状态。
**验证:**
- 后台表单提交后,返回结果包含时间窗信息。
- 列表页能看到状态与时间。
---
### Task 5: 给后台所有写操作加二次确认
**Objective:** 统一拦截所有后台写动作,避免误点直接生效。
**覆盖范围建议:**
- 邀请码新增/更新
- 兑换码新增/更新/停用
- 任务配置新增/更新/停用
- 后续新增的管理写操作
**实现要求:**
- 在真正调用 API 之前弹出确认框。
- 确认框需要展示对象名、操作类型、影响范围。
- 取消后不发送请求。
- 尽量抽象出通用确认组件/通用 action 包装函数,避免每个页面重复写。
**验证:**
- 点击“保存”不会直接提交,需先确认。
- 点击“取消”不会发请求。
- 所有后台写入口行为一致。
---
### Task 6: 补充文档与交互说明
**Objective:** 把新规则写进项目文档,避免后续实现偏差。
**要点:**
- 记录邀请码时间窗语义。
- 记录后台二次确认规范。
- 说明哪些动作属于“必须确认”的写操作。
---
## 测试与验证
### 服务端
- 邀请码时间窗单测 / 集成测试
- 邀请码 redeem 流程回归测试
- 旧数据兼容测试
### API / 前端
- 管理后台列表展示正确
- 表单提交能回传新字段
- 二次确认取消后不请求接口
- 二次确认确认后正常提交
### 推荐验证命令
- 视项目现有脚本执行对应后端测试
- 前端按 admin-web 构建/测试脚本验证
- 如涉及生成绑定,先确认生成产物无漏字段
---
## 风险与权衡
1. **时间字段格式不统一**
- 风险:前后端对时间单位理解不一致。
- 处理:在契约层明确是 ISO 字符串还是微秒整数,并全链路统一。
2. **后台“所有操作”范围过大**
- 风险:遗漏某些写入口。
- 处理:先枚举现有写 API再做统一确认封装。
3. **邀请码过期后历史链接解释成本**
- 风险:用户误以为历史邀请码失效影响已绑定关系。
- 处理:文案明确“仅影响新填写,不影响已绑定记录”。
4. **契约与生成绑定联动较多**
- 风险:字段变更后生成文件数量较多。
- 处理:先改源契约与服务端,再统一重生成 bindings。
---
## 待确认问题
1. `starts_at` / `expires_at` 在接口里要返回 **ISO 字符串** 还是 **微秒整数**
2. 后台二次确认是否统一用一个全局弹窗组件,还是页面级本地实现?
3. 邀请码列表是否需要直接展示“状态标签”还是只展示时间字段由前端推导?
4. 现有后台所有写操作里,是否还要覆盖调试类接口,还是仅覆盖业务管理接口?
---
## 建议执行顺序
1. 先确认时间字段格式与确认弹窗范围。
2. 再改服务端契约与迁移。
3. 再改 redeem 校验与后台接口。
4. 最后统一改 admin-web 的二次确认与表单展示。
---
**结论:** 这是一个适合分阶段落地的改动,建议先做“邀请码时间窗 + 后台统一二次确认”的基础能力,再补交互细节。

View File

@@ -1,584 +0,0 @@
# 我的页签反馈入口与反馈页 Implementation Plan
> **For Hermes:** Use subagent-driven-development skill to implement this plan task-by-task.
**Goal:** 在平台“我的”页签中新增“反馈”入口,点击后进入独立反馈路由,并按用户提供的参考图落地反馈页面 UI。
**Architecture:** 复用现有前端单页路由体系:`SelectionStage` 负责页面阶段,`appPageRoutes.ts` 负责 URL 映射,`PlatformEntryFlowShellImpl` 负责按阶段渲染视图。“我的”页签只增加一个入口回调,不在当前面板下方展开内容;反馈页作为独立页面组件挂到新阶段。首版先做前端静态表单与本地提交成功态,不新增后端表结构或 SpacetimeDB 写入,除非产品补充明确要求持久化反馈。
**Tech Stack:** React 19、TypeScript、Tailwind utility class、lucide-react、现有 Genarrative 平台入口组件体系。
---
## Current context / assumptions
## Reference image
![帮助与反馈参考图](assets/profile-feedback-reference-2026-05-08.png)
参考图是一张移动端“帮助与反馈”页面,视觉和信息结构如下:
- 页面整体:浅灰背景,白色圆角卡片,黑/深灰标题文字,浅灰 placeholder蓝色主按钮与蓝色文本链接。
- 顶部栏:白色导航/header左侧为小 home 图标,中间标题为“帮助与反馈”,右侧为胶囊形更多/控制区。项目实现时可按现有平台导航规范简化为返回按钮 + 居中标题;若需要完全贴近图片,可使用 home 图标作为返回到“我的”页签的按钮。
- 内容区 section label左上灰色文字“反馈问题”。
- 第一张表单卡标题“问题描述”大文本输入区域placeholder 为“请填写10个字以上的问题描述以便我们提供更好的帮助温馨提醒您请勿填写身份证号等个人隐私信息。”右下角字数统计“0/200”。
- 第二张表单卡:标题“上传凭证(提供问题截图)”,左侧虚线边框上传方块,内含图片/上传 + 加号图标,文字“上传凭证”“(最多四张)”。
- 第三张表单卡标题“联系电话”placeholder 为“选填,如您填写则将会同步开发者与您联系”。
- 底部操作:大号蓝色圆角按钮“提交”,下方居中蓝色链接“查看反馈与投诉记录”。
实现约束:
- 反馈页面应命名为“帮助与反馈”,但“我的”页签入口可显示为“反馈”或“帮助与反馈”,优先以清爽短入口为准。
- 问题描述最少 10 个字、最多 200 个字,并实时显示 `当前字数/200`
- 上传凭证首版如不接后端,可先支持前端选择/预览最多 4 张图片,提交时仅进入成功态;如无法快速安全实现预览,可先保留上传占位并在文档中标注待接入。
- 联系电话为选填。
- “查看反馈与投诉记录”首版无后端记录时可以先禁用、隐藏,或点击后给出轻量提示;若保留可见,应在计划/PRD 标明记录页不在首版范围。
1. 当前工作区是 `/home/dsk/workspace/Genarrative/.worktrees/hermes-19e77eb0`,不要额外拼接 `Genarrative/`
2. 平台首页复用 `src/components/rpg-entry/RpgEntryHomeView.tsx``src/components/platform-entry/PlatformEntryHomeView.tsx` 只是 re-export。
3. “我的”页签的常用功能区域位于 `src/components/rpg-entry/RpgEntryHomeView.tsx:3958-4000`,现有入口包括“每日任务 / 邀请好友 / 填邀请码 / 玩家社区”。
4. 当前页面阶段类型位于 `src/components/platform-entry/platformEntryTypes.ts:16-38`;路由映射位于 `src/routing/appPageRoutes.ts:7-27`
5. `src/App.tsx:60-63` 调用 `pushAppHistoryPath(resolvePathForSelectionStage(stage))`,所以新增阶段必须同步 `APP_STAGE_ROUTES`
6. `PlatformEntryFlowShellImpl``src/components/platform-entry/PlatformEntryFlowShellImpl.tsx:5081+` 根据 `selectionStage` 渲染不同页面,平台首页在 `selectionStage === 'platform'` 分支。
7. 参考图片已保存到 `.hermes/plans/assets/profile-feedback-reference-2026-05-08.png`,计划与实现均以该图片内容为主要 UI 依据。
8. 按项目约束,工程修改需同步文档;若没有更具体 PRD需要先补一份简洁落地文档到 `docs/`
## Proposed approach
新增一个轻量前端反馈页面阶段:
- 路由:`/profile/feedback`
- 阶段:`profile-feedback`
- 组件:`src/components/platform-entry/PlatformFeedbackView.tsx`
- “我的”页签入口:在常用功能区增加“反馈”按钮,点击调用新 prop `onOpenFeedback`
- 页面行为:
- 顶部返回按钮返回 `platform` 阶段,并切回 `profile` 页签。
- 未登录用户点击入口时,优先弹登录;如果产品允许匿名反馈,可改为允许进入。
- 表单字段首版只在前端维护:问题描述、上传凭证图片、联系电话。
- 提交后显示成功态,不做 API 请求;后续如要持久化,再补 `shared-contracts + api-server + SpacetimeDB` 方案。
## Step-by-step plan
### Task 1: 补充反馈页落地文档
**Objective:** 先把反馈入口和页面边界写清楚,避免编码时需求漂移。
**Files:**
- Create: `docs/prd/PROFILE_FEEDBACK_ENTRY_PRD_2026-05-08.md`
**Step 1: 新建 PRD 文档**
写入内容建议包含:
```markdown
# 我的页签反馈入口 PRD
## 目标
- 在“我的”页签提供反馈入口。
- 点击入口进入独立反馈路由 `/profile/feedback`
- 反馈页移动端优先,桌面端居中卡片展示。
## 首版范围
- 前端表单:问题描述、上传凭证占位/前端图片预览、联系电话。
- 问题描述 10-200 字,显示实时字数统计。
- 提交后显示成功态。
- 不新增后端存储,不修改 SpacetimeDB 表结构。
## 交互
- 已登录用户:点击“反馈”进入反馈页。
- 未登录用户:点击入口触发登录弹窗。
- 返回:回到平台首页并定位“我的”页签。
## UI
-`.hermes/plans/assets/profile-feedback-reference-2026-05-08.png` 为准,落地“帮助与反馈”移动端表单。
- 不在 UI 中堆叠说明性长文案。
- 入口是独立页面导航,不在“我的”面板下方展开。
## 验收
- `/profile/feedback` 可被浏览器前进/后退访问。
- “我的”页签反馈入口可进入该路由。
- 移动端和桌面端均不溢出。
- `npm run check:encoding``npm run typecheck` 通过。
```
**Step 2: 验证文档编码**
Run: `npm run check:encoding`
Expected: PASS无中文编码错误。
**Step 3: Commit**
```bash
git add docs/prd/PROFILE_FEEDBACK_ENTRY_PRD_2026-05-08.md
git commit -m "docs: add profile feedback entry prd"
```
### Task 2: 扩展页面阶段与路由映射
**Objective:**`/profile/feedback` 成为主应用可识别的独立路由。
**Files:**
- Modify: `src/components/platform-entry/platformEntryTypes.ts`
- Modify: `src/routing/appPageRoutes.ts`
**Step 1: 修改 SelectionStage 类型**
`SelectionStage` union 中追加:
```ts
| 'profile-feedback'
```
推荐放在 `'platform'` 附近或末尾,保持字面量清晰。
**Step 2: 修改 STAGE_ROUTE_ENTRIES**
`src/routing/appPageRoutes.ts``STAGE_ROUTE_ENTRIES` 中追加:
```ts
['profile-feedback', '/profile/feedback'],
```
建议放在 `['platform', '/']` 后面,表示平台个人页子路由。
**Step 3: 验证类型推导**
Run: `npm run typecheck`
Expected: 若还未创建渲染组件,可能只通过路由类型;若出现 exhaustive 相关错误,留到后续任务处理。
**Step 4: Commit**
```bash
git add src/components/platform-entry/platformEntryTypes.ts src/routing/appPageRoutes.ts
git commit -m "feat: add profile feedback route stage"
```
### Task 3: 新建反馈页面组件
**Objective:** 创建移动端优先的独立反馈页面。
**Files:**
- Create: `src/components/platform-entry/PlatformFeedbackView.tsx`
**Step 1: 创建组件 props**
组件接口建议:
```ts
export type PlatformFeedbackViewProps = {
onBack: () => void;
onSubmit?: (payload: PlatformFeedbackPayload) => void | Promise<void>;
};
export type PlatformFeedbackPayload = {
description: string;
contactPhone: string;
evidenceFiles: File[];
};
```
**Step 2: 实现 UI 状态**
使用 `useState` 管理:
- `description`
- `contactPhone`
- `evidenceFiles`
- `evidencePreviewUrls`
- `error`
- `isSubmitting`
- `submitted`
**Step 3: 实现页面结构**
建议结构:
```tsx
import { ArrowLeft, CheckCircle2, Home, ImagePlus, Send } from 'lucide-react';
import { useEffect, useState } from 'react';
const MAX_FEEDBACK_DESCRIPTION_LENGTH = 200;
const MIN_FEEDBACK_DESCRIPTION_LENGTH = 10;
const MAX_FEEDBACK_EVIDENCE_COUNT = 4;
```
页面外壳建议复用现有视觉变量:
```tsx
<div className="platform-page-stage platform-remap-surface min-h-0 min-w-0 overflow-y-auto px-4 py-4 sm:px-6 lg:px-8">
<div className="mx-auto flex w-full max-w-2xl flex-col gap-4">
<header className="platform-surface platform-surface--soft rounded-[1.6rem] px-4 py-4">
<button type="button" onClick={onBack} ...>
<ArrowLeft ... />
</button>
<h1></h1>
</header>
...
</div>
</div>
```
注意:不要写大段“功能说明类文案”;字段 label 简短即可。
**Step 4: 表单校验**
提交时:
- `description.trim().length < 10`提示“请填写10个字以上的问题描述”
- `description.trim().length > 200`:提示“问题描述不能超过 200 字”
- `contactPhone.trim().length > 40`:提示“联系电话不能超过 40 字”
- 上传凭证最多 4 张;超出时提示“最多上传四张凭证”
**Step 5: 提交行为**
首版无后端时:
```ts
await onSubmit?.({
description: description.trim(),
contactPhone: contactPhone.trim(),
evidenceFiles,
});
setSubmitted(true);
```
如果没有传 `onSubmit`,也显示成功态。代码注释说明:
```ts
// 中文注释:首版反馈页只完成前端收集与成功态;接入后端时在 onSubmit 中替换为 API 调用。
```
**Step 6: Commit**
```bash
git add src/components/platform-entry/PlatformFeedbackView.tsx
git commit -m "feat: add platform feedback view"
```
### Task 4: 在“我的”页签增加反馈入口 prop
**Objective:** 让 Profile 页面能触发反馈路由,同时保持组件职责清晰。
**Files:**
- Modify: `src/components/rpg-entry/RpgEntryHomeView.tsx`
- Modify: `src/components/platform-entry/PlatformEntryHomeView.tsx`通常无需改re-export 类型会自动带出)
**Step 1: 扩展 Props**
`RpgEntryHomeViewProps` 中新增:
```ts
onOpenFeedback?: () => void;
```
**Step 2: 从 props 解构**
`RpgEntryHomeView` 函数参数解构区新增:
```ts
onOpenFeedback,
```
**Step 3: 增加入口按钮**
`profileContent` 的常用功能 grid 中,建议在“玩家社区”后追加:
```tsx
<ProfileShortcutButton
label="反馈"
subLabel="问题与建议"
icon={MessageCircle}
onClick={onOpenFeedback}
/>
```
如果参考图中入口位置不同,按参考图调整;但仍必须进入独立路由。
**Step 4: 未提供回调时行为**
`ProfileShortcutButton` 已允许 `onClick` 为空;此处传 `onOpenFeedback` 即可。若希望按钮始终可点,应在父组件必传。
**Step 5: 验证类型**
Run: `npm run typecheck`
Expected: PASS 或只剩父组件未传 prop 的问题。
**Step 6: Commit**
```bash
git add src/components/rpg-entry/RpgEntryHomeView.tsx src/components/platform-entry/PlatformEntryHomeView.tsx
git commit -m "feat: add feedback shortcut to profile tab"
```
### Task 5: 接入 PlatformEntryFlowShellImpl 渲染与导航
**Objective:** 点击“反馈”进入 `/profile/feedback`,返回后回到“我的”页签。
**Files:**
- Modify: `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
**Step 1: 导入组件**
在 imports 中新增:
```ts
import { PlatformFeedbackView } from './PlatformFeedbackView';
```
**Step 2: 创建打开反馈页函数**
`const { setPlatformTab } = platformBootstrap;` 附近新增:
```ts
const openProfileFeedback = useCallback(() => {
if (!authUi?.user) {
authUi?.openLoginModal();
return;
}
setPlatformTab('profile');
setSelectionStage('profile-feedback');
}, [authUi, setPlatformTab, setSelectionStage]);
```
如产品允许匿名反馈,则移除登录判断。
**Step 3: 给首页传入入口回调**
`PlatformEntryHomeView` props 中加入:
```tsx
onOpenFeedback={openProfileFeedback}
```
**Step 4: 增加渲染分支**
`selectionStage === 'platform'` 分支后、详情页分支前新增:
```tsx
{selectionStage === 'profile-feedback' && (
<motion.div
key="platform-profile-feedback"
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -12 }}
className="flex h-full min-h-0 flex-col"
>
<PlatformFeedbackView
onBack={() => {
setPlatformTab('profile');
setSelectionStage('platform');
}}
/>
</motion.div>
)}
```
**Step 5: 直接访问路由的 tab 同步**
为处理用户直接访问 `/profile/feedback` 后返回,返回逻辑已 `setPlatformTab('profile')`。如需要进入反馈页时也设置 tab可加 effect
```ts
useEffect(() => {
if (selectionStage === 'profile-feedback') {
setPlatformTab('profile');
}
}, [selectionStage, setPlatformTab]);
```
**Step 6: Commit**
```bash
git add src/components/platform-entry/PlatformEntryFlowShellImpl.tsx
git commit -m "feat: wire profile feedback navigation"
```
### Task 6: 增加路由与反馈页基础测试
**Objective:** 用自动化测试覆盖新路由映射和反馈页核心交互。
**Files:**
- Create or Modify: `src/routing/appPageRoutes.test.ts`
- Create: `src/components/platform-entry/PlatformFeedbackView.test.tsx`
**Step 1: 路由测试**
如果已有 `appPageRoutes.test.ts`,追加;否则创建:
```ts
import { describe, expect, it } from 'vitest';
import {
resolvePathForSelectionStage,
resolveSelectionStageFromPath,
} from './appPageRoutes';
describe('appPageRoutes', () => {
it('resolves profile feedback route', () => {
expect(resolveSelectionStageFromPath('/profile/feedback')).toBe('profile-feedback');
expect(resolvePathForSelectionStage('profile-feedback')).toBe('/profile/feedback');
});
});
```
**Step 2: 反馈页测试**
测试重点:
- 渲染“帮助与反馈”标题。
- 问题描述过短时提交显示错误。
- 输入有效问题描述后提交显示成功态。
- 字数统计随输入更新。
- 上传凭证入口最多接受 4 张图片。
- 点击返回调用 `onBack`
示例:
```tsx
import { fireEvent, render, screen } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import { PlatformFeedbackView } from './PlatformFeedbackView';
describe('PlatformFeedbackView', () => {
it('validates content before submit', () => {
render(<PlatformFeedbackView onBack={vi.fn()} />);
fireEvent.click(screen.getByRole('button', { name: '提交' }));
expect(screen.getByText('请填写10个字以上的问题描述')).toBeInTheDocument();
});
});
```
注意检查项目当前 test setup 是否已引入 jest-dom matcher若没有使用 truthy DOM 节点断言:
```ts
expect(screen.getByText('请补充反馈内容')).toBeTruthy();
```
**Step 3: 运行定向测试**
Run:
```bash
npm run test -- src/routing/appPageRoutes.test.ts src/components/platform-entry/PlatformFeedbackView.test.tsx
```
Expected: PASS。
**Step 4: Commit**
```bash
git add src/routing/appPageRoutes.test.ts src/components/platform-entry/PlatformFeedbackView.test.tsx
git commit -m "test: cover profile feedback route and form"
```
### Task 7: 全量前端验证与移动端 smoke
**Objective:** 确认新增页面不破坏编码、类型和基础交互。
**Files:**
- No code changes unless validation finds issues.
**Step 1: 编码检查**
Run: `npm run check:encoding`
Expected: PASS。
**Step 2: ESLint**
Run: `npm run lint:eslint`
Expected: PASS。
**Step 3: TypeScript**
Run: `npm run typecheck`
Expected: PASS。
**Step 4: 测试**
Run: `npm run test -- src/routing/appPageRoutes.test.ts src/components/platform-entry/PlatformFeedbackView.test.tsx`
Expected: PASS。
**Step 5: 本地页面 smoke**
Run: `npm run dev:web`
手动验证:
1. 打开 `http://127.0.0.1:3000/`
2. 登录后进入“我的”页签。
3. 点击“反馈”。
4. 地址变为 `/profile/feedback`
5. 页面显示反馈表单。
6. 提交空内容出现错误。
7. 输入有效内容后显示成功态。
8. 点击返回后回到首页“我的”页签。
9. 直接打开 `http://127.0.0.1:3000/profile/feedback` 能显示反馈页。
10. 使用移动端视口(如 390×844确认按钮和表单不溢出。
**Step 6: Commit validation fixes if any**
```bash
git add <fixed-files>
git commit -m "fix: polish profile feedback validation"
```
## Files likely to change
- `docs/prd/PROFILE_FEEDBACK_ENTRY_PRD_2026-05-08.md`:新增反馈入口落地文档。
- `src/components/platform-entry/platformEntryTypes.ts`:新增 `profile-feedback` 阶段。
- `src/routing/appPageRoutes.ts`:新增 `/profile/feedback` 路由映射。
- `.hermes/plans/assets/profile-feedback-reference-2026-05-08.png`:反馈页参考图。
- `src/components/platform-entry/PlatformFeedbackView.tsx`:新增反馈页面。
- `src/components/rpg-entry/RpgEntryHomeView.tsx`:新增“我的”页签反馈入口和 `onOpenFeedback` prop。
- `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`:接入反馈页打开与返回导航。
- `src/routing/appPageRoutes.test.ts`:新增路由映射测试。
- `src/components/platform-entry/PlatformFeedbackView.test.tsx`:新增反馈页交互测试。
## Tests / validation
Minimum required:
```bash
npm run check:encoding
npm run typecheck
npm run test -- src/routing/appPageRoutes.test.ts src/components/platform-entry/PlatformFeedbackView.test.tsx
```
Recommended before merge:
```bash
npm run lint:eslint
npm run test
npm run build
```
Manual smoke:
- 登录后“我的”页签显示“反馈”入口。
- 点击入口进入 `/profile/feedback`
- 浏览器后退和页面返回按钮行为符合预期。
- 移动端视口无横向溢出。
- 页面没有把反馈表单展开在“我的”页签下方。
## Risks, tradeoffs, and open questions
1. **参考图落地风险:** 参考图是浅色移动端表单,而项目现有平台 UI 可能偏游戏化/深色变量;实现时需要优先复刻信息结构与交互,不要为了完全一致而破坏现有主题适配。
2. **反馈是否需要后端存储:** 本计划首版不新增后端,只做前端收集和成功态。若产品要求真实提交,需要新增后端方案:`shared-contracts` DTO、`api-server` 路由、SpacetimeDB 表/迁移、后台查看入口,并按 SpacetimeDB skills 执行。
3. **登录要求:** 计划默认未登录用户点击入口弹登录。若希望匿名反馈,应取消该限制,并在 payload 中允许无用户身份。
4. **入口位置:** 当前建议放在“我的”页签常用功能 grid 中。若参考图明确是列表项或设置区入口,应按图调整,但仍进入独立路由。
5. **图标复用:** 可先用 `MessageCircle``MessageSquareText`,避免引入新依赖。
6. **现有大文件风险:** `RpgEntryHomeView.tsx` 很大,实施时必须局部补丁,避免整文件重写导致中文编码或格式大范围变化。
## Implementation notes
- 所有中文注释和文案保持 UTF-8。
- 不要新增 `.env.local``.gitignore`
- 不要把反馈页做成“我的”页签内部展开面板。
- 不要新增后端或数据库,除非用户确认反馈必须持久化。
- 若后续接入后端,必须先补技术文档,再按 DDD 与 SpacetimeDB 约束落地。

View File

@@ -1,561 +0,0 @@
# 声控狗叫对战 2D 浏览器游戏设计与实现计划
## 目标
基于用户提供的视频:
`C:\Users\DSK\Videos\一款双方比狗叫的游戏 - 1.一款双方比狗叫的游戏(Av116504192360177,P1).mp4`
提取其中“双方比狗叫”的核心玩法,并按照 BDD / TDD / DDD 的方法,为 Genarrative 中可运行于浏览器的 2D 游戏方案生成一份可落地设计与实现思路。实现方向遵循仓库内 `game-studio` 插件工作流,默认采用 2D Phaser + TypeScript + Vite + DOM HUD 的浏览器游戏架构。
本计划仅做方案设计,不直接编码。
## 当前上下文与输入分析
### 已识别视频核心画面
通过抽帧观察,视频中的游戏呈现出以下稳定特征:
- 画面是横版 2D 手绘舞台,场景包括公园、海边等固定关卡背景。
- 双方各有一只狗作为对战角色,站在左右两侧。
- 中央有明显倒计时,例如 `30``28`
- 顶部有红蓝双方拉锯式能量条 / 进度条。
- 中央提示出现:`对着麦克风汪一声``用声音大小 + 叫声次数推动能量条!`
- 玩家输入不是传统键鼠,而是麦克风声音。
- 玩家需要模仿狗叫,系统根据声音大小与叫声次数推动能量条。
- 屏幕会根据叫声出现 `BARK``WOOF``WAN``WANGOOF` 等拟声词与冲击波视觉反馈。
- 回合结束时,根据能量条偏向或推进结果判定胜负。
### 提炼出的核心玩法
这是一个“声控拔河式狗叫对战”小游戏:
- 两名玩家 / 一名玩家对 AI 分别代表左右两只狗。
- 每局限时 30 秒。
- 玩家通过麦克风持续发出狗叫声。
- 游戏实时分析音量峰值、叫声次数、叫声节奏。
- 声音越大、叫声越密集,己方推动力越强。
- 顶部能量条在双方推动力差值下左右移动。
- 时间结束后,能量条偏向哪一方,哪一方获胜。
### 需要合理抽象的地方
视频中存在直播弹幕、贴图、表情包、遮挡层,这些不是游戏本体机制。本方案只吸收游戏本体核心:
- 双方狗狗对叫
- 麦克风输入
- 声音强度 + 次数判定
- 红蓝拉锯能量条
- 限时回合
- 夸张拟声词与冲击波反馈
## game-studio 插件路线
根据仓库内 `.hermes/plugins/game-studio` 技能:
- 早期游戏工作先走 `game-studio` 总入口。
- 2D 浏览器游戏默认选择 Phaser。
- 架构上需要分离 simulation 与 renderer。
- HUD / 菜单 / 设置优先使用 DOM overlay不把密集文字塞进 canvas。
- 玩法状态不应由 Phaser Scene 直接持有Scene 只负责渲染、动画、相机、输入适配。
因此本方案采用:
- RuntimePhaser 3
- LanguageTypeScript
- BuildVite
- UIReact/DOM HUD overlay 或项目现有 DOM UI 层
- Audio inputWeb Audio API + MediaDevices.getUserMedia
- Simulation纯 TS domain/service 层
- RendererPhaser Scene 读取 simulation snapshot 并播放动画/特效
## 游戏概念设计
### 游戏名建议
- 中文:`汪汪声浪大作战`
- 英文代号:`bark-battle`
- Play type ID 建议:`bark-battle`
### 玩家幻想
玩家不是通过按键战斗,而是真的对着麦克风“汪汪叫”,把自己的狗狗声浪推向对手。游戏目标是在倒计时结束前用更响、更密集、更有节奏的叫声赢得声浪拔河。
### 核心动词
- 叫:对麦克风发出狗叫声。
- 推:通过叫声推动能量条。
- 压制:让能量条持续向对手方向倾斜。
- 爆发:短时间内连续高质量叫声触发冲击波。
- 防守:对手强势时通过持续叫声把能量条拉回。
### 单局流程
1. 准备阶段
- 展示双方狗狗、地图、麦克风权限提示。
- 用户授权麦克风。
- 系统检测环境噪音并校准阈值。
2. 倒计时阶段
- 3、2、1 或中央 `30` 倒计时开始。
- 玩家看到提示:`对着麦克风汪一声`
3. 对战阶段
- 每帧或固定 tick 采集麦克风音量。
- 根据音量峰值与短促叫声次数计算本方 barkPower。
- AI 或远端对手产生 opponentPower。
- 能量条根据 `playerPower - opponentPower` 拉锯。
- 狗狗张嘴动画、拟声词、冲击波按声音强度生成。
4. 结算阶段
- 30 秒结束。
- 能量条偏玩家侧则胜利,偏对手侧则失败,接近中线则平局。
- 展示叫声次数、最大音量、平均节奏、声浪评分。
5. 重开 / 返回
- 支持再来一局。
- 支持返回玩法入口或结果页。
## 规则设计
### 关键状态
```ts
type BarkBattlePhase = 'permission' | 'calibration' | 'countdown' | 'playing' | 'finished'
type BarkBattleSnapshot = {
phase: BarkBattlePhase
remainingMs: number
energy: number // -100 到 100负数偏对手正数偏玩家
player: BarkSideState
opponent: BarkSideState
winner: 'player' | 'opponent' | 'draw' | null
}
type BarkSideState = {
barkCount: number
currentVolume: number
recentPeak: number
combo: number
power: number
isBarking: boolean
}
```
### 输入判定
#### 音量采样
- 使用 Web Audio API 创建 `AnalyserNode`
- 每个 simulation tick 读取频域或时域数据。
- 计算 RMS 或 peak volume。
- 根据校准后的环境噪音设置动态阈值。
#### 一次“叫声”的判定
一次有效叫声建议满足:
- 音量超过 `barkThreshold`
- 与上一次叫声峰值至少间隔 `minBarkGapMs`,避免持续噪音被无限计数。
- 持续时长在合理范围,例如 80ms 到 1200ms。
- 可选频谱能量集中在中高频不强制做复杂语音识别MVP 先用音量 + 峰值节奏。
#### 推动力计算
```text
playerPower = volumeScore * 0.65 + barkRateScore * 0.35 + comboBonus
opponentPower = aiPower 或远端玩家 power
energyDelta = (playerPower - opponentPower) * deltaTime * balanceFactor
energy = clamp(energy + energyDelta, -100, 100)
```
### AI 对手 MVP
若先做单机浏览器版,右侧对手可由 AI 模拟:
- 简单难度周期性小叫power 低。
- 普通难度有节奏地爆发power 中等。
- 困难难度:根据玩家领先程度自适应追赶,但不得作弊到不可赢。
后续可扩展为多人实时对战。
## BDD 行为场景
### 功能: 麦克风授权与准备
```gherkin
功能: 狗叫对战麦克风准备
场景: 玩家允许麦克风权限后进入准备倒计时
假如
那么
而且
场景: 玩家拒绝麦克风权限
假如
那么
而且
而且
```
### 功能: 声音推动能量条
```gherkin
功能: 声音大小和叫声次数推动能量条
场景: 玩家发出一次有效狗叫
假如 playing
而且
那么 1
而且
而且
场景: 玩家连续大声狗叫压制对手
假如 playing
而且
那么
而且
场景: 环境噪音低于阈值不计入叫声
假如 playing
那么
而且
```
### 功能: 限时胜负结算
```gherkin
功能: 狗叫对战胜负结算
场景: 倒计时结束时玩家侧占优
假如
而且
那么
而且
场景: 倒计时结束时双方接近平衡
假如
而且
那么
而且
```
### 功能: 移动端与无麦克风降级
```gherkin
功能: 声控游戏移动端与无麦克风降级
场景: 当前浏览器不支持麦克风 API
假如 getUserMedia
那么
而且
场景: 移动端进入对战页面
假如使
那么
而且
```
## DDD 领域划分
### 领域层bark-battle domain
职责:只处理玩法规则,不依赖 Phaser、DOM、Web Audio、后端。
建议模块:
- `BarkBattleSession`
- 管理 phase、remainingMs、energy、winner。
- `BarkDetector`
- 根据音量样本判断是否形成一次有效叫声。
- `EnergyTugOfWar`
- 根据双方 power 更新能量条。
- `BarkBattleScoring`
- 计算最大音量、叫声次数、combo、评分。
- `OpponentStrategy`
- 单机 AI 对手策略接口。
领域规则必须可用纯单元测试验证。
### 应用层use case / controller
职责编排麦克风输入、simulation tick、AI 对手、结果输出。
建议用例:
- `requestMicrophonePermission()`
- `calibrateAmbientNoise()`
- `startBarkBattleSession()`
- `submitAudioSample(sample)`
- `tickBarkBattle(deltaMs)`
- `finishBarkBattle()`
### 基础设施层
职责:浏览器 API 与引擎适配。
- `BrowserMicrophoneInput`
- 封装 `navigator.mediaDevices.getUserMedia`
- 输出 normalized volume samples。
- `PhaserBarkBattleScene`
- 渲染狗狗、背景、拟声词、冲击波。
- 不持有核心玩法规则。
- `DomBarkBattleHud`
- 展示倒计时、能量条、权限提示、结算面板。
### 表现层
- Phaser Canvas地图、狗狗、声浪、粒子、拟声词。
- DOM HUD顶部能量条、倒计时、权限/结算/设置面板。
## TDD 落地顺序
### 第一轮:领域规则 RED-GREEN-REFACTOR
先写纯 TS 单元测试,不接 Phaser不接麦克风。
目标测试:
- `BarkDetector`:超过阈值且间隔足够时计为一次叫声。
- `BarkDetector`:持续噪音不会无限增加叫声次数。
- `EnergyTugOfWar`:玩家 power 高于对手时 energy 向玩家侧移动。
- `EnergyTugOfWar`energy 被 clamp 在 -100 到 100。
- `BarkBattleSession`:倒计时归零后进入 finished。
- `BarkBattleSession`:根据 energy 判定 player/opponent/draw。
### 第二轮:应用层测试
- 模拟音频 sample 输入,验证 session snapshot 更新。
- 模拟 AI 对手 power验证能量条拉锯。
- 模拟权限失败,验证 phase 不进入 playing。
### 第三轮:组件 / 集成测试
- HUD 根据 snapshot 显示倒计时。
- HUD 根据 energy 渲染红蓝能量条比例。
- 权限拒绝时显示重试入口。
- 结算阶段显示胜负与再来一局。
### 第四轮:浏览器 smoke / playtest
- 本地启动页面。
- 授权麦克风。
- 对麦克风发声后看到拟声词与能量条变化。
- 移动端宽度下主游戏画面不被 HUD 遮挡。
## 建议文件结构
如果作为独立前端玩法原型,可采用:
```text
src/games/bark-battle/
domain/
BarkBattleSession.ts
BarkDetector.ts
EnergyTugOfWar.ts
BarkBattleScoring.ts
OpponentStrategy.ts
application/
BarkBattleController.ts
BrowserMicrophoneInput.ts
phaser/
BarkBattleScene.ts
BarkBattlePreloadScene.ts
barkBattleAssets.ts
ui/
BarkBattleHud.tsx
BarkBattleResultPanel.tsx
BarkBattlePermissionPanel.tsx
tests/
BarkDetector.test.ts
EnergyTugOfWar.test.ts
BarkBattleSession.test.ts
```
如果接入 Genarrative 玩法类型闭环,后续还需要按 `genarrative-play-type-integration` 扩展:
```text
src/components/bark-battle-runtime/BarkBattleRuntimeShell.tsx
src/components/bark-battle-result/BarkBattleResultView.tsx
src/services/barkBattleRuntimeClient.ts
packages/shared/src/contracts/barkBattle.ts
server-rs/crates/shared-contracts/src/bark_battle.rs
```
MVP 阶段建议先做浏览器单机 runtime 原型,再决定是否进入创作入口、作品发布、广场和后端持久化。
## UI / 视觉方向
### 画面
- 横版固定舞台。
- 左右两只狗对峙。
- 背景可先做公园一张图,后续扩展海边、街区等地图。
- 狗狗用 2D sprite 或简单骨架帧动画。
### HUD
- 顶部:红蓝声浪能量条。
- 中央:大号倒计时,只在开局和关键时间突出显示。
- 左右:双方狗狗状态,不堆叠复杂面板。
- 底部或角落:麦克风状态、小型重试按钮。
- 结算:居中弹出简洁面板,显示胜负和关键数据。
### 动效
- 叫声触发狗狗张嘴。
- 声音越大,拟声词越大,冲击波越宽。
- combo 时触发短暂屏幕震动,但不能遮挡能量条。
- 尊重 reduced motion非必要动画可降级。
## 测试映射
| BDD 场景 | 测试层级 | 目标文件 | 状态 |
| --- | --- | --- | --- |
| 玩家允许麦克风权限后进入准备倒计时 | application/component | `BarkBattleController.test.ts`, `BarkBattlePermissionPanel.test.tsx` | planned |
| 玩家拒绝麦克风权限 | application/component | `BarkBattleController.test.ts`, `BarkBattlePermissionPanel.test.tsx` | planned |
| 玩家发出一次有效狗叫 | unit | `BarkDetector.test.ts` | planned |
| 玩家连续大声狗叫压制对手 | unit/integration | `EnergyTugOfWar.test.ts`, `BarkBattleController.test.ts` | planned |
| 环境噪音低于阈值不计入叫声 | unit | `BarkDetector.test.ts` | planned |
| 倒计时结束时玩家侧占优 | unit | `BarkBattleSession.test.ts` | planned |
| 倒计时结束时双方接近平衡 | unit | `BarkBattleSession.test.ts` | planned |
| 当前浏览器不支持麦克风 API | component | `BarkBattlePermissionPanel.test.tsx` | planned |
| 移动端进入对战页面 | visual/smoke | Playwright 或人工 playtest 清单 | planned |
## 验证命令建议
具体命令以后续实际落地位置为准,建议包括:
```bash
npm run test -- --run src/games/bark-battle/**/*.test.ts
npm run test -- --run src/games/bark-battle/**/*.test.tsx
npm run typecheck
npm run check:encoding
```
若接入 Genarrative 后端或玩法配置,还需要追加:
```bash
cd server-rs && cargo check -p api-server -p shared-contracts --no-default-features
npm run test -- src/components/platform-entry/platformEntryCreationTypes.test.ts
```
## 实施阶段拆分
### Phase 0产品与技术定稿
- 确认玩法 ID`bark-battle`
- 确认 MVP 只做单机玩家 vs AI不做实时多人。
- 确认是否只做 runtime 原型,还是接入 Genarrative 创作入口。
- 确认是否允许浏览器麦克风权限作为核心输入。
### Phase 1纯领域模型
- 建立 bark-battle domain。
- 按 TDD 写 `BarkDetector``EnergyTugOfWar``BarkBattleSession` 测试。
- 实现最小规则让测试通过。
### Phase 2麦克风输入适配
- 封装 Web Audio API。
- 支持权限请求、权限失败、环境噪音校准。
- 使用 mock input 完成自动化测试,真实麦克风做 smoke。
### Phase 3Phaser 2D runtime
- 新建 Phaser Scene。
- 绘制或占位加载公园背景、左右狗狗、声浪特效。
- Scene 只消费 snapshot不写规则。
- 接入 DOM HUD。
### Phase 4反馈与结算
- 加入拟声词、冲击波、狗狗张嘴动画。
- 加入结算面板。
- 加入再来一局与返回入口。
### Phase 5Genarrative 集成可选项
若要正式接入玩法类型:
-`shared-contracts` 中 bark-battle runtime/result DTO。
- 补前端 service 与 runtime shell。
- 补入口配置数据库 seed。
- 补作品架 / 发布 / 广场链路,若需要持久化成绩或作品。
-`genarrative-play-type-integration` 执行完整闭环验证。
## 风险与权衡
### 麦克风权限风险
浏览器麦克风权限受 HTTPS、浏览器策略、用户设置影响。MVP 需要明确:
- 本地开发可在 localhost 使用。
- 线上必须 HTTPS。
- 权限拒绝需要可恢复。
### 声音识别准确性风险
MVP 不建议做复杂“是否真的是狗叫”的 AI 识别,否则实现成本高、误判多。建议先用:
- 音量阈值
- 峰值次数
- 节奏间隔
- 环境噪音校准
后续再考虑加入频谱特征或 ML 分类。
### 噪音作弊风险
玩家可以喊叫、拍桌子或播放音频。若是娱乐派对玩法可以接受;若要竞技公平,需要后续加入:
- 频谱特征
- 输入冷却
- 异常持续噪音削弱
- 本地/服务端反作弊策略
### 移动端兼容风险
移动端 Web Audio 可能需要用户手势激活 AudioContext。计划中需把“开始”按钮作为显式用户手势避免自动启动失败。
### UI 遮挡风险
视频原型中的核心可读信息非常少:倒计时、能量条、狗狗、拟声词。实现时应避免把说明文案、复杂面板长期铺在画面上。
## 开放问题
1. MVP 是“玩家 vs AI”还是需要从第一版开始支持双人同屏 / 联机?
2. 是否要作为 Genarrative 新玩法入口完整接入,还是先做独立 runtime 原型?
3. 是否需要记录成绩、发布作品、进入作品架和广场?
4. 狗狗与背景素材是使用临时占位、AI 生成,还是需要复用项目既有素材系统?
5. 是否允许游戏强依赖麦克风权限,还是必须提供键盘备用输入?
## 推荐下一步
建议下一步先执行 Phase 0 + Phase 1
1. 明确 MVP 边界:单机玩家 vs AI。
2.`BarkDetector` / `EnergyTugOfWar` / `BarkBattleSession` 的 BDD 对应单元测试。
3. 不接 Phaser、不接麦克风先把核心规则用 TDD 跑通。
4. 规则稳定后再接 Web Audio 与 Phaser runtime。

View File

@@ -1,709 +0,0 @@
# bark-battle 三阶段实施计划:浏览器原型 → AI 创作入口 → 数据库落地
> **For Hermes:** Use subagent-driven-development skill to implement this plan task-by-task.
**Goal:** 按“三阶段”推进 `bark-battle / 汪汪声浪大作战`:第一阶段先做纯浏览器可运行游戏原型并验证玩法跑通;第二阶段接入 Genarrative 创作入口,用 AI 生成可试玩内容;第三阶段再打通后端数据库、发布、成绩和作品闭环。
**Architecture:** 第一阶段只在前端 runtime 内闭环,优先落 `src/games/bark-battle/` 与直达路由,不依赖后端和 SpacetimeDB。第二阶段在已有创作入口、Agent flow controller、结果页和 runtime shell 上接入 `bark-battle`AI 只生成配置化草稿,不承接正式业务真相。第三阶段按 `server-rs + Axum + SpacetimeDB` DDD 分层落库,前端只展示后端投影和调用后端 API。
**Tech Stack:** React 19、TypeScript、Vite、Vitest、Testing Library第一阶段优先 DOM/Canvas 原型,可在验证玩法后再引入 Phaser 3后端阶段使用 `server-rs`、Axum、SpacetimeDB、shared-contracts。
---
## 0. 当前上下文 / 假设
- 现有需求与技术文档:
- `docs/prd/BARK_BATTLE_BDD_2026-05-11.md`
- `docs/technical/BARK_BATTLE_2D_RUNTIME_TECHNICAL_PLAN_2026-05-11.md`
- `docs/technical/BARK_BATTLE_BACKEND_DDD_TECHNICAL_PLAN_2026-05-11.md`
- 用户明确要求阶段顺序:
1. 第一阶段:先制作纯浏览器运行的游戏原型,需要测试游戏功能是否能跑通。
2. 第二阶段:打通创作入口,使用 AI 赋能游戏内容创作。
3. 第三阶段:最后打通数据库落地。
- 因此本计划调整原技术方案中的落地优先级:
- 第一阶段不新增后端表、不接发布、不接作品架。
- 第一阶段可以用 mock / local draft 配置与直达路由 `/bark-battle` 完成 playable prototype。
- 第一阶段若 Phaser 依赖尚未安装,优先用 React DOM + Canvas/CSS 2D 原型跑通功能;待核心规则验证后再决定是否引入 Phaser避免第一阶段被依赖安装和素材管线阻塞。
- 当前仓库 `package.json` 还没有 `phaser` 依赖;如实现者选择 Phaser需要单独评估依赖引入、包体和测试影响。
- 本计划只写计划,不直接实现代码。
## 1. 总体分阶段验收口径
### Phase 1纯浏览器游戏原型
目标:打开本地前端路由即可玩到一局 `bark-battle`,并通过自动测试确认核心规则跑通。
必须满足:
- 可从 `/bark-battle` 进入独立原型页面。
- 不登录、不请求后端、不依赖数据库。
- 支持开发 mock input点击/按键/按钮可模拟音量峰值;有真实麦克风时可走 Web Audio。
- 能完成:权限/开始 → 校准或 mock 准备 → 倒计时 → 30 秒 playing → 结算 → 再来一局。
- 低于阈值输入不计数;有效叫声计数;能量条向玩家或对手移动;结算胜/负/平。
- 移动端至少能看到能量条、倒计时、双方狗狗、主要按钮和结算。
### Phase 2AI 创作入口
目标:创作者能从创作中心选择 `bark-battle`,用 AI 生成玩法配置草稿,并进入结果页试玩。
必须满足:
- 后端入口配置中出现 `bark-battle`,按开关展示/可点击。
- 前端类型分流、SelectionStage、工作台、结果页、runtime 入口齐全。
- AI 生成内容仅限配置化草稿标题、主题、狗狗外观描述、背景风格、难度、局长、AI 对手参数、提示文案 key 等。
- 生成结果可在本地 runtime 中试玩。
- 未落库前可先用 session/local state 保存草稿,但要清楚标识为“未发布草稿”。
### Phase 3数据库落地与正式作品闭环
目标:`bark-battle` 草稿、发布态配置、runtime start/finish、成绩和作品级游玩埋点都进入后端 DDD / SpacetimeDB 链路。
必须满足:
- `shared-contracts``module-bark-battle``spacetime-module``spacetime-client``api-server` 分层清晰。
- 发布为稳定作品 IDruntime 从后端读取发布态 config。
- start 成功写 `work_play_start``scope_kind=work``scope_id=稳定作品 ID`、metadata 包含 `playType/workId/sourceRoute/userId`
- finish 只上传派生指标,不保存原始麦克风音频、波形或可还原语音内容。
- 作品架/广场/分享/排行榜如启用,均来自后端投影。
---
## 2. Phase 1纯浏览器运行游戏原型
### Task 1.1:补齐阶段边界文档
**Objective:** 在现有技术方案中明确“先浏览器原型,后 AI 创作,最后数据库”的落地顺序,避免实现时过早接后端。
**Files:**
- Modify: `docs/technical/BARK_BATTLE_2D_RUNTIME_TECHNICAL_PLAN_2026-05-11.md`
- Modify: `docs/prd/BARK_BATTLE_BDD_2026-05-11.md`
**Steps:**
1. 在 runtime 技术方案中新增“三阶段落地顺序”小节。
2. 明确 Phase 1 不接后端、不接数据库、不接创作入口事实源。
3. 在 BDD 中补充“浏览器原型 smoke”验收场景。
4. 运行:
```bash
npm run check:encoding -- docs/prd/BARK_BATTLE_BDD_2026-05-11.md docs/technical/BARK_BATTLE_2D_RUNTIME_TECHNICAL_PLAN_2026-05-11.md
git diff --check
```
**Expected:** 编码检查和 diff 空白检查通过。
### Task 1.2:建立 Phase 1 目录骨架和类型
**Objective:** 建立不依赖 React/DOM/Web Audio 的核心类型,后续所有测试和 UI 都围绕这些类型。
**Files:**
- Create: `src/games/bark-battle/domain/BarkBattleTypes.ts`
- Create: `src/games/bark-battle/application/BarkBattleConfig.ts`
**Key design:**
- `BarkBattlePhase = 'permission' | 'calibration' | 'countdown' | 'playing' | 'finished' | 'unavailable'`
- `MicrophoneFailureReason` 覆盖已有文档中的 9 类失败原因。
- `BarkBattleSnapshot` 包含 `phase/uiState/errorReason/statusMessageKey/remainingMs/energy/player/opponent/winner/result/lastEvents`。
- `BarkBattleConfig` 包含 `roundDurationMs/drawThreshold/minBarkGapMs/minBarkDurationMs/maxBarkDurationMs/balanceFactor/calibrationMaxWaitMs`。
**Tests:**
- 本任务可先不写运行时逻辑,但需要让 typecheck 能引用这些类型。
**Validation:**
```bash
npm run typecheck
npm run check:encoding -- src/games/bark-battle/domain/BarkBattleTypes.ts src/games/bark-battle/application/BarkBattleConfig.ts
```
### Task 1.3TDD 实现叫声检测 BarkDetector
**Objective:** 用纯函数/纯类把音频样本转换为有效叫声事件。
**Files:**
- Create: `src/games/bark-battle/domain/BarkDetector.ts`
- Create: `src/games/bark-battle/domain/__tests__/BarkDetector.test.ts`
**Test cases:**
1. 超过阈值、持续时长合规、间隔足够时计为一次有效叫声。
2. 持续噪音不在每个 tick 无限计数。
3. 低于阈值的背景噪音不计数。
4. `minBarkGapMs` 内连续峰值不重复计数。
5. 过短脉冲不计数;过长持续声削弱为单段输入。
**Validation:**
```bash
npm run test -- --run src/games/bark-battle/domain/__tests__/BarkDetector.test.ts
npm run typecheck
```
### Task 1.4TDD 实现能量条 EnergyTugOfWar
**Objective:** 验证玩家/对手推动力能稳定改变 `energy`,并 clamp 到 `-100..100`。
**Files:**
- Create: `src/games/bark-battle/domain/EnergyTugOfWar.ts`
- Create: `src/games/bark-battle/domain/__tests__/EnergyTugOfWar.test.ts`
**Test cases:**
1. 玩家 power 高于对手时 `energy` 增加。
2. 对手 power 高于玩家时 `energy` 减少。
3. energy 不超过 `100`。
4. energy 不低于 `-100`。
5. power 相等时变化不超过浮点误差。
**Validation:**
```bash
npm run test -- --run src/games/bark-battle/domain/__tests__/EnergyTugOfWar.test.ts
npm run typecheck
```
### Task 1.5TDD 实现单局状态机 BarkBattleSession
**Objective:** 跑通 permission/calibration/countdown/playing/finished/unavailable 状态流转和结算。
**Files:**
- Create: `src/games/bark-battle/domain/BarkBattleSession.ts`
- Create: `src/games/bark-battle/domain/BarkBattleScoring.ts`
- Create: `src/games/bark-battle/domain/OpponentStrategy.ts`
- Create: `src/games/bark-battle/domain/__tests__/BarkBattleSession.test.ts`
- Create: `src/games/bark-battle/domain/__tests__/BarkBattleScoring.test.ts`
**Test cases:**
1. 校准完成后进入 countdown。
2. countdown 结束后进入 playing。
3. playing 中 `remainingMs` 随 tick 递减。
4. `remainingMs <= 0` 后进入 finished。
5. `energy > drawThreshold` 判定玩家胜。
6. `energy < -drawThreshold` 判定对手胜。
7. `abs(energy) <= drawThreshold` 判定平局。
8. finished 后新输入不再改变本局计数和能量。
**Validation:**
```bash
npm run test -- --run src/games/bark-battle/domain/__tests__/BarkBattleSession.test.ts src/games/bark-battle/domain/__tests__/BarkBattleScoring.test.ts
npm run typecheck
```
### Task 1.6:实现 mock-first Application Controller
**Objective:** 不依赖真实麦克风,先用 mock audio sample 驱动完整 snapshot。
**Files:**
- Create: `src/games/bark-battle/application/BarkBattleController.ts`
- Create: `src/games/bark-battle/application/BarkBattleSnapshotStore.ts`
- Create: `src/games/bark-battle/application/__tests__/BarkBattleController.test.ts`
**Behavior:**
- `startWithMockInput()` 进入校准完成或直接 countdown。
- `submitMockSample(sample)` 更新玩家输入。
- `tick(deltaMs)` 推进对手、能量条、倒计时。
- `restart()` 重置状态。
- `failMicrophone(reason)` 进入 `phase: 'unavailable'`,并设置 `errorReason/statusMessageKey`。
**Test cases:**
1. mock start 后能进入 countdown/playing。
2. 提交 mock 峰值后 bark count 增加。
3. tick 后 energy 可变化。
4. finish 后生成 result。
5. `failMicrophone('permission-denied')` 不进入 playing。
**Validation:**
```bash
npm run test -- --run src/games/bark-battle/application/__tests__/BarkBattleController.test.ts
npm run typecheck
```
### Task 1.7:实现浏览器原型 UI Shell不接平台
**Objective:** 提供 `/bark-battle` 可访问的 playable prototype。
**Files:**
- Create: `src/BarkBattlePlaygroundApp.tsx`
- Create: `src/games/bark-battle/ui/BarkBattleRuntimeShell.tsx`
- Create: `src/games/bark-battle/ui/BarkBattleHud.tsx`
- Create: `src/games/bark-battle/ui/BarkBattleResultPanel.tsx`
- Create: `src/games/bark-battle/ui/BarkBattleHud.css`
- Modify: `src/routing/appRoutes.tsx`
**Behavior:**
- 新增路由匹配:`/bark-battle`。
- 首屏只有清爽开始面板,不常驻大段规则。
- 提供开发原型按钮:开始、模拟叫声、模拟对手增强、再来一局。
- playing 画面展示:顶部能量条、倒计时、玩家/对手狗狗、叫声次数、麦克风/mock 状态。
- 结算面板独立居中,不追加在当前面板下方。
**UI constraints:**
- 移动端优先。
- 正常 playing 阶段不在 playfield 常驻规则说明。
- 大动效不遮挡顶部能量条和倒计时。
**Validation:**
```bash
npm run test -- --run src/games/bark-battle/ui/**/*.test.tsx
npm run typecheck
npm run dev:web
# 手动 smoke: 访问 /bark-battle → 开始 → 模拟叫声 → energy 变化 → 结算 → 再来一局
```
### Task 1.8:实现 HUD 组件测试
**Objective:** 自动验证核心 UI 状态,不只依赖人工试玩。
**Files:**
- Create: `src/games/bark-battle/ui/__tests__/BarkBattleHud.test.tsx`
- Create: `src/games/bark-battle/ui/__tests__/BarkBattleResultPanel.test.tsx`
**Test cases:**
1. playing 阶段展示倒计时和能量条。
2. energy 正值时玩家侧占比更大。
3. energy 负值时对手侧占比更大。
4. permission-denied 展示重试授权入口。
5. unsupported 不展示开始声控按钮。
6. finished 展示胜负、叫声次数、再来一局。
**Validation:**
```bash
npm run test -- --run src/games/bark-battle/ui/__tests__/BarkBattleHud.test.tsx src/games/bark-battle/ui/__tests__/BarkBattleResultPanel.test.tsx
npm run typecheck
```
### Task 1.9:接入真实 Web Audio可晚于 mock 原型)
**Objective:** 在支持麦克风的浏览器中真实采样并驱动 controller同时保留 mock fallback 便于测试。
**Files:**
- Create: `src/games/bark-battle/infrastructure/BrowserMicrophoneInput.ts`
- Create: `src/games/bark-battle/infrastructure/AudioAnalyserSampler.ts`
- Create: `src/games/bark-battle/infrastructure/MicrophonePermission.ts`
- Create: `src/games/bark-battle/infrastructure/__tests__/BrowserMicrophoneInput.test.ts`
- Create: `src/games/bark-battle/infrastructure/__tests__/AudioAnalyserSampler.test.ts`
**Behavior:**
- 用户点击开始后才请求麦克风。
- 用户手势后创建/resume `AudioContext`。
- 输出归一化 `BarkAudioSample`。
- 捕获并映射unsupported、permission-denied、non-secure-context、not-found、not-readable、audio-context-blocked、unknown。
- stop/restart/page unload 时停止 tracks。
**Validation:**
```bash
npm run test -- --run src/games/bark-battle/infrastructure/__tests__/BrowserMicrophoneInput.test.ts src/games/bark-battle/infrastructure/__tests__/AudioAnalyserSampler.test.ts
npm run typecheck
npm run dev:web
# 手动 smoke: 真实麦克风授权 → 校准 → 发声 → 结算
```
### Task 1.10Phase 1 收口验证
**Objective:** 确认“纯浏览器原型”已经可以交给产品/测试试玩。
**Commands:**
```bash
npm run test -- --run src/games/bark-battle/domain/**/*.test.ts src/games/bark-battle/application/**/*.test.ts src/games/bark-battle/infrastructure/**/*.test.ts src/games/bark-battle/ui/**/*.test.tsx
npm run typecheck
npm run lint:eslint
npm run check:encoding
npm run dev:web
```
**Manual smoke checklist:**
- [ ] `/bark-battle` 能打开。
- [ ] mock 模式可完整完成一局。
- [ ] 真实麦克风模式可授权、校准、发声、结算。
- [ ] 拒绝权限后不会进入 playing。
- [ ] 移动端窄屏能看到核心信息并能点击主要按钮。
- [ ] 再来一局不会继承上一局 energy/barkCount/result。
---
## 3. Phase 2打通创作入口用 AI 赋能内容创作
### Task 2.1:定义 `bark-battle` 草稿契约(前端本地版)
**Objective:** 在接后端前,先定义 AI 可生成的 runtime draft shape。
**Files:**
- Create: `packages/shared/src/contracts/barkBattle.ts`(临时前端共享类型,后端阶段再对齐 Rust shared-contracts
- Create: `src/services/bark-battle-creation/barkBattleDraftDefaults.ts`
- Create: `src/services/bark-battle-creation/barkBattleDraftValidation.ts`
**Draft fields:**
- `title`
- `description`
- `themePrompt`
- `playerDogName`
- `opponentDogName`
- `backgroundStyle`
- `difficulty`
- `roundDurationMs`
- `drawThreshold`
- `opponentConfig`
- `audioSensitivityPreset`
- `visualStyle`
**Validation:**
```bash
npm run test -- --run src/services/bark-battle-creation/**/*.test.ts
npm run typecheck
```
### Task 2.2:新增创作入口配置
**Objective:** 让 `bark-battle` 出现在创作中心入口中,但可通过后端入口配置开关控制。
**Files likely to change:**
- `server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs`
- `server-rs/crates/module-runtime/src/domain.rs`
- `server-rs/crates/module-runtime/src/application.rs`
- `server-rs/crates/shared-contracts/src/creation_entry_config.rs`
- `src/components/platform-entry/platformEntryCreationTypes.ts`
- `src/components/platform-entry/platformEntryCreationTypes.test.ts`
**Plan:**
1. 在入口 seed 中新增 `bark-battle`,首轮可设:`visible: true`、`open: true`(若需要灰度则 `open: false`)。
2. 前端展示派生只消费 API 返回,不恢复旧静态入口事实源。
3. 更新排序和锁定态测试。
**Validation:**
```bash
npm run test -- src/components/platform-entry/platformEntryCreationTypes.test.ts
npm run typecheck
cd server-rs && cargo check -p api-server -p spacetime-module --no-default-features
```
### Task 2.3:扩展 SelectionStage 与流程分流
**Objective:** 点击 `bark-battle` 入口后进入对应创作工作台。
**Files likely to change:**
- `src/components/platform-entry/platformEntryTypes.ts`
- `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
- `src/components/platform-entry/usePlatformCreationAgentFlowController.ts`(如复用通用 agent flow
**Stages:**
- `bark-battle-agent-workspace`
- `bark-battle-generating`(可选)
- `bark-battle-result`
- `bark-battle-runtime`
**Validation:**
```bash
npm run test -- src/components/platform-entry/**/*.test.tsx src/components/platform-entry/**/*.test.ts
npm run typecheck
```
### Task 2.4:实现 AI 创作工作台
**Objective:** 用对话式或表单式输入生成 `BarkBattleDraft`。
**Files:**
- Create: `src/components/bark-battle-creation/BarkBattleAgentWorkspace.tsx`
- Create: `src/services/bark-battle-creation/barkBattleCreationClient.ts`
- Create: `src/services/bark-battle-creation/barkBattlePromptBuilder.ts`
- Create: `src/services/bark-battle-creation/__tests__/barkBattlePromptBuilder.test.ts`
**Behavior:**
- 用户输入一句主题,例如“柴犬在赛博公园比谁叫得响”。
- AI 返回结构化草稿。
- 前端校验并填默认值,不让非法 roundDuration/difficulty 进入 runtime。
- 错误时保留用户输入和已生成草稿。
**Validation:**
```bash
npm run test -- --run src/services/bark-battle-creation/**/*.test.ts
npm run typecheck
```
### Task 2.5:实现结果页与试玩入口
**Objective:** AI 草稿生成后可查看、返回编辑、进入 Phase 1 runtime 试玩。
**Files:**
- Create: `src/components/bark-battle-result/BarkBattleResultView.tsx`
- Create: `src/components/bark-battle-result/BarkBattleResultView.test.tsx`
- Modify: `src/games/bark-battle/ui/BarkBattleRuntimeShell.tsx`(允许传入 draft config
**Behavior:**
- 展示标题、主题、狗狗名、背景风格、难度、局长。
- 提供“返回编辑”“试玩”按钮。
- 暂不展示发布按钮,或发布按钮显示为后端阶段能力。
**Validation:**
```bash
npm run test -- --run src/components/bark-battle-result/BarkBattleResultView.test.tsx
npm run typecheck
npm run dev:web
# 手动 smoke: 创作入口 → AI 草稿 → 结果页 → 试玩 → 返回编辑
```
### Task 2.6Phase 2 收口验证
**Commands:**
```bash
npm run test -- src/components/platform-entry/platformEntryCreationTypes.test.ts src/components/bark-battle-result/BarkBattleResultView.test.tsx src/services/bark-battle-creation/**/*.test.ts src/games/bark-battle/**/*.test.ts src/games/bark-battle/**/*.test.tsx
npm run typecheck
npm run lint:eslint
npm run check:encoding
```
**Manual smoke checklist:**
- [ ] 创作中心展示 `bark-battle`。
- [ ] 点击入口进入工作台。
- [ ] AI 可生成草稿。
- [ ] 草稿结果页可展示并返回编辑。
- [ ] 试玩使用草稿配置影响 runtime 表现。
- [ ] 未接数据库前不会假装发布成功。
---
## 4. Phase 3数据库落地与正式作品闭环
### Task 3.1:补齐 shared contracts
**Objective:** 前后端共享 bark-battle DTO避免前端手写正式契约漂移。
**Files likely to change:**
- `server-rs/crates/shared-contracts/src/bark_battle.rs`
- `server-rs/crates/shared-contracts/src/lib.rs`
- `packages/shared/src/contracts/barkBattle.ts`
**DTO:**
- `BarkBattleDraft`
- `BarkBattlePublishedConfig`
- `CreateBarkBattleSessionRequest/Response`
- `BarkBattleRuntimeStartRequest/Response`
- `BarkBattleRuntimeFinishRequest/Response`
- `BarkBattleRunResult`
- `BarkBattleScoreSummary`
- `BarkBattleLeaderboardEntry`
**Validation:**
```bash
npm run typecheck
cd server-rs && cargo check -p shared-contracts --no-default-features
```
### Task 3.2:新增 `module-bark-battle` 纯领域模块
**Objective:** 后端正式分数、配置校验、提交合法性不写在 api-server handler 里。
**Files:**
- Create: `server-rs/crates/module-bark-battle/`
- Modify: `server-rs/Cargo.toml`
**Responsibilities:**
- 配置合法性校验。
- run start/finish 状态约束。
- 派生指标范围校验。
- 分数与排行榜排序分计算。
- 不接 Axum、不接 SpacetimeDB、不接 HTTP。
**Validation:**
```bash
cd server-rs && cargo test -p module-bark-battle --no-default-features
cd server-rs && cargo check -p module-bark-battle --no-default-features
```
### Task 3.3SpacetimeDB 表、reducer、migration
**Objective:** 保存草稿、发布态配置、run、result、leaderboard 投影。
**Files likely to change:**
- `server-rs/crates/spacetime-module/src/runtime/bark_battle.rs`(或按现有模块目录命名)
- `server-rs/crates/spacetime-module/src/migration.rs`
- `server-rs/crates/spacetime-module/src/lib.rs`
- 生成绑定目录(通过命令生成,不手改生成物)
**Tables draft:**
- `bark_battle_draft`
- `bark_battle_published_config`
- `bark_battle_run`
- `bark_battle_run_result`
- `bark_battle_leaderboard_entry`
**Reducers/procedures:**
- `create_bark_battle_draft`
- `publish_bark_battle_config`
- `start_bark_battle_run`
- `finish_bark_battle_run`
- `list_bark_battle_leaderboard`
**Validation:**
```bash
npm run spacetime:generate
cd server-rs && cargo check -p spacetime-module --no-default-features
npm run check:server-rs-ddd
```
### Task 3.4spacetime-client facade
**Objective:** api-server 通过 facade 调用 SpacetimeDB不直接散落 reducer 细节。
**Files likely to change:**
- `server-rs/crates/spacetime-client/src/runtime.rs`
- `server-rs/crates/spacetime-client/src/mapper.rs`
- `server-rs/crates/spacetime-client/src/lib.rs`
**Validation:**
```bash
cd server-rs && cargo check -p spacetime-client --no-default-features
```
### Task 3.5api-server BFF 路由
**Objective:** 提供创作、发布态 runtime start/finish、leaderboard API。
**Files likely to change:**
- `server-rs/crates/api-server/src/bark_battle.rs`
- `server-rs/crates/api-server/src/main.rs` 或路由注册文件
**Routes draft:**
- `POST /api/bark-battle/sessions`
- `GET /api/bark-battle/sessions/:sessionId`
- `POST /api/bark-battle/runtime/start`
- `POST /api/bark-battle/runtime/finish`
- `GET /api/bark-battle/works/:workId/runtime-config`
- `GET /api/bark-battle/works/:workId/leaderboard`
**Tracking:**
- runtime start 成功后主动写 `work_play_start`。
- `scope_kind=work`。
- `scope_id=稳定作品 ID`。
- metadata 包含 `playType=bark-battle`、`workId`、`sourceRoute`、`userId`。
**Validation:**
```bash
npm run api-server
# 另一个终端检查 /healthz并执行对应 API smoke
cd server-rs && cargo check -p api-server --no-default-features
```
### Task 3.6:前端正式 client 与 runtime 切换
**Objective:** runtime 从本地草稿模式升级为可读取后端发布态 config并提交正式派生结果。
**Files likely to change:**
- Create: `src/services/bark-battle-runtime/barkBattleRuntimeClient.ts`
- Create: `src/services/bark-battle-works/barkBattleWorksClient.ts`
- Modify: `src/games/bark-battle/ui/BarkBattleRuntimeShell.tsx`
- Modify: `src/components/bark-battle-result/BarkBattleResultView.tsx`
- Modify: `src/components/custom-world-home/creationWorkShelf.ts`
- Modify: `src/components/custom-world-home/CustomWorldCreationHub.tsx`
- Modify: `src/services/publicWorkCode.ts`
**Behavior:**
- 本地 preview 仍可使用 draft config。
- 正式作品 runtime 必须先调用 start API拿 run token/session。
- finish 只提交派生 metrics。
- 发布后刷新作品架/广场。
**Validation:**
```bash
npm run test -- src/services/bark-battle-runtime/**/*.test.ts src/games/bark-battle/**/*.test.ts src/games/bark-battle/**/*.test.tsx
npm run typecheck
npm run check:encoding
```
### Task 3.7Phase 3 收口验证
**Commands:**
```bash
npm run test -- src/games/bark-battle/**/*.test.ts src/games/bark-battle/**/*.test.tsx src/services/bark-battle-runtime/**/*.test.ts src/services/bark-battle-creation/**/*.test.ts
npm run typecheck
npm run lint:eslint
npm run check:encoding
npm run check:server-rs-ddd
cd server-rs && cargo test -p module-bark-battle --no-default-features
cd server-rs && cargo check -p api-server -p spacetime-module -p spacetime-client -p shared-contracts --no-default-features
npm run api-server
```
**Manual smoke checklist:**
- [ ] 创作者生成并发布 bark-battle 作品。
- [ ] 玩家从作品页进入 runtime。
- [ ] start API 成功并写 `work_play_start`。
- [ ] 浏览器本地完成一局。
- [ ] finish API 只上传派生指标。
- [ ] 成绩/排行榜/作品架刷新来自后端投影。
- [ ] 拒绝麦克风权限时不会创建非法 finished result。
---
## 5. 文件清单总览
### Phase 1 likely files
- `src/routing/appRoutes.tsx`
- `src/BarkBattlePlaygroundApp.tsx`
- `src/games/bark-battle/domain/BarkBattleTypes.ts`
- `src/games/bark-battle/domain/BarkDetector.ts`
- `src/games/bark-battle/domain/EnergyTugOfWar.ts`
- `src/games/bark-battle/domain/BarkBattleSession.ts`
- `src/games/bark-battle/domain/BarkBattleScoring.ts`
- `src/games/bark-battle/domain/OpponentStrategy.ts`
- `src/games/bark-battle/application/BarkBattleConfig.ts`
- `src/games/bark-battle/application/BarkBattleController.ts`
- `src/games/bark-battle/application/BarkBattleSnapshotStore.ts`
- `src/games/bark-battle/infrastructure/BrowserMicrophoneInput.ts`
- `src/games/bark-battle/infrastructure/AudioAnalyserSampler.ts`
- `src/games/bark-battle/infrastructure/MicrophonePermission.ts`
- `src/games/bark-battle/ui/BarkBattleRuntimeShell.tsx`
- `src/games/bark-battle/ui/BarkBattleHud.tsx`
- `src/games/bark-battle/ui/BarkBattleResultPanel.tsx`
- `src/games/bark-battle/ui/BarkBattleHud.css`
### Phase 2 likely files
- `packages/shared/src/contracts/barkBattle.ts`
- `src/components/platform-entry/platformEntryTypes.ts`
- `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
- `src/components/platform-entry/platformEntryCreationTypes.ts`
- `src/components/bark-battle-creation/BarkBattleAgentWorkspace.tsx`
- `src/components/bark-battle-result/BarkBattleResultView.tsx`
- `src/services/bark-battle-creation/*`
### Phase 3 likely files
- `server-rs/crates/shared-contracts/src/bark_battle.rs`
- `server-rs/crates/module-bark-battle/*`
- `server-rs/crates/spacetime-module/src/runtime/bark_battle.rs`
- `server-rs/crates/spacetime-module/src/migration.rs`
- `server-rs/crates/spacetime-client/src/runtime.rs`
- `server-rs/crates/spacetime-client/src/mapper.rs`
- `server-rs/crates/api-server/src/bark_battle.rs`
- `src/services/bark-battle-runtime/*`
- `src/services/bark-battle-works/*`
- `src/components/custom-world-home/creationWorkShelf.ts`
- `src/services/publicWorkCode.ts`
---
## 6. 风险、取舍与开放问题
### 风险
1. **麦克风权限和移动端 AudioContext 差异大。** 需要 mock input 保底,否则自动化和本地开发会被真实设备阻塞。
2. **第一阶段过早引入 Phaser 可能拖慢验证。** 当前仓库没有 `phaser` 依赖;建议先用 DOM/Canvas 跑通玩法,再决定是否引入 Phaser。
3. **AI 草稿和正式发布配置容易漂移。** Phase 2 临时 TS 类型必须在 Phase 3 与 Rust shared-contracts 对齐。
4. **不能保存原始音频。** 后端阶段只能保存派生指标,任何音频片段、波形、频谱明细都不应落库。
5. **入口配置事实源在后端/SpacetimeDB。** Phase 2 接入口时不要恢复旧前端静态入口配置。
### 取舍
- Phase 1 先把“游戏是否好玩、功能是否跑通”作为第一目标,不追求正式作品闭环。
- Phase 2 让 AI 生成内容配置,而不是让 AI 直接生成任意代码或不受控规则。
- Phase 3 再把正式业务真相交给后端,避免前端 runtime 先背上发布、成绩、排行榜的复杂度。
### 开放问题
1. Phase 1 是否必须使用 Phaser如果只是验证玩法可先使用 DOM/CSS/Canvas 原型,后续再替换 renderer。
2. `bark-battle` 的正式中文名是否固定为“汪汪声浪大作战”?如果名称要改,需先统一文档、入口配置和分享标题。
3. AI 创作阶段是否需要生成图片/狗狗视觉资产,还是只生成风格描述和使用占位素材?
4. 是否需要排行榜作为 Phase 3 必选,还是作为数据库落地后的增强项?
5. 真实麦克风 smoke 需要哪些目标设备Chrome 桌面、Android Chrome、iOS Safari 是否都纳入首批验收?
---
## 7. 建议执行方式
1. 先按 Phase 1 执行,且每个 domain/application task 坚持 TDD先失败测试再实现。
2. Phase 1 合并前不要接数据库,不要新增后端表,不要把入口配置切到 open。
3. Phase 1 验证通过后,让产品/团队试玩 `/bark-battle`,确认玩法数值和 UI 方向。
4. 再进入 Phase 2把 AI 创作工作台接到同一个 runtime draft config。
5. 最后进入 Phase 3按后端 DDD 文档做数据库、发布、成绩和追踪闭环。

View File

@@ -1,310 +0,0 @@
# K6 作品列表压测计划(使用 spacetime-migration-7.json 作为数据源)
## 目标
使用 K6 对 Genarrative 的“作品列表”相关接口进行压测,并将用户提供的 `spacetime-migration-7.json` 作为压测数据源;数据处理时**只导入作品列表相关数据**,不导入用户、会话、钱包、埋点、运行存档等非作品表,避免把敏感或无关数据带入压测环境。
## 当前上下文
- 工作区:`/c/proj/Genarrative`
- 原始迁移文件:`C:\Users\DSK\AppData\Local\hermes\cache\documents\doc_150e84029b2d_spacetime-migration-7.json`
- 已确认原始迁移文件结构:
- `schema_version = 1`
- `tables = 53`
- 作品相关表中当前有数据的重点表:
- `puzzle_work_profile`80 行
- `custom_world_profile`1 行
- `match3d_work_profile`0 行
- `big_fish_*`:当前样本中相关表为 0 行
- 原始文件还包含 `user_account``auth_identity``refresh_session``profile_wallet_ledger``asset_object`、运行记录等数据,压测导入时必须过滤。
- 当前仓库未发现现成 K6 脚本或 `k6` 相关文件,需要新增压测脚本与数据提取脚本。
- `package.json` 当前有 `dev/dev:rust/test/check` 等脚本,未发现 K6 npm script。
## 范围约束
### 本次只导入/使用
1. 作品列表表:
- `puzzle_work_profile`
- `custom_world_profile`
- 后续若接口覆盖其他玩法,可扩展:
- `match3d_work_profile`
- `square_hole_work_profile`(以实际 SpacetimeDB 表名为准)
- `big_fish_work_profile`(以实际 SpacetimeDB 表名为准)
- `visual_novel_work_profile`(以实际 SpacetimeDB 表名为准)
2. 为作品列表卡片展示所需的最小字段:
- 稳定 ID`profile_id``work_id``public_work_code`
- 标题:`work_title` / `level_name` / `world_name`
- 描述:`work_description` / `summary` / `summary_text` / `subtitle`
- 作者:`owner_user_id``author_display_name``author_public_user_code`
- 封面:`cover_image_src``cover_asset_id`(如果接口只返回 asset id则压测阶段不额外导入二进制 asset
- 状态与计数:`publication_status``published_at``play_count``like_count``remix_count`
- 作品内容摘要:`levels_json``profile_payload_json``theme_tags_json` 等列表渲染或进入作品详情可能需要的 JSON 字段
### 本次不导入/不使用
- 认证与账号:`user_account``auth_identity``refresh_session``auth_store_snapshot`
- 用户资产与钱包:`profile_wallet_ledger``profile_dashboard_state``profile_redeem_*``profile_invite_*`
- 游玩历史/存档/运行态:`profile_played_world``public_work_play_daily_stat``puzzle_runtime_run``profile_save_archive``runtime_snapshot`
- AI 任务过程:`ai_task``ai_task_stage``ai_text_chunk`
- asset 二进制与绑定:`asset_object``asset_entity_binding`,除非后续确认作品列表接口强依赖它们;即便需要,也只导入作品列表封面所需的最小 metadata不导入原始大对象。
## 推荐目录与文件
建议新增:
```text
.hermes/plans/2026-05-11_195214-k6-works-list-load-test-plan.md # 本计划
scripts/loadtest/extract-works-list-data.mjs # 从迁移文件提取作品列表数据
scripts/loadtest/k6-works-list.js # K6 压测脚本
scripts/loadtest/data/works-list.sample.json # 过滤后的样例数据(不要提交敏感原始迁移全量)
scripts/loadtest/README.md # 执行说明与指标阈值
```
可选新增 npm scripts
```json
{
"loadtest:extract-works": "node scripts/loadtest/extract-works-list-data.mjs",
"loadtest:k6:works": "k6 run scripts/loadtest/k6-works-list.js"
}
```
## 数据提取方案
### 输入
默认读取:
```bash
node scripts/loadtest/extract-works-list-data.mjs \
--input "C:/Users/DSK/AppData/Local/hermes/cache/documents/doc_150e84029b2d_spacetime-migration-7.json" \
--output scripts/loadtest/data/works-list.local.json
```
### 输出结构
建议输出为 K6 直接可读的 JSON
```json
{
"source": "spacetime-migration-7.json",
"generatedAt": "<iso datetime>",
"tables": {
"puzzle_work_profile": [
{
"profile_id": "...",
"work_id": "...",
"owner_user_id": "...",
"work_title": "...",
"work_description": "...",
"publication_status": "Published",
"published_at": { "__timestamp_micros_since_unix_epoch__": 0 },
"play_count": 0,
"like_count": 0,
"levels_json": "..."
}
],
"custom_world_profile": []
},
"workIds": {
"puzzle": ["<profile_id>"],
"customWorld": ["<profile_id>"]
}
}
```
### 过滤原则
1.`tables[].name` 白名单过滤,只保留作品 profile 表。
2. 对每个 row 再按字段白名单过滤避免误带账号、手机号、token、钱包流水等字段。
3. 对特别大的字段进行处理:
- `cover_image_src` 如果是 `data:image/...base64`,默认替换为占位符或截断,避免压测数据文件过大。
- `levels_json``profile_payload_json` 保留原文,但可以记录大小;如果过大,再提供 `--compact` 选项只保留摘要。
4. 输出 `.local.json` 默认加入 `.gitignore`;如果要提交样例数据,只提交脱敏/裁剪后的 `works-list.sample.json`
## K6 压测接口矩阵
需要先确认本地 api-server 实际端口。默认以 `http://127.0.0.1:8787` 为例,实际运行时通过环境变量覆盖:
```bash
BASE_URL=http://127.0.0.1:<actual-api-port> k6 run scripts/loadtest/k6-works-list.js
```
初版建议覆盖以下“作品列表”读接口,具体路径以仓库服务端路由为准,实施时需要通过搜索 api-server 路由确认:
| 场景 | 目的 | 候选路径 |
| --- | --- | --- |
| 拼图作品列表 | 作品列表主场景之一,当前数据量最多 | `/api/creation/puzzle/works` 或实际 puzzle works list route |
| RPG/自定义世界作品列表 | 使用 `custom_world_profile` 数据 | `/api/creation/custom-world/works` 或实际 custom world works route |
| 作品详情/启动前读取 | 模拟用户从列表点进作品 | `/api/creation/*/works/:profileId``/api/runtime/*/works/:profileId` |
| 公开作品库 | 如果首页/发现页依赖 | `/api/runtime/*/works` 或 gallery/list route |
> 注意:不要凭空固定 endpoint。实施阶段先用 `search_files` / 路由源码确认真实路径,再写入 K6 脚本。
## K6 场景设计
### 阶段 1基线 smoke
目的:确认脚本、数据和目标服务可用。
```js
export const options = {
scenarios: {
smoke: {
executor: 'constant-vus',
vus: 1,
duration: '30s'
}
},
thresholds: {
http_req_failed: ['rate<0.01'],
http_req_duration: ['p(95)<800']
}
};
```
### 阶段 2常规读压
目的:模拟日常列表浏览。
- `constant-vus`: 10/25/50 三档
- 每个 VU 随机选择作品类型和列表分页参数
- `sleep(0.5~2s)` 模拟用户停留
- 阈值建议:
- `http_req_failed < 1%`
- `p95 < 800ms`
- `p99 < 1500ms`
### 阶段 3峰值/突刺
目的:模拟首页入口或活动导致的作品列表突增。
- `ramping-arrival-rate`
- 从 5 RPS 增长到 100 RPS维持 2~5 分钟,再降回
- 单独输出 `checks`:列表接口状态码、响应 JSON shape、items 数量
### 阶段 4容量探索
目的:找瓶颈,不作为每次回归必跑。
- 每轮提升 RPS 或 VU
- 观察api-server CPU/内存、SpacetimeDB 日志、错误率、p95/p99
- 一旦 `http_req_failed >= 5%` 或 p95 持续超过 2s停止继续加压并记录容量点。
## K6 脚本设计要点
1. 使用 `SharedArray` 加载 `works-list.local.json`,避免每个 VU 重复解析大 JSON。
2. 基于数据源里的 `profile_id` / `work_id` 随机抽样,保证请求覆盖真实作品 ID。
3. 对列表接口添加分页/排序 query例如
- `?limit=20&offset=0`
- `?pageSize=20&cursor=...`(以真实 API 为准)
4. 使用 `check()` 验证:
- HTTP 200
- 响应体是 JSON
- `items``works` 是数组
- 列表项包含 `profileId/profile_id`、标题字段、状态字段
5. 使用 `Trend` / `Rate` 细分指标:
- `works_list_duration`
- `works_detail_duration`
- `works_list_shape_error_rate`
6. 支持环境变量:
```bash
BASE_URL=http://127.0.0.1:8787 \
WORKS_DATA=scripts/loadtest/data/works-list.local.json \
SCENARIO=baseline \
k6 run scripts/loadtest/k6-works-list.js
```
## 实施步骤
1. **确认路由**
- 搜索 api-server / BFF 的作品列表路由。
- 明确各玩法对应 endpoint、鉴权要求、分页参数、返回字段。
2. **实现数据提取脚本**
- 新增 `scripts/loadtest/extract-works-list-data.mjs`
- 只按表白名单读取作品列表 profile 表。
- 对字段做白名单与脱敏/截断。
- 输出 `works-list.local.json`
3. **生成本地压测数据**
- 用用户提供的迁移文件生成 `scripts/loadtest/data/works-list.local.json`
- 验证输出只包含作品表和作品字段。
4. **实现 K6 脚本**
- 新增 `scripts/loadtest/k6-works-list.js`
- 支持 `BASE_URL``WORKS_DATA``SCENARIO`
- 覆盖列表接口,必要时增加详情/启动前读取接口。
5. **新增执行说明**
-`scripts/loadtest/README.md` 写明:安装 K6、启动本地 dev 栈、提取数据、运行 smoke/baseline/spike、查看结果。
6. **本地验证**
- 启动 Genarrative dev 栈;注意端口可能漂移,使用实际 api-server 端口。
- 跑 smoke`SCENARIO=smoke`
- 确认失败率、p95、响应 shape。
7. **可选集成 npm scripts**
- 如果团队希望标准化入口,再加入 `package.json` scripts。
8. **记录结果**
- 将 smoke/baseline/spike 的结果摘要追加到 `scripts/loadtest/README.md` 或单独保存到 `.hermes/plans/` 的结果文档中。
## 启动与运行建议
本地服务启动按当前 Genarrative dev 栈约定:
```bash
npm run dev
```
如果 SpacetimeDB/API/Vite 端口被占用,项目脚本会寻找可用端口;压测时必须从启动日志中读取实际 api-server 地址,并传给 K6
```bash
BASE_URL=http://127.0.0.1:<actual-api-port> \
WORKS_DATA=scripts/loadtest/data/works-list.local.json \
SCENARIO=smoke \
k6 run scripts/loadtest/k6-works-list.js
```
## 验证标准
### 数据源验证
- `works-list.local.json` 中只出现作品 profile 表。
- 不出现以下字段或内容:
- `password_hash`
- `refresh_token_hash`
- `phone_number_e164`
- `phone_number_masked`
- `wallet_ledger_id`
- `auth_identity`
- `user_account`
- `puzzle_work_profile` 行数应接近原始文件中的 80 行。
- `custom_world_profile` 行数应接近原始文件中的 1 行。
### K6 smoke 验证
- 所有目标接口返回 2xx。
- `http_req_failed < 1%`
- 响应 JSON shape 与 shared contracts 对齐:`items``works` 数组。
- K6 输出中能区分不同 endpoint 的耗时。
### 性能阈值初稿
- Smoke`p95 < 800ms`,失败率 `< 1%`
- Baseline`p95 < 1000ms``p99 < 2000ms`,失败率 `< 1%`
- Spike允许短暂 p95 抖动,但 1 分钟内应恢复;失败率 `< 5%`
阈值后续需要结合本地机器性能、SpacetimeDB 本地模式和正式部署规格调整。
## 风险与注意事项
1. **原始迁移文件包含敏感数据。** 必须只提取作品列表白名单字段,禁止把原始 JSON 全量提交到仓库。
2. **base64 封面可能导致压测数据膨胀。** 默认截断或替换为占位符,除非本次明确要测封面 payload 对响应体积的影响。
3. **本地 SpacetimeDB 与 api-server 端口会漂移。** 不要硬编码端口,运行时通过 `BASE_URL` 注入。
4. **列表接口可能需要鉴权。** 若实际接口要求登录,不要导入真实 refresh session应使用本地测试账号或专门的压测 token 生成流程。
5. **作品表名/接口路径可能与候选名称不完全一致。** 实施前必须以源码路由为准。
6. **本计划仅保存压测方案,不执行实际压测。** 后续执行时再创建/修改脚本、导出过滤数据、跑 K6 并记录结果。
## 开放问题
1. 压测目标是本地 dev 栈、测试环境,还是预发/生产只读接口?不同环境阈值和安全边界不同。
2. “作品列表”是否只包含拼图和自定义世界,还是要覆盖 match3d、square-hole、big-fish、visual-novel 的统一列表入口?
3. 是否允许使用专门压测账号/token如果接口无鉴权则无需处理。
4. 是否需要测封面/asset 加载,还是只测作品列表 JSON API

View File

@@ -1,447 +0,0 @@
# Genarrative 容灾方案设计计划
> **For Hermes:** Use subagent-driven-development skill to implement this plan task-by-task.
**Goal:** 基于当前 Genarrative 单机生产部署、Jenkins 流水线、SpacetimeDB 与 Rust `api-server` 架构,补齐一套可落地、可演练、可审计的容灾方案。
**Architecture:** 首版容灾不引入复杂多活系统,优先围绕现有 `systemd + Nginx + SpacetimeDB + api-server + Jenkins` 单机生产推荐方案做“备份可恢复、版本可回滚、故障可切换、演练可复盘”。方案采用分层容灾入口层、静态资源层、API 服务层、SpacetimeDB 数据层、外部服务与密钥层、Jenkins/发布链路层。
**Tech Stack:** Nginx、systemd、SpacetimeDB self-hosting、Rust `api-server` / Axum、Jenkins Pipeline、Shell/Node.js 运维脚本、仓库 `deploy/``docs/technical/` 文档体系。
---
## 1. 当前上下文与已确认事实
### 1.1 当前生产部署口径
来自 `docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md` 的现状:
- 生产为单机推荐方案,不使用 Docker。
- 公网入口为 Nginx负责 HTTPS、静态站点、后台静态页面、维护页、`/admin/api/` 与临时 `/api/*` 反向代理。
- SpacetimeDB 作为 systemd 服务运行:
- `spacetimedb.service`
- 监听:`127.0.0.1:3101`
- 数据根目录:`/stdb`
- Rust `api-server` 作为 systemd 服务运行:
- `genarrative-api.service`
- 监听:`127.0.0.1:8082`
- 环境文件:`/etc/genarrative/api-server.env`
- 静态站点发布到 release/current 目录:
- `/opt/genarrative/releases/<version>/`
- `/opt/genarrative/current`
- `/srv/genarrative/web`
- 已有维护模式:
- 开关文件:`/var/lib/genarrative/maintenance/enabled`
- API 发布、SpacetimeDB 模块发布、数据库导入、服务器配置变更必须进入维护模式。
- 已有数据库导入导出 Jenkins Job
- `Genarrative-Database-Export`
- `Genarrative-Database-Import`
- 对应文件:`jenkins/Jenkinsfile.production-database-export``jenkins/Jenkinsfile.production-database-import`
- 已有回滚基本口径:
- Web 回滚:切 `/srv/genarrative/web``/opt/genarrative/current` 到上一版本并 reload Nginx。
- API 回滚:切 `/opt/genarrative/current` 到上一版本并重启 `genarrative-api.service`
- SpacetimeDB 模块回滚:发布上一版本 `spacetime_module.wasm`
- 数据回滚:使用导入流水线恢复指定备份,必须进入维护模式。
### 1.2 关键风险
- 当前是单机生产拓扑,单机磁盘、系统盘、`/stdb`、Nginx 或公网 IP 故障会造成整体不可用。
- SpacetimeDB 是核心业务真相,容灾重点必须围绕 `/stdb`、数据库导出产物、schema 迁移与导入验证。
- `/etc/genarrative/api-server.env` 持有生产密钥,不能进入 Git也不能写进普通备份明文归档。
- Jenkins controller/agent 同时承担构建、发布、备份、导入导出编排Jenkins 不可用时仍需要有最小人工恢复路径。
- 外部 LLM、图片、语音、3D 网关不是本仓库可控系统,容灾只能做到配置降级、超时隔离、能力熔断与可观测告警。
---
## 2. 容灾目标
### 2.1 恢复目标建议
| 灾难类型 | 目标 RTO | 目标 RPO | 首版策略 |
| --- | ---: | ---: | --- |
| Web 静态资源发布失败 | 5 分钟 | 0 | release/current 原子切换回滚 |
| API 发布失败 | 10 分钟 | 0 | 维护模式 + 上一版二进制回滚 |
| SpacetimeDB wasm 发布失败 | 15 分钟 | 0 或按迁移前备份 | 发布前导出 + 上一版 wasm 回滚 |
| 数据误写 / 迁移失败 | 30-60 分钟 | 最近一次导出点 | 导入流水线从备份恢复 |
| 生产机磁盘损坏 | 2-4 小时 | 最近一次异地备份 | 新机器 provision + 拉取 release 包 + 恢复数据库 |
| Jenkins controller 不可用 | 1-2 小时 | 不影响线上数据 | 手工脚本恢复 + Jenkins 备份恢复 |
| 第三方模型网关不可用 | 5-15 分钟内降级 | 不丢核心数据 | 配置切换 / 功能熔断 / 队列失败可重试 |
### 2.2 首版不做
- 不做跨地域双活写入。
- 不做 SpacetimeDB 在线主从复制,除非后续官方能力与项目压测验证支持。
- 不让前端绕过 `api-server` 直接承担正式业务真相。
- 不把生产密钥、Token、数据库 dump、Jenkins secret 写入 Git。
- 不恢复旧 `server-node`、Express、PostgreSQL 或 Docker 一体化部署方案。
---
## 3. 总体容灾设计
### 3.1 分层策略
1. **入口层Nginx / DNS / HTTPS**
- 保留 Nginx 配置模板在 Git`deploy/nginx/genarrative.conf``deploy/nginx/genarrative-dev-http.conf`
- 为 release 环境建立 Nginx 配置备份与证书恢复流程。
- 明确 DNS 切换预案:生产机不可恢复时,将域名指向灾备机公网 IP。
2. **静态资源层Web / Admin Web**
- 依赖 `web.tar.gz``web.tar.gz.sha256``release-manifest.json`
- 保留最近 N 个 release 目录与构建产物指针。
- 回滚只切软链,不重新构建。
3. **API 服务层Rust `api-server`**
- 依赖归档的 `api-server` 二进制、checksum、`release-manifest.json`
- `/etc/genarrative/api-server.env` 通过加密备份或密钥管理恢复,不进入 release 包。
- systemd unit 由 `deploy/systemd/genarrative-api.service` 重新安装。
4. **数据层SpacetimeDB**
- 每次高风险发布前强制导出数据库。
- 定时导出:建议每天至少 1 次;高活跃期可每 4 小时 1 次。
- 导出产物同时保存在Jenkins 归档 + 生产机 `SERVER_BACKUP_DIRECTORY` + 异地对象存储/备份机。
- 导入前自动生成安全备份,保留当前实现口径。
5. **发布编排层Jenkins**
- Jenkins Job、Jenkinsfile 在 Git 中可恢复。
- Jenkins controller 配置、凭据、插件清单需要额外备份。
- 发布 agent 使用 inbound + systemd 自恢复agent secret 仅存在目标机或 Jenkins 凭据。
6. **密钥与外部服务层**
- `/etc/genarrative/api-server.env`、Jenkins Secret Text、SSH PEM、agent secret 不进 Git。
- 制定密钥清单和恢复责任人,但不在仓库记录明文。
- 外部服务配置按 `docs/technical/API_SERVER_EXTERNAL_SERVICE_ENV_CONFIG_2026-05-07.md` 维护必配项。
---
## 4. 建议新增/更新的文档
### Task 1: 新增生产容灾技术方案文档
**Objective:** 形成团队可共享、可执行的容灾总纲。
**Files:**
- Create: `docs/technical/PRODUCTION_DISASTER_RECOVERY_PLAN_2026-05-11.md`
- Modify: `docs/technical/README.md`(若已有技术索引,应加入该文档入口)
- Optional Modify: `.hermes/shared-memory/project-overview.md`(只加稳定索引,不写敏感信息)
**文档必须覆盖:**
1. 容灾目标RTO/RPO 表。
2. 生产资产清单Nginx、systemd、release/current、`/stdb``/etc/genarrative/api-server.env`、Jenkins、构建产物。
3. 备份策略:
- 数据库导出。
- release 产物保留。
- Nginx/systemd/env 配置备份。
- Jenkins 配置备份。
4. 恢复流程:
- Web 回滚。
- API 回滚。
- Stdb module 回滚。
- 数据恢复。
- 整机重建。
5. 演练计划:每月一次数据库恢复演练,每季度一次整机重建演练。
6. 安全边界:密钥不进 Git备份加密最小权限。
7. 验收命令与人工检查清单。
**Verification:**
```bash
npm run check:encoding
```
Expected: PASS无中文乱码、无 BOM/CRLF 问题。
---
## 5. 建议新增/更新的脚本与流水线
### Task 2: 增强数据库定时备份流水线
**Objective:** 把现有人工导出扩展为可定时执行、可异地保存、可审计的备份流程。
**Files:**
- Modify: `jenkins/Jenkinsfile.production-database-export`
- Modify: `docs/technical/PRODUCTION_DISASTER_RECOVERY_PLAN_2026-05-11.md`
- Optional Create: `scripts/deploy/production-backup-sync.sh`
**Implementation notes:**
- 在 Jenkins Job 中保留人工触发能力,同时建议配置 cron
- development每天凌晨。
- release每天凌晨或业务低峰。
- 增加备份命名规范:
- `spacetime-migration-<database>-<yyyyMMdd-HHmmss>-<source_commit>.json`
- 增加 `SERVER_BACKUP_DIRECTORY` 默认建议:
- `/var/backups/genarrative/spacetimedb/<database>/`
- 增加备份保留策略:
- 本机保留 7-14 天。
- 异地保留 30-90 天。
- 如实现 `production-backup-sync.sh`,只做同步框架,不硬编码真实 bucket、账号、endpoint 或密钥。
**Verification:**
```bash
bash -n scripts/deploy/production-backup-sync.sh
npm run check:encoding
```
Expected: shell 语法通过;文档编码检查通过。
---
### Task 3: 增加灾备恢复 Runbook
**Objective:** 在真正故障时不依赖临场推理,按清单执行恢复。
**Files:**
- Create: `docs/operations/PRODUCTION_DR_RUNBOOK_2026-05-11.md`
- Modify: `docs/operations/README.md`(如果存在)
**Runbook sections:**
1. 故障分级P0/P1/P2。
2. 第一响应:
- 判断 Nginx 是否在线。
- 判断 `genarrative-api.service` 是否在线。
- 判断 `spacetimedb.service` 是否在线。
- 判断磁盘是否满。
- 判断 Jenkins agent 是否在线。
3. 快速止血:
- 开维护模式。
- 禁止继续发布。
- 保留现场日志。
4. 回滚流程:
- Web 回滚命令。
- API 回滚命令。
- Stdb wasm 回滚命令。
5. 数据恢复流程:
- 选择备份。
- dry-run 导入。
- 确认导入。
- smoke test。
6. 整机重建流程:
- 新机器 provision。
- 恢复 `/etc/genarrative/api-server.env`
- 恢复 SpacetimeDB 数据。
- 发布最近稳定 release。
- DNS 切换。
7. 复盘模板。
**Verification:**
```bash
npm run check:encoding
```
Expected: PASS。
---
### Task 4: 增加备份健康检查与恢复演练记录模板
**Objective:** 防止“有备份但不可恢复”。
**Files:**
- Create: `docs/operations/DR_DRILL_REPORT_TEMPLATE.md`
- Optional Create: `scripts/deploy/verify-database-backup.sh`
- Modify: `docs/technical/PRODUCTION_DISASTER_RECOVERY_PLAN_2026-05-11.md`
**建议检查项:**
- 备份文件存在且大小非 0。
- 备份文件 checksum 可验证。
- 备份文件可被 `Genarrative-Database-Import` dry-run 解析。
- 最近一次备份时间未超过 RPO 阈值。
- 导入后 `/healthz` 可用。
- 首页、后台登录页、关键 API smoke 可用。
**Verification:**
```bash
bash -n scripts/deploy/verify-database-backup.sh
npm run check:encoding
```
Expected: PASS。
---
## 6. 具体恢复流程草案
### 6.1 Web 静态资源回滚
1. 进入目标机。
2. 查看 release 目录:`/opt/genarrative/releases/`
3. 选择上一个稳定版本。
4. 切换 `/srv/genarrative/web``/opt/genarrative/current` 软链。
5. 执行 Nginx 配置检查与 reload。
6. 访问首页与后台静态入口。
验收:
- `/` 返回最新稳定页面。
- `/admin/` 返回后台页面。
- 静态资源无 404。
### 6.2 API 回滚
1. 开维护模式。
2.`/opt/genarrative/current` 到上一版包含稳定 `api-server` 的 release。
3. 重启 `genarrative-api.service`
4. 本机检查 `http://127.0.0.1:8082/healthz`
5. 检查 Nginx 反代路径。
6. 解除维护模式。
验收:
- `systemctl status genarrative-api.service` 正常。
- `/healthz` 正常。
- 后台 `/admin/api/*` 基础接口正常。
### 6.3 SpacetimeDB 模块回滚
1. 开维护模式。
2. 确认目标数据库名与当前 API 环境一致:`GENARRATIVE_SPACETIME_DATABASE`
3. 选择上一版 `spacetime_module.wasm`
4. 使用 `spacetimedb` 服务用户发布上一版 wasm。
5. 重启或检查 `spacetimedb.service`
6. 检查 `api-server` 对目标数据库访问。
7. 解除维护模式。
注意:如果 schema 已迁移且旧 wasm 不兼容当前数据,需要走数据恢复,不应直接盲目发布旧 wasm。
### 6.4 数据恢复
1. 开维护模式。
2. 从 Jenkins 归档或 `SERVER_BACKUP_DIRECTORY` 选择备份。
3. 先执行导入 dry-run。
4. 真正导入前生成当前数据库安全备份。
5. 执行导入。
6. 执行 smoke test。
7. 解除维护模式。
必须记录:
- 备份文件名。
- 来源 Job/build number。
- 恢复目标 database。
- 恢复开始/结束时间。
- 恢复后验证结果。
### 6.5 整机重建
1. 准备新 Linux 机器。
2. 接入 Jenkins release deploy agent或准备人工 SSH 运维路径。
3. 运行 `Genarrative-Server-Provision`
- 创建用户和目录。
- 安装 SpacetimeDB。
- 安装 systemd unit。
- 安装 Nginx 配置。
4. 恢复 `/etc/genarrative/api-server.env`
5. 发布最近稳定 Web/API/Stdb 产物。
6. 导入最近一次有效数据库备份。
7. smoke test。
8. 切 DNS。
9. 观察 30-60 分钟。
---
## 7. 文件可能变更清单
首版落地建议按以下文件收口:
- Create: `docs/technical/PRODUCTION_DISASTER_RECOVERY_PLAN_2026-05-11.md`
- Create: `docs/operations/PRODUCTION_DR_RUNBOOK_2026-05-11.md`
- Create: `docs/operations/DR_DRILL_REPORT_TEMPLATE.md`
- Modify: `docs/technical/README.md`
- Modify: `docs/operations/README.md`(若存在)
- Modify: `.hermes/shared-memory/project-overview.md`(仅增加文档索引)
- Optional Modify: `jenkins/Jenkinsfile.production-database-export`
- Optional Modify: `jenkins/Jenkinsfile.production-database-import`
- Optional Create: `scripts/deploy/production-backup-sync.sh`
- Optional Create: `scripts/deploy/verify-database-backup.sh`
---
## 8. 测试与验收
### 8.1 文档与编码
```bash
npm run check:encoding
```
Expected: PASS。
### 8.2 Shell 脚本语法
如新增 shell 脚本:
```bash
bash -n scripts/deploy/production-backup-sync.sh
bash -n scripts/deploy/verify-database-backup.sh
```
Expected: PASS。
### 8.3 Jenkinsfile 静态检查
建议在 Jenkins UI 或本地 Jenkins Pipeline Linter 中检查:
- `jenkins/Jenkinsfile.production-database-export`
- `jenkins/Jenkinsfile.production-database-import`
Expected: Pipeline syntax valid。
### 8.4 演练验收
至少完成一次 development 目标演练:
1. 触发 `Genarrative-Database-Export`
2. 确认备份产物存在并归档。
3. 使用 `Genarrative-Database-Import` dry-run 验证备份可解析。
4. 不覆盖生产数据的前提下,记录演练报告。
release 目标演练应在业务低峰进行,并先确认通知渠道可用。
---
## 9. 风险、取舍与开放问题
### 9.1 风险
- 单机生产仍存在物理机级单点故障,首版只能通过“快速重建 + 异地备份”降低恢复时间。
- SpacetimeDB schema 回滚不一定可逆,必须把发布前备份作为强约束。
- Jenkins controller 若在本地 Windowscontroller 自身备份和恢复需要单独制定,不应只依赖 agent 自恢复。
- 外部模型网关失败可能影响创作能力,但不应影响已发布作品浏览和后台基础能力。
### 9.2 取舍
- 选择先做可执行 runbook 和备份恢复演练,而不是直接引入复杂多活。
- 选择继续复用现有 Jenkins 导入导出流水线,降低工程改造风险。
- 选择不把密钥恢复细节写死到 Git 文档,避免泄露。
### 9.3 开放问题
1. release 环境是否已经有独立备份机或对象存储?如果有,需要补充备份同步目标,但不能提交密钥。
2. Jenkins controller 的 `JENKINS_HOME` 当前实际部署在哪里?是否已有周期备份?
3. 生产域名 DNS TTL 当前是多少?是否可降低到适合故障切换的值?
4. `/stdb` 所在磁盘是否独立于系统盘?是否已有磁盘水位告警?
5. release 环境的通知渠道除邮件外是否需要接入企业微信/飞书/Telegram
---
## 10. 推荐实施顺序
1. 先只落文档:技术方案 + runbook + 演练模板。
2. 在 development 目标做一次数据库导出 + dry-run 导入演练。
3. 根据演练结果补脚本:备份同步、备份健康检查。
4. 再把 release 备份设置为定时任务。
5. 最后规划整机重建演练与 DNS 切换演练。
首版完成标准:
- 团队任一成员打开 runbook即可在 30 分钟内完成 Web/API 回滚或数据库备份 dry-run 恢复。
- 最近一次数据库备份时间、备份位置、checksum、恢复演练结果可追溯。
- 生产密钥仍只存在于服务器/Jenkins 凭据/加密备份中,不进入 Git。

View File

@@ -1,403 +0,0 @@
# 当前项目安全漏洞检查计划
> **For Hermes:** Use subagent-driven-development skill only if the user later asks to execute this plan. 本计划当前仅用于规划,不实施代码修改。
**Goal:** 对 Genarrative 当前工作区做一次可复现的安全漏洞基线检查,覆盖依赖漏洞、密钥泄露、常见高风险代码模式、后端 Rust crate 风险和前端/Node 供应链风险,并输出可落地的整改清单。
**Architecture:** 采用“只读扫描 → 结果归档 → 人工分级 → 最小修复建议”的方式推进。先不直接升级依赖或改代码,避免安全扫描引入不可控 breaking change执行阶段只在用户确认后运行扫描命令并把报告保存到 `docs/audits/``.hermes/plans/` 附件中。
**Tech Stack:** Node/Vite/React/TypeScript、Rust workspace/Axum/SpacetimeDB、npm lockfile、Cargo.lock、Git worktree。
---
## 当前上下文 / 假设
- 当前有效工作区:`C:/proj/Genarrative/.worktrees/hermes-3337436a`
- 本次用户以 `/plan` 模式要求“检查一下当前项目的安全漏洞”,因此本轮只制定计划,不执行会产生报告、安装工具、修改依赖、提交或推送的操作。
- 已确认项目包含:
-`package.json`,脚本包括 `npm run lint``npm run test``npm run build``npm run check:encoding`
-`package-lock.json`
- `server-rs/Cargo.toml``server-rs/Cargo.lock`
- `apps/admin-web/package.json``packages/shared/package.json`
- `.hermes/shared-memory/development-workflow.md` 要求开发前读取共享记忆,并以当前代码、`docs/``AGENTS.md` 为准。
- 安全扫描不应把真实密钥写入仓库;发现疑似密钥时只记录文件位置、变量名、脱敏片段和处置建议。
## 总体策略
1. 先做仓库状态和范围确认,避免扫描其他 worktree 或错误路径。
2. 优先运行不会修改文件的安全检查:`npm audit --json``cargo audit`、密钥扫描、危险代码模式扫描。
3. 分前端供应链、后端供应链、源码安全、配置/脚本安全四类归档。
4. 对结果按严重级别分层Critical / High / Medium / Low / Informational。
5. 对每个真实问题给出:影响范围、证据、可行修复、验证命令、是否需要业务回归。
6. 只有在用户确认进入执行/修复阶段后,才做依赖升级、代码修复、文档更新、测试和提交。
---
## Step-by-step Plan
### Task 1: 确认扫描工作区和基线状态
**Objective:** 确保后续扫描针对当前 worktree且不会误把既有未提交变更当成安全修复结果。
**Files:**
- Read-only: `AGENTS.md`
- Read-only: `.hermes/README.md`
- Read-only: `.hermes/shared-memory/development-workflow.md`
- Read-only: `package.json`
- Read-only: `server-rs/Cargo.toml`
**Commands:**
```bash
pwd
git status --short
git branch --show-current
git rev-parse --show-toplevel
```
**Expected:**
- `pwd` / `git rev-parse --show-toplevel` 指向 `C:/proj/Genarrative/.worktrees/hermes-3337436a` 对应路径。
- 分支为当前隔离 worktree 分支。
- 记录是否已有未提交变更;如存在,扫描报告需标注“基于含未提交变更的工作区”。
**Validation:**
- 不修改任何项目文件。
- 如发现路径不是当前 worktree停止并重新确认路径。
### Task 2: 生成依赖清单和锁文件基线
**Objective:** 明确 Node 与 Rust 依赖入口,避免漏扫子包或 admin web。
**Files:**
- Read-only: `package.json`
- Read-only: `package-lock.json`
- Read-only: `apps/admin-web/package.json`
- Read-only: `packages/shared/package.json`
- Read-only: `server-rs/Cargo.toml`
- Read-only: `server-rs/Cargo.lock`
**Commands:**
```bash
npm --version
node --version
cargo --version
rustc --version
```
可选只读清单:
```bash
npm ls --all --json > /tmp/genarrative-npm-ls.json
cargo metadata --manifest-path server-rs/Cargo.toml --format-version 1 > /tmp/genarrative-cargo-metadata.json
```
**Expected:**
- 明确 npm / Node / Rust / Cargo 版本。
-`npm ls` 因 peer dependency 或历史依赖问题非 0保留输出并继续 audit。
**Validation:**
- `/tmp` 输出不进入 Git。
- 不运行 `npm install``npm update``cargo update`
### Task 3: Node 供应链漏洞扫描
**Objective:** 检查根 lockfile 覆盖的前端、脚本和 admin web 依赖漏洞。
**Files:**
- Read-only: `package-lock.json`
- Read-only: `package.json`
**Commands:**
```bash
npm audit --json > /tmp/genarrative-npm-audit.json
npm audit --audit-level=moderate
```
**Expected:**
- `npm audit --json` 生成机器可读结果。
- 第二条命令给出人类可读摘要;如返回非 0按漏洞严重度记录不直接执行 `npm audit fix`
**Result fields to extract:**
- package name
- vulnerable versions
- installed version
- severity
- CVE / GHSA
- via chain
- fixAvailable 是否为 major/breaking
- affected direct dependency or transitive dependency
**Validation:**
- 不执行 `npm audit fix`
- 如 npm registry 网络不可用,记录阻塞原因和可重试命令。
### Task 4: Rust 供应链漏洞扫描
**Objective:** 检查 `server-rs` workspace 的 Cargo 依赖漏洞、弃用 crate 和 yanked crate。
**Files:**
- Read-only: `server-rs/Cargo.toml`
- Read-only: `server-rs/Cargo.lock`
**Commands:**
优先:
```bash
cargo audit --json --manifest-path server-rs/Cargo.toml > /tmp/genarrative-cargo-audit.json
cargo audit --manifest-path server-rs/Cargo.toml
```
如果本机没有 `cargo audit`
```bash
cargo install cargo-audit --locked
cargo audit --manifest-path server-rs/Cargo.toml
```
**Execution note:**
- 安装 `cargo-audit` 会改变用户 Cargo 工具目录,不属于纯只读扫描;执行前需用户确认。
- 如果用户不希望安装工具则记录“Rust 漏洞扫描未完成”,并给出本地安装或 CI 执行建议。
**Result fields to extract:**
- advisory id
- package
- version
- patched versions
- unaffected versions
- severity / CVSS if available
- dependency path
- whether it is runtime reachable in `api-server` / `spacetime-module`
**Validation:**
- 不运行 `cargo update`
- 不改 `Cargo.lock`
### Task 5: 密钥和敏感配置泄露扫描
**Objective:** 检查仓库中是否误提交 API key、token、私钥、cookie、`.env` 类文件或个人 Hermes 配置。
**Files / paths to scan:**
- Full repo excluding `.git/`, `node_modules/`, `target/`, `dist/`, build artifacts。
- 特别关注:`.hermes/``scripts/``server-rs/``apps/admin-web/``src/``docs/`
**Preferred commands:**
如果有 gitleaks
```bash
gitleaks detect --source . --no-git --redact --report-format json --report-path /tmp/genarrative-gitleaks.json
```
如果没有 gitleaks先用只读 grep/ripgrep 兜底:
```bash
git ls-files -z | xargs -0 grep -nIE "(api[_-]?key|secret|password|passwd|token|private[_-]?key|BEGIN (RSA|OPENSSH|EC|DSA)? ?PRIVATE KEY|AKIA[0-9A-Z]{16}|xox[baprs]-|sk-[A-Za-z0-9_-]{20,})" > /tmp/genarrative-secret-grep.txt || true
```
**Execution note:**
- 安装 gitleaks 需要用户确认。
- grep 结果包含 false positive必须人工分级不得直接当作泄露结论。
**Validation:**
- 报告中对值做脱敏,只保留前后 3-4 位或完全不记录值。
- 如果发现 `.env.local` 或真实 token 被跟踪,立即标为 Critical。
### Task 6: 常见源码安全模式扫描
**Objective:** 快速发现高风险代码模式命令注入、动态执行、路径穿越、危险反序列化、XSS、日志泄密、宽松 CORS 等。
**Files / paths:**
- `src/**/*.{ts,tsx,js,mjs,cjs}`
- `apps/admin-web/**/*.{ts,tsx,js,mjs,cjs}`
- `scripts/**/*.{js,mjs,cjs,ts}`
- `server-rs/crates/**/*.rs`
**Commands:**
```bash
# JS/TS 动态执行与 HTML 注入
rg -n "\beval\(|new Function\(|dangerouslySetInnerHTML|innerHTML\s*=|document\.write\(" src apps scripts packages
# Node 命令执行风险
rg -n "exec\(|execSync\(|spawn\(|spawnSync\(|shell:\s*true|child_process" scripts src apps packages
# Rust 命令、文件路径、unwrap 风险热点
rg -n "Command::new|std::process|\.unwrap\(|\.expect\(|fs::|File::open|PathBuf|set_header|cors|CorsLayer" server-rs/crates
# 宽松 CORS / Cookie / Auth 相关热点
rg -n "allow_origin|Any|cookie|Authorization|Bearer|refresh|access_token|set_cookie|SameSite|Secure|HttpOnly" server-rs/crates src apps scripts
```
**Expected:**
- 输出作为“热点清单”,不等同于漏洞。
- 对 auth/session、文件上传、OSS 签名、外部 LLM/图片服务请求、SpacetimeDB 访问 facade 做人工复核。
**Validation:**
- 每个疑似问题必须能说明可利用条件,无法说明则降级为 Informational。
### Task 7: Web/API 安全配置人工复核
**Objective:** 对项目特有的安全边界做代码审阅,补足工具扫描无法覆盖的业务风险。
**Likely files to review:**
- `server-rs/crates/api-server/src/**`
- `server-rs/crates/platform-auth/src/**`
- `server-rs/crates/platform-oss/src/**`
- `server-rs/crates/platform-llm/src/**`
- `server-rs/crates/spacetime-client/src/**`
- `src/services/**`
- `apps/admin-web/src/**`
- `scripts/*deploy*`
- `scripts/*api-server*`
- `.github/workflows/**` if present
**Checklist:**
- Auth / sessionaccess token 与 refresh cookie 的生命周期、SameSite/Secure/HttpOnly、错误日志是否泄露 token。
- CORS开发环境与生产环境是否区分是否存在生产 `Any`
- SSRF / outboundLLM、图片生成、OSS、任意 URL 下载是否校验协议和大小。
- Upload / Data URL大小限制、MIME 校验、base64 解析错误处理。
- Path traversal脚本和后端是否拼接用户输入路径。
- Admin后台接口是否有权限校验是否复用普通用户 token。
- SpacetimeDBprivate table / reducer 是否绕过 api-server facade 暴露敏感数据。
- Logging日志是否打印 API key、token、cookie、用户私密内容。
**Validation:**
- 对每个命中的真实风险,记录具体文件路径和函数名。
- 对“需要运行环境才能验证”的风险,列出 smoke 或单测建议。
### Task 8: 汇总漏洞分级与整改建议
**Objective:** 把扫描结果转成团队可执行的安全整改报告。
**Deliverable candidates:**
- `docs/audits/SECURITY_VULNERABILITY_SCAN_YYYY-MM-DD.md`
- 或如果用户只要临时报告:`.hermes/plans/assets/security-scan-YYYY-MM-DD.md`
**Report structure:**
```markdown
# 安全漏洞扫描报告 YYYY-MM-DD
## 扫描范围
## 扫描命令与环境
## 摘要
## Critical
## High
## Medium
## Low
## Informational / False Positive
## 依赖升级建议
## 代码修复建议
## 需要人工确认的问题
## 验证命令
```
**Validation:**
- 报告不包含真实密钥。
- 每条问题都有“证据、影响、建议、验证”。
- 明确哪些是工具扫描结果,哪些是人工判断。
### Task 9: 如用户要求修复,再分批执行最小修复
**Objective:** 避免一次性大规模升级导致回归,把修复拆为可验证的小批次。
**Suggested order:**
1. Critical secrets立即移除、轮换密钥、补 `.gitignore`/文档约束(注意项目约束:不要在 `.gitignore` 中添加 `.env.local`)。
2. Critical/High direct dependencies优先升级 direct dependency运行最小测试。
3. Critical/High transitive dependencies评估是否由 direct dependency patch/minor 升级带出。
4. 源码漏洞:按入口编写回归测试,再修复。
5. Medium/Low按风险和 breaking change 代价排期。
**Required verification after fixes:**
```bash
npm run check:encoding
npm run lint:eslint
npm run typecheck
npm run test
npm run build
cd server-rs && cargo test --workspace
```
后端 API 或 auth 修复涉及运行态时,还需要:
```bash
npm run api-server
# 另一个终端检查 /healthz 并执行对应 smoke
```
**Validation:**
- 修复后重新跑对应 audit / secret scan。
-`requesting-code-review` 的独立安全复核流程。
---
## Files likely to change仅修复阶段
本计划阶段不修改以下文件;只有用户确认执行修复时才可能变化:
- `package.json`
- `package-lock.json`
- `apps/admin-web/package.json`
- `server-rs/Cargo.toml`
- `server-rs/Cargo.lock`
- `server-rs/crates/api-server/src/**`
- `server-rs/crates/platform-auth/src/**`
- `server-rs/crates/platform-oss/src/**`
- `server-rs/crates/platform-llm/src/**`
- `src/services/**`
- `apps/admin-web/src/**`
- `scripts/**`
- `docs/audits/SECURITY_VULNERABILITY_SCAN_YYYY-MM-DD.md`
- `.hermes/shared-memory/pitfalls.md`(仅当发现长期有效、会反复踩的安全排障经验时更新)
## Tests / Validation
安全扫描执行阶段:
```bash
npm audit --json > /tmp/genarrative-npm-audit.json
npm audit --audit-level=moderate
cargo audit --manifest-path server-rs/Cargo.toml
rg -n "\beval\(|new Function\(|dangerouslySetInnerHTML|innerHTML\s*=|document\.write\(" src apps scripts packages
rg -n "exec\(|execSync\(|spawn\(|spawnSync\(|shell:\s*true|child_process" scripts src apps packages
rg -n "Command::new|std::process|\.unwrap\(|\.expect\(|fs::|File::open|PathBuf|set_header|cors|CorsLayer" server-rs/crates
```
修复执行阶段:
```bash
npm run check:encoding
npm run lint:eslint
npm run typecheck
npm run test
npm run build
cd server-rs && cargo test --workspace
```
如变更后端运行态、安全中间件、auth/session
```bash
npm run api-server
# 检查 /healthz
# 执行相关 auth / API smoke
```
## Risks, tradeoffs, and open questions
- `npm audit fix` 可能升级 major version破坏 Vite/React/ESLint/Vitest 兼容性;必须先人工审查 `fixAvailable`
- `cargo audit` 可能需要安装 `cargo-audit`;安装工具属于用户环境变更,应先确认。
- 密钥扫描极易产生 false positive必须人工复核报告中禁止输出真实密钥。
- Rust `unwrap/expect` 不是天然漏洞;只有对外部输入、网络、文件、数据库响应等不可信数据造成 panic/DoS 时才升级为真实风险。
- Web 安全检查需要区分开发环境和生产环境;开发 CORS 放宽不等于生产漏洞,但生产配置必须有明确边界。
- 如果扫描发现历史提交中曾泄露密钥,删除当前文件不够,必须轮换密钥并考虑历史清理策略。
- 当前计划未直接访问 CI/Jenkins/生产配置;若用户希望覆盖 CI/CD、镜像、部署主机和运行时端口需要补充 Jenkins console、部署脚本和生产环境配置的只读访问方式。
## Missing artifacts / follow-up checkpoints
- 尚未获得用户确认是否允许安装 `cargo-audit` / `gitleaks` 等工具。
- 尚未执行真实扫描,因此当前没有漏洞结论;执行后需要生成正式报告。
- 如果用户希望“检查当前项目”包含远端仓库历史 secrets、Docker 镜像、Jenkins 凭据和生产运行时配置,需要另行确认访问范围和凭据边界。

View File

@@ -1,206 +0,0 @@
# 远端作品列表压测排查报告
时间2026-05-12 06:16 CST
目标:`http://82.157.175.59`
SSH远端生产机 root 账号(具体私钥路径仅保留在本机环境,不写入仓库)
## 背景
远端 `k6-works-list.js` 压测中:
- smoke 通过。
- baseline 10 VU无 HTTP 错误,但 p95/p99 超阈值。
- 50 RPS spike`http_req_failed` / `works_list_shape_error_rate` 约 21.99%。
- 100 RPS spike`http_req_failed` / `works_list_shape_error_rate` 约 25.47%。
- 从 k6 check 看,失败主要集中在 `puzzle_gallery_list``custom_world_gallery_list` 基本正常。
## 已完成排查
### 1. 服务器进程与资源
远端服务监听:
- Rust api-server`127.0.0.1:8082`systemd 服务 `genarrative-api.service`
- SpacetimeDB`127.0.0.1:3101`systemd 服务 `spacetimedb.service`
- Nginx公网 80 反代 `/api/*``127.0.0.1:8082`
服务器规格/状态:
- 2 vCPU。
- 内存约 1.9GiB。
- Swap 约 1.9GiB,已有约 600MiB 使用。
- `/` 磁盘约 69%。
- Rust api-server 当前 CPU 不高。
- SpacetimeDB 当前 CPU 不高。
发现一个独立异常:
- PM2 下旧 `server-node` 进程 `genarrative` 正在重启风暴。
- cwd`/work/Genarrative/server-node`
- 错误:连接 `127.0.0.1:5432` PostgreSQL 被拒绝。
- PM2 restart 次数已超过 33 万。
- 该进程不是当前公网 `/api/*` 使用的 Rust api-server但会制造额外 CPU/内存/日志抖动。
### 2. 压测窗口服务端日志
子任务聚合了 2026-05-12 04:50-05:05 的 nginx 与 api-server 日志。
nginx access
- `/api/runtime/puzzle/gallery`4661 次,全部 200。
- `/api/runtime/custom-world-gallery`4659 次,全部 200。
api-server journal
`/api/runtime/puzzle/gallery`
- completed4661
- status200 全部
- slow_request0
- latency_msmin 13 / p50 30 / p90 43 / p95 50 / p99 62 / max 88
`/api/runtime/custom-world-gallery`
- completed4659
- status200 全部
- slow_request0
- latency_msmin 0 / p50 1 / p90 5 / p95 7 / p99 13 / max 49
结论:
- 在服务端视角,两个接口在该窗口都没有 5xx也没有慢请求。
- 这与 k6 客户端侧 30s timeout / failed check 存在明显不一致。
- 需要进一步区分:客户端侧网络/连接耗尽/本机 k6 执行环境问题,还是 k6 统计混合/响应解析问题。
### 3. k6 脚本行为
文件:`scripts/loadtest/k6-works-list.js`
`AUTH_TOKEN` 时,每轮 iteration 顺序请求两个接口:
1. `GET /api/runtime/puzzle/gallery`
2. `GET /api/runtime/custom-world-gallery`
`DETAIL_RATIO=0` 时不会请求详情。
`works_list_shape_error_rate` 不只代表字段结构错误,只要下面任意 check 失败都会计入:
- status is 200
- returns json object
- has collection
- list item shape
因此 timeout、非 JSON、非 200、响应结构不符合都会表现为 shape error。
数据文件实际路径:
- `scripts/loadtest/data/works-list.local.json`
脚本里 `data/works-list.local.json` 是相对 k6 脚本文件解析的,因此本身合理。
### 4. 代码层疑似瓶颈
虽然这次远端服务端日志没有复现慢请求,但代码层仍发现一个真实性能隐患。
`/api/runtime/puzzle/gallery` 调用链:
- `server-rs/crates/api-server/src/app.rs:1192`
- `server-rs/crates/api-server/src/puzzle.rs:1385-1409`
- `server-rs/crates/spacetime-client/src/puzzle.rs:367-381`
- `server-rs/crates/spacetime-module/src/puzzle.rs:430-443`
- `server-rs/crates/spacetime-module/src/puzzle.rs:1393-1404`
关键实现:
- `list_puzzle_gallery_tx``puzzle_work_profile().iter()` 全表扫描。
- 再过滤 `publication_status == Published`
- 对每个公开作品调用 `build_puzzle_work_profile_from_row_with_recent_count`
- 该函数调用 `count_recent_public_work_plays(ctx, "puzzle", &row.profile_id, now_micros)`
`count_recent_public_work_plays`
- 文件:`server-rs/crates/spacetime-module/src/runtime/profile.rs:1296-1321`
- 当前实现对 `public_work_play_daily_stat().iter()` 全表扫描过滤。
- 但表定义已有复合索引:
- `server-rs/crates/spacetime-module/src/runtime/profile.rs:242-248`
- `by_public_work_play_daily_stat_work_day(source_type, profile_id, played_day)`
- 当前统计函数未使用该索引。
复杂度风险:
```text
puzzle gallery ~= O(puzzle_work_profile 全表扫描 + Published作品数 * public_work_play_daily_stat 全表扫描)
```
`custom-world-gallery` 与 puzzle 的差异:
- custom-world 使用 `CustomWorldGalleryEntry` 公开读模型表。
- puzzle 直接从 `puzzle_work_profile` 即席拼装。
- 两者都调用 recent count但 puzzle 更容易受作品表规模和统计表规模影响。
## 当前判断
本次排查有两个层面的结论:
1. 生产服务端日志没有证明 `puzzle/gallery` 在 04:50-05:05 窗口真的 30s 慢或 5xx。
- api-server 记录的 p95 只有 50ms。
- nginx 看到两个接口都是 200。
- 所以 k6 侧的 30s timeout 需要进一步从客户端网络、连接池、Windows/k6 执行环境、summary 混合统计角度验证。
2. 代码层确实存在可修的性能隐患。
- `count_recent_public_work_plays` 未使用已有索引。
- puzzle gallery 对每个作品重复做 recent count。
- puzzle gallery 未使用 `publication_status` 索引或读模型。
## 建议下一步
### A. 先处理服务器 PM2 重启风暴
建议确认旧 Node 服务是否仍需要。
如果不需要,应停止并禁用 PM2 中的旧 `server-node`
```bash
PM2_HOME=/home/ubuntu/.pm2 pm2 stop genarrative
PM2_HOME=/home/ubuntu/.pm2 pm2 delete genarrative
PM2_HOME=/home/ubuntu/.pm2 pm2 save
```
这是生产侧操作,执行前需要确认。
### B. 单接口短压验证客户端/服务端不一致
不要继续用混合脚本大压。
建议新增或临时使用单接口 k6 脚本,分别只测:
- `/api/runtime/puzzle/gallery`
- `/api/runtime/custom-world-gallery`
并在同一时间窗口并行采集:
- k6 客户端 summary
- nginx access 请求数/状态码
- api-server journal latency
- 本机到服务器网络错误/timeout
目标是确认 timeout 是不是发生在客户端侧连接/网络,而不是服务端处理慢。
### C. 修复代码性能隐患
优先级建议:
1. `count_recent_public_work_plays` 改为使用 `by_public_work_play_daily_stat_work_day` 复合索引,或至少改成批量统计,避免 N 次全表扫描。
2. `list_puzzle_gallery_tx` 使用 `by_puzzle_work_publication_status` 索引查询 Published或参考 custom-world 建立 `puzzle_gallery_entry` 公开读模型。
3. gallery 列表页不要实时逐条扫描统计表,可维护读模型或批量聚合 `recent_play_count_7d`
### D. 调整 k6 脚本输出
建议 k6 summary 按 endpoint tag 输出或新增单接口模式,否则 overall 指标会把 puzzle/custom-world 混在一起。
建议增加:
- `ENDPOINT=puzzle_gallery_list`
- `ENDPOINT=custom_world_gallery_list`
让脚本只跑一个 endpoint避免诊断时混淆。

View File

@@ -1,343 +0,0 @@
# Genarrative 视觉小说“一句话生成”最小闭环落地计划
生成时间2026-05-13 11:22
工作区:`C:/proj/Genarrative/.worktrees/hermes-visual-novel`
参考文档:`C:/Users/DSK/Documents/Interactive-fiction/一句话生成视觉小说整体流程总结.md`
## 1. 目标
把 Interactive-fiction 总结文档中的“一句话生成视觉小说”流程,映射并落地到 Genarrative 现有视觉小说能力中,优先做成一个可端到端验证的最小闭环:
1. 用户在视觉小说入口输入一句话并选择画风。
2. 前端进入生成过程页,展示分阶段进度。
3. 后端创建视觉小说创作会话,并基于 seedText 生成 `VisualNovelResultDraft`
4. 生成完成后进入草稿结果页,可看到世界观、角色、场景、剧情阶段、开场选择。
5. 草稿可编译/保存为作品 profile并进入视觉小说运行态测试/正式游玩。
本计划只覆盖 Genarrative 内部最小闭环,不引入 Interactive-fiction 原项目的独立 TXT 播放记录、分享播放包、外部活动运营、独立账号/交易/资产系统。
## 2. 当前上下文与已发现实现
### 2.1 Interactive-fiction 总结文档提炼
参考文档将整体流程分为:
- 输入侧:一句话创意、主题/风格、可选文档或素材。
- 生成侧:理解意图、扩展世界观、角色、场景、剧情阶段、开场与选择。
- 编辑侧:草稿页可查看和调整生成结果。
- 运行侧:从草稿进入视觉小说游玩,支持剧情推进、玩家选择、历史与状态。
- 资产侧:角色立绘、背景、音乐/音效可作为后续增强,最小闭环可先使用文字描述与空资产占位。
### 2.2 Genarrative 已有实现基础
已确认项目中视觉小说相关能力并非从零开始:
- 前端入口表单:
- `src/components/visual-novel-creation/VisualNovelAgentWorkspace.tsx`
- 已有“一句话创作” textarea、6 个视觉画风选项、提交按钮“生成视觉小说草稿”。
- 前端入口 payload/progress
- `src/components/visual-novel-creation/visualNovelEntryGeneration.ts`
- 已有 `VisualNovelEntryFormPayload`、锚点展示、一句话/画风生成进度步骤。
- 前端平台主流程:
- `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
- 已接入 `createVisualNovelDraftFromForm`,会创建 session、stream message、进入 `visual-novel-generating`,完成后进入 `visual-novel-result`
- 前端 API client
- `src/services/visual-novel-creation/visualNovelCreationClient.ts`
- 已封装 session/message/action/compile 接口。
- 共享契约:
- `packages/shared/src/contracts/visualNovel.ts`
- 已定义 `VisualNovelResultDraft`、world/characters/scenes/storyPhases/opening/runtimeConfig/work/run/history 等结构。
- 后端 API
- `server-rs/crates/api-server/src/visual_novel.rs`
- 已有创建 session、发消息、流式消息、执行 action、compile、work、runtime run 等接口。
- 后端 prompt
- `server-rs/crates/api-server/src/prompt/visual_novel.rs`
- 已有 `VISUAL_NOVEL_CREATION_SYSTEM_PROMPT`、结构化输出契约、runtime GM prompt、repair prompt。
- SpacetimeDB 模块:
- `server-rs/crates/spacetime-module/src/visual_novel.rs`
- 已有 session/message/work/run/history/event 表与 procedure。
- 文档参考:
- `docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`
- `docs/technical/VISUAL_NOVEL_IMPLEMENTATION_HANDOFF_2026-05-07.md`
- `docs/technical/VISUAL_NOVEL_PROMPT_AND_LLM_TOOLS_VN03_2026-05-05.md`
### 2.3 关键实现判断
当前项目已经实现了视觉小说的主要骨架,本次不应大规模重写。更合理的落地方式是补齐“一句话生成”闭环中最容易断裂的点:
- 入口输入与画风信息是否被稳定传给后端 prompt。
- 后端生成 draft 后是否自动保存/关联可编辑 work profile。
- 生成过程页是否能清晰展示 Interactive-fiction 文档中提到的阶段。
- 结果页是否有足够的字段展示与继续游玩入口。
- 运行态是否能基于 opening/choices 正常启动,而不依赖尚未生成的图片/音乐资产。
## 3. 拟采用方案
### 3.1 最小闭环范围
本次优先实现:
1. “一句话 + 视觉画风”作为 `sourceMode: 'idea'` 的 seedText。
2. 后端生成完整 `VisualNovelResultDraft`,包括:
- world
- 3-6 个角色
- 3-8 个场景
- 3-6 个剧情阶段
- opening narration/firstDialogue/2-4 个 choices
- runtimeConfig
3. 若 LLM 输出失败,使用 repair 或确定性 fallback保证可回到草稿页并显示错误/警告。
4. 结果页支持保存/编译为 work profile。
5. work profile 支持启动 runtime runopening 能展示初始场景、旁白、对话和选择。
暂不做或仅预留:
- 真实图片/音乐生成队列。
- 多文档解析导入的完整链路。
- 复杂分镜/节点图编辑器。
- 外部 Interactive-fiction 项目的播放器、TXT 记录包、分享活动、独立账号系统。
### 3.2 与 Genarrative 架构的映射
| Interactive-fiction 概念 | Genarrative 落点 |
| --- | --- |
| 一句话创意 | `VisualNovelEntryFormPayload.ideaText` / `seedText` |
| 画风/主题 | `seedText` 中的“视觉画风/画风要求”,后续可结构化为 metadata |
| 世界观设定 | `VisualNovelResultDraft.world` |
| 角色设定 | `VisualNovelResultDraft.characters` |
| 场景设定 | `VisualNovelResultDraft.scenes` |
| 剧情阶段/章节 | `VisualNovelResultDraft.storyPhases` |
| 开场文本与选项 | `VisualNovelResultDraft.opening` |
| 运行时剧情推进 | `VisualNovelRuntimeStep[]` + run snapshot/history |
| 发布/作品库 | `VisualNovelWorkProfileRecord` / works API |
## 4. 分步计划
### Step 1补齐入口 payload 与生成过程语义
涉及文件:
- `src/components/visual-novel-creation/VisualNovelAgentWorkspace.tsx`
- `src/components/visual-novel-creation/visualNovelEntryGeneration.ts`
- `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
任务:
1. 保持现有 6 个画风选项,但确认每个 option 的 prompt 会进入 `seedText`
2. 将生成过程阶段从当前 3 步细化为更贴合参考文档的 4-5 步,例如:
- 理解一句话创意
- 扩展世界观与玩家身份
- 设计角色/场景/剧情阶段
- 生成开场与选择
- 准备可编辑草稿
3. 生成过程页的 anchor 保留“一句话”和“视觉画风”,必要时增加“生成目标:视觉小说草稿”。
4. 确认 `createVisualNovelDraftFromForm` 对失败状态会保留返回入口/重试能力。
验收点:提交一句话后能进入 `visual-novel-generating`,看到阶段进度;完成后进入 `visual-novel-result`
### Step 2增强后端 creation prompt 与 fallback 约束
涉及文件:
- `server-rs/crates/api-server/src/prompt/visual_novel.rs`
- `server-rs/crates/api-server/src/visual_novel.rs`
- 如已有 domain crate`server-rs/crates/module-visual-novel/**` 或相关 normalize/validate 文件
任务:
1. 在 creation prompt 中显式吸收 Interactive-fiction 的“一句话生成”目标:
- 从 seedText 提取核心创意、视觉风格、故事类型。
- 生成可直接运行的 opening 和 choices。
- 图片/音乐资产先置 null但必须有可生成图像的描述。
2. 强化输出约束:
- `opening.sceneId` 必须指向存在且 availability 为 `opening` 的 scene。
- `opening.initialChoices` 必须 2-4 个。
- `storyPhases[0]` 必须包含 opening scene 和主要角色。
- `publishReady` 的判定与 validationIssues 一致。
3. 检查 `submit_visual_novel_message_turn` / `resolve_action_draft` / compile 相关代码:
- 如果 LLM 失败,是否已有 fallback没有则补确定性 fallback draft。
- 如果 draft 不完整,是否会 normalize/repair 并写入 session。
4. 保留现有“不要输出旧 TXT 播放记录、分享播放包、外部商业字段”的约束,避免把参考项目的外部概念误并入 Genarrative。
验收点:后端给定 seedText 时,返回 session.draft 不为空且满足共享契约。
### Step 3确认草稿结果页、保存/编译与作品库链路
涉及文件:
- `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
- `src/components/visual-novel-creation/**`
- `src/services/visual-novel-works*` 或相关 visual novel works client
- `server-rs/crates/api-server/src/visual_novel.rs`
- `packages/shared/src/contracts/visualNovel.ts`
任务:
1. 查找并确认 `visual-novel-result` 页面组件:
- 是否显示 workTitle/workDescription/world/characters/scenes/storyPhases/opening。
- 是否有保存/发布/开始试玩按钮。
2. 确认 `compileVisualNovelWorkProfile``executeVisualNovelAction({kind:'compile_work_profile'})` 会生成/更新 work profile。
3. 确认作品架上使用 `profileId` 而不是 sessionId 作为稳定作品 ID。
4. 如果结果页缺少“一句话来源/画风”的可视化提示,可在结果页或 summary 中补轻量展示,避免用户以为画风丢失。
验收点:生成完成后能保存为作品;作品出现在“我的作品/创作架”;再次打开能读取同一 draft。
### Step 4确认运行态 opening 闭环
涉及文件:
- `src/components/visual-novel-runtime/**`
- `src/services/visual-novel-runtime*`
- `server-rs/crates/api-server/src/visual_novel.rs`
- `server-rs/crates/api-server/src/prompt/visual_novel.rs`
- `packages/shared/src/contracts/visualNovel.ts`
任务:
1. 启动 visual novel work run 时,优先使用 `draft.opening` 生成第一轮 runtime snapshot/history。
2. 如果没有图片/音乐,前端 runtime shell 必须可用文字 fallback不应白屏或阻断游玩。
3. 玩家选择 `choice` 后,后端 runtime GM prompt 生成下一轮 `VisualNovelRuntimeStep[]`
4. 确认正式游玩入口调用 `work_play_start`,并满足已有埋点约定:
- `scope_kind=work`
- `scope_id=稳定作品 ID`
- metadata 包含 `playType/workId/sourceRoute/userId` 等。
验收点:从生成出的作品进入运行态,能看到 opening 并点击至少一个选择推进一轮。
### Step 5补测试与文档
涉及文件:
- 前端测试:按仓库现有测试布局查找 `*.test.ts` / `*.test.tsx`
- Rust 测试:`server-rs/crates/api-server/src/**` 或 domain crate tests
- 文档:可追加到 `docs/technical/``.hermes/shared-memory/decision-log.md`(如团队约定需要)
建议测试:
1. TypeScript 单元测试:
- `buildVisualNovelEntryGenerationProgress` 阶段输出。
- `buildVisualNovelEntryGenerationAnchorEntries` 能展示一句话和画风。
2. Rust 单元测试:
- creation prompt 包含 seedText、sourceMode、输出契约。
- draft normalize/fallback 能生成合法 opening/choices。
- runtime opening 或 first-step 构造不依赖图片/音乐。
3. 集成/手工测试文档:
- 访问平台视觉小说入口。
- 输入一句话。
- 选择画风。
- 点击生成。
- 查看结果页。
- 保存作品。
- 启动试玩并点击选择。
## 5. 可能改动文件清单
高概率改动:
- `src/components/visual-novel-creation/VisualNovelAgentWorkspace.tsx`
- `src/components/visual-novel-creation/visualNovelEntryGeneration.ts`
- `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
- `server-rs/crates/api-server/src/prompt/visual_novel.rs`
- `server-rs/crates/api-server/src/visual_novel.rs`
- `packages/shared/src/contracts/visualNovel.ts`
中概率改动:
- `src/components/visual-novel-runtime/**`
- `src/services/visual-novel-creation/**`
- `src/services/visual-novel-runtime/**`
- `src/services/visual-novel-works/**`
- `server-rs/crates/spacetime-module/src/visual_novel.rs`
- `server-rs/crates/spacetime-client/**` 生成/绑定文件,若 SpacetimeDB contract 需要更新
低概率/仅文档:
- `docs/technical/VISUAL_NOVEL_IMPLEMENTATION_HANDOFF_2026-05-07.md`
- `docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`
- `.hermes/shared-memory/decision-log.md`
## 6. 验证计划
### 6.1 静态检查
在 worktree 根目录执行:
```bash
npm run typecheck
```
如仓库无统一 typecheck则按 package scripts 选择最接近的前端类型检查命令。
### 6.2 前端定向测试
优先运行与 visual novel / platform entry 相关测试,如存在:
```bash
npm test -- visual-novel
npm test -- platform-entry
```
若仓库使用 vitest
```bash
npm run test -- visual-novel
```
### 6.3 Rust 定向测试
`server-rs` 下运行 visual novel 相关测试:
```bash
cargo test -p api-server visual_novel
cargo test -p shared-contracts visual_novel
```
如改动 SpacetimeDB module
```bash
cargo test -p spacetime-module visual_novel
```
### 6.4 人工验收步骤
1. 启动本地 dev 栈。
2. 访问 Genarrative 主站。
3. 进入创作/视觉小说入口。
4. 输入:`一个雨夜,失忆的高中生在旧图书馆发现一本会回应她心声的日记。`
5. 选择任一画风,例如“映画动画”。
6. 点击“生成视觉小说草稿”。
7. 预期:进入生成过程页,能看到分阶段进度。
8. 预期:完成后进入草稿结果页,包含标题、简介、世界观、角色、场景、剧情阶段和 opening choices。
9. 点击保存/编译作品。
10. 从作品入口进入试玩。
11. 预期opening 文本出现,至少 2 个选择可点击;点击后剧情继续推进一轮。
## 7. 风险、权衡与开放问题
### 7.1 风险
- 现有视觉小说代码已较完整,贸然新增一套 parallel pipeline 会制造重复逻辑;应复用当前 `VisualNovelResultDraft` 与 creation agent flow。
- LLM 输出不稳定可能导致草稿结构不完整;需要 normalize/repair/fallback 确保最小闭环。
- 视觉/音乐资产生成未接入时UI 必须接受 null asset否则运行态可能白屏。
- `PlatformEntryFlowShellImpl.tsx` 文件很大,改动需局部、谨慎,避免影响其他玩法入口。
- 若改动 SpacetimeDB 表结构,可能牵涉 publish、client binding、清库/迁移;最小闭环阶段应尽量避免 schema 变更。
### 7.2 权衡
- 先让文字版视觉小说完整跑通,再补角色立绘/背景图生成。
- 先用 `seedText` 承载画风,再考虑把 `visualStyleId/Label/Prompt` 结构化进 draft metadata。
- 先用现有 result/work/runtime 页面闭环,不引入新编辑器。
### 7.3 开放问题
1. 用户是否要求把 Interactive-fiction 原项目中的具体 UI 样式/页面布局迁移到 Genarrative当前计划只迁移流程语义不迁移独立 UI。
2. 画风是否需要成为作品可编辑字段?当前以 seedText/prompt 影响生成内容,后续可在 draft 中增加 metadata。
3. 文档导入模式是否本期要做当前计划聚焦一句话模式document 模式只保留契约能力。
4. 是否需要真实图片/音乐生成?当前计划作为后续增强,不纳入最小闭环。
## 8. 建议实施顺序
1. 先做只改 prompt/progress/少量前端展示的轻量闭环修补。
2. 运行前后端定向测试,确认现有能力是否已足够。
3. 如果后端没有 fallback 或 normalize再补 Rust 层确定性兜底。
4. 手工跑通“一句话 -> 生成 -> 结果页 -> 保存 -> 试玩”。
5. 最后再考虑是否需要资产生成、文档导入、结构化画风 metadata。

View File

@@ -1,549 +0,0 @@
# Bark Battle Phase 2 Platform Work Loop Implementation Plan
> **For Hermes:** Use subagent-driven-development skill to implement this plan task-by-task.
**Goal:**`bark-battle` 从内部试玩 demo 升级为 Genarrative 正式 play type打通轻创作配置、发布态作品、正式 runtime、run start / finish、后端裁决、个人历史、作品统计和最小排行榜闭环。
**Architecture:** 先冻结 shared contracts 与 `module-bark-battle` 纯领域规则,再落 SpacetimeDB 表/reducer、`spacetime-client` facade 和 `api-server` BFF随后接前端最小纵切最后补排行榜/个人历史/作品统计投影体验。前端只承接表现、交互和临时 UI 状态,正式业务真相由后端裁决。
**Tech Stack:** React + TypeScript + Vite, server-rs + Axum, SpacetimeDB Rust module, shared-contracts, Vitest, Cargo tests, npm scripts.
---
## 0. 已确认决策
1. “有效叫声”统一为 **有效声浪触发**:当前采样响度达到有效阈值且满足 `minBarkGapMs` 冷却即触发;不再要求 `minBarkDurationMs` / `maxBarkDurationMs`,也不等待响度回落。
2. Phase 2 范围是 **Bark Battle 平台作品闭环**,不是单纯玩法表现深化。
3. 作品形态是 **轻创作配置作品**:标题、描述、主题/背景预设、狗狗皮肤预设、难度预设、排行榜开关。
4. 难度预设只影响 AI 对手行为;不影响有效阈值、冷却、时长、分数公式或反作弊阈值。
5. 排行榜按 `workId + difficultyPreset + rulesetVersion` 分榜。
6. 后端裁决正式单局结果;前端只提交派生指标,`clientResult` 只用于 debug/对账。
7. 排行榜只收录 `serverResult = player_win` 且未被反作弊拒绝的单局结果,排序以 `finalEnergy` 优先。
8. 作品统计使用最小后端投影start、finish、win/draw/loss、flagged、leaderboard、best/avg energy。
9. 个人历史成绩 = 最近记录列表 + 个人最佳摘要;仅本人可见。
10. 正式入口闭环覆盖创作入口、作品详情 CTA、广场/作品卡片、我的作品、稳定作品 ID runtime 路由和 `work_play_start`
11. 创作编辑形态是单页轻配置表单 + 预览卡片。
12. 实施顺序固定为:契约与领域规则 → SpacetimeDB 表/reducer 与 api-server BFF → 最小前端纵切 → 投影与列表体验 → 收口验证。
---
## 1. 必读文档与约束
实施前先读:
- `AGENTS.md`
- `CONTEXT.md`
- `docs/prd/BARK_BATTLE_BDD_2026-05-11.md`
- `docs/technical/BARK_BATTLE_BACKEND_DDD_TECHNICAL_PLAN_2026-05-11.md`
- `docs/technical/BARK_BATTLE_2D_RUNTIME_TECHNICAL_PLAN_2026-05-11.md`
- `docs/technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md`
- `docs/technical/SERVER_RS_DDD_FULL_REFACTOR_2026-04-28.md`
- `.codex/skills/spacetimedb-cli/SKILL.md`
- `.codex/skills/spacetimedb-rust/SKILL.md`
- `.codex/skills/spacetimedb-concepts/SKILL.md`
- `.codex/skills/spacetimedb-typescript/SKILL.md`
关键约束:
- 后端路线固定 `server-rs + Axum + SpacetimeDB`
- 领域规则进 `module-bark-battle`SpacetimeDB 表和事务编排进 `spacetime-module`
- HTTP/SSE/BFF 留在 `api-server`
- 前后端 DTO 留在 `shared-contracts`
- 数据库表结构更改必须同步 `migration.rs` 和生成绑定。
- 人工命令/文档示例禁止继续使用 `spacetime --root-dir`
- 修改中文文件后必须跑 `npm run check:encoding`
---
## 2. 阶段一:契约与领域规则
### Task 1.1: 新增 Rust shared-contracts 模块
**Objective:** 定义 Bark Battle Phase 2 的 Rust DTO 边界。
**Files:**
- Create: `server-rs/crates/shared-contracts/src/bark_battle.rs`
- Modify: `server-rs/crates/shared-contracts/src/lib.rs`
- Test: `server-rs/crates/shared-contracts/src/bark_battle.rs`
**Steps:**
1. 新增枚举:`BarkBattleDifficultyPreset { Easy, Normal, Hard }``BarkBattleServerResult { PlayerWin, OpponentWin, Draw }``BarkBattleFinishStatus { Accepted, AcceptedWithFlags, Rejected }`
2. 新增配置 DTO`BarkBattleDraftConfig``BarkBattlePublishedConfig``BarkBattleRuntimeConfig`
3. 新增 run DTO`BarkBattleRunStartRequest/Response``BarkBattleRunFinishRequest/Response`
4. 新增派生指标 DTO`BarkBattleDerivedMetrics`,字段包含 `trigger_count``max_volume``average_volume``final_energy``combo_max`
5. 新增排行榜/历史/统计 DTO`BarkBattleLeaderboardEntry``BarkBattlePersonalHistoryItem``BarkBattlePersonalBestSummary``BarkBattleWorkStats`
6.`lib.rs` 导出 `pub mod bark_battle;`
**Verification:**
```bash
cargo test -p shared-contracts bark_battle
```
Expected: contracts tests pass.
### Task 1.2: 新增 TypeScript shared contracts mirror
**Objective:** 让前端获得与 Rust DTO 对齐的类型。
**Files:**
- Create: `packages/shared/src/contracts/barkBattle.ts`
- Modify: `packages/shared/src/contracts/index.ts`
- Test: `packages/shared/src/contracts/barkBattle.test.ts`
**Steps:**
1. 定义 `BarkBattleDifficultyPreset = 'easy' | 'normal' | 'hard'`
2. 定义 `BarkBattleServerResult = 'player_win' | 'opponent_win' | 'draw'`
3. 定义 draft / published / runtime config 类型。
4. 定义 start / finish request response 类型。
5. 定义 leaderboard / personal history / work stats 类型。
6. 写最小序列化/fixture 测试,确保字段命名采用前端约定 camelCase并在 API client 层做必要映射。
**Verification:**
```bash
npm test -- --run packages/shared/src/contracts/barkBattle.test.ts
npx tsc -p tsconfig.typecheck-guardrails.json --noEmit --pretty false
```
### Task 1.3: 新建 module-bark-battle crate
**Objective:** 将正式裁决规则放入纯领域 crate。
**Files:**
- Create: `server-rs/crates/module-bark-battle/Cargo.toml`
- Create: `server-rs/crates/module-bark-battle/src/lib.rs`
- Create: `server-rs/crates/module-bark-battle/src/domain.rs`
- Create: `server-rs/crates/module-bark-battle/src/scoring.rs`
- Modify: `server-rs/Cargo.toml`
**Steps:**
1. 在 workspace 中注册 `module-bark-battle`
2. 定义 `RulesetVersion`,首版固定如 `bark-battle-ruleset-v1`
3. 定义 `BarkBattleRuleset`,包含标准局时长 30s、`min_bark_gap_ms`、合法音量/能量/连击范围、duration tolerance。
4. 实现 `validate_finish_metrics()`
5. 实现 `adjudicate_result()`:以后端 `final_energy` 和 draw threshold 生成 `serverResult`
6. 实现 `compute_leaderboard_score()`:只允许胜利局入榜,排序因子为 `finalEnergy``triggerCount``maxVolume`、duration 接近度、`finishedAt`
**Verification:**
```bash
cargo test -p module-bark-battle
```
### Task 1.4: 领域规则单测覆盖作弊边界
**Objective:** 防止前端伪造 finish 直接刷榜。
**Files:**
- Modify: `server-rs/crates/module-bark-battle/src/scoring.rs`
**Test cases:**
- 28s-35s 合法窗口内可接受。
- 1s / 300s 应 rejected 或 flagged。
- `triggerCount > durationMs / minBarkGapMs + tolerance` 应 flagged。
- `finalEnergy` 越界应 rejected。
- 平/负不生成 leaderboard entry。
- easy/normal/hard 不改变阈值、冷却、分数公式,只改变 AI preset key。
**Verification:**
```bash
cargo test -p module-bark-battle -- --nocapture
```
---
## 3. 阶段二SpacetimeDB 表/reducer 与 api-server BFF
### Task 2.1: 设计 SpacetimeDB 表目录
**Objective:** 新增 Bark Battle 表并与 migration 对齐。
**Files:**
- Create: `server-rs/crates/spacetime-module/src/bark_battle/mod.rs`
- Create: `server-rs/crates/spacetime-module/src/bark_battle/types.rs`
- Create: `server-rs/crates/spacetime-module/src/bark_battle/tables.rs`
- Modify: `server-rs/crates/spacetime-module/src/lib.rs`
- Modify: `server-rs/crates/spacetime-module/src/migration.rs`
**Tables:**
- `bark_battle_draft_config`
- `bark_battle_published_config`
- `bark_battle_runtime_run`
- `bark_battle_score_record`
- `bark_battle_leaderboard_entry`
- `bark_battle_work_stats_projection`
- `bark_battle_personal_best_projection`
**Pitfalls:**
- 表结构不要 derive `SpacetimeType`
- reducer 使用 `&ReducerContext`
- 授权身份来自 `ctx.sender()`
- 需要公开订阅的表才加 `public`
**Verification:**
```bash
cargo test -p spacetime-module
```
### Task 2.2: 实现草稿/发布 reducer
**Objective:** 支持轻配置草稿保存和发布态 config 固化。
**Reducers:**
- `create_bark_battle_draft`
- `update_bark_battle_draft_config`
- `publish_bark_battle_work`
- `get_bark_battle_runtime_config` 如仓库约定使用 reducer/procedure 查询则按现有 pattern 实现。
**Rules:**
- 草稿配置只允许标题、描述、主题/背景预设、狗狗皮肤预设、难度预设、排行榜开关。
- 发布生成稳定作品 ID / config version。
- 发布态 config 包含 `rulesetVersion`
**Verification:**
```bash
cargo test -p spacetime-module bark_battle
```
### Task 2.3: 实现 run start / finish reducer
**Objective:** 打通正式运行态后端事务。
**Reducers:**
- `start_bark_battle_run`
- `finish_bark_battle_run`
- `get_bark_battle_run`
**Rules:**
- start 创建 `run_id` 和一次性 `run_token`
- start 记录 work/config/ruleset/difficulty 快照。
- finish 必须校验 run token、未 finish、work/config/ruleset/difficulty 一致。
- finish 调用 `module-bark-battle` 裁决结果。
- accepted 写 score record。
- `serverResult = player_win` 且排行榜开启且未 rejected 时写 leaderboard entry。
- accepted / accepted_with_flags 更新 work stats 和 personal best projection。
**Verification:**
```bash
cargo test -p spacetime-module bark_battle_run
```
### Task 2.4: 更新 migration 与生成绑定
**Objective:** 让 SpacetimeDB 表结构变更可发布。
**Files:**
- Modify: `server-rs/crates/spacetime-module/src/migration.rs`
- Generated: `server-rs/crates/spacetime-client/src/module_bindings/*bark*`
**Commands:**
按仓库现有脚本优先;不要手改 generated bindings。
```bash
npm run spacetime:build
npm run spacetime:generate
```
若脚本名不同,先查 `package.json``server-rs` README。
### Task 2.5: 实现 spacetime-client facade
**Objective:** api-server 不直接操作 generated bindings。
**Files:**
- Create: `server-rs/crates/spacetime-client/src/bark_battle.rs`
- Modify: `server-rs/crates/spacetime-client/src/lib.rs`
**Methods:**
- `create_bark_battle_draft`
- `save_bark_battle_draft_config`
- `publish_bark_battle_work`
- `get_bark_battle_runtime_config`
- `start_bark_battle_run`
- `finish_bark_battle_run`
- `list_bark_battle_leaderboard`
- `list_my_bark_battle_history`
- `get_my_bark_battle_best_summary`
- `get_bark_battle_work_stats`
**Verification:**
```bash
cargo test -p spacetime-client bark_battle
```
### Task 2.6: 实现 api-server BFF 路由
**Objective:** 暴露前端需要的 HTTP API。
**Files:**
- Create: `server-rs/crates/api-server/src/bark_battle.rs`
- Modify: `server-rs/crates/api-server/src/app.rs`
**Routes:**
- `POST /api/bark-battle/drafts`
- `PATCH /api/bark-battle/drafts/:draftId`
- `POST /api/bark-battle/drafts/:draftId/publish`
- `GET /api/bark-battle/works/:workId/runtime-config`
- `POST /api/bark-battle/runs/start`
- `POST /api/bark-battle/runs/:runId/finish`
- `GET /api/bark-battle/works/:workId/leaderboard`
- `GET /api/bark-battle/me/history`
- `GET /api/bark-battle/me/best-summary`
- `GET /api/bark-battle/works/:workId/stats`
**Verification:**
```bash
cargo test -p api-server bark_battle
npm run api-server
curl -f http://127.0.0.1:<api-port>/healthz
```
---
## 4. 阶段三:最小前端纵切
### Task 3.1: 新增前端 service client
**Files:**
- Create: `src/services/bark-battle/barkBattleClient.ts`
- Test: `src/services/bark-battle/barkBattleClient.test.ts`
**Methods:** 与 BFF routes 一一对应。
**Verification:**
```bash
npm test -- --run src/services/bark-battle/barkBattleClient.test.ts
```
### Task 3.2: 接入创作入口与 SelectionStage
**Files:**
- Modify: `src/config/newWorkEntryConfig.ts`
- Modify: `src/components/platform-entry/platformEntryCreationTypes.ts`
- Modify: `src/components/platform-entry/platformEntryTypes.ts`
- Modify: `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
**Rules:**
- 新增 `bark-battle` play type。
- 入口打开单页轻配置表单,不走复杂 agent workspace。
- 移动端入口布局不能溢出。
### Task 3.3: 实现单页轻配置表单 + 预览卡片
**Files:**
- Create: `src/components/bark-battle-creation/BarkBattleConfigEditor.tsx`
- Create: `src/components/bark-battle-creation/BarkBattlePreviewCard.tsx`
- Test: `src/components/bark-battle-creation/BarkBattleConfigEditor.test.tsx`
**UI fields:**
- 标题必填
- 简介选填
- 主题/背景预设
- 狗狗皮肤预设
- 难度预设,默认 `normal`
- 排行榜开关,默认开启
**UI constraints:**
- 不堆大段玩法说明。
- 按现有游戏 UI 风格设计。
- 移动端优先。
### Task 3.4: 发布后进入作品详情
**Files:**
- Modify: `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
- Modify: `src/components/platform-entry/PlatformWorkDetailView.tsx`
- Modify: `src/components/custom-world-home/CustomWorldCreationHub.tsx`
- Modify: `src/components/custom-world-home/creationWorkShelf.ts`
**Rules:**
- 发布成功刷新 works/gallery/shelf。
- 跳作品详情。
- 详情 CTA 可以进入正式 runtime。
### Task 3.5: runtime 拉发布态 config 并 start / finish
**Files:**
- Modify: `src/games/bark-battle/*`
- Modify: `src/games/bark-battle/ui/BarkBattleRuntimeShell.tsx`
- Create/Modify: `src/components/bark-battle-runtime/BarkBattleRuntimeRoute.tsx` 如需要
**Rules:**
- runtime 通过稳定 `workId``BarkBattleRuntimeConfig`
- 开始正式局时调用 start run。
- 结束时提交 finish 派生指标。
- 结算展示 `serverResult``scoreSummary``antiCheatFlags`、leaderboard entry。
- 麦克风原始音频不上传。
**Verification:**
```bash
npm test -- --run src/games/bark-battle/domain/__tests__/BarkDetector.test.ts src/games/bark-battle/application/__tests__/BarkBattleController.test.ts src/games/bark-battle/ui/__tests__/BarkBattleRuntimeShell.test.tsx
```
---
## 5. 阶段四:投影与列表体验
### Task 4.1: 排行榜 UI
**Files:**
- Create: `src/components/bark-battle-leaderboard/BarkBattleLeaderboardPanel.tsx`
- Test: `src/components/bark-battle-leaderboard/BarkBattleLeaderboardPanel.test.tsx`
- Modify: `src/components/platform-entry/PlatformWorkDetailView.tsx`
**Rules:**
- 查询维度 `workId + difficultyPreset + rulesetVersion`
- 只展示胜利入榜成绩。
- 不展示平/负/flagged 历史。
### Task 4.2: 个人历史最近记录 + 最佳摘要 UI
**Files:**
- Create: `src/components/bark-battle-history/BarkBattlePersonalHistoryPanel.tsx`
- Test: `src/components/bark-battle-history/BarkBattlePersonalHistoryPanel.test.tsx`
**Rules:**
- 默认最近 20 条。
- 仅本人可见。
- 可按 workId / difficultyPreset 过滤。
- flagged 只做轻提示,不展示详细反作弊原因。
### Task 4.3: 作品统计展示
**Files:**
- Create: `src/components/bark-battle-stats/BarkBattleWorkStatsPanel.tsx`
- Test: `src/components/bark-battle-stats/BarkBattleWorkStatsPanel.test.tsx`
**Fields:**
- `playStartCount`
- `finishCount`
- `winCount`
- `drawCount`
- `lossCount`
- `flaggedCount`
- `leaderboardEntryCount`
- `bestLeaderboardScore`
- `bestFinalEnergy`
- `averageFinalEnergy`
- `updatedAt`
### Task 4.4: 广场卡片/我的作品适配
**Files:**
- Modify: `src/components/custom-world-home/creationWorkShelf.ts`
- Modify: `src/components/custom-world-home/CustomWorldCreationHub.tsx`
- Modify: `src/components/rpg-entry/rpgEntryWorldPresentation.ts`
- Modify: `src/services/publicWorkCode.ts` 如分享码需要支持
**Rules:**
- Bark Battle 作品能展示、打开详情、开始游玩。
- 不新增独立 Bark Battle 专区。
---
## 6. 阶段五:收口验证
### Task 5.1: 自动测试清单
```bash
cargo test -p shared-contracts bark_battle
cargo test -p module-bark-battle
cargo test -p spacetime-module bark_battle
cargo test -p spacetime-client bark_battle
cargo test -p api-server bark_battle
npm test -- --run packages/shared/src/contracts/barkBattle.test.ts
npm test -- --run src/services/bark-battle/barkBattleClient.test.ts
npm test -- --run src/components/bark-battle-creation/BarkBattleConfigEditor.test.tsx
npm test -- --run src/games/bark-battle/domain/__tests__/BarkDetector.test.ts src/games/bark-battle/application/__tests__/BarkBattleController.test.ts src/games/bark-battle/ui/__tests__/BarkBattleRuntimeShell.test.tsx
npx tsc -p tsconfig.typecheck-guardrails.json --noEmit --pretty false
npm run check:encoding
git diff --check
```
### Task 5.2: 后端 smoke
1. 按项目脚本启动 SpacetimeDB + api-server优先使用 `npm run api-server`,不要使用旧命令。
2. 确认 `/healthz`
3. smoke 流程:创建草稿 → 保存配置 → 发布 → 拉 runtime config → start run → finish run → 查询 leaderboard/history/stats。
### Task 5.3: 人工验收路径
1. 进入创作入口/玩法选择,选择 Bark Battle。
2. 在单页轻配置表单中填写标题,选择主题、狗狗皮肤、难度,保持排行榜开启。
3. 保存草稿。
4. 发布作品。
5. 发布后自动进入作品详情。
6. 点击开始游玩进入正式 runtime。
7. 授权麦克风,完成 30 秒单局。
8. 结算页显示后端 `serverResult` 和 score summary。
9. 若胜利,排行榜出现本局成绩。
10. 我的记录显示最近记录和个人最佳摘要。
11. 作品详情/作者视角能看到作品统计。
12. 广场/作品卡片和我的作品入口都能再次进入详情和 runtime。
---
## 7. 不做范围
- 不做实时多人。
- 不做 ghost replay。
- 不做 AI 狗叫识别。
- 不保存原始音频、PCM、waveform 或可还原语音内容。
- 不做独立 Bark Battle 专区/活动页。
- 不做挑战分享、好友邀请、多人数房间。
- 不做复杂编辑器、多步骤向导、规则参数编辑、AI 生成配置。
- 不做 DAU/留存、按小时统计曲线、好友对比。
---
## 8. 三人并行建议
### 开发者 A后端契约与领域规则
负责 Task 1.1、1.3、1.4。先提交 contracts 与 `module-bark-battle`,为后续后端/前端提供稳定类型和裁决规则。
### 开发者 BSpacetimeDB + api-server
负责 Task 2.1 到 2.6。必须等开发者 A 的 DTO/领域规则基本稳定后开始,或先基于计划字段开分支实现表结构。
### 开发者 C前端纵切与 UI
负责 Task 3.x 与 4.x。开始时可先做组件空态和 service client 类型,真正联调等 B 的 BFF ready。
---
## 9. 推荐提交节奏
1. `feat: add bark battle contracts and domain rules`
2. `feat: add bark battle spacetime tables and reducers`
3. `feat: add bark battle api server routes`
4. `feat: add bark battle creation editor`
5. `feat: connect bark battle runtime to server results`
6. `feat: add bark battle leaderboard history stats`
7. `docs: finalize bark battle phase2 verification guide`
---
## 10. 完成定义
Phase 2 完成必须同时满足:
- Bark Battle 可以从正式创作入口创建轻配置作品。
- 作品可以发布为稳定 workId。
- 作品详情/广场/我的作品可以发现并进入正式 runtime。
- runtime 从后端发布态 config 拉配置。
- start run 写 `work_play_start`
- finish 只上传派生指标。
- 后端裁决 `serverResult` / `scoreSummary` / `leaderboardScore` / `antiCheatFlags`
- 胜利局进入按 `workId + difficultyPreset + rulesetVersion` 分榜的排行榜。
- 个人历史和作品统计可查询。
- 自动测试、encoding、typecheck、diff check 和人工验收路径通过。

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

View File

@@ -11,18 +11,34 @@
- 决策:最终决定是什么
- 影响范围:涉及哪些模块/文档/流程
- 验证方式:如何确认决策仍有效
- 关联文档:相关 PRD、技术文档、提交或 Issue
- 关联文档:`docs/【项目基线】当前产品与工程约束-2026-05-15.md`
```
---
## 2026-05-15 汪汪声浪和宝贝识物入口设为敬请期待
- 背景:当前需要暂时关闭汪汪声浪和宝贝识物两个模板的创建链路,但仍保留创作 Tab 中的模板占位。
- 决策:`bark-battle``baby-object-match` 的默认创作入口配置调整为 `visible=true``open=false``badge=敬请期待`SpacetimeDB 入口配置种子会把旧默认开放行纠偏为敬请期待api-server 路由熔断覆盖 `/api/creation/bark-battle/*``/api/runtime/bark-battle/*``/api/creation/edutainment/baby-object-match/*`
- 影响范围:`module-runtime` 默认入口配置、`spacetime-module` 入口配置种子纠偏、`api-server` 入口路由熔断、创作玩法文档和入口配置排障记忆。
- 验证方式:执行 `cargo test -p module-runtime default_creation_entry_types --manifest-path server-rs/Cargo.toml``cargo test -p api-server creation_entry_config --manifest-path server-rs/Cargo.toml``cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml``npm run test -- src/components/platform-entry/platformEntryCreationTypes.test.ts``npm run check:encoding`,并用 `npm run api-server` 后检查 `/healthz``/api/creation-entry/config`
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 2026-05-15 抓大鹅生成素材记录物品相对尺寸
- 背景:抓大鹅 2D 五视角素材此前只保存物品名称和图片,运行态所有生成素材显示尺寸一致;用户要求生成物品名称时同时给出合理的 `大 / 中 / 小` 相对尺寸,并把当前默认尺寸视为 `大`
- 决策:`match3d_compile_draft` 的生成计划 `items[]` 增加 `itemSize`,只允许 `大 / 中 / 小`;后端把该字段持久化到 `generatedItemAssets[].itemSize` 并通过 Agent / Works DTO 下发。历史缺失 `itemSize` 的素材按 `大` 兼容;模型缺失或返回非法值时按物品名称本地推断,仍无法判断时使用 `中`。运行态只用该字段缩放生成 2D 图片本体,不改变后端下发的布局半径、点击半径和规则真相。
- 影响范围:`api-server` Match3D 草稿生成计划、`shared-contracts` 与 TS Match3D 作品契约、结果页素材合并、`Match3DRuntimeShell` 场内/托盘/飞行动画图片显示、Match3D PRD 与素材流水线技术文档。
- 验证方式:执行 `cargo test -p api-server match3d --manifest-path server-rs\Cargo.toml``cargo test -p shared-contracts match3d --manifest-path server-rs\Cargo.toml``npm run typecheck``npm run check:encoding`,并定向跑 `Match3DRuntimeShell.test.tsx` 中尺寸和生成图片相关用例。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 2026-05-14 创作页图像输入统一封装为图像组件
- 背景:拼图创作页已经具备“画面描述生图 / 多参考图生图 / 上传主图后 AI 重绘 / 上传主图后不重绘”四条路径,抓大鹅封面和后续创作页也会复用同一套交互;继续在页面内复制会导致参考图、预览、删除确认和重绘开关漂移。
- 决策:通用图像输入 UI 统一使用 `src/components/common/CreativeImageInputPanel.tsx`。组件采用受控模式只负责主图上传卡、画面描述输入、参考图缩略图与预览、AI 重绘开关、错误展示和提交按钮;外层页面负责文件读取/裁剪、历史素材弹层、计费确认、自动保存和具体后端请求。
- 影响范围:拼图创作入口、后续抓大鹅封面生成入口、其它需要复用图像输入链路的创作页。
- 验证方式:拼图入口交互测试继续覆盖四种路径;后续页面接入时只传入业务回调与文案,不复制上传卡和参考图缩略图实现。
- 关联文档:`docs/technical/【前端体验】图像组件统一封装与复用边界-2026-05-14.md`
- 关联文档:`docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 2026-05-14 汪汪声浪创作入口改为创作 Tab 内嵌轻配置
@@ -30,7 +46,7 @@
- 决策:`bark-battle` 的创作入口只在创作 Tab 内嵌渲染轻配置表单,入口点击只切到创作页并选中该模板,不再使用 `bark-battle-config` 独立阶段runtime 退出时回到创作页并恢复汪汪声浪模板选中态。
- 影响范围:`PlatformEntryFlowShellImpl``BarkBattleConfigEditor``BarkBattleRuntimeShell`、入口配置说明和相关交互测试。
- 验证方式:创作 Tab 中点击汪汪声浪后直接看到内嵌表单,不应再出现单独配置页;发布进入 runtime 后退出应回到创作页的汪汪声浪模板。
- 关联文档:`docs/technical/NEW_WORK_ENTRY_CONFIG_2026-05-01.md`
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 2026-05-14 拼图与抓大鹅生成页移动端收口为等待与计时双栏
@@ -38,7 +54,7 @@
- 决策:这两类轻量玩法的生成页隐藏“当前批次”模块,只保留“预计等待”和“计时”并排展示;生成步骤进入页面时按顺序从左侧滑入,强化推进感。
- 影响范围:`CustomWorldGenerationView`、拼图与抓大鹅创作入口调用处、移动端生成页体验文档。
- 验证方式:拼图与抓大鹅生成页在手机竖屏下只显示等待与计时双栏,步骤卡按顺序滑入;其它未传入隐藏参数的生成页继续保留原批次模块。
- 关联文档:`docs/experience/MOBILE_UI_DEV_EXPERIENCE.md`
- 关联文档:`docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 2026-05-14 移动端输入法弹出时平台画布不压缩
@@ -46,7 +62,7 @@
- 决策:主站入口统一注册移动端输入法聚焦适配;输入法未打开时记录稳定布局高度,输入法打开期间 `.platform-viewport-shell` 不跟随 `visualViewport.height` 缩小,只通过 `--platform-keyboard-focus-offset` 上移画面聚焦当前输入框,并临时隐藏移动端底部 dock。
- 影响范围:主站平台壳、移动端创作首页底部输入框、后续所有复用 `.platform-viewport-shell` 的输入表单;业务组件不重复注册键盘适配。
- 验证方式:手机竖屏点击输入框,画布不压缩,输入框移动到输入法上方;输入法关闭后画布回位,底部 dock 恢复。
- 关联文档:`docs/technical/【前端体验】移动端输入法不压缩画布聚焦方案-2026-05-14.md``docs/experience/MOBILE_UI_DEV_EXPERIENCE.md`
- 关联文档:`docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 2026-05-14 抓大鹅物品素材批量重新生成复用 item-assets 替换模式
@@ -54,7 +70,7 @@
- 决策:继续复用 `POST /api/creation/match3d/works/{profileId}/item-assets`,请求体通过 `mode = "replace"` 表达替换模式;前端面板预填当前素材名称,只提交仍能匹配到已有素材的名称。后端只替换匹配素材的 `imageSrc/imageObjectKey/imageViews/status/error`,保留原 `itemId`、列表顺序、模型兼容字段、UI 背景、历史背景音乐和点击音效字段;未匹配名称不计费、不新增、不持久化。
- 影响范围Match3D 结果页素材配置、前端/后端 shared contracts、`api-server` Match3D item-assets 编排、运行态物品类型映射和素材生成技术文档。
- 验证方式:执行 `npm run test -- src/components/match3d-result/Match3DResultView.test.tsx``cargo test -p api-server match3d_item_asset --manifest-path server-rs\Cargo.toml``cargo test -p api-server match3d_regenerated_asset --manifest-path server-rs\Cargo.toml``npm run check:encoding`
- 关联文档:`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 2026-05-14 拼图与抓大鹅音频生成入口临时关闭
@@ -62,7 +78,7 @@
- 决策:拼图 `compile_puzzle_draft` 不再自动生成背景音乐,结果页素材配置只保留 `UI`;抓大鹅 `match3d_compile_draft` 和批量新增只生成 2D 图片、背景和容器 UI不再调用 Suno/Vidu结果页隐藏 `背景音乐` 子 Tab 与点击音效生成控件;通用 `/api/creation/audio/*` 当前整体返回 `410 Gone`。历史已写入的 `backgroundMusic` / `clickSound` 字段保留,运行态继续兼容播放旧音频。
- 影响范围:`api-server` 拼图/抓大鹅草稿编排、通用创作音频路由、拼图/抓大鹅结果页、生成进度模型、相关技术文档。
- 验证方式:执行拼图/抓大鹅结果页定向测试、生成进度单测、`cargo check -p api-server --manifest-path server-rs/Cargo.toml``npm run check:encoding`
- 关联文档:`docs/technical/PUZZLE_MATCH3D_RESULT_AUDIO_TAB_2026-05-11.md``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 2026-05-14 抓大鹅物品素材 sheet 改用 VectorEngine Gemini
@@ -70,7 +86,7 @@
- 决策:抓大鹅物品素材 sheet 生图固定走 VectorEngine `POST {VECTOR_ENGINE_BASE_URL}/v1beta/models/gemini-3-pro-image-preview:generateContent?key={VECTOR_ENGINE_API_KEY}`,请求体使用 `contents[].parts[].text``generationConfig.responseModalities = ["TEXT", "IMAGE"]``imageConfig.aspectRatio = "1:1"`;响应从 `candidates[].content.parts[].inlineData.data` / `inline_data.data` 读取 base64 图片。封面、9:16 纯背景图、1:1 容器 UI 图、切图、OSS、扣费和运行态消费链路保持不变音频以后续“拼图与抓大鹅音频生成入口临时关闭”决策为准。
- 影响范围:`server-rs/crates/api-server/src/match3d.rs``server-rs/crates/api-server/src/config.rs``deploy/env/api-server.env.example`、抓大鹅素材生成技术文档。
- 验证方式:执行 `cargo test -p api-server match3d_material_sheet --manifest-path server-rs\Cargo.toml``cargo test -p api-server match3d_vector_engine_gemini --manifest-path server-rs\Cargo.toml``cargo check -p api-server --manifest-path server-rs\Cargo.toml``npm run check:encoding`
- 关联文档:`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 2026-05-14 草稿页作品卡对齐分类页列表
@@ -78,7 +94,7 @@
- 决策:草稿页作品卡统一收口为与分类页一致的横向列表卡结构,左侧承载标题/状态/类型/摘要与必要数据,右侧显示带透明度的封面图;移动端保持单列列表,网页端使用两到三列卡片式网格,避免宽屏长条列表。不再常驻“继续创作”“查看详情”“查看进度”等右侧动作按钮。原有删除、分享、积分激励、公开统计、未读红点全部保留,其中删除与分享进入左滑操作层,常态不显示删除按钮,也不得透出删除底层。生成中的作品在整卡上加半透明蒙版、旋转等待符号和“生成中...”标识,但不移除任何原有信息。
- 影响范围:`src/components/custom-world-home/CustomWorldCreationHub.tsx``src/components/custom-world-home/CustomWorldWorkCard.tsx`、相关样式与测试、草稿页 UI 文档。
- 验证方式:草稿页作品卡与分类页列表视觉口径保持一致;`npm run test -- src/components/custom-world-home/CustomWorldCreationHub.test.tsx src/components/custom-world-home/CustomWorldCreationHub.interaction.test.tsx``npm run typecheck``npm run check:encoding`
- 关联文档:`docs/design/MOBILE_CREATION_WORK_LIST_TWO_COLUMN_LAYOUT_2026-04-29.md``docs/experience/MOBILE_UI_DEV_EXPERIENCE.md`
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
2026-05-14 补充:草稿页作品卡不再用“草稿 / 已发布”文字标识状态,改为图标化 UI 状态点;作品封面直接铺到卡片右半区并从右向左渐隐;已发布作品右上角常驻分享图标;草稿长按弹出删除面板,已发布长按弹出分享和删除面板。
@@ -88,7 +104,7 @@
- 决策:运行期认证变更继续由 `module-auth` 生成一致内存快照,但 `api-server` 改为调用 `import_auth_store_snapshot_json` 直接覆盖导入 `user_account/auth_identity/refresh_session``auth_store_projection_meta/default` 只记录正式认证表最近一次导入时间;`upsert_auth_store_snapshot``import_auth_store_snapshot` 仅保留为旧库迁移和兜底入口。
- 影响范围:`spacetime-module` auth procedures/tables、`spacetime-client` auth facade/bindings、`api-server` 认证同步和启动恢复、SpacetimeDB 表目录与认证 Stage 3 文档。
- 验证方式:执行 `npm run spacetime:generate -- --rust-only``cargo check -p api-server --manifest-path server-rs/Cargo.toml`、认证相关定向测试和 `npm run check:encoding`
- 关联文档:`docs/technical/AUTH_SPACETIMEDB_FORMAL_TABLE_RECOVERY_STAGE3_2026-04-24.md``docs/technical/SPACETIMEDB_TABLE_CATALOG.md`
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## 2026-05-13 微信小程序支付以后端通知为唯一入账事实
@@ -96,7 +112,7 @@
- 决策:`paymentChannel = "mock"` 继续创建即 paid 订单并立即入账;`paymentChannel = "wechat_mp"` 先在 `profile_recharge_order` 写入 `pending` 订单,再由 `api-server` 调微信支付 JSAPI 下单并返回小程序 `wx.requestPayment` 参数。小程序或 H5 的支付成功回调只触发刷新,不直接发放泥点或会员;最终入账只由 `/api/profile/recharge/wechat/notify` 验签、解密并确认 `trade_state = SUCCESS` 后完成。`provider_transaction_id` 保存微信支付平台交易号,用于对账、查单、退款和客服排障。
- 影响范围:`profile_recharge_order` 表、SpacetimeDB 充值 procedure、`api-server` 微信支付客户端、小程序 native 支付页、H5 充值弹窗与共享 contract。
- 验证方式:执行 `npm run typecheck``npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx``cargo test -p module-runtime recharge --manifest-path server-rs/Cargo.toml``cargo test -p api-server wechat_pay --manifest-path server-rs/Cargo.toml`,后端联调仍用 `npm run api-server``/healthz`
- 关联文档:`docs/technical/MY_TAB_ACCOUNT_RECHARGE_IMPLEMENTATION_2026-04-25.md``docs/technical/SPACETIMEDB_TABLE_CATALOG.md`
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## 2026-05-13 修改密码后全设备强制下线
@@ -104,7 +120,7 @@
- 决策:`POST /api/auth/password/change` 成功后必须在同一认证真相更新中撤销该用户全部 active `refresh_session`,继续递增 `token_version`,响应清除当前 refresh cookie前端 `changePassword` 成功后清空本地 access token 并回到未登录态。用户需要使用新密码重新登录。
- 影响范围:`module-auth` 修改密码用例、`api-server` password management route、`AuthGate``authService`、密码登录/重置技术文档。
- 验证方式:执行 `cargo test -p api-server --manifest-path server-rs/Cargo.toml password_change_allows_login_with_new_password_only -- --nocapture``npm run test -- AuthGate.test.tsx authService.test.ts``npm run check:encoding``git diff --check`
- 关联文档:`docs/technical/PASSWORD_LOGIN_CHANGE_RESET_DESIGN_2026-04-24.md``docs/technical/AUTH_SESSIONS_QUERY_DESIGN_2026-04-21.md`
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## 2026-05-13 refresh_session 会话组后端聚合与远端踢下线
@@ -112,7 +128,7 @@
- 决策:`GET /api/auth/sessions` 由后端按“同设备 + 同 IP”聚合 active refresh sessions响应保留代表 `sessionId` 并新增 `sessionIds/sessionCount`;组内包含当前 refresh hash 或 Bearer `sid` 时整组视为当前设备组,前端不展示踢下线。新增 `POST /api/auth/sessions/{session_id}/revoke`,只允许撤销当前用户自己的非当前会话,不递增 `token_version`,但认证中间件会校验 access token `sid` 对应 active refresh session使被踢设备立即失效。`/api/auth/logout` 在 refresh cookie 缺失时回退用 Bearer `sid` 撤销当前 session并继续递增 `token_version`
- 影响范围:`module-auth` refresh session service、`api-server` auth middleware/logout/sessions route、`shared-contracts`/TS auth contract、`AuthGate``AccountModal`、认证会话技术文档和路由/埋点索引。
- 验证方式:执行 `cargo test -p module-auth --manifest-path server-rs/Cargo.toml refresh_session``cargo test -p api-server --manifest-path server-rs/Cargo.toml auth_sessions -- --nocapture``cargo test -p api-server --manifest-path server-rs/Cargo.toml revoke_auth_session -- --nocapture``cargo test -p api-server --manifest-path server-rs/Cargo.toml logout_succeeds_without_refresh_cookie_when_bearer_token_is_valid -- --nocapture``npm run test -- AuthGate.test.tsx AccountModal.test.tsx authService.test.ts``npm run check:encoding``git diff --check`,并用 `npm run api-server` 检查 `/healthz`
- 关联文档:`docs/technical/AUTH_SESSIONS_QUERY_DESIGN_2026-04-21.md``docs/technical/SPACETIMEDB_REFRESH_SESSION_TABLE_DESIGN_2026-04-21.md``docs/technical/SERVER_RS_DDD_G1_CONTRACT_AND_ROUTE_MATRIX_2026-04-29.md`
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 2026-05-12 抓大鹅入口素材风格改为 2D 常见素材风格
@@ -120,7 +136,7 @@
- 决策:抓大鹅创作入口 `2D素材风格` 固定为 `扁平图标 / 赛璐璐卡通 / 像素复古 / 手绘水彩 / 贴纸描边 / 厚涂图标 / 自定义`;默认风格为 `flat-icon`。入口参考图统一由 `npm run assets:match3d-style-references -- --live` 调用 VectorEngine `gpt-image-2-all` 生成,输出到 `public/match3d-style-references/`。旧 3D 风格参考图不再保留为入口资产。
- 影响范围:`Match3DAgentWorkspace`、抓大鹅入口交互测试、Match3D PRD、素材生成流水线技术文档、F1 入口文档和 `public/match3d-style-references/` 静态资产。
- 验证方式:执行 `npm run test -- src\components\match3d-creation\Match3DAgentWorkspace.interaction.test.tsx``cargo test -p shared-contracts match3d --manifest-path server-rs\Cargo.toml``npm run typecheck``npm run check:encoding`,并人工抽查 `.tmp/match3d-style-preview.png`
- 关联文档:`docs/prd/AI_NATIVE_MATCH3D_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-04-30.md``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md``docs/technical/MATCH3D_F1_CREATION_ENTRY_AND_AGENT_UI_2026-04-30.md`
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 2026-05-12 拼图与抓大鹅草稿背景音乐按纯音乐自动生成
@@ -128,7 +144,7 @@
- 决策:复用通用 VectorEngine Suno 创作音频链路,不新增 SpacetimeDB 表;拼图音乐保存到首关 `PuzzleDraftLevel.backgroundMusic`,运行态通过 `PuzzleRuntimeLevelSnapshot.backgroundMusic` 下发;抓大鹅音乐保存到首个 `generatedItemAssets[].backgroundMusic`。两者草稿生成都使用 `title` 驱动、`prompt = ""``make_instrumental = true`;自动草稿阶段必须拿到可播放 `audioSrc` 才能返回成功,失败时停留在生成页并允许重试同一 session/profile。结果页内的手动重新生成继续作为已有草稿的补救入口。
- 影响范围:`api-server` 音频生成、拼图草稿编译、抓大鹅草稿编译、Puzzle/Match3D 结果页和运行态音频播放。
- 验证方式:检查草稿 response / work detail 中的 `backgroundMusic.audioSrc`,运行态开局后隐藏 audio 循环播放;执行音频相关后端 check、前端 typecheck 和编码检查。
- 关联文档:`docs/technical/PUZZLE_MATCH3D_RESULT_AUDIO_TAB_2026-05-11.md``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 2026-05-12 拼图 UI 背景图复用 levels_json 持久化
@@ -136,7 +152,7 @@
- 决策:拼图 UI 背景字段存入首关 `levels_json`,字段为 `uiBackgroundPrompt``uiBackgroundImageSrc``uiBackgroundImageObjectKey``compile_puzzle_draft` 草稿编译阶段在首图完成后自动生成首关 UI 背景,自动草稿阶段必须拿到 `uiBackgroundImageSrc``uiBackgroundImageObjectKey` 才能返回成功;结果页新增 `UI` Tab可编辑提示词并触发 `generate_puzzle_ui_background`,手动生成失败只展示在当前面板。`api-server` 读取 `public/ui-previews/puzzle-image-compact-ui-2026-05-08.png` 作为非拼图 UI 参考图,调用 VectorEngine `gpt-image-2-all` 生成 9:16 背景并要求中央正方形拼图区与外部 UI 背景边界清晰。SpacetimeDB 只保存结果,不做外部 I/O。
- 影响范围:拼图结果页、拼图运行态背景渲染、拼图 agent action、`module-puzzle` / `spacetime-module` / `spacetime-client` 的拼图关卡 JSON 映射、拼图流程技术文档。
- 验证方式:执行 `npm run test -- src/components/puzzle-result/PuzzleResultView.test.tsx``cargo test -p api-server puzzle_ui_background --manifest-path server-rs/Cargo.toml``cargo check -p api-server --manifest-path server-rs/Cargo.toml``npm run typecheck``npm run check:encoding`
- 关联文档:`docs/technical/PUZZLE_FORM_CREATION_FLOW_2026-04-29.md`
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 2026-05-12 抓大鹅结果页素材编辑统一走作品级资产面板
@@ -144,7 +160,7 @@
- 决策:结果页 `作品信息` 的封面图点击打开独立面板,封面图面板对齐拼图入口上传卡。已有上传主图时,请求体传 `uploadedImageSrc`AI 重绘走 VectorEngine `/v1/images/edits`;关闭 AI 重绘时只写回上传图,不调用生图。没有上传主图时,请求体传 `referenceImageSrcs`,可混合本地上传、物品素材和 UI 素材,多参考图作为 `gpt-image-2-all` generations 的 `image` 数组传入。生成结果统一调用 `POST /api/creation/match3d/works/{profileId}/cover-image` 并转存到 `generated-match3d-assets``素材配置 > 物品` 列表项点击打开独立预览面板,不再提供单项重新生成按钮;单项删除和批量新增都写回同一份 `generated_item_assets_json`。批量新增调用 `POST /api/creation/match3d/works/{profileId}/item-assets`,复用草稿生成的 2D 素材图、5x5 切图、OSS 上传和可选点击音效链路,仅作用于新增物品,不新增 SpacetimeDB 表。
- 影响范围Match3D 结果页、Match3D works shared contracts、`api-server` Match3D 作品路由、生成资产历史类型和草稿恢复路径。
- 验证方式:执行 `npm run test -- src/components/match3d-result/Match3DResultView.test.tsx``npm run typecheck``cargo test -p api-server match3d --manifest-path server-rs/Cargo.toml``cargo check -p api-server --manifest-path server-rs/Cargo.toml``npm run check:encoding`
- 关联文档:`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 2026-05-12 平台法律文档入口与登录协议确认
@@ -152,14 +168,14 @@
- 决策:法律文档内容读取 `media/files/*.md`,统一通过 `LegalDocumentModal` 独立弹窗展示;“我的”页常用功能区固定 3 列,设置入口下方展示法律信息和 `京ICP备2026025677号` 外链。登录弹窗用 `genarrative.auth.legal-consent.v1` 记录本机确认,首次未勾选时短信 / 密码登录按钮禁用,法律链接不自动勾选。
- 影响范围:平台个人页、登录弹窗、法律 Markdown 渲染和前端认证交互测试。
- 验证方式:执行 `npm run test -- src/components/auth/AuthGate.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`、触碰文件 ESLint、`npm run check:encoding`
- 关联文档:`docs/prd/PROFILE_LEGAL_INFO_AND_AUTH_AGREEMENT_PRD_2026-05-12.md`
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
## 2026-05-12 微信小程序待绑定手机号优先走原生手机号授权
- 背景:微信小程序 `web-view` 壳登录后若返回 `pending_bind_phone`H5 仍会展示手输手机号和短信验证码绑定页,体验上多了一步。
- 决策:小程序壳在 `pending_bind_phone` 时暂不打开 H5先展示原生 `button open-type="getPhoneNumber"`;用户同意后把 `bindgetphonenumber` 返回的 `code` 作为 `wechatPhoneCode` 调用 `/api/auth/wechat/bind-phone`。后端通过微信 `stable_token``getuserphonenumber` 换取平台验证后的手机号,再复用现有微信待绑定账号合并逻辑并重新签发 active 系统 token。H5 旧短信验证码绑定流程继续作为非小程序环境兜底。
- 影响范围:`miniprogram/pages/web-view/index.*``server-rs/crates/platform-auth``server-rs/crates/api-server/src/wechat_auth.rs`、认证共享契约、微信小程序 web-view 壳技术文档。
- 验证方式:执行 `npm run check:encoding``node scripts/check-wechat-miniprogram-auth-smoke.mjs``cargo test -p shared-contracts wechat_bind_phone_request_accepts_mini_program_phone_code --manifest-path server-rs/Cargo.toml``cargo test -p api-server wechat_miniprogram_bind_phone_code_activates_pending_user --manifest-path server-rs/Cargo.toml -- --nocapture`
- 关联文档:`docs/technical/WECHAT_MINIPROGRAM_WEB_VIEW_SHELL_2026-05-03.md`
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
## 2026-05-13 宝贝爱画先作为寓教于乐独立本地 Demo 落地
@@ -167,7 +183,7 @@
- 决策:`baby-love-drawing / 宝贝爱画` 先作为独立运行态接入,入口由发现页寓教于乐默认卡片打开,并支持 `/runtime/baby-love-drawing` 直达;关闭 `VITE_ENABLE_EDUTAINMENT_ENTRY` 时前端不展示频道/卡片且直达路由回落主应用。绘画魔法统一走 `POST /api/creation/edutainment/baby-love-drawing/magic` 后端安全代理,使用 VectorEngine `gpt-image-2-all` 与原始画布 Data URL 参考图生成绘本风图片;保存只写 localStorage正式持久化后续再设计。
- 影响范围:`packages/shared/src/contracts/edutainmentBabyDrawing.ts``src/components/edutainment-runtime/BabyLoveDrawingRuntimeShell.tsx``src/services/edutainment-baby-drawing/``src/routing/appRoutes.tsx``src/components/rpg-entry/RpgEntryHomeView.tsx``server-rs/crates/api-server/src/edutainment_baby_drawing.rs``src/index.css`、宝贝爱画 PRD 与技术方案。
- 验证方式:执行宝贝爱画 model/runtime/service/route 定向测试、`npm run typecheck`、定向 ESLint、`cargo test -p api-server edutainment_baby_drawing --manifest-path server-rs/Cargo.toml``cargo test -p api-server resolves_runtime_paths_to_creation_type_ids --manifest-path server-rs/Cargo.toml` 和编码检查;真实魔法生成需配置 `VECTOR_ENGINE_BASE_URL``VECTOR_ENGINE_API_KEY`
- 关联文档:`docs/prd/BABY_LOVE_DRAWING_EDUTAINMENT_LEVEL_PRD_2026-05-13.md``docs/technical/BABY_LOVE_DRAWING_RUNTIME_DEMO_IMPLEMENTATION_2026-05-13.md`
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 2026-05-12 宝贝识物创作同时生成玩法视觉主题包
@@ -175,7 +191,7 @@
- 决策:`POST /api/creation/edutainment/baby-object-match/assets` 同一次 image-2 / VectorEngine 调用链返回两个物品图和 `visualPackage`。视觉包包含 `background``ui-frame``gift-box``basket``smoke-puff` 五类资源;总风格保持寓教于乐明亮卡通绘本插画风,主题按两个物品关键词匹配,水果偏果园自然,动漫角色 / 玩具偏动漫玩具。物品图和礼物盒 / 篮子 / UI / 烟雾特效资源走透明 PNG 后处理,背景为清爽不遮挡玩法区的环境图;运行态中礼物盒按约 2 倍视觉尺寸展示、篮子按约 1.5 倍展示,礼物盒打开时使用 `smoke-puff` 弹出中央物品并移除礼盒。前端草稿保存该包,运行态消费该包;旧草稿以 `visualPackage = null` 继续使用 CSS 兜底。
- 影响范围:`packages/shared/src/contracts/edutainmentBabyObject.ts``server-rs/crates/api-server/src/edutainment_baby_object.rs``src/services/edutainment-baby-object/babyObjectMatchClient.ts``src/components/edutainment-runtime/BabyObjectMatchRuntimeShell.tsx``src/index.css`、宝贝识物 PRD 与技术方案。
- 验证方式:执行宝贝识物 service / runtime 定向测试、`cargo test -p api-server edutainment_baby_object --manifest-path server-rs/Cargo.toml`、相关 ESLint 与编码检查;真实生图需配置 `VECTOR_ENGINE_BASE_URL``VECTOR_ENGINE_API_KEY`
- 关联文档:`docs/prd/BABY_OBJECT_MATCH_EDUTAINMENT_TEMPLATE_PRD_2026-05-11.md``docs/technical/BABY_OBJECT_MATCH_CREATION_PUBLISH_IMPLEMENTATION_2026-05-11.md`
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 2026-05-11 拼图与抓大鹅结果页音频资产复用通用创作音频链路
@@ -184,7 +200,7 @@
- 2026-05-12 补充:抓大鹅入口页新增 `generateClickSound` 开关,默认关闭;开启时 `match3d_compile_draft` 在生成首批 2D 物品素材后并行生成各物品点击音效,并继续复用通用创作音频路由的 OSS、资产绑定和扣费口径。
- 影响范围:拼图结果页、抓大鹅结果页、抓大鹅运行态音频播放、通用创作音频 shared contracts、`api-server` 音频路由和资产绑定。
- 验证方式:执行拼图/抓大鹅结果页定向测试、`npm run typecheck``cargo test -p api-server vector_engine_audio_generation``cargo test -p shared-contracts creation_audio``cargo check -p api-server`,真实生成需配置 VectorEngine 与 OSS 私密环境。
- 关联文档:`docs/technical/PUZZLE_MATCH3D_RESULT_AUDIO_TAB_2026-05-11.md`
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 2026-05-11 寓教于乐公开作品使用独立 `edutainment` 来源接入
@@ -192,7 +208,7 @@
- 决策:寓教于乐公开作品在前端公共作品模型中使用 `sourceType = edutainment`,当前只承接 `templateId = baby-object-match``templateName = 宝贝识物`;进入“发现 / 寓教于乐”频道仍必须携带精确等于 `寓教于乐` 的公开标签,不因模板名或近似标签自动归类。公开详情、推荐运行态、改造、编辑、点赞和分享链路都必须显式识别 `edutainment`,不得回落到 RPG 默认处理。
- 影响范围:公开作品卡、发现页频道、作品号搜索、公开详情深链、分享、作品架聚合、后续儿童动作 Demo 模板的发布结果展示。
- 验证方式执行第4线程定向单测、前端类型检查、ESLint 与编码检查;关闭 `VITE_ENABLE_EDUTAINMENT_ENTRY` 时确认精确 `寓教于乐` 作品不可通过任何公开入口访问。
- 关联文档:`docs/design/CHILD_MOTION_EDUTAINMENT_DISCOVER_ENTRY_2026-05-09.md``docs/prd/BABY_OBJECT_MATCH_EDUTAINMENT_TEMPLATE_PRD_2026-05-11.md``docs/technical/BABY_OBJECT_MATCH_CREATION_PUBLISH_IMPLEMENTATION_2026-05-11.md`
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 2026-05-10 儿童动作 Demo 视觉资产统一为绘本草地舞台
@@ -200,7 +216,7 @@
- 决策:热身舞台及后续儿童动作 Demo 场景、物品、UI 资源统一采用明亮卡通绘本草地视觉语言。真实资源默认输出到 `public/child-motion-demo/`。背景沿用 `picture-book-grass-stage.png`;地面、指示环、角色轮廓和 UI 已拆分为 v2 用途专属资源:`picture-book-foreground-grass-v2.png``picture-book-ground-ring-v2.png``picture-book-character-outline-v2.png``picture-book-hud-strip-v2.png``picture-book-calibration-strip-v2.png``picture-book-start-panel-v2.png``picture-book-ui-button-v2.png`。生成脚本固定为 `scripts/generate-child-motion-demo-assets.mjs`,并通过 `npm run assets:child-motion-demo` 调用 VectorEngine `gpt-image-2-all`;透明资源使用品红底生成后本地去背,中间源图仅保存在 `tmp/child-motion-demo-assets/`。在缺少 `VECTOR_ENGINE_BASE_URL``VECTOR_ENGINE_API_KEY` 时,只允许 dry-run 和 CSS 兜底,不伪造 live 生图结果。
- 影响范围:`src/index.css``src/components/child-motion-demo/ChildMotionWarmupDemo.tsx` 的舞台视觉层、儿童动作 Demo 技术文档、后续 image-2 资产生成流程。
- 验证方式:检查 `/child-motion-demo` 舞台是否在未生成资产时仍有可用草地绘本兜底;补齐 VectorEngine 私密配置后运行 `npm run assets:child-motion-demo -- --live``--live --only <asset-id>` 应能写出对应 PNG并确认页面静态资源返回 `image/png`。若只调整透明去背、裁切或品红边缘,可运行 `npm run assets:child-motion-demo -- --live --postprocess-only --force --only <asset-id>` 复用源图后处理。页面接入时必须按资源原始比例等比使用,不得把方形软纸面板拉伸成 HUD、状态条或底部草坪。
- 关联文档:`docs/technical/CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md``docs/technical/VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md`
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 2026-05-10 方洞挑战从创作页入口和作品架隐藏
@@ -208,7 +224,7 @@
- 决策SpacetimeDB `creation_entry_type_config``square-hole.visible=false` 作为创作页统一开关;创作 Tab 模板入口、旧选择弹层、创作 Hub 卡带和创作页作品架都基于该开关隐藏方洞挑战。既有方洞详情、作品号、广场和运行态链路暂不删除api-server 路由熔断只按 `open=false` 禁用玩法 API。
- 影响范围SpacetimeDB 入口配置默认种子、`platformEntryCreationTypes``CustomWorldCreationHub``PlatformEntryFlowShellImpl` 以及创作入口相关文档和回归测试。
- 验证方式:执行入口配置、创作 Hub 和平台入口交互定向测试,确认看不到“方洞挑战” Tab、按钮和作品架条目。
- 关联文档:`docs/technical/NEW_WORK_ENTRY_CONFIG_2026-05-01.md``docs/design/PLATFORM_CREATE_TAB_CREATIVE_AGENT_HOME_2026-05-05.md`
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 2026-05-14 视觉小说从创作页入口隐藏
@@ -216,7 +232,7 @@
- 决策SpacetimeDB `creation_entry_type_config` 默认种子中 `visual-novel.visible=false``open=false`;旧默认可见配置会被迁移为隐藏和关闭。前端继续只消费 `GET /api/creation-entry/config`,不得用硬编码恢复视觉小说模板入口。
- 影响范围SpacetimeDB 入口配置默认种子、api-server 测试配置、创作页模板 Tab、创作 Hub 测试和创作入口文档。
- 验证方式:执行入口配置、创作 Hub、平台入口交互和 api-server 路由熔断定向测试,确认“视觉小说”不出现在创作页且 `/api/creation/visual-novel/*` 默认被熔断。
- 关联文档:`docs/design/PLATFORM_CREATE_TAB_CREATIVE_AGENT_HOME_2026-05-05.md``docs/technical/ADMIN_CREATION_ENTRY_SWITCH_CONFIG_2026-05-11.md`
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 2026-05-10 运行态输入设备抽象层全项目通用化
@@ -224,7 +240,7 @@
- 决策:前端运行态输入统一通过 `src/services/input-devices/` 承接,设备适配层只输出 `press / move / release / tap / drop` 等通用语义和通用坐标;玩法组件自己解释目标对象、落点和业务动作,输入层不得写拼图等玩法专用规则。
- 影响范围:拼图运行态鼠标/触控/mocap 输入、后续运行态设备接入、运行态输入技术文档与相关前端回归测试。
- 验证方式:执行 `npm run test -- src\services\input-devices\runtimeDragInputController.test.ts``npm run test -- src\components\puzzle-runtime\PuzzleRuntimeShell.test.tsx``npm run typecheck` 和编码检查。
- 关联文档:`docs/technical/RUNTIME_INPUT_DEVICE_ABSTRACTION_2026-05-10.md``docs/technical/PUZZLE_RUNTIME_FRONTEND_LOGIC_REHOME_2026-05-02.md`
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## 2026-05-11 前端调试模式统一判断
@@ -232,7 +248,7 @@
- 决策:前端新增 `src/config/debugMode.ts` 作为全局调试模式判断,默认跟随 Vite 开发态,允许 `VITE_DEBUG_MODE=true/false` 显式覆盖。2026-05-14 起,拼图运行态已临时移除 mocap 调用、体感光标和 mocap 调试面板;调试模式仍供其它局部诊断 UI 使用。
- 影响范围:前端局部调试 UI、拼图运行态 mocap 诊断面板、`.env.example` 和运行态输入技术文档。
- 验证方式:执行 `npm run test -- src\components\puzzle-runtime\PuzzleRuntimeShell.test.tsx``npm run typecheck` 和编码检查。
- 关联文档:`docs/technical/RUNTIME_INPUT_DEVICE_ABSTRACTION_2026-05-10.md`
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 2026-05-10 儿童动作热身关直接消费 mocap 数据源
@@ -240,7 +256,7 @@
- 决策:热身关全流程直接接入 `useMocapInput`,通过本地 mocap WebSocket `/stream` 消费 `general.body.center_norm` 身体中心、`actions/action/gesture/gestures/event/name/type` 动作名,以及 `hands[]``leftHand/rightHand``left_hand/right_hand` 手部坐标;位置步骤由身体中心推进,`wave_greeting``wave_left_hand``wave_right_hand``jump_once` 由 mocap 手势/轨迹推进。浏览器摄像头只作为背景层,动作数据源状态优先展示,键鼠仍作为本地调试兜底。
- 影响范围:`src/services/useMocapInput.ts``src/components/child-motion-demo/ChildMotionWarmupDemo.tsx`、对应单测与热身关技术文档。
- 验证方式:执行 `npx vitest run src/services/useMocapInput.test.ts src/components/child-motion-demo/ChildMotionWarmupDemo.test.tsx src/components/child-motion-demo/childMotionWarmupModel.test.ts src/services/child-motion-demo/childMotionDebugInput.test.ts src/routing/appRoutes.test.ts``npx eslint ...``npm run typecheck``npm run check:encoding`,并确认 `http://127.0.0.1:8876/stream` WebSocket 可握手、`http://127.0.0.1:3000/child-motion-demo` 可访问。
- 关联文档:`docs/technical/CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md`
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 2026-05-09 GPT-image-2 图片生成统一迁移到 VectorEngine
@@ -248,7 +264,7 @@
- 决策:所有 GPT-image-2 生图请求统一走 VectorEngine `POST /v1/images/generations`,基础配置读取 `VECTOR_ENGINE_BASE_URL` / `VECTOR_ENGINE_API_KEY` / `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`,上游模型使用 `gpt-image-2-all`,请求体不再携带 `official_fallback`,参考图字段改为 `image`。APIMart 只保留给创意 Agent 的 `gpt-5` Responses 文本/多模态链路。
- 影响范围:`api-server` 共享图片 helper、拼图图片生成、角色主图、RPG 场景图、开局 CG 故事板、方洞视觉资产、生产环境示例、gpt-image-2 本地 skill 和相关技术文档。
- 验证方式:执行 `npm run check:encoding``cargo test -p api-server openai_image --manifest-path server-rs/Cargo.toml``cargo test -p api-server puzzle --manifest-path server-rs/Cargo.toml``cargo test -p api-server custom_world_ai --manifest-path server-rs/Cargo.toml``cargo test -p api-server character_visual --manifest-path server-rs/Cargo.toml`,并用 `npm run api-server` + `/healthz` 做后端 smoke。
- 关联文档:`docs/technical/VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md``docs/technical/API_SERVER_EXTERNAL_SERVICE_ENV_CONFIG_2026-05-07.md`
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 2026-05-08 Hyper3D Rodin Gen-2 只通过后端安全代理接入
@@ -256,7 +272,7 @@
- 决策Hyper3D 统一走 `api-server``/api/assets/hyper3d/*` 鉴权路由,配置只读取 `HYPER3D_BASE_URL` / `HYPER3D_API_KEY` / `HYPER3D_MODEL_REQUEST_TIMEOUT_MS` 及兼容 `RODIN_*` 变量;生成提交、状态查询和下载列表都由后端代理。首版不写 SpacetimeDB、不确认 `asset_object`,下载链接后续由调用方决定是否进入 OSS 资产链。
- 影响范围:`api-server` 外部服务配置、Hyper3D route、`shared-contracts` / TS contract、前端 service、生产环境示例和外部服务环境变量文档。
- 验证方式:执行 `cargo test -p api-server hyper3d --manifest-path server-rs/Cargo.toml``cargo test -p shared-contracts hyper3d --manifest-path server-rs/Cargo.toml``cargo check -p api-server --manifest-path server-rs/Cargo.toml``npm run typecheck` 和编码检查;真实 API smoke 只在本地私密环境配置 key 后手动执行。
- 关联文档:`docs/technical/HYPER3D_RODIN_GEN2_MODEL_GENERATION_2026-05-08.md``docs/technical/API_SERVER_EXTERNAL_SERVICE_ENV_CONFIG_2026-05-07.md`
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
## 2026-05-08 APIMart 接口统一携带 `official_fallback`
@@ -266,7 +282,7 @@
- 决策:凡是仓库内调用 APIMart 的 OpenAI 兼容接口,请求体统一携带 `official_fallback: true`;其中图片生成请求直接固定写入,`platform-llm` 的 APIMart GPT-5 client 通过显式开关开启,不默认扩散到 Ark 等其它 provider。
- 影响范围:`server-rs/crates/api-server/src/openai_image_generation.rs``server-rs/crates/api-server/src/puzzle.rs``server-rs/crates/api-server/src/state.rs``server-rs/crates/platform-llm/src/lib.rs``.codex/skills/gpt-image-2-apimart/` 和相关技术文档。
- 验证方式:图片生成与 creative-agent APIMart 路径的单测都应断言 `official_fallback` 已写入请求 JSON编码检查和相关 Rust 测试应持续通过。
- 关联文档:`docs/technical/PUZZLE_APIMART_IMAGE_MODEL_ROUTING_2026-05-01.md``docs/technical/RPG_IMAGE_GENERATION_GPT_IMAGE_2_MIGRATION_2026-05-02.md``docs/technical/CREATIVE_INTERACTIVE_CONTENT_AGENT_TECHNICAL_SOLUTION_2026-05-05.md`
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 2026-05-07 server-rs Cargo 依赖集中到 workspace
@@ -274,23 +290,23 @@
- 决策:`server-rs/Cargo.toml``[workspace.dependencies]` 统一维护第三方依赖版本和 workspace 内部 crate path成员 crate 默认使用 `{ workspace = true }`,只保留自身 feature、optional 或 target-specific 差异;不再新增 `sha1`OSS 与阿里云 OpenAPI 签名统一走 `sha2::Sha256` 对应的 V4/V3 口径。
- 影响范围:`server-rs/Cargo.toml`、所有 `server-rs/crates/*/Cargo.toml``platform-oss``platform-auth`、后续新增 Rust crate 或新增 Rust 依赖的开发流程。
- 验证方式:修改 Cargo 配置后先执行 `cargo metadata --manifest-path server-rs\Cargo.toml --format-version 1 --no-deps`,再按影响范围执行 `cargo check`、DDD 边界检查和编码检查。
- 关联文档:`docs/technical/RUST_WORKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md`
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 2026-05-08 资料页反馈提交必须走 Rust 后端与 SpacetimeDB
- 背景:`/profile/feedback` 首版页面曾只做前端成功态,无法沉淀到用户账号和数据库,也容易与主站平台主题脱节。
- 决策:反馈提交统一走鉴权 HTTP 路由 `POST /api/profile/feedback`,由 `api-server` 取当前 access token 用户,调用 `spacetime-client` facade再通过 `spacetime-module` procedure 写入私有表 `profile_feedback_submission`前端只负责输入采集、Data URL 预览和提交元数据,不再保存 `File[]` 作为外部契约。
- 影响范围:`src/components/platform-entry/PlatformFeedbackView.tsx``src/services/rpg-entry/rpgProfileClient.ts``packages/shared/src/contracts/runtime.ts``server-rs/crates/shared-contracts``api-server``module-runtime``spacetime-client``spacetime-module`、表目录与 bindings。
- 验证方式:前端定向测试应覆盖 Data URL 预览与 `/api/profile/feedback` 请求体;后端变更需同步 `migration.rs``SPACETIMEDB_TABLE_CATALOG.md` 和生成绑定API smoke 使用 `npm run api-server``/healthz`
- 关联文档:`docs/prd/PROFILE_FEEDBACK_ENTRY_PRD_2026-05-08.md``docs/technical/PROFILE_FEEDBACK_BACKEND_INTEGRATION_2026-05-08.md`
- 验证方式:前端定向测试应覆盖 Data URL 预览与 `/api/profile/feedback` 请求体;后端变更需同步 `migration.rs``docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md` 和生成绑定API smoke 使用 `npm run api-server``/healthz`
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 2026-05-06 Maincloud 历史残留引用禁止再使用
- 背景:项目已经全面移除 Maincloud 运行口径,但历史脚本、测试名和文档仍可能让后续开发误用 `api-server:maincloud``GENARRATIVE_SPACETIME_MAINCLOUD_*`
- 决策:`maincloud` / `Maincloud` / `MAINCLOUD` 相关代码、脚本、测试、环境变量、命令和文档要求全部视为历史残留,后续禁止新增、运行或引用;后端 API smoke 统一使用 `npm run api-server` 并检查 `/healthz`
- 影响范围:`AGENTS.md``docs/technical/``.hermes/shared-memory/`、后端启动脚本、测试支撑和所有后续工程文档。
- 影响范围:`AGENTS.md`当前 `docs/` 融合文档`.hermes/shared-memory/`、后端启动脚本、测试支撑和所有后续工程文档。
- 验证方式:新增或修改后端相关文档时,检查不得要求 `api-server:maincloud``GENARRATIVE_SPACETIME_MAINCLOUD_*`;触碰历史残留时同步删除或改名。
- 关联文档:`docs/technical/MAINCLOUD_REFERENCE_REMOVAL_POLICY_2026-05-06.md``docs/technical/SPACETIMEDB_CLOUD_CONFIG_REMOVAL_2026-05-02.md`
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
## 2026-05-05 新手引导首版复用拼图本地运行时
@@ -298,7 +314,7 @@
- 决策:未登录首次访问由前端 localStorage 标记触发;生成入口走公开 BFF `POST /api/runtime/puzzle/onboarding/generate` 生成 1 关临时拼图;登录后保存走鉴权 BFF `POST /api/runtime/puzzle/onboarding/save`,由服务端创建当前用户拼图 agent session 并更新其草稿作品 profile游玩阶段复用现有本地拼图运行时。
- 影响范围:平台入口首屏、新手引导 PRD、拼图 BFF、拼图作品契约与前端 puzzle runtime。
- 验证方式:未登录首次访问应展示新手引导;生成后只进入 1 关本地拼图;通关后登录保存应在当前用户拼图作品架出现草稿作品;不应产生 SpacetimeDB schema 变更。
- 关联文档:`docs/prd/FIRST_LAUNCH_PUZZLE_ONBOARDING_PRD_2026-05-05.md`
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 2026-05-05 text-game 作为陶泥儿幕间文字游戏模板接入
@@ -306,7 +322,7 @@
- 决策:新增 `text-game` 作为陶泥儿 AI 原生文字游戏模板口径,展示名可用“幕间”或“幕间文字”;它与 `visual-novel` 分离,重点是 AI GM、自由行动、状态后果、长期记忆、章节目标和轻量剧本模拟器入口、作品、发布、资产、钱包、埋点、存档和广场全部复用陶泥儿平台接口禁止新增 replay、外部社区、外部支付、外部榜单和私有存档系统。
- 影响范围:后续 `text-game` shared contracts、`module-text-game`、SpacetimeDB 表、`api-server` 路由、前端入口 / workspace / result / runtime、平台作品架和发现聚合。
- 验证方式:后续落地时确认路由使用 `/api/creation/text-game/*``/api/runtime/text-game/*`;确认正式业务真相在 Rust / SpacetimeDB 后端;确认没有 `replay` 能力和外部平台功能误入;确认 `text-game` 不复用 `visual-novel` step 契约作为运行态真相。
- 关联文档:`docs/prd/AI_NATIVE_TEXT_GAME_TEMPLATE_MOKU_REFERENCE_PRD_2026-05-05.md`
- 关联文档:`docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 2026-05-05 2048 玩法模板采用 `twenty-forty-eight` 工程域
@@ -314,15 +330,15 @@
- 决策:面向用户展示名保持 `2048`,工程玩法 ID 固定为 `twenty-forty-eight`Rust 模块与表前缀使用 `twenty_forty_eight`,公开作品号前缀使用 `TF-`;玩法按完整闭环设计,包含 Agent 创作、结果页、试玩、发布、公开运行、后端棋盘裁决、排行榜和作品架 / 广场接入。
- 影响范围:后续 SpacetimeDB 创作入口配置、平台 `SelectionStage`、前端 `twenty-forty-eight-*` 组件与 service、`module-twenty-forty-eight``shared-contracts``spacetime-module` 表、`spacetime-client` facade、`api-server` 路由、作品号和 PRD 索引。
- 验证方式:后续落地时确认用户可见标题为 `2048`,代码、路由和表统一使用 `twenty-forty-eight` / `twenty_forty_eight`;移动、合并、生成新方块、目标达成、失败和榜单成绩由后端正式裁决,前端不伪造分数或目标达成。
- 关联文档:`docs/prd/AI_NATIVE_2048_GAMEPLAY_TEMPLATE_PRD_2026-05-05.md`
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 2026-05-05 幸存者类玩法作为平台模板接入
- 背景:平台继续扩展新玩法模板,需要把幸存者 / 割草 / 轻度 Roguelite 类玩法纳入统一创作中心、作品架、广场和运行态体系,避免再起一套独立小游戏工程。
- 决策:新增 `survivor` 作为 Genarrative 平台玩法模板,统一使用 `server-rs + Axum + SpacetimeDB`,创作端、结果页、试玩、发布和运行态都复用平台接口;前端只负责表现和高频模拟,不承接正式规则真相。
- 影响范围:`docs/prd/AI_NATIVE_SURVIVOR_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-05-05.md`、后续 `survivor` shared contracts、前端入口 / result / runtime、`server-rs` DDD 分层、SpacetimeDB 表设计和平台作品闭环。
- 影响范围:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、后续 `survivor` shared contracts、前端入口 / result / runtime、`server-rs` DDD 分层、SpacetimeDB 表设计和平台作品闭环。
- 验证方式:后续落地时检查 `survivor` 入口、session、work profile、runtime run、checkpoint、升级候选和结算接口是否都落在平台统一链路内并确认没有新增独立小游戏壳层。
- 关联文档:`docs/prd/AI_NATIVE_SURVIVOR_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-05-05.md`
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 2026-05-05 视觉小说 TXT 玩法只作为平台模板接入且删除回放
@@ -330,15 +346,15 @@
- 决策:`visual-novel` 只作为 Genarrative 视觉小说模板接入,保留想法 / 文档 / 空白创建、世界观 / 角色 / 场景 / 剧情阶段编辑、视觉小说 step 运行时、历史和重生成等模板能力;入口、作品、发布、资产、钱包、存档和广场全部使用 Genarrative 平台接口;彻底删除回放、分享回放、回放编译、回放路由、回放表和回放 UI。
- 影响范围:视觉小说 PRD、旧 TXT 文档口径、后续 `visual-novel` shared contracts、前端入口 / result / runtime、`server-rs` DDD 分层、SpacetimeDB 表设计和平台存档接入。
- 验证方式:后续落地时扫描前端、后端、契约、表和文档,确认不存在 `replay` 能力;确认视觉小说没有迁入外部平台账号、订单、会员、促销、后台、公开市场或私有存档系统;确认后端落在 `server-rs + Axum + SpacetimeDB`
- 关联文档:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md``docs/prd/TXT_MODE_CORE_GAMEPLAY_PRD_2026-04-20.md``docs/technical/TXT_MODE_VISUAL_NOVEL_MIGRATION_EXECUTION_PLAN_2026-04-20.md`
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 2026-05-05 视觉小说 VN-02 表与 spacetime-client facade 收口
- 背景:`visual-novel` 后续 API、创作工作台和运行时需要稳定的 SpacetimeDB schema 与 Rust facade且必须延续“无回放、无私有存档”的产品边界。
- 决策:视觉小说首批数据库只落六张表:`visual_novel_agent_session``visual_novel_agent_message``visual_novel_work_profile``visual_novel_runtime_run``visual_novel_runtime_history_entry``visual_novel_runtime_event``visual_novel_runtime_event``public event` 审计事件表,不是 replay 数据源;运行历史只保存继续体验与历史重生成需要的 typed step 和快照哈希。`api-server` 后续接入必须经 `spacetime-client/src/visual_novel.rs` typed facade不直接依赖生成 bindings。
- 影响范围:`server-rs/crates/spacetime-module/src/visual_novel.rs``migration.rs``server-rs/crates/spacetime-client/src/visual_novel.rs``module_bindings/``docs/technical/SPACETIMEDB_TABLE_CATALOG.md`、VN-05 API 联调。
- 影响范围:`server-rs/crates/spacetime-module/src/visual_novel.rs``migration.rs``server-rs/crates/spacetime-client/src/visual_novel.rs``module_bindings/``docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、VN-05 API 联调。
- 验证方式:执行 `npm run spacetime:generate -- --rust-only``cargo check -p spacetime-module``cargo check -p spacetime-client``npm run check:encoding`;扫描视觉小说 schema / facade / 表目录确认没有 `replay` 表、路由或私有 save 表。
- 关联文档:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md``docs/technical/SPACETIMEDB_TABLE_CATALOG.md`
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## 2026-05-05 视觉小说 VN-07 前端创作闭环按阶段边界落地
@@ -346,31 +362,31 @@
- 决策VN-07 前端只接入口、Agent 工作台、可编辑 `VisualNovelResultDraft` 结果页和测试 run`blank` 起点直接生成本地空白草稿进入结果页,`idea` / `document` 继续调用 `/api/creation/visual-novel/sessions`;结果页保存先更新当前 session 草稿,显式“编译草稿”才调用 `/compile`,测试 run 在真实 runtime 不可用时降级为本地 test run。
- 影响范围:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx``src/components/visual-novel-creation/``src/components/visual-novel-result/``packages/shared/src/contracts/visualNovel.ts`、视觉小说 PRD。
- 验证方式:执行前端 typecheck、视觉小说工作台 / 结果页定向测试和编码检查;确认未新增 replay、作品聚合或正式 runtime 能力。
- 关联文档:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 2026-05-07 视觉小说 VN-11 负向扫描门禁
- 背景:视觉小说 TXT 模板进入收口后,需要一个可重复执行的守门方式,避免工程代码误入回放能力或外部平台功能。
- 决策:新增 `npm run check:visual-novel-vn11`,由 `scripts/check-visual-novel-vn11-negative-scan.mjs` 扫描 `src/``packages/shared/src/``server-rs/crates/``docs/``.hermes/shared-memory/`;工程代码中不允许出现 replay / 回放 / 录制 / 复盘类直出命中;外部平台能力误入只在视觉小说实现路径内检查,避免把平台已有账号、会员、后台等能力误判为视觉小说迁入。
- 影响范围:视觉小说 VN-11 验收、后续 `visual-novel` 增量改动、同类新玩法负向扫描脚本。
- 验证方式:执行 `npm run check:visual-novel-vn11`,报告写入 `docs/audits/VN11_NEGATIVE_SCAN_REPORT_2026-05-07.md`;当前扫描结论为工程代码无回放类直出命中,视觉小说实现路径无外部平台能力误入。
- 关联文档:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md``docs/audits/VN11_NEGATIVE_SCAN_REPORT_2026-05-07.md`
- 验证方式:执行 `npm run check:visual-novel-vn11`,报告写入 `.tmp/VN11_NEGATIVE_SCAN_REPORT_2026-05-07.md`;当前扫描结论为工程代码无回放类直出命中,视觉小说实现路径无外部平台能力误入。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 2026-05-07 视觉小说 VN-12 采用单独验收门禁脚本
- 背景VN-12 是视觉小说模板的全链路联调与自动化验收收口任务需要把关键路径、API smoke、前端测试和报告输出固化成可复跑门禁避免后续改动只靠手工口述结论。
- 决策:新增 `npm run check:visual-novel-vn12`,由 `scripts/check-visual-novel-vn12-acceptance.mjs` 校验 PRD、VN-11 报告、关键前端测试、视觉小说 service client、`api-server` / `module-visual-novel` / `shared-contracts` 相关文件和路由命中,并生成 `docs/audits/VN12_FULL_CHAIN_ACCEPTANCE_REPORT_2026-05-07.md`
- 决策:新增 `npm run check:visual-novel-vn12`,由 `scripts/check-visual-novel-vn12-acceptance.mjs` 校验 PRD、VN-11 报告、关键前端测试、视觉小说 service client、`api-server` / `module-visual-novel` / `shared-contracts` 相关文件和路由命中,并生成 `.tmp/VN12_FULL_CHAIN_ACCEPTANCE_REPORT_2026-05-07.md`
- 影响范围VN-12 验收、视觉小说后续回归、同类玩法的收口门禁模式。
- 验证方式:执行 `npm run check:visual-novel-vn12 -- --write-report`报告应覆盖自动化验收清单、API smoke、前端关键路径、桌面/移动端检查说明和已执行命令;若脚本失败,直接回流到对应 owner 修复。
- 关联文档:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md``docs/audits/VN12_FULL_CHAIN_ACCEPTANCE_REPORT_2026-05-07.md`
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 2026-05-07 视觉小说 VN-13 文档与交接收口
- 背景:视觉小说模板主链已经落地完成,需要把 PRD、表目录、prompt 工具说明、负向扫描报告和维护经验收成新开发者可直接接手的一组文档,避免后续仍回头查旧 TXT 迁移方案。
- 决策:视觉小说后续维护的正式入口固定为 `AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md``SPACETIMEDB_TABLE_CATALOG.md``VISUAL_NOVEL_PROMPT_AND_LLM_TOOLS_VN03_2026-05-05.md``VISUAL_NOVEL_IMPLEMENTATION_HANDOFF_2026-05-07.md``VISUAL_NOVEL_HANDOFF_AND_MAINTENANCE_2026-05-07.md``VN11_NEGATIVE_SCAN_REPORT_2026-05-07.md`;旧 TXT 迁移文档仅保留历史参考地位
- 决策:视觉小说后续维护的正式入口固定为当前玩法链路文档、当前后端架构文档和 `npm run check:visual-novel-vn11` / `npm run check:visual-novel-vn12` 两个门禁;旧 TXT 迁移文档和旧视觉小说阶段文档不再作为实现目标
- 影响范围:视觉小说 PRD 收口、技术文档索引、经验文档索引、Hermes 共享记忆和后续维护阅读顺序。
- 验证方式:打开上述文档即可获得当前实现边界、表目录、Prompt 口径、负向扫描和维护经验;后续维护不需要把旧 TXT 平台工程文档重新当作实现目标。
- 关联文档:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md``docs/technical/VISUAL_NOVEL_IMPLEMENTATION_HANDOFF_2026-05-07.md``docs/experience/VISUAL_NOVEL_HANDOFF_AND_MAINTENANCE_2026-05-07.md`
- 验证方式:打开当前融合文档并运行 VN 门禁即可获得当前实现边界、表目录、Prompt 口径、负向扫描和维护经验;后续维护不需要把旧 TXT 平台工程文档重新当作实现目标。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 2026-05-05 平台移动端一级 Tab 改为推荐/发现/创作/草稿/我的
@@ -378,7 +394,7 @@
- 决策:前端内部继续复用 `PlatformHomeTab``home/category/create/saves/profile` 状态值,但用户看到的一级 Tab 分别为“推荐/发现/创作/草稿/我的”;`home` 直接展示公开推荐流,`category` 承载发现页及排行子 Tab`saves` 承载草稿作品架,原存档结构并入“我的-玩过”弹层。
- 影响范围:平台入口导航、移动端推荐页、发现页子 Tab、创作中心作品架、个人页玩过弹层、相关设计文档。
- 验证方式:检查移动端底部导航文案和顺序,确认登录态为“推荐/发现/创作/草稿/我的”,未登录态为“推荐/创作/发现”且创作居中;“推荐”无搜索/频道栏直出作品流,“发现”包含搜索/推荐/今日/分类/排行,“创作”只显示新建入口,“草稿”显示作品架,“我的-玩过”可恢复存档。
- 关联文档:`docs/design/PLATFORM_MOBILE_RECOMMEND_DISCOVER_DRAFT_TAB_REDESIGN_2026-05-05.md`
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 2026-05-14 推荐页卡片主视觉优先于底部作者热区
@@ -386,7 +402,7 @@
- 决策:推荐页卡片底部信息区保持紧凑固定高度,切换手势仍只绑定在该区域;视觉主体高度优先扩展,不再让作者信息区占用过多首屏空间。
- 影响范围:`src/components/rpg-entry/RpgEntryHomeView.tsx` 的推荐页卡片布局,以及 `src/index.css` 中的推荐页卡片热区样式。
- 验证方式:移动端推荐页首屏应明显看到更大的作品内容区,底部作者信息区只保留紧凑一条,不再明显挤压运行态。
- 关联文档:`docs/technical/PLATFORM_MOBILE_RECOMMEND_CARD_SAFE_SWIPE_LAYOUT_2026-05-12.md`
- 关联文档:`docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 2026-05-05 创作 Tab 固定为智能创作首页,草稿 Tab 承接旧作品架
@@ -394,7 +410,7 @@
- 决策:`create` 只承载 `CreativeAgentHome` 智能创作首页与会话流,顶部品牌栏、问候、快捷胶囊、底部输入框和左侧抽屉是主结构;旧的新建作品类型卡不再在 `create` 里展示。原本的 RPG / 拼图 / 大鱼 / Match3D / 方洞 / 视觉小说作品架统一归到 `saves` 草稿 Tab。
- 影响范围:平台创作页布局、创作首页抽屉、草稿页作品架、相关交互测试、旧创作入口 helper。
- 验证方式:移动端点击“创作”直接看到智能创作首页;点击“草稿”看到旧作品架;旧模板入口不再从创作页出现。
- 关联文档:`docs/design/PLATFORM_CREATE_TAB_CREATIVE_AGENT_HOME_2026-05-05.md`
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 2026-05-05 创意互动内容生成 Agent 采用 LangChain-Rust 六模块闭环
@@ -402,7 +418,7 @@
- 决策:新增方案改为基于 LangChain-Rust 的六模块 Agent 架构核心模块是感知、思考、记忆、行动、反思、协作首版只支持拼图玩法但必须先展示多个拼图子模板候选用户选择某个模板后再确认该模板下的关卡模式、关卡数和预计积分范围确认后才生成草稿Agent 理解、规划和修订统一使用 APIMart Responses `gpt-5` 并支持文本/图像多模态输入Agent 创作方式就是填充和修订模板草稿字段,表单化创作页与 Agent 自然语言修订都操作同一份 `PuzzleResultDraft`,且草稿可编辑字段只收敛为 `workTitle``workDescription``workTags``levels[].levelName``levels[].pictureDescription``levels[].pictureReference`;其中 `pictureReference` 已采用 `PuzzleDraftLevel.pictureReference` / Rust `picture_reference` 正式字段方案,不再走 metadata 过渡;单关卡/多关卡图片生成通过拼图模块 Tool 与模板协议实现;生成好的内容必须可立即试玩。
- 影响范围:创作中心入口、`platform-agent``module-creative-agent``module-puzzle` 拼图模板协议和工具、`shared-contracts``api-server` creative facade、SpacetimeDB creative agent 表、拼图玩法工具。
- 验证方式:后续落地时以创意互动内容生成 Agent 技术方案和 Phase 1 PRD 为编码依据,优先完成拼图 Phase 1并执行 shared contracts、module、platform-agent、api-server、前端 typecheck 与编码检查。
- 关联文档:`docs/technical/CREATIVE_INTERACTIVE_CONTENT_AGENT_TECHNICAL_SOLUTION_2026-05-05.md``docs/prd/CREATIVE_INTERACTIVE_AGENT_PHASE1_LANGCHAIN_RUST_PUZZLE_LOOP_PRD_2026-05-05.md`
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 2026-05-05 creative-agent Task C 首版平台 PoC 已落地
@@ -410,7 +426,7 @@
- 决策:新增 `server-rs/crates/platform-agent` 作为独立 workspace crate保留项目自有 `CreativeAgentExecutor`、工具注册表、回调事件和 mock executor`platform-llm` 的 Responses 请求体扩展为可序列化 `input_text` / `input_image` content part。
- 影响范围:`server-rs/Cargo.toml``server-rs/crates/platform-agent``server-rs/crates/platform-llm`、任务 C 的后续 API / SSE 接入。
- 验证方式:`cargo check -p platform-agent``cargo test -p platform-agent``cargo test -p platform-llm responses_multimodal` 已通过。
- 关联文档:`docs/technical/CREATIVE_INTERACTIVE_CONTENT_AGENT_TECHNICAL_SOLUTION_2026-05-05.md`
- 关联文档:`docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 2026-05-05 creative-agent Task E API / SSE facade 已落地
@@ -418,7 +434,7 @@
- 决策:`api-server` 挂载 `/api/runtime/creative-agent/*` 六个鉴权路由creative session 在 Task D 表未收口前暂存在 `api-server` 运行态并按 authenticated user 校验 owner未确认模板前不创建拼图 session`confirm-template` 后才通过既有 `spacetime-client` 创建/编译 `puzzle_agent_session``gpt-5` 请求只从 `APIMART_BASE_URL` / `APIMART_API_KEY` 构造专用 Responses client不复用通用 `GENARRATIVE_LLM_API_KEY`
- 影响范围:`server-rs/crates/api-server/src/creative_agent.rs``creative_agent_sse.rs``app.rs``state.rs``module-puzzle` creative template/tool、Phase 1 PRD。
- 验证方式:`cargo check -p api-server``cargo test -p module-puzzle creative``cargo test -p api-server creative_agent``npm run api-server` 后检查 `/healthz``POST /api/runtime/creative-agent/sessions``POST /api/runtime/creative-agent/sessions/{sessionId}/messages/stream`
- 关联文档:`docs/prd/CREATIVE_INTERACTIVE_AGENT_PHASE1_LANGCHAIN_RUST_PUZZLE_LOOP_PRD_2026-05-05.md`
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 2026-05-10 视觉小说入口收敛为单句创作 + 画风选择
@@ -426,15 +442,15 @@
- 决策:入口页只展示一句话创作输入框和横向视觉画风卡片;画风通过 `seedText` 追加 `视觉画风``画风要求` 两行透传给既有创作链路;点击生成后先进入 `visual-novel-generating` 过程页,再自动进入 `visual-novel-result`。画风卡片主视觉固定消费 `public/visual-novel-style-references/` 下由 VectorEngine `gpt-image-2-all` 生成的静态参考图,不在前端运行时现场调用生图接口。
- 影响范围:`VisualNovelAgentWorkspace``visualNovelEntryGeneration``PlatformEntryFlowShellImpl`、视觉小说 PRD 和创作 Tab 设计文档;不新增后端字段或数据库结构。
- 验证方式:执行 `npm run test -- VisualNovelAgentWorkspace`、视觉小说工作台相关 ESLint、`npx prettier --check``npm run check:encoding``npm run typecheck` 若失败需先区分是否来自无关 Match3D / RPG 既有改动。
- 关联文档:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md``docs/design/PLATFORM_CREATE_TAB_CREATIVE_AGENT_HOME_2026-05-05.md`
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 2026-05-10 用户标签只做后端白名单投影
- 背景:运营邀请码需要给账号打标签,但标签默认不能暴露到前端通用用户资料;拼图排行榜仅需展示特定标签。
- 决策:`user_account.user_tags` 保存账号标签,数据库默认 `None`,业务按空数组读取;后台预置邀请码使用后授予的标签不再使用独立列,统一存放并解析自 `profile_invite_code.metadata_json.userTags`,兼容读取 `user_tags`。通用登录态和个人资料不返回原始标签。首版只在拼图排行榜 `visibleTags` 中白名单投影 `北科`
- 影响范围:用户认证表、邀请码后台、邀请兑换事务、拼图排行榜响应和 UI。
- 验证方式:表结构变更需同步 `migration.rs``SPACETIMEDB_TABLE_CATALOG.md` 和 SpacetimeDB bindings后端运行 `cargo check -p api-server`,后台运行 `npm run admin-web:typecheck`
- 关联文档:`docs/technical/USER_TAG_INVITE_AND_PUZZLE_LEADERBOARD_2026-05-10.md`
- 验证方式:表结构变更需同步 `migration.rs``docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md` 和 SpacetimeDB bindings后端运行 `cargo check -p api-server`,后台运行 `npm run admin-web:typecheck`
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 2026-05-10 抓大鹅草稿元信息由 gpt-4o 生成
@@ -442,7 +458,7 @@
- 决策:`match3d_compile_draft` 使用 `gpt-4o` 生成 `gameName` 与 3 到 6 个标签;`summary` 默认保持空字符串;标签可由结果页 `作品信息` Tab 手动编辑或再次 AI 生成。草稿生成会按难度产出多视角 2D 物品图片并写入 `generated_item_assets_json`,运行态必须优先消费 `generatedItemAssets[].imageViews[]`,默认积木只做兜底。
- 影响范围:`api-server` Match3D 编译、Match3D works 标签接口、结果页 `作品信息``素材配置` Tab、运行态 `Match3DRuntimeShell` / `Match3DPhysicsBoard`、生成进度和 Match3D 技术文档。
- 验证方式:执行 `npm run test -- src/components/match3d-result/Match3DResultView.test.tsx``npm run test -- src/services/miniGameDraftGenerationProgress.test.ts``cargo test -p api-server match3d --manifest-path server-rs/Cargo.toml``npm run check:encoding`,并用 `npm run api-server` 检查 `/healthz`
- 关联文档:`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md``docs/technical/MATCH3D_RODIN_ASSET_TAB_2026-05-10.md` 仅作历史参考
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 2026-05-12 抓大鹅物品种类从消除次数中拆出并改为 2D 五视角素材
@@ -450,7 +466,7 @@
- 决策:难度配置统一使用 `物品种类`:轻松 3、标准 9、进阶 15、硬核 21历史硬核 `clearCount=20` 在运行态升为 21 组三消。新草稿和批量新增不再调用 Rodin、不再生成 GLB。每个物品生成 5 个不同 2D 视角,单张 1K 素材图固定按 5x5 切割,最多承载 5 个物品;超过 5 个物品时由 `api-server` 自动分批并行生图。发布必须校验已生成 `image_ready` 且有 `imageViews[]` 或首图引用的素材数量满足当前难度;试玩通过 `itemTypeCountOverride` 自动降到可用 2D 素材数量。历史模型字段只作为旧数据兼容,不再进入新生产链路。
- 影响范围Match3D 结果页、运行态启动契约、`module-match3d` 初始 run 生成、SpacetimeDB start input / restart、发布校验和 Match3D 技术文档。
- 验证方式:`npm run test -- src/components/match3d-result/Match3DResultView.test.tsx``cargo test -p module-match3d --manifest-path server-rs\Cargo.toml`、相关后端 check / tests。
- 关联文档:`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 2026-05-07 移动端整页缩放由入口统一锁定
@@ -458,7 +474,7 @@
- 决策:主站入口统一使用 `viewport` 锁定 `minimum-scale=1.0``maximum-scale=1.0``user-scalable=no``viewport-fit=cover`,并在应用启动时调用 `lockMobileViewportZoom()` 拦截 iOS `gesture*` 与多指 `touchmove` 触发的页面级缩放。
- 影响范围:主站 `index.html``src/main.tsx`、后续所有依赖主入口的移动端游戏/画布页面;不要求每个画布组件重复实现缩放锁定。
- 验证方式:移动端打开主站后,双指捏合和快速双击不应再缩放整页;单指滚动、点击和组件内交互保持正常。
- 关联文档:`docs/experience/MOBILE_UI_DEV_EXPERIENCE.md`
- 关联文档:`docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 2026-05-07 视觉小说 VN-10 资产引用统一走平台资产对象
@@ -466,7 +482,7 @@
- 决策VN 上传统一复用 `/api/assets/direct-upload-tickets`、OSS 直传、`/api/assets/objects/confirm``/api/assets/read-url`。文档上传后只把 `assetObjectId` 放入 `sourceAssetIds``seedText` 仅放截断摘要;封面、场景、角色、音乐只写 `/generated-*` 引用和平台 asset id。角色立绘写入 `imageAssets[].source = platform_asset`。运行时图片渲染统一使用 `ResolvedAssetImage` 换签。
- 影响范围:`src/services/visual-novel-creation/visualNovelAssetClient.ts``VisualNovelAgentWorkspace``VisualNovelResultView``VisualNovelRuntimeShell``server-rs/crates/api-server/src/visual_novel.rs`
- 验证方式VN 定向前端测试、`npm run typecheck``npm run check:encoding``cargo test -p api-server visual_novel``cargo test -p api-server creation_agent_document_input`
- 关联文档:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 2026-05-04 在仓库 `.hermes/` 中建立团队共享记忆
@@ -474,15 +490,15 @@
- 决策:不共享个人 `~/.hermes`,先在 Genarrative 仓库内使用 `.hermes/` 保存可 Git 同步的团队共享记忆、计划和未来 skills。
- 影响范围:`AGENTS.md``.hermes/README.md``.hermes/shared-memory/`
- 验证方式:任一开发者拉取仓库后,在项目根目录启动 Hermes均可读取同一套 `.hermes/shared-memory/` 文件。
- 关联文档:`.hermes/README.md``.hermes/shared-memory/team-conventions.md`
- 关联文档:`docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 2026-04-25 后端唯一落地口径固定为 Rust / SpacetimeDB
- 背景:项目经历过 Node/Express/PostgreSQL、Go 试验、Rust/SpacetimeDB 等多条后端路线,旧路线文档容易造成开发歧义。
- 决策新功能以后端当前基线为准HTTP 门面使用 Rust `api-server` / Axum业务真相使用 SpacetimeDB领域和契约在 `server-rs` 多 crate 分层维护。
- 影响范围:所有后端、数据真相、运行时状态、创作结果、用户系统、资产、任务、埋点、后台 API 等相关开发。
- 验证方式:开发前优先阅读 `CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md`;旧 `server-node`、Express、PostgreSQL、Go 方向只允许作为迁移参考。
- 关联文档:`docs/technical/CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md``AGENTS.md`
- 验证方式:开发前优先阅读当前后端架构文档;旧 `server-node`、Express、PostgreSQL、Go 方向只允许作为迁移参考。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
## 2026-04-28/29 server-rs DDD 分层与契约矩阵冻结
@@ -490,28 +506,28 @@
- 决策:按 DDD 总纲和 G1 契约/路由矩阵开发:`module-*` 承载领域,`spacetime-module` 承载表和事务,`spacetime-client` 承载 facade`api-server` 承载 HTTP/SSE/BFF`platform-*` 承载外部副作用,`shared-contracts` 承载 DTO。
- 影响范围server-rs 全部 crate、前端 API client、SpacetimeDB schema、旧接口清理。
- 验证方式:执行任务前对照 DDD 总纲、并行任务清单、G1 矩阵;提交前运行相关 DDD 边界检查和定向测试。
- 关联文档:`SERVER_RS_DDD_FULL_REFACTOR_2026-04-28.md``SERVER_RS_DDD_G1_CONTRACT_AND_ROUTE_MATRIX_2026-04-29.md``SERVER_RS_DDD_PARALLEL_TASKLIST_2026-04-29.md`
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
## SpacetimeDB 表结构变更必须显式维护迁移与表目录
- 背景SpacetimeDB 的 schema 迁移模型不同于 PostgreSQL部分变更会触发冲突或拒绝自动迁移。
- 决策:凡涉及 table、reducer、procedure、row shape 或 binding 变化,必须同步 `migration.rs`、表目录和生成绑定;涉及 private 表迁移时按 JSON 导入导出和分片导入流程处理。
- 影响范围:`server-rs/crates/spacetime-module``spacetime-client` bindings、`SPACETIMEDB_TABLE_CATALOG.md`、部署/发布脚本。
- 验证方式:发布前检查 `SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md` 清单,更新 `SPACETIMEDB_TABLE_CATALOG.md`,执行生成绑定和相关测试。
- 关联文档:`SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md``SPACETIMEDB_TABLE_CATALOG.md``SPACETIMEDB_JSON_STRING_MIGRATION_PROCEDURE_2026-04-27.md`
- 影响范围:`server-rs/crates/spacetime-module``spacetime-client` bindings、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、部署/发布脚本。
- 验证方式:发布前检查 `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md` 清单,更新 `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`,执行生成绑定和相关测试。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## 生产部署切换到 systemd + Nginx + 自托管 SpacetimeDB
- 背景:旧一体化启动脚本和历史 Jenkinsfile 已不再是生产发布唯一入口。
- 决策:生产部署以 systemd 托管 SpacetimeDB 与 Rust `api-server`Nginx 负责站点和代理,生产 Jenkinsfile 按 web/api/stdB module/build/deploy/publish 拆分。
- 影响范围部署脚本、服务器目录、维护模式、Jenkins、Nginx、systemd 服务。
- 验证方式生产发布、服务器配置、Jenkins Job 重建或回滚时,先看 `PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`
- 关联文档:`PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`
- 验证方式生产发布、服务器配置、Jenkins Job 重建或回滚时,先看 `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## 个人任务与埋点首版边界冻结
- 背景“我的”Tab、任务、奖励、钱包和埋点涉及用户、运营、分析多条链路需要避免范围泛化。
- 决策:埋点原始事实进入 `tracking_event`,聚合投影进入 `tracking_daily_stat`;个人任务配置/进度/领奖/钱包分别进入 `profile_task_config``profile_task_progress``profile_task_reward_claim``profile_wallet_ledger`;首版个人任务 scope 仅支持 `user`
- 影响范围:用户侧任务中心、后台任务配置、运营查询、埋点查询、钱包流水。
- 验证方式:非 `user` scope 的个人任务配置应被 API 和领域构造层拒绝;任务查询与埋点查询分别放在 `docs/operations/``docs/tracking/`
- 关联文档:`PROFILE_TASK_AND_TRACKING_SYSTEM_2026-05-03.md``RUNTIME_PROFILE_TASK_SCOPE_2026-05-04.md``ANALYTICS_DATE_DIMENSION_IMPLEMENTATION_2026-05-04.md`
- 验证方式:非 `user` scope 的个人任务配置应被 API 和领域构造层拒绝;任务查询与埋点查询口径统一维护在当前开发运维文档
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`

View File

@@ -1,40 +1,20 @@
# 开发工作流
> 用途:给本地 Hermes 和开发人员提供统一的开发、测试、提交流程。具体命令以 `package.json`、`server-rs/Cargo.toml`、`AGENTS.md` 和相关 `docs/` 最新文档为准。
更新时间:`2026-05-15`
## 标准任务流程
## 标准流程
```text
同步代码 读取 AGENTS.md 读取 .hermes/shared-memory → 查找/完善 docs → 制定计划 → 小步实现 本地验证 更新文档/记忆 → 提交
同步代码 -> 读取 AGENTS.md -> 读取 .hermes/shared-memory -> 查当前 docs -> 小步实现 -> 本地验证 -> 更新 docs / .hermes -> 提交
```
## 建议启动方式
当前 `docs/` 已压缩为少量融合文档。复杂任务优先读:
在项目根目录启动 Hermes
```bash
cd /path/to/Genarrative
hermes
```
在本机当前常见路径为:
```bash
/home/dsk/workspace/Genarrative
```
其他开发者以自己本地实际路径为准,不要把个人绝对路径写入共享文档作为通用规则。
## 开发前检查清单
- [ ] 当前分支是否正确
- [ ] 是否已拉取最新代码
- [ ] 是否阅读 `AGENTS.md`
- [ ] 是否阅读 `.hermes/shared-memory/` 相关文件
- [ ] 是否阅读 `README.md` 中的运行和检查命令
- [ ] 是否阅读 `docs/README.md` 及任务相关分类 README
- [ ] 是否存在足够具体的 PRD / 设计 / 技术文档
- [ ] 是否明确测试、验收和文档更新方式
1. `docs/README.md`
2. `docs/【项目基线】当前产品与工程约束-2026-05-15.md`
3. `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
4. `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
5. `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## 本地运行命令
@@ -44,19 +24,12 @@ hermes
npm install
```
完整联调开发环境
完整联调:
```bash
npm run dev
```
该命令会启动:
- SpacetimeDB standalone
- Rust `api-server`
- 主站 Vite
- 后台 Vite
单独启动前端:
```bash
@@ -69,15 +42,7 @@ npm run dev:web
npm run api-server
```
该命令会保留终端实时输出,并把同一份输出持久化到 `logs/api-server/api-server-<timestamp>.log`。完整联调入口 `npm run dev` / `npm run dev:rust` 启动的 Rust `api-server` 也会写入 `logs/api-server/api-server-dev-rust-<timestamp>.log`。如需改写路径,可设置 `GENARRATIVE_API_SERVER_LOG_FILE`;如只改目录,可设置 `GENARRATIVE_API_SERVER_LOG_DIR`
查看本地 Rust/SpacetimeDB 日志:
```bash
npm run dev:rust:logs
```
后台管理前端:
后台前端:
```bash
npm run admin-web:dev
@@ -85,128 +50,82 @@ npm run admin-web:build
npm run admin-web:typecheck
```
SpacetimeDB bindings 生成
SpacetimeDB bindings
```bash
npm run spacetime:generate
```
## 常用检查命令
- 后端通用用户行为埋点统一通过 `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 check:spacetime-schema
npm run check:server-rs-ddd
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:data
npm run check:overrides
npm run check:smoke
npm run check:content
```
全量检查:
综合检查:
```bash
npm run lint
npm run check
```
DDD 边界检查
视觉小说门禁
```bash
npm run check:server-rs-ddd
npm run check:visual-novel-vn11
npm run check:visual-novel-vn12
```
## 后端相关默认验证
## 后端默认验证
后端修改后,按 DDD 文档中的验收命令执行。涉及 API smoke 时
后端代码修改后按范围选择
- 使用 `npm run api-server` 重新拉起后端。
- 禁止使用 `npm run api-server:maincloud``npm.cmd run api-server:maincloud` 或任何 `GENARRATIVE_SPACETIME_MAINCLOUD_*` 口径;这些只属于历史残留。
- 检查 `/healthz`
- 执行对应自动测试。
- 涉及 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、插入中间、字段删除/改名/重排/改类型,以及漏改迁移、表目录或绑定。
- `cargo test -p <crate> --manifest-path server-rs/Cargo.toml`
- `cargo check -p api-server --manifest-path server-rs/Cargo.toml`
- `cargo check -p spacetime-client --manifest-path server-rs/Cargo.toml`
- `cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml`
- `npm run check:server-rs-ddd`
- `npm run api-server` 后请求 `/healthz`
关键文档
涉及 SpacetimeDB table、reducer、procedure、row shape 或 bindings 时,还必须运行
- `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`
```bash
npm run spacetime:generate
npm run check:spacetime-schema
```
## 前端相关默认验证
禁止使用旧 `api-server:maincloud` 或任何 `GENARRATIVE_SPACETIME_MAINCLOUD_*` 口径。
前端修改后,应根据修改范围选择:
## 前端默认验证
前端修改后按范围选择:
- `npm run check:encoding`
- `npm run lint:eslint`
- `npm run typecheck`
- `npm run test`
- `npm run test -- <具体测试文件>`
- 页面交互 smoke
- 移动端视口检查
前端原则
UI 相关修改重点检查
- 移动端优先,再兼容网页端
- 页面只展示后端返回的状态,不自行计算结论型业务状态
- 创作中心入口配置事实源在 SpacetimeDB通过 `GET /api/creation-entry/config` 下发;前端只在 `platformEntryCreationTypes.ts` 做展示派生api-server 路由熔断也使用同一份配置,禁止恢复前端硬编码入口配置文件
- 优先复用现有面板、抽屉、弹窗,不新建独立大系统
- 不在 UI 中默认写功能说明类文本
- 弹出独立面板的交互不要实现成在当前面板下方追加内容。
- 390px 左右移动端宽度不横向溢出
- 输入法弹出时平台画布不被压缩
- 弹窗、抽屉和独立面板没有实现成当前面板下方展开
- UI 不包含默认规则说明长文
- 私有图片和音频不裸请求 `/generated-*`
## 文档更新规则
## 文档更新
- 工程修改要同步更新对应文档。
- 如果没有现成文档,新文档统一放入 `docs/` 下合适分类
- `.hermes/shared-memory/` 只记录高频、长期、团队共享的摘要和索引,不替代完整 PRD/技术文档
- 工程修改要同步更新当前 `docs/` 文档。
- 新增稳定知识优先合并进现有 4 份文档;只有现有文档无法容纳时才新增带 `【标签名】` 的 Markdown
- `.hermes/shared-memory/` 只记录高频、长期、团队共享的摘要和索引。
- 阶段性流水账、一次性计划和已关闭 TODO 不再作为长期仓库文档依据。
- 如果 `.hermes/shared-memory/` 与代码或 `docs/` 冲突,以代码和最新 `docs/` 为准,并同步修正共享记忆。
## 提交前建议让 Hermes 执行
```text
请检查当前 git diff指出
1. 是否违反 AGENTS.md 或 .hermes/shared-memory 约定;
2. 是否需要补充 docs
3. 是否有长期知识需要写入 .hermes/shared-memory
4. 建议的测试命令和提交信息。
```

View File

@@ -1,103 +1,51 @@
# 文档地图与阅读索引
> 用途:根据 `README.md`、`AGENTS.md` 和 `docs/` 下文档索引整理团队记忆入口,帮助本地 Hermes 快速选择应该读哪些资料。
更新时间:`2026-05-15`
## 全局入口
## 当前文档入口
| 场景 | 优先阅读 |
| --- | --- |
| 建立项目背景 | `README.md``AGENTS.md``.hermes/shared-memory/project-overview.md` |
| 找文档分类 | `docs/README.md` |
| 开发方法论 | `docs/experience/README.md` |
| 查风险与历史问题 | `docs/audits/README.md` |
| 做玩法/交互/系统设计 | `docs/design/README.md` |
| 做技术实现/后端/部署 | `docs/technical/README.md` |
| 排期与拆阶段 | `docs/planning/README.md` |
| 查脚本/Function/prompt/职责地图 | `docs/reference/README.md` |
| 查埋点 SQL | `docs/tracking/README.md` |
| 查运营/任务/钱包对账 SQL | `docs/operations/README.md` |
| 查 PRD | `docs/prd/` |
| 找当前文档 | `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/【开发运维】本地开发验证与生产运维-2026-05-15.md` |
| UI 像素资产与 9-slice 规范 | `UI_CODING_STANDARD.md` |
## docs 分类规则
- `experience/`:方法论、交接经验、长期有效的开发结论。
- `audits/`:现状扫描、问题定位、是否达标的审查类文档。
- `design/`:玩法机制、叙事关系、系统结构设计。
- `technical/`:技术选型、实现路线、竞品/产品形态拆解。
- `planning/`:阶段优先级与推进顺序。
- `reference/`:目录、速查、检索辅助。
- `tracking/`:埋点原始事实和聚合投影查询。
- `operations/`:后台运营核查、对账和排障查询。
- `prd/`:产品需求与阶段计划。
新增 Markdown 文档文件名必须以分类标签开头,格式为 `【标签名】中文标题-日期.md`。标签用于跨目录检索,不替代 `docs/` 的目录分类;历史文档不要求批量重命名。
## 推荐阅读顺序
## 阅读顺序
通用复杂任务:
1. `AGENTS.md`
2. `.hermes/shared-memory/`
3. `docs/README.md`
4. `docs/experience/README.md`
5. `docs/audits/README.md`
6. 任务对应分类下的 README 和具体文档
4. 与任务匹配的当前融合文档
后端 / 数据真相 / SpacetimeDB
1. `docs/technical/CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md`
2. `docs/technical/SERVER_RS_DDD_FULL_REFACTOR_2026-04-28.md`
3. `docs/technical/SERVER_RS_DDD_G1_CONTRACT_AND_ROUTE_MATRIX_2026-04-29.md`
4. `docs/technical/SERVER_RS_DDD_PARALLEL_TASKLIST_2026-04-29.md`
5. `docs/technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md`
6. `docs/technical/SPACETIMEDB_TABLE_CATALOG.md`
7. 具体模块方案文档
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/【项目基线】当前产品与工程约束-2026-05-15.md`
3. 相关前端组件、service、shared contract 和后端 module
生产部署 / 服务器 / Jenkins
1. `docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`
2. 需要迁移时再看 `SPACETIMEDB_JSON_STRING_MIGRATION_PROCEDURE_2026-04-27.md`
3. 历史 Jenkins / CORS / 本地远端脚本文档只作追溯,不作为当前入口
1. `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
2. `deploy/env/api-server.env.example`
3. `deploy/nginx/README.md`
RPG 创作与运行时链路:
## 维护规则
1. `docs/reference/RPG_CREATION_AND_RUNTIME_SCRIPT_RESPONSIBILITY_MAP_2026-04-28.md`
2. `docs/technical/CREATION_FLOW_CHAIN_REFACTOR_EXECUTION_PLAN_2026-04-21.md`
3. `docs/technical/RPG_ENTRY_RUNTIME_CHAIN_REFACTOR_EXECUTION_PLAN_2026-04-21.md`
4. 相关工作包 progress / closure 文档
移动端 UI / 游戏 UI
1. `docs/experience/MOBILE_UI_DEV_EXPERIENCE.md`
2. `UI_CODING_STANDARD.md`
3. 相关 `docs/design/` 文档
创作 Agent / 自定义世界:
1. `docs/design/CUSTOM_WORLD_CREATOR_INPUT_AND_AI_BOUNDARY_DESIGN_2026-04-06.md`
2. `docs/design/CUSTOM_WORLD_CREATOR_MANUAL_AI_SYSTEM_BALANCE_DESIGN_2026-04-12.md`
3. `docs/design/CUSTOM_WORLD_CREATOR_PURE_AGENT_COMPARISON_AND_CONVERSION_DESIGN_2026-04-12.md`
4. `docs/technical/UNIFIED_CREATION_AGENT_CHAT_FRAMEWORK_2026-04-22.md`
5. 相关 `SPACETIMEDB_CUSTOM_WORLD_*` 技术方案
拼图 / 大鱼 / Match3D
- 拼图:优先看 `PUZZLE_CREATION_AND_RUNTIME_MINIMAL_IMPLEMENTATION_2026-04-22.md``PUZZLE_RUNTIME_FRONTEND_LOGIC_REHOME_2026-05-02.md` 和相关 Puzzle 技术文档。
- 大鱼吃小鱼:优先看 `BIG_FISH_CREATION_AND_RUNTIME_MINIMAL_IMPLEMENTATION_2026-04-22.md` 和相关 Big Fish 技术/经验文档。
- 抓大鹅 Match3D优先看 `docs/prd/AI_NATIVE_MATCH3D_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-04-30.md``MATCH3D_CREATION_AND_RUNTIME_MINIMAL_IMPLEMENTATION_2026-04-30.md` 和相关 Match3D 技术文档。
个人任务 / 埋点 / 运营查询:
1. `docs/technical/PROFILE_TASK_AND_TRACKING_SYSTEM_2026-05-03.md`
2. `docs/technical/RUNTIME_PROFILE_TASK_SCOPE_2026-05-04.md`
3. `docs/technical/ANALYTICS_DATE_DIMENSION_IMPLEMENTATION_2026-05-04.md`
4. `docs/tracking/TRACKING_QUERY_PLAYBOOK_2026-05-03.md`
5. `docs/operations/PROFILE_TASK_QUERY_PLAYBOOK_2026-05-03.md`
## 文档维护规则
- 新增工程实现时,如果已有对应文档,必须同步更新。
- 如果没有对应文档,新文档放入 `docs/` 下合适分类。
- 新文档文件名必须使用 `【标签名】` 前缀,标题尽量保留中文语义,日期使用 `YYYY-MM-DD`
- `.hermes/shared-memory/` 只保留跨任务、跨成员、高频使用的摘要和索引。
- 当前 `docs/` 只保留少量融合文档。
- 新增工程实现时,如果已有对应当前文档,必须同步更新。
- 如果没有合适位置,新文档文件名必须使用 `【标签名】中文标题-YYYY-MM-DD.md`
- 阶段性流水账、一次性修复记录和已关闭实验不要再新增为长期文档
- 阶段性计划和一次性 TODO 不再作为长期文档目录;需要保留的决策、流程和坑点应进入 `docs/` 当前文档或 `.hermes/shared-memory/`
- 如果文档与代码冲突,先确认代码事实,再更新过期文档和共享记忆。

View File

@@ -22,12 +22,12 @@
- 验证:拼图入口测试仍可通过,且新组件可通过不同页面复用而不需要复制上传卡实现。
- 关联:`src/components/common/CreativeImageInputPanel.tsx``src/components/puzzle-agent/PuzzleAgentWorkspace.tsx`
## 汪汪声浪入口不要再回到独立配置阶段
## 汪汪声浪重新开放时不要再回到独立配置阶段
- 现象:汪汪声浪入口如果继续切换到独立配置阶段,会和拼图、抓大鹅的创作页内嵌结构不一致,用户会感觉入口跳页。
- 原因:旧实现把 `bark-battle` 单独挂到 `bark-battle-config` selectionStage而不是复用创作 Tab 里的模板区。
- 处理:入口点击只设置 `activeCreationFormType = 'bark-battle'` 并回到创作 Tab`BarkBattleConfigEditor` 作为内嵌表单使用默认隐藏返回按钮和页面标题runtime `onExit` 重新回到创作 Tab 的汪汪声浪模板。
- 验证:点击汪汪声浪后直接看到创作页内嵌表单,不再出现独立配置页;测试应覆盖内嵌表单与 runtime 返回路径。
- 处理:当前 `bark-battle` 入口为 `visible=true``open=false`展示为“敬请期待”api-server 会熔断 `/api/creation/bark-battle/*``/api/runtime/bark-battle/*`。后续重新开放时,入口点击只设置 `activeCreationFormType = 'bark-battle'` 并回到创作 Tab`BarkBattleConfigEditor` 作为内嵌表单使用默认隐藏返回按钮和页面标题runtime `onExit` 重新回到创作 Tab 的汪汪声浪模板。
- 验证:当前点击汪汪声浪不进入创作表单,直连创作 / 运行态 API 返回 `creation_entry_disabled`;重新开放时再覆盖内嵌表单与 runtime 返回路径。
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx``src/components/bark-battle-creation/BarkBattleConfigEditor.tsx``src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`
## 抓大鹅批量重新生成物品不要新增 itemId
@@ -36,7 +36,7 @@
- 原因:重新生成和批量新增共用 `item-assets` 接口,如果前端不传 `mode = "replace"`,或后端替换时重新分配 `itemId` / 追加未匹配名称,就会破坏 `generatedItemAssets` 顺序和运行态类型映射。
- 处理:批量重新生成只提交当前素材列表中能匹配到的名称,并传 `mode = "replace"`;后端只对同名已有素材生成新图片,合并时保留原 `itemId``itemName`、模型兼容字段、UI 背景和历史音频字段,未匹配名称直接忽略且不计费。
- 验证:`npm run test -- src\components\match3d-result\Match3DResultView.test.tsx` 覆盖前端提交口径,`cargo test -p api-server match3d_item_asset --manifest-path server-rs\Cargo.toml``cargo test -p api-server match3d_regenerated_asset --manifest-path server-rs\Cargo.toml` 覆盖后端替换计划与身份保留。
- 关联:`src/components/match3d-result/Match3DResultView.tsx``server-rs/crates/api-server/src/match3d.rs``packages/shared/src/contracts/match3dWorks.ts``server-rs/crates/shared-contracts/src/match3d_works.rs``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 抓大鹅生成封面图不要覆盖物品素材或配置
@@ -44,7 +44,7 @@
- 原因:封面生成属于定向图片槽位更新;若后端复用草稿编译写回,可能按 session config 重算作品行。即使后端已修正,前端若直接把封面接口返回的整份 `item` 当成最新 profile也可能用旧回包里的空 `generatedItemAssets` 覆盖当前页面素材。
- 处理:`POST /api/creation/match3d/works/{profileId}/cover-image` 只保存 `coverImageSrc` / `coverAssetId` 等封面字段,保留当前 `generated_item_assets_json`、难度、消除次数、题材和描述;前端收到回包后只合并 `coverImageSrc`,继续保留当前可见 `generatedItemAssets``clearCount``difficulty`
- 验证:`npm run test -- src\components\match3d-result\Match3DResultView.test.tsx` 覆盖旧回包不覆盖物品素材和配置;`cargo test -p api-server match3d_cover --manifest-path server-rs\Cargo.toml` 覆盖封面提示词与参考图链路。
- 关联:`src/components/match3d-result/Match3DResultView.tsx``server-rs/crates/api-server/src/match3d.rs``server-rs/crates/spacetime-module/src/match3d/mod.rs``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## OSS V4 签名时间和 bucket/object_key 兼容
@@ -60,7 +60,7 @@
- 原因:生成音乐转存到 OSS 私有对象后,`audioSrc` 是 generated legacy path浏览器 `<audio>` 不能像公开静态资源一样直接请求裸路径。另一个常见误判是浏览器拒绝自动播放,资源已经进入运行态但开局第一次 `audio.play()` 被拦截。
- 处理:结果页试听控件和运行态隐藏 `<audio>` 设置 `src` 前,都先通过 `useResolvedAssetReadUrl``resolveAssetReadUrl` 换签;签名未就绪时不要回退请求裸 generated 路径。运行态自动播放失败只静默兜底,但玩家首次按下拼图块或点击抓大鹅物品时要重试同一个背景音乐播放函数。拼图读取 `currentLevel.backgroundMusic.audioSrc`,抓大鹅读取 `generatedItemAssets[].backgroundMusic.audioSrc`
- 验证:结果页试听和运行态 `<audio loop>``src` 为签名 URL 或公开 URL拼图/抓大鹅运行态首次局内交互后会再次尝试播放背景音乐;`npm run typecheck` 不报契约字段缺失,后端 run response 带 `backgroundMusic`
- 关联:`src/components/puzzle-runtime/PuzzleRuntimeShell.tsx``src/components/match3d-runtime/Match3DRuntimeShell.tsx``docs/technical/PUZZLE_MATCH3D_RESULT_AUDIO_TAB_2026-05-11.md`
- 关联:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 抓大鹅背景音乐是作品级字段但暂存在首个物品素材
@@ -68,7 +68,7 @@
- 原因:当前表结构没有作品级音频字段,背景音乐暂存在 `generatedItemAssets[]`。如果 action response 的 draft assets 缺音乐,前端又优先用它覆盖 work detail或音乐落在非首个素材而结果页只读 `assetDrafts[0].backgroundMusic`,就会丢掉已生成音乐。
- 处理:前端统一使用 `normalizeMatch3DGeneratedItemAssetsForRuntime` / `mergeMatch3DGeneratedItemAssetsForRuntime`:把任意素材上的 `backgroundMusic` 与音乐元信息迁移到首个素材清空其它素材上的作品级音乐字段action draft assets 与 work detail assets 按 `itemId` 合并保留详情里的音乐、UI 背景和点击音效。
- 验证:`npm run test -- src\services\match3dGeneratedModelCache.test.ts src\components\match3d-result\Match3DResultView.test.tsx src\components\match3d-runtime\Match3DRuntimeShell.test.tsx`;平台推荐流定向跑 `RpgEntryFlowShell.agent.interaction.test.tsx` 中的 Match3D runtime assets 用例;`npm run typecheck`
- 关联:`src/services/match3dGeneratedModelCache.ts``src/components/match3d-result/Match3DResultView.tsx``src/components/platform-entry/PlatformEntryFlowShellImpl.tsx``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 中文乱码与编码风险
@@ -89,7 +89,7 @@
- 原因:重置/修改密码会更新 `password_hash``password_login_enabled``token_version`,如果 API 层只更新本地 `InMemoryAuthStore`,没有调用 `sync_auth_store_snapshot_to_spacetime()``api-server` 重启时可能从旧的 SpacetimeDB 表或旧快照恢复账号状态。
- 处理:`POST /api/auth/password/change``POST /api/auth/password/reset` 成功后必须同步认证快照;启动恢复时从 SpacetimeDB 表、SpacetimeDB 快照记录和本地 `GENARRATIVE_AUTH_STORE_PATH` 文件中选择可判断的最新快照,本地文件更新时尝试回写 SpacetimeDB。
- 验证:执行 `cargo test -p module-auth password --manifest-path server-rs/Cargo.toml``cargo test -p api-server password --manifest-path server-rs/Cargo.toml`;手测时重设密码后旧密码应失败,新密码应成功,重启后仍应保持。
- 关联:`server-rs/crates/api-server/src/password_management.rs``server-rs/crates/api-server/src/state.rs``docs/technical/PASSWORD_LOGIN_CHANGE_RESET_DESIGN_2026-04-24.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## 抓大鹅生成页只显示服务暂不可用先查 reason 和外部服务配置
@@ -97,7 +97,7 @@
- 原因:配置缺失类错误通常在后端 `error.details.reason` 中给出具体缺项,前端如果只读 `details.message` 会吞掉原因;本地只配置 `ALIYUN_OSS_BUCKET` / `ALIYUN_OSS_ENDPOINT` 时,旧逻辑还会在启动期构造空 AccessKey 的 OSS 客户端并失败。抓大鹅新链路仍是 2D 生图切割,不需要也不应回退 Rodin/GLB。
- 处理:前端 API 错误展示优先读取 `details.reason`,再读取 `details.message`,避免底层 `error sending request` 覆盖真正可操作的配置或网络原因;`api-server` 只有在 OSS 四件套齐全时初始化 OSS 客户端,部分缺失只记 warning 并让具体 generated 上传/换签接口返回 `OSS 未完成环境变量配置`。抓大鹅素材、封面和背景生成在调用 VectorEngine 前先预检 OSS并通过 `details.missingEnv` 列出缺项;真实生成需补齐 `VECTOR_ENGINE_BASE_URL``VECTOR_ENGINE_API_KEY` 和完整 `ALIYUN_OSS_*` 四件套。抓大鹅 `5*5` 素材图提示词还必须要求相邻物体主体至少保留 `1/4` 单格宽度空白间距,避免切割后相邻格内容污染。
- 验证:`npm run test -- src/services/apiClient.test.ts` 覆盖 `details.reason``cargo test -p api-server state --manifest-path server-rs/Cargo.toml` 覆盖半配置 OSS 不阻断启动;`npm run api-server` 后按实际 `GENARRATIVE_API_PORT` 请求 `/healthz`,不要默认打 `3100`
- 关联:`packages/shared/src/http.ts``server-rs/crates/api-server/src/state.rs``docs/technical/API_SERVER_EXTERNAL_SERVICE_ENV_CONFIG_2026-05-07.md``docs/technical/AUTH_SNAPSHOT_AND_MATCH3D_LOCAL_DEV_FIX_2026-05-01.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
2026-05-14 补充:抓大鹅“物品素材 sheet”已改用 VectorEngine Gemini `gemini-3-pro-image-preview` 原生 `generateContent`,真实生成读取 `VECTOR_ENGINE_BASE_URL``VECTOR_ENGINE_API_KEY``VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`;封面和 `9:16` 背景图走 VectorEngine `/v1/images/generations``1:1` 容器 UI 走 VectorEngine `/v1/images/edits` multipart 参考图链路。排查素材 sheet 时看请求路径是否为 `/v1beta/models/gemini-3-pro-image-preview:generateContent?key=...`,响应图片在 `candidates[].content.parts[].inlineData.data` / `inline_data.data`,不要再按 APIMart `/images/generations``/tasks/{task_id}` 排查。
@@ -107,7 +107,7 @@
- 原因:发布按钮被 `publishReady` 直接禁用,导致未满足门槛时无法进入发布检查面板;封面编辑仍挂在作品信息 Tab不能和发布检查一起收口。
- 处理:发布按钮只受忙碌态控制,点击后始终打开独立发布面板;发布面板内先展示阻断项,再承载封面图上传 / AI 重绘 / 参考图编辑,满足条件后再点击 `发布到广场`
- 验证:`npm run test -- src/components/match3d-result/Match3DResultView.test.tsx``npm run typecheck`
- 关联:`src/components/match3d-result/Match3DResultView.tsx``src/components/match3d-result/Match3DResultView.test.tsx``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## `.hermes` 只放共享内容,不放个人 Hermes 配置
@@ -123,7 +123,7 @@
- 原因:浏览器摄像头视频流只是舞台背景;如果热身关把 `getUserMedia` 状态当成主动作数据源,或只在 gesture 阶段消费 `useMocapInput`,就会错过 mocap 的身体中心、动作名和手部坐标。
- 处理:确认 `src/components/child-motion-demo/ChildMotionWarmupDemo.tsx` 全热身流程启用 `useMocapInput`,页面主提示展示 mocap 动作数据源状态而不是浏览器摄像头状态;确认 `src/services/useMocapInput.ts` 能解析 `/stream` 包里的 `general.body.center_norm``actions/action/gesture/gestures/event/name/type``hands[]``leftHand/rightHand``left_hand/right_hand`、左右手标记和 `open_palm/grab` 状态。`/stream` 是 WebSocket普通 HTTP 访问返回 404 不能当成服务不可用。
- 验证:运行 `npx vitest run src\services\useMocapInput.test.ts src\components\child-motion-demo\ChildMotionWarmupDemo.test.tsx`,并在本地硬件服务启动后进入 `/child-motion-demo` 实测站位、招手、左右手挥动和跳跃阶段。
- 关联:`src/services/useMocapInput.ts``src/components/child-motion-demo/ChildMotionWarmupDemo.tsx``docs/technical/CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 儿童动作 Demo 左右手阶段误通过先查身体侧映射和手臂展开阈值
@@ -131,7 +131,7 @@
- 原因:本地 mocap 的 handedness 当前按摄像头视角输出,不能直接当作用户身体左/右;同时左右手阶段的目标是确认现实空间安全,需要验证手臂向外打开和上下摆动角度,不能只看手部 `x` 轨迹范围。
- 处理:热身关中用户左手应消费 camera-right用户右手应消费 camera-left左右手阶段只在同侧肩肘腕外展、手腕非自然下垂、连续有效帧、横向范围、上下摆动范围、肩腕角度范围和上下方向变化全部达标时完成并记录轨迹空间包络、角度范围和最大外展距离。
- 验证:运行 `npx vitest run src\components\child-motion-demo\ChildMotionWarmupDemo.test.tsx src\components\child-motion-demo\childMotionWarmupModel.test.ts`,确认相反侧手、自然下垂、单纯横向轨迹不会完成,真实展开上下摆动可以完成。
- 关联:`src/components/child-motion-demo/ChildMotionWarmupDemo.tsx``src/components/child-motion-demo/childMotionWarmupModel.ts``docs/technical/CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md`
- 关联:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 儿童动作 Demo 角色轮廓抽搐先查 mocap 坐标防抖和渲染分层
@@ -139,7 +139,7 @@
- 原因:`general.body.center_norm.x` 原始值逐包直接写入 `avatarX` 时,硬件坐标小噪声会直接驱动位置保持判定和 CSS 动画;如果角色外层同时承担横向定位和跳跃 `transform`,半透明 PNG 在移动时也更容易出现重采样抖动观感。
- 处理mocap 身体中心进入角色位置前必须先 clamp再经过小幅死区、低通阻尼和单包最大步长限制键盘 A/D 调试输入仍保持即时。角色 DOM 外层只负责横向定位,内层 sprite 负责轮廓图和跳跃位移,避免同一层 `transform` 同时表达多种运动。
- 验证:运行 `npx vitest run src\components\child-motion-demo\ChildMotionWarmupDemo.test.tsx src\components\child-motion-demo\childMotionWarmupModel.test.ts src\services\useMocapInput.test.ts src\services\child-motion-demo\childMotionDebugInput.test.ts`,并用真实硬件进入站位阶段观察小幅身体晃动不会导致角色频繁左右跳动。
- 关联:`src/components/child-motion-demo/ChildMotionWarmupDemo.tsx``src/index.css``docs/technical/CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md`
- 关联:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 宝贝识物选篮误触发先查多套判定和残余轨迹
@@ -148,7 +148,7 @@
- 处理:宝贝识物选篮只使用明确 `leftHand` / `rightHand` 的连续横向轨迹阈值;侧别为 `unknown` 的手部轨迹不参与选篮;反馈阶段清空轨迹,不在非 `active` 阶段累计路径。进入关卡和每次正确反馈结束后自动弹出物品,不再用 `open_palm -> grab` 抓握序列激活礼物盒。
- 补充:当前本地 mocap 的 handedness 是摄像头视角,宝贝识物选篮前需要换算为用户身体视角;`rightHand` 轨迹代表玩家左手并进入左篮,`leftHand` 轨迹代表玩家右手并进入右篮。键鼠调试不走该换算,仍保持鼠标左键=左篮、右键=右篮。
- 验证:运行 `npm run test -- src/components/edutainment-runtime/BabyObjectMatchRuntimeShell.test.tsx src/services/useMocapInput.test.ts`,确认动作名负向测试、未知侧别负向测试和左右手横向轨迹测试通过。
- 关联:`src/components/edutainment-runtime/BabyObjectMatchRuntimeShell.tsx``docs/technical/BABY_OBJECT_MATCH_CREATION_PUBLISH_IMPLEMENTATION_2026-05-11.md`
- 关联:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 宝贝爱画左右手反了先查 mocap 摄像头视角换算
@@ -156,7 +156,7 @@
- 原因:本地 mocap 的 handedness 当前按摄像头视角输出,不能直接当成用户身体左 / 右;宝贝爱画初版直接消费 `latestCommand.leftHand/rightHand`,漏做摄像头视角到用户身体视角的换算。
- 处理:宝贝爱画运行态消费 mocap 前先换算:`rightHand` 作为用户左手,用于颜色悬停和左手指示器;`leftHand` 作为用户右手,用于画笔 / 橡皮光标、绘制、擦除和工具切换。键鼠调试输入不做该换算,继续保持鼠标左键为左手、右键为右手。
- 验证:运行 `npm run test -- src/components/edutainment-runtime/BabyLoveDrawingRuntimeShell.test.tsx src/components/edutainment-runtime/babyLoveDrawingModel.test.ts`,确认 camera-left 驱动用户右手画笔、camera-right 渲染用户左手选色指示器。
- 关联:`src/components/edutainment-runtime/BabyLoveDrawingRuntimeShell.tsx``docs/technical/BABY_LOVE_DRAWING_RUNTIME_DEMO_IMPLEMENTATION_2026-05-13.md`
- 关联:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 宝贝识物创作卡在准备结果页先查长耗时 image-2 请求
@@ -164,15 +164,15 @@
- 原因:宝贝识物一次创作会生成 2 张物品图和 `background``ui-frame``gift-box``basket``smoke-puff` 5 张视觉包装图。旧前端只等待 180 秒并对长耗时 POST 自动重试,容易在 VectorEngine 仍在生成时先 abort再重复发起第二次生成上游某张图超过后端 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS` 或返回 5xx 时会表现为 502。
- 处理:`babyObjectMatchClient``/api/creation/edutainment/baby-object-match/assets` 使用 10 分钟超时并取消自动重试;后端并发启动物品图和视觉主题包生成,并把该路由的 VectorEngine 单图请求等待预算提升到至少 8 分钟,按资源类别输出开始、完成和耗时日志。
- 验证:运行 `npm run test -- src/services/edutainment-baby-object/babyObjectMatchClient.test.ts src/services/miniGameDraftGenerationProgress.test.ts``cargo test -p api-server edutainment_baby_object --manifest-path server-rs/Cargo.toml` 和编码检查;真实联调时查看 `宝贝识物 image-2 资源生成完成` 耗时是否小于前端超时,若仍 502 再看 `VectorEngine 图片生成上游错误``upstreamStatus/raw_excerpt`
- 关联:`src/services/edutainment-baby-object/babyObjectMatchClient.ts``src/services/miniGameDraftGenerationProgress.ts``server-rs/crates/api-server/src/edutainment_baby_object.rs``docs/technical/BABY_OBJECT_MATCH_CREATION_PUBLISH_IMPLEMENTATION_2026-05-11.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 寓教于乐作品和宝贝识物模板同时消失先查入口种子
- 现象:发现页“寓教于乐”分类下已发布的宝贝识物作品突然消失,同时创作界面模板选项中也看不到或无法正常展示 `宝贝识物`
- 原因:创作入口配置事实源已迁到 SpacetimeDB `creation_entry_type_config`;前端用 `baby-object-match` 入口可见性同时控制创作模板展示和发现页宝贝识物公开作品合入。若默认种子或后台配置缺少 `baby-object-match` 行,两条链路会一起被判定为不可见。
- 处理:确认 `server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs` 默认种子包含 `id=baby-object-match``title=宝贝识物``visible=true``open=true``sort_order=90`api-server 测试降级配置也要同步包含该类型。入口图片路径需指向真实存在资源,避免卡片图片 404。
- 验证:运行 `cargo test -p module-runtime default_creation_entry_types_include_baby_object_match --manifest-path server-rs/Cargo.toml``cargo test -p api-server test_creation_entry_config_response_keeps_baby_object_match_visible --manifest-path server-rs/Cargo.toml``cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml``npm run test -- src/components/platform-entry/platformEntryCreationTypes.test.ts`
- 关联:`server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs``server-rs/crates/api-server/src/creation_entry_config.rs``docs/technical/NEW_WORK_ENTRY_CONFIG_2026-05-01.md`
- 处理:确认 `server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs` 默认种子包含 `id=baby-object-match``title=宝贝识物``visible=true``open=false``badge=敬请期待``sort_order=90`api-server 测试降级配置也要同步包含该类型。入口图片路径需指向真实存在资源,避免卡片图片 404。若只是“敬请期待”,不要把 `visible` 关掉,否则发现页寓教于乐公开作品可见性也会受影响。
- 验证:运行 `cargo test -p module-runtime default_creation_entry_types_include_baby_object_match --manifest-path server-rs/Cargo.toml``cargo test -p api-server creation_entry_config --manifest-path server-rs/Cargo.toml``cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml``npm run test -- src/components/platform-entry/platformEntryCreationTypes.test.ts`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 儿童动作 Demo 绘本风资源未生成先查 VectorEngine 配置
@@ -180,7 +180,7 @@
- 原因:儿童动作 Demo 的真实背景、地面、UI、地面指示环和角色轮廓资源都使用 VectorEngine `gpt-image-2-all` 生成,脚本只读取 `VECTOR_ENGINE_BASE_URL``VECTOR_ENGINE_API_KEY` 和可选 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`;仓库内不能提交真实 key缺配置时页面只能使用 CSS 草地绘本兜底。
- 处理:在本地私密环境补齐 `VECTOR_ENGINE_BASE_URL=https://api.vectorengine.ai``VECTOR_ENGINE_API_KEY`,不要把 key 写入 Git先运行 `npm run assets:child-motion-demo -- --dry-run` 核对 prompt再运行 `npm run assets:child-motion-demo -- --live``npm run assets:child-motion-demo -- --live --only ui-panel` 等小批量命令生成资源。透明资源的品红底源图写入 `tmp/child-motion-demo-assets/`,不要把源图或预览图放入 `public/child-motion-demo/` 作为正式资产。
- 验证:生成后确认 `public/child-motion-demo/` 只保留页面引用的最终 PNG重新打开 `/child-motion-demo` 可看到真实绘本草地背景、地面、圆环、角色轮廓和 UI 资源;`npm run check:encoding` 仍通过。
- 关联:`scripts/generate-child-motion-demo-assets.mjs``src/index.css``docs/technical/CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 儿童动作 Demo 绘本资源变形先查用途拆分和透明后处理
@@ -188,7 +188,7 @@
- 原因:早期资源中 `picture-book-ui-panel.png` 是接近方形画布,`picture-book-grass-floor.png` 也含大量透明边界;若 CSS 用 `background-size: 100% 100%` 把同一资源强行铺成 HUD、状态条、开始面板或底部地板就会出现变形和层叠观感。
- 处理:使用 v2 用途专属资源:`picture-book-foreground-grass-v2.png``picture-book-ground-ring-v2.png``picture-book-character-outline-v2.png``picture-book-hud-strip-v2.png``picture-book-calibration-strip-v2.png``picture-book-start-panel-v2.png``picture-book-ui-button-v2.png`CSS 按资源比例等比缩放底部草坪只覆盖下沿HUD / 状态条 / 开始托盘分别引用各自资源。若只需修透明裁切或品红边,运行 `npm run assets:child-motion-demo -- --live --postprocess-only --force --only <asset-id>`,不重新请求 image-2。
- 验证:用横屏截图检查没有新旧资源叠加、没有方形面板拉成长条、角色和地面指示环不被前景草坪埋住;同时运行 `npm run check:encoding`
- 关联:`scripts/generate-child-motion-demo-assets.mjs``src/index.css``public/child-motion-demo/``docs/technical/CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## GPT-image-2 不再读 APIMart 图片配置
@@ -196,7 +196,7 @@
- 原因2026-05-09 后 GPT-image-2 图片生成已切到 VectorEngine `gpt-image-2-all`APIMart 只保留给创意 Agent 的 `gpt-5` Responses 文本/多模态链路。
- 处理:为图片生成配置 `VECTOR_ENGINE_BASE_URL=https://api.vectorengine.ai``VECTOR_ENGINE_API_KEY``VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`;排查请求体时确认路径为 `/v1/images/generations`、模型为 `gpt-image-2-all`、参考图字段为 `image`
- 验证:运行 `cargo test -p api-server openai_image --manifest-path server-rs/Cargo.toml` 和相关玩法图片生成测试;真实联调只在本地私密环境放置 VectorEngine key。
- 关联:`docs/technical/VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md``server-rs/crates/api-server/src/openai_image_generation.rs`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 拼图参考图没有影响生成时先查 action payload 和阶段日志
@@ -220,7 +220,7 @@
- 原因:`gpt-image-2-all``/v1/images/generations` 更适合纯文生图;有参考图且需要重绘时应切到 `/v1/images/edits` 的 multipart 图生图接口。
- 处理:`referenceImageSrc` 存在且 `aiRedraw = true` 时直接走 editsprompt 仍保留参考图强约束;入口页关闭 AI 重绘时直接应用上传图,不调用图片生成;前端把参考图压到单边 1024 内,后端解析后拒绝超过 8MB 的参考图字节。
- 验证:后端单测应覆盖 `images/edits` 路由、`b64_json` 响应解码和参考图强提示;真实联调先看日志里是否命中 `拼图 VectorEngine 图片编辑 HTTP 返回`
- 关联:`server-rs/crates/api-server/src/puzzle.rs``src/services/puzzleReferenceImage.ts``docs/technical/VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 拼图 edits 报 error sending request 先看网络分类
@@ -228,7 +228,7 @@
- 原因:这是 `reqwest``send()` 阶段失败,尚未收到 VectorEngine HTTP 响应;常见原因是服务器网络 / DNS / 防火墙 / 代理问题,或上游网关中断 multipart 连接。
- 处理:查看错误响应 `details.reason/source/connect/body/timeout/endpoint``拼图 VectorEngine 请求发送失败` 日志。拼图图片客户端已强制 HTTP/1.1,降低 multipart HTTP/2 兼容风险;若 `connect=true` 先查网络出口,若 `body=true` 先查参考图大小和 multipart 发送。
- 验证:`curl --http1.1 -i -X POST https://api.vectorengine.ai/v1/images/edits -H "Authorization: Bearer invalid" -F "model=gpt-image-2" -F "prompt=test" -F "n=1" -F "size=1024x1024" -F "image=@public/match3d-background-references/pot-fused-reference.png;type=image/png"` 至少应返回 HTTP `401`说明域名、TLS、路径和 multipart 上传可达;执行 `cargo test -p api-server puzzle_vector_engine --manifest-path server-rs/Cargo.toml`
- 关联:`server-rs/crates/api-server/src/puzzle.rs``docs/technical/VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md``docs/technical/API_SERVER_EXTERNAL_SERVICE_ENV_CONFIG_2026-05-07.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 拼图 UI 背景缺失先区分生成失败和消费链路丢字段
@@ -236,7 +236,7 @@
- 原因:`compile_puzzle_draft` 设计上会在首图后生成 UI 背景,且缺 `uiBackgroundImageSrc/uiBackgroundImageObjectKey` 会让自动草稿失败;若草稿已成功,通常不是“没生成”,而是前端消费链路漏了 `levels[].uiBackgroundImageObjectKey` 回退,或本地 `startLocalPuzzleRun(...)` 只把 `coverImageSrc` 带入 `currentLevel`
- 处理:结果页预览、运行态和本地运行态统一用 `resolvePuzzleUiBackgroundSource`,优先 `uiBackgroundImageSrc`,为空时把 `uiBackgroundImageObjectKey` 规范成 `/generated-...` 路径并交给 `/api/assets/read-url` 换签;`startLocalPuzzleRun` 与本地下一关 handoff 都要从 `PuzzleWorkSummary.levels[]` 复制 `uiBackgroundImageSrc/uiBackgroundImageObjectKey/backgroundMusic``currentLevel`。结果页 `UI背景提示词` 输入框不得把本地兜底 prompt 直接显示成已保存提示词,避免误判为后端已生成。
- 验证:`npm run test -- src/components/puzzle-result/PuzzleResultView.test.tsx src/services/puzzle-runtime/puzzleLocalRuntime.test.ts src/components/puzzle-runtime/PuzzleRuntimeShell.test.tsx`,以及 `npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "puzzle draft generation auto starts trial"`;后端用 `cargo test -p api-server puzzle_ui_background --manifest-path server-rs\Cargo.toml` 确认生成 / 序列化链路。
- 关联:`src/services/puzzle-runtime/puzzleUiBackgroundSource.ts``src/services/puzzle-runtime/puzzleLocalRuntime.ts``src/components/puzzle-runtime/PuzzleRuntimeShell.tsx``src/components/puzzle-result/PuzzleResultView.tsx``docs/technical/PUZZLE_FORM_CREATION_FLOW_2026-04-29.md`
- 关联:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 拼图草稿生成后音乐/UI 又变空先查结果页回包合并
@@ -244,7 +244,7 @@
- 原因:结果页若已有本地 `generationStatus = generating` 编辑态,后端生成完成回包会走 `mergeDraftEditStateWithIncomingState(...)` 合并。该合并必须把生成候选图、正式图、`uiBackground*``backgroundMusic` 作为同一批生成资产处理;漏掉 `backgroundMusic` 时,随后自动保存会把空音乐写回 `levels_json`
- 处理:`PuzzleResultView` 合并生成完成回包时同步保留 `backgroundMusic`,并用回归测试覆盖 UI 预览、音乐试听和试玩 payload 都读取最新 `levels[]` 资产。
- 验证:`npm run test -- src/components/puzzle-result/PuzzleResultView.test.tsx`,以及自动试玩入口测试 `npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "puzzle draft generation auto starts trial"`
- 关联:`src/components/puzzle-result/PuzzleResultView.tsx``src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx``docs/technical/PUZZLE_MATCH3D_RESULT_AUDIO_TAB_2026-05-11.md`
- 关联:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## 自动草稿成功但缺音乐或 UI 先查后端吞错
@@ -252,7 +252,7 @@
- 原因:自动草稿阶段如果把 VectorEngine / Suno / OSS / 资产绑定错误记录为 warning 后继续返回成功,前端只能拿到缺关键资产的成功 draft随后保存和试玩都会消费这份空资产状态。
- 处理:自动草稿必须把必需生成资产当作后端完成条件:拼图首关需同时具备 `levels[0].backgroundMusic.audioSrc``levels[0].uiBackgroundImageSrc/uiBackgroundImageObjectKey`;抓大鹅需在 `generatedItemAssets[]` 中具备非空 `backgroundMusic.audioSrc`。缺失或上游失败时返回错误并停留在生成页,结果页手动重新生成只作为已有草稿补救入口。
- 验证:`cargo test -p api-server puzzle_initial_draft_assets_must_include_music_and_ui_background match3d_background_music_ready_requires_audio_src match3d_background_music_title_is_required_for_auto_draft --manifest-path server-rs\Cargo.toml`,并重启 `npm run api-server` 后检查 `/healthz`
- 关联:`server-rs/crates/api-server/src/puzzle.rs``server-rs/crates/api-server/src/match3d.rs``docs/technical/PUZZLE_FORM_CREATION_FLOW_2026-04-29.md``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 拼图草稿生成 180 秒后 502/504 先查 VectorEngine 超时与前端重试
@@ -260,7 +260,7 @@
- 原因:首图生成走 VectorEngine `gpt-image-2-all`,默认 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS=1000000`;若上游在该窗口内未返回,后端退款并返回超时错误。旧前端 action 写请求会对 502/503/504 自动重试一次,导致同一次点击重复触发生图与扣退费。
- 处理:拼图/创作 Agent 的 `executeAction` 默认不做前端自动重试;后端将 VectorEngine / 图片请求超时映射为 `504 Gateway Timeout``error.details.provider=vector-engine``timeout=true`。真实排障按日志同一 `session_id``拼图 VectorEngine 图片生成 HTTP 返回` 是否缺失,以及钱包流水扣费到退款的时间差是否接近 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`
- 验证:运行 `npm run test -- src/services/creation-agent/creationAgentClientFactory.test.ts src/services/apiClient.test.ts``cargo test -p api-server puzzle_vector_engine --manifest-path server-rs/Cargo.toml`,真实联调重启 `npm run api-server` 后检查 `/healthz`
- 关联:`src/services/creation-agent/creationAgentClientFactory.ts``server-rs/crates/api-server/src/puzzle.rs``docs/technical/API_SERVER_EXTERNAL_SERVICE_ENV_CONFIG_2026-05-07.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 本地脚本调 VectorEngine 生图卡住先区分 fetch 首部超时
@@ -274,17 +274,17 @@
- 现象:开发时参考到 Express、Node、PostgreSQL 或 Go 方向旧文档,导致接口、数据真相或部署路径与当前主线不一致。
- 原因:项目历史文档较多,部分旧方案仍保留作迁移参考。
- 处理涉及服务端、数据真相、SpacetimeDB、运行时状态时先看 `CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md`,再看 DDD 总纲和具体技术方案
- 处理涉及服务端、数据真相、SpacetimeDB、运行时状态时先看当前后端架构文档,再看相关 crate README 和源码
- 验证:代码改动应落在 `server-rs + Axum + SpacetimeDB` 主线;旧路线只作为迁移参考,不作为兼容目标。
- 关联:`docs/technical/CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md``AGENTS.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
## SpacetimeDB 表结构变更不能按 PostgreSQL 迁移直觉处理
- 现象:发布时 schema 冲突、自动迁移拒绝、旧客户端调用 reducer 失败、private 表数据迁移遗漏。
- 原因SpacetimeDB 对字段删除、类型变化、索引/主键/RLS/reducer 变化有不同自动迁移边界。
- 处理:变更前阅读 `SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md`;已有表新增字段必须放在 Rust 表结构体最后并设置明确默认值;需要修改字段名时,先询问用户并确认迁移计划;涉及表变化时同步 `migration.rs``SPACETIMEDB_TABLE_CATALOG.md` 和 bindings必要时走 JSON 导入导出与分片导入迁移流程。
- 处理:变更前阅读 `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`;已有表新增字段必须放在 Rust 表结构体最后并设置明确默认值;需要修改字段名时,先询问用户并确认迁移计划;涉及表变化时同步 `migration.rs``docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md` 和 bindings必要时走 JSON 导入导出与分片导入迁移流程。
- 验证:发布前运行 `npm run check:spacetime-schema`,完成 schema 检查、bindings 生成、表目录更新和相关 smoke。
- 关联:`docs/technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md``docs/technical/SPACETIMEDB_TABLE_CATALOG.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
## SpacetimeDB publish 报 wasm-bindgen 时先查 shared-contracts feature
@@ -292,7 +292,7 @@
- 原因SpacetimeDB module 的 wasm32 构建树被间接带入原生/网页依赖;已验证链路是 `reqwest -> platform-oss -> shared-contracts -> module-runtime -> spacetime-module`,由共享契约默认启用资产 OSS 契约触发。
- 处理:让 `shared-contracts` 的 OSS 资产契约走 `oss-contracts` featureworkspace 根依赖保持 `default-features = false``api-server` 这类原生后端需要资产 DTO 时在自身 `Cargo.toml` 显式启用 `features = ["oss-contracts"]`
- 验证:执行 `cargo tree -i wasm-bindgen --manifest-path server-rs\crates\spacetime-module\Cargo.toml --target wasm32-unknown-unknown` 应显示 nothing to print再执行 `cargo check -p spacetime-module --manifest-path server-rs\Cargo.toml --target wasm32-unknown-unknown`
- 关联:`server-rs/crates/shared-contracts/Cargo.toml``server-rs/crates/api-server/Cargo.toml``docs/technical/RUST_WORKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 本地 SpacetimeDB replica identity 不匹配
@@ -300,7 +300,7 @@
- 原因:本地 SpacetimeDB 数据目录中的 replica 数据残留与当前数据库身份不一致。
- 处理:按本地 replica identity mismatch 文档进行备份、重建和脚本诊断。
- 验证:本地 SpacetimeDB 可正常启动并 publish / 访问。
- 关联:`docs/technical/SPACETIMEDB_LOCAL_REPLICA_IDENTITY_MISMATCH_FIX_2026-04-30.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
## 本地 SpacetimeDB publish 403 优先查 CLI 身份和目标库
@@ -308,7 +308,7 @@
- 原因:当前 CLI 登录态不是目标数据库的创建者或授权身份,或 `.env.local` / publish 命令指向了另一个数据库或 SpacetimeDB 服务。
- 处理:除 CI/CD 脚本内部受控用法外,不再使用 `spacetime --root-dir` 排障或发布。先执行 `spacetime login show``spacetime server list`,再用 `spacetime list --server http://127.0.0.1:3101` 或实际 `--server-url` 确认当前身份是否能看到目标库;本地开发发布优先使用 `npm run dev:rust` 或从 `server-rs` 目录执行显式 `--server``spacetime publish`。如果身份不对,重新登录正确身份、使用项目脚本重新生成本地库,或在 SpacetimeDB 侧补授权。
- 验证:`spacetime list --server http://127.0.0.1:3101` 能看到目标库;重新发布不再使用无权限 identity。
- 关联:`scripts/dev-rust-stack.sh``docs/technical/SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## `npm run dev` 本地 SpacetimeDB 401 / 403 可重置默认 local 身份
@@ -316,7 +316,7 @@
- 原因:本机 `spacetime` CLI 保存的旧 token、默认 server、正在运行的 standalone 进程或默认 local 数据库与当前发布身份不一致。
- 处理:确认只是本地测试库且数据可丢弃后,先查看并停止本地 `spacetimedb-standalone`,执行 `spacetime logout`,确认并设置 `spacetime server set-default local`,停 server 后用 `spacetime server clear -y` 清空默认本地库,再 `spacetime start`,另开终端执行 `spacetime login --server-issued-login local`,最后用 `spacetime publish --server local A` 或项目脚本重新发布。
- 验证:`spacetime server list` 默认目标为 local重新登录后发布不再返回 `401` / `403``npm run dev` 可以完成 SpacetimeDB publish 并继续启动 `api-server`
- 关联:`docs/technical/SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md``scripts/dev-rust-stack.sh`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## 本地 SpacetimeDB 联调可按阶段跳过宿主或发布
@@ -324,7 +324,7 @@
- 原因:`npm run dev` 默认同时启动 SpacetimeDB standalone、发布 `server-rs/crates/spacetime-module`、启动 Rust `api-server`、主站 Vite 与后台 Vite并非每个阶段都需要完整重启和重新发布。
- 处理:`npm run dev` 启动后会把实际 SpacetimeDB URL 记录到 `server-rs/.spacetimedb/local/data/dev-rust-spacetime-url`。下次启动即使没有传 `--skip-spacetime`,脚本也会先检查 `spacetime.pid` 对应进程和该 URL 是否在线;在线则直接复用现有宿主。确认需要新启动 SpacetimeDB 时,脚本先检测 `3101`,被占用则输出占用进程并选择最近可用端口,保证 publish 与 `api-server` 都连接同一个实际 SpacetimeDB URL。显式传 `--skip-spacetime` 时表示复用既有宿主,脚本不再对 SpacetimeDB 端口做可用性漂移;`--spacetime-port 3101` 就是后端要连接的实际端口,避免被误改到空闲但未启动的 `3102``api-server` 启动前也会检测 `8082` 并选择最近可用端口。Windows / Git Bash 下不要用 `tr/head/xargs` 管道读取 `spacetime.pid` 或 URL 记录,脚本应使用 Node 读取并短重试,避免 `tr: read error: Device or resource busy`;未修改 `spacetime-module` 时使用 `npm run dev -- --skip-publish`;只查模块语法时执行 `cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml``npm run dev` 会在启动前检查 SpacetimeDB、api-server、主站 Vite、后台 Vite 端口,不可用时自动寻找后续可用端口,并把实际端口传给 publish、后端环境变量和前端代理目标。
- 验证:`--skip-spacetime` 后脚本复用现有 `http://127.0.0.1:3101`;日志中的 `[dev:rust] spacetime:` 不应漂移到没有服务的 `3102``GET /api/creation-entry/config` 不应返回连接空端口导致的 `502``3101``8082` 被其他进程占用时,脚本输出占用进程并使用最近可用端口;`--skip-publish` 后不再进入 publish 阶段;`cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml` 能完成 Rust 语法和类型检查。端口漂移时控制台会打印 `[dev:ports] ... 不可用,改用 ...`,后续 `[dev:rust] web/admin web/rust api/spacetime` 地址应与实际端口一致。
- 关联:`docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md``scripts/dev-rust-stack.sh`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## 本地 SpacetimeDB publish 401 可清本地库重发
@@ -332,7 +332,7 @@
- 原因:本地开发数据目录中保留的数据库、控制库身份或发布身份与当前目标不一致。
- 处理:确认本地开发数据可以丢弃后,停止本地 SpacetimeDB备份或删除 `server-rs/.spacetimedb/local/data`,再重新运行 `npm run dev` 或本地 publish不要用 `--root-dir` 手工清库。
- 验证:重新发布日志应显示创建新的数据库,而不是更新旧数据库;若仍显示更新或继续 `401`,继续检查数据目录、库名和 CLI 身份。
- 关联:`docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md``docs/technical/SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## SpacetimeDB 模块 publish 报 `wasm-bindgen detected`
@@ -340,7 +340,7 @@
- 原因SpacetimeDB 模块是数据库内 WASM不允许拉入 Web/HTTP client 链路;常见误因是 `spacetime-module -> module-* -> shared-contracts -> platform-* -> reqwest -> wasm-bindgen` 这类反向依赖。
- 处理:执行 `cargo tree -i wasm-bindgen --manifest-path server-rs/Cargo.toml -p spacetime-module --target wasm32-unknown-unknown` 找到链路;把平台实现类型从 `shared-contracts``module-*` 中移除,只保留公开 DTO平台响应到 DTO 的转换放回 `api-server` 等 adapter 层。
- 验证:上述 `cargo tree` 输出 `warning: nothing to print``cargo check -p shared-contracts``cargo check -p api-server` 通过;重新 `spacetime publish ... --module-path server-rs/crates/spacetime-module` 不再报 wasm-bindgen。
- 关联:`docs/technical/RUST_WORKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md``server-rs/crates/shared-contracts/src/assets.rs``server-rs/crates/api-server/src/assets.rs`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## Vite SPA fallback 吞掉 API 请求
@@ -348,7 +348,7 @@
- 原因Vite 代理缺少对应 `/api/*` 前缀API 请求落到 SPA fallback。
- 处理:补齐 Vite 代理,让 API 请求转发到 Rust `api-server`
- 验证:请求返回 JSON相关页面不再出现 HTML parse 错误。
- 关联:`docs/technical/PROFILE_MAIN_ROUTE_VITE_PROXY_FIX_2026-05-02.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
## 反馈页清空 file input 前必须先拷贝 FileList
@@ -356,7 +356,7 @@
- 原因:浏览器传入的 `FileList` 可能跟 `<input type="file">` 保持 live 绑定;如果先执行 `input.value = ''`,再从参数里的 `FileList` 读取文件,列表可能已经为空。
- 处理:在清空 file input 前先执行 `const selectedFiles = files ? Array.from(files) : []`后续图片类型、大小、Data URL 读取和预览都基于这个普通数组。
- 验证:`PlatformFeedbackView.test.tsx` 用 mock `FileReader` 断言选择图片后出现 `反馈凭证预览`,且提交 payload 带 `evidenceItems[].dataUrl`
- 关联:`src/components/platform-entry/PlatformFeedbackView.tsx``docs/technical/PROFILE_FEEDBACK_BACKEND_INTEGRATION_2026-05-08.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 拼图 VectorEngine 图片生成密钥不能复用 DashScope / ARK key
@@ -364,7 +364,7 @@
- 原因:拼图 `gpt-image-2` / 历史 `nanobanana2` 图片生成已统一走 VectorEngine后端只读取 `VECTOR_ENGINE_BASE_URL``VECTOR_ENGINE_API_KEY``VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`,不会用 `DASHSCOPE_API_KEY``LLM_API_KEY``ARK_API_KEY``APIMART_API_KEY` 兜底。
- 处理:在本机私密配置 `.env.secrets.local` 或进程环境中配置真实 `VECTOR_ENGINE_API_KEY`,不要提交到 Git填入后必须重启 `api-server` / `npm run dev`,运行中的进程不会自动加载新 env。
- 验证:不打印密钥内容,只检查 `VECTOR_ENGINE_API_KEY` 非空;重启后触发拼图生成不再返回本地配置缺失的 503。
- 关联:`docs/technical/VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md``.codex/skills/gpt-image-2-apimart/SKILL.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## `npm run api-server` 读取 env 的顺序必须让 `.env.secrets.local` 最后覆盖
@@ -372,7 +372,7 @@
- 原因:`scripts/api-server-dev.mjs` 之前按 `.env.secrets.local → .env.local → .env` 合并,结果仓库里的 `.env` 空示例值会把前面已经设置好的私密 key 覆盖掉。
- 处理:`npm run api-server` / `npm run dev:rust` / `npm run dev` 统一按“外层 shell 变量优先,其后 `.env``.env.local``.env.secrets.local` 逐层覆盖”的顺序加载;真实密钥优先放 `.env.secrets.local`
- 验证:本地加入临时测试后,`HYPER3D_API_KEY` 应能被 `.env.secrets.local` 覆盖,且 shell 变量仍然最高优先级。
- 关联:`scripts/api-server-dev.mjs``server-rs/crates/api-server/src/hyper3d_generation.rs``docs/technical/HYPER3D_RODIN_GEN2_MODEL_GENERATION_2026-05-08.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## OSS 密钥键名不要把字母 O 写成数字 0
@@ -387,7 +387,7 @@
- 原因:`platform-oss` 曾用 `OffsetDateTime::time().to_string()` 拼接 `x-oss-date`UTC 小时、分钟或秒为个位数时可能缺少前导零,导致 V4 签名时间不是固定 `YYYYMMDDTHHMMSSZ`
- 处理OSS V4 签名日期统一显式补零格式化;签名 scope 用 `YYYYMMDD`,完整签名时间用 `YYYYMMDDTHHMMSSZ`,不要再依赖 `time().to_string()`
- 验证:运行 `cargo test -p platform-oss``cargo check -p api-server`;重启 `npm run api-server` 后检查 `/healthz`,再重新触发拼图生成。
- 关联:`server-rs/crates/platform-oss/src/lib.rs``server-rs/crates/api-server/src/assets.rs``docs/technical/M6_OSS_SERVER_UPLOAD_AND_STS_POLICY_2026-04-21.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
## 拼图生成完成后图片只显示破图或 alt 文案
@@ -395,7 +395,7 @@
- 原因:拼图正式图保存为 `/generated-puzzle-assets/*` 兼容标识,旧 `/generated-*` 直读代理已删除;如果前端没有通过 `ResolvedAssetImage` / `/api/assets/read-url` 换签,或收到无前导斜杠的 `generated-puzzle-assets/*` object key 后未识别为 generated 私有资源,浏览器会直接请求裸路径并失败。生成完成后的结果图还会传入 `refreshKey`,它只能用于重新请求 `/api/assets/read-url`,不能给 OSS V4 签名 URL 追加 `_v`OSS 会把 query 纳入签名,额外参数会让签名失效,历史素材常因未传 `refreshKey` 而表现正常。
- 处理:拼图结果页、发布预览、运行态和历史素材预览都走 `ResolvedAssetImage``useResolvedAssetReadUrl``isGeneratedLegacyPath(...)` 必须同时识别 `/generated-*``generated-*``refreshKey` 只绕过前端签名缓存并重新换签,不修改已返回的 OSS 签名 URL禁止恢复 `/generated-puzzle-assets` 直读代理。
- 验证:运行 `npm run test -- src\services\assetReadUrlService.test.ts src\hooks\useResolvedAssetReadUrl.test.tsx src\components\puzzle-result\PuzzleResultView.test.tsx`,再触发一次真实生成确认 Network 中先请求 `/api/assets/read-url`,图片 `src` 为未追加 `_v` 的签名 URL。
- 关联:`src/services/assetReadUrlService.ts``src/components/ResolvedAssetImage.tsx``docs/technical/PUZZLE_IMAGE_ASSET_PROXY_FIX_2026-04-27.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 本地短信登录页签突然消失
@@ -408,7 +408,7 @@
- 单独 `npm run dev:web` 启动瞬间另一个临时 API 端口可用,脚本若自动切过去,之后临时 API 停掉也会让 3000 继续代理到空端口。
- 处理:优先用 `npm run api-server``npm run dev:rust``npm run dev` 启动,这些入口应保持 shell 环境变量最高优先级,并允许 `.env.local` 覆盖 `.env`;完整栈启动时还要确保脚本计算出的 `RUST_SERVER_TARGET` 不被 `.env.local` 里的旧值覆盖。排查时先请求 3000 域名下的 `/api/auth/login-options`,再直连 Rust API 目标,并核对 `.env.local``SMS_AUTH_ENABLED` 与代理端口;若 3001/3002 才返回正确结果,说明当前 3000 是旧前端进程,应清理旧进程后重启。
- 验证:`http://127.0.0.1:3000/api/auth/login-options` 返回至少 `{"availableLoginMethods":["phone","password"]}` 后,登录弹窗会恢复短信登录页签和“获取验证码”按钮。
- 关联:`scripts/api-server-dev.mjs``scripts/api-server-maincloud.mjs``scripts/dev-rust-stack.sh``scripts/dev-web-rust.mjs``docs/technical/AUTH_LOGIN_OPTIONS_DESIGN_2026-04-21.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## 本地短信收不到验证码先查 provider
@@ -416,7 +416,7 @@
- 原因:本地 `.env.local` 里如果是 `SMS_AUTH_PROVIDER="mock"`,后端不会发真实短信,只会返回固定 mock 验证码;另外 `npm run api-server` 过去曾让 `.env` 覆盖 `.env.local`,导致本地真实短信配置被错误压回默认值。
- 处理:真实短信联调时把 `.env.local``SMS_AUTH_PROVIDER` 显式设为 `aliyun`,然后重启 `api-server`;如果只想验证 UI 和账号链路,则保留 `mock` 并使用 `SMS_AUTH_MOCK_VERIFY_CODE`
- 验证:`GET /api/auth/login-options` 返回 `["phone","password"]``api-server` 日志里 `provider=aliyun` 才说明真实短信链路已生效。
- 关联:`scripts/api-server-dev.mjs``docs/technical/AUTH_LOGIN_OPTIONS_DESIGN_2026-04-21.md``docs/technical/PHONE_SMS_REAL_PROVIDER_MANUAL_VERIFICATION_RUNBOOK_2026-04-23.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## 手机验证码登录 500 先查短信 provider 语义
@@ -424,7 +424,7 @@
- 原因:真实短信 provider 的配置错误或上游失败曾被 `module-auth` 折叠成 `PhoneAuthError::Store`HTTP 层只能按内部错误返回 `500`,掩盖了 provider 失败。
- 处理:保留 provider 错误语义,配置错误映射 `503 Service Unavailable`,上游短信失败映射 `502 Bad Gateway`;本地只验证 UI/账号链路时可用 shell 临时覆盖 `SMS_AUTH_PROVIDER=mock` 后启动 `npm run api-server`
- 验证:`cargo test -p api-server phone_auth_sms_provider_errors_keep_upstream_http_semantics --manifest-path server-rs/Cargo.toml`,真实 provider 频控时接口不再返回 `500`
- 关联:`server-rs/crates/module-auth/src/errors.rs``server-rs/crates/api-server/src/phone_auth.rs``docs/technical/PHONE_SMS_PROVIDER_ERROR_HTTP_MAPPING_FIX_2026-05-08.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
## 手机验证码登录成功后又瞬间回到未登录
@@ -432,7 +432,7 @@
- 原因:`AuthGate` 首次 hydrate 会异步轮换 refresh cookie 并请求 `/api/auth/me`。如果用户在 hydrate 完成前已经登录,晚到的旧 hydrate 仍可能把刚写入的 `user` 覆盖成 `null`
- 处理:给 `AuthGate` 的 hydrate 增加版本号保护;登录成功、退出登录和全局 auth 事件都会推进版本号,旧 hydrate 结果到达后直接丢弃。
- 验证:`npm run test -- src/components/auth/AuthGate.test.tsx`,新增用例应覆盖“旧 guest hydrate 不覆盖新登录态”。
- 关联:`src/components/auth/AuthGate.tsx``src/components/auth/AuthGate.test.tsx``docs/technical/AUTH_GATE_LOGIN_RACE_GUARD_FIX_2026-05-09.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## 刷新网页后登录态失效
@@ -440,7 +440,7 @@
- 原因:`AuthGate` hydrate 曾先强制调用 `refreshStoredAccessToken()`;当 refresh cookie 临时失效、代理错配或后端返回 `401` 时,该方法会先清空本地 access token随后 `/api/auth/me` 只能恢复成未登录。
- 处理:`refreshStoredAccessToken()` 增加 `clearOnFailure` 选项;`AuthGate` 在已有本地 access token 时先用 `/api/auth/me` 确认用户,确认成功后再后台 refresh 续期与写每日登录埋点,后台 refresh 失败不清 token。
- 验证:`npm run test -- src/services/apiClient.test.ts src/components/auth/AuthGate.test.tsx -t "explicit refresh opts out|auth gate keeps a valid local token login"`
- 关联:`src/services/apiClient.ts``src/components/auth/AuthGate.tsx``docs/technical/AUTH_RESTORE_AND_RECOMMEND_LOADING_FIX_2026-05-09.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 登录后推荐页加载出作品又回到未登录
@@ -451,7 +451,7 @@
- 追加处理:从推荐页点进公开拼图作品并启动完整运行态后,`startPuzzleRun`、通关自动 `submitPuzzleLeaderboard`、下一关 `advancePuzzleNextLevel` 和重开同样属于当前玩法局部同步;这些请求失败时只应留在拼图错误态,不应清 token 或广播 auth 事件。
- 追加处理:通关后 `refreshSaveArchives()`、首屏 bootstrap 的个人看板/作品架/浏览历史读写也只是平台投影刷新,失败应显示局部错误,不能充当全局登录态判定。
- 验证:`npm run test -- src/services/apiClient.test.ts src/services/assetReadUrlService.test.ts``npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "home recommendation starts embedded puzzle"``npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "formal puzzle runtime uses frontend move merge logic and backend leaderboard"``npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "formal puzzle similar work keeps current run level progression"`
- 关联:`src/services/apiClient.ts``src/services/assetReadUrlService.ts``src/services/puzzle-runtime/puzzleRuntimeClient.ts``src/components/platform-entry/PlatformEntryFlowShellImpl.tsx``docs/technical/RECOMMEND_RUNTIME_AUTH_FAILURE_ISOLATION_FIX_2026-05-09.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 推荐页作品卡一直显示加载中
@@ -459,7 +459,7 @@
- 原因:推荐页自动启动嵌入运行态时先设置 `activeRecommendEntryKey` / `activeRecommendRuntimeKind` / `isStartingRecommendEntry`,但失败或并发切换时外层缺少稳定错误态和请求版本保护,旧启动请求可能晚到覆盖新状态。
- 处理:`selectRecommendRuntimeEntry` 使用启动请求版本号丢弃旧请求;启动失败统一设置 `activeRecommendRuntimeError = "作品暂时无法进入,请稍后再试。"` 并关闭 `isStartingRecommendEntry`
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "home recommendation surfaces start failure"`
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx``src/components/rpg-entry/RpgEntryHomeView.tsx``docs/technical/AUTH_RESTORE_AND_RECOMMEND_LOADING_FIX_2026-05-09.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 推荐页未登录入口误打开公开详情
@@ -467,7 +467,7 @@
- 原因:`RpgEntryHomeView` 曾只有 `onOpenGalleryDetail` 一个回调,同时服务发现页公开详情和推荐页作品入口;一旦为发现页保留公开浏览能力,推荐页也会跟着打开详情。
- 处理:公开详情与推荐页入口分离为 `onOpenGalleryDetail``onOpenRecommendGalleryDetail`。发现页、搜索和排行榜保留公开详情;推荐 Tab、推荐封面、推荐运行态错误重试和桌面推荐模块统一走登录门禁。未登录推荐页只显示封面点击封面只弹登录窗不携带登录后自动打开详情的回调。
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "logged out recommend"`
- 关联:`src/components/rpg-entry/RpgEntryHomeView.tsx``src/components/platform-entry/PlatformEntryFlowShellImpl.tsx``docs/technical/AUTH_RESTORE_AND_RECOMMEND_LOADING_FIX_2026-05-09.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## Rust 冷编译导致 api-server 健康检查误超时
@@ -475,7 +475,7 @@
- 原因:脚本把 SpacetimeDB 与 api-server 等待窗口混在一起,未考虑 Rust 冷编译耗时。
- 处理:按冷编译超时修复文档拆分等待窗口。
- 验证:冷启动时不再误杀仍在编译的 api-server。
- 关联:`docs/technical/API_SERVER_DEV_STACK_COLD_BUILD_TIMEOUT_FIX_2026-04-25.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## Windows debug api-server 主线程栈溢出
@@ -499,7 +499,7 @@
- 原因:旧 `api-server` 仍监听默认 `8082` 时,脚本的 `/healthz` 探测会命中旧进程并误判新服务已就绪;旧 Vite 占住 `3000`Vite 默认漂移到新端口,浏览器仍可能打开旧页面。
- 处理:`scripts/dev-rust-stack.sh` 已在 publish / 编译前预检 `api-server`、主站 Vite、后台 Vite 端口,并让 Vite 使用 `--strictPort`;遇到端口占用时按脚本打印的 PID 停止旧进程,或显式传入 `--api-port` / `--web-port` / `--admin-web-port`
- 验证:默认端口被占用时,完整栈应在发布模块前直接失败并打印监听进程;清理端口后重新启动不再漂移端口或命中旧 `/healthz`
- 关联:`scripts/dev-rust-stack.sh``docs/technical/DEV_RUST_STACK_PORT_CONFLICT_PRECHECK_2026-05-09.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## Windows debug 长 SSE Future 触发 api-server 断连
@@ -518,7 +518,7 @@
- 工具开始事件必须等同一 `toolCallId``tool_completed` 收口;兼容旧流时可按后续同名完成事件兜底。
- 思考摘要只展示用户可见摘要,且流结束或会话进入等待/完成/失败态后必须改成 done。
- 验证:前端测试断言完成后 `CreativeAgentProcessItem` 不再存在 `tone === 'active'`;后端测试确认工具开始/完成事件使用相同 `toolCallId`
- 关联:`src/components/creative-agent/creativeAgentViewModel.ts``server-rs/crates/api-server/src/creative_agent.rs``docs/prd/CREATIVE_INTERACTIVE_AGENT_PHASE1_LANGCHAIN_RUST_PUZZLE_LOOP_PRD_2026-05-05.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## creative-agent 会话切换要清理本地待确认模板
@@ -534,15 +534,15 @@
- 原因前端上传与预览容易混在一起若不走平台资产对象SpacetimeDB 和长期草稿会被大文本或大二进制污染。
- 处理VN 资产统一用 `/api/assets/direct-upload-tickets`、OSS 直传、`/api/assets/objects/confirm`,长期状态只保存 `assetObjectId``/generated-*` 引用;运行时图片用 `ResolvedAssetImage` 换签。
- 验证:文档模式 `sourceAssetIds` 为平台资产 id草稿中不出现 `data:`;图片和音乐字段为平台 generated 引用或 null。
- 关联:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md``src/services/visual-novel-creation/visualNovelAssetClient.ts`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 视觉小说 VN-13 交接时不要再回头找旧迁移方案
- 现象:接手视觉小说的人容易重新打开旧 TXT 迁移文档,把“外部平台工程迁入”误当成当前实现目标。
- 原因:视觉小说历史资料里保留了很多迁移阶段的讨论,而当前真正的实现口径已经收口到 PRD、表目录、Prompt 工具说明、实现收口文档和负向扫描报告。
- 处理:维护视觉小说时优先看 `AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md``SPACETIMEDB_TABLE_CATALOG.md``VISUAL_NOVEL_PROMPT_AND_LLM_TOOLS_VN03_2026-05-05.md``VISUAL_NOVEL_IMPLEMENTATION_HANDOFF_2026-05-07.md``VISUAL_NOVEL_HANDOFF_AND_MAINTENANCE_2026-05-07.md``VN11_NEGATIVE_SCAN_REPORT_2026-05-07.md`
- 处理:维护视觉小说时优先看当前玩法链路文档、当前后端架构文档,并运行 `npm run check:visual-novel-vn11` / `npm run check:visual-novel-vn12` 确认负向扫描和全链路验收
- 验证:新开发者只读这组文档即可继续维护,不需要把旧 TXT 迁移方案重新当作编码依据。
- 关联:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md``docs/technical/VISUAL_NOVEL_IMPLEMENTATION_HANDOFF_2026-05-07.md``docs/experience/VISUAL_NOVEL_HANDOFF_AND_MAINTENANCE_2026-05-07.md`
- 关联:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 视觉小说公开广场不要触发登录刷新
@@ -550,7 +550,7 @@
- 原因:公开只读接口如果复用默认 `requestJson` 选项,缺少 access token 时会先走静默 refresh。
- 处理:视觉小说公开广场列表使用 `skipAuth: true``skipRefresh: true`;鉴权 mutation 仍保持默认鉴权链路。
- 验证:执行 `src/services/visual-novel-runtime/visualNovelRuntimeClient.test.ts`,确认 `/api/runtime/visual-novel/gallery` 请求携带 `skipAuth` / `skipRefresh`,而 run、重生成和存档 mutation 仍走受保护路由。
- 关联:`src/services/visual-novel-runtime/visualNovelRuntimeClient.ts``docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`
- 关联:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 创作 Tab 语义迁移后,旧“新建作品”测试要改看智能创作首页
@@ -566,7 +566,7 @@
- 原因workspace default-members 当前只包含 `crates/api-server`SpacetimeDB module 有独立构建/发布方式。
- 处理:默认 Rust 构建只覆盖原生 `api-server`;本地模块发布继续走 `spacetime publish --module-path ... --build-options="--debug"` / bindings 生成流程。
- 验证:查看 `server-rs/Cargo.toml` default-members并按相关 SpacetimeDB 文档执行模块构建。
- 关联:`server-rs/Cargo.toml``docs/technical/RUST_WORKSPACE_DEFAULT_BUILD_SCOPE_FIX_2026-04-25.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## Windows 原生 `spacetime-module` 单测会链接缺失 SpacetimeDB 宿主符号
@@ -574,7 +574,7 @@
- 原因:`spacetime-module` 依赖的 SpacetimeDB runtime API 面向 wasm 宿主环境,原生 test exe 链接不到这些宿主导出。
- 处理:日常语法和类型验证使用 `cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml`;需要验证模块行为时走 SpacetimeDB publish/dev 或模块域纯 Rust crate 的单测,不把该原生链接错误当作业务测试失败。
- 验证:`cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml` 能通过;原生 `cargo test` 若仍报上述宿主符号缺失,按当前限制记录为未执行。
- 关联:`server-rs/crates/spacetime-module``docs/technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md`
- 关联:`server-rs/crates/spacetime-module``docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
## Rust 构建不要让不可用的 sccache 阻断 rustc
@@ -582,15 +582,15 @@
- 原因环境、Jenkinsfile 或 `server-rs/.cargo/config.toml` 启用了 `sccache` wrapper但当前 agent 没有可执行的 `sccache`、PATH 中 shim 损坏,或本地 sccache server/client 通道状态损坏。Windows 本机若配置了 `SCCACHE_OSS_*`sccache daemon 冷启动会先经 OSS/本机代理完成缓存读写检查,再监听 `127.0.0.1:4226`;代理或 OSS 链路慢时Cargo 的 `sccache rustc -vV` 可能先超时。
- 处理:保留 `server-rs/.cargo/config.toml``rustc-wrapper = "sccache"`Windows 本机优先在 `%APPDATA%\Mozilla\sccache\config\config` 写入 `server_startup_timeout_ms = 60000`,拉长 client 等待 daemon 完成 OSS 初始化的时间,然后删除 `server-rs/target/.rustc_info.json` 里缓存的失败探测结果并重跑原始 Cargo 命令。冷启动验证优先用 `sccache --stop-server`,不要在另一个 `cargo` / `rustc` 仍在编译时 `taskkill /F /IM sccache.exe /T`,否则 proc-macro crate 可能被打断并表现为 `serde_derive` / `spacetimedb-bindings-macro``sccache ... exit code: 1`。若只做临时排障,可在 Git Bash 中执行 `RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= cargo build ...`,或在 PowerShell 用 `cargo check -p api-server --config "build.rustc-wrapper=''"` 一次性绕过 wrapper生产流水线必须先实际执行 `sccache --version`,失败时移除 `RUSTC_WRAPPER` 并回退到直接 `rustc`
- 验证:`rustc -Vv` 能输出版本;冷启动后原始 `cargo check -p api-server``cargo check -p spacetime-module` 能通过;`sccache --show-stats` 显示 `Cache location oss, name: genarrative-sccache`,证明仍在使用 sccache/OSS 缓存Jenkins 日志出现“未找到可用 sccache改用 rustc 直接构建”后仍继续真实构建。
- 关联:`scripts/dev-rust-stack.sh``jenkins/Jenkinsfile.production-stdb-module-build``docs/technical/SPACETIMEDB_PUBLISH_SCCACHE_FALLBACK_2026-05-09.md``docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## 生产发布入口不要沿用旧 Jenkinsfile / 一体化脚本
- 现象:部署、回滚或 Jenkins Job 重建时参考旧发布文档,导致 systemd、Nginx、SpacetimeDB 自托管和生产包拆分不一致。
- 原因:旧 Jenkins / 旧本地远端部署脚本文档仍作为历史经验保留。
- 处理:生产相关操作先看 `PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`,再按需追溯旧文档。
- 处理:生产相关操作先看 `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`,再按需追溯旧文档。
- 验证:发布链路使用当前 `deploy/systemd``deploy/nginx``scripts/deploy``jenkins/Jenkinsfile.production-*`
- 关联:`docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`
- 关联:`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## Release Web 产物通过内网 rsync 拉取
@@ -598,7 +598,7 @@
- 原因Web 大包为了避免从 Linux 构建机拉回 Jenkins controller默认留在构建机稳定缓存目录development 目标与构建机同机可直接读取release 目标是独立机器,需要内网同步。
- 处理release 服务器的 Jenkins 运行用户配置 SSH Host `genarrative-build-internal` 指向构建机内网地址,`Genarrative-Web-Deploy``DEPLOY_TARGET=release` 且本地缺少大包时默认执行 `rsync` 拉取同一路径内容;真实内网 IP、用户和私钥路径只放在服务器本机 SSH config不写入 Jenkinsfile。
- 验证:在 release 服务器上先手工跑通 `rsync -av --progress "genarrative-build-internal:${SRC}/" "${DST}/"`,再运行 Web Deploy流水线会继续执行 `web.tar.gz.sha256` 校验。
- 关联:`jenkins/Jenkinsfile.production-web-deploy``docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`
- 关联:`jenkins/Jenkinsfile.production-web-deploy``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## Jenkins 生产流水线拉 Git 先本机再域名备用
@@ -606,7 +606,7 @@
- 原因:`127.0.0.1` 只代表当前执行阶段的 agent 自身;当 release agent 与 Git 服务不在同一台机器,或本机 Git/Web 服务临时不可用时,固定写死 localhost 会阻断 Jenkinsfile 内部源码/脚本 checkout。即使只使用域名 Git如果 `GitSCM` 没有显式 refspec 并开启 `CloneOption honorRefspec=true`Jenkins Git 插件也会拉取所有分支。
- 处理Jenkins Job 的 `Pipeline script from SCM` 由 Windows controller 执行SCM URL 使用公网域名 `https://git.genarrative.world/GenarrativeAI/Genarrative.git`。运行于 Linux agent 的 Jenkinsfile 首次 `checkout([$class: 'GitSCM', ...])` 层先尝试 `GIT_REMOTE_URL=http://127.0.0.1:3000/GenarrativeAI/Genarrative.git`,失败后直接尝试 `GIT_REMOTE_FALLBACK_URL=https://git.genarrative.world/GenarrativeAI/Genarrative.git`,不再配置内网 IP fallback所有生产 Jenkinsfile 的首次 checkout 都必须使用目标分支 refspec、`CloneOption shallow=true depth=1 noTags=true honorRefspec=true`。后续统一走 `scripts/jenkins-checkout-source.sh`,该脚本也按主地址、域名备用地址顺序重新 fetch 并把 `origin` 切到实际可用地址;`COMMIT_HASH` 为空时继续 `--depth=1 --no-tags`,只有指定 commit 时才允许加深历史做分支归属校验。
- 验证:扫描本地 Jenkins live job `config.xml`,确认 SCM `<url>` 都是 `https://git.genarrative.world/GenarrativeAI/Genarrative.git`;扫描所有生产 Jenkinsfile 的首次 `GitSCM checkout`,确认 `userRemoteConfigs``+refs/heads/${params.SOURCE_BRANCH}:refs/remotes/origin/${params.SOURCE_BRANCH}``CloneOption``honorRefspec: true`;运行 `bash -n scripts/jenkins-checkout-source.sh`
- 关联:`jenkins/Jenkinsfile.production-full-build-and-deploy``jenkins/Jenkinsfile.production-web-build``jenkins/Jenkinsfile.production-api-build``jenkins/Jenkinsfile.production-stdb-module-build``jenkins/Jenkinsfile.production-web-deploy``jenkins/Jenkinsfile.production-api-deploy``jenkins/Jenkinsfile.production-stdb-module-publish``jenkins/Jenkinsfile.production-server-provision``jenkins/Jenkinsfile.production-database-export``jenkins/Jenkinsfile.production-database-import``scripts/jenkins-checkout-source.sh``docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`
- 关联:`jenkins/Jenkinsfile.production-full-build-and-deploy``jenkins/Jenkinsfile.production-web-build``jenkins/Jenkinsfile.production-api-build``jenkins/Jenkinsfile.production-stdb-module-build``jenkins/Jenkinsfile.production-web-deploy``jenkins/Jenkinsfile.production-api-deploy``jenkins/Jenkinsfile.production-stdb-module-publish``jenkins/Jenkinsfile.production-server-provision``jenkins/Jenkinsfile.production-database-export``jenkins/Jenkinsfile.production-database-import``scripts/jenkins-checkout-source.sh``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## Jenkins 可选参数在 set -u 下不能裸读
@@ -614,7 +614,7 @@
- 原因Jenkins string/boolean 参数留空时不一定会导出同名环境变量,而生产数据库导入导出脚本块启用了 `set -u`
- 处理:进入 Bash 执行块后先使用 `${VAR:-}``${VAR:-默认值}` 收敛成本地变量;必填项使用 `${VAR:?中文错误}` 明确失败原因。
- 验证:扫描 `jenkins/Jenkinsfile.production-database-export``jenkins/Jenkinsfile.production-database-import`,确认 `INCLUDE_TABLES``CHUNK_SIZE``SERVER_BACKUP_DIRECTORY``SMOKE_HEALTH_URL` 等可选参数不再裸读。
- 关联:`docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md``jenkins/Jenkinsfile.production-database-export``jenkins/Jenkinsfile.production-database-import`
- 关联:`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md``jenkins/Jenkinsfile.production-database-export``jenkins/Jenkinsfile.production-database-import`
## 个人任务 scope 不得扩成 work/site/module
@@ -622,7 +622,7 @@
- 原因:首版个人任务只支持用户维度,非 user scope 会造成任务进度读取语义错误。
- 处理Admin 任务配置页不展示范围选择,保存时固定 `scopeKind: 'user'`API 和领域构造层拒绝非 `User`
- 验证:非 `user` scope 返回错误;相关测试覆盖 `Site` / `Module` / `Work` 被拒绝。
- 关联:`docs/technical/RUNTIME_PROFILE_TASK_SCOPE_2026-05-04.md``docs/technical/ANALYTICS_DATE_DIMENSION_IMPLEMENTATION_2026-05-04.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 拼图发布 409 不一定是接口故障
@@ -630,7 +630,7 @@
- 原因:`publish_puzzle_work` 是资产操作发布入口,发布前会预扣 `1` 枚泥点;余额不足时后端按业务冲突返回 `409 CONFLICT``details.message``泥点余额不足`
- 处理:前端发布弹窗在用户点击发布后必须保留并展示后端业务错误,不能只把错误写到弹窗背后的页面 banner。
- 验证:`PuzzleResultView` 单测覆盖发布弹窗内展示 `泥点余额不足`
- 关联:`src/components/puzzle-result/PuzzleResultView.tsx``docs/technical/PUZZLE_RESULT_AUTOSAVE_AND_TAG_GATE_FIX_2026-04-28.md``docs/technical/ASSET_GENERATION_POINTS_CONSUMPTION_2026-04-27.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## WebGL 画布在高 DPR 移动端放大溢出
@@ -638,7 +638,7 @@
- 原因:`WebGLRenderer.setPixelRatio(...)` 会把绘图缓冲区乘上设备 DPR如果没有给 `renderer.domElement` 单独设置 CSS `width/height: 100%` 和绝对铺满,浏览器可能把高 DPR 缓冲区尺寸当成页面显示尺寸。
- 处理:中心棋盘和托盘预览的 WebGL canvas 统一套用 `position:absolute; inset:0; width:100%; height:100%; display:block``renderer.setSize(..., false)` 只负责同步绘图缓冲区。
- 验证:强制移动端 `390x844`、DPR 2 截图确认棋盘左右边界在视口内canvas CSS 尺寸等于容器尺寸,内部 `width/height` 属性可大于 CSS 尺寸。
- 关联:`src/components/match3d-runtime/Match3DPhysicsBoard.tsx``docs/technical/MATCH3D_RUNTIME_3D_GEOMETRY_EXPERIMENT_2026-05-02.md`
- 关联:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## Hyper3D subscriptionKey 不要按固定短文本限长
@@ -646,7 +646,7 @@
- 原因:`subscriptionKey` 是 Hyper3D 返回的 opaque token长度由上游决定后端状态查询曾复用普通文本校验把它限制在 256 字符。
- 处理:`query_task_status``subscriptionKey` 只做 trim 和非空校验,不做固定长度限制;前端临时任务和 Match3D 草稿响应可继续展示该 token但不要把它当作可编辑短文本。
- 验证:`cargo test -p api-server accepts_opaque_subscription_key_without_length_cap --manifest-path server-rs/Cargo.toml`
- 关联:`server-rs/crates/api-server/src/hyper3d_generation.rs``docs/technical/HYPER3D_RODIN_GEN2_MODEL_GENERATION_2026-05-08.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
## 抓大鹅新草稿不要再接回 Rodin 或 GLB 生成
@@ -654,7 +654,7 @@
- 原因:仓库里保留了 Hyper3D 通用代理和历史模型字段,旧文档也曾要求草稿阶段同步生成 GLB。当前产品口径已经改为 2D 多视角素材。
- 处理:新 `match3d_compile_draft` 与批量新增只生成 2D 图片:每个物品 5 个视角,单张 1K 素材图固定 5x5最多承载 5 个物品,一行对应一个物品,不足 5 个物品也补齐到完整 5 行;超过 5 个物品自动分批并行生图。素材图 prompt 固定要求纯绿色绿幕背景,切割前先把绿幕处理为透明 alpha再做格内内容前景边界校准并带留白避免固定内缩切掉贴近格线的主体。`generatedItemAssets[].status` 使用 `image_ready`,发布校验看 `imageViews[]` 或首图引用。`generated-models` 仅用于历史外部模型链接转存,不能作为新生产链路。
- 验证:`cargo test -p api-server match3d --manifest-path server-rs/Cargo.toml``npm run test -- src\services\miniGameDraftGenerationProgress.test.ts src\components\match3d-result\Match3DResultView.test.tsx src\components\match3d-runtime\Match3DRuntimeShell.test.tsx`
- 关联:`server-rs/crates/api-server/src/match3d.rs``src/components/match3d-runtime/Match3DRuntimeShell.tsx``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 抓大鹅切图路径不能只用中文物品名
@@ -662,7 +662,7 @@
- 原因:中文物品名经过 OSS 路径段清洗后都可能退化成 `item`,多张切割图片写到同一个 object key后写入覆盖先写入。
- 处理:切割图上传路径必须带稳定唯一 `itemId` 前缀,例如 `items/match3d-item-1-item/views/view-01.png`;运行态读取 generated 私有图片时通过同源 `/api/assets/read-url` 换签,不直接请求裸 OSS 路径。
- 验证:后端单测覆盖中文名路径唯一,前端运行态测试覆盖 generated 图片源解析。
- 关联:`server-rs/crates/api-server/src/match3d.rs``src/components/match3d-result/Match3DResultView.tsx``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 抓大鹅生成素材不能只挂在 compile response
@@ -670,7 +670,7 @@
- 原因:`generatedItemAssets` 如果只附加在 `match3d_compile_draft` 的 HTTP response draft 上,刷新或重进时 `getMatch3DWorkDetail` 只能读取 SpacetimeDB 中的 `match3d_work_profile`;旧 mapper 返回空数组,自然无法恢复素材。拼图链路已经通过 `save_puzzle_generated_images` 把候选图和 levels 写回 work profile抓大鹅也必须同样写持久字段。
- 处理compile 成功时把独立物品图片列表序列化写入 `match3d_work_profile.generated_item_assets_json``update_match3d_work` / `publish_match3d_work` 保留该字段API work summary/detail 映射反序列化为 `generatedItemAssets`。前端保持“本次 draft 优先,重进 profile 兜底”的读取顺序。
- 验证:`cargo test -p spacetime-client match3d --manifest-path server-rs/Cargo.toml``cargo test -p api-server match3d --manifest-path server-rs/Cargo.toml``npm run test -- src/components/match3d-result/Match3DResultView.test.tsx`
- 关联:`server-rs/crates/spacetime-module/src/match3d/*``server-rs/crates/spacetime-client/src/mapper.rs``server-rs/crates/api-server/src/match3d.rs``src/components/match3d-result/Match3DResultView.tsx``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 抓大鹅试玩和正式运行态不要只读草稿页本地素材预览
@@ -678,7 +678,7 @@
- 原因:结果页本地 `assetDrafts` 和作品 profile 的 `generatedItemAssets` 可能不同步;推荐流内嵌运行态若只读卡片摘要,卡片缺素材时会把已持久化 profile 素材丢掉;点击试玩时 React state 异步更新也可能让运行态第一帧读取旧 `match3dProfile`
- 处理:删除、批量新增、音效生成或封面引用物品素材后,都把当前 `generatedItemAssets` 写回作品 profile`Match3DResultView` 合并同 `itemId` 的 draft/profile 素材,用 profile 已有 `imageViews[]`、首图引用、`backgroundMusic``backgroundAsset` 补齐旧 draft点击试玩前把试玩可用物品种类通过 `itemTypeCountOverride` 降到已生成 2D 素材数量;推荐流内嵌运行态启动前若卡片摘要没有物品图片素材,补读 `getMatch3DWorkDetail(profileId)` 并把详情资产传给 `Match3DRuntimeShell``PlatformEntryFlowShellImpl` 需要维护 `match3dRuntimeProfile`,在 `startMatch3DRunFromProfile` 创建 run 后立即锁定本次完整 profileruntime 渲染时优先按 `run.profileId` 使用这份 profile而不是等待普通 `match3dProfile` state 下一轮刷新。同 profile 下已有 `generatedItemAssets` 时不能因为图片完整性判断失败就覆盖为空数组。判断是否需要补读详情时只看 `imageViews[]``imageSrc/imageObjectKey`;背景、音乐、容器 UI 是附属运行态资产,不能单独证明物品素材已完整。
- 验证:执行 `npm run test -- src/components/match3d-result/Match3DResultView.test.tsx``npm run test -- src/components/match3d-runtime/Match3DRuntimeShell.test.tsx``npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`,并检查历史草稿和公开 M3 作品的 Network 响应里 `generatedItemAssets[].imageViews/imageSrc/imageObjectKey`
- 关联:`src/components/match3d-result/Match3DResultView.tsx``src/components/platform-entry/PlatformEntryFlowShellImpl.tsx``src/components/match3d-runtime/Match3DPhysicsBoard.tsx``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 抓大鹅 UI 背景和容器只在顶层字段时也要传进运行态
@@ -686,7 +686,15 @@
- 原因:部分链路把 UI 资产只放在作品顶层 `generatedBackgroundAsset` / `backgroundImageObjectKey`,没有同步放进首个 `generatedItemAssets[].backgroundAsset`;如果运行态入口只传 `generatedItemAssets``backgroundImageSrc``Match3DRuntimeShell` 就拿不到 `containerImageObjectKey`
- 处理:`PlatformMatch3DGalleryCard``mapPublicWorkDetailToMatch3DWork``resolveMatch3DRuntimeGeneratedBackgroundAsset``Match3DRuntimeShell` 都必须保留并传递顶层 `generatedBackgroundAsset`;运行态背景读取顺序为 `backgroundImageSrc` / 顶层 `generatedBackgroundAsset.image*` / `generatedItemAssets[].backgroundAsset.image*`,容器读取顺序为顶层 `generatedBackgroundAsset.containerImage*` / `generatedItemAssets[].backgroundAsset.containerImage*`
- 验证:执行 `npm run test -- src/components/match3d-runtime/Match3DRuntimeShell.test.tsx``npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "Match3D runtime"`;浏览器 Network 中背景和容器 generated path 应先请求 `/api/assets/read-url` 换签,局内出现 `match3d-background-image``match3d-container-image` 对应图片。
- 关联:`src/components/match3d-runtime/Match3DRuntimeShell.tsx``src/components/platform-entry/PlatformEntryFlowShellImpl.tsx``src/components/rpg-entry/rpgEntryWorldPresentation.ts``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 抓大鹅重启时不要清空 generated 图片签名缓存
- 现象:抓大鹅进入局内时背景或生成物品首帧缺失;点击右上角重启后,局内短暂显示默认积木,过一段时间才换回实际生成素材。
- 原因:`Match3DRuntimeShell` 在 run / resources 变化时清空 `resolvedImageSources`,导致同一批 generated 私有图每次重启都重新换签;换签未完成期间渲染层把空图片当成“没有生成素材”,直接落到默认积木兜底。另一个常见遗漏是运行态预加载只覆盖物品图,没覆盖顶层或物品挂载的 UI 背景和容器图。
- 处理:运行态解析 generated 图片时按源列表保留已有签名 URL只移除不再使用的源generated 图仍在换签时先隐藏对应物品,不显示默认积木,只有没有生成源或换签失败后才使用兜底。`preloadMatch3DGeneratedRuntimeAssets` 必须同时预加载物品视角、纯背景和容器 UI推荐流卡片摘要缺物品图或 UI 背景 / 容器字段时,进入运行态前补读 `getMatch3DWorkDetail`
- 验证:执行 `npm run test -- src/components/match3d-runtime/Match3DRuntimeShell.test.tsx -t "generated 图片素材换签未完成前不显示默认积木|同一批 generated 图片素材在重启 run 时保留已解析地址|运行态会从顶层 UI 资产加载背景和容器图"``npm run test -- src/services/match3dGeneratedModelCache.test.ts``npm run typecheck``npm run check:encoding`
- 关联:`src/components/match3d-runtime/Match3DRuntimeShell.tsx``src/services/match3dGeneratedModelCache.ts``src/components/platform-entry/PlatformEntryFlowShellImpl.tsx``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 抓大鹅容器参考图必须走 edits 并接管棋盘外观
@@ -694,7 +702,7 @@
- 原因:`/v1/images/generations``image` 数组更适合弱参考文生图,难以稳定锁定大尺寸轻俯视容器构图;即使生成了容器图,如果运行态继续保留默认 `rounded-full` 锅壳和 `overflow-hidden`,生成图也会被默认视觉覆盖或裁掉。
- 处理:抓大鹅 `1:1` 容器 UI 图必须用 VectorEngine `POST /v1/images/edits` multipart`public/match3d-background-references/pot-fused-reference.png` 作为 `image` part 上传;共享 GPT-image-2 HTTP client 承载 multipart 时强制 HTTP/1.1。`Match3DRuntimeShell` 在容器图换签并成功加载后,把棋盘外壳切为透明和 `overflow-visible`,只在容器缺失或加载失败时使用默认圆形容器。
- 验证:执行 `cargo test -p api-server vector_engine --manifest-path server-rs/Cargo.toml``cargo test -p api-server match3d_background --manifest-path server-rs/Cargo.toml``npm run test -- src/components/match3d-runtime/Match3DRuntimeShell.test.tsx src/components/match3d-result/Match3DResultView.test.tsx`;真实联调看容器生成请求是否命中 `/v1/images/edits`,局内 `match3d-container-image` 是否渲染且 `match3d-board` 不再含默认 `rounded-full`
- 关联:`server-rs/crates/api-server/src/openai_image_generation.rs``server-rs/crates/api-server/src/match3d.rs``src/components/match3d-runtime/Match3DRuntimeShell.tsx``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 抓大鹅结果页音频试听也要先换签
@@ -702,7 +710,7 @@
- 原因:结果页试听控件和运行态一样运行在浏览器里,不能直接读取 generated 私有对象;只在运行态换签会造成“运行态可能有声,结果页不能预览”的割裂。
- 处理:结果页音频控件统一通过 `useResolvedAssetReadUrl` / `/api/assets/read-url` 取得签名 URL 后再传给 `<audio>`;换签失败时只显示“音频已绑定”,不要回退请求裸 generated path。
- 验证:`npm run test -- src/components/match3d-result/Match3DResultView.test.tsx` 覆盖背景音乐和点击音效试听使用签名 URL。
- 关联:`src/components/match3d-result/Match3DResultView.tsx``src/services/assetReadUrlService.ts``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 法律文档弹窗通过 portal 挂载时要显式带平台主题
@@ -717,7 +725,7 @@
- 原因:完成回调用 `selectionStageRef.current` 判断用户是否仍在生成页;如果执行 compile 前只调用 `setSelectionStage('*-generating')`action 很快返回时 ref 仍可能是旧 stage。
- 处理:进入各玩法生成页时同步写 `selectionStageRef.current = '*-generating'`,再调用 `setSelectionStage('*-generating')`。这不是为渲染服务,而是给同一异步链路里的完成回调提供即时事实。
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx` 覆盖抓大鹅和拼图生成后自动试玩 / 返回结果页。
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 微信支付回调验签不要用商户私钥
@@ -726,7 +734,7 @@
- 处理api-server 真实微信支付配置同时需要商户私钥与微信平台公钥:`WECHAT_PAY_PRIVATE_KEY_*` 用于签名,`WECHAT_PAY_PLATFORM_PUBLIC_KEY_*``WECHAT_PAY_PLATFORM_SERIAL_NO` 用于通知验签,`WECHAT_PAY_API_V3_KEY` 只用于解密通知 resource。支付成功后只通过通知里的 `out_trade_no` 确认本地 pending 订单,并保存 `transaction_id``profile_recharge_order.provider_transaction_id`
- APIv3 通知成功应答使用 HTTP `204 No Content`,不要沿用 V2 XML 成功报文;失败仍返回 4XX/5XX 让微信重试。
- 验证mock 通知测试只能覆盖本地回调推进;真实环境还需用微信支付平台公钥、真实通知头和 API v3 密钥验证签名与解密链路。
- 关联:`server-rs/crates/api-server/src/wechat_pay.rs``docs/technical/MY_TAB_ACCOUNT_RECHARGE_IMPLEMENTATION_2026-04-25.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 微信支付 JSAPI 下单必须显式带 User-Agent
@@ -734,7 +742,7 @@
- 原因:`reqwest` 请求即使已设置 `Accept: application/json`,也不会默认附带业务侧 `User-Agent`;微信支付网关会校验这两个头。
- 处理:`api-server` 的 JSAPI 下单请求统一通过 `with_wechat_pay_jsapi_headers(...)` 设置 `Accept: application/json``Content-Type: application/json``User-Agent: Genarrative-WechatPay/1.0`
- 验证:执行 `cargo test -p api-server jsapi_order_request_sets_wechat_required_http_headers --manifest-path server-rs/Cargo.toml`
- 关联:`server-rs/crates/api-server/src/wechat_pay.rs``docs/technical/MY_TAB_ACCOUNT_RECHARGE_IMPLEMENTATION_2026-04-25.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 后台表查询展示 SpacetimeDB 枚举时不要套用 Option 解码
@@ -742,7 +750,7 @@
- 原因SpacetimeDB HTTP SQL 对无载荷枚举会返回 SATS 形态 `[variant_index, []]`;后台通用 normalizer 曾把任何 `[0, value]` 都当作 `Option::Some(value)` 展开,导致 `[0, []]` 最终只剩 `[]`
- 处理:通用表查询解析应先按表名和列名识别已知业务枚举,再落回 Option / Timestamp 通用展开;例如 `profile_recharge_order.kind` 映射为 `points` / `membership``profile_recharge_order.status` 映射为 `pending` / `paid` / `failed` / `closed` / `refunded`
- 验证:执行 `cargo test -p api-server admin_database -- --nocapture`,并确认后台详情弹层的 `raw` 与表格 `cells` 都显示业务字符串。
- 关联:`server-rs/crates/api-server/src/admin.rs``docs/technical/ADMIN_DATABASE_TABLE_QUERY_2026-05-08.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 抓大鹅历史草稿外部 Rodin GLB 链接必须转存后再试玩或发布
@@ -750,7 +758,7 @@
- 原因:历史结果页手动 `重新生成` 会把 Hyper3D/Rodin 的外部 CDN 下载链接直接保存到 `generatedItemAssets[].modelSrc`,同时 `modelObjectKey` 为空。外部链接可能过期、跨域、返回 HTML 错误页或非 GLB 内容;前端预览和运行态不能把它当作稳定私有资产。
- 处理:该问题只适用于旧数据。结果页发现 `status = model_ready``modelSrc = https://...` 且无 `modelObjectKey` 时,可调用 `POST /api/creation/match3d/works/{profileId}/generated-models` 做一次性转存;新草稿和批量新增不得继续生成或依赖 GLB。若历史半修复数据同时保留外部 `modelSrc` 和平台 `modelObjectKey`,旧模型预览读取层优先用 `modelObjectKey`
- 验证:`npm run test -- src\components\match3d-result\Match3DResultView.test.tsx``npm run test -- src\components\match3d-runtime\Match3DRuntimeShell.test.tsx``npm run test -- src\components\rpg-entry\RpgEntryFlowShell.agent.interaction.test.tsx``cargo test -p api-server match3d_model_download --manifest-path server-rs\Cargo.toml`,并检查修复后响应中的 `generatedItemAssets[].modelObjectKey` 不为空。
- 关联:`server-rs/crates/api-server/src/match3d.rs``src/components/match3d-result/Match3DResultView.tsx``src/components/match3d-result/Match3DModelPreview.tsx``src/components/match3d-runtime/Match3DPhysicsBoard.tsx``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 抓大鹅难度配置的物品种类和消除次数必须分离
@@ -758,7 +766,7 @@
- 原因:旧运行态把消除次数和类型数量绑在一起,结果页文案又同时展示“素材图片 / 局内类型”,导致前端、发布校验和 run start 口径不一致。
- 处理:统一使用 `物品种类` 口径:轻松 3、标准 9、进阶 15、硬核 21历史 `clearCount=20` 且难度为硬核的运行态按新硬核升为 21 组三消,避免 20 组却要求 21 种素材。发布前按 `image_ready` 且有 `imageViews[]``imageSrc/imageObjectKey` 的生成素材数量阻断不足难度;试玩不阻断,但通过 `itemTypeCountOverride` 自动降到已生成 2D 素材数量。重启从已有 run 快照反推实际物品种类,保持同一局重开不变。
- 验证:`npm run test -- src\components\match3d-result\Match3DResultView.test.tsx``cargo test -p module-match3d --manifest-path server-rs\Cargo.toml`,涉及发布 reducer 时补跑 `cargo test -p spacetime-module match3d --manifest-path server-rs\Cargo.toml`
- 关联:`src/components/match3d-result/Match3DResultView.tsx``src/services/match3d-runtime/match3dRuntimeClient.ts``server-rs/crates/module-match3d/src/application.rs``server-rs/crates/spacetime-module/src/match3d/mod.rs``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 抓大鹅标签清洗不要把 `3D素材` 当编号剥掉
@@ -774,7 +782,7 @@
- 原因:素材 sheet 可能是“每格内部绿幕、整张图外圈近白底”,内部绿幕不一定连通到 sheet 外边缘;旧 flood fill 只从外边缘找背景会漏掉这种绿幕块。白底抗锯齿如果不纳入抠像和边缘去污染,也会随裁剪输出成一圈白边。即使顺序已是先整张 sheet 去绿再裁剪,较厚的半透明或混色软绿边仍可能低于高置信绿幕阈值,被当作前景带进独立 PNG。
- 处理:`api-server``slice_match3d_material_sheet` 必须先在整张 sheet 上做透明背景后处理:外边缘连通绿幕/近白底清 alpha非连通但高置信纯绿块也清 alpha沿整张 sheet 透明背景继续吃掉软绿边,边缘近白和绿幕抗锯齿做透明或去污染;同时保护不够纯的绿色主体像素。不要改成先裁剪单格再去绿。
- 验证:`cargo test -p api-server match3d_material_sheet_slicing --manifest-path server-rs\Cargo.toml` 覆盖非连通绿幕、白边、贴边主体保留和固定 5x5 切图。
- 关联:`server-rs/crates/api-server/src/match3d.rs``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 抓大鹅物品详情大方格只做单张大图查看
@@ -782,7 +790,7 @@
- 原因:旧预览把上方区域当作横向视角带,当前焦点只是带内缩略图的一张,视觉上不是“详细查看物品形象”的大图。
- 处理:上方方格只渲染当前选中的单张大图,使用 `object-contain` 和少量内边距放大查看;底部缩略图栏负责切换视角,缩略图可以保留选中态边框,但上方大图不渲染焦点内框或缩略图容器边框。
- 验证:`npm run test -- src/components/match3d-result/Match3DResultView.test.tsx` 覆盖上方大图、底部缩略图和视角切换。
- 关联:`src/components/match3d-result/Match3DResultView.tsx``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 草稿页卡片有真实素材但仍显示黑卡先查摘要字段
@@ -790,7 +798,7 @@
- 原因:拼图列表摘要若不下发 `levels`,前端拿不到关卡 `coverImageSrc` / 候选图;抓大鹅列表摘要若只提供公开 URL、不保留 `generatedBackgroundAsset``generatedItemAssets` 中的 object key前端无法换签读取私有生成图。卡片封面组件如果自带暗色默认背景也会让兜底失败时看起来仍是黑卡。
- 处理:拼图 `map_puzzle_work_summary_response` 必须保留 `levels`;草稿页优先用关卡 `coverImageSrc`,再用候选图。抓大鹅货架封面解析必须读取 `backgroundImageObjectKey``generatedBackgroundAsset.imageObjectKey/containerImageObjectKey``generatedItemAssets[].imageObjectKey``imageViews[].imageObjectKey`。图片渲染统一交给 `ResolvedAssetImage` 换签,并给卡片传入玩法参考图与暖色底兜底。
- 验证:执行 `npm run test -- src/components/custom-world-home/creationWorkShelf.test.ts src/components/custom-world-home/CustomWorldCreationHub.test.tsx src/hooks/useResolvedAssetReadUrl.test.tsx``cargo test -p api-server puzzle_work_summary_response_keeps_levels_for_shelf_cover --manifest-path server-rs\Cargo.toml``npm run typecheck`
- 关联:`src/components/custom-world-home/creationWorkShelf.ts``src/components/CustomWorldCoverArtwork.tsx``server-rs/crates/api-server/src/puzzle.rs``docs/technical/CREATION_WORK_SHELF_UNIFICATION_2026-04-25.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 用户标签不要直接外显SpacetimeDB Vec 字段不要写 default 宏
@@ -798,7 +806,7 @@
- 原因SpacetimeDB 的 table default 宏会走编译期常量求值,不能直接使用有析构逻辑的堆分配类型默认值。
- 处理:`user_account.user_tags` 使用 `Option<Vec<String>>` + `#[default(None::<Vec<String>>)]` 表达数据库默认空,业务层统一把 `None` 归一化为空数组;邀请码授予标签复用 `metadata_json.userTags` 存储和解析,不再新增独立 Vec 列。用户标签原始值不得进入登录态、个人资料等通用响应,只能在明确业务白名单里投影,例如拼图排行榜 `visibleTags` 首版仅允许 `北科`
- 验证:`npm run spacetime:generate -- --rust-only` 能通过;`user_account` 旧迁移 JSON 缺字段时能导入,`profile_invite_code``metadata_json` 时按 `{}` 兼容。
- 关联:`docs/technical/USER_TAG_INVITE_AND_PUZZLE_LEADERBOARD_2026-05-10.md``docs/technical/SPACETIMEDB_TABLE_CATALOG.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 公开作品详情深链找不到作品不能停在空详情页
@@ -806,7 +814,7 @@
- 原因:旧恢复逻辑只覆盖 `/runtime/...`,没有覆盖 `/works/detail`。同时 `selectionStage === 'work-detail'``selectedPublicWorkDetail === null` 时没有兜底渲染,详情数据为空就只剩空页面。
- 处理:公开详情失效统一走 `resolveWorkNotFoundRecoveryAction(...)`,覆盖 `/works/detail``/gallery/puzzle/detail``/gallery/visual-novel/detail`;搜索失败和拼图详情 404 分支清理详情/运行态临时状态并回首页;`work-detail` 空数据阶段显示轻量读取态,避免异步间隙白屏。
- 验证:`npm run test -- src/routing/runtimeNotFoundRecovery.test.ts``npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "direct missing public work detail alert returns to platform home"`
- 关联:`docs/technical/PUBLIC_WORK_DETAIL_NOT_FOUND_RECOVERY_2026-05-11.md``src/routing/runtimeNotFoundRecovery.ts``src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 拼图 UI 背景只有 objectKey 时不要回退默认 UI
@@ -822,7 +830,7 @@
- 原因:首关命名 LLM 旧契约只返回 `levelName`,自动 UI 背景阶段只能用作品名、作品描述、关卡描述和标签拼接确定性兜底提示词;如果模型返回截断 JSON解析层还可能把 `levelNam` 这类字段名片段当作普通英文关卡名归一化通过。
- 处理:首关命名 LLM 契约必须同时返回 `{"levelName":"...","workDescription":"...","workTags":["..."],"uiBackgroundPrompt":"..."}`;解析层必须拒绝 `levelNam``levelName``workDescription``workTags``uiBackgroundPrompt` 等字段名片段作为关卡名。草稿自动 UI 背景生成优先使用该 AI 提示词,作品描述和 6 个作品标签默认填入草稿;视觉精修请求若返回新提示词或作品元信息则覆盖文本请求结果,否则保留文本请求结果。前端文本框只展示已保存的 `uiBackgroundPrompt` 或用户编辑值,字段为空时不展示本地兜底模板。
- 验证:执行 `cargo test -p api-server puzzle_level_naming_parser --manifest-path server-rs\Cargo.toml``cargo test -p api-server puzzle_first_level_name --manifest-path server-rs\Cargo.toml``cargo test -p api-server puzzle_initial --manifest-path server-rs\Cargo.toml``npm run test -- src/components/puzzle-result/PuzzleResultView.test.tsx`
- 关联:`server-rs/crates/api-server/src/prompt/puzzle/level_name.rs``server-rs/crates/api-server/src/puzzle.rs``src/components/puzzle-result/PuzzleResultView.tsx``docs/technical/PUZZLE_FORM_CREATION_FLOW_2026-04-29.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 拼图 / 抓大鹅 UI 背景重生成报 No such procedure 先查 SpacetimeDB 版本漂移
@@ -830,7 +838,7 @@
- 原因:`api-server``spacetime-client` 已按新 bindings 调用 procedure但目标 SpacetimeDB 数据库仍运行旧 wasm尚未导出钱包扣退费、拼图 UI 背景保存或 Match3D 写回相关 procedure。
- 处理:临时容错是把这类 `No such procedure` 当作后端版本漂移:泥点预扣阶段跳过扣费,图片已经生成但保存失败时返回本次内存快照 / 内存 profile避免草稿页直接报错。长期修复仍是发布最新 `spacetime-module`、重新生成 bindings并用 `spacetime describe` 或定向 smoke 确认 procedure 已导出。
- 验证:`cargo test -p api-server asset_operation_billing_skips_spacetime_connectivity_errors --manifest-path server-rs\Cargo.toml``cargo test -p api-server match3d_fallback_work_profile_keeps_generated_background_asset --manifest-path server-rs\Cargo.toml``npm run api-server` 后检查 `/healthz`
- 关联:`server-rs/crates/api-server/src/asset_billing.rs``server-rs/crates/api-server/src/match3d.rs``docs/technical/PUZZLE_FORM_CREATION_FLOW_2026-04-29.md``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 拼图合并块拖起后原位置出现红色块先查选中态泄漏
@@ -838,7 +846,7 @@
- 原因:合并块拖拽的可见层来自 `mergedGroups` 绝对定位整体层,但 `pointerdown` 会同步写入 `selectedPieceId`;若棋盘格里的底层单块 DOM 先匹配选中态,再匹配合并态,整体层移开后就会露出单块选中填充色。
- 处理:合并格底层 DOM 只作为透明定位占位,`isSelected` 必须排除 `isMerged`;合并格样式优先级高于单块选中态。
- 验证:运行 `npm run test -- src/components/puzzle-runtime/PuzzleRuntimeShell.test.tsx -t "拖拽合并大块时底层单格不显示选中色块"`,并确认合并块拖拽时底层 `[data-piece-id]` 仍为 `puzzle-runtime-piece--merged`
- 关联:`src/components/puzzle-runtime/PuzzleRuntimeShell.tsx``src/components/puzzle-runtime/PuzzleRuntimeShell.test.tsx``docs/technical/PUZZLE_FORM_CREATION_FLOW_2026-04-29.md`
- 关联:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## 拼图历史图片列表不要把账号归属当图片名
@@ -846,4 +854,4 @@
- 原因:`/api/assets/history?kind=puzzle_cover_image` 返回的 `ownerLabel` 是资产归属账号,不是图片标题;`createdAt` 可能是 SpacetimeDB / shared-kernel 秒级时间字符串,不能只用浏览器 `new Date(value)` 解析。历史图的 `imageSrc``/generated-*` 私有兼容路径,浏览器预览必须换签。
- 处理:前端标题和选中标签从 `imageSrc` 路径末尾推导,例如 `image.png`;时间解析兼容 ISO 与 `1713686400.000000Z`;创作页主图、历史列表图和结果页参考图继续用 `ResolvedAssetImage`,提交给后端时仍保留原始 `imageSrc`
- 验证:`npm run test -- src/components/puzzle-agent/PuzzleAgentWorkspace.interaction.test.tsx src/components/puzzle-result/PuzzleResultView.test.tsx`,并执行 `npm run check:encoding`
- 关联:`src/services/puzzle-works/puzzleHistoryAsset.ts``src/components/puzzle-agent/PuzzleHistoryAssetPickerDialog.tsx``docs/technical/ASSET_HISTORY_PUZZLE_COVER_KIND_FIX_2026-04-27.md`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`

View File

@@ -1,143 +1,55 @@
# Genarrative 项目共享概览
> 用途:给团队成员本地 Hermes 快速建立项目背景。内容应保持高层、稳定、可验证;细节以代码、`README.md`、`AGENTS.md` 和 `docs/` 最新文档为准。
更新时间:`2026-05-15`
## 一句话定位
Genarrative / AI Native Visual RPG 是一个以 **AI 叙事 + 本地规则 + 像素演出** 为核心的视觉 RPG 与 AI 原生游戏创作平台原型
Genarrative / 陶泥儿是一个 AI 原生互动内容与小游戏平台,把 AI 创作、作品草稿、公开分发、运行态、用户账号、钱包任务、后台管理和小程序外壳收在同一套工程中
项目当前不只是单一 RPG demo而是在同一平台内同时承载
## 当前主要能力
- RPG / 自定义世界创作与运行时
- 拼图玩法创作与运行时
- 大鱼吃小鱼玩法链路
- 抓大鹅 Match3D 玩法链路
- 用户账号、存档、钱包、任务、埋点、后台管理与生产部署链路
- RPG / 自定义世界创作与运行时
- 拼图玩法创作、草稿、发布、运行态和排行榜。
- 抓大鹅 Match3D 创作、2D 多视角素材生成、发布和运行态。
- 大鱼吃小鱼、方洞挑战、视觉小说、汪汪声浪和儿童向寓教于乐玩法。
- 账号、短信 / 密码 / 微信登录、个人资料、任务、钱包、邀请码、充值、反馈、法律信息和后台管理。
## 已具备的主要能力
## 当前入口
来自根目录 `README.md` 的当前主能力:
- 主站:`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`
- 世界与角色选择
- AI 剧情推进与流式对话
- 战斗演出、NPC 战斗、切磋
- NPC 交易、送礼、求助、招募
- 宝藏交互
- 同伴跟随与战斗
- 游戏主流程内嵌的角色资产工坊、自定义世界实体编辑与角色形象编辑
- 自动存档与继续游戏
移动端一级 Tab`推荐 / 发现 / 创作 / 草稿 / 我的`
## 当前前端与平台入口
## 当前后端路线
- 主站默认地址:`http://127.0.0.1:3000`
- 后台可从 `http://127.0.0.1:3000/admin/` 进入,也可直连 `http://127.0.0.1:3102`
- 主站、后台和 Rust 后端联调默认走 `npm run dev`
- 只启动前端页面可用 `npm run dev:web`,默认代理到本地 Rust `api-server`
- 后台管理独立前端工程为 `apps/admin-web`,管理端只做表现,数据和写操作走 `server-rs``/admin/api/*`
## 当前后端唯一落地口径
后端主线已经切到:
唯一有效后端路线:
```text
server-rs + Axum + SpacetimeDB
```
当前唯一有效后端方向
职责边界
- HTTP 门面Rust `api-server` / Axum
- 实时状态与业务真相:`server-rs/crates/spacetime-module` / SpacetimeDB
- 共享领域与契约:`server-rs` 多 crate 分层维护
- 前端职责:表现、输入采集、临时 UI 状态、服务端结果渲染
- `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` 口径,以及前端承接正式业务真相的路线。
- `server-node` / Express / PostgreSQL 正式后端路线
- Go 服务端试验路线
- 浏览器侧承担正式运行时逻辑、正式生成编排或正式数据真相的路线
## 当前文档入口
## server-rs DDD 分层边界
- `docs/README.md`
- `docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
- `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
- `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
DDD 分层边界以 `docs/technical/SERVER_RS_DDD_FULL_REFACTOR_2026-04-28.md``SERVER_RS_DDD_PARALLEL_TASKLIST_2026-04-29.md``AGENTS.md` 为准:
- `module-*`:领域模型、命令、应用编排结果、领域事件、领域错误
- `spacetime-module`SpacetimeDB 表、reducer、procedure、事务 adapter、mapper
- `spacetime-client`:后端访问 SpacetimeDB 的 typed facade
- `api-server`HTTP / SSE / BFF adapter 与外部平台服务编排
- `platform-*`LLM、OSS、SMS、微信等外部副作用
- `shared-contracts`:前后端 DTO 与公开协议
- `shared-kernel`:跨纯领域 crate 复用的基础字符串、ID、时间和归一化能力
- `tests-support``server-rs` workspace 共享测试支撑
## 当前 Rust workspace 主要 crate
`server-rs/Cargo.toml` 为准,当前主要成员包括:
- 业务领域:`module-ai``module-assets``module-auth``module-big-fish``module-combat``module-inventory``module-custom-world``module-match3d``module-npc``module-puzzle``module-progression``module-quest``module-runtime``module-runtime-story``module-runtime-item``module-story`
- 平台副作用:`platform-oss``platform-auth``platform-llm`
- 共享层:`shared-contracts``shared-kernel``shared-logging`
- SpacetimeDB 接入:`spacetime-client``spacetime-module`
- HTTP 服务与测试:`api-server``tests-support`
注意:`server-rs` 的默认 `cargo build` 只构建 `crates/api-server`,本地 SpacetimeDB 模块发布继续走 `spacetime publish --module-path ... --build-options="--debug"`
Cargo 依赖口径:第三方依赖版本和 workspace 内部 crate path 统一维护在 `server-rs/Cargo.toml``[workspace.dependencies]`,成员 crate 默认继承 workspace 依赖,只保留自身 `features``optional` 或 target-specific 差异。
Rust 加密摘要依赖口径:新代码不再引入 `sha1`OSS V4 签名、阿里云 OpenAPI V3 签名和 refresh session token 摘要统一使用 `sha2::Sha256`
## SpacetimeDB 表域总览
`docs/technical/SPACETIMEDB_TABLE_CATALOG.md` 为持续维护入口。当前表域包括:
- 运维迁移:`database_migration_operator``database_migration_import_chunk`
- 认证:`auth_store_snapshot``user_account``auth_identity``refresh_session`
- 运行时档案:`runtime_setting``runtime_snapshot``user_browse_history``profile_dashboard_state``profile_wallet_ledger``analytics_date_dimension``tracking_event``tracking_daily_stat``profile_task_config``profile_task_progress``profile_task_reward_claim`
- RPG 运行时:`story_session``story_event``npc_state``inventory_slot``battle_state``treasure_record``quest_record``quest_log``player_progression``chapter_progression`
- 世界创作:`custom_world_profile``custom_world_session``custom_world_agent_session``custom_world_agent_message``custom_world_agent_operation``custom_world_draft_card``custom_world_gallery_entry`
- 拼图:`puzzle_agent_session``puzzle_agent_message``puzzle_work_profile``puzzle_event``puzzle_runtime_run``puzzle_leaderboard_entry`
- 抓大鹅 Match3D`match3d_agent_session``match3d_agent_message``match3d_work_profile``match3d_runtime_run`
- 大鱼吃小鱼:`big_fish_creation_session``big_fish_agent_message``big_fish_asset_slot``big_fish_event``big_fish_runtime_run`
- 资产:`asset_object``asset_entity_binding``asset_event`
- AI 任务:`ai_task``ai_task_stage``ai_text_chunk``ai_result_reference``ai_task_event`
## 产品命名与运营口径
`docs/technical/PRODUCT_NAMING_BAIMENG_RENAME_2026-05-01.md` 为准:
- 产品展示名:陶泥儿
- 消费单位:泥点
- 公开账号标识:陶泥号
- 创作侧称谓:陶泥儿主
个人任务与埋点系统首版边界:
- 埋点原始事实写入 `tracking_event`
- 聚合投影写入 `tracking_daily_stat`
- 任务配置写入 `profile_task_config`
- 任务进度写入 `profile_task_progress`
- 领奖记录写入 `profile_task_reward_claim`
- 钱包流水写入 `profile_wallet_ledger`
- “星光”奖励复用现有“泥点”钱包,不新增第二种货币
- 个人任务 scope 首版仅支持 `user`
## 关键文档入口
- 根项目说明:`README.md`
- 项目总约束:`AGENTS.md`
- 文档总入口:`docs/README.md`
- 经验沉淀:`docs/experience/README.md`
- 审计与复盘:`docs/audits/README.md`
- 系统设计:`docs/design/README.md`
- 技术方案:`docs/technical/README.md`
- 规划与优先级:`docs/planning/README.md`
- 参考目录:`docs/reference/README.md`
- 埋点查询:`docs/tracking/README.md`
- 运营查询:`docs/operations/README.md`
- 后端当前基线:`docs/technical/CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md`
- 后端 DDD 总纲:`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`
- SpacetimeDB 表结构变更约束:`docs/technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md`
- SpacetimeDB 表目录:`docs/technical/SPACETIMEDB_TABLE_CATALOG.md`
- Rust workspace 依赖集中配置:`docs/technical/RUST_WORKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md`
- 生产部署计划:`docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`
旧 PRD、设计、审计、阶段计划和技术流水账已融合进上述文档没有融合的旧材料不再作为实现依据。

View File

@@ -1,100 +1,64 @@
# 团队协作约定
> 用途:约定 3 名开发人员在各自本地 Hermes 中协作开发、共享项目记忆的方式。
更新时间:`2026-05-15`
## 基本模式
- 每位开发人员在自己的电脑上使用本地 Hermes。
- 每位开发人员本地拉取同一个项目仓库,独立修改代码、运行测试、提交分支
- 团队共享内容优先放在本仓库 `.hermes/` `docs/` 中,通过 Git 同步
- 不共享个人 `~/.hermes` 目录。
## 共享与禁止共享
推荐共享:
- `.hermes/shared-memory/` 团队级长期记忆
- `.hermes/plans/` 阶段性实施计划
- `.hermes/todos/` 已确定需要执行、但尚未进入实施的共享 TODO 计划
- `.hermes/skills/` 未来可复用仓库级 skills
- `docs/` 中 PRD、设计、技术、经验、审计、查询手册
- `AGENTS.md` 项目级 Agent 约束
禁止提交:
- 个人 `~/.hermes/config.yaml`
- 个人 `~/.hermes/.env`
- 个人 `~/.hermes/sessions/`
- API Key、Token、Cookie、认证文件
- 个人本地私密路径和个人隐私信息
- 构建产物、日志、缓存、数据库 dump
- 团队共享内容放在仓库 `.hermes/``docs/` 中,通过 Git 同步
- 不共享个人 `~/.hermes` 目录、密钥、会话、Token、Cookie、认证文件、本地私密路径或构建产物
## 开发前
1. 拉取最新代码。
2. 阅读 `AGENTS.md`
3. 阅读 `.hermes/shared-memory/` 中与任务相关的文件。
4. 阅读 `docs/README.md`任务相关分类 README
5. 阅读对应 PRD、设计、技术、经验或审计文档。
6. 如果文档不足以指导编码,先补充或修正文档。
4. 阅读 `docs/README.md` 4 份当前融合文档中与任务相关的部分
5. 如果文档不足以指导编码,先补充或修正文档。
当前文档入口:
- `docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
- `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
- `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## 开发中
- 保持修改范围聚焦,不做无关重构。
- 复用、修改、扩展现有系统优先,避免新建重复系统或页面
- 新增 Markdown 文档时,文件名必须以分类标签开头,格式为 `【标签名】中文标题-日期.md`;只在任务需要时重命名历史文档,避免无关大 diff
- 优先复用、修改、扩展现有系统、页面、组件和弹层
- 新增 Markdown 文档时,文件名必须以分类标签开头,格式为 `【标签名】中文标题-YYYY-MM-DD.md`
- 阶段性计划、一次性 TODO 和已关闭实验不要长期沉淀为仓库文档;仍有效内容合并进当前 `docs/``.hermes/shared-memory/`
- 涉及中文文本时注意 UTF-8 编码和乱码排查。
- 涉及后端时遵循 DDD 分层,不把业务真相下沉到前端或临时兼容层。
- `maincloud` / `Maincloud` / `MAINCLOUD` 相关代码、脚本、测试、环境变量、命令和文档要求均视为历史残留,禁止新增、运行或引用API smoke 统一使用 `npm run api-server``/healthz`
- 涉及 SpacetimeDB 表结构、发布或迁移时,先看 `SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md` `SPACETIMEDB_TABLE_CATALOG.md`
- 涉及生产发布、服务器配置、Jenkins Job 重建或回滚时,先看 `PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`
- `maincloud` / `Maincloud` / `MAINCLOUD` 相关代码、脚本、测试、环境变量、命令和文档要求均视为历史残留,禁止新增、运行或引用。
- API smoke 统一使用 `npm run api-server` `/healthz`
- 涉及 SpacetimeDB 表结构、发布或迁移时,先看当前后端架构文档的 schema 变更规则和表目录
- 涉及生产发布、服务器配置、Jenkins Job 重建或回滚时,先看当前开发运维文档。
## 开发后
1. 运行与修改范围匹配的测试或验证命令。
2. 更新相关 `docs/` 文档。
3. 新增或沉淀 Markdown 文档时,确认文件名已使用 `【标签名】` 前缀
4.产生长期有效知识,更新 `.hermes/shared-memory/`
5. 若形成可复用流程,考虑沉淀到 `.hermes/skills/`
6. 在提交信息中区分代码变更与文档/记忆变更。
## 文档阅读顺序
通用任务建议:
1. `README.md`
2. `AGENTS.md`
3. `.hermes/shared-memory/`
4. `docs/README.md`
5. `docs/experience/README.md`
6. `docs/audits/README.md`
7. 任务所属分类:`docs/design/``docs/technical/``docs/planning/``docs/prd/``docs/reference/``docs/tracking/``docs/operations/`
后端任务建议:
1. `docs/technical/CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md`
2. `docs/technical/SERVER_RS_DDD_FULL_REFACTOR_2026-04-28.md`
3. `docs/technical/SERVER_RS_DDD_G1_CONTRACT_AND_ROUTE_MATRIX_2026-04-29.md`
4. `docs/technical/SERVER_RS_DDD_PARALLEL_TASKLIST_2026-04-29.md`
5. `docs/technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md`
6. `docs/technical/SPACETIMEDB_TABLE_CATALOG.md`
7. `docs/technical/MAINCLOUD_REFERENCE_REMOVAL_POLICY_2026-05-06.md`
2. 更新相关 `docs/` 当前文档。
3. 若产生长期有效知识,更新 `.hermes/shared-memory/`
4.形成可复用流程,考虑沉淀到 `.hermes/skills/`
5. 提交信息区分代码变更、文档变更和共享记忆变更
## 共享记忆更新准则
适合更新:
- 新增稳定架构约定
- 新增长期开发流程
- 已验证的踩坑和排障步骤
- 重要接口契约变化
- 团队协作规范变化
- 文档索引或阅读顺序变化
- 稳定架构约定
- 长期开发流程
- 已验证的踩坑和排障步骤
- 重要接口契约变化
- 团队协作规范变化
- 当前文档索引或阅读顺序变化
不适合更新:
- 一次性临时计划
- 未验证猜测
- 个人偏好和个人路径
- 敏感信息
- 大段聊天记录
- 一次性临时计划
- 未验证猜测
- 个人偏好和个人路径
- 敏感信息
- 大段聊天记录

View File

@@ -280,26 +280,26 @@ fn anonymous_user_cannot_publish_generated_draft() {
| 产物类型 | 推荐路径 | 适用场景 |
| --- | --- | --- |
| 实施前分析 / 临时计划 | `.hermes/plans/<task-name>-bdd-scenarios.md` | 某次 Hermes 开发任务前,用于澄清行为、拆测试、辅助实现;不一定作为长期产品依据。 |
| 正式产品验收 / PRD 场景 | `docs/prd/<FEATURE>_BDD_YYYY-MM-DD.md` | 产品、测试、开发都需要长期参考的验收标准、用户故事、功能边界。 |
| 技术/API/领域行为场景 | `docs/technical/<FEATURE>_BDD_YYYY-MM-DD.md` | 后端 API、领域规则、状态机、SpacetimeDB reducer/table、SSE/异步任务、埋点副作用。 |
| 实施前分析 / 临时计划 | 当前任务说明或 `.tmp/<task-name>-bdd-scenarios.md` | 某次 Hermes 开发任务前,用于澄清行为、拆测试、辅助实现;不作为长期产品依据。 |
| 正式产品验收 / PRD 场景 | 当前 `docs/` 融合文档,必要时新增 `docs/【产品验收】<功能名>BDD场景-YYYY-MM-DD.md` | 产品、测试、开发都需要长期参考的验收标准、用户故事、功能边界。 |
| 技术/API/领域行为场景 | 当前 `docs/` 融合文档,必要时新增 `docs/【技术验收】<功能名>BDD场景-YYYY-MM-DD.md` | 后端 API、领域规则、状态机、SpacetimeDB reducer/table、SSE/异步任务、埋点副作用。 |
| 自动化 Gherkin feature 文件 | `tests/features/*.feature``e2e/features/*.feature` | 项目已接入 Cucumber/Playwright BDD 等 Gherkin runner 时。未接入前不要随意新建测试 runner 目录。 |
| 稳定流程或团队经验 | `.hermes/shared-memory/``.hermes/skills/` | 不是某个功能验收,而是长期可复用的团队流程、坑点、执行规范。 |
默认规则:
1. 用户只说“先用 BDD 梳理一下/写场景/写 Gherkin”默认写到 `.hermes/plans/<task-name>-bdd-scenarios.md`
2. 用户说“正式验收标准/PRD/产品文档/给测试验收”,写到 `docs/prd/<FEATURE>_BDD_YYYY-MM-DD.md`
3. 用户说“API 行为/后端规则/状态机/埋点/异步任务/SpacetimeDB”写到 `docs/technical/<FEATURE>_BDD_YYYY-MM-DD.md`
1. 用户只说“先用 BDD 梳理一下/写场景/写 Gherkin”默认在当前任务上下文中输出;需要文件时写到 `.tmp/<task-name>-bdd-scenarios.md`
2. 用户说“正式验收标准/PRD/产品文档/给测试验收”,优先合并到当前 `docs/` 融合文档;无法容纳时新增带 `【产品验收】` 标签的 Markdown
3. 用户说“API 行为/后端规则/状态机/埋点/异步任务/SpacetimeDB”优先合并到当前后端架构或开发运维文档;无法容纳时新增带 `【技术验收】` 标签的 Markdown
4. 用户明确要求“可执行 feature 文件”且项目已有 runner再写 `.feature` 文件;否则先写 Markdown BDD 文档,并在测试映射中标注未来自动化落点。
5. 如果 BDD 场景会作为编码依据,文档中必须包含“测试映射”表,标注场景要落到哪些测试文件。
命名建议:
```text
.hermes/plans/profile-feedback-bdd-scenarios.md
docs/prd/PROFILE_FEEDBACK_BDD_2026-05-11.md
docs/technical/WORK_PLAY_TRACKING_BDD_2026-05-11.md
.tmp/profile-feedback-bdd-scenarios.md
docs/【产品验收】帮助与反馈BDD场景-2026-05-11.md
docs/【技术验收】作品游玩埋点BDD场景-2026-05-11.md
tests/features/profile-feedback.feature
e2e/features/invite-code.feature
```
@@ -308,9 +308,9 @@ e2e/features/invite-code.feature
除 BDD/Gherkin 场景外,相关配套内容可放在:
- 实施计划:`.hermes/plans/<task-name>.md`
- 产品/验收文档:`docs/prd/<FEATURE>_PRD_YYYY-MM-DD.md`
- 技术设计:`docs/technical/<FEATURE>_TECHNICAL_YYYY-MM-DD.md`
- 实施计划:当前任务上下文或 `.tmp/<task-name>.md`
- 产品/验收文档:当前 `docs/` 融合文档,必要时新增 `docs/【产品验收】中文标题-YYYY-MM-DD.md`
- 技术设计:当前 `docs/` 融合文档,必要时新增 `docs/【技术方案】中文标题-YYYY-MM-DD.md`
- 共享经验或稳定流程:`.hermes/shared-memory/``.hermes/skills/`
BDD 文档建议包含:

View File

@@ -42,7 +42,7 @@ metadata:
### 1. 先补技术方案文档
项目要求工程修改前先检查/补充落地文档。若没有明确文档,先写到 `docs/technical/`,至少说明:
项目要求工程修改前先检查/补充落地文档。优先更新当前融合文档;后台、接口、表查询、埋点和运营查询通常落到 `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。只有现有文档无法容纳时,才新增 `docs/【标签名】中文标题-YYYY-MM-DD.md`,至少说明:
- 后台页面目标。
- 后端接口路径、鉴权、query/body、response。

View File

@@ -52,4 +52,4 @@ git diff --check
## 提交注意
- 不要提交 `.env.local``.env.secrets.local` 或任何 token/密码/连接串。
- 若工作区里有本地敏感文件,只提交明确改动的 Rust 文件和 `docs/technical/*` 文档。
- 若工作区里有本地敏感文件,只提交明确改动的 Rust 文件和当前 `docs/` 文档。

View File

@@ -15,9 +15,9 @@
## 关键文件
- `docs/technical/PROFILE_TASK_AND_TRACKING_SYSTEM_2026-05-03.md`
- 第 47 行左右写明:用户打开任务中心时后端幂等记录当日 `daily_login`;点击领取时校验进度和领奖记录
- 接口说明中写明 `GET /api/profile/tasks` 会读取任务中心并记录当日登录埋点
- `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
- 当前任务、钱包、埋点和运营查询口径统一维护在该文档
- 历史阶段文档曾记录过:用户打开任务中心时后端幂等记录当日 `daily_login`;点击领取时校验进度和领奖记录。若当前代码已变更,以代码和当前融合文档为准
- `server-rs/crates/api-server/src/runtime_profile.rs`
- `get_profile_task_center` 调用 `state.spacetime_client().get_profile_task_center(user_id)`
- `claim_profile_task_reward` 调用 `state.spacetime_client().claim_profile_task_reward(user_id, task_id)`
@@ -36,11 +36,7 @@
git grep -n "daily_login\|tracking_event\|get_profile_task_center\|claim_profile_task_reward" -- server-rs apps docs
```
2. 对照设计文档中的事件口径
```bash
sed -n '35,58p' docs/technical/PROFILE_TASK_AND_TRACKING_SYSTEM_2026-05-03.md
```
2. 对照当前开发运维文档中的任务、钱包、埋点和运营查询口径
3. 追 API handler 到 SpacetimeDB reducer

View File

@@ -75,7 +75,7 @@ metadata:
- refresh session 是否在 rotate 与 access token 签发成功后调用 helper。
- 失败策略是否只 warning、不阻断响应。
4. 如涉及 SpacetimeDB procedure/table/binding按项目 SpacetimeDB skills 与文档同步检查绑定生成、`migration.rs`、private table 限制。
5. 修改前补齐 `docs/technical/` 中对应方案/根因;修改后同步更新。
5. 修改前补齐当前 `docs/` 中对应方案/根因;修改后同步更新当前融合文档和必要的共享记忆
## 关键经验:已登录打开网页也要主动 refresh 才能写登录埋点

View File

@@ -71,7 +71,7 @@ metadata:
- `scripts/dev-stack-port-utils.mjs`
- `scripts/dev-rust-stack.sh`
- `scripts/dev-web-rust.mjs`
- `docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md`
- `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
- `.hermes/shared-memory/pitfalls.md`
2. 优先改公共端口工具,不要把端口探测逻辑复制到多个脚本。
3. 对 Bash 脚本只做局部补丁,避免整文件重写导致中文注释或换行大面积变化。
@@ -122,6 +122,6 @@ node scripts/dev-stack-port-utils.mjs resolve-dev-stack spacetime:127.0.0.1:0 ap
- [ ] `dev-rust-stack.sh` 通过 `bash -n`
- [ ] `npm run dev` / `npm run dev:rust` 的 SpacetimeDB、publish、api-server、主站 Vite、后台 Vite 都使用实际端口。
- [ ] `npm run dev:web` 在主站端口不可用时能切换到可用端口。
- [ ] 文档同步更新 `docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md`
- [ ] 文档同步更新 `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
- [ ] 长期踩坑同步更新 `.hermes/shared-memory/pitfalls.md`
- [ ] 修改中文文件后运行 `npm run check:encoding`

View File

@@ -4,7 +4,7 @@
## 推荐顺序
1. 先读仓库 `README.md``AGENTS.md`、相关 `/docs/technical``.hermes/plans`,确认当前阶段范围。
1. 先读仓库 `README.md``AGENTS.md` 和当前 `docs/` 融合文档,确认当前阶段范围。
2. 遵循 TDD先在 `server-rs/crates/module-runtime/tests/` 写纯函数测试,验证缺失类型/函数导致 RED。
3.`module-runtime/src/domain.rs` 增加领域类型,例如:
- `AnalyticsGranularity``day | week | month | quarter | year`
@@ -28,7 +28,7 @@
11.`api-server`
- `src/runtime_profile.rs`Query params / parser / handler / response builder。
- `src/app.rs`:挂路由,例如 profile 或 admin analytics endpoint选择路径前确认产品定位。
12. 最后更新 docs/plan,并确认 diff 不只是生成物。
12. 最后更新当前 `docs/` 文档和必要的 `.hermes/shared-memory/` 摘要,并确认 diff 不只是生成物。
## 验证命令示例

View File

@@ -61,8 +61,8 @@ metadata:
## 推荐实施顺序
1. 读取 `.hermes/plans/...` 或产品文档,确认入口、路由、页面行为。
2. 若现有文档不足,先在 `docs/prd/` 增加可编码落地的 PRD
1. 读取当前融合文档,确认入口、路由、页面行为。
2. 若现有文档不足,优先更新 `docs/【项目基线】当前产品与工程约束-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`;只有无法容纳时才新增 `docs/【标签名】中文标题-YYYY-MM-DD.md`
3. 增加 `SelectionStage``appPageRoutes` 映射,并先跑 `npm run typecheck`
4. 新建独立页面组件,尽量通过 props 暴露 `onBack`/`onSubmit`,避免直接耦合全局状态。
5.`RpgEntryHomeView.tsx` 增加入口 prop 与按钮。

View File

@@ -10,7 +10,8 @@
## 关键文件
- `docs/prd/PROFILE_FEEDBACK_ENTRY_PRD_2026-05-08.md`
- `docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
- `src/components/platform-entry/platformEntryTypes.ts`
- `src/routing/appPageRoutes.ts`
- `src/components/platform-entry/PlatformFeedbackView.tsx`

View File

@@ -1,15 +0,0 @@
# 项目共享 TODO 计划
本目录用于存放已经讨论定稿、需要后续执行,但当前尚未实施的项目级计划文档。
## 使用规则
- 每个 TODO 计划单独一个 Markdown 文件,文件名优先使用可检索的中文标题与日期。
- 每个 TODO 计划文件名必须以分类标签开头,格式为 `【标签名】中文标题-日期.md`
- 计划内容必须足够明确,后续开发者可以直接据此开始实施。
- 已执行完成的计划应迁移到对应 `docs/` 技术/规划文档,或在本文档中标记完成并移出 TODO 队列。
- 不在这里保存个人私密路径、密钥、临时聊天记录或未确认猜测。
## 当前待执行
- [【后端架构】api-server 能力模块化与图片资产 Adapter 收口计划](./【后端架构】api-server能力模块化与图片资产Adapter收口计划-2026-05-14.md)

View File

@@ -1,108 +0,0 @@
# api-server 能力模块化与生成资产 Adapter 完整收口计划
状态:待执行
## Summary
目标是把 `api-server` 从“超大 `app.rs` + 多个超大 handler 文件 + 多处生成资产重复链路”收成可长期维护的能力 Module 结构。
本计划完整覆盖:路由能力模块化、生成图片资产 Adapter、复杂媒体链路扩展、大 handler 瘦身、文档与验收。全程不改变 HTTP contract、DTO、SpacetimeDB schema、前端行为和计费语义除非某阶段文档先明确提出并单独批准。
## 文档交付
先新增一个总纲,再按阶段新增单独执行文档:
- 总纲:`docs/technical/【后端架构】api-server能力模块化与生成资产Adapter总纲-2026-05-14.md`
- 阶段 1`docs/technical/【后端架构】api-server路由能力模块化执行计划-2026-05-14.md`
- 阶段 2`docs/technical/【后端架构】生成图片资产Adapter收口执行计划-2026-05-14.md`
- 阶段 3`docs/technical/【后端架构】复杂媒体资产链路Adapter扩展计划-2026-05-14.md`
- 阶段 4`docs/technical/【后端架构】api-server大Handler瘦身执行计划-2026-05-14.md`
- 同步更新 `server-rs/crates/api-server/README.md``docs/technical/README.md`、本 TODO 文档。
## 阶段 0基线盘点与总纲冻结
- 盘点 `app.rs` 当前全部 route`admin/auth/assets/profile/creation/runtime/story/platform/internal` 分类形成 route inventory。
- 盘点 Big Fish、Square Hole、Custom World、Puzzle、Match3D、Visual Novel、音频、视频、GLB 的生成资产链路,标出 provider、下载方式、OSS prefix、asset kind、entity binding、计费位置、降级行为。
- 在总纲文档中冻结边界:`api-server` 只做 HTTP/SSE/BFF、鉴权、DTO 映射、平台服务编排;领域规则仍归 `module-*`SpacetimeDB 真相仍归 `spacetime-module`
退出条件:总纲和 inventory 能指导后续编码,不再只是一段方向描述。
## 阶段 1路由能力模块化完整收口
- 新增 `server-rs/crates/api-server/src/modules/`,每个能力 Module 暴露 `router(state) -> Router<AppState>`
- 第一批迁移低风险路由:`admin``auth``assets``profile``internal``health`
- 第二批迁移平台编排路由:`ai_tasks``llm``speech``wechat``creative_agent``visual_novel`、通用 audio。
- 第三批迁移玩法路由:`big_fish``square_hole``puzzle``match3d``custom_world``story`、runtime save/settings/chat/inventory。
- `app.rs` 最终只保留全局 middleware、TraceLayer、request context、tracking middleware、`.merge(modules::*::router(...))` 和少量顶层 glue。
- handler 实现第一阶段可以继续留在原文件;本阶段只改变路由装配位置,不混入业务重构。
验收route inventory 中所有原 route 仍存在;旧明确下线 route 继续 404`cargo test -p api-server app --manifest-path server-rs/Cargo.toml` 或等价 route 回归通过。
## 阶段 2稳定单图生成资产 Adapter 收口
- 新增 `modules/assets/generated_image_assets` 内部 ModuleInterface 覆盖“provider 生成 -> 下载/base64 解码 -> MIME/extension 归一 -> OSS private upload -> HEAD -> asset_object confirm -> entity binding”。
- Adapter 输入包含 provider、prompt、negative prompt、size、reference images、OSS prefix/path/file name、asset kind、entity kind/id、slot、owner/profile/source_job_id、metadata、可选透明背景后处理。
- Adapter 输出包含 `legacy_public_path``object_key``asset_object_id``mime_type``extension``task_id``actual_prompt`
- 首批迁移:
- Big Fish 正式图片:主图、动作图、舞台背景。
- Square Hole 图片:保留生成成功但入库失败时回退 Data URL。
- Custom World 场景图、自动草稿场景图、生成封面图。
- `asset_billing.rs` 仍由调用方显式包裹Adapter 不扣费、不退款、不读钱包。
验收:三类调用方都经过同一 Adapter删除旧重复 persist 函数后行为不变Big Fish、Square Hole、Custom World 定向测试通过。
## 阶段 3复杂媒体资产链路扩展
- 扩展 Adapter但不把玩法图像处理规则塞进公共 Interface。
- Puzzle
- 收口普通 generations、edits/multipart 生成结果的下载、OSS、asset object、binding。
- 拼图关卡 JSON 更新、参考图策略、UI 背景落位仍留在 Puzzle 编排层。
- Match3D
- APIMart material sheet、VectorEngine 背景/容器/封面生成接入统一入库能力。
- 5x5 切图、绿幕透明化、格内校准、批量新增补齐规则仍留在 Match3D 专属处理器。
- 音频:
- 评估是否抽 `generated_media_assets`,但背景音乐、点击音效的计费和落位语义不与图片 Adapter 混用。
- GLB/视频:
- 仅历史转存链路复用“OSS + asset_object + binding”底层持久化能力不恢复新草稿 GLB 生产。
验收Puzzle 与 Match3D 的 generated 私有资产仍通过 `/api/assets/read-url` 换签读取Match3D 不回退 Rodin/GLB音频试听和运行态仍可播放。
## 阶段 4超大 handler 能力内瘦身
- 在路由已模块化、资产 Adapter 已稳定后,再拆大文件,避免同时改 route 和业务实现。
-`match3d.rs``puzzle.rs``custom_world.rs``custom_world_ai.rs``big_fish.rs``square_hole.rs` 分别按能力拆:
- `router.rs` 只挂路由。
- `handlers.rs` 只做 Axum extract、鉴权、request/response。
- `application.rs` 做 api-server 层编排。
- `assets.rs` 只放玩法专属生成资产策略。
- `mapper.rs` 只做 DTO/record 映射。
- `errors.rs` 只做该能力错误映射。
- 不把领域规则留在 handler发现领域规则时只登记迁出候选不在本阶段直接扩大到 `module-*` 重构。
验收:每个原超大文件显著缩小;新文件按能力可读;定向玩法测试和全量 `api-server` 测试通过。
## 阶段 5清理、文档和最终验收
- 删除旧重复 helper、过时注释和已迁移的私有函数。
- 更新 `api-server` README目录规则、Router 暴露规则、Adapter 边界、禁止事项。
- 更新 `.hermes/shared-memory` 中长期有效的架构约定和排障经验。
- 最终验收命令:
- `cargo check -p api-server --manifest-path server-rs/Cargo.toml`
- `cargo test -p api-server --manifest-path server-rs/Cargo.toml`
- `npm run check:server-rs-ddd`
- `npm run check:encoding`
- `git diff --check`
- `npm run api-server` 后检查 `/healthz`
- 禁止使用 `api-server:maincloud` 作为本轮 smoke。
## Public Interfaces
- HTTP route、DTO、error envelope、SpacetimeDB schema、前端调用方式默认不变。
- 新增的都是 `api-server` 内部 Rust Interface不进入 `shared-contracts`
- 若后续任何阶段发现必须改 contract先更新对应阶段文档和 G1 route/contract 矩阵,再单独实施。
## Assumptions
- 本计划目标是完整收口,不是只完成第一阶段。
- 可以分阶段提交,但每个阶段都必须有文档、测试和明确退出条件。
- `generated_image_assets` 首版必须至少被三个真实调用方使用,否则不算形成有效 Module。