From 15871914f734d09d3a293aae2ca5b43c750a123a Mon Sep 17 00:00:00 2001 From: Suzumiya Date: Wed, 7 Oct 2026 00:51:53 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A7=BB=E5=8A=A8=E7=AB=AF=E9=97=A8=E9=99=90?= =?UTF-8?q?=EF=BC=9A=E5=8F=AA=E8=AF=BB=E6=B5=8F=E8=A7=88=20stage=EF=BC=88?= =?UTF-8?q?=E6=97=8F=E8=B0=B1/=E5=85=B1=E5=88=9B=E4=B8=BB=E9=A2=98?= =?UTF-8?q?=EF=BC=89=E8=B1=81=E5=85=8D=E6=AC=A2=E8=BF=8E=E5=BC=B9=E7=AA=97?= =?UTF-8?q?=EF=BC=9B=E5=90=8C=E6=AD=A5=E8=A7=84=E8=8C=83=20=C2=A73.6.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...�技术方案】游戏共创与作品Fork-2026-10-03.md | 43 +++++++++++++-- ...PlatformActiveMobileWelcomeDialog.test.tsx | 52 +++++++++++++++++++ .../PlatformActiveMobileWelcomeDialog.tsx | 23 +++++++- .../PlatformEntryActiveFlowShell.test.tsx | 37 +++++++++++++ 4 files changed, 150 insertions(+), 5 deletions(-) diff --git a/docs/【技术方案】游戏共创与作品Fork-2026-10-03.md b/docs/【技术方案】游戏共创与作品Fork-2026-10-03.md index 4f9834838..a133e6afd 100644 --- a/docs/【技术方案】游戏共创与作品Fork-2026-10-03.md +++ b/docs/【技术方案】游戏共创与作品Fork-2026-10-03.md @@ -137,7 +137,7 @@ stateDiagram-v2 | --- | --- | | `/games/mine` 每张作品卡 | 新增「共创授权」三态设置(**只有母版**有该入口:仅允许提升,终态 `full` 时只读;**衍生作品的档位由父作品继承、是终态**,不提供该入口——入口隐藏已落地(`4f888d8c9`:网页端改为只读展示「继承自父作品」),服务端即使收到 `PUT` 也一律 409 `FORK_AUTHORIZATION_INHERITED`);新增「被改编 N」入口,弹层列出直接子代(标题 / 作者 / 代际 / 状态);提升授权后若当前公开版本没有工程源包,行内提示「上传工程源码以支持源码级改造」(上传在桌面端客户端完成,网页端只做引导) | | `/games/publish`、AGC 发布面板 | 新增「授权共创」三态单选(默认「禁止共创」,页面提示:开启后可被他人复刻改编,开启后不可撤销);**上架时随创建请求一起提交**(`forkAuthorization`),不必事后补一次提升;从父作品 Fork 而来时显示只读的「改编自《X》」**且档位由服务端继承父作品当时的档位**——该场景下客户端所选档位不生效(不报错,见 §2.3),且**该作品从此没有「修改授权」入口**(终态);AGC 发布面板同样只读展示「授权继承自父作品」(同轮改动) | -| AGC 客户端 | ① 详情页「改造这个作品」唤起 AGC(唤起方式**待拍板**,候选见 §7 第 10 条;`genarrative://fork?gameId=` 的 deep link 目前**未注册**,只是候选之一);② AGC 首页/项目入口提供「从平台作品开始创作」(输入 gameId 或从平台跳转) | +| AGC 客户端 | ① 侧边栏「共创」页列出公开目录里 `forkAuthorization != forbidden` 的作品(卡片:封面 / 标题 / 作者 / 类型 / 创建时间),点卡片「Fork」→ 「Fork 工程确认页」;② `genarrative://fork?gameId=` deep link **已注册**(原生解析 + 聚焦主窗口),带 `gameId` 时**直接落到确认页**、不带时回到「共创」页并如实展示链接原因;③ 手动粘贴作品 ID 的输入块已**移除**(重复入口,口径与理由见 §3.6.2) | | `/games/detail` 详情页 | 新增「收藏」按钮(收藏 / 已收藏两态)。初始态取自公开详情的 `collected`(登录才有该字段);点击后 `PUT` / `DELETE /api/game-distribution/games/{gameId}/collection`,按钮态改用**响应里的权威投影值**(不写本地乐观状态——前端本地状态正是本功能要消灭的东西)。未公开 / 已软删除的作品返回 409,按钮按失败态提示 | | `/games/mine`(或「我的」入口)| 新增「我的收藏(收录)」列表:`GET /api/game-distribution/my-collections`,形状与广场一致(封面 / 标题 / 作者 / 游玩数),点击进详情。列表只含**当前公开可读**的作品;已下架作品的收藏行保留,作品重新公开后自动回来(不需要用户重新收藏) | @@ -196,7 +196,7 @@ sequenceDiagram API->>API: 校验授权/来源版本/计算 generation 与 root → 写 lineage ``` -> 说明:本图是**目标形态**。唤起方式尚未拍板(§7 第 10 条);`GET …/fork-source` **M2a 已实现**(受鉴权元数据 + 取件本体,实际字段与状态码见 §3.4「登录用户」表),但本图里的响应字段名是 M2b 的目标形状,定稿一律以 §3.4 表格为准。 +> 说明:本图是**目标形态**。唤起方式已定:**deep link `genarrative://fork?gameId=` 已注册并落地**(原生解析 + 单实例聚焦,带 `gameId` 直接落到 AGC 的 Fork 工程确认页,见 §3.6.2;§7 第 10 条的其余候选不再需要);`GET …/fork-source` **M2a 已实现**(受鉴权元数据 + 取件本体,实际字段与状态码见 §3.4「登录用户」表),但本图里的响应字段名是 M2b 的目标形状,定稿一律以 §3.4 表格为准。 **C. 溯源与命名** @@ -442,7 +442,7 @@ A 路线里有一个必须提前知道的互斥点:`create_npm_scaffold` 的 | `src/components/game-distribution/MyGamesPage.tsx` | 授权三态设置(只升,**仅母版**;衍生作品显示只读「继承自父作品」)、分类标签、「被改编 N」行内展开 | | `src/components/game-distribution/GamePublishPage.tsx` | 发布新版本的**「本次核心改动说明」**(衍生作品必填,20~500 字,前端同等校验) | | `src/services/gameDistributionClient.ts` | `setForkAuthorization` / `getGameLineage` / `getDerivedGames` / `setGameCollection` / `listMyCollections` / `listThemes` / `getTheme`;`getGame` 类型增量 | -| `apps/ai-game-creator-shell` | 发布面板授权选择;`genarrative://fork` deep link 注册;改造入口与进度/失败提示 | +| `apps/ai-game-creator-shell` | 「共创」列表页(公开目录过滤 + 卡片入口,无粘贴输入块)、Fork 工程确认页(父作品卡 / 强制阅读 3 秒 / 真实同步阶段 / 吸附底部操作条)、发布面板(授权三态 + 衍生作品只读「继承自父作品」 + 「本次核心改动说明」)、`genarrative://fork` deep link 注册、外壳滚动链(见 §3.6.2) | | `apps/admin-web/src/pages/AdminGameManagementPage.tsx` | 列表新增授权 / 代际列 | #### 3.6.1 交互口径(2026-10-06 补充) @@ -457,8 +457,32 @@ A 路线里有一个必须提前知道的互斥点:`create_npm_scaffold` 的 - **图谱视图为可选开关**:切到画布后支持拖拽平移 / 缩放;布局层支持 `orientation: 'horizontal'`(根在左、代际向右,兄弟分支沿纵向排开)——窄屏上比顶→下更省横向空间。 - **信息面板用 bottom sheet**:从底部升起(含简介 / 授权 / 核心改动说明 / 前往体验 / Fork 此版本),遮罩点击或关闭按钮收起;桌面保持画布右上浮层。 - 面包屑横向可滚动且**当前项常驻可见**;尊重 `prefers-reduced-motion`;底部留 `env(safe-area-inset-bottom)`。 + - **移动端门限豁免(2026-10-06)**:平台原有的「移动端仅支持作品展示」欢迎弹窗只看 `!isDesktopLayout`、忽略路由,会把公开只读的族谱树 / 共创主题页首屏盖住。现在按**显式白名单**豁免这三个 stage:`game-lineage` / `game-themes` / `game-theme`(清单在 `PlatformActiveMobileWelcomeDialog.tsx` 的 `MOBILE_WELCOME_EXEMPT_STAGES`,键名即 stage 名,不做前缀/范围匹配)。**创作 / 项目 / 发布 / 我的 / 平台首页仍保持门限**——那些才是「移动端不支持体验创作工具」的部分;弹窗文案未改(与豁免后的事实不矛盾:族谱与主题页本身就是作品展示)。 - **截断 / 空树 / 不可用父节点**在两种形态下都有降级:截断提示、空态、以及「原作品已不可用」的占位节点(可聚焦、不可点进详情、不展示被删作品标题与作者名)。 +#### 3.6.2 AGC 客户端:「共创」列表页与 Fork 工程确认页(2026-10-07 补充) + +对外口径只有一句,两处文案一字不差:**Fork = 把平台上的作品整个工程复制到你的项目里继续改造(像 git fork)**。 + +**入口只有两条** + +- **卡片**:`src/view/co-creation/index.tsx` 顺序扫公开目录(最多 3 页 × 50,够 12 个即停)过滤 `forkAuthorization !== 'forbidden'`,卡片点「Fork」→ 确认页。 +- **深链**:`genarrative://fork?gameId=` 由原生解析后 `WorkspaceLauncher` 直接 `open({origin:'deep-link'})` + 切到确认页;**不带 `gameId`** 的链接回到「共创」页并把链接原因原样显示成一条提示。 +- **已移除**:手动粘贴「作品 ID 或链接」的输入块(连同 `parsePlatformGameId` / `PLATFORM_FORK_INPUT_ERROR` 死代码)。理由:深链已覆盖「从平台过来」的场景,手动粘贴属于重复入口;删掉后**深链仍直达确认页**(回归用例:`tests/appSurface/home.suite.ts` 的「深链带 gameId 时直接落到确认页」)。 + +**Fork 工程确认页**(`src/view/fork-confirm/index.tsx`) + +- 结构:父作品信息卡(标题 / 作者 / 授权类型 / 作品 ID,缺项逐项降级)→ 授权须知与风险说明(**五条全文**:可修改范围 / 商用权限说明 / 署名要求 / 溯源规则 / 违规后果说明)→ 工程同步(四个**原生真实**阶段:取回作品内容 / 校验摘要与字节 / 解压到项目 / 生成工程与来源记录,不造百分比)→ 底部操作条。 +- **强制阅读 = 3 秒计时门禁**(`MANDATORY_READING_SECONDS`):徽章显示 `Ns 后可确认`,按钮文案带倒计时且 `disabled`;**不要求「把须知滚到底」**——须知块**没有内层滚动容器**,全文随页面自然流动(页面滚动由外壳统一承担,见下)。 +- **底部操作条吸附**:`position: sticky; bottom: 0`(`FORK_STICKY_FOOTER`)挂在页面根下,滚动容器是 `.launcher-main`,因此长页面下「取消 / 确认并进入编辑」始终可见可点。 +- **卡片时间字段如实**:公开目录 payload 只有 `createdAt`(无 `updatedAt`),卡片就写「创建时间」并按本地时区格式化为 `YYYY-MM-DD`;字段缺失或不可解析时**整行不渲染**,不留恒为「—」的死行。 + +**外壳滚动链(全应用一次修好,不再每页打补丁)** + +- 唯一页面滚动容器 = **`.launcher-main`**(`src/styles.css`):`display:flex; flex-direction:column; min-height:0; overflow-y:auto; overflow-x:hidden`,视图统一 `flex: 0 0 auto` 保持自然高度;链路上 `#root`(`100dvh`)、`.window-chrome__content`(`height:100%` + `min-height:0` + `overflow:hidden`)、`.launcher-shell`(`height:100%` + `min-height:0`)逐层都给足高度并允许收缩。 +- **改动前的断点就在这一层**:`.launcher-main` 原是 `display:grid` + `grid-template-rows: auto auto auto` + `overflow:hidden`——行高被内容撑到 1494px、容器只有 570px,`overflow:hidden` 又让用户滚不动(实测:`overflow-y: hidden`、`userScrollable=false`、最后一行 `bottom=1512 > viewport 620`)。因此「共创」列表最后一行与确认页底部按钮都被裁掉、够不到。 +- **版式令牌**:两页共用 `src/features/platform-fork/forkPageStyles.ts`(内容列 `min(860px, 100vw - 124px)`、正文 `72ch` 上限、4/8 步进间距、状态用**徽章**而非灰字、只用 `packages/shared/src/theme.css` 既有 `--platform-*` 变量、`:focus-visible` 统一 `3px solid var(--platform-warm-text)`)。 + ### 3.7 灰度与可观测 - **M1 实现口径**:共创写入复用现役 `game-distribution:publish` 开关(`ensure_publish_enabled`),不新增独立灰度键——本功能与发布能力同批上线,先少一个开关减少漂移面。独立键 `game-distribution:fork`(含 `/api/runtime/frontend-config` 的 `gameDistributionForkEnabled` 与前端入口门禁)作为后续增强保留,接入前需要同时改 `module-runtime`、`api-server` 与前端配置读取。 @@ -983,6 +1007,17 @@ procedure 另要求**受信服务身份**(归属判定靠 `owner_user_id`, 实测固定口径(可直接复用):pinned 端口 `--api-port 8188 --bgfilter-worker-port 8189 --spacetime-port 3110 --admin-web-port 3111 --web-port 3112`;管理员 `admin / 123456`;Playwright 走临时目录(`E2E_PLAYWRIGHT_DIR`)+ 本机 Chromium(`E2E_CHROMIUM_EXECUTABLE`);断言口径一律按**符号**引用实现(不写行号),详见各脚本头部注释。 +### 5.7 AGC 客户端共创页 / Fork 确认页与外壳滚动链(2026-10-07)实施证据 + +| 项 | 证据 | 状态 | +| --- | --- | --- | +| 滚动链断点(改动前的实测) | 真实 Chromium(1440×900 / 1000×620 / 800×600)逐层量计算值:`#root` 620(h)/`min-height:0`、`.window-chrome__content` 620/`hidden`、`.launcher-shell` 570/`min-height:0`、**`.launcher-main` `display:grid` + `grid-template-rows: 1494px 0 0` + `overflow-y:hidden`**、`.fork-page` 1494。用户滚不动(`overflow-y: hidden`)、最后一行 `bottom=1512 > viewport 620` | ✅ 已验证(A/B:注入旧规则复现,见下) | +| 修后行为 | `.launcher-main` `display:flex` / `overflow-y:auto`、`clientHeight 570` / `scrollHeight 1494`;列表页滚到底 `scrollTop=924`「最后一张卡可见」、确认页 `scrollTop=579`「吸附页脚仍在视口内且两个按钮可见」;三档窗口 `horizontalOverflow=false` | ✅ 已验证 | +| 冲突容器收敛 | 确认页须知块计算值 `overflow-y: visible` / `max-height: none` / `noticesClipped=false`(无内层滚动);页脚 `position: sticky` + `bottom: 0`(滚动前后 `footerVisible=true`) | ✅ 已验证 | +| jsdom 用例 | 新增 `tests/launcherScrollContract.test.ts`(CSS 契约:根/外壳/`.launcher-main` 逐层高度与 `overflow`、视图 `flex: 0 0 auto`、旧 grid 覆盖已删、内容列 720–880px 与 `72ch`);`tests/coCreation.test.tsx`(删除「直接 Fork 指定作品」标题/输入框/按钮/占位的四条断言、`创建时间` 真实值与缺失整行不渲染、`.fork-page` 内无 `overflow-y-auto`);`tests/forkConfirm.test.tsx`(须知块无内层滚动且五条全文、页脚 `sticky bottom-0` 且是页面根最后一个子元素、徽章倒计时 3s→0 与按钮 enable) | ✅ 已验证 | +| 死代码删除 | `parsePlatformGameId` / `PLATFORM_FORK_INPUT_ERROR` / `GAME_ID_PATTERN` / `GAME_ID_QUERY_KEYS` 与对应解析用例全部移除(`platformGameForkModel.ts` 只剩失败分类);`formatCatalogTime` 更名 `formatCatalogCreatedAt` 并返回 `null` 表示「无值」 | ✅ 已验证 | +| 门禁(AGC 本工作树自跑,逐条 exit) | `tsc -p tsconfig.json` 0;`tsc -p tsconfig.tests.json` 0;`npm run ai-game-creator-shell:typecheck` 0(含 `check-config.mjs`:`read_public_game_detail` 已登记 native-only 白名单);`npx vitest run apps/ai-game-creator-shell/tests` 全绿;`npx eslint . --ext .ts,.tsx,.js,.mjs,.cjs --max-warnings 0` 0;`npm run check:encoding` 0;`git diff --check` 0 | ✅ 已验证 | + --- ## 6. 里程碑拆分(评审后逐个开实施计划) @@ -1016,6 +1051,6 @@ procedure 另要求**受信服务身份**(归属判定靠 `owner_user_id`, 7. **对外表述**:第二阶段是否接受「只能试玩 + 素材复用,不能一键复刻工程」?建议接受并同步改文案(AGC 侧注释 `export.rs:407-408` 已把无 `package.json` 的静态项目排除在发布合同外)。 8. **血缘承载位**:血缘只作为客户端本地注释(项目内独立文件,如 `.agent/fork.json`),还是要平台可查询(服务端新增字段)或随工程包传输(manifest 升 v2,旧客户端读到即失败)?建议先本地,等工程源包(M2b)落地再上平台字段。 9. **是否立刻做工程源包(M2b)**:它需要**新增工程包下载/授权接口**(当前不存在)并定义包内禁项(服务端已拒绝 `node_modules`/`.git`/`.env*`/`*.map`/`*.pem`/`*.key` 与嵌套 zip,`module-game-distribution/src/package.rs:135-142`、`:170-176`),成本最大。建议先做 A 验证需求。 -10. **从平台唤起 AGC 的方式**:AGC 内手工输入 gameId / 作品链接,还是平台发取件码在客户端兑换,还是注册 deep link(`tauri.conf.json` 未注册 scheme、无 `tauri-plugin-deep-link`、无单实例插件,需动安装器与升级链路)?建议手工输入起步。 +10. **从平台唤起 AGC 的方式(2026-10-07 部分落地)**:已实现并落地 **deep link** —— `tauri.conf.json` 注册 `genarrative://` scheme、`tauri-plugin-deep-link` + 单实例插件、原生解析 `genarrative://fork?gameId=` 并聚焦主窗口,带 `gameId` 时直接落到 AGC 的 Fork 工程确认页(§3.6.2);**手工输入作品 ID/链接**这一候选已按产品要求移除(重复入口)。因此本条只剩「平台网页端何时把「改造这个作品」按钮改成直接发 deep link」这个前端接线问题,不再涉及唤起机制选型。 11. **是否允许「伪工程」(C 路线,补声明 phaser 4/vite 的 `package.json` 让成品包过检)用于存量作品兼容**:产出不可再构建,建议不允许;若确要做,需产品书面接受「不可再构建」的语义并限定为一次性兼容。 12. **收藏(收录)已纳入并落地**(2026-10-06):原先它在 §2.2「非目标」里,现已实现为真实用户态投影(表 `game_distribution_collection` + `PUT`/`DELETE /games/{gameId}/collection` + `GET /my-collections` + 公开详情 `collected`),见 §2.4 / §3.4 与 `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md` 的 `game_distribution_collection` 小节。仍**不在**范围内的是「收藏数公开计数」「收藏动态流」「关注 / 粉丝」——它们各自需要独立的口径与表。 diff --git a/src/components/platform-entry/PlatformActiveMobileWelcomeDialog.test.tsx b/src/components/platform-entry/PlatformActiveMobileWelcomeDialog.test.tsx index 58688c05e..21b33b349 100644 --- a/src/components/platform-entry/PlatformActiveMobileWelcomeDialog.test.tsx +++ b/src/components/platform-entry/PlatformActiveMobileWelcomeDialog.test.tsx @@ -4,6 +4,7 @@ import { fireEvent, render, screen } from '@testing-library/react'; import { expect, test, vi } from 'vitest'; import { + MOBILE_WELCOME_EXEMPT_STAGES, PlatformActiveMobileWelcomeDialog, shouldOpenActiveMobileWelcomeDialog, shouldShowActiveMobileWelcomeDialog, @@ -44,6 +45,57 @@ test('shows the active mobile welcome when entering the mobile site shell', () = ).toBe(true); }); +test('mobile welcome exempts read-only browsing stages(族谱 / 共创主题)', () => { + // 防退化:白名单内容必须与实现一致(不是只断言某一个 stage)。 + expect( + Object.keys(MOBILE_WELCOME_EXEMPT_STAGES).sort(), + ).toEqual(['game-lineage', 'game-theme', 'game-themes']); + + // 每个豁免 stage(含带 ?id= 的公开深链)在移动端都不再弹。 + for (const stage of Object.keys(MOBILE_WELCOME_EXEMPT_STAGES)) { + expect( + shouldShowActiveMobileWelcomeDialog({ + isDesktopLayout: false, + selectionStage: stage, + platformTab: stage, + pathname: + stage === 'game-lineage' + ? '/games/lineage?id=game_ab12cd34' + : `/games/${stage}`, + }), + `${stage} 属于公开只读的「作品展示」页面,应豁免`, + ).toBe(false); + // 桌面端本来就不弹。 + expect( + shouldShowActiveMobileWelcomeDialog({ + isDesktopLayout: true, + selectionStage: stage, + platformTab: stage, + pathname: '/', + }), + ).toBe(false); + } + + // 创作 / 项目 / 发布 / 我的 / 平台首页仍保持门限(只读浏览的豁免不外溢)。 + for (const stage of [ + 'platform', + 'creation-home', + 'project', + 'game-publish', + 'game-mine', + ]) { + expect( + shouldShowActiveMobileWelcomeDialog({ + isDesktopLayout: false, + selectionStage: stage, + platformTab: stage, + pathname: '/', + }), + `${stage} 不是只读浏览页,应保持门限`, + ).toBe(true); + } +}); + test('keeps the active mobile welcome dismissed across site navigation', () => { expect( shouldOpenActiveMobileWelcomeDialog({ diff --git a/src/components/platform-entry/PlatformActiveMobileWelcomeDialog.tsx b/src/components/platform-entry/PlatformActiveMobileWelcomeDialog.tsx index fa22cb944..54338e941 100644 --- a/src/components/platform-entry/PlatformActiveMobileWelcomeDialog.tsx +++ b/src/components/platform-entry/PlatformActiveMobileWelcomeDialog.tsx @@ -18,10 +18,31 @@ type PlatformActiveMobileWelcomeDialogProps = { onClose: () => void; }; +/** + * 移动端欢迎弹窗的**豁免清单**:这些 stage 是**公开只读的「作品展示」页面**。 + * + * 为什么豁免它们:弹窗文案自己说的是「移动端仅支持作品展示」,而族谱树 / 共创主题页恰恰 + * 就是作品展示;被弹窗盖住会让公开只读深链(例如客户端里分享出来的 `/games/lineage?id=…`) + * **首屏完全不可用**,需要用户先点掉才能看。这是口径不一致,不是有意的功能限制。 + * + * 明确**不放进来**的:创作 / 项目 / 发布 / 我的 / 平台首页 —— 那些才是「移动端不支持」的部分, + * 门限照旧。新增豁免必须显式写在这里(键名 = stage 名),不要用范围匹配或前缀判断。 + */ +export const MOBILE_WELCOME_EXEMPT_STAGES: Readonly> = { + // 创作族谱树:`/games/lineage?id=`(公开只读) + 'game-lineage': true, + // 共创主题列表:`/games/themes`(公开只读) + 'game-themes': true, + // 共创主题详情:`/games/theme?id=`(公开只读) + 'game-theme': true, +}; + export function shouldShowActiveMobileWelcomeDialog({ isDesktopLayout, + selectionStage, }: MobileHomeWelcomeDecisionInput) { - return !isDesktopLayout; + if (isDesktopLayout) return false; + return MOBILE_WELCOME_EXEMPT_STAGES[selectionStage] !== true; } export function shouldOpenActiveMobileWelcomeDialog({ diff --git a/src/components/platform-entry/PlatformEntryActiveFlowShell.test.tsx b/src/components/platform-entry/PlatformEntryActiveFlowShell.test.tsx index 9cf11c4c4..c54b9c3b9 100644 --- a/src/components/platform-entry/PlatformEntryActiveFlowShell.test.tsx +++ b/src/components/platform-entry/PlatformEntryActiveFlowShell.test.tsx @@ -564,6 +564,43 @@ describe('PlatformEntryActiveFlowShell', () => { }); }); + it.each(['game-lineage', 'game-themes', 'game-theme'] as const)( + 'mobile deep link into the read-only %s stage skips the blocking welcome', + async (selectionStage) => { + responsiveMock.isDesktopLayout = false; + window.history.replaceState( + null, + '', + selectionStage === 'game-lineage' + ? '/games/lineage?id=game_ab12cd34' + : '/games/themes', + ); + const { container } = render( + , + ); + + // 只读浏览页(族谱 / 共创主题)属于「作品展示」,不该被欢迎弹窗盖住首屏。 + expect(screen.queryByRole('dialog', { name: '欢迎' })).toBeNull(); + expect(container.querySelector('.platform-mobile-home-welcome-dialog')).toBeNull(); + }, + ); + + it('non make-content mobile stages still show the blocking welcome', async () => { + responsiveMock.isDesktopLayout = false; + window.history.replaceState(null, '', '/profile'); + render( + , + ); + + expect(await screen.findByRole('dialog', { name: '欢迎' })).toBeTruthy(); + }); + it('restores the original blocking welcome on mobile until acknowledged', async () => { responsiveMock.isDesktopLayout = false; const { rerender } = render(