Merge branch 'master' into codex/puzzle-clear-template-runtime-fixes

# Conflicts:
#	.hermes/shared-memory/decision-log.md
#	.hermes/shared-memory/project-overview.md
#	docs/【开发运维】本地开发验证与生产运维-2026-05-15.md
#	scripts/dev.test.ts
#	server-rs/crates/api-server/src/creation_entry_config.rs
#	server-rs/crates/api-server/src/wooden_fish.rs
#	server-rs/crates/module-auth/src/lib.rs
#	server-rs/crates/spacetime-client/src/wooden_fish.rs
#	server-rs/crates/spacetime-module/src/auth/procedures.rs
#	src/components/custom-world-home/creationWorkShelf.ts
#	src/components/platform-entry/PlatformEntryFlowShellImpl.tsx
#	src/components/rpg-entry/rpgEntryWorldPresentation.ts
#	src/services/miniGameDraftGenerationProgress.test.ts
#	src/services/miniGameDraftGenerationProgress.ts
This commit is contained in:
2026-06-04 11:24:14 +08:00
451 changed files with 18452 additions and 5266 deletions

View File

@@ -23,11 +23,20 @@
- 验证:触发任一平台级异步失败时,页面应出现包含“错误来源”和“错误内容”的弹窗;复制内容应包含来源和错误正文;旧页面内错误 banner 不再重复出现。
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx``src/components/platform-entry/PlatformErrorDialog.tsx``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 暗色创作进度卡不要被 platform-remap-surface 改成深色文字
- 现象:统一创作页里的暗色进度卡背景是深绿 / 深蓝,但“创作进度”、百分比和进度提示显示成深色,移动端几乎看不清。
- 原因:`platform-remap-surface` 在浅色主题下会把后代 `[class*='text-white']` 强制重映射成 `var(--platform-text-strong)`,并且使用 `!important`;暗色 hero 卡片如果只写通用 `text-white*`,刷新后仍会被全局 remap 覆盖成深色。早期还混用了 `text-white/72``text-white/88``border-white/14``bg-white/12` 等不稳透明度档位,进一步放大了问题。
- 处理:给暗色 hero 加组件专属 class例如 `creation-agent-hero__progress-label``creation-agent-hero__progress-value``creation-agent-hero__progress-hint`,并在 `src/index.css` 的 remap 规则之后用更具体选择器和 `!important` 固定白色透明度、边框和进度条底色。
- 验证:`CreationAgentWorkspace` 测试应断言进度标题、百分比和提示文本带专属 class`src/index.test.ts` 应断言这些 class 在 remap surface 内有白色覆盖规则;移动端截图中暗色卡片文字应保持可读。
- 关联:`src/components/creation-agent/CreationAgentWorkspace.tsx``src/components/creation-agent/CreationAgentWorkspace.test.tsx``src/index.css``src/index.test.ts``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## VectorEngine 图片生成 SendRequest 超时要按传输失败排查
- 现象:`external_api_call_failure` 里看到 `failureStage=request_send``timeout=true``statusCode=null``errorSource=client error (SendRequest)`,前端只知道图片生成失败。
- 现象:`external_api_call_failure` 里看到 `failureStage=request_send``timeout=true``statusCode=null``errorSource` 可能是 `client error (SendRequest)` 或更完整的 reqwest 底层错误链,前端只知道图片生成失败。
- 原因:`timeout=true` 来自 `reqwest::Error::is_timeout()`,不是业务代码固定写死;`SendRequest` 是 Hyper 发送请求阶段的错误来源标签,只说明请求未拿到可归类的 HTTP 响应,不会包含上游 JSON 错误体。
- 处理:先按 `provider/failureStage/statusClass` 聚合,再用 `user_id` / `profile_id``metadata_json.userId/profileId` 定位触发者草稿 / 作品;`request_send + timeout=true` 优先查请求体大小、参考图数量、出口网络、代理/Nginx、VectorEngine 当时可用性和同一 request_id 日志。若记录有 `502``429 moderation_blocked`,按上游网关或审核失败另行处理,不要归到传输超时。
- 处理:先按 `provider/failureStage/statusClass` 聚合,再用 `user_id` / `profile_id``metadata_json.userId/profileId/requestId` 定位触发者草稿 / 作品和同一次 HTTP 请求`request_send + timeout=true` 优先查 provider 日志的 `source_chain`请求体大小、参考图数量、出口网络、代理/Nginx、VectorEngine 当时可用性和同一 request_id 日志。若记录有 `502``429 moderation_blocked`,按上游网关或审核失败另行处理,不要归到传输超时。
- 拼图关卡资产生成按 `level_scene -> ui_spritesheet -> level_background` 顺序执行,每个资产会输出 `slot``asset_kind``elapsed_ms`;排查拼图草稿失败时优先看同一 request_id 下最后一个失败 slot。
- 验证:`cargo check -p api-server --manifest-path server-rs/Cargo.toml`;查询 `tracking_event` 时失败记录应能看到触发者 `user_id` 和可用的 `profile_id`
- 关联:`server-rs/crates/api-server/src/external_api_audit.rs``server-rs/crates/api-server/src/openai_image_generation.rs``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
@@ -122,10 +131,25 @@
## 玩法入口分类字段缺失要前端兜底
- 现象:平台创作入口初始化时,`platformEntryCreationTypes.ts` 直接对 `creationTypes[].categoryId` / `categoryLabel``trim()`,一旦后端旧数据、局部 mock 或异常返回里缺字段,整个创作页会在 `derivePlatformCreationTypes(...)` 里直接炸掉。
- 处理:`normalizeCategoryId(...)``normalizeCategoryLabel(...)` 必须接收可空值,并分别回退到 `recent` / `最近创作`。前端这里是展示派生层,不能要求所有历史配置都先补齐字段
- 处理:`normalizeCategoryId(...)``normalizeCategoryLabel(...)` 必须接收可空值,并分别回退到 `recommended` / `热门推荐`;历史 `recent` / `最近创作` 也要归一到推荐分类。`最近创作` 不属于模板分类页签,只能由真实草稿 / 作品架后端数据决定是否展示
- 验证:`npm test -- src/components/platform-entry/platformEntryCreationTypes.test.ts`,再打开本地创作页确认能正常进入创作 Tab。
- 关联:`src/components/platform-entry/platformEntryCreationTypes.ts``src/components/platform-entry/platformEntryCreationTypes.test.ts``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 创作入口公告不要恢复前端固定两卡
- 现象:点击底部加号进入的创作入口页只展示固定的拼图 / 抓大鹅主题卡,后台改公告表单后前台没有变化。
- 原因:前端重新硬编码 banner 列表,绕过了 `GET /api/creation-entry/config``eventBanners` 配置。
- 处理:创作入口页公告位优先读取后端 `eventBanners` 数组,多条自动轮播;旧 `eventBanner` 只做单条兼容兜底。后台主格式是标题与 HTML 内容表单,保存时序列化为后端 `eventBannersJson` 传输字段,只允许受控 HTML 片段经空权限 iframe 展示,不执行 JSX 或直接 DOM 注入。
- 验证:后台保存两条以上公告后,点击底部加号进入创作入口页应自动轮播这些后台配置项;`CustomWorldCreationHub` 相关测试应断言标题来自后端配置。
- 关联:`src/components/custom-world-home/CustomWorldCreationStartCard.tsx``server-rs/crates/module-runtime/src/application.rs``apps/admin-web/src/pages/AdminCreationEntrySwitchPage.tsx`
## 移动端草稿卡不要长按选中文字
- 现象:移动端草稿页长按作品卡标题或摘要时触发系统文字选区,容易误触并打断作品架操作。
- 处理:移动端只对 `#platform-tab-panel-saves .creation-work-card` 禁止 `user-select``-webkit-touch-callout`;输入框、文本域和 `[contenteditable='true']` 保留文本选择能力,避免破坏真实编辑场景。
- 验证:移动端草稿页长按普通作品卡文字不出现系统选区;`src/index.test.ts` 应覆盖 CSS 选择器和可编辑控件例外。
- 关联:`src/index.css``src/index.test.ts``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 草稿页未读点不要继续用红色 literal
- 现象:草稿页底部 Tab 和作品架的未读点视觉上仍像红点,或 glow 仍带红色阴影,和平台暖棕体系不一致。
@@ -158,6 +182,21 @@
- 验证:浏览器里这三页的根区应仍保留 `platform-remap-surface`,但不再出现 `platform-page-stage`;草稿页顶部筛选样式应和发现页频道标签一致。
- 关联:`src/components/custom-world-home/CustomWorldCreationHub.tsx``src/components/custom-world-home/CustomWorldWorkTabs.tsx``src/components/rpg-entry/RpgEntryHomeView.tsx``src/index.css`
## 统一创作壳现在自己负责页面滚动和四条入口外壳
- 现象:统一创作页最初只包住拼图、抓大鹅和敲木鱼的工作台内容,跳一跳仍然保留独立工作台壳,页面级滚动职责也散落在平台入口 motion wrapper 里,导致移动端不同入口的可见外壳不一致。
- 原因:`UnifiedCreationPage` 只做了标题和隐藏契约,入口壳还在各自工作台里保留 `platform-remap-surface` / `overflow-y-auto``jump-hop` 也没进入统一 spec。
- 处理:把 `jump-hop` 纳入 `unifiedCreationSpec`,让 `UnifiedCreationPage` 自己承担页面级滚动与统一标题栏;`JumpHopCreationWorkspace``WoodenFishCreationWorkspace``unifiedChrome` / `showBackButton`,平台壳不再给这几条统一入口套额外滚动壳。
- 验证:`npm run test -- src/components/unified-creation/unifiedCreationSpecs.test.ts src/components/unified-creation/UnifiedCreationPage.test.tsx src/components/unified-creation/UnifiedGenerationPage.test.tsx src/components/unified-creation/workspaces/JumpHopCreationWorkspace.test.tsx src/components/unified-creation/workspaces/WoodenFishCreationWorkspace.test.tsx` 通过后,`/creation/puzzle``/creation/match3d``/creation/jump-hop``/creation/wooden-fish` 都应由同一套统一创作页外壳承载。
- 关联:`src/components/unified-creation/UnifiedCreationPage.tsx``src/components/unified-creation/unifiedCreationSpecs.ts``src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
## 统一创作编排层不要再让平台壳直挂旧工作台
- 现象:平台入口壳已经切到统一创作外壳,但源码里仍直接 lazy import 并渲染四个旧工作台分支,看起来还是四套入口编排。
- 原因:统一创作页只收口了可见外壳,入口层没有再抽一层统一创作编排组件,导致平台壳依旧要认识各玩法旧工作台。
- 处理:新增 `UnifiedCreationWorkspace`,由它内部按 `playId` 选择真实工作台;平台壳只依赖这一层,不再直接挂旧工作台分支。旧工作台已迁入 `src/components/unified-creation/workspaces/`,不再是入口编排事实源。
- 验证:`PlatformEntryFlowShellImpl.tsx` 中不应再出现四个旧工作台的入口渲染分支,创作 Tab 与 `/creation/<play>` 仍能正常进入对应工作台。
- 关联:`src/components/unified-creation/UnifiedCreationWorkspace.tsx``src/components/platform-entry/PlatformEntryFlowShellImpl.tsx``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## Jenkinsfile 开头不能带 UTF-8 BOM
@@ -167,6 +206,14 @@
- 验证:检查 `jenkins/Jenkinsfile.production-stdb-module-publish` 文件开头字节不再是 `EF BB BF`,并用 Jenkins `validateDeclarativePipeline` 或重放 `Genarrative-Stdb-Module-Publish`,不应再停在 `No such DSL method 'pipeline'`
- 关联:`jenkins/Jenkinsfile.production-stdb-module-publish``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## Linux 多用户 dev 端口冲突先查系统级端口段注册表
- 现象:同一台 Linux 机器上多个用户同时开发时,`npm run dev` 报端口段已被其他用户占用、同一用户已有活跃端口段,或 SpacetimeDB 复用记录指向当前用户端口段之外的地址;未手动指定时自动分配应从 `10000-10099` 起步。
- 原因Linux dev 脚本会通过 `/var/tmp/genarrative-dev-port-ranges/registry.json` 做系统级端口段分配,避免两个用户配置相同或重叠端口段;同一用户后续启动会继续复用自己已经占用的固定端口段。注册表会保留该用户的段记录,不会因为多开而要求重新分配。
- 处理:先确认当前用户已经占用的端口段,再让后续 `npm run dev` / `dev:*` 继续沿用这段;如确实要切换段,手动释放或清掉对应 registry 记录后再重启。需要临时隔离测试时用 `GENARRATIVE_DEV_PORT_RANGE_REGISTRY_DIR=<tmp-dir>` 覆盖注册表目录。不要在 Windows 上按这个注册表排查Windows 仍走原有端口探测与漂移逻辑。未指定端口段时,系统会从 `10000-10099` 开始顺序分配。
- 验证:重新启动后终端应打印 `[dev] port-range: <start-end> (<user>)``[dev] port-range-registry: .../registry.json``node node_modules/vitest/vitest.mjs run scripts/dev-stack-port-utils.test.ts scripts/dev.test.ts` 应通过 Linux registry、自动分配 `10000-10099` 与 Windows bypass 用例。
- 关联:`scripts/dev-stack-port-utils.mjs``scripts/dev.mjs``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## SpacetimeDB 入口迁移 helper 合并时不要只保留调用
- 现象:`cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml` 或 Jenkins `Genarrative-Stdb-Module-Build``E0425 cannot find function migrate_rpg_entry_from_old_hidden_default in this scope`,位置在 `server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs` 的默认入口配置播种流程。
@@ -263,7 +310,7 @@
- 原因:通用图像输入是受控输入面板,不是只服务单页的临时实现;图片、提示词、参考图数组、重绘开关等业务真相应由外层页面持有,组件最多持有参考图预览、删除确认这类短生命周期 UI 状态。
- 处理:抽 `CreativeImageInputPanel` 时,保留上传卡、参考图入口、缩略图、预览弹层、删除确认和提交按钮的统一壳,但把主图文件读取、裁剪、历史素材、计费确认和具体提交动作留给外层页面;后续页面接入时只传业务回调和文案。
- 验证:拼图入口测试仍可通过,且新组件可通过不同页面复用而不需要复制上传卡实现。
- 关联:`src/components/common/CreativeImageInputPanel.tsx``src/components/puzzle-agent/PuzzleAgentWorkspace.tsx`
- 关联:`src/components/common/CreativeImageInputPanel.tsx``src/components/unified-creation/workspaces/PuzzleCreationWorkspace.tsx`
## RPG 发布不能只依赖 agent session seed_text
@@ -315,6 +362,7 @@
## Windows provision 下载截断要断点续传而不是回退目标机下载
- 当前状态已废弃。2026-06-01 起生产 Jenkins 流水线统一切到 Linux agent`Genarrative-Server-Provision` 不再维护 Windows 下载阶段。
- 现象:`Genarrative-Server-Provision``Download Provision Tool Archives` 阶段出现 `curl: (18) end of response ... bytes missing`,常见于 `otelcol-contrib_0.151.0_linux_amd64.tar.gz` 等 GitHub release 大文件。
- 原因:这是 Windows Jenkins 节点到 GitHub 的响应体被截断;若每轮都删除 `.download` 临时文件,就会丢掉已下载部分,下一次又从头开始。
- 处理Windows 下载函数保留 `${Output}.download``curl` 失败时下一轮使用 `-C -` 断点续传;最终只以 GitHub release asset 的 SHA256 `digest` 作为放行条件,完整返回但 digest 不匹配才删除临时文件重新下载。不要把 SpacetimeDB 或 `otelcol-contrib` 下载挪回 Linux 目标机。
@@ -353,12 +401,11 @@
- 验证:`tr '\0' '\n' < /proc/$(systemctl show genarrative-api.service -p MainPID --value)/environ | grep GENARRATIVE_TRACKING_OUTBOX_DIR` 应指向 `/var/lib/genarrative/tracking-outbox`;重启后当前 PID 不再出现 `Permission denied (os error 13)`
- 关联:`scripts/deploy/production-api-deploy.sh``scripts/jenkins-server-provision.sh``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## 外部 API 失败没法追溯先查 external_api_call_failure
- 现象VectorEngine 图片生成 / 编辑接口对前端只表现为 `502` / `504` 或“上游服务请求失败”,但难以区分是请求发送失败、上游 429/5xx、响应解析失败、未返回图片还是下载图片失败。
- 原因:外部 API 失败如果只靠普通日志,不一定能和 OTLP 指标、trace 与 SpacetimeDB 历史查询稳定关联;重启后也容易丢失上下文。
- 处理:先查 OTLP 指标 `genarrative.external_api.failures{provider,failure_stage,status_class,retryable}`,再查 `tracking_event``event_key = 'external_api_call_failure'``metadata_json`。当前通用 VectorEngine `gpt-image-2-all` 适配器会记录 provider、endpoint、operation、failureStage、statusCode、statusClass、timeout、retryable、errorMessage、latencyMs、promptChars、referenceImageCount、imageModelrawExcerpt。
- 处理:先查 OTLP 指标 `genarrative.external_api.failures{provider,failure_stage,status_class,retryable}`,再查 `tracking_event``event_key = 'external_api_call_failure'``metadata_json`。当前通用 VectorEngine `gpt-image-2-all` 适配器会记录 provider、endpoint、operation、failureStage、statusCode、statusClass、timeout、retryable、errorMessage、errorSource、latencyMs、promptChars、referenceImageCount、imageModelrawExcerpt 和 requestId
- 验证:`SELECT event_id, scope_id AS provider, metadata_json, occurred_at FROM tracking_event WHERE event_key = 'external_api_call_failure' ORDER BY occurred_at DESC LIMIT 50;`;如果查不到同时看 tracking outbox 目录权限和 sealed 文件是否堆积。
- 关联:`server-rs/crates/api-server/src/external_api_audit.rs``server-rs/crates/api-server/src/openai_image_generation.rs``docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
@@ -553,10 +600,18 @@
- 现象:用户通过“忘记密码”重设密码后,接口返回成功或页面进入登录态,但再次使用新密码登录仍提示“手机号或密码错误”;重启后还可能出现 `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` 成功后必须同步认证快照。2026-05-27 起,启动恢复只允许从 SpacetimeDB 正式认证表恢复;`auth_store_snapshot` 只保留行级记录,不再写 `default` 聚合单行,也不再把本地文件 `auth-store.json` / `GENARRATIVE_AUTH_STORE_PATH` 当作恢复源。若启动时连不上 SpacetimeDB`api-server` 等待启动恢复超时后进入依赖不可用模式,所有请求返回 `503 SERVICE_UNAVAILABLE``details.reason = "spacetime_startup_unavailable"`
- 处理:`POST /api/auth/password/change``POST /api/auth/password/reset` 成功后必须同步认证快照。2026-05-27 起,启动恢复只允许从 SpacetimeDB 正式认证表恢复;`auth_store_snapshot` 只保留行级记录,不再写 `default` 聚合单行,也不再把本地文件 `auth-store.json` / `GENARRATIVE_AUTH_STORE_PATH` 当作恢复源。认证创建、登录会话、刷新、退出、改密、重置密码、绑定和资料变更等写操作必须在返回客户端前成功同步 SpacetimeDB同步失败时接口返回错误不允许把只存在于当前进程内存的账号或会话当成成功结果。新用户注册奖励、邀请码绑定和登录埋点必须排在认证同步成功之后避免认证没落库时先写出钱包或邀请关系。若启动时连不上 SpacetimeDB`api-server` 等待启动恢复超时后进入依赖不可用模式,所有请求返回 `503 SERVICE_UNAVAILABLE``details.reason = "spacetime_startup_unavailable"`
- 验证:执行 `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`
## 密码登录失败且短信登录提示手机号已存在先查孤儿手机号索引
- 现象:老账号用密码登录提示“手机号或密码错误”,改用短信验证码登录又提示“手机号已存在 / 已注册”,用户卡在既不能登录也不能重新创建的状态。
- 原因:历史版本或停服务时认证同步不完整,可能在 SpacetimeDB `auth_identity(provider=phone)``module-auth` 快照里留下 `phone_to_user_id` 映射,但对应 `user_account` / `users_by_username` 用户行已经不存在。密码登录按手机号索引找不到真实用户,短信登录尝试创建新用户时又被孤儿手机号索引挡住。
- 处理:`export_auth_store_snapshot_from_tables` 导出时必须过滤没有 `user_account` 的 phone / wechat identity、union 索引和 refresh session`module-auth` 从 JSON 快照恢复时也必须二次丢弃指向不存在用户的索引。运行时创建手机号用户前若发现手机号映射指向不存在的用户,应删除孤儿映射后继续创建,避免死锁态继续扩散。
- 验证:`cargo test -p module-auth snapshot_json_drops_orphan_phone_index_before_phone_login --manifest-path server-rs/Cargo.toml``cargo test -p module-auth phone --manifest-path server-rs/Cargo.toml``cargo test -p spacetime-module auth --manifest-path server-rs/Cargo.toml``cargo test -p api-server phone_login_reuses_existing_user_for_same_phone_number --manifest-path server-rs/Cargo.toml`
- 关联:`server-rs/crates/module-auth/src/lib.rs``server-rs/crates/spacetime-module/src/auth/procedures.rs``docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
## 认证本地文件快照已废弃,旧 procedure 也已删
- 现象:有些旧代码和生成 bindings 里还会残留 `get_auth_store_snapshot``upsert_auth_store_snapshot``import_auth_store_snapshot`,或者把 `auth-store.json` 误当成认证恢复源。
@@ -701,7 +756,7 @@
- 原因:首图生成只通过 `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`
- 关联:`src/components/unified-creation/workspaces/PuzzleCreationWorkspace.tsx``src/components/puzzle-result/PuzzleResultView.tsx``server-rs/crates/api-server/src/puzzle.rs`
## 拼图首图生成后要把入口参考图写回 `pictureReference`
@@ -763,8 +818,8 @@
- 现象RPG 结果页点击开局 CG 后,`POST /api/runtime/custom-world/opening-cg` 在较长等待后返回“开局 CG 故事板生成失败创建图片生成任务失败error sending request for url (https://api.vectorengine.ai/v1/images/generations)”。
- 原因:该故事板会把角色图和首幕背景图作为参考图一起传给 VectorEngine `gpt-image-2-all`,请求体和上游生成耗时都比普通单图更大;若运行中的 `api-server` 仍沿用旧 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`,或者参考图过大,会在请求发送/等待阶段被 reqwest 截断。日志里 `timeout=false connect=false request=true body=false source=client error (SendRequest)` 表示还没拿到上游 HTTP 响应,通常优先怀疑大 JSON 请求体、上游网关中断或 HTTP 协议兼容,而不是业务响应解析失败。直接请求 VectorEngine 若无效 token 可快速返回 401不能据此判断真实生图不会超时。
- 处理:开局 CG 参考图入参先压到单边 768 的 JPEG`/v1/images/generations` 保持 reqwest 默认 HTTP 协商,只有 multipart `/v1/images/edits` 单独强制 HTTP/1.1。后端图片 helper 将 `request_body_bytes`、每张参考图 Data URL 长度、`timeout/connect/body/source/rootSource/sourceChain/endpoint` 分类写入日志和 `error.details`前端优先展示 `details.reason`。修改 `.env.secrets.local` 后必须重启 `api-server``npm run dev` 终端用 `rs api-server`,否则旧进程仍按旧超时运行。
- 验证:分别运行 `cargo test -p api-server custom_world_ai --manifest-path server-rs/Cargo.toml``cargo test -p api-server openai_image_generation --manifest-path server-rs/Cargo.toml`;真实联调重启后再触发开局 CG若仍失败看返回的 `details.reason/source/rootSource/sourceChain/timeout/connect/body/endpoint``logs/api-server/` 同一 request_id。
- 处理:开局 CG 参考图入参先压到单边 768 的 JPEG`/v1/images/generations` 保持 reqwest 默认 HTTP 协商,只有 multipart `/v1/images/edits` 单独强制 HTTP/1.1。后端图片 helper 将 `timeout/connect/body/source/source_chain/source_chain_depth/endpoint` 分类写入日志和 `error.details`失败审计通过 `metadata_json.errorSource/requestId` 保留底层错误链和请求标识。修改 `.env.secrets.local` 后必须重启 `api-server``npm run dev` 终端用 `rs api-server`,否则旧进程仍按旧超时运行。
- 验证:分别运行 `cargo test -p api-server custom_world_ai --manifest-path server-rs/Cargo.toml``cargo test -p api-server openai_image_generation --manifest-path server-rs/Cargo.toml`;真实联调重启后再触发开局 CG若仍失败看返回的 `details.errorSource/source/timeout/connect/body/endpoint``tracking_event.metadata_json.errorSource/requestId``logs/api-server/` 同一 request_id。
- 关联:`server-rs/crates/api-server/src/custom_world_ai.rs``server-rs/crates/api-server/src/custom_world_ai/opening_cg.rs``server-rs/crates/api-server/src/openai_image_generation.rs``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## 开局 CG 成功后又变空白要保留 profile.openingCg
@@ -1146,13 +1201,13 @@
- 验证:发布链路使用当前 `deploy/systemd``deploy/nginx``scripts/deploy``jenkins/Jenkinsfile.production-*`
- 关联:`docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`
## Release Web 产物通过内网 rsync 拉取
## Web Deploy 只从 Jenkins 构建归档取包
- 现象:`Genarrative-Web-Deploy` 发布到 `release` 目标时,release agent 本地没有 `/var/cache/genarrative-build/web-artifacts/<job>/<build>/<version>/web.tar.gz`,但 Jenkins controller 又只归档轻量元数据,导致发布阶段找不到 Web 大包
- 原因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`
- 现象:`Genarrative-Web-Deploy` 需要发布 Web 时,不应再在构建机或 release agent 本地缓存目录查找 `web.tar.gz`
- 原因Web 发布包已经由 `Genarrative-Web-Build` 归档到 Jenkins 构建产物deploy 阶段继续读本地缓存或通过 `rsync` 回构建机拉包会让 release agent 依赖机器拓扑和本地路径
- 处理:`Genarrative-Web-Build` 直接归档 `build/<version>/web.tar.gz``web.tar.gz.sha256``release-manifest.json``Genarrative-Web-Deploy` 使用 `copyArtifacts` 从指定 `BUILD_JOB_NAME` / `BUILD_NUMBER_TO_DEPLOY` 复制完整产物,不保留 `WEB_ARTIFACT_ROOT``WEB_ARTIFACT_SYNC_HOST``web-artifact-pointer.txt` 口径
- 验证:deploy 工作区应直接出现 `build/<version>/web.tar.gz``web.tar.gz.sha256`;后续仍由 `scripts/deploy/production-web-deploy.sh` 执行 checksum 校验和解压 smoke
- 关联:`jenkins/Jenkinsfile.production-web-deploy``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## Jenkins 生产流水线拉 Git 先本机再域名备用
@@ -1178,12 +1233,12 @@
- 验证:运行 `git ls-files --stage scripts/prepare-server-provision-tools.sh`,确认 mode 为 `100755`;重新跑 `Genarrative-Server-Provision` 时应进入工具下载/打包日志,而不是停在 `Permission denied`
- 关联:`jenkins/Jenkinsfile.production-server-provision``scripts/prepare-server-provision-tools.sh``scripts/jenkins-checkout-source.sh``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## Server-Provision 下载阶段不要放回 genarrative-build-01
## Server-Provision 工具准备只在 Linux build 节点做一次
- 现象:`Genarrative-Server-Provision` 日志里 `Prepare Provision Tools` 显示 `Running on genarrative-build-01 in /root/...`,随后在该节点上下载 GitHub release `install.spacetimedb.com` 失败
- 原因:`genarrative-build-01` 在当前 provision 流程里是 Linux 目标发布机/目标 agent不是用户本地 Windows 下载环境;把下载阶段放在 `linux && genarrative-build` 等于让目标机自己外连
- 处理:下载必须发生在 Jenkins `windows` 节点 `Download Provision Tool Archives` 阶段,先下载 SpacetimeDB Linux release tarball 和 `otelcol-contrib` Linux amd64 包,再 `stash/unstash` 到目标 Linux 节点。目标机执行 `scripts/prepare-server-provision-tools.sh` 时设置 `PROVISION_REQUIRE_LOCAL_DOWNLOADS=true`,缺少下载件直接失败,不回退联网下载
- 验证Jenkins 日志应先出现 `Running on ... windows``[prepare-provision-downloads] 下载 ...`,目标节点只出现 `[prepare-provision-tools] 使用已下载的 ...`;如果目标节点出现 `下载 otelcol-contrib:``下载 SpacetimeDB 官方安装器脚本:`,说明又回退到错误路径
- 现象:`Genarrative-Server-Provision` 在后续目标发布节点重复执行 `scripts/prepare-server-provision-tools.sh`,或日志里出现目标节点继续访问 GitHub release / `install.spacetimedb.com`
- 原因:当前流水线已经改成 Linux build 节点一次性准备 `provision-tools/` 并 stash 给目标发布阶段;如果目标发布阶段又重新准备工具包,就会重复下载并把目标节点暴露到外网依赖
- 处理:只允许 `Prepare Provision Tools` 阶段在 `linux && genarrative-build` 节点生成 `provision-tools/`;后续 `Provision Server` 阶段`unstash 'server-provision-tools'` 并安装其中的 `spacetime``otelcol-contrib`,不要再运行 `prepare-server-provision-tools.sh`
- 验证Jenkins 日志应先在 Linux build 节点出现 `[prepare-provision-tools] 工具包已准备`,后续目标发布节点只出现安装 / systemd / Nginx provision 日志;目标节点不应出现 `下载 otelcol-contrib:``下载 SpacetimeDB 官方安装器脚本:`
- 关联:`jenkins/Jenkinsfile.production-server-provision``scripts/prepare-server-provision-tools.sh``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## 个人任务 scope 不得扩成 work/site/module
@@ -1463,16 +1518,16 @@
- 现象:拼图创作页或结果页打开“选择历史图片”后,历史列表显示 `账号 user-1` 之类归属文案而不是图片名;`1713686400.000000Z` 这类时间显示为未知;选中后预览或生成参考图可能被怀疑不可用。
- 原因:`/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`
- 验证:`npm run test -- src/components/unified-creation/workspaces/PuzzleCreationWorkspace.interaction.test.tsx src/components/puzzle-result/PuzzleResultView.test.tsx`,并执行 `npm run check:encoding`
- 关联:`src/services/puzzle-works/puzzleHistoryAsset.ts``src/components/unified-creation/shared/PuzzleHistoryAssetPickerDialog.tsx``docs/technical/ASSET_HISTORY_PUZZLE_COVER_KIND_FIX_2026-04-27.md`
## 拼图历史图关闭 AI 重绘不要强制 Data URL
- 现象:拼图创作页从历史生成图片中选择主图,再关闭 AI 重绘生成草稿时,后端报“上传图必须是图片 Data URL”。
- 原因:历史图 `imageSrc``/generated-puzzle-assets/...` 私有兼容路径AI 重绘开启时后端参考图分支会解析该路径,但关闭 AI 重绘的“直用上传图”分支旧实现只调用 `parse_puzzle_image_data_url`
- 处理:关闭 AI 重绘时也复用拼图参考图解析入口,允许 Data URL 与 `/generated-*` 历史路径统一转成 `PuzzleDownloadedImage` 后持久化;前端不需要下载历史图再转 base64。
- 验证:`npm run test -- src/components/puzzle-agent/PuzzleAgentWorkspace.interaction.test.tsx src/components/puzzle-result/PuzzleResultView.test.tsx``cargo test -p api-server puzzle_uploaded_cover_can_reuse_resolved_history_image --manifest-path server-rs\Cargo.toml``npm run dev:api-server` 后检查 `/healthz`
- 关联:`server-rs/crates/api-server/src/puzzle/draft.rs``server-rs/crates/api-server/src/puzzle/vector_engine.rs``src/components/puzzle-agent/PuzzleAgentWorkspace.interaction.test.tsx`
- 验证:`npm run test -- src/components/unified-creation/workspaces/PuzzleCreationWorkspace.interaction.test.tsx src/components/puzzle-result/PuzzleResultView.test.tsx``cargo test -p api-server puzzle_uploaded_cover_can_reuse_resolved_history_image --manifest-path server-rs\Cargo.toml``npm run dev:api-server` 后检查 `/healthz`
- 关联:`server-rs/crates/api-server/src/puzzle/draft.rs``server-rs/crates/api-server/src/puzzle/vector_engine.rs``src/components/unified-creation/workspaces/PuzzleCreationWorkspace.interaction.test.tsx`
## 拼图结果页局部生图不要污染草稿生成态
@@ -1490,7 +1545,7 @@
- 原因:上传图直用路径应把 Data URL 或 `/generated-*` 历史图解析后持久化为 `sourceType=uploaded` 的正式候选,再继续生成 9:16 关卡画面、UI spritesheet 和纯背景;如果只把 `aiRedraw=false` 当作“不参考图片生成”,就会误走首图生成。
- 处理:入口页用 payload 的 `aiRedraw` 写入生成页 metadata`puzzleAiRedraw=false` 时进度跳过 `生成拼图首图`;后端 `compile_puzzle_draft` 和结果页 `generate_puzzle_images` 都在 `aiRedraw=false && referenceImageSrc 非空` 时走上传图直用候选。结果页关卡详情必须复用 `CreativeImageInputPanel`,不要把正式图当成可重绘参考图;本次上传或历史选择的图才显示 AI 重绘开关并可删除。
- 验证:`npm run test -- src/services/miniGameDraftGenerationProgress.test.ts src/components/puzzle-result/PuzzleResultView.test.tsx``cargo test -p api-server puzzle_result_level_direct_upload_skips_cover_image_generation --manifest-path server-rs\Cargo.toml`
- 关联:`src/services/miniGameDraftGenerationProgress.ts``src/components/puzzle-agent/PuzzleAgentWorkspace.tsx``src/components/puzzle-result/PuzzleResultView.tsx``server-rs/crates/api-server/src/puzzle/draft.rs``server-rs/crates/api-server/src/puzzle/generation.rs`
- 关联:`src/services/miniGameDraftGenerationProgress.ts``src/components/unified-creation/workspaces/PuzzleCreationWorkspace.tsx``src/components/puzzle-result/PuzzleResultView.tsx``server-rs/crates/api-server/src/puzzle/draft.rs``server-rs/crates/api-server/src/puzzle/generation.rs`
## Jenkins 数据库导入导出脚本先补 Node 工具链 PATH
@@ -1502,6 +1557,7 @@
## Windows Jenkins `powershell` step 在 Stdb module 构建里曾触发 CreateProcess error=5
- 当前状态:已废弃。`Genarrative-Stdb-Module-Build` 已切到 Linux agent不再执行 Windows PowerShell 流程。
- 现象:`Genarrative-Stdb-Module-Build` 在 Windows Jenkins 节点上报 `java.io.IOException: Cannot run program "powershell" (in directory "C:\\Users\\DSK\\.jenkins-local\\workspace\\Genarrative-Stdb-Module-Build"): CreateProcess error=5, 拒绝访问。`;日志里能看到 `durable-task` 已写出 `powershellWrapper.ps1`,但在真正启动裸 `powershell` 子进程时失败。
- 原因Jenkins durable-task 的 `powershell` step 依赖一个隐式命令解析/启动路径,在这台 Windows 本地 Jenkins 环境里会被拒绝。`powershell.exe` 本体和 workspace ACL 都是正常的,问题出在 Jenkins step 的启动方式,而不是 PowerShell 脚本内容。修复后若日志能打印 `[jenkins-powershell] exe:`,但随后仅报 `拒绝访问` / `script returned exit code 5`,通常已经不是 PowerShell 启动失败,而是 Checkout 脚本内部命令在 Windows workspace 里触发权限拒绝。若 `.jenkins-*.ps1` 里中文 `throw '[stdb-build] ...'``MissingArrayIndexExpression`,则是 Windows PowerShell 5.1 用 `-File` 解析无 BOM UTF-8 脚本时按本地 ANSI 误解码。
- 处理:把 `jenkins/Jenkinsfile.production-stdb-module-build``Checkout``Build Stdb Module` 两处 `powershell` step 收口成 `runWindowsPowerShell(...)` helper先用 `writeFile` 写出临时 `.ps1`,再用显式 `powershell.exe` 把脚本重写成 UTF-8 with BOM最后通过 `%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -File ...` 执行。这个 helper 写在 Groovy GString 里时PowerShell 的 `$path` / `$text` / `$true` 必须写成 `\$path` / `\$text` / `\$true`,否则 Jenkinsfile 会在 Groovy 编译阶段报 `unexpected token: true`。Checkout 阶段优先复用 Jenkins GitSCM 已完成的工作区结果;`COMMIT_HASH` 为空或已经等于当前 `HEAD` 时不再重复 `git fetch` / `git checkout` / `git clean`,只有确实要切到另一个指定 commit 时才补 fetch、归属校验和 checkout。
@@ -1540,14 +1596,30 @@
- 验证:`npm run test -- src/components/rpg-entry/rpgEntryWorldPresentation.test.ts src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx``npm run typecheck``npm run check:encoding`
- 关联:`src/index.css``src/components/rpg-entry/RpgEntryHomeView.tsx``src/components/rpg-entry/rpgEntryWorldPresentation.ts``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 生成中草稿刷新后不要复用旧 updatedAt 当展示起点
## 公开作品卡作者行不要拼手机号或陶泥号
- 现象:拼图或抓大鹅草稿生成中刷新网页后,作品架卡片能显示等待遮罩,但进入生成页时总进度首帧直接跳到 80%+,看起来像已经跑了一大半
- 原因:前端只把持久化 `generationStatus=generating` 当作恢复生成页的条件,但恢复展示时仍沿用了作品摘要 `updatedAt` 作为伪 `startedAtMs`;同时拼图总进度又把后端 `progressPercent` 直接当作 floor导致 `86%` 之类未到首个里程碑的会话一进页就抬到 80%+
- 处理:恢复生成中的草稿时,展示起点改用“进入生成页的当前时间”;`updatedAt` 只保留给作品架排序和摘要,不再参与生成页假进度起算。拼图总进度还要忽略 `88` 以下的后端进度 floor拼图保留后端里程碑推进抓大鹅等非拼图玩法则从 `0%` 平滑起步,避免刚进页就看到 4% / 88% / 80%+
- 验证:`npm test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "persisted generating"``npm run test -- src/services/miniGameDraftGenerationProgress.test.ts -t "match3d draft generation starts total progress from zero"`
- 现象:发现页 / 推荐页公开作品卡作者行显示 `158****3533 · SY-00000003` 这类手机号掩码和陶泥号组合,列表卡片看起来像暴露账号标识
- 原因:`resolvePlatformWorkAuthorDisplayName(...)` 曾把公开昵称和 `publicUserCode` 拼接为 `昵称 · SY-*`,并在无法解析公开昵称时直接回退后端卡片里的 `authorDisplayName`;当后端或旧投影把手机号掩码写进展示名时,卡片会原样外露
- 处理:公开卡片作者名只取可读公开昵称;识别手机号掩码、单独 `SY-*``手机号掩码 · SY-*` 时回退为 `玩家`。作品号复制、陶泥号搜索和完整身份展示只放在详情页、搜索或明确复制入口,不塞进卡片作者行
- 验证:`npm run test -- src/components/rpg-entry/rpgEntryWorldPresentation.test.ts src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx src/components/platform-entry/PlatformWorkDetailView.test.tsx`
- 关联:`src/components/rpg-entry/rpgEntryWorldPresentation.ts``src/components/rpg-entry/RpgEntryHomeView.tsx``src/components/platform-entry/PlatformWorkDetailView.tsx``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 生成中草稿恢复要按后端时间戳计时
- 现象:拼图或抓大鹅草稿生成中刷新网页后,进入生成页的“已耗时”从 `0 秒` 重新开始;另一类旧问题是后端 `progressPercent=88` 时总进度首帧直接跳到 `88%`
- 原因:生成页恢复曾把展示态 `startedAtMs` 重置为进入页面的当前时间,导致计时不跟随后端真实生成时刻;拼图总进度也曾把后端里程碑当作百分比地板,导致步骤刚切换就抬高总进度。
- 处理:恢复生成中的草稿时,展示起点使用后端 session `updatedAt` 或作品摘要 `updatedAt``88/94/96` 只切换当前步骤,不直接作为总进度地板。总进度按已完成步骤权重加当前步骤内假进度推导,非完成态最多停在 `98%`
- 验证:`node node_modules/vitest/vitest.mjs run src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "persisted generating"``node node_modules/vitest/vitest.mjs run src/services/miniGameDraftGenerationProgress.test.ts`
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx``src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx``src/services/miniGameDraftGenerationProgress.ts``docs/【玩法创作】拼图生成页进度口径-2026-05-23.md`
## 生成失败草稿回到作品架不能继续显示生成中
- 现象:拼图生成页已经收到 VectorEngine 图片编辑失败并进入重试态,但用户返回草稿 Tab 后同一草稿仍显示“生成中”连续触发多个拼图生成时失败后还可能只剩一条新增草稿或者只看到标题为“第1关”的半成品空壳抓大鹅后台失败时也可能没有任何通知点击草稿又像重新开始生成。
- 原因:前端失败 notice 只更新生成页局部状态pending 作品架条目在失败时被清掉或被非 `generating` 状态误映射为 `ready`;后端作品摘要也可能短暂仍是 `generationStatus=generating`。如果失败消息没有写入 notice用户离开生成页后不会弹出 `PlatformErrorDialog`;如果打开草稿只看持久化 `generating`,就会绕过失败态恢复。
- 处理:失败时按 session 保留 pending 作品架条目并标记 `failed`,失败 notice 保存错误消息并触发带来源的 `PlatformErrorDialog`;拼图契约没有 `failed` 枚举pending 拼图映射为 `idle`,同时用本地失败 notice 覆盖持久化生成中状态和旧的“正在生成”摘要。点击失败草稿应优先用 notice / 后端 session / fallback payload 组装失败生成页,不能重新从 0 秒启动新进度;拼图失败半成品没有有效 `workTitle` 时,作品架标题回退为“拼图草稿”。
- 验证:`node node_modules/vitest/vitest.mjs run src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "failed parallel puzzle|background match3d"`
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx``src/components/custom-world-home/creationWorkShelf.ts``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 汪汪声浪草稿试玩不要写正式 run
- 现象:如果草稿结果页试玩和发布后 runtime 共用同一写成绩路径,未发布或未确认资源的草稿试玩会污染正式单局、排行榜和作品统计。
@@ -1561,6 +1633,13 @@
- 现象:移动端创作 Tab 里进入汪汪声浪表单后,页面右侧出现不自然的内层滚动条,最后的形象描述输入框容易被“生成草稿”按钮、键盘或底部 TabBar 挤压 / 遮挡;顶部玩法卡首尾也可能贴边显得被裁。
- 原因:外层 `.platform-tab-panel` 已经是纵向滚动容器,创作页中间又有多层 `overflow-hidden`,旧的 `BarkBattleConfigEditor` 根节点再加 `overflow-y-auto`,形成外层 Tab 面板 + 内层表单的套滚动;底部按钮只预留 safe-area不预留真实操作区距离顶部玩法卡横向滚动条隐藏且首尾没有 scroll padding。
- 处理:移动端让 Bark Battle 表单跟随父级滚动,`lg` 以上才恢复表单内滚动;创作页容器移动端使用 `overflow-visible` 和 safe-area 底部 padding顶部模板 tablist 加 `scroll-px-3` / 横向 padding移动端卡片宽度收窄避免首尾 ring 和圆角贴边裁切。
## 统一创作页不要把竖屏滚动锁进内部内容区
- 现象:竖屏打开拼图、抓大鹅或敲木鱼创作页时,浏览器页面本身无法滚动,生成按钮或右侧表单面板落到视口外;木鱼的敲击音效和功德词条看起来像被塞进单独滑动窗口。
- 原因:平台根壳固定一屏并隐藏溢出,`UnifiedCreationPage` 又使用 `h-full min-h-0 overflow-hidden` 和内容区 `overflow-y-auto`,导致滚动责任落到内部内容窗,而不是整个创作 stage。
- 处理:`UnifiedCreationPage` 统一负责标题、隐藏字段契约、内容包装和页面级纵向滚动;拼图、抓大鹅、跳一跳和敲木鱼的外层 `motion.div` 不再额外包 `overflow-y-auto`。各工作台在 `unifiedChrome` 下收起旧 `h-full overflow-hidden` 外壳,让表单主体跟随统一页面滚动。
- 验证:用竖屏浏览器视口打开 `/creation/wooden-fish``/creation/puzzle``/creation/match3d``/creation/jump-hop`,统一创作页应可滚动到生成按钮;`.unified-creation-page` 应包含页面级 `overflow-y-auto`,木鱼工作台内部也不应出现独立纵向滚动容器,拼图 / 抓大鹅可见标题不应重复。
- 验证:`npm run test -- src/components/bark-battle-creation/BarkBattleConfigEditor.test.tsx``npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "create tab shows template tabs"`、移动端视口检查最后一个输入框与“生成草稿”按钮不重叠。
- 关联:`src/components/bark-battle-creation/BarkBattleConfigEditor.tsx``src/components/platform-entry/PlatformEntryFlowShellImpl.tsx``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
@@ -1620,6 +1699,14 @@
- 验证:移动端视口检查视频 `rect` 应覆盖整个视口,`paused` 应最终变为 `false``currentTime` 应持续前进。
- 关联:`src/components/GenerationProgressHero.tsx``docs/【玩法创作】生成页圆环布局口径-2026-05-23.md`
## 跳一跳结果页直达时不要把恢复面板当成空白页
- 现象:浏览器直接打开 `/creation/jump-hop/result`,如果没有 `sessionId``profileId``draftId``workId`,页面以前会看起来像空白,容易误判成结果页坏了。
- 原因:跳一跳结果页恢复原先只盯 `jumpHopSession.draft`,没有把“缺恢复信息”明确兜成可见恢复面板;直达结果页时也没有优先用 `profileId -> getWorkDetail` 补回完整作品。
- 处理:`PlatformEntryFlowShellImpl` 的跳一跳恢复逻辑改成先尝试 `profileId -> getWorkDetail`,再尝试 `sessionId -> getSession`;两者都没有时显示 `跳一跳草稿未恢复``返回创作`,不再留空白页。
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "direct jump hop result route"`,并手测 `/creation/jump-hop/result``/creation/jump-hop/result?profileId=<id>` 两种情况。
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx``src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx``docs/planning/【玩法创作】创作流程统一总计划-2026-05-30.md`
2026-05-24 补充:`GenerationPageBackdrop` 不要通过 portal 挂到 `document.body`。body 级 fixed 背景会逃离生成页自己的 stacking context即使业务内容有局部 `z-10`,真实浏览器里也可能把整页 UI 压住。背景视频应作为生成页根容器子节点保留 `fixed inset-0 z-0`,生成页内容保持 `relative z-10`;相关测试应同时断言背景容器低层级、生成页根容器高层级,以及视频节点仍在生成页 DOM 内部。视觉调整时还要记住:空心圆环的中心块要抽掉,时间卡与总进度标题都应缩小,不要让生成页再回到“纯色底 + 大字号说明卡”的状态。顶部返回和右上状态也不能沿用 `text-lg` / `sm:text-2xl` 这类展示级字号;当前步骤名、步骤状态和底部玩法信息标题要维持普通 UI 字号档位,优先保持 `text-xs``text-sm` 区间。
2026-05-24 补充:生成页“预计等待 / 已耗时”卡片本身已经有标签,传给 `GenerationProgressHero` 的值只能是纯时间,例如 `4 分钟``1 分 15 秒`,不要再拼接“预计还需”或“已耗时”;两张时间卡也要和当前步骤卡一样保持半透明。拼图总进度初始帧必须允许显示 `0%`,不要再用 `Math.max(1, nextProgress)` 之类的保护把启动态抬到 `1%`