@@ -0,0 +1,37 @@
|
||||
# Profile 主链 Vite 代理修复
|
||||
|
||||
## 1. 问题
|
||||
|
||||
“我的”和“存档”页面在本地开发环境报:
|
||||
|
||||
```text
|
||||
Unexpected token '<', "<!doctype "... is not valid JSON
|
||||
```
|
||||
|
||||
这不是后端返回了坏 JSON,而是前端请求 `/api/profile/*` 时没有命中 Vite 代理,Vite 将请求按 SPA fallback 返回了 `index.html`。`requestJson` 随后对 HTML 执行 `JSON.parse`,首字符 `<` 触发该错误。
|
||||
|
||||
## 2. 现有约束
|
||||
|
||||
DDD 路由矩阵已冻结 profile 主链:
|
||||
|
||||
1. “我的”与存档读取统一走 `/api/profile/*`。
|
||||
2. 旧 `/api/runtime/profile/*` 已取消挂载,不允许前端回退到旧路径。
|
||||
3. 后端 `api-server` 已挂载 `/api/profile/dashboard`、`/api/profile/save-archives` 等路由,问题只在本地 Vite 代理层。
|
||||
|
||||
## 3. 修复
|
||||
|
||||
`vite.config.ts` 在现有 `/api/auth`、`/api/runtime` 等代理旁补齐:
|
||||
|
||||
```ts
|
||||
'/api/profile': {
|
||||
target: runtimeServerTarget,
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
```
|
||||
|
||||
这样 profile 主链请求在 `npm run dev:web` 下会直接转发到 Rust API server,不再落到前端入口页。
|
||||
|
||||
## 4. 回归
|
||||
|
||||
新增 `src/config/viteProxyConfig.test.ts`,断言 Vite server proxy 必须包含 `/api/profile`。后续若再调整 profile route 或代理配置,先更新本文和测试,再改工程实现。
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
1. 通关后默认点击“下一关”,优先加载当前拼图作品的下一关。
|
||||
2. 当前作品没有下一关时,后端按标签语义相似度选出相似度最高的三个已发布作品。
|
||||
3. 用户在通关弹窗里点击候选作品后,进入该作品并从第 `1` 关重新开始。
|
||||
3. 用户在通关弹窗里点击候选作品后,切换到候选作品的第一张图,但运行时关卡序号、切割规格和倒计时继续按当前 run 累进。
|
||||
4. 移动端优先,候选卡片要紧凑,不写玩法说明类文案。
|
||||
|
||||
## 数据契约
|
||||
@@ -51,7 +51,8 @@
|
||||
- 返回最高的 3 个候选
|
||||
4. `advance_puzzle_next_level`:
|
||||
- `nextLevelMode = sameWork` 时加载当前作品的下一关,并继续当前 run。
|
||||
- `nextLevelMode = similarWorks` 时默认加载候选第一项,并把 `entryProfileId / clearedLevelCount / currentLevelIndex` 重置到目标作品第 `1` 关。
|
||||
- `nextLevelMode = similarWorks` 时默认加载候选第一项的第一张图;正式 UI 点击具体候选作品时通过 `targetProfileId` 指定候选。
|
||||
- 任何跨作品进入都只切换图片来源,不重置 `entryProfileId / clearedLevelCount / currentLevelIndex`,并按当前 run 的下一关配置切割规格和倒计时。
|
||||
5. `local-next-level` 兼容接口同样优先找同作品下一关;没有时返回 `similarWorks` 候选并保持当前通关 run,只有候选池为空时才进入旧草稿兜底。
|
||||
|
||||
## 前端规则
|
||||
@@ -70,6 +71,6 @@
|
||||
|
||||
1. 当前作品有下一关时,点击“下一关”进入当前作品下一关。
|
||||
2. 当前作品没有下一关时,通关弹窗显示最多 3 个相似作品。
|
||||
3. 点击相似作品后进入该作品第 `1` 关,HUD 关卡序号、切割规格和倒计时都按第 `1` 关显示。
|
||||
3. 点击相似作品后进入该作品第一张图,HUD 关卡序号、切割规格和倒计时继续按运行时下一关显示。
|
||||
4. 旧 `recommendedNextProfileId` 为空时,只要 `nextLevelMode = sameWork`,按钮仍可用。
|
||||
5. 拼图 runtime 单测、Rust 拼图模块测试和编码检查通过。
|
||||
|
||||
@@ -20,6 +20,14 @@
|
||||
5. 面板次按钮为 `保存并退出`,点击后关闭面板并执行原返回逻辑。
|
||||
6. 非首次点击返回不再弹出面板,直接执行原返回逻辑。
|
||||
|
||||
## UI 布局
|
||||
|
||||
1. 面板保持居中独立弹层,移动端宽度不超过屏幕安全边距,桌面端保持紧凑。
|
||||
2. 面板只展示标题与两个行动按钮,不增加说明性文案。
|
||||
3. 标题使用两行居中排版,顶部可以放无文字图标强化游戏感。
|
||||
4. `作品改造` 为主按钮,视觉权重高于 `保存并退出`。
|
||||
5. 两个按钮纵向排列,固定触控高度,确保移动端易点击。
|
||||
|
||||
## 首次状态
|
||||
|
||||
首次曝光是浏览器侧 UI 引导状态,不是业务真相态:
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
# 拼图运行态低延迟交互前端化修正 2026-05-02
|
||||
|
||||
## 背景
|
||||
|
||||
本次检查发现正式平台入口的拼图运行态存在后端裁决回流:
|
||||
|
||||
1. 作品详情、公开作品卡和结果页试玩会启动后端 run。
|
||||
2. `PuzzleRuntimeShell` 的交换和拖动回调在非本地 run 时会调用 `swapPuzzlePieces`、`dragPuzzlePieceOrGroup`。
|
||||
3. 服务端 run snapshot 如果直接覆盖前端当前棋盘,会让移动、交换、合并和通关反馈出现延迟或回退。
|
||||
|
||||
这与 PRD 中“前端以本地计算得到的 `allTilesResolved = true` 或 `status = cleared` 作为本关通关真相;后端不再参与拼块布局裁决”的规则冲突。
|
||||
|
||||
## 修正口径
|
||||
|
||||
正式平台入口采用混合运行态:
|
||||
|
||||
1. 正式平台开局仍调用后端 `startPuzzleRun`,保留真实 `runId`、游玩记录、排行榜和下一关存储锚点。
|
||||
2. 点击交换只调用 `swapLocalPuzzlePieces`。
|
||||
3. 拖动单块或合并块只调用 `dragLocalPuzzlePiece`。
|
||||
4. 自动合并、拆分、合并块整体平移、被覆盖块交换和通关判定都以前端当前 `PuzzleRunSnapshot` 为准。
|
||||
5. 通关后调用后端 `submitPuzzleLeaderboard` 持久化成绩并读取真实排行榜;前端只合并排行榜与下一关 handoff,不用后端棋盘覆盖当前棋盘。
|
||||
6. 点击同作品下一关调用后端 `advancePuzzleNextLevel`,由 SpacetimeDB 返回新的运行态快照。
|
||||
7. 当前作品没有下一关时,通关弹窗展示后端 handoff 返回的相似作品;用户点击具体候选作品时直接 `startPuzzleRun(profileId, null)`,从目标作品第 `1` 关重新开始。
|
||||
8. 失败状态点击“重新开始”时,正式 run 使用当前关 `levelId` 重新 `startPuzzleRun`,草稿/本地 run 使用本地重建,二者都保留当前失败关卡。
|
||||
9. 结果页草稿试玩没有正式后端 run 时,继续使用本地 run、local leaderboard 和本地下一关兜底。
|
||||
|
||||
## 工程落点
|
||||
|
||||
1. `src/services/puzzle-runtime/puzzleLocalRuntime.ts`
|
||||
- `startLocalPuzzleRun` 支持按 `levelId` 启动。
|
||||
- `advanceLocalPuzzleLevel` 仅作为草稿试玩和无后端 run 的兜底。
|
||||
- 正式平台的移动、交换、合并、拆分和通关裁决仍复用本地函数,避免交互延迟。
|
||||
2. `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
|
||||
- 平台拼图开局、恢复存档、排行榜、下一关接回后端。
|
||||
- 正式平台入口不再调用 `/api/runtime/puzzle/runs/{runId}/swap`、`/drag`。
|
||||
- 后端排行榜返回的 run 只合并排行榜和 `nextLevelMode / nextLevelProfileId / nextLevelId / recommendedNextWorks`,不覆盖当前棋盘。
|
||||
- 相似作品候选卡点击启动目标作品新 run;失败重开按当前关 `levelId` 启动新 run。
|
||||
3. `src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`
|
||||
- 公开拼图玩法交互测试断言前端本地交换函数被调用。
|
||||
- 同时断言后端 `swap / drag` 不参与棋盘交互,后端 `leaderboard / next-level` 继续参与非即时链路。
|
||||
|
||||
## 边界
|
||||
|
||||
本次只收回拼图玩法内的移动、交换、合并、拆分和通关裁决。创作 Agent、作品保存、发布、公开广场读取、作品详情读取、作品改造、排行榜、同作品下一关、相似候选生成、失败重开和游玩记录仍走现有后端链路。
|
||||
|
||||
旧 `SERVER_RS_DDD_WP_PZ_RUNTIME_BACKEND_TRUTH_CLOSURE_2026-05-01.md` 作为历史收尾记录保留;若与本文冲突,以本文的“低延迟棋盘前端裁决,非即时链路后端持久化”口径为准。
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
第 11 关开始,每 6 关循环复用第 5 关到第 10 关的配置,即 `5x5/210000ms`、`6x6/240000ms`、`5x5/210000ms`、`7x7/270000ms`、`5x5/240000ms`、`7x7/270000ms`。
|
||||
|
||||
同作品下一关必须使用同一个运行时关卡序号继续推进。跨作品相似推荐代表进入新作品,必须从目标作品第 `1` 关重新开始。
|
||||
同作品下一关必须使用同一个运行时关卡序号继续推进。跨作品相似推荐只切换到候选作品的第一张图,运行时关卡序号、切割规格和倒计时继续按当前 run 累进,不重置难度循环。
|
||||
|
||||
失败状态点击“重新开始”时,不进入作品第 `1` 关,而是重开当前失败关卡:前端需要传当前关 `levelId`,服务端按该 `levelId` 在作品内的位置恢复 `currentLevelIndex`、切割规格和倒计时。
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
## 文档列表
|
||||
|
||||
- [PUZZLE_RUNTIME_FRONTEND_LOGIC_REHOME_2026-05-02.md](./PUZZLE_RUNTIME_FRONTEND_LOGIC_REHOME_2026-05-02.md):记录拼图正式平台入口移动、交换、合并、拆分和通关裁决收回前端即时运行态,排行榜、下一关和游玩记录继续由后端持久化处理。
|
||||
- [PROFILE_MAIN_ROUTE_VITE_PROXY_FIX_2026-05-02.md](./PROFILE_MAIN_ROUTE_VITE_PROXY_FIX_2026-05-02.md):记录“我的”和“存档”页面在本地把 `/api/profile/*` 请求落到 Vite SPA fallback、导致 HTML 被当 JSON 解析的根因,以及 `/api/profile` 代理补齐与回归测试。
|
||||
- [SERVER_RS_DDD_WP_DEL_CLEANUP_2026-05-01.md](./SERVER_RS_DDD_WP_DEL_CLEANUP_2026-05-01.md):记录 `WP-DEL 删除旧层与命名收口`,物理删除旧 runtime story HTTP DTO、前端 `Rpg*` alias、旧 `/api/custom-world/*` 非 runtime 前缀、Puzzle `local-next-level` 入口和 `/generated-*` 资产直读代理;生成资产读取统一走 OSS read-url 链路。
|
||||
- [SERVER_RS_DDD_WP_API_BFF_CLOSURE_2026-05-01.md](./SERVER_RS_DDD_WP_API_BFF_CLOSURE_2026-05-01.md):记录 `WP-API api-server BFF` 收尾,补齐 `/api/llm/chat/completions` 的 `stream=true` SSE 代理,明确手机号/微信配置门控和角色动画资产占位不阻塞本次 BFF 关闭。
|
||||
- [SERVER_RS_DDD_WP_AS_ASSET_CHAIN_CLOSURE_2026-05-01.md](./SERVER_RS_DDD_WP_AS_ASSET_CHAIN_CLOSURE_2026-05-01.md):记录 `WP-AS Assets` 资产主链收尾,补齐资产领域事件、`asset_event` event table、OSS 确认、API facade、Rust bindings、表目录和 migration 白名单。
|
||||
|
||||
@@ -109,7 +109,7 @@ src/
|
||||
|
||||
聚合:
|
||||
|
||||
1. `AuthUser`:账号、公开叙世号、登录方式、绑定状态、token version。
|
||||
1. `AuthUser`:账号、公开百梦号、登录方式、绑定状态、token version。
|
||||
2. `RefreshSession`:refresh token hash、客户端信息、过期、吊销、last seen。
|
||||
3. `SmsVerification`:手机号、场景、验证码状态、冷却、失败次数。
|
||||
4. `WechatBinding`:微信 provider 身份、union id、绑定状态。
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
10. `RefreshSessionRecord`
|
||||
11. `AuthStoreSnapshotRecord`
|
||||
12. 密码长度、短信验证码长度、验证码 TTL、冷却、失败次数等领域常量。
|
||||
13. 手机号规范化、手机号脱敏、公开叙世号规范化、验证码 key 构造等纯函数。
|
||||
13. 手机号规范化、手机号脱敏、公开百梦号规范化、验证码 key 构造等纯函数。
|
||||
|
||||
本次将以下写入输入落入 `commands.rs`:
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
本次继续把留在 SpacetimeDB adapter 中的纯规则收回 `module-runtime`:
|
||||
|
||||
1. played world、snapshot wallet ledger、save archive、recharge order、recharge wallet ledger、redeem usage、redeem ledger 等 ID 生成规则。
|
||||
2. 首充叙世币奖励计算。
|
||||
2. 首充光点奖励计算。
|
||||
3. 会员购买续期时间计算。
|
||||
4. 邀请码 deterministic 生成、邀请链接、每日奖励窗口和邀请人奖励上限判断。
|
||||
5. 兑换码 public / unique / private 模式使用资格校验。
|
||||
|
||||
@@ -249,7 +249,7 @@ SELECT * FROM profile_membership WHERE user_id = '<user_id>';
|
||||
|
||||
### `profile_recharge_order`
|
||||
|
||||
- 作用:充值订单表,记录用户购买叙世币或会员的订单、支付渠道、支付时间、积分变更和会员到期时间。
|
||||
- 作用:充值订单表,记录用户购买光点或会员的订单、支付渠道、支付时间、积分变更和会员到期时间。
|
||||
- 结构:`order_id PK: String`, `user_id: String`, `product_id: String`, `product_title: String`, `kind: RuntimeProfileRechargeProductKind`, `amount_cents: u64`, `status: RuntimeProfileRechargeOrderStatus`, `payment_channel: String`, `paid_at: Timestamp`, `created_at: Timestamp`, `points_delta: i64`, `membership_expires_at: Option<Timestamp>`。
|
||||
- 索引:`user_id`, `(user_id, created_at)`。
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
2. 标题下显示可复制的分享文本。
|
||||
3. 分享文本下方显示主按钮“分享”,点击后复制完整分享文本。
|
||||
4. 页面底部显示三个分享渠道 icon:微信、QQ、抖音。
|
||||
5. 移动端使用底部弹层,桌面端居中展示,复用 `UnifiedModal` 的平台弹窗外壳。
|
||||
5. 移动端与桌面端都使用居中独立面板,复用 `UnifiedModal` 的平台弹窗外壳。
|
||||
|
||||
## 分享文本
|
||||
|
||||
@@ -39,6 +39,10 @@
|
||||
|
||||
仓库现有 `media/social-media-group/wechat.png` 与 `qq.png` 是社群二维码,不作为本面板渠道 icon 使用。渠道 icon 采用轻量圆形文字标识,避免误导用户进入社群。
|
||||
|
||||
## 面板样式约束
|
||||
|
||||
分享面板通过 `UnifiedModal` portal 挂载到页面根部时,需要在遮罩层补齐当前平台主题类,避免主题变量脱离页面容器后丢失。面板外壳继续使用 `platform-modal-shell` 的 `--platform-modal-fill` 背景,并在移动端覆盖平台弹窗默认底部抽屉布局,保持居中显示。
|
||||
|
||||
## 接入范围
|
||||
|
||||
- `RpgCreationResultActionBar`:RPG 发布成功后由父层回传分享数据并打开面板。
|
||||
|
||||
Reference in New Issue
Block a user