docs(03-03): docs/修改记录.md 顶部追加 Phase 3 条目

- 在 L26 注释之后、L28 Phase 2 条目之前插入 [2026-05-08] Phase 3 完整条目
- 条目按 CLAUDE.md L72-82 4 字段格式(文件路径 / 修改类型 / 修改内容 / 修改原因)+ 跨项目联动 + 服务端联动
- 列出 3 个改动文件(app/layout.tsx + components/ai-model/credential-slot-dialog.tsx + app/ai-model/page.tsx)
- 修改原因段显式说明 access_token 强制输入语义的业务权衡 + 候选下一周期 milestone 锚点(识别脱敏掩码保留旧值)
- 跨项目联动 + 服务端联动字段使用 CONTEXT.md / 上下文锁定的中文文本
- 覆盖 CRED-FE-04 + CRED-FE-05
- CLAUDE.md L70-94 修改记录强制规则闭环
This commit is contained in:
pmc 2026-05-08 12:40:16 +08:00
parent 89cd768765
commit 892b0b10da

View File

@ -25,6 +25,60 @@
<!-- 新的修改记录添加在此处下方,最新的在最前面 --> <!-- 新的修改记录添加在此处下方,最新的在最前面 -->
### [2026-05-08] Phase 3前端凭据槽位编辑对话框 + 提交反馈
配套服务端 Phase本 phase **不**触达服务端;与服务端 v1.0 Phase 2「管理端读写接口」commit `46d72b8` 既有契约保持兼容GET 脱敏掩码 + PUT 全字段覆写语义不变)
覆盖前端需求CRED-FE-04、CRED-FE-05
- **文件路径**
- `app/layout.tsx`(修改)
- `components/ai-model/credential-slot-dialog.tsx`(新增)
- `app/ai-model/page.tsx`(修改)
- **修改类型**: 修改 + 新增(前端 UI 收尾;纯前端,无新依赖、不动 lockfile、不触达服务端
- **修改内容**:
- `app/layout.tsx`(修复仓库 pre-existing 死代码 bug
- 顶部新增 `import { Toaster } from "@/components/ui/sonner"`
- `<body>{children}</body>``{children}` 之后追加 `<Toaster />`
- 修复仓库内 9 处 `toast(...)` 调用全部静默的 dead-code 状态(`components/ui/sonner.tsx` 早就存在 Toaster 包装但**从未在 RootLayout 挂载**
- `components/ai-model/credential-slot-dialog.tsx`**新建** ~150 行):
- 顶部 `"use client"` 指令;具名导出 `CredentialSlotDialog`
- 文件命名 **kebab-case**(与仓库 9 个现有业务对话框 `user-form-dialog.tsx` / `add-song-dialog.tsx` 等对齐)
- 表单技术栈React Hook Form + Zod + shadcn Form wrapper1:1 模板自 `components/users/user-form-dialog.tsx`
- Zod schema`appId` + `accessToken``min(1)`access_token **强制输入**,见「修改原因」段权衡说明)
- 受控接口:`{ open: boolean; onOpenChange: (open: boolean) => void }`
- 打开时 `useEffect``getCredentialSlot()` 拉取 → `form.reset({ appId: data.appId, accessToken: "" })` —— **accessToken 永远默认空串**,绝不回填脱敏掩码
- `<Input placeholder={slot?.accessTokenMasked} />` —— 仅作视觉提示
- `<FormDescription>每次保存都需要重新输入 Access Token不会显示原值避免回写脱敏掩码</FormDescription>`
- `updatedAt``new Date(slot.updatedAt).toLocaleString("zh-CN")` 中文只读显示
- 提交成功:`toast.success("凭据槽位已更新", { description: "配置已生效" })` + `handleOpenChange(false)`
- 提交失败:`toast.error("保存失败", { description: handleApiError(e) })` + 对话框保持打开 + 表单值不丢
- 关闭时 `form.reset({ appId: "", accessToken: "" })` + `setSlot(null)` —— 避免下次打开残留上次输入
- useEffect cleanup `cancelled` flag 防止快速开关导致的 race condition
- **关键 import 决策**(避免仓库内同名 dead code
- `import { toast } from "sonner"` —— **不**走 `@/hooks/use-toast`Radix Toast 实现,与 Sonner 不通信)
- `import { handleApiError } from "@/lib/api/error-handler"` —— **不**走 barrel `@/lib/api`barrel 内有同名重复定义)
- `app/ai-model/page.tsx`
- 删除 L9-15 Dialog 系列命名导入整段(`Dialog / DialogContent / DialogDescription / DialogHeader / DialogTitle`)—— 占位 Dialog 删除后 page 不再直接使用 Dialog primitive
- 在 lucide-react import 之后新增 `import { CredentialSlotDialog } from "@/components/ai-model/credential-slot-dialog"`
- 删除 L473-485 占位 Dialog`<DialogTitle>通用凭据槽位</DialogTitle>` + `<DialogDescription>对话框真实内容由 Phase 3 落地</DialogDescription>`
- 替换为 `<CredentialSlotDialog open={isCredentialDialogOpen} onOpenChange={setIsCredentialDialogOpen} />`
- 保留 L1 `"use client"` / L20-25 mounted state + isCredentialDialogOpen state + useEffect 守卫 / L35-43「凭据槽位」Button 入口(含 `mounted && hasPermission("credential-slot")` 守卫)
- Tabs / TabsContent / Card 等其余内容L18-471逐字不动
- **修改原因**:
- 收尾 Milestone v1.0「通用凭据槽位前端集成」:让授权运营能查看脱敏的当前凭据、安全提交新值,且成功 / 失败两条路径都有清晰的中文 toast 反馈
- 修复 `app/layout.tsx` 的 pre-existing dead code仓库 `components/ui/sonner.tsx` 早已存在 Sonner Toaster 包装但**从未挂载到 RootLayout**,导致仓库内 9 处既有 `toast(...)` 调用全部静默;本 phase 顺手修复(否则 Phase 3 反馈不可见)
- 拆出独立组件 `credential-slot-dialog.tsx` 而非把表单内联进 page.tsx(a) 与仓库 9 个现有业务对话框抽离风格一致;(b) 让 page.tsx 保持简洁,不掺业务表单状态;(c) 关闭对话框时组件级 form.reset 隔离干净
- **业务语义权衡(重要 — 候选下一周期 milestone 锚点)**:本 phase Zod schema 把 `accessToken: z.string().min(1)` —— **强制每次重输** access_token**不实现** ROADMAP success criteria #2 中提到的「留空保留旧值」语义。原因:
- 后端 PUT 是全字段覆写语义qy_lty 后端 v1.0 Phase 2 已锁定 commit `46d72b8`
- 后端 GET 返回的 access_token 字段是脱敏掩码(末 4 位明文 + 前缀 `*`),前端永远拿不到真值
- 「留空保留旧值」需后端配合识别 PUT body 中 access_token 的脱敏掩码格式并保留旧值(后端逻辑:`if access_token == mask_token(current.access_token): preserve old`
- 该后端识别逻辑不在 Milestone v1.0 范畴,**已记入候选下一周期 milestone**(参见 PROJECT.md / REQUIREMENTS.md 候选清单 + STATE.md 风险段)
- 当前实现退化为「每次保存都要重输 access_token」—— UX 略差但语义正确(永远不会回写脱敏掩码导致后端清空真实凭据)
- 沿用 Phase 1 已建立的「`accessTokenMasked` vs `accessToken` 类型层屏障」(前者是脱敏字符串、后者是明文)—— TS 编译期会拦截「把脱敏字符串赋给 accessToken 字段」的 bug 路径
- Sonner`components/ui/sonner.tsx`+ `lib/api/error-handler.ts:handleApiError` 是 CONTEXT D-Toast / D-错误处理 锁定的双依赖;不引入新依赖、不混合 Radix Toast hook、不走 barrel 同名重复定义,避免仓库内 dead code 串扰
- **跨项目联动**: 无 — Phase 3 是前端 UI 收尾access_token 强制输入语义为 Phase 1+2 已建立的前后端互引commit `46d72b8`)的延续;'留空保留旧值' 语义需后端识别脱敏掩码格式 + 保留旧值,已记入候选下一周期 milestone不属于 v1.0 范畴)
- **服务端联动**: 同上「跨项目联动」字段;后端 commit `46d72b8` 已建立互引闭环,本 phase 无需再次互引;未来若启动「识别脱敏掩码保留旧值」的后端 patch milestone届时双端各写新一轮互引条目
### [2026-05-08] Phase 2前端RBAC 收敛 + AI 模型页凭据槽位入口 ### [2026-05-08] Phase 2前端RBAC 收敛 + AI 模型页凭据槽位入口
配套服务端 Phase本 phase **不**触达服务端;与服务端 v1.0 Phase 2「管理端读写接口」commit `46d72b8` 既有契约保持兼容(不引入新契约) 配套服务端 Phase本 phase **不**触达服务端;与服务端 v1.0 Phase 2「管理端读写接口」commit `46d72b8` 既有契约保持兼容(不引入新契约)