Commit Graph

434 Commits

Author SHA1 Message Date
suzmii 54e05d5e84 lineage viz polish 2: dual-axis fit, readable floor, fixed axis layer, edge contrast
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/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
2026-10-06 14:47:51 +08:00
suzmii 2a04f293b1 Merge remote-tracking branch 'origin/master' into feat/game-fork
Project CI / AI game creator shell Rust crates (pull_request) Successful in 4m25s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m18s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m31s
Project CI / Frontend tests (pull_request) Successful in 2m38s
Project CI / Native shell tests (pull_request) Failing after 2m52s
Project CI / AI game creator shell web tests (pull_request) Failing after 1m29s
Project CI / Backend tests (pull_request) Successful in 7m44s
Project CI / Repository checks (pull_request) Successful in 5m37s
2026-10-06 12:24:10 +08:00
k88936 a38407344d 修复(钱包store): 应用余额快照时空值安全
- captureWalletBalanceSnapshot 在 owner 不匹配时返回 null,applyWalletBalanceSnapshot 之前会直接解引用
- applyWalletBalanceSnapshot 入参放宽为 ProfileWalletBalanceSnapshot | null,null 直接返回 false
- 单测去掉非空断言,锁住「他人 owner 的快照捕获为 null 且必须被拒绝」
2026-10-06 10:32:00 +08:00
k88936 796cf9843c Merge remote-tracking branch 'origin/master' into feat/pricing-plan
Project CI / AI game creator shell Rust crates (pull_request) Successful in 6m0s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m43s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m42s
Project CI / Frontend tests (pull_request) Failing after 1m16s
Project CI / Backend tests (pull_request) Successful in 8m51s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m34s
Project CI / Repository checks (pull_request) Successful in 5m34s
Project CI / Native shell tests (pull_request) Successful in 7m18s
# Conflicts:
#	docs/project-memory/shared-memory/decision-log.md
2026-10-06 10:14:15 +08:00
suzmii 0d0166c2d9 feat(游戏共创): 后台读取主题成员名单(含草稿/归档主题与不可见成员 + 可见性列)
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (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 Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
缺口:后台原先只能借**公开投影** `GET /api/game-distribution/themes/{themeId}` 列成员,而公开投影只服务
`published` 主题 ⇒ **草稿 / 已归档主题在后台看不到成员名单**,运营没法在发布前核对。

新增 `GET /admin/api/game-distribution/themes/{theme_id}/members?limit=&cursor=`:

- **鉴权照既有两层**:路由挂既有 admin 支的 `route_layer(require_admin_auth)`,handler 取
  `Extension<AuthenticatedAdmin>`(未登录/失效 → 401 `UNAUTHORIZED`、非 admin → 403,同组同码同形);
  模块侧 procedure 先跑 `require_editor_generation_runtime_service_identity`(照 `033e3aa79` 五条写接口)。
- **返回全部成员行,不套公开可见性过滤**,且**不要求主题已发布**(`draft` / `archived` 照常可读;
  只有主题真的不存在才 404 `THEME_NOT_FOUND`)。响应:
  `{ themeId, totalMembers, members: [{ rootGameId, title, sortOrder, createdAt, visible, visibility }], nextCursor }`。
- **两个可见性字段刻意独立**:
  · `visible` = 「公开侧此刻会不会出现」,**复用** `game_distribution_theme_member_visible`(未删 + 已公开
    + 有当前公开版本),不另写第二套判定;
  · `visibility` = 更细状态(新纯函数):游戏行不存在 → `missing`(优先于 `deleted`),软删除行 → `deleted`,
    否则原样透传游戏行 `visibility`(`published` / `unpublished` / `suspended`)。
    因此 `visibility == "published"` 但无当前公开版本时 `visible = false` ——运营能看出「公开了但没公开版本」;
    三处钉住这个组合(纯函数单测、api-server payload 单测、事务结构断言)。
- **排序与分页照既有口径**:复用 `sort_theme_members`(`sort_order` 升序 + 成员 id 兜底)+ 新增
  `page_admin_theme_members`;`limit` 缺省 20 / 上限 50 / `0` 取默认 / 超界截断(同一归一化);
  游标 `"{sortOrder}:{memberId}"`,解析**只委托** `parse_game_distribution_theme_cursor` ⇒ 非法游标仍是
  上一轮刚修好的**可达** `THEME_INVALID_CURSOR`(不另造一个不可达码);`totalMembers` 是成员**行**总数,
  切页前算,不受分页影响。
- **契约与登记**:`packages/shared/src/contracts/gameDistribution.ts` 新增
  `GameDistributionAdminThemeMemberRow` / `…ListResponse` / `…MemberVisibility`;parity 登记
  `TS_ONLY_TYPES` +3、`RESPONSE_BUILDERS` +2(列表响应与单条成员各一条,条目形状有独立证据);api-server
  用命名构建器(不内联 `json!`)。
- **测试**:module-game-distribution +5 条纯函数(`missing` 优先于 `deleted`、三类不可见各一条并断言
  `visible`/`visibility` 组合、`published` 无公开版本 ⇒ `visible=false`、游标委托共享实现、翻页不重不漏、
  末页 null、复用共享比较器与同一归一化);spacetime-module +1 并扩 2(结构断言:走主题索引、不套公开可见性
  过滤、不复用「只取可见成员」的助手、不自写排序、不删行、`totalMembers` 在切页前取);spacetime-client +1
  mapper(行总数与游标透传、两个可见性字段不互相推导、空名单是正常结果);api-server 例(401/403、草稿主题
  可读、含不可见成员仍返回、`totalMembers` 不受分页影响、limit 缺省与截断、非法游标 → 400 `THEME_INVALID_CURSOR`、
  主题不存在 → 404)。
- **文档**:技术方案 `§3.10.8`(新行 + `visible`/`visibility` 语义 + 「草稿也能读」的理由)与 `§3.4` 后台接口表;
  里程碑(后台路由清单、实施清单、验收与测试清单,原「只能借公开投影」的留白项标记为已落地)。
- **生成绑定**:新增 4 个 `admin_theme_member_list*` / `list_admin_…_procedure` 生成文件 + `module_bindings.rs`
  入口;wire format 有新增(新 procedure/结果类型)⇒ 部署需重新 publish 模块。

门禁:wasm build 0;`cargo check --all-targets` 0;`cargo test -p api-server game_distribution` **89 passed**;
`cargo test -p module-game-distribution` **106 passed**;`cargo test -p spacetime-module` **287 passed** / 1 ignored;
`cargo test -p spacetime-client` 39 passed;DTO parity 0(58 组 / **17** 构建器 / **15** 手拼类型);
`check:spacetime-schema` 0(96 tables);`check:encoding` 0(5424 files);`cargo fmt --all -- --check` 0;
`git diff --check` 0。

不在本提交内(属另一个 session 的文件,工作区里由我的子代理顺带做了**纯新增性**改动,未提交):
`apps/admin-web/src/api/adminGameThemeTypes.ts` 增加了三个指向共享契约新类型的别名 + 一段注释 —— 与该 session
「登记好之后改回引用共享契约」的计划一致,交由其 review / 提交(或直接删掉其本地重复定义)。
2026-10-06 04:28:39 +08:00
suzmii ce614d59c1 feat(游戏共创): 主题详情 rootsTruncated 信号(memberCount 报真实可见成员数)
Project CI / AI game creator shell Rust crates (pull_request) Successful in 6m1s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 7m10s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m58s
Project CI / Backend tests (pull_request) Successful in 9m10s
Project CI / Frontend tests (pull_request) Successful in 2m58s
Project CI / AI game creator shell web tests (pull_request) Successful in 2m32s
Project CI / Native shell tests (pull_request) Successful in 6m37s
Project CI / Repository checks (pull_request) Successful in 4m55s
裁定(与族谱 `truncated` 同约定):`memberCount` = **真实可见成员数**(不受展示上限污染),新增布尔
`rootsTruncated`,**仅当 `roots` 被 50 上限截断时为 true**。此前两个数被同一个 50 上限截断 ⇒ 可见成员
> 50 时静默截断,客户端既看不到全部树、也拿不到任何信号。

- 纯函数:`game_distribution_theme_roots_truncated(visible_member_count, returned_root_count)`
  (相等 / 回传更多 ⇒ false;后者是防御性口径:两组数字不同源时不得凭空报「还有更多」)。
- 契约:`GameDistributionThemeDetail` 加 `roots_truncated`(serde camelCase ⇒ `rootsTruncated`),
  TS 镜像同步;parity `public_theme_detail_payload.mustEmit` 6 键 → **7 键**。
- 事务:`member_count` 走可见成员清单的 `len()`(**不再**经 `page_theme_members` 与上限),`roots` 仍是
  同一清单排序后取前 50,`rootsTruncated` 由纯函数算出(不写死「>50 就 true」——阈值与上限是两个
  独立事实);列表入口共用同一计数函数 ⇒ 同一主题在列表与详情里不会报出两个 `memberCount`。
- api-server:`public_theme_detail_payload` 增加 `rootsTruncated`;形状单测 6 → 7 键,并加
  `memberCount=120 / roots=1 / rootsTruncated=true` 用例,证明两个键口径独立。
- 结构断言同步修正:列表侧原「成员数与 roots 共用同一上限」改为「成员数数的是全部可见成员(断言
  源码里不含切页函数与上限常量)」;详情侧改为「必须调用共享纯函数 + 不得再恒等 `roots.len()` +
  该字段必须进入返回值」。
- 生成绑定:`module_bindings/game_distribution_theme_detail_result_type.rs` 手改后用临时 out-dir 重跑
  `spacetime generate --lang rust` 逐字节校对一致。**wire format 变更 ⇒ 部署需重新 publish 模块**
  (客户端绑定已同步)。
- 文档:技术方案 §3.4 / §3.10.6 / §3.10.9(原「已知限制·三个备选待拍板」→ 已定口径)、里程碑
  (依赖、接口表、验收标准、纯函数表、测试清单、待确认项)、以及数据契约文档里那句「主题详情的
  roots 与列表的 memberCount 永远一致」(本改动已使其为假)。

门禁:wasm build 0;`cargo check --all-targets` 0;`cargo test -p api-server game_distribution` 86 passed;
`cargo test -p module-game-distribution` **101 passed**;`cargo test -p spacetime-module` 286 passed /
1 ignored;DTO parity 0(58 组 / **15** 构建器 / 12 手拼类型);`check:spacetime-schema` 0(96 tables);
`check:encoding` 0(5420 files);`cargo fmt --all -- --check` 0;`git diff --check` 0。

遗留(不在本仓可改范围,需前端 session 跟进):`src/services/gameDistributionClient.ts` 有自己一份主题
详情本地类型与 normalize,会丢弃 `rootsTruncated`(约 3 行小改),因此网页主题页暂时拿不到截断信号;
admin-web 走共享契约类型,后台侧即时生效。
2026-10-06 04:09:45 +08:00
suzmii 4a33397822 chore(游戏共创): 主题注释失真修正 + 后台主题 payload 纳入契约门禁
Project CI / AI game creator shell Rust crates (pull_request) Successful in 5m53s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m48s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m42s
Project CI / Backend tests (pull_request) Successful in 9m47s
Project CI / Frontend tests (pull_request) Successful in 4m4s
Project CI / AI game creator shell web tests (pull_request) Successful in 2m56s
Project CI / Native shell tests (pull_request) Successful in 7m55s
Project CI / Repository checks (pull_request) Successful in 6m8s
1) 注释失真(独立复核 §B8):`module-game-distribution/src/theme.rs` 与
`spacetime-module/src/game_distribution.rs` 原先声称「api-server 与事务都调
`game_distribution_theme_text_violation`」,但全仓只有写入事务一处调用(HTTP 层没有预校验)。
选择**改注释**(单一判据来源就是事务),不给 HTTP 层再叠一层同名校验——否则「HTTP 挡住的」
与「落库挡住的」会重新长出两套判据。两处注释都改成明确写「只有事务调用 + api-server 不做预校验」。

2) 后台主题 payload 的契约门禁:`admin_theme_payload` / `admin_themes_payload` /
`admin_theme_mutation_payload` / `admin_theme_member_payload` 原先既无 TS 类型也未登记 parity,
形状只有 api-server 单测钉着 → 后台 UI 落地时容易与实现漂移。

- `packages/shared/src/contracts/gameDistribution.ts` 新增 5 个 TS 类型
  (`GameDistributionAdminTheme`、`…ListResponse`、`…MutationResponse`、`…MemberResponse`、
  `…MemberRemovalResponse`),注释里写清两处刻意差异:后台主题条目比公开摘要多出
  `sortOrder`/`status`/时间戳,且 `memberCount` 是**成员行总数**(含当前不可见成员),
  公开投影里同名键是**当前可见**成员数;后台列表**没有** `nextCursor`。
- `api-server`:把 handler 里内联的成员移除响应抽成命名构建器
  `admin_theme_member_removal_payload`——内联 `json!` 不在 parity 的证据范围内,抽出来这条
  形状的漂移才会被门禁抓住。
- parity 登记:`TS_ONLY_TYPES` +5(这些形状服务端同样是逐字段手拼 JSON,没有 Rust 结构体可对,
  要收口得先把响应改成结构化类型),`RESPONSE_BUILDERS` +5(响应键与 TS 字段逐键比对)。

门禁:`cargo test -p api-server game_distribution` 86 passed;`cargo test -p spacetime-module`
286 passed / 1 ignored;DTO parity 0(58 组类型 / **15** 个构建器 / **12** 个手拼类型);
`check:encoding` 0(5420 files);`cargo fmt --all -- --check` 0;`git diff --check` 0。
2026-10-06 03:53:15 +08:00
suzmii 2fa201e0dc feat(游戏共创): 共创主题(一)数据模型与领域纯函数
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m30s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 4m12s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 5m44s
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
按 `docs/【技术方案】游戏共创与作品Fork-2026-10-03.md §3.10` 与里程碑《共创主题与作品树-2026-10-06》的清单 A/B/F 落地(本块**不含** procedure / 路由)。

- **表**:`game_distribution_theme`(`theme_id` 主键、`name`/`summary`/`badge`/`sort_order`/`status ∈ draft|published|archived`/`created_by_user_id`/`created_at`/`updated_at`)+ `game_distribution_theme_member`(确定性主键 `member_id = "{theme_id}:{root_game_id}"`,`theme_id`、`root_game_id` 各具名 btree 索引,`sort_order`、`created_at`);归档 / 下架只改公开投影,不删成员行。`migration.rs` 白名单登记两张表(随迁移导出/导入)。
- **生成绑定**:4 个 `game_distribution_theme*` 生成文件 + `module_bindings.rs` 入口(纯追加)。生成时**不覆盖工作树**(本仓共享工作树禁止 git 写命令):先 `spacetime generate --lang rust --out-dir <仓外临时目录>`,rustfmt 后只拷回本次相关文件,避免上一轮踩过的「rustfmt 漂移把别人文件一起改」。
- **领域纯函数**(`module-game-distribution/src/theme.rs`,风格照同目录 `collection.rs`):状态合法/公开可见判定、`game_distribution_theme_member_id`、成员可见性(**委托** `collection.rs` 的判定,不复制第二套)、`game_distribution_theme_root_acceptable(has_lineage_row)`、页大小(缺省 20 / 上限 50 / 0 取默认 / 超界截断)、游标编解码(`"{createdAtMicros}:{themeId}"`,Err 文案「主题列表游标格式无效」,已核对不含 404/409 关键词)、`sort_public_themes`(`created_at` 倒序 + `theme_id` 兜底)、`page_public_themes`(排序 → retain 游标之后 → 多取一条判 has_more;**先过滤再切页**的顺序语义写进文档注释)、`sort_theme_members`(`sort_order` 升序 + `member_id` 兜底)与 `page_theme_members`。
- **契约**:`shared-contracts` 新增 `GameDistributionThemeStatus` / `GameDistributionThemeReference` / `GameDistributionThemeSummary` / `GameDistributionThemeListResponse` / `GameDistributionThemeDetail` / `GameDistributionCreateThemeRequest` / `GameDistributionUpdateThemeRequest` / `GameDistributionUpsertThemeMemberRequest`,`GameDistributionGameSummary` 追加可选 `themes`(按 `collected` 先例只在详情路径下发);TS 镜像同步;parity 登记 **58 组**(响应构建器待路由落地后再登记,parity 要求函数真实存在)。
- **数据契约表目录**:补两张表小节,94 → **96**。
- **测试**:`theme.rs` 16 个单测(成员 id 确定性、状态三态与公开可见、成员可见性逐格与收藏判定等价、根判定、页大小边界、游标往返与非法输入、排序稳定性与同 `sort_order` 兜底、分页不重不漏)。
- **门禁**:wasm build 0;`cargo check --all-targets` 0;`cargo test -p module-game-distribution` **97 passed**;`cargo test -p spacetime-module` 274 passed / 1 ignored;DTO parity **58 组**;`check:spacetime-schema` 0(**96** tables);`check:server-rs-ddd` 0;`check:encoding` 0(5388 files);`cargo fmt --all -- --check` 0;`git diff --check` 0(8 个改动文件 0 删除行)。
2026-10-06 01:46:23 +08:00
suzmii f09607d903 feat(游戏共创): 收录(收藏)列表补真分页(cursor + limit)
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/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
- 纯函数(`module-game-distribution/src/collection.rs`,与收藏另两条规则同文件):`encode/parse_game_distribution_collection_cursor`(`"{created_at_micros}:{collection_id}"`,复用仓库既有 `"{micros}:{id}"` 惯例;非法 → Err「收藏列表游标格式无效」→ 400)、`game_distribution_collection_page_limit`(0/缺省 → 20,超界 → 50)、`game_distribution_collection_page`(稳定全序排序 → 跳过游标之前 → 取 limit → 多取一条判 has_more,只对页内最后一条编码下一游标)
- 排序与顺序语义:`created_at` 倒序 + `collection_id` 升序兜底(主键、同用户内唯一 ⇒ 全序,翻页不重不漏);**先按可见性过滤、再排序切页**——游标位置必须落在已过滤序列上,否则被过滤的行会让下一页漏项(推理写进 tx 与纯函数注释);snapshot 只对最终页成员计算
- 模块:list 的 input 增 `limit` / `cursor`,result 增 `next_cursor`;api-server `my-collections` 加 `Query<MyCollectionsQuery>`,默认 20 / 上限 50(网格一屏 vs 单响应体积;与后台列表的 200 口径不必相等,理由写进注释),超界**截断**不报错,非法游标 400 透传不吞,日志带 `has_more`
- 契约:Rust DTO 增 `next_cursor`、TS 镜像同步、parity 登记(50 组一致)
- 文档:§3.4 的 `my-collections` 行补分页语义(默认/上限/游标格式/非法 400/末页 null/排序口径)
- 测试 13 条:纯函数 8(不足一页 → None;恰好整页不产生空游标;5 条 limit=2 连翻 3 页不重不漏;同 `created_at` 用 `collection_id` 兜底;limit=0 与超界;游标往返 + 7 种非法输入)、模块事务结构 2、api-server 3(默认与截断、`nextCursor` 透传、非法游标 → 400)
- 门禁:wasm build 0;`cargo check --all-targets` 0;`cargo test -p module-game-distribution` **81 passed**;`cargo test -p api-server game_distribution` **73 passed**;`cargo test -p spacetime-module` 274 passed / 1 ignored;DTO parity **50 组**;`check:spacetime-schema` 0(94 tables);`check:encoding` 0(5382 files);`cargo fmt --all -- --check` 0;`git diff --check` 0
2026-10-06 01:31:09 +08:00
suzmii 4b1f24a8fc feat(游戏共创): 收录(收藏)契约与文档同步(块 3/3)
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
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 / 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
- `shared-contracts`:新增 `GameDistributionCollectionState { collected, replayed: Option<bool> }` 承载 PUT/DELETE 共用形状(PUT 才发 `replayed`);既有 `GameDistributionGameSummary` 增 `collected: Option<bool>`(公开详情登录时才带)——该类型只被 api-server 测试与 `GameDistributionListResponse` 引用,加字段不影响任何现有发送方
- `packages/shared` TS 镜像同步(`{ collected: boolean; replayed?: boolean }` 与 `GameDistributionGame.collected?: boolean`),并按既有约定登记进 `scripts/check-game-distribution-dto-parity.mjs`(PAIRS + 手拼响应构建器);注意 `json!` 字面量里不插注释行(parity 脚本抓顶层键时不剥注释,这个坑今天踩过一次)
- 技术方案:`§2.4` 入口矩阵补「收藏按钮 / 我的收藏(登录)与我的作品页同行风格」;`§3.4` 接口表补三条路由(含 404/409 语义、PUT 要求幂等键而 DELETE 不要求及理由、`my-collections` 只含公开作品且不删行)与公开详情 `collected` 的可见性口径;`§7` 把「收录/收藏」从范围外移除并标注已纳入落地
- 数据契约表目录:新增 `game_distribution_collection` 小节(主键确定性、两个索引、为什么不删行、为什么不建唯一索引),供 `check:spacetime-schema` 守卫通过(94 tables)
- 门禁:DTO parity 0(**49 组** Rust/TS 一致,较本轮前 +7);`check:spacetime-schema` 0(94 tables,对照新 merge-base `51cb05f4`);`check:encoding` 0;`git diff --check` 0
2026-10-06 01:14:05 +08:00
suzmii bdcefe91d1 Merge remote-tracking branch 'origin/master' into feat/game-fork
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m31s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m20s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 4m3s
Project CI / Frontend tests (pull_request) Successful in 2m54s
Project CI / Backend tests (pull_request) Successful in 7m36s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m26s
Project CI / Repository checks (pull_request) Successful in 6m25s
Project CI / Native shell tests (pull_request) Successful in 9m34s
# Conflicts:
#	deploy/container/nginx.conf
#	deploy/nginx/genarrative-dev-http.conf
#	deploy/nginx/genarrative.conf
#	server-rs/crates/api-server/src/modules/game_distribution.rs
#	src/components/game-distribution/GameDetailPage.tsx
#	src/components/game-distribution/GameDistributionPages.test.tsx
#	src/components/platform-entry/PlatformEntryActiveFlowShell.tsx
2026-10-06 00:19:40 +08:00
lhk229 51cb05f418 补充创作者主页与关注粉丝工程文档 (#638)
Project CI / AI game creator shell Rust crates (push) Successful in 5m56s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 6m36s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 7m37s
Project CI / Backend tests (push) Successful in 9m22s
Project CI / Frontend tests (push) Successful in 4m40s
Project CI / AI game creator shell web tests (push) Successful in 3m50s
Project CI / Repository checks (push) Successful in 6m30s
Project CI / Native shell tests (push) Successful in 8m32s
本 PR 补齐创作者主页与关注粉丝功能的产品合同和工程设计,供后续按里程碑实施;本次只修改文档。

桌面导航第四项进入自己的创作者主页,“我的”移到第五项。游戏详情可进入作者主页并关注/取消关注;自己或他人的关注、粉丝列表均公开可查看,点击用户头像或昵称可进入其主页。本人支持取消/重新关注、回关和移除粉丝,并明确两个关注方向独立。

工程设计包含关系表与 DTO、认证和事务边界、跨页状态同步、共享组件、深链及验收要求,拆分关系查询和页面交互两个里程碑。按用户要求,游戏列表沿用最多 48 项的现有限制,不增加游戏目录分页改造。

验证:
- 已合入最新 origin/master(21c79f54a);共享决策记录冲突保留双方新增内容。
- npm run check:encoding 通过。
- npm run check:doc-index 通过。
- git diff --check 与暂存差异检查通过。
- 相对 master 仅六份文档发生变化;没有业务代码或 schema 修改,未执行业务测试或部署。

Reviewed-on: #638
Co-authored-by: Linghong <ink29535@proton.me>
Co-committed-by: Linghong <ink29535@proton.me>
2026-10-05 22:05:11 +08:00
k88936 b8309eb3ae 重构(后台金额): 抽出统一的元金额格式化
- shared AdminFormat 新增 formatAdminCurrencyCents(分 → ¥x.xx)并从 components 导出
- AdminUserDetailDialog / AdminMembershipPlanPage / AdminRechargeOrderPage / AdminRechargeProductPage 删除各自重复的 formatMoney/formatPrice
- 补充 formatAdminCurrencyCents 单测,避免后台各页面金额格式漂移
2026-10-05 18:12:22 +08:00
suzmii d7c4f82d7d 修复自查发现的门禁与体验问题:格式、超时态语义、压缩 CPU 边界
Project CI / AI game creator shell Rust crates (pull_request) Successful in 4m48s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m3s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m33s
Project CI / Frontend tests (pull_request) Successful in 3m24s
Project CI / Backend tests (pull_request) Successful in 7m29s
Project CI / AI game creator shell web tests (pull_request) Successful in 2m55s
Project CI / Repository checks (pull_request) Successful in 6m33s
Project CI / Native shell tests (pull_request) Successful in 9m0s
- 新增 Rust 测试补齐 cargo fmt;ESLint 按仓库约定收口:常量与解析函数拆到 platformGameLoadingSurfaceModel.ts,删除 GAME_PLAY_STARTUP_TIMEOUT_MS 常量别名(测试直接用共享常量)
- 共享加载面新增 stalled:宿主判定等待不再推进时停掉进度动画并把 aria-busy 置回 false(去掉与 role=status 重复的 aria-live)
- 后台试玩超时时传入 stalled,避免「一边转圈一边说超时」
- Accept-Encoding 权重参数名改为大小写不敏感(RFC 9110 §12.5.3),GZIP;Q=0 视为拒绝并补用例
- 发行资源压缩分级:≥2 MiB 改用 level 1(zlib 端实测约 1/3 耗时、压缩比差约 3%),小文件仍用 level 6
- 文档:主规范补压缩与条件请求契约(并写明 60 秒窗口与撤销窗口不放宽)、nginx README 修正 Pingora 压缩关闭时的口径、pitfalls 补 CPU 边界与措辞修正
2026-10-05 18:02:48 +08:00
k88936 87a3794423 文档(会员契约): 后台改档请求并发上限不再硬编码 128
- AdminUpsertProfileMembershipPlanRequest.concurrentJobLimit 改为引用后端下发的 unlimitedConcurrencyThreshold
- 同步重新生成 ts-rs 绑定
2026-10-05 17:56:02 +08:00
k88936 8e9d2389e0 文档(会员契约): 并发上限不再硬编码 128 哨兵
- ProfileMembershipPlanResponse.concurrentJobLimit 改为引用后端下发的 unlimitedConcurrencyThreshold
- 同步重新生成 ts-rs 绑定,避免哨兵值变更时注释误导消费方
2026-10-05 17:54:38 +08:00
k88936 759bcf98a8 文档(会员契约): 澄清 status 兜底只作用于 Rust 客户端
- ProfileMembershipResponse.status 注释改为“Rust 反序列化未知取值按 active 兜底,TS 侧不归一化”
- 同步重新生成 ts-rs 绑定,避免 Web 消费方误以为前端也会 fail-open
2026-10-05 17:53:23 +08:00
k88936 8ed716c6e2 修复(充值弹层): 任一商品提交中即禁用会员结算
- 会员结算 canSubmit 由 !submitting 改为 !submittingProductId,与充点分支的 ctaDisabled 口径一致
- 避免充点在途时仍能提交会员结算、被 buyRechargeProduct 静默丢弃后直接关闭弹层
- 补充反向用例:泥点订单提交中时会员结算按钮禁用
2026-10-05 17:41:26 +08:00
k88936 92a8fa1b66 重构(共享导出): 会员商品 id helper 提升为公共导出
- packages/shared/src/index.ts 导出 resolveMembershipProductId
- useAccountWallet 改从 @genarrative/shared 公共入口引入,去掉对弹层私有文件结构的深路径依赖
2026-10-05 17:34:27 +08:00
k88936 ff1e15194d 重构(泥点钱包): 去掉冗余的余额视图联合成员
- PlatformMudPointWalletEntryProps.breakdown 收敛为 ProfileMudPointBalance | null
- ProfileMudPointBalanceView 是 ProfileMudPointBalance 的扩展,联合会被 TypeScript 折叠,属于多余类型噪音
- 同步删除不再需要的 type-only import
2026-10-05 17:34:03 +08:00
suzmii 604ce7f3ec feat(游戏共创): M2b 数据模型——版本行三列工程源包 + 契约与绑定(块 A)
- `server-rs/crates/spacetime-module/src/game_distribution.rs`:`game_distribution_version` 表尾追加三列(明确默认值,遵循既有追加规则)——`project_bundle_object_key: Option<String>`(`#[default(None)]`)、`project_bundle_bytes: u64`(`#[default(0)]` = 未上传)、`project_bundle_sha256: Option<String>`(`#[default(None)]`);三列同步进 `GameDistributionVersionSnapshot`、版本快照构造与建版本时的初始化
- 同文件:`GameDistributionForkSourceSnapshot` 与扁平结果类型追加 `project_bundle_sha256` / `project_bundle_bytes`,下行据此判断走 `project` 还是回落 `package`(字节数与摘要成对,缺一视为没有工程包)
- 同文件:新增 `GAME_DISTRIBUTION_ACTION_UPLOAD_PROJECT_BUNDLE` 动作常量、输入 `GameDistributionConfirmProjectBundleInput`、事务 `confirm_game_distribution_project_bundle_tx` 与 procedure `confirm_game_distribution_project_bundle_and_return`。语义:可选资产、**不驱动版本状态机**;owner 校验 → 阶段门(`awaiting_upload` / `upload_failed`,与发行包确认同口径,已公开/已取消/已撤回一律拒绝)→ 同内容幂等重放 / 换内容冲突 → 写三列 + 幂等收据
- `spacetime-client`:`GameDistributionVersionRecord` 与 `map_version` 同步三列(生成绑定把 `project_bundle_sha256` 折成 `project_bundle_sha_256`,与 `package_sha_256` 同约定);新增 `GameDistributionConfirmProjectBundleRecordInput` 与 `confirm_game_distribution_project_bundle`(沿用 `map_game_distribution_game_result` 再取 version 的既有写法)
- 契约:`shared-contracts` 的 `GameDistributionPrivateVersion` 追加 `project_bundle_bytes`(必需,`0` = 未上传)与 `project_bundle_sha256`(可选);`packages/shared` TS 镜像同步;api-server `private_version_payload` 追加同名键——**对象键不下发**,只回摘要与字节数
- 生成物:重跑 `npm run spacetime:generate -- --rust-only`,只保留 `module_bindings/game_distribution*`(含 2 个新文件)与 `module_bindings.rs`,其余 `module_bindings/**` 的 rustfmt 漂移已 `git checkout --` 还原
- 文档:数据契约表目录 `game_distribution_version` 小节补三列语义与对象键不出服务端的口径
- 顺手记下一条门禁陷阱:`scripts/check-game-distribution-dto-parity.mjs` 抓 `json!` 顶层键时要求「键前一个非空字符是 `{` 或 `,`」且**不剥注释**,在字面量里插注释行会让紧随其后的键被判成缺失(已写进 `private_version_payload` 的文档注释)
- 门禁:wasm build 0;`cargo check --all-targets` 0;`cargo test -p module-game-distribution` 69 passed;`cargo test -p api-server game_distribution` 54 passed;DTO parity 42 组 OK;`check:spacetime-schema` 92 tables OK;`check:encoding` 5301 files OK;`git diff --check` 0;`cargo fmt --all -- --check` 0
2026-10-05 17:30:48 +08:00
suzmii eeb345f2ad feat(游戏共创): 上架时即可指定共创授权档位(创建作品请求增量)
- TS 契约 `GameDistributionCreateGameRequest` 新增可选 `forkAuthorization?: GameDistributionForkAuthorization`,注释写明缺省禁止共创、非法值整请求 400、上架后只能单向提升
- Rust 契约同结构体新增 `#[serde(default)] pub fork_authorization: GameDistributionForkAuthorization`(非 `Option`):省略与显式传 `forbidden` 得到同一个值,**幂等摘要因此相同**——创建请求的摘要取自整个请求体,若写成 `Option` + `skip_serializing_if`,两种写法会变成「同 key 不同请求」而互相冲突;未知取值由反序列化直接拒绝
- api-server `create_game`:载荷改为 `Result<Json<..>, JsonRejection>`,拒绝统一映射成平台信封 400(与 `set_fork_authorization` 同修法),并按 rejection 文本区分文案(含 `forkAuthorization` → 「共创授权档位不合法,只接受 forbidden / nonCommercial / full」,其余 → 「创建作品请求字段不合法」;框架文本只用于选文案,不回传客户端);创建记录入参新增 `fork_authorization: fork_authorization_value(payload.fork_authorization)`
- spacetime-client:`GameDistributionCreateGameRecordInput` 新增 `fork_authorization: String` 并透传给 procedure;重新生成的 `GameDistributionCreateGameInput` 同步带上该字段
- spacetime-module:`GameDistributionCreateGameInput` 新增 `fork_authorization: String`;创建事务里用 `ForkAuthorization::parse` 解析,未知取值失败关闭(`FORK_AUTHORIZATION_UNKNOWN`,**不静默落成 forbidden**),并把原来的硬编码 `FORK_AUTHORIZATION_FORBIDDEN` 换成解析结果
- 资料编辑复用创建校验的转换函数与既有测试夹具按新字段补默认值;AGC 侧测试字面量同步(AGC 发布面板的 UI 接线留到后续,缺省值等价于不传)
- 测试:`create_game_request_defaults_fork_authorization_without_changing_digest`(缺省 → forbidden;显式 forbidden 与原缺省**摘要相同**;`nonCommercial` / `full` 原样保留;`"allowed"` 反序列化失败)与 `create_game_rejects_unknown_fork_authorization_with_envelope_bad_request`(未知档位 → 400 + 平台信封且无框架纯文本;非档位字段问题走通用文案;合法载荷不会被误拒——测试态发布灰度未配置,得到 503 而非 400,即证明载荷路径已通过且未触达创建)
- 文档:技术方案 §2.4(发布入口上架时即选档位)、§3.4(`POST /games` 请求增量补 `forkAuthorization`)、§3.9(补充说明上架指定档位已支持、事后提升仍保留)
2026-10-05 17:30:46 +08:00
suzmii 8f87c826cf feat(游戏共创): M2a 受鉴权内容下发通道(fork-source 元数据 + 整包取件)
- 契约:新增 `GameDistributionForkSourceKind` / `GameDistributionForkSource` / `GameDistributionForkSourceResponse`(shared-contracts + TS 镜像 + parity 登记),字段 `gameId / versionId / source / sha256 / bytes / downloadPath`,**不含对象键**
- 读取:spacetime-module 新增 `GameDistributionForkSourceInput`、扁平结果类型与 procedure `get_game_distribution_fork_source_and_return`;tx 把「行是否存在 / 能否作为来源(未删+已公开+有当前公开版本)/ 授权档位原值」分开回传——既有 owner-or-public 读会把「已软删除」与「未公开」都折成 None,HTTP 层就没法区分 404 与 409;`sha256` / `bytes` 取自版本行,不重算
- 客户端:spacetime-client 新增 record + mapper + `get_game_distribution_fork_source`(生成绑定把 `package_sha256` 折成 `package_sha_256`,与版本快照同约定)
- 路由:新增 `fork_sources` 子路由(`require_bearer_auth` + `add_no_store_response_headers`,**不叠加发布灰度**),两条:`GET …/fork-source`(元数据)与 `GET …/fork-source/package`(ZIP 字节)
- 校验:抽出纯函数 `fork_source_target` 供两个 handler 共用——404 `FORK_SOURCE_NOT_FOUND` / 409 `FORK_SOURCE_NOT_AVAILABLE` / 403 `FORK_NOT_AUTHORIZED`(未知档位按禁止解释);`available` 为真却缺版本元数据时失败关闭为 409
- 取件本体:复用发行网关的 `release_package_bytes`(整包进内存 + 进程内缓存,4 条 / 256 MiB),不新造 OSS 客户端;缓存值由 `Arc<Vec<u8>>` 改为 `Bytes`,整包交给响应体只加引用计数、不复制;响应**不做**引用归一化与 `RELEASE_STORAGE_BOOTSTRAP` 注入(下发原始构建产物,客户端按摘要校验后离线解压);`RELEASE_PACKAGE_CACHE` 顺手改用 `LazyLock`(初始值编译期已知)
- 路径:`downloadPath` 为同源相对路径,复用新抽出的 `is_path_safe_game_id`(与发行入口同一判据),绝不下发对象键
- 测试:新增 4 条——两条路由未带 Bearer → 401;`fork_source_target` 的 404 / 409 / 403 / 未知档位 / 缺元数据失败关闭逐项断言(409 先于 403);元数据 payload 的 sha256/bytes 取自行、downloadPath 形状、序列化后不含对象键;取件包头与长度(`application/zip`、`Content-Length` = 行 `package_bytes`、`Content-Disposition`、`no-store`)
- 文档:技术方案 §3.4 把两个接口从「规划」改成与实现一致的形状,并单独拆出「登录用户(Bearer,不叠加发布灰度)」小节;§3.5.1 的「下载通道(当前不存在)」改为 M2a 已实现,写明它只服务当前公开版本、不做归一化、大包续传仍待做
2026-10-05 17:30:45 +08:00
suzmii 39669763fe 实现游戏共创 M3:创作族谱树与衍生列表
- 新增 module-game-distribution/src/lineage.rs:锚点可读性、可见性过滤、稳定排序、上限截断的纯函数,附 15 个单测(复用 M1 的软删除判定,不重写第二套规则)
- 新增公开只读 procedure get_game_distribution_lineage / list_game_distribution_derived_games;锚点必须公开可读(未公开/已软删除/不存在一律按不存在处理),树内只出现未软删除且已公开的节点,不补 null 占位节点
- api-server 新增 GET /games/{id}/lineage 与 /games/{id}/derived(匿名可读、no-store),响应走结构化 DTO,节点键集合由测试钉死,杜绝下发对象键/素材键
- 作者自有游戏聚合追加 forkCount(与公开详情 forkCount 同口径),供「被改编 N」入口使用
- shared-contracts 与 TS 契约新增 LineageNode / LineageResponse / DerivedResponse 三型并登记 DTO 一致性检查
- 前端新增 /games/lineage 族谱页,登记 stage 类型、路由表、页面标题与壳层渲染分支;详情页共创卡新增「查看创作族谱」入口(仅已公开作品)
- 我的作品页新增「被改编 N」行内展开,列出直接子代的标题/作者/代际/状态,含空态与失败重试(仅已公开作品)
- 补前端组件/路由/标题/客户端用例与 api-server 路由、负载映射用例;同步里程碑验收标准与技术方案 §3.4 的锚点可读性口径
2026-10-05 17:30:45 +08:00
suzmii 1e7ec6dbe7 实现游戏共创 M1:作品级共创授权与改编血缘
- game_distribution_game 表尾追加 fork_authorization(默认 forbidden,只升不降,旧行自动取默认)
- 新增 game_distribution_lineage 表,记录父作品、来源版本、根作品与代际,并按 parent/root/owner 建索引
- migration.rs 登记新表并注明血缘随迁移导出
- 新增 set_game_distribution_fork_authorization_and_return procedure 与幂等收据、期望值 CAS
- 创建作品时校验改编声明:来源存在、已公开、授权开放、来源版本等于当前公开版本;复用既有身份时拒绝携带血缘
- 公开与后台响应增量:forkAuthorization、forkCount、lineage、generation、forkedFromGameId、derivedCount
- 公开快照的作者名与头像改为读时联 user_account,不再落到「创作者」兜底文案
- api-server 新增 PUT /api/game-distribution/games/{game_id}/fork-authorization(Bearer、幂等键、期望值校验)
- 错误映射新增 FORK_ 前缀分支并置于通用子串分支之前,避免被误判成 409 或 404
- module-game-distribution 增加授权阶梯、代际计算与错误码,并补单测覆盖阶梯与错误码前缀
- 网页端详情页新增共创信息卡,我的作品页新增授权提升入口,补服务层调用与定向测试
- 重新生成 SpacetimeDB 绑定,登记 DTO 一致性脚本的共创类型
2026-10-05 17:30:44 +08:00
suzmii 3206f8f49b 修复在线游玩加载体验:加载期黑屏/空白与发行资源传输开销
- packages/shared 新增共享游戏加载面 PlatformGameLoadingSurface:封面/标题 + 动效进度 + 按时长推进的阶段文案(准备/资源/引擎)+ 慢加载提示,附用例
- 网页游玩页用共享加载面替换近黑壳上的一行小字,加载上限改为共享常量 PLATFORM_GAME_LOADING_TIMEOUT_MS
- 后台审核试玩补加载面、20 秒超时与「重新创建试玩会话」入口,新会话重置加载态;补两条用例
- 发行网关(api-server)对文本类发行资源按 Accept-Encoding 下发 gzip,并下发强 ETag、命中 If-None-Match 返回 304;补 5 条 Rust 用例
- E2E 脚本 check-game-distribution-ops-rollback-e2e 补压缩/Vary/ETag/304/不接受 gzip 四条断言
- 同步 pitfalls、开发运维文档、nginx README 与两份里程碑记录
2026-10-05 17:14:32 +08:00
k88936 bc3e36ff33 重构(会员契约): 会员结构体改用 ts-rs 生成类型
- shared-contracts 为 10 个会员结构体补 ts_rs::TS / ts(export),64 位整数字段补 ts(type = "number") 对齐前端 number
- 新增 10 个生成绑定;packages/shared 与 admin-web 改为 re-export 生成类型,仅 kind / status 等前端收窄联合用 Omit 组合保留
- 同步设计文档 §2.5 与决策日志
2026-10-05 15:51:07 +08:00
k88936 e87837381a 修复(充值弹层): 下单进行中禁用泥点结算入口
- 会员订单提交时泥点结算 CTA 仍可点,点击后会被宿主 buyRechargeProduct 守卫静默丢弃
- 泥点结算弹层传 ctaDisabled,底部「充值」CTA 也在任意提交进行中禁用
- 保留上一轮「会员提交不波及泥点档位可选性」的既有行为,仅收口下单动作;补回归测试
2026-10-05 14:10:42 +08:00
k88936 cdbc6b4ac2 修复(充值弹层): max 档不再提示可补差升档
- 状态卡原先无条件显示「可同周期补差升档」,已是最高档时并无更高档位可升
- 父组件按启用目录计算 hasUpgradePath(存在 rank 更高的档位),无升级路径时只保留「周期切换需等到期后」
- 补 max 档与 plus 档的对照断言
2026-10-05 14:08:26 +08:00
k88936 3a948c5f58 功能(充值弹层): 会员周期切换支持键盘方向键
- 月付/年付 radiogroup 原先无 roving tabindex,方向键无法移动选项
- 复用 resolveRovingIndex 与 ref,方向键同步移动选中与焦点;锁定时不响应方向键
- 补一条周期切换键盘导航测试
2026-10-05 14:03:48 +08:00
k88936 c7150b590c 修复(LLM目录契约): protocol 缺省改为按 agent_mode 推导
- LlmModelSummary/LlmUnavailableModel 反序列化缺失 protocol 时取 agent_mode 的默认协议,不再一律 openai_responses
- 补 cc / anthropic 别名 / 显式 protocol 用例;同步生成绑定注释
2026-10-05 12:18:05 +08:00
k88936 af419e82c7 文档(泥点钱包): 统一三池合计的展示口径为可用总额
- mudPoints 文档不再声明合计「不代表可用额度」,与钱包入口/充值弹层的「可用总额」文案对齐
2026-10-05 12:14:53 +08:00
k88936 985b8718b8 清理(泥点定价): 移除硬编码的「¥1 = 10 泥点」文案
- 充值商品页描述去掉固定比例,不再把定价规则写死在 UI
- 同步去掉公共充值弹层与测试里同口径的注释
2026-10-05 12:14:20 +08:00
k88936 d3f02d6bc9 修复(充值弹层): 泥点档位禁用范围收敛到泥点商品
- submittingProductId 现与会员下单共用,仅当提交的是泥点商品时才禁用整组泥点档位,避免会员提交期间跨页签耦合
2026-10-05 11:59:47 +08:00
k88936 f0dfddb910 修复(充值弹层): 未选中时左方向键回卷到最后一项
- resolveRovingIndex 的 currentIndex 允许为 -1,ArrowLeft 特判为 length-1,避免落到倒数第二项
2026-10-05 11:59:32 +08:00
k88936 5d2de88a40 修复(充值弹层): 当前档位缺失时禁用会员下单
- resolveMembershipPlanAction 在有效会员查不到当前档位时返回「套餐信息缺失」并禁用,避免误判为新客对所有档位开放购买
2026-10-05 11:59:12 +08:00
k88936 a48b7f8de8 Merge remote-tracking branch 'origin/master' into feat/pricing-plan
Project CI / Backend tests (pull_request) Failing after 48s
Project CI / Native shell tests (pull_request) Failing after 3m50s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 5m21s
Project CI / Frontend tests (pull_request) Failing after 47s
Project CI / AI game creator shell web tests (pull_request) Failing after 39s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m6s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m23s
Project CI / Repository checks (pull_request) Successful in 6m31s
# Conflicts:
#	docs/project-memory/shared-memory/decision-log.md
2026-10-05 10:50:30 +08:00
k88936 54d1df9d21 refactor(契约): 会员 token 枚举走 ts-rs 生成,前端不再手写联合类型
- shared-contracts 给 ProfileMembershipPlanToken/CycleKindToken/StatusToken/ModelAccessToken/ChangeKindToken 补 ts_rs::TS + ts(export),生成 5 个 .ts 到 packages/shared/src/contracts/generated/(评审第 14 条)
- packages/shared/src/contracts/runtime.ts 的 ProfileMembershipPlan/Status/CycleKind/ModelAccess 改为生成类型的别名并 re-export
- apps/admin-web/src/api/adminApiTypes.ts 删除本地手写联合类型,改为从 @genarrative/shared 导入 / re-export
- npm run check:generated-bindings、admin-web:typecheck、npm run typecheck 通过
2026-10-04 19:35:28 +08:00
suzmii e593236f9b 公开作品详情页不再显示占位作者名「创作者」,按作者 ID 补查真实昵称
Project CI / Backend tests (pull_request) Failing after 30s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m27s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 5m23s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 4m15s
Project CI / Repository checks (pull_request) Failing after 33s
Project CI / Frontend tests (pull_request) Successful in 3m59s
Project CI / AI game creator shell web tests (pull_request) Successful in 4m10s
Project CI / Native shell tests (pull_request) Successful in 8m37s
- useGameAuthorName.ts(新增):useGameAuthorDisplayName —— 回包名是占位词(「创作者」/「未知作者」)或空时,按 author.id 调 GET /api/auth/public-users/by-id/{id} 取 displayName;查询中返回 null(避免先闪占位词),查不到返回空串、不编造名字
- GameDetailPage.tsx:详情页用解析后的作者名装配展示对象(displayGame),替代直接用公开投影里的 author.name
- packages/shared/src/components/GameDetailDisplay/index.tsx:作者名为空时不渲染「由 X 制作」那一行;头像首字改用 Array.from(name)[0](避免 emoji 代理对被截断);作者行补 title 悬浮显示完整名字
2026-10-04 17:59:02 +08:00
k88936 55bbf27e8f fix(充值弹窗): 当前档位改从未过滤目录解析
- 有效会员的当前档位被后台临时禁用时,filter 后的 membershipPlans 查不到,状态卡退化成「会员」、按钮变「开通会员」,可在有效会员上重复开通(评审第 5 条)
- 结算弹窗升级摘要的 currentPlan 同样取自过滤列表,禁用档位会丢「原套餐标准价」;改由父组件传入未过滤目录解析出的 currentPlan,并移除弹窗对 membership 的无用依赖(评审第 11 条)
- 新增回归:active 会员 + 当前档 disabled 时仍显示「当前 Plus · 年付」、Max 提供「补差升级」,摘要保留「原套餐标准年价」
2026-10-04 17:12:55 +08:00
k88936 1e90254d0a perf(泥点): 复用模块级 Intl.DateTimeFormat 格式化刷新时刻
- formatMudPointResetAt 原先每次调用都新建 Intl.DateTimeFormat,而它跑在多个渲染路径里
- locale / 时区 / 字段恒定,提升为模块级常量 MUD_POINT_RESET_FORMATTER 复用,输出不变
2026-10-04 15:57:22 +08:00
k88936 b431eb6074 refactor(钱包 store): 抽出余额就绪态统一构造,消除重复 set
- applyWalletBalanceSnapshot 与刷新循环各自重复「resolve 视图 + 写 4 个 ready 字段」
- 抽出 readyWalletBalanceState 统一派生 totalPoints / status / error,两条路径不再有漂移风险
- 行为不变(注:本文件既有的 rejects a snapshot from another owner 用例在改动前后均失败,与本次无关)
2026-10-04 15:55:38 +08:00
k88936 9e13b83171 refactor(充值弹窗): 摊平套餐卡片里的嵌套三元价格行
- 原年付实付文案在模板字符串里再嵌一层 savingCents 三元,属于嵌套三元
- 把年付价格行提升为 annualPriceLine 常量,JSX 只保留 annual 单层分支,文案不变
2026-10-04 15:52:29 +08:00
k88936 349af04034 refactor(充值弹窗): 摊平结算弹窗里的嵌套三元 listPriceCents
- 原写法 plan / annual 两层三元叠加,扩展条件时容易漏分支
- 改为按 annual 分支 + 可选链与 ?? 兜底,保持 plan 为空时取 0 的语义不变
2026-10-04 15:49:31 +08:00
k88936 f3f89efddd feat(shared): 转发 AGC 模型目录 ts-rs 绑定
- 新增 packages/shared/src/llm/modelCatalog.ts,re-export Rust 生成的目录 DTO 与枚举
- 根 index.ts 增加 export type * from './llm/modelCatalog',AGC 从 @genarrative/shared 根导入
2026-10-04 13:29:32 +08:00
k88936 e89420053a feat(会员): 模型列表返回可用与不可用分桶
- shared-contracts 新增 llm_catalog.rs:LlmModelsResponse/LlmModelSummary/LlmUnavailableModel/LlmModelUnavailableReason(plan_required/disabled/unknown),未停用优先于档位的 disabled 语义留给域层
- AgcAgentMode/AgcModelProtocol 从 module-runtime 迁入 shared-contracts(前后端 + ts-rs 单一真源),module-runtime re-export 保持路径可用
- 整个目录 DTO 与枚举走 ts-rs,生成到 packages/shared/src/contracts/generated,revision 标 number
- module-runtime 新增 AgcModelCatalog::unavailable_models_for(access):保持目录顺序,停用优先于档位不足,补单测
- api-server public_model_catalog 产出 models + unavailableModels 两桶,重写 model_catalog_tests
2026-10-04 13:29:29 +08:00
k88936 582a329468 修正(会员弹层):支付方式静态文案去掉 radiogroup/radio 语义
- 微信支付是纯展示的单一选项,不可交互的 radio 会误导读屏
- 改为普通文本容器,视觉样式不变
2026-10-04 10:33:04 +08:00
k88936 951b44b582 修正(会员弹层):升级报价未到时不再把标准价显示为本次补付
- 新增 amountPending:升级报价缺失时金额区显示占位符,CTA 只显示「补差升级」
- 补一条回归测试覆盖报价加载中的确认页
2026-10-04 10:32:50 +08:00
k88936 93837a736d 重构(会员弹层):抽取 resolveRovingIndex 复用方向键索引
- 页签列表与泥点档位网格共用同一个 roving index 解析函数
- 用 switch 取代深层嵌套三元,Home/End/左右键语义保持不变
2026-10-04 10:31:52 +08:00
k88936 987e82605b 重构(会员弹层):档位卡按钮文案去掉嵌套三元
- 用 if/else 预计算 actionButtonLabel,明确 禁用/提交中/默认 的优先级
2026-10-04 10:31:20 +08:00