1
This commit is contained in:
@@ -152,6 +152,45 @@
|
||||
2. 无会话时会正常落回未登录分支
|
||||
3. 不会因为探测型 401 把自己重新唤醒并刷爆控制台
|
||||
|
||||
## 4.2 2026-04-22 补充修正:公开认证入口误触发 refresh
|
||||
|
||||
在登录弹窗链路继续联调时,又暴露出一条更细的请求边界问题:
|
||||
|
||||
1. 用户处于未登录态,浏览器本地没有 access token
|
||||
2. 点击“获取验证码”会调用 `sendPhoneLoginCode()`
|
||||
3. `authService.ts` 复用了通用 `requestJson(...)`
|
||||
4. `apiClient.ts` 在“无本地 token 且未显式关闭 refresh”时,会先尝试 `POST /api/auth/refresh`
|
||||
5. 若当前浏览器本来也没有 refresh session cookie,就会先打出一条 `401 Unauthorized`
|
||||
6. 最终表现成:验证码接口真正发送前,前端控制台先报一次 `/api/auth/refresh 401`
|
||||
|
||||
这条链的问题不在“验证码接口失败”,而在:
|
||||
|
||||
**登录前公开认证入口被错误当成了需要先补票的受保护请求。**
|
||||
|
||||
因此这里再补一条明确约束:
|
||||
|
||||
1. `sendPhoneLoginCode()`
|
||||
2. `loginWithPhoneCode()`
|
||||
3. `authEntry()`
|
||||
4. `getAuthLoginOptions()`
|
||||
5. `startWechatLogin()`
|
||||
|
||||
以上这些“获取登录态之前”的公开认证入口,统一显式传入:
|
||||
|
||||
1. `skipAuth: true`
|
||||
2. `skipRefresh: true`
|
||||
|
||||
这样修完后:
|
||||
|
||||
1. 未登录用户点击“获取验证码”不会先打 `/api/auth/refresh`
|
||||
2. 公开认证入口不会误带旧 token,也不会制造无意义的 401 噪音
|
||||
3. 真正需要 refresh 的仍然只有已拿到登录态后的受保护请求
|
||||
|
||||
本次补修的定向验证:
|
||||
|
||||
1. `npx vitest run src/services/authService.test.ts`
|
||||
2. `npm run check:encoding`
|
||||
|
||||
---
|
||||
|
||||
## 5. 本批次完成后的实际收益
|
||||
|
||||
@@ -114,6 +114,11 @@
|
||||
- `ready`:渲染平台内容和账号能力
|
||||
- `pending_bind_phone`:继续保留当前绑定手机号流程,不在这次入口改造里拆散
|
||||
|
||||
首屏之后的鉴权刷新补充约束:
|
||||
|
||||
- 平台内容已经渲染后,后续 access token 刷新、401 后重试、账号状态后台重算不能再把整棵平台应用卸载成 `checking / recovering` 加载页。
|
||||
- 后台鉴权重算期间需要保持当前平台页与主 Tab 状态,避免用户手动切到“创作 / 存档 / 我的”后因为鉴权事件闪屏回到首页。
|
||||
|
||||
同时需要在 context 中提供:
|
||||
|
||||
- 当前用户
|
||||
|
||||
@@ -163,6 +163,7 @@
|
||||
注意:
|
||||
|
||||
- 这个默认进入逻辑只在平台首屏初始化时执行,不能覆盖用户手动切换后的选择。
|
||||
- 若平台首页的公开作品、个人数据、存档列表仍在异步加载中,用户已经手动切到“创作 / 存档 / 我的”时,请求完成后也不能把当前 Tab 回刷成默认 Tab。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
# 创作类别开启超时兜底修复记录
|
||||
|
||||
日期:`2026-04-22`
|
||||
|
||||
## 1. 问题现象
|
||||
|
||||
创作中心点击某个创作类别后,入口卡片会进入 `正在开启` 状态,但在后端创建会话迟迟不返回时,界面没有明确失败反馈,用户体感就是“卡死”。
|
||||
|
||||
本次定位覆盖入口:
|
||||
|
||||
1. `角色扮演 RPG`
|
||||
2. `大鱼吃小鱼`
|
||||
3. `拼图玩法`
|
||||
|
||||
## 2. 根因结论
|
||||
|
||||
这次问题不是前端少写了 `finally`。
|
||||
|
||||
真实根因是:
|
||||
|
||||
1. 创作类别点击后会立即把入口置为 busy。
|
||||
2. 会话创建请求如果在运行时后端 / Rust 代理 / Spacetime client 这一段长时间无返回,前端 Promise 就不会及时结束。
|
||||
3. 旧实现缺少“创作入口启动阶段”的独立超时兜底,所以 busy 会持续停留在 `正在开启`。
|
||||
|
||||
## 3. 本次修复口径
|
||||
|
||||
本轮只修“类别开启阶段不能无限等待”,不改创作工作区内部消息流与生成流的超时策略。
|
||||
|
||||
冻结口径如下:
|
||||
|
||||
1. 创作类别创建会话请求统一增加启动超时。
|
||||
2. 超时后必须退出 `正在开启` busy 状态。
|
||||
3. UI 必须展示中文可读错误,不能直接显示底层 `TimeoutError` 或毫秒数字。
|
||||
4. Node 代理转发 Rust 新玩法接口时也必须有上游超时,避免代理层持续悬挂。
|
||||
|
||||
## 4. 具体落地
|
||||
|
||||
### 4.1 前端请求层
|
||||
|
||||
在 `src/services/apiClient.ts` 增加 `timeoutMs` 能力:
|
||||
|
||||
1. 请求可选传入超时毫秒数。
|
||||
2. 到达超时后通过 `AbortController` 中断请求。
|
||||
3. 向上抛出统一 `TimeoutError`。
|
||||
|
||||
### 4.2 创作类别入口
|
||||
|
||||
以下创建会话入口统一使用 `15000ms` 启动超时:
|
||||
|
||||
1. `src/services/rpg-creation/rpgCreationAgentClient.ts`
|
||||
2. `src/services/big-fish-creation/bigFishCreationClient.ts`
|
||||
3. `src/services/puzzle-agent/puzzleAgentClient.ts`
|
||||
|
||||
### 4.3 错误文案
|
||||
|
||||
在 `src/components/rpg-entry/rpgEntryShared.ts` 中统一把超时错误映射为中文提示:
|
||||
|
||||
1. RPG:`开启创作工作台超时,请确认运行时后端已启动后重试。`
|
||||
2. Big Fish:`开启大鱼吃小鱼创作工作台超时,请确认运行时后端已启动后重试。`
|
||||
3. 拼图:`开启拼图创作工作台超时,请确认运行时后端已启动后重试。`
|
||||
|
||||
### 4.4 Node 代理
|
||||
|
||||
以下代理路由新增上游超时:
|
||||
|
||||
1. `server-node/src/routes/bigFishProxyRoutes.ts`
|
||||
2. `server-node/src/routes/puzzleProxyRoutes.ts`
|
||||
|
||||
超时后返回:
|
||||
|
||||
1. `大鱼吃小鱼后端响应超时`
|
||||
2. `拼图后端响应超时`
|
||||
|
||||
## 5. 验收标准
|
||||
|
||||
修复后需要满足:
|
||||
|
||||
1. 点击创作类别时,后端长时间无返回不会无限停留在 `正在开启`。
|
||||
2. 超时后入口按钮恢复可点击。
|
||||
3. 页面展示中文错误提示。
|
||||
4. Big Fish / 拼图的新玩法代理链同样不会无限挂起。
|
||||
|
||||
## 6. 本轮回归
|
||||
|
||||
本轮至少补以下回归:
|
||||
|
||||
1. `apiClient` 请求超时回归。
|
||||
2. Big Fish 类别开启超时回归。
|
||||
3. 拼图类别开启超时回归。
|
||||
@@ -0,0 +1,85 @@
|
||||
# 创作入口鉴权错误串味修复
|
||||
|
||||
日期:`2026-04-22`
|
||||
|
||||
## 1. 问题现象
|
||||
|
||||
平台首页点击“创作”后,用户在创作入口浮层或创作中心起始卡片中会看到:
|
||||
|
||||
- `缺少 Authorization Bearer Token`
|
||||
|
||||
该文案直接暴露了后端鉴权实现细节,不符合平台入口的产品语义,也会让用户误以为“点击创作弹窗本身就失败了”。
|
||||
|
||||
## 2. 根因拆解
|
||||
|
||||
本次问题实际由两层叠加造成:
|
||||
|
||||
1. `useRpgCreationSessionController` 把“恢复旧 Agent 会话失败”的错误写入 `creationTypeError`。
|
||||
2. `PlatformEntryFlowShellImpl` 又把 `creationTypeError` 同时透传给:
|
||||
- 创作中心起始卡片 `createError`
|
||||
- 创作类型浮窗 `error`
|
||||
- 平台首页 `platformError`
|
||||
|
||||
结果是:
|
||||
|
||||
- 旧会话恢复失败
|
||||
- 未登录态残留会话恢复
|
||||
- 本地 access token 丢失但 refresh cookie 仍在
|
||||
|
||||
这些与“当前点击新建创作”并不完全等价的错误,被错误地展示到了新建创作入口上。
|
||||
|
||||
## 3. 修复策略
|
||||
|
||||
### 3.1 错误分层
|
||||
|
||||
在 `useRpgCreationSessionController` 中新增:
|
||||
|
||||
- `agentWorkspaceRestoreError`
|
||||
|
||||
约束:
|
||||
|
||||
1. 旧 Agent 会话恢复失败只写入 `agentWorkspaceRestoreError`
|
||||
2. 用户主动点击新建创作失败才写入 `creationTypeError`
|
||||
3. 创作中心起始卡片和创作类型浮窗只展示“新建入口错误”
|
||||
4. 平台页和工作区恢复占位文案展示“恢复态错误”
|
||||
|
||||
### 3.2 鉴权兜底
|
||||
|
||||
在 `fetchWithApiAuth` 中补充规则:
|
||||
|
||||
1. 受保护请求若本地没有 bearer token
|
||||
2. 且请求未声明 `skipAuth / skipRefresh`
|
||||
3. 先尝试 `ensureStoredAccessToken()` 静默补票
|
||||
4. 补票失败再继续原始请求
|
||||
|
||||
这样可以覆盖“refresh cookie 仍有效,但本地 access token 丢失”的场景,避免后端直接返回“缺少 Authorization Bearer Token”。
|
||||
|
||||
### 3.3 用户态错误文案收敛
|
||||
|
||||
`resolveRpgEntryErrorMessage` 对 `401 UNAUTHORIZED` 与 `缺少 Authorization Bearer Token` 统一映射为:
|
||||
|
||||
- `当前登录状态已失效,请重新登录后继续。`
|
||||
|
||||
目标是把后端实现细节收束成平台用户可理解的恢复动作。
|
||||
|
||||
## 4. 影响范围
|
||||
|
||||
本轮覆盖:
|
||||
|
||||
1. RPG / Custom World 创作入口
|
||||
2. 平台创作中心起始卡片
|
||||
3. 平台创作类型浮窗
|
||||
4. 统一前端 API 鉴权请求层
|
||||
|
||||
本轮不改:
|
||||
|
||||
1. 后端 `401` 契约
|
||||
2. 登录弹窗交互
|
||||
3. Big Fish / Puzzle 的后端路由鉴权策略
|
||||
|
||||
## 5. 验收
|
||||
|
||||
1. 点击“创作”后,不再出现原始 `Authorization Bearer Token` 报错文案
|
||||
2. 旧会话恢复失败时,错误只停留在恢复上下文,不污染新建创作入口
|
||||
3. 本地 token 丢失但 refresh 仍有效时,前端可自动补票后继续请求
|
||||
4. 相关测试与编码检查通过
|
||||
58
docs/technical/CREATION_HUB_CARD_ACTIONS_2026-04-22.md
Normal file
58
docs/technical/CREATION_HUB_CARD_ACTIONS_2026-04-22.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# 创作中心作品卡操作入口落地说明
|
||||
|
||||
日期:`2026-04-22`
|
||||
|
||||
## 1. 本次目标
|
||||
|
||||
创作中心作品卡需要补齐两个直接操作入口:
|
||||
|
||||
1. **体验**:对已经满足运行条件的作品,直接从卡片启动对应玩法,不再必须先进详情页。
|
||||
2. **删除**:对已有正式删除契约的 RPG 已发布作品,直接从卡片删除并刷新创作中心。
|
||||
|
||||
## 2. 操作语义
|
||||
|
||||
| 作品类型 | 状态 | 主按钮 | 体验入口 | 删除入口 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| RPG Agent 草稿 | `draft` | `继续创作` / `继续完善` | 不展示,草稿需要先走发布链 | 不展示,本轮不新增 Agent session 物理删除 |
|
||||
| RPG 已发布作品 | `published` 且 `canEnterWorld=true` | `查看详情` | 展示 `体验`,直接调用现有进入世界链 | 展示 `删除`,走 owner-only 软删除 |
|
||||
| 拼图草稿 | `draft` | `查看详情` | 不展示 | 不展示,本轮不新增拼图删除契约 |
|
||||
| 拼图已发布作品 | `published` | `查看详情` | 展示 `体验`,直接调用 `startPuzzleRun` | 不展示,本轮不新增拼图删除契约 |
|
||||
|
||||
## 3. 后端边界
|
||||
|
||||
RPG 删除必须继续遵守后端治理里的软删除规则:
|
||||
|
||||
1. `custom_world_profile` 增加 `deleted_at` 语义字段。
|
||||
2. 删除时不物理删除 profile,只设置 `deleted_at`、把发布态回退为 `draft`、清空 `published_at`,并删除公开 gallery projection。
|
||||
3. `library / gallery detail / works` 读取默认过滤 `deleted_at != null` 的作品。
|
||||
4. 重复删除同一 profile 保持幂等,返回当前可见作品列表。
|
||||
|
||||
## 4. 前端边界
|
||||
|
||||
1. 卡片只做表现和动作分发,不在前端拼删除逻辑。
|
||||
2. 删除前使用浏览器确认,避免移动端误触。
|
||||
3. 卡片按钮移动端优先换行铺开,避免小屏幕上三个按钮拥挤。
|
||||
4. 不在 UI 中默认展示大段规则说明,失败信息沿用创作中心现有错误 banner。
|
||||
|
||||
## 5. 本轮不做
|
||||
|
||||
1. 不新增 Agent session 草稿删除。
|
||||
2. 不新增拼图作品删除。
|
||||
3. 不新增独立删除面板。
|
||||
4. 不新建创作页或运行时页面,只复用现有 `CustomWorldCreationHub`、RPG 进入世界链和拼图运行时链。
|
||||
|
||||
## 6. 已落地结果
|
||||
|
||||
1. 创作中心 RPG 已发布作品卡主按钮统一调整为 `查看详情`,避免和直接进入玩法的动作混淆。
|
||||
2. RPG 与拼图已发布作品卡新增独立 `体验` 入口,直接复用各自现有运行时进入链路。
|
||||
3. RPG 已发布作品卡新增 `删除` 入口,调用 `/api/runtime/custom-world-library/{profile_id}` 的 `DELETE` 路由,按 owner-only 软删除规则刷新作品列表与公开广场。
|
||||
4. 创作中心详情页原有删除链路继续保留,和卡片删除共用同一后端删除契约。
|
||||
|
||||
## 7. 已验证
|
||||
|
||||
1. `corepack pnpm vitest run src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`
|
||||
2. 交互测试已覆盖:
|
||||
- 创作卡点击 `查看详情` 进入详情页。
|
||||
- 创作卡点击 `体验` 直接进入世界选择链路。
|
||||
- 创作卡点击 `删除` 直接从作品列表移除。
|
||||
- 详情页删除入口在新卡片动作语义下仍然可用。
|
||||
@@ -0,0 +1,62 @@
|
||||
# 世界底稿可选文本字段缺省防护修复 2026-04-22
|
||||
|
||||
更新时间:`2026-04-22`
|
||||
|
||||
## 1. 问题背景
|
||||
|
||||
用户在生成世界底稿时,进度在“补全场景角色细节”前后暂停,最终 operation 进入失败态。
|
||||
|
||||
数据库中的失败记录为:
|
||||
|
||||
```text
|
||||
sessionId = custom-world-agent-session-c8fc39e07da4537cce75314bf4a5f92b
|
||||
operation = draft_foundation
|
||||
status = failed
|
||||
phaseLabel = 编译世界底稿
|
||||
error = Cannot read properties of undefined (reading 'replace')
|
||||
```
|
||||
|
||||
这说明模型分批生成链路已经推进到末端,失败点不是“补全场景角色细节”模型请求本身,而是后续把分批结果编译成 foundation draft / 兼容结果快照时遇到可选文本字段缺省。
|
||||
|
||||
## 2. 根因
|
||||
|
||||
`server-node/src/services/customWorldAgentFoundationDraftService.ts` 内部的 `clampText(value: string, maxLength: number)` 直接调用:
|
||||
|
||||
```ts
|
||||
value.replace(...)
|
||||
```
|
||||
|
||||
但 `CustomWorldGenerationRoleOutline` 中以下字段是可选字段:
|
||||
|
||||
1. `visualDescription`
|
||||
2. `actionDescription`
|
||||
3. `sceneVisualDescription`
|
||||
|
||||
模型在“场景角色叙事基础 / 档案细节”批次中允许只补关键叙事字段,不保证每批都回传所有可选视觉字段。合并详情后,某些角色仍可能缺少这些字段。
|
||||
|
||||
当编译函数执行:
|
||||
|
||||
```ts
|
||||
clampText(role.visualDescription, 36)
|
||||
```
|
||||
|
||||
如果 `role.visualDescription` 为 `undefined`,就会触发 `Cannot read properties of undefined (reading 'replace')`,导致整版世界底稿失败。
|
||||
|
||||
## 3. 修复原则
|
||||
|
||||
1. 可选文本字段缺省属于正常模型输出波动,不应阻断世界底稿主链。
|
||||
2. 编译层必须把 `undefined/null/非字符串` 统一归一为空文本,再进入裁剪逻辑。
|
||||
3. foundation draft 主链应继续保留中文 fallback,不能用英文占位替代中文内容。
|
||||
4. 回归测试需要覆盖“场景角色详情批次缺省可选视觉字段”的真实失败形态。
|
||||
|
||||
## 4. 本次落地范围
|
||||
|
||||
1. 加固 `customWorldAgentFoundationDraftService.ts` 的本地文本裁剪入口。
|
||||
2. 加固 `runtime-profile/normalizeShared.ts` 的公共文本裁剪入口,避免兼容 runtime profile 后续遇到同类缺省字段。
|
||||
3. 新增回归测试,模拟场景角色详情批次省略可选视觉字段时仍能成功生成世界底稿。
|
||||
|
||||
## 5. 验收标准
|
||||
|
||||
1. 同类模型输出缺省 `visualDescription/actionDescription/sceneVisualDescription` 时,底稿生成不再抛出 `.replace` undefined。
|
||||
2. operation 应继续推进到 `completed`,并进入结果页可浏览的草稿卡链路。
|
||||
3. 测试覆盖这次失败的核心路径。
|
||||
@@ -0,0 +1,121 @@
|
||||
# 平台入口鉴权守卫与资源 404 去重修复
|
||||
|
||||
日期:`2026-04-22`
|
||||
|
||||
## 1. 问题现象
|
||||
|
||||
平台入口当前存在两类前端噪音:
|
||||
|
||||
1. 登录态正在 `checking / recovering` 时,首页内容为了避免闪烁会继续保留旧 `user`,但部分受保护请求直接以 `user.id` 作为触发条件,导致 `Puzzle works` 列表仍会提前请求并打出 `401 Unauthorized`。
|
||||
2. 卡面、封面和角色图命中旧 `/generated-*` 路径且后端确认对象不存在时,前端每次渲染都会重新请求 `/api/assets/read-url`,导致控制台重复刷 `404 Not Found`。
|
||||
|
||||
## 2. 根因拆解
|
||||
|
||||
### 2.1 受保护请求误判
|
||||
|
||||
`AuthGate` 当前为了保持平台内容稳定,会在鉴权自检阶段继续向子树暴露 `readyUser`。
|
||||
这本来只该影响展示层,但 `PlatformEntryFlowShellImpl` 与 `useRpgEntryBootstrap` 里部分逻辑直接用:
|
||||
|
||||
1. `Boolean(user)`
|
||||
2. `user?.id`
|
||||
|
||||
来判断是否可以读取受保护数据,结果把“展示继续挂载”误当成“鉴权已经稳定”。
|
||||
|
||||
### 2.2 旧资源路径重复换签
|
||||
|
||||
`assetReadUrlService` 只缓存成功返回的签名 URL,没有缓存“对象不存在”这一失败结果。
|
||||
当 UI 多次渲染同一个缺失资源时,会持续对同一 legacy path 重复调用:
|
||||
|
||||
1. `GET /api/assets/read-url?...`
|
||||
2. 后端稳定返回 `404`
|
||||
3. 前端再次重试同一路径
|
||||
|
||||
## 3. 修复策略
|
||||
|
||||
### 3.1 显式区分展示态与可读保护数据态
|
||||
|
||||
在 `AuthUiContext` 中新增:
|
||||
|
||||
1. `canAccessProtectedData`
|
||||
|
||||
约束:
|
||||
|
||||
1. `user` 仍可在 `checking / recovering` 阶段用于保持 UI 挂载。
|
||||
2. 只有 `status === 'ready' && Boolean(user)` 时,`canAccessProtectedData` 才为 `true`。
|
||||
3. 平台入口 bootstrap 与拼图作品列表请求统一改为依赖 `canAccessProtectedData`,不再直接拿 `user.id` 当鉴权就绪条件。
|
||||
|
||||
### 3.2 为 404 旧资源增加短期失败缓存
|
||||
|
||||
在 `assetReadUrlService` 中新增失败缓存:
|
||||
|
||||
1. 仅针对 `ApiClientError.status === 404` 的 legacy path 读取失败进行缓存。
|
||||
2. 默认缓存窗口为 `60s`。
|
||||
3. 同一路径在失败缓存有效期内直接短路,不再重复向 `/api/assets/read-url` 发请求。
|
||||
4. 成功读取后清理对应失败缓存。
|
||||
|
||||
说明:
|
||||
|
||||
1. 本轮只做“404 去重”,不改原始图片回退逻辑。
|
||||
2. `ResolvedAssetImage` 仍会在换签失败时保留原路径回退,避免界面直接空白。
|
||||
|
||||
## 4. 影响范围
|
||||
|
||||
本轮覆盖:
|
||||
|
||||
1. `AuthGate` 鉴权 UI 上下文
|
||||
2. 平台入口 bootstrap
|
||||
3. 拼图作品列表预取
|
||||
4. 旧 generated 资源换签服务
|
||||
5. 相关交互测试与资源服务测试
|
||||
|
||||
本轮不改:
|
||||
|
||||
1. 后端 `401 / 404` 契约
|
||||
2. 登录弹窗流程
|
||||
3. 旧 `/generated-*` 同源代理路由
|
||||
|
||||
## 5. 验收
|
||||
|
||||
1. 鉴权自检阶段平台内容可继续显示,但不会再误发 `Puzzle works` 受保护请求。
|
||||
2. `当前登录状态已失效,请重新登录后继续。` 仍是入口层统一错误文案。
|
||||
3. 同一条缺失的 legacy 资源路径在短时间内不会重复刷 `/api/assets/read-url` 的 `404`。
|
||||
4. 相关测试通过,编码检查通过。
|
||||
|
||||
## 6. 2026-04-22 补充修正:登录成功后拼图作品列表 401 循环
|
||||
|
||||
### 6.1 问题现象
|
||||
|
||||
用户已经完成登录,但平台入口进入创作区后,控制台仍持续出现:
|
||||
|
||||
1. `GET /api/runtime/puzzle/works 401 (Unauthorized)`
|
||||
2. `AuthGate` 被动收到全局鉴权变更事件
|
||||
3. 平台壳层重新 hydrate
|
||||
4. 拼图作品列表再次自动请求
|
||||
|
||||
最终表现成“登录成功后仍循环刷 401”。
|
||||
|
||||
### 6.2 根因拆解
|
||||
|
||||
这次实际是前后端两层边界叠加:
|
||||
|
||||
1. Node 代理路由 `server-node/src/routes/puzzleProxyRoutes.ts` 已经完成外层 JWT 校验,并把用户 id 通过内部转发头带给 Rust API。
|
||||
2. Rust API `server-rs/crates/api-server/src/auth.rs` 之前只允许 `big-fish` 路径信任这类内部转发头,没有把 `/api/runtime/puzzle/**` 纳入白名单。
|
||||
3. 因此拼图代理链路会在 Node 首层通过后,Rust 二跳再次因为“缺少 Bearer”返回 `401`。
|
||||
4. 前端 `fetchWithApiAuth(...)` 在“首个 401 -> refresh 成功 -> 重试后的业务请求仍 401”时,又会把刚刷新到的 token 清掉并广播一次全局鉴权变更。
|
||||
5. `AuthGate` 监听到事件后重新 hydrate,平台入口又重新预取拼图作品列表,于是形成循环。
|
||||
|
||||
### 6.3 修复策略
|
||||
|
||||
1. Rust `require_bearer_auth` 新增 `allows_internal_forwarded_auth(...)`,显式允许:
|
||||
- `/api/runtime/big-fish/**`
|
||||
- `/api/runtime/puzzle/**`
|
||||
2. 前端 `fetchWithApiAuth(...)` 调整 401 后置处理:
|
||||
- 只有“尚未尝试 refresh”的 401,才清 token 并广播鉴权变化
|
||||
- 若 refresh 已成功,但重试请求仍返回 401,则保留新 token,把失败收敛为当前业务请求自身错误
|
||||
3. 补充请求层回归测试,覆盖“refresh 成功但重试仍 401”的场景。
|
||||
|
||||
### 6.4 修后约束
|
||||
|
||||
1. 拼图运行时代理链路与大鱼链路统一使用同一套内部已鉴权转发约束。
|
||||
2. 单个业务接口的 401 不再自动放大成整个平台的登录态震荡。
|
||||
3. 平台首页和创作区仍保留原有 `canAccessProtectedData` 守卫,不会在未登录态预取拼图私有数据。
|
||||
@@ -0,0 +1,36 @@
|
||||
# 平台主 Tab 渲染稳定性修复
|
||||
|
||||
日期:`2026-04-22`
|
||||
|
||||
## 问题现象
|
||||
|
||||
平台首页在“首页 / 创作 / 存档 / 我的”之间切换时,页面组件会出现短暂闪烁、错位或像是先渲染上一页元素再变成当前页的感觉。
|
||||
|
||||
## 根因拆解
|
||||
|
||||
本次问题集中在表现层:
|
||||
|
||||
1. 四个主 Tab 共用同一个 `content` 根节点,React 在条件分支切换时会按位置复用上一页 DOM,图片卡片、面板和按钮容易在首帧被改造成新页面结构。
|
||||
2. 移动端和桌面端都共用一个滚动容器,切到新 Tab 时会继承上一页的 `scrollTop`,用户会先看到错位位置或短暂空白。
|
||||
3. 创作中心等重页面被卸载后再挂载,内部筛选状态、图片加载状态和布局测量会重新跑一遍,来回切换时会放大闪烁。
|
||||
|
||||
## 修复策略
|
||||
|
||||
1. 主 Tab 内容改为稳定面板栈,四个 Tab 各自拥有独立的内容根节点。
|
||||
2. 非当前 Tab 使用隐藏类保留挂载,不参与布局和交互,避免每次切换销毁再重建页面。
|
||||
3. 每个 Tab 面板自带独立滚动容器,切换时不再继承其他 Tab 的滚动位置。
|
||||
4. 桌面端首页保留控制台化布局,非首页 Tab 继续复用移动端内容结构,但放入桌面独立滚动面板中。
|
||||
|
||||
## 后续约束
|
||||
|
||||
1. 平台主 Tab 新增页面时,优先加入现有面板栈,不要恢复成单个 `content` 条件分支。
|
||||
2. Tab 切换只做可见性切换,不要默认触发页面级 `AnimatePresence` 进出场。
|
||||
3. 需要展示加载态时,优先在当前 Tab 内部局部展示骨架,不要把整页替换为空白加载页。
|
||||
4. UI 中不增加规则说明类文案,只保留入口、状态和业务信息。
|
||||
|
||||
## 验收要点
|
||||
|
||||
1. 手机端连续点击四个底部 Tab,不出现上一页元素先闪一下再变成当前页。
|
||||
2. 在首页滚动后切到“我的 / 存档”,新页面不继承首页滚动位置。
|
||||
3. 创作 Tab 来回切换后,创作中心内部筛选和已加载卡片保持稳定。
|
||||
4. 桌面端左侧导航切换时,顶部栏和左侧导航不重挂载,主内容不出现整页淡入闪烁。
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
## 文档列表
|
||||
|
||||
- [CREATION_HUB_CARD_ACTIONS_2026-04-22.md](./CREATION_HUB_CARD_ACTIONS_2026-04-22.md):冻结创作中心作品卡“体验 / 删除”入口的最小落地语义,明确 RPG 已发布作品软删除、卡片直达运行时,以及暂不扩草稿 / 拼图删除契约。
|
||||
- [CREATION_CATEGORY_OPENING_TIMEOUT_GUARD_FIX_2026-04-22.md](./CREATION_CATEGORY_OPENING_TIMEOUT_GUARD_FIX_2026-04-22.md):记录创作中心点击类别后长时间停留在“正在开启”的根因与修复口径,收口前端创建会话启动超时、中文错误提示以及 Big Fish / 拼图代理上游超时兜底。
|
||||
- [RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md](./RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md):冻结 Rust 本地一键联调脚本与 Ubuntu 发布包构建脚本的执行口径,覆盖 `npm run dev:rust`、`npm run build:rust:ubuntu`、Vite release、Linux `api-server`、SpacetimeDB wasm、启动停止脚本、默认 scp 上传和安全清库开关。
|
||||
- [RUST_API_SERVER_ROUTE_INDEX_2026-04-22.md](./RUST_API_SERVER_ROUTE_INDEX_2026-04-22.md):记录当前 Rust `api-server` 已挂载的 96 条 Axum 路由,按 auth、assets、runtime、custom world、story、generated path 等挂载面归类,用于对照 Node 能力基线与切流 smoke 清单。
|
||||
- [BACKEND_REWRITE_CROSS_CUTTING_GOVERNANCE_2026-04-22.md](./BACKEND_REWRITE_CROSS_CUTTING_GOVERNANCE_2026-04-22.md):冻结后端重写收口阶段的横向治理规则,覆盖 TypeScript contract 到 Rust DTO 映射、SpacetimeDB schema 演进、大对象 / workflow cache 存储边界和文档维护门禁。
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
把平台内所有“先 Agent 聊天收束锚点,再生成结果页”的创作流程统一到一套前端框架:
|
||||
|
||||
1. UI 交互共用一套:标题区、返回、生成结果页按钮、锚点卡片、进度条、操作横幅、聊天气泡、推荐回复、输入框。
|
||||
1. UI 交互共用一套:标题区、返回、进度条、进度操作按钮、生成结果页按钮、操作横幅、聊天气泡、推荐回复、输入框。
|
||||
2. 对话进度管理共用一套:进度归一化、忙碌态判断、SSE `reply_delta / session / error` 解析、操作状态展示。
|
||||
3. 品类差异只允许落在配置和后端领域逻辑:锚点列表、提示词/占位文案、生成结果页 action、快捷补全/总结话术、结果页与运行态。
|
||||
|
||||
@@ -47,6 +47,13 @@ src/services/creation-agent/
|
||||
6. `streamingReplyText / isStreamingReply / isBusy / error`
|
||||
7. `onBack / onSubmitText / onPrimaryAction / onQuickAction`
|
||||
|
||||
聊天页展示规则:
|
||||
|
||||
1. Agent 聊天页不展示锚点内容卡片,锚点只作为进度与后端生成依据。
|
||||
2. 生成草稿 / 生成结果页主按钮只在 `progressPercent` 归一化后达到 `100%` 时显示。
|
||||
3. 进度条下方承载“总结当前设定”“补全剩余设定”等进度操作按钮。
|
||||
4. “补全剩余设定”必须配置 `minTurn: 2`,对话不足两轮时不显示。
|
||||
|
||||
组件内部只做表现,不读取任何 RPG、Big Fish、Puzzle 专属字段。
|
||||
|
||||
### 3.2 会话 view model
|
||||
@@ -121,6 +128,6 @@ src/services/creation-agent/
|
||||
## 6. 验收
|
||||
|
||||
1. 三个创作流程的 Agent 聊天区都通过 `CreationAgentWorkspace` 渲染。
|
||||
2. Big Fish 与 Puzzle 不再各自复制聊天 UI、锚点卡片、输入框和进度条。
|
||||
2. Big Fish 与 Puzzle 不再各自复制聊天 UI、输入框和进度条。
|
||||
3. RPG / Custom World 保留原有“总结当前设定 / 补全剩余设定 / 生成游戏设定草稿”交互。
|
||||
4. 定向 TypeScript / ESLint / 编码检查通过。
|
||||
|
||||
Reference in New Issue
Block a user