规范 §3.4:目录接口补「网页侧已接」——分页语义与加载更多交互/实测证据
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled

This commit is contained in:
2026-10-07 13:09:48 +08:00
parent fde0567a0c
commit f59a7d21df
@@ -317,7 +317,7 @@ pub(crate) project_bundle_sha256: Option<String>,
| 方法 / 路径 | 说明 |
| --- | --- |
| `GET /games?search=&category=&authorId=&limit=&cursor=`(**既有,2026-10-07 补真分页**) | 公开游戏目录(游戏广场)。匿名可读 + `no-store`;只含公开可玩的作品(`published` + 未软删除 + 有当前公开版本)。**真游标分页**:`limit` 缺省 **48**(保持既有首屏语义,AGC 共创页首屏就是 48 条)、上限 **100**(约束单响应体量),超界**截断**而非报错;`cursor` 形如 `"{createdAtMicros}:{gameId}"`(与 `/my-collections`、主题列表同一套 `"{micros}:{id}"` 惯例,解析只切第一个冒号);**游标格式非法 → 400 `CATALOG_INVALID_CURSOR`**(模块侧报错透传,不吞成 200 空页)。响应 `{ games: [<公开作品投影>], nextCursor }`;排序 `createdAt` 倒序 + `gameId` 升序兜底(全序,翻页不重不漏),顺序定义为「**先按可见性过滤、再排序切页**」。**修正的问题**:此前 `limit` 写死 48 且 `nextCursor` 恒为 `null`,库里第 49 条起的作品(含最老的母版与主干)在 HTTP 面永久不可见(客户端只能显示「已检查最新 48 个作品」)——数据一直在、详情与 `/lineage` 都读得到,缺的只是翻页通道 |
| `GET /games?search=&category=&authorId=&limit=&cursor=`(**既有,2026-10-07 补真分页**) | 公开游戏目录(游戏广场)。匿名可读 + `no-store`;只含公开可玩的作品(`published` + 未软删除 + 有当前公开版本)。**真游标分页**:`limit` 缺省 **48**(保持既有首屏语义,AGC 共创页首屏就是 48 条)、上限 **100**(约束单响应体量),超界**截断**而非报错;`cursor` 形如 `"{createdAtMicros}:{gameId}"`(与 `/my-collections`、主题列表同一套 `"{micros}:{id}"` 惯例,解析只切第一个冒号);**游标格式非法 → 400 `CATALOG_INVALID_CURSOR`**(模块侧报错透传,不吞成 200 空页)。响应 `{ games: [<公开作品投影>], nextCursor }`;排序 `createdAt` 倒序 + `gameId` 升序兜底(全序,翻页不重不漏),顺序定义为「**先按可见性过滤、再排序切页**」。**修正的问题**:此前 `limit` 写死 48 且 `nextCursor` 恒为 `null`,库里第 49 条起的作品(含最老的母版与主干)在 HTTP 面永久不可见(客户端只能显示「已检查最新 48 个作品」)——数据一直在、详情与 `/lineage` 都读得到,缺的只是翻页通道。**网页侧已接(2026-10-07)**:`listGames()` 返回 `{ games, nextCursor }` 并支持 `limit` / `cursor` 透传(不再丢弃游标、不再假装「一次拿全」);游戏广场与创作者主页都在 `nextCursor` 非空时渲染「加载更多」,点击用服务端游标取下一页、**按 `gameId` 去重追加**、加载中禁用、**失败保留已加载内容**并给可读提示、**末页(`nextCursor = null`)自动隐藏入口**。真机实测(真实栈 51 条 published):首屏 48 条 → 加载更多 → 51 条,最老的 `共创演示·A0/A1/A11` 可见 |
| `GET /games/{gameId}`(**既有,响应增量**) | 追加 `forkAuthorization`、`forkCount`、`lineage`(可选)。**不追加 `forkSourceAvailable`**:网页端「改造这个作品」入口的显隐只依据 `forkAuthorization`(已公开 + 非禁止即可引导去取件),真实可复刻形态由 `/games/{gameId}/fork-source` 的 `source` 字段回答,不需要在公开详情里提前判断;若 M2b 需要在详情页区分「可源码级改造 / 只能参考」,再在 M2b 里加该字段。**另追加 `collected`(仅登录用户,2026-10-06)**:登录已认证时返回 `true` / `false`(真实投影,值来自 `is_game_distribution_collected_and_return`);**匿名请求不返回该字段**(也不发 `false`——`false` 会把「未登录」说成「没收藏」)。该字段随请求者变化,所以这条路径必须 `no-store`、不得有任何共享缓存。**再追加 `themes: [{ themeId, name, badge }]`(2026-10-06 增量,共创主题)**:该作品所属的**公开**主题(只含 `status == published`);**匿名与登录都发、无主题时恒发空数组**(与「仅登录才发」的 `collected` 不同——没有主题与没登录因此不会被混成同一种缺键)。口径是「先取该作品的**根**、再按根反查公开主题」,所以**第 N 代作品也能看到并跳到主题页**;单条只够渲染跳转入口(简介与成员数去主题页取)**。**版本摘要 `currentVersion` 再追加 `changeSummary`(2026-10-06,衍生作品发布必填项)**:该版本相对**改编来源**作品的核心改动说明——衍生作品必有(`20–500` 个字符,详见下方作者路由的 `POST /versions` 行),0 代母版为 **`null`**(发键不发值:客户端不必靠缺键猜),因此详情页与族谱溯源能逐代展示「这一版改了什么」。它不含任何私有字段(没有对象键 / 素材 id),匿名可读,也不需要按查看者变化 |
| `GET /games/{gameId}/lineage`(新) | 以该 game 的根为顶返回树:`{ rootGameId, root: LineageNode \| null, nodes: [LineageNode], truncated }`,`LineageNode = { gameId, title, authorName, generation, parentGameId, playCount, status, coverObjectKey }`(`coverObjectKey` 为该节点作品**当前生效的封面对象键**,与公开目录 / 详情**同源同口径**、都来自游戏行 `cover_object_key`,**无封面时为 `null`**;只发对象键,不带 `coverAssetId` 等私有 id),按代际升序 / 同代创建时间升序稳定排序;节点上限 200,超出返回 `truncated: true`。**锚点必须公开可读**:未公开、已软删除或不存在的作品返回 404(与公开详情同口径),不用空标题占位或空树代替 404。树内只出现未软删除且已公开的作品;父/祖辈被排除时孩子照常出现并保留 `generation` 与 `parentGameId`,由展示层标注「原作品已不可用」,不补 null 占位节点。**「累计世代数」不是服务端字段**:本作品子树的深度 = 子树最大代际 − 本作品代际(根作品若有 3 层后代则为 3),前端从同一次响应的 `generation` 自算,服务端不新增字段也不为此多查一次。**右侧面板的「本次核心改动说明」不贴到节点上**:`LineageNode` 保持现在的字段集(不加 `changeSummary`),面板只对**选中节点**请求一次公开详情取 `currentVersion.changeSummary`(衍生作品为字符串、母版为 `null`,见上方公开详情行)——即**不做 N+1**:不是每渲染一个节点就查一次详情(两条前端口径已落地:`bd73acfcc`) |
| `GET /games/{gameId}/derived`(新,可选分页) | 直接子代列表:`{ gameId, nodes: [LineageNode], truncated }`,只含未软删除且已公开的直接子代(与公开详情 `forkCount` 同口径,因此条数与「被改编 N」一致);锚点同样必须公开可读,否则 404 |