567 lines
75 KiB
Markdown
567 lines
75 KiB
Markdown
# 踩坑与排障记录
|
||
|
||
> 用途:记录已验证、未来很可能再次遇到的问题。每条都应包含现象、原因、处理方式和验证方式。
|
||
|
||
## 记录格式
|
||
|
||
```md
|
||
## 问题标题
|
||
|
||
- 现象:看到什么错误或异常行为
|
||
- 原因:确认后的根因
|
||
- 处理:具体修复步骤
|
||
- 验证:如何确认修复有效
|
||
- 关联:相关文件、文档、提交或 Issue
|
||
```
|
||
|
||
## OSS V4 签名时间和 bucket/object_key 兼容
|
||
|
||
- 现象:OSS V4 私有读签名在部分时间点失败,可能出现 `OSS V4 签名时间格式化失败` 或服务端判定签名格式错误;排查用例中 bucket 为 `xushi-dev`,object_key 为 `generated-square-hole-assets/.../image.png`。
|
||
- 原因:旧逻辑依赖 `time::Time::to_string()` 再去掉冒号,小时小于 10 时输出不稳定补零;同时排查时容易把 bucket 名误当成 object_key 的一部分。
|
||
- 处理:OSS V4 `x-oss-date` 使用固定宽度 `yyyyMMdd'T'HHmmss'Z'` 格式化;调用读签名或 `HEAD Object` 时只传 object_key,不要传 `bucket/object_key` 拼接路径。
|
||
- 验证:运行 `cd server-rs && cargo test -p platform-oss -- --nocapture`,并用 bucket=`xushi-dev`、object_key=`generated-square-hole-assets/square-hole-session-546d881972684be2980a2a882cd0cc71/square-hole-profile-134411276ce1469cbe398f946a25d7f8/square-hole-shape-image/rabbit-option/asset-1777979289912039/image.png` 覆盖签名生成。
|
||
- 关联:`server-rs/crates/platform-oss/src/lib.rs`、`server-rs/crates/platform-oss/README.md`。
|
||
|
||
## generated 音频路径进运行态前要先换签
|
||
|
||
- 现象:草稿页 audio 控件能播放背景音乐,但拼图或抓大鹅运行态开局后背景音乐不响,Network 可能出现裸 `/generated-*-assets/...mp3` 私有路径 403。
|
||
- 原因:生成音乐转存到 OSS 私有对象后,`audioSrc` 是 generated legacy path;浏览器 `<audio>` 不能像公开静态资源一样直接请求裸路径。
|
||
- 处理:运行态隐藏 `<audio>` 设置 `src` 前,先通过 `useResolvedAssetReadUrl` 或 `resolveAssetReadUrl` 换签;播放失败只静默兜底,不阻断局内交互。拼图读取 `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`。
|
||
|
||
## 中文乱码与编码风险
|
||
|
||
- 现象:中文文案、注释、剧情或文档显示为乱码,或被改写成英文。
|
||
- 原因:Windows/PowerShell/终端编码不一致,或整文件重写导致编码变化。
|
||
- 处理:
|
||
- 不要直接沿用乱码文本。
|
||
- 不要用英文替换中文,除非用户明确要求翻译。
|
||
- 在 PowerShell 5.1 中显式使用 UTF-8。
|
||
- 优先用 Python/Node 或 `Get-Content -Encoding UTF8` 核对原文。
|
||
- 修改中文文件时优先局部补丁,避免无关内容重写。
|
||
- 验证:运行仓库已有编码检查;人工抽查修改文件中的中文内容。
|
||
- 关联:`AGENTS.md`、`npm run check:encoding`。
|
||
|
||
## 忘记密码后仍提示手机号或密码错误先查认证快照同步
|
||
|
||
- 现象:用户通过“忘记密码”重设密码后,接口返回成功或页面进入登录态,但再次使用新密码登录仍提示“手机号或密码错误”;重启后还可能出现 `Bearer JWT 版本已失效`,日志里的 token version 与本地快照不一致。
|
||
- 原因:重置/修改密码会更新 `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`。
|
||
|
||
## `.hermes` 只放共享内容,不放个人 Hermes 配置
|
||
|
||
- 现象:团队成员误把个人 Hermes 配置、会话或密钥复制进仓库。
|
||
- 原因:仓库 `.hermes/` 与个人 `~/.hermes/` 名称相似。
|
||
- 处理:仓库 `.hermes/` 只放 Markdown 共享记忆、计划和可公开 skills;不提交 `.env`、`config.yaml`、`sessions/`、`auth.json`。
|
||
- 验证:提交前检查 `git diff -- .hermes`,确认没有密钥、会话记录或个人路径敏感信息。
|
||
- 关联:`.hermes/README.md`。
|
||
|
||
## 儿童动作 Demo 卡在摄像头不可用或挥手不推进先查 mocap 消费链路
|
||
|
||
- 现象:`/child-motion-demo` 打开后即使 `http://127.0.0.1:8876/` 已启动,页面仍提示“摄像头暂不可用”,或到“打个招呼”、左右手挥动、站位步骤时真实硬件动作无法检测通过,只能用鼠标拖拽或键盘调试继续。
|
||
- 原因:浏览器摄像头视频流只是舞台背景;如果热身关把 `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`。
|
||
|
||
## 宝贝识物选篮误触发先查多套判定和残余轨迹
|
||
|
||
- 现象:`宝贝识物` 运行态打开礼物盒或反馈结束后,当前物品被连续送入左侧或右侧篮子,或硬件动作名偶发命中导致未做明确横移动作也触发选篮。
|
||
- 原因:选篮如果同时消费 `wave_left_hand` / `wave_right_hand` / `wave` 动作名和手部轨迹,或在 `correct` / `wrong` 反馈阶段继续累计手部路径,会把抓握、反馈期间残留移动或未知侧别手部误算成下一次选篮。
|
||
- 处理:宝贝识物选篮只使用明确 `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`。
|
||
|
||
## 儿童动作 Demo 绘本风资源未生成先查 VectorEngine 配置
|
||
|
||
- 现象:`/child-motion-demo` 已经呈现绘本草地风格,但 `public/child-motion-demo/picture-book-grass-stage.png`、`picture-book-grass-floor.png`、`picture-book-ground-ring.png`、`picture-book-character-outline.png`、`picture-book-ui-panel.png` 或 `picture-book-ui-button.png` 不存在,Network 里对应图片返回 404,或运行 `npm run assets:child-motion-demo -- --live` 返回缺少 VectorEngine 配置。
|
||
- 原因:儿童动作 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`。
|
||
|
||
## 儿童动作 Demo 绘本资源变形先查用途拆分和透明后处理
|
||
|
||
- 现象:`/child-motion-demo` 背景风格正确,但底部草坪被拉成厚色块、顶部 HUD 或右下状态条像方形面板被横向拉伸,或旧 `picture-book-ui-panel.png` 与新资源叠在一起。
|
||
- 原因:早期资源中 `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`。
|
||
|
||
## GPT-image-2 不再读 APIMart 图片配置
|
||
|
||
- 现象:配置了 `APIMART_BASE_URL` / `APIMART_API_KEY` 后,RPG、拼图或方洞的 GPT-image-2 生图仍返回缺配置,或请求体里还出现 `official_fallback` / `image_urls`。
|
||
- 原因: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`。
|
||
|
||
## 拼图参考图没有影响生成时先查 action payload 和阶段日志
|
||
|
||
- 现象:拼图上传参考图后生成出的画面明显不像参考图,或结果页重新生成没有按保存的参考图走图生图。
|
||
- 原因:首图生成只通过 `compile_puzzle_draft.referenceImageSrc` 临时传 Data URL,不持久化到 SpacetimeDB;结果页重新生成则要把当前上传图或关卡 `pictureReference` 作为 `generate_puzzle_images.referenceImageSrc` 继续传给后端。
|
||
- 处理:浏览器 Network 里确认 action payload 带 `referenceImageSrc`;api-server 日志按同一 `session_id` 查看 `拼图参考图解析完成`、`拼图 VectorEngine 图片生成 HTTP 返回`、`拼图 VectorEngine 图片下载完成`、`拼图生成图片已写入 OSS 与资产索引`,可定位慢在参考图读取、VectorEngine、下载或 OSS。
|
||
- 验证:前端测试覆盖上传图 + AI 重绘、结果页保存的 `pictureReference` 重新生成;后端单测覆盖 VectorEngine 请求体 `image` 字段。
|
||
- 关联:`src/components/puzzle-agent/PuzzleAgentWorkspace.tsx`、`src/components/puzzle-result/PuzzleResultView.tsx`、`server-rs/crates/api-server/src/puzzle.rs`。
|
||
|
||
## 拼图首图生成后要把入口参考图写回 `pictureReference`
|
||
|
||
- 现象:入口页上传图后,首图看着像没吃到参考图;结果页重新生成时默认只沿用关卡旧图,没有继续带入口上传图。
|
||
- 原因:首图生成请求虽然已经把 `referenceImageSrc` 传给 VectorEngine,但如果后端只更新 `cover_image_src` / `selected_candidate_id` 而不回写首关 `pictureReference`,结果页后续重绘就会丢失参考图。
|
||
- 处理:在 `compile_puzzle_draft` 和 `generate_puzzle_images` 的成功与 SpacetimeDB 降级快照路径里,都把本次入口参考图写入首关 `pictureReference`。
|
||
- 验证:后端单测覆盖 `build_puzzle_levels_with_primary_update` 和 `apply_generated_puzzle_candidates_to_session_snapshot`;结果页重新生成应在未重新上传时继续带入 `level.pictureReference`。
|
||
- 关联:`server-rs/crates/api-server/src/puzzle.rs`、`src/components/puzzle-result/PuzzleResultView.tsx`。
|
||
|
||
## 拼图图生图仍不像参考图时先看是否走了 edits
|
||
|
||
- 现象:Network payload 已带 `referenceImageSrc`,但 VectorEngine 生成结果仍明显不像上传图。
|
||
- 原因:`gpt-image-2-all` 的 `/v1/images/generations` 更适合纯文生图;有参考图且需要重绘时应切到 `/v1/images/edits` 的 multipart 图生图接口。
|
||
- 处理:`referenceImageSrc` 存在且 `aiRedraw = true` 时直接走 edits,prompt 仍保留参考图强约束;入口页关闭 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`。
|
||
|
||
## 旧后端路线文档造成判断漂移
|
||
|
||
- 现象:开发时参考到 Express、Node、PostgreSQL 或 Go 方向旧文档,导致接口、数据真相或部署路径与当前主线不一致。
|
||
- 原因:项目历史文档较多,部分旧方案仍保留作迁移参考。
|
||
- 处理:涉及服务端、数据真相、SpacetimeDB、运行时状态时,先看 `CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md`,再看 DDD 总纲和具体技术方案。
|
||
- 验证:代码改动应落在 `server-rs + Axum + SpacetimeDB` 主线;旧路线只作为迁移参考,不作为兼容目标。
|
||
- 关联:`docs/technical/CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md`、`AGENTS.md`。
|
||
|
||
## SpacetimeDB 表结构变更不能按 PostgreSQL 迁移直觉处理
|
||
|
||
- 现象:发布时 schema 冲突、自动迁移拒绝、旧客户端调用 reducer 失败、private 表数据迁移遗漏。
|
||
- 原因:SpacetimeDB 对字段删除、类型变化、索引/主键/RLS/reducer 变化有不同自动迁移边界。
|
||
- 处理:变更前阅读 `SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md`;涉及表变化时同步 `migration.rs`、`SPACETIMEDB_TABLE_CATALOG.md` 和 bindings;必要时走 JSON 导入导出与分片导入迁移流程。
|
||
- 验证:发布前完成 schema 检查、bindings 生成、表目录更新和相关 smoke。
|
||
- 关联:`docs/technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md`、`docs/technical/SPACETIMEDB_TABLE_CATALOG.md`。
|
||
|
||
## SpacetimeDB publish 报 wasm-bindgen 时先查 shared-contracts feature
|
||
|
||
- 现象:发布 `spacetime-module` 时报 `wasm-bindgen detected`,提示 `wasm-bindgen is only for webassembly modules that target the web platform`。
|
||
- 原因:SpacetimeDB module 的 wasm32 构建树被间接带入原生/网页依赖;已验证链路是 `reqwest -> platform-oss -> shared-contracts -> module-runtime -> spacetime-module`,由共享契约默认启用资产 OSS 契约触发。
|
||
- 处理:让 `shared-contracts` 的 OSS 资产契约走 `oss-contracts` feature,workspace 根依赖保持 `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`。
|
||
|
||
## 本地 SpacetimeDB replica identity 不匹配
|
||
|
||
- 现象:本地 standalone 启动时报 `mismatched database identity`。
|
||
- 原因:本地 SpacetimeDB 数据目录中的 replica 数据残留与当前数据库身份不一致。
|
||
- 处理:按本地 replica identity mismatch 文档进行备份、重建和脚本诊断。
|
||
- 验证:本地 SpacetimeDB 可正常启动并 publish / 访问。
|
||
- 关联:`docs/technical/SPACETIMEDB_LOCAL_REPLICA_IDENTITY_MISMATCH_FIX_2026-04-30.md`。
|
||
|
||
## 本地 SpacetimeDB publish 403 优先查 CLI 身份和目标库
|
||
|
||
- 现象:`spacetime publish` 在 `Pre-publish check` 阶段返回 `403 Forbidden`,提示当前 identity 无权对目标 database identity 执行 `update database`。
|
||
- 原因:当前 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`。
|
||
|
||
## `npm run dev` 本地 SpacetimeDB 401 / 403 可重置默认 local 身份
|
||
|
||
- 现象:`npm run dev` 启动本地开发栈时,SpacetimeDB 在登录、发布或预检查阶段返回 `401` / `403`,清理后仍像在使用旧 token 或旧本地库。
|
||
- 原因:本机 `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`。
|
||
|
||
## 本地 SpacetimeDB 联调可按阶段跳过宿主或发布
|
||
|
||
- 现象:本地 `npm run dev` 因 `3101` 已占用、重复发布 SpacetimeDB wasm 编译太慢,或只想检查 `spacetime-module` 语法而被完整联调链路拖慢。
|
||
- 原因:`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。`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`;`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`。
|
||
|
||
## 本地 SpacetimeDB publish 401 可清本地库重发
|
||
|
||
- 现象:本地 `spacetime publish` 显示 `401` 无权限,或重新发布仍像是在更新旧库。
|
||
- 原因:本地开发数据目录中保留的数据库、控制库身份或发布身份与当前目标不一致。
|
||
- 处理:确认本地开发数据可以丢弃后,停止本地 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`。
|
||
|
||
## SpacetimeDB 模块 publish 报 `wasm-bindgen detected`
|
||
|
||
- 现象:`spacetime publish` 已经完成 Rust 编译,但随后报 `wasm-bindgen detected`,提示依赖树里有面向 Web 平台的 wasm-bindgen。
|
||
- 原因: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`。
|
||
|
||
## Vite SPA fallback 吞掉 API 请求
|
||
|
||
- 现象:本地请求 `/api/profile/*` 等接口时返回 HTML,被前端当 JSON 解析报错。
|
||
- 原因: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`。
|
||
|
||
## 反馈页清空 file input 前必须先拷贝 FileList
|
||
|
||
- 现象:点击上传凭证会打开文件选择框,但选择图片后页面没有展示预览,提交时也没有携带图片凭证。
|
||
- 原因:浏览器传入的 `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`。
|
||
|
||
## 拼图 VectorEngine 图片生成密钥不能复用 DashScope / ARK key
|
||
|
||
- 现象:拼图新手引导或拼图创作点击生成后返回 `VectorEngine 图片生成密钥未配置`。
|
||
- 原因:拼图 `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`。
|
||
|
||
## `npm run api-server` 读取 env 的顺序必须让 `.env.secrets.local` 最后覆盖
|
||
|
||
- 现象:`POST /api/assets/hyper3d/text-to-model` 在本地返回 503,详情里提示 `HYPER3D_API_KEY 未配置`,但开发者明明已经在本地私密文件里写了 key。
|
||
- 原因:`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`。
|
||
|
||
## 拼图图片生成 98% 后报 OSS V4 签名时间格式化失败
|
||
|
||
- 现象:拼图创作表单生成进度卡在 98%,`POST /api/runtime/puzzle/agent/sessions/{sessionId}/actions` 返回 `502 Bad Gateway`,前端提示 `拼图图片生成失败:OSS V4 签名时间格式化失败`。
|
||
- 原因:`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`。
|
||
|
||
## 拼图生成完成后图片只显示破图或 alt 文案
|
||
|
||
- 现象:拼图结果页生成完成后,“画面图”区域出现破图图标和作品名,图片无法正常预览;但打开历史拼图素材时同一张图可能可以正常预览。
|
||
- 原因:拼图正式图保存为 `/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`。
|
||
|
||
## 本地短信登录页签突然消失
|
||
|
||
- 现象:登录弹窗只剩密码登录,短信登录页签看起来像被删掉,但 `LoginScreen` 中手机号验证码表单仍存在。
|
||
- 原因:前端根据 `GET /api/auth/login-options` 返回的 `availableLoginMethods` 渲染页签;常见根因有两类:
|
||
- 本地启动脚本没有让 `.env.local` 覆盖 `.env`,`SMS_AUTH_ENABLED=true` 不生效,后端只返回 `["password"]`。
|
||
- Rust API 直连已返回 `["phone","password"]`,但 Vite 代理目标指向未监听端口,导致 3000 域名下的 `login-options` 返回 `500`,`AuthGate` 降级成 `["password"]`。
|
||
- 3000 端口被旧 `dev:web` 占用后,新的完整栈 Vite 自动漂移到 3001/3002;浏览器仍打开旧 3000 页面,旧页面继续代理到已经下线的端口。
|
||
- 单独 `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`。
|
||
|
||
## 本地短信收不到验证码先查 provider
|
||
|
||
- 现象:登录弹窗可以进入短信页签,但点击“获取验证码”后,手机没有收到短信。
|
||
- 原因:本地 `.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`。
|
||
|
||
## 手机验证码登录 500 先查短信 provider 语义
|
||
|
||
- 现象:登录弹窗手机号验证码登录失败,浏览器看到 `POST /api/auth/phone/login 500`,后端日志里同时出现阿里云短信 `UNKNOWN`、`biz.FREQUENCY` 或 `check frequency failed`。
|
||
- 原因:真实短信 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`。
|
||
|
||
## 手机验证码登录成功后又瞬间回到未登录
|
||
|
||
- 现象:手机号验证码登录先成功,随后 UI 又闪回“未登录”,登录弹窗可能重新出现。
|
||
- 原因:`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`。
|
||
|
||
## 刷新网页后登录态失效
|
||
|
||
- 现象:刷新网页后,用户明明有本地 access token,却回到未登录状态。
|
||
- 原因:`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`。
|
||
|
||
## 登录后推荐页加载出作品又回到未登录
|
||
|
||
- 现象:前端登录成功后进入推荐页,推荐页自动加载出一个作品,随后瞬间回到未登录;停留在其他页面或推荐页没加载出作品时不复现。
|
||
- 原因:推荐页 embedded 运行态会自动发起受保护写请求。若这些卡片级后台请求遇到 `401` 或 refresh 失败,默认请求层曾清空 access token 并广播全局 auth 事件,导致 `AuthGate` 重新 hydrate 成未登录态。更隐蔽的是,`refreshAccessToken()` 自身曾在 refresh 失败时静默清 token,即便调用方关闭了 `clearAuthOnUnauthorized`,也可能让后续 hydrate 变成未登录。
|
||
- 处理:请求层统一使用 `authImpact: 'global' | 'local'` 区分账号权威请求与局部后台请求;推荐页自动运行态、图片换签、公开拼图运行态和平台 bootstrap 私有投影刷新统一使用 `BACKGROUND_AUTH_REQUEST_OPTIONS` / `RUNTIME_BACKGROUND_AUTH_OPTIONS`,并等 `canReadProtectedData` 为 true 后再启动;用户主动点击的账号动作仍保留默认全局鉴权失败处理。
|
||
- 追加处理:generated 私有图片换签 `/api/assets/read-url` 也属于展示层后台请求;推荐页拼图运行态挂载后会立即解析封面图,若换签 401 触发全局鉴权事件,也会表现成“进入拼图作品后瞬间未登录”。资源换签失败只应让当前图片为空,不应清 token、广播 auth 事件或主动 refresh。
|
||
- 追加处理:从推荐页点进公开拼图作品并启动完整运行态后,`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`。
|
||
|
||
## 推荐页作品卡一直显示加载中
|
||
|
||
- 现象:推荐页有公开作品,但主视口一直停在“加载中...”,没有进入作品,也没有显示可操作错误。
|
||
- 原因:推荐页自动启动嵌入运行态时先设置 `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`。
|
||
|
||
## 推荐页未登录入口误打开公开详情
|
||
|
||
- 现象:新用户默认在发现页,但点击推荐页或推荐封面后,如果复用公开作品详情入口,可能绕过推荐页“登录后游玩”的产品门禁。
|
||
- 原因:`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`。
|
||
|
||
## Rust 冷编译导致 api-server 健康检查误超时
|
||
|
||
- 现象:`npm run dev:rust` 在 Windows 冷编译/链接阶段误判 `/healthz` 等待超时并杀掉 `cargo run`。
|
||
- 原因:脚本把 SpacetimeDB 与 api-server 等待窗口混在一起,未考虑 Rust 冷编译耗时。
|
||
- 处理:按冷编译超时修复文档拆分等待窗口。
|
||
- 验证:冷启动时不再误杀仍在编译的 api-server。
|
||
- 关联:`docs/technical/API_SERVER_DEV_STACK_COLD_BUILD_TIMEOUT_FIX_2026-04-25.md`。
|
||
|
||
## Windows debug api-server 主线程栈溢出
|
||
|
||
- 现象:`cargo check -p api-server` 和 `build_router` 测试通过,但 `npm run api-server` 在 Windows debug 启动时 `thread 'main' has overflowed its stack`。
|
||
- 原因:`api-server` Axum 路由树已经很深,debug 主线程默认栈偏小,初始化状态和构造路由时容易触顶。
|
||
- 处理:入口 `main` 用显式 16MB 栈线程启动 Tokio runtime,并把实际服务逻辑放入 `run_server()`;新增路由时优先用小 router `.merge()`,避免继续拉长主链。
|
||
- 验证:`npm run api-server` 后 `/healthz` 返回 200,相关路由冒烟通过。
|
||
- 关联:`server-rs/crates/api-server/src/main.rs`、`server-rs/crates/api-server/src/app.rs`。
|
||
|
||
## Windows debug api-server.exe 锁文件与强杀退出码容易混淆
|
||
|
||
- 现象:`cargo run -p api-server` 或 `npm run api-server` 报 `failed to remove file ... target\debug\api-server.exe`;清理旧进程后,旧终端可能继续打印 `process didn't exit successfully: server-rs\target\debug\api-server.exe (exit code: 0xffffffff)`。
|
||
- 原因:Windows 不能覆盖仍在运行的 exe;通常是上一条 `npm run api-server` 链路仍在运行,进程树为 `npm run api-server -> node scripts/api-server-dev.mjs -> cargo run -> api-server.exe`。`0xffffffff` 常见于排障时用 `Stop-Process -Force` 强制结束旧 `api-server.exe` 后由 Cargo 回显,不一定代表新启动失败。
|
||
- 处理:先按目标路径确认并停止本仓库的旧 `api-server.exe` 及其父级 `cargo/node/cmd` 启动链路,再重新启动;不要同时开多个 `npm run api-server`。
|
||
- 验证:确认没有匹配 `C:\Genarrative\server-rs\target\debug\api-server.exe` 的进程后,`Remove-Item` 能删除旧 exe;随后 `npm run api-server` 启动并访问 `/healthz` 返回 200。
|
||
- 关联:`scripts/api-server-dev.mjs`、`server-rs/crates/api-server/src/main.rs`。
|
||
|
||
## dev-rust-stack 端口被旧进程占用时会误判健康检查
|
||
|
||
- 现象:`node scripts/run-bash-script.mjs scripts/dev-rust-stack.sh --skip-spacetime` 输出 `Port 3000 is in use, trying another one...`,随后 `api-server.exe` 报 `AddrInUse` / `code: 10048`。
|
||
- 原因:旧 `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`。
|
||
|
||
## Windows debug 长 SSE Future 触发 api-server 断连
|
||
|
||
- 现象:前端 Vite 代理请求 `/api/runtime/creative-agent/sessions/{sessionId}/messages/stream` 报 `read ECONNRESET`,随后 `api-server.exe` 以 `0xffffffff` 退出,`dev:rust` 回收 SpacetimeDB、Vite 和后台 Vite。
|
||
- 原因:单个 `async_stream::stream!` 中塞入 Agent 执行、外部模型请求、会话更新和大量 SSE 事件,会在 Windows debug 下生成很大的 Future;真实消费 SSE body 时容易触发 worker 线程栈压力或进程级中断,单元测试若只测函数和路由状态会漏掉。
|
||
- 处理:长 SSE 路由优先使用 `tokio::spawn` 跑业务流程,通过 `mpsc` + `UnboundedReceiverStream` 向 Axum 返回轻量 stream;失败时更新会话为 `failed` 并发送 SSE `error`,不要把大段执行逻辑内联到路由返回的 stream future 中。
|
||
- 验证:补充实际 `collect()` SSE body 的路由测试,确认首轮包含 `stage`、`puzzle_template_catalog` 和 `done`,且不会提前发送 `puzzle_template_selection` / `puzzle_cost_range`;再执行 `cargo check -p api-server`、`cargo test -p api-server creative_agent`,联调时用 `npm run api-server` 检查 `/healthz`。
|
||
- 关联:`server-rs/crates/api-server/src/creative_agent.rs`、`server-rs/crates/api-server/src/app.rs`。
|
||
|
||
## creative-agent 过程项不要把历史事件渲染成运行中
|
||
|
||
- 现象:智能创作页过程中多个阶段从一开始同时转圈,生成结束或进入模板确认后仍有过程项保持转圈。
|
||
- 原因:前端把历史 `stage`、`tool_started` 和 `thought_summary_delta` 都按 active 渲染;后端工具开始/完成事件如果 `toolCallId` 不一致,也会导致开始事件无法收口。
|
||
- 处理:
|
||
- 只有最新且仍在执行的 stage 可为 active;等待确认、等待用户、target ready 和 failed 都是静态状态。
|
||
- 工具开始事件必须等同一 `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`。
|
||
|
||
## creative-agent 会话切换要清理本地待确认模板
|
||
|
||
- 现象:用户在一个智能创作会话中点开模板确认面板后,立即切到另一条创作会话,可能看到上一会话的确认面板残留。
|
||
- 原因:模板确认面板的 `pendingSelection` 是 `CreativeAgentWorkspace` 本地 UI 状态,不属于后端 session 快照;组件复用时如果不监听 `sessionId` 清理,会跨会话泄漏。
|
||
- 处理:工作区以 `session?.sessionId` 为边界清空 `pendingSelection`;服务端仍以 `puzzleTemplateSelection` / `targetBinding` 作为正式业务状态。
|
||
- 验证:前端测试先点开模板确认面板,再 rerender 到另一 session,断言确认面板消失。
|
||
- 关联:`src/components/creative-agent/CreativeAgentWorkspace.tsx`、`src/components/creative-agent/CreativeAgentWorkspace.test.tsx`。
|
||
|
||
## 视觉小说 VN-10 不要绕过平台资产引用
|
||
|
||
- 现象:文档、封面、场景背景、角色立绘或音乐为了预览方便被写成 Data URL、裸对象路径、外部 URL 或本地临时文件路径。
|
||
- 原因:前端上传与预览容易混在一起,若不走平台资产对象,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`。
|
||
|
||
## 视觉小说 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`。
|
||
- 验证:新开发者只读这组文档即可继续维护,不需要把旧 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`。
|
||
|
||
## 视觉小说公开广场不要触发登录刷新
|
||
|
||
- 现象:未登录用户进入平台公开广场或从推荐流读取视觉小说公开作品时,前端可能先尝试 `/api/auth/refresh`,失败后再读取公开列表,导致无意义的鉴权噪声或 401 状态刷新。
|
||
- 原因:公开只读接口如果复用默认 `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`。
|
||
|
||
## 创作 Tab 语义迁移后,旧“新建作品”测试要改看智能创作首页
|
||
|
||
- 现象:把 `create` 从旧创作中心切到 `CreativeAgentHome` 后,旧测试仍尝试在创作页找“新建作品”类型卡,导致用例失败或定位不到元素。
|
||
- 原因:产品语义已经变成“创作 = 智能创作首页,草稿 = 旧作品架”,但测试夹具和 helper 还沿用旧入口。
|
||
- 处理:把这类测试改成验证智能创作首页、快捷胶囊、抽屉与草稿 Tab;同时给 `useRpgEntryLibraryDetail` 这类恢复路径补上 `setPlatformTabToDraft`。
|
||
- 验证:定向 `vitest`、`eslint`、`typecheck`、`check:encoding` 都通过。
|
||
- 关联:`src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`、`src/components/rpg-entry/useRpgEntryAgentDraftRestore.test.tsx`、`src/components/rpg-entry/useRpgEntryLibraryDetail.ts`。
|
||
|
||
## server-rs 默认 cargo build 不能等同于构建 SpacetimeDB 模块
|
||
|
||
- 现象:在 `server-rs` 下无参数 `cargo build` 期望同时构建 `spacetime-module`,导致链接或构建范围误判。
|
||
- 原因: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`。
|
||
|
||
## Windows 原生 `spacetime-module` 单测会链接缺失 SpacetimeDB 宿主符号
|
||
|
||
- 现象:在 Windows 上执行 `cargo test -p spacetime-module --manifest-path server-rs/Cargo.toml` 可能编译到链接阶段后失败,出现 `LNK2019` / `LNK1120`,缺失 `datastore_insert_bsatn`、`procedure_start_mut_tx`、`console_log` 等 SpacetimeDB 宿主符号。
|
||
- 原因:`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`。
|
||
|
||
## Rust 构建不要让不可用的 sccache 阻断 rustc
|
||
|
||
- 现象:Cargo 报 `could not execute process sccache ... rustc.exe -vV (never executed)`、`sccache: error: Timed out waiting for server startup`,或 `sccache: caused by: Failed to send data to or receive data from server / Failed to read response header / failed to fill whole buffer`;真实 `rustc -Vv` 可以执行,但构建在调用包装器时失败。
|
||
- 原因:环境、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`。
|
||
|
||
## 生产发布入口不要沿用旧 Jenkinsfile / 一体化脚本
|
||
|
||
- 现象:部署、回滚或 Jenkins Job 重建时参考旧发布文档,导致 systemd、Nginx、SpacetimeDB 自托管和生产包拆分不一致。
|
||
- 原因:旧 Jenkins / 旧本地远端部署脚本文档仍作为历史经验保留。
|
||
- 处理:生产相关操作先看 `PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`,再按需追溯旧文档。
|
||
- 验证:发布链路使用当前 `deploy/systemd`、`deploy/nginx`、`scripts/deploy` 和 `jenkins/Jenkinsfile.production-*`。
|
||
- 关联:`docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`。
|
||
|
||
## Jenkins 可选参数在 set -u 下不能裸读
|
||
|
||
- 现象:数据库导入或导出流水线报 `INCLUDE_TABLES: unbound variable`,或其它可选参数在 Bash 中未定义即退出。
|
||
- 原因: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`。
|
||
|
||
## 个人任务 scope 不得扩成 work/site/module
|
||
|
||
- 现象:个人任务配置为 `work` / `site` / `module` 后进度串桶或静默按 0 处理。
|
||
- 原因:首版个人任务只支持用户维度,非 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`。
|
||
|
||
## 拼图发布 409 不一定是接口故障
|
||
|
||
- 现象:拼图结果页点击发布后,控制台出现 `POST /api/runtime/puzzle/agent/sessions/{sessionId}/actions 409 (Conflict)`,用户只看到发布失败。
|
||
- 原因:`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`。
|
||
|
||
## WebGL 画布在高 DPR 移动端放大溢出
|
||
|
||
- 现象:抓大鹅试玩入口进入后,3D 锅体和物体从中心圆形区域向右下溢出,顶部状态和底部备选栏也可能看起来被右侧裁切。
|
||
- 原因:`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`。
|
||
|
||
## Hyper3D subscriptionKey 不要按固定短文本限长
|
||
|
||
- 现象:抓大鹅生成草稿时,内联 Rodin 图生 3D 模型提交成功后,状态轮询报 `subscriptionKey 超过 256 字符`,导致 `/api/creation/match3d/sessions/{sessionId}/actions` 返回 400。
|
||
- 原因:`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`。
|
||
|
||
## 抓大鹅新草稿不要再接回 Rodin 或 GLB 生成
|
||
|
||
- 现象:修改抓大鹅素材时容易沿用旧 Rodin/GLB 方案,导致新草稿生成耗时变长、进度停在模型阶段,或运行态等待不存在的 GLB。
|
||
- 原因:仓库里保留了 Hyper3D 通用代理和历史模型字段,旧文档也曾要求草稿阶段同步生成 GLB。当前产品口径已经改为 2D 多视角素材。
|
||
- 处理:新 `match3d_compile_draft` 与批量新增只生成 2D 图片:每个物品 5 个视角,单张 1K 素材图固定 5x5 切割,一行对应一个物品,超过 5 个物品自动分批并行生图。`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`。
|
||
|
||
## 抓大鹅切图路径不能只用中文物品名
|
||
|
||
- 现象:草稿页 `素材配置 > 物品` 中多个素材名称不同,但预览图片完全一样。
|
||
- 原因:中文物品名经过 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`。
|
||
|
||
## 抓大鹅生成素材不能只挂在 compile response
|
||
|
||
- 现象:抓大鹅草稿生成完成后停留在结果页能看到切割好的物品图片;退出后从草稿 Tab 重新进入同一草稿,素材列表变回默认占位或为空,已生成的物品名称和图片丢失。
|
||
- 原因:`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`。
|
||
|
||
## 抓大鹅试玩和正式运行态不要只读草稿页本地素材预览
|
||
|
||
- 现象:结果页能看到生成的物品图片,但点击试玩或从推荐 / 公开作品进入正式抓大鹅时,局内仍显示默认积木素材。
|
||
- 原因:结果页本地 `assetDrafts` 和作品 profile 的 `generatedItemAssets` 可能不同步;推荐流内嵌运行态若只读卡片摘要,卡片缺素材时会把已持久化 profile 素材丢掉;点击试玩时 React state 异步更新也可能让运行态第一帧读取旧 `match3dProfile`。
|
||
- 处理:删除、批量新增、音效生成或封面引用物品素材后,都把当前 `generatedItemAssets` 写回作品 profile;`Match3DResultView` 合并同 `itemId` 的 draft/profile 素材,用 profile 已有 `imageViews[]` 或首图引用补齐旧 draft;点击试玩前把试玩可用物品种类通过 `itemTypeCountOverride` 降到已生成 2D 素材数量;推荐流内嵌运行态启动前若卡片摘要没有生成素材,补读 `getMatch3DWorkDetail(profileId)` 并把详情资产传给 `Match3DRuntimeShell`。`PlatformEntryFlowShellImpl` 需要维护 `match3dRuntimeProfile`,在 `startMatch3DRunFromProfile` 创建 run 后立即锁定本次完整 profile,runtime 渲染时优先按 `run.profileId` 使用这份 profile,而不是等待普通 `match3dProfile` state 下一轮刷新。
|
||
- 验证:执行 `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`。
|
||
|
||
## 法律文档弹窗通过 portal 挂载时要显式带平台主题
|
||
|
||
- 现象:登录弹窗内点击协议链接打开法律文档时,弹窗可能继承不到 `platform-theme--light/dark` 变量,或者层级低于登录遮罩导致不可见。
|
||
- 原因:`UnifiedModal` 默认通过 portal 挂到 `document.body`,不再处于原页面的主题容器内;登录弹窗自身又使用较高 z-index。
|
||
- 处理:法律文档弹窗组件应支持传入 `platformTheme`,overlay 上显式挂 `platform-theme platform-theme--*`,并使用高于登录遮罩的层级。法律内容必须作为独立面板打开,不要在当前个人页或登录面板下方内联展开。
|
||
- 验证:登录页协议链接、个人页法律入口均能打开可滚动 `LegalDocumentModal`,亮色 / 暗色主题文本和按钮可读。
|
||
|
||
## 生成页完成回调不能只依赖异步 React state
|
||
|
||
- 现象:抓大鹅或拼图点击生成后,进度页已经显示 100% / 生成完成,但没有自动进入试玩或结果页。
|
||
- 原因:完成回调用 `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`。
|
||
|
||
## 抓大鹅历史草稿外部 Rodin GLB 链接必须转存后再试玩或发布
|
||
|
||
- 现象:草稿页预览模型失败并报 `GL_INVALID_ENUM: Invalid cap.`,或结果页能看到历史生成记录但试玩、发布和正式运行态仍显示默认积木。
|
||
- 原因:历史结果页手动 `重新生成` 会把 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`。
|
||
|
||
## 抓大鹅难度配置的物品种类和消除次数必须分离
|
||
|
||
- 现象:历史草稿选择标准 / 硬核难度后,系统可能把 `clearCount` 当成局内物品种类数量,导致标准需要 12 种、硬核需要 20/21 种;素材不足时发布或试玩行为不一致。
|
||
- 原因:旧运行态把消除次数和类型数量绑在一起,结果页文案又同时展示“素材图片 / 局内类型”,导致前端、发布校验和 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`。
|
||
|
||
## 抓大鹅标签清洗不要把 `3D素材` 当编号剥掉
|
||
|
||
- 现象:AI 或兜底生成的 `3D素材` 标签在后端规范化后变成 `D素材`。
|
||
- 原因:标签清洗在去掉编号列表前缀后,又无条件剥离开头数字和标点,把合法标签中的 `3D` 当成列表编号处理。
|
||
- 处理:只移除明确的编号列表前缀,例如 `1. 标签`、`1、标签`、`1) 标签`;不要对普通标签开头数字做二次剥离。
|
||
- 验证:`cargo test -p api-server match3d_tag_normalization --manifest-path server-rs/Cargo.toml`,并保留 `normalize_match3d_tag("3D素材") == "3D素材"` 的单测。
|
||
- 关联:`server-rs/crates/api-server/src/match3d.rs`。
|
||
|
||
## 用户标签不要直接外显,SpacetimeDB Vec 字段不要写 default 宏
|
||
|
||
- 现象:给 `user_account.user_tags` 或邀请码独立标签列写 `#[default(Vec::<String>::new())]` 时,SpacetimeDB WASM 构建报 `destructor of Vec<String> cannot be evaluated at compile-time`。
|
||
- 原因: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`。
|
||
|
||
## 公开作品详情深链找不到作品不能停在空详情页
|
||
|
||
- 现象:直接访问 `/works/detail?work=PZ-...`,作品不存在或已下架时会弹出“作品不存在或已下架,将返回首页。”;关闭提示后仍可能停在大白屏。
|
||
- 原因:旧恢复逻辑只覆盖 `/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`。
|