# v0.8 用户旅程交接文档

> Status: **必读起点**，用户清完上下文后从这里继续
> 日期：2026-05-30
> 目标：让用户从 **创作 → 生成 → 时间轴 → 预览 → 导出** 端到端跑通
> 当前阻塞：5 个未修 bug（详见 §4）

## 0. 给"清完上下文的下一轮 Claude"

**你是接班的。一切发生在 `/Users/aoqimin/Desktop/Nomi/.claude/worktrees/impl-v0.6.0/`**——不是父仓库，必须用绝对路径或 `cd` 到这里。

**最近 commit `a05d48c`** 在 origin/main。本地有 13 commits ahead 已 push 完毕。

**重读 `CLAUDE.md`**（项目根目录）才能开工——里面有 4 条硬规则，违反 = 错。

---

## 1. 用户当前阶段 = 真测用户旅程

用户已经反复明示三件事：
1. **"加新必删旧"**（CLAUDE.md 规则 1）
2. **"用户视角 + 极简"**（CLAUDE.md 规则 2）
3. **真测用户旅程，不是猜 / 不是片段补**

我们之前 patch 了 12 个坎（详见 §3）。现在用户**不让继续打地鼠**，要求：
> "你不能说我设置了用户上传，然后用户又不能用。底层上跑通。User Journey 必须测一遍，真实跑通的。"

**接下来你的核心任务 = §6 的 user journey 真测**。

---

## 2. 当前状态快照

### 已 commit + push 的功能
- ✓ Onboarding Agent v0.8（curl-first，验过 GPT Image-2 成功）
- ✓ 模型设置浮卡（OnboardingFloatingPanel 320px 右上）
- ✓ Wizard Modal（OnboardingWizard 480px 中心）
- ✓ Catalog v2 + safeStorage 加密 + 自动迁移
- ✓ 卡片视觉瘦身（TitlePill 删、信息区条件渲染、ErrorBadge）
- ✓ 默认 catalog 清空（不再有 chatfire 幻影）
- ✓ Wizard / Drawer / Modal 三层职责清楚，无并存

### 用户的本地 catalog 当前状态
```
~/Library/Application Support/nomi/model-catalog.json (lowercase "nomi"!)
{
  vendors: [dm-fox, kie]
  models: [
    gpt-5.5     (text)   on dm-fox
    kling-3.0/video (video) on kie     ← 有 9 个 onboarding.fields 但没 meta.imageUrlSlots
  ]
  apiKeysByVendor: {kie: safeStorage, dm-fox: safeStorage 或 plain}
}
```

钥匙串：用户应该已经点过"始终允许"。

### 用户的开发环境
- macOS Apple Silicon
- Nomi.app 装在 `/Applications/Nomi.app`（无签名 dmg, xattr -cr 过）
- 启动 env vars：
  ```bash
  export NOMI_ONBOARDING_AGENT_BASE_URL="https://dm-fox.rjj.cc/codex/v1"
  export NOMI_ONBOARDING_AGENT_MODEL="gpt-5.5"
  export NOMI_ONBOARDING_AGENT_KEY="$(cat .secrets/agent.key)"
  ```
- Secrets 文件：
  - `.secrets/target.key` — kie.ai 的 API key
  - `.secrets/agent.key` — dm-fox gpt-5.5 endpoint key

---

## 3. 历史踩过的 12 个坎（不要再踩）

| # | 撞到的 | 已修 commit | 教训 |
|---|---|---|---|
| 1 | chatfire 种子模型无 key | 96b06f7 | 默认 catalog 必须空 |
| 2 | 找不到配模型入口（删 UI 删过头）| 96b06f7 | 加新必删旧，但删之前先想清楚 |
| 3 | 浮卡（原抽屉）遮挡半屏 | 63b47d4 | 全屏 Drawer 不适合稀疏列表 |
| 4 | 点输入框关浮卡（Mantine Portal）| c8cf20a | 处理 Portal 渲染时考虑跨树点击 |
| 5 | GPT Image 2 partial（agent 卡 maxSteps）| 00a70a6 | curl-first 架构改造 |
| 6 | "GPT Image" 无参数（种子没 fields）| 96b06f7 | 同 #1 |
| 7 | Kling 不在图片节点 dropdown | （没改，filter 合理）| — |
| 8 | GPT-5.5 → 创作拆镜头 404 | a05d48c | `endpoint()` 不能盲拼 `/v1` |
| 9 | Kling 直接上传后还是不能生成 | **未修（B0 任务）** | onboarding.fields 没转 meta.imageUrlSlots |
| 10 | 视频节点按钮灰看不到原因 | a05d48c | placeholder 改 "拖图片节点过来" |
| 11 | 创作 → 生成数据流不通 | **未测（B2）** | — |
| 12 | 时间轴 → 预览 → 导出 | **未测（B2）** | — |

---

## 4. 未修的 5 个 bug（已诊断）

### B0-bug — Kling 3.0 上传不出现"+"槽
**文件**：`src/workbench/generationCanvas/nodes/NodeParameterControls.tsx:756-759`
**代码**：
```ts
const modelImageUrlSlots = [
  ...buildImageUrlSlots(selectedModelOption?.meta),  // ← 用户的 Kling 这里返回 []
  ...imageCatalogReferenceSlot(imageCatalogConfig),
]
```
**根因**：Wizard 接入的模型字段在 `model.onboarding.fields[]`，不在 `model.meta.imageUrlSlots`。`buildImageUrlSlots` 只读 meta。
**修法**：
- 选项 A（首选）：在 `buildImageUrlSlots` 里 fallback 到 `model.onboarding.fields`，识别 `type === 'image-url'` 的字段
- 选项 B：在 `commitOnboardedModelToCatalog`（`electron/runtime.ts`）写入时把 image-url 字段同步到 `meta.imageUrlSlots`

A 更通用，但要查 `selectedModelOption.meta` 的 shape——`useModelOptions.ts` 处。

### B1-bug — onboarding.fields 没全暴露成节点参数
节点上**根本看不到** Kling 3.0 的 9 个参数（duration / aspect_ratio / mode / sound 等）。
**根因**：`NodeParameterControls` 渲染参数也只看 `selectedModelOption.meta.parameters`，不读 `onboarding.fields`。
**修法**：同 B0，把 `onboarding.fields` 桥接到 `selectedModelOption.meta` 的标准 shape。

### B2-bug — 创作 → 拆镜头是否真能跑
**未测**。需要验：用户在创作 ProseMirror 编辑器写文字，点"拆镜头" → `CanvasAssistantPanel` 收事件 → `workbenchAgentsChat` IPC → `runtime.runAgentChat` → 调 dm-fox /chat/completions → 返回 storyboard → 写入画布。

`endpoint()` 修了之后理论上通，但 IPC 链路 + 节点创建 + agent tool calls 没全跑过。

### B3-bug — 时间轴拖入 + 预览 + 导出
**完全未测**。`buildClipFromGenerationNode` + 时间轴 store + ffmpeg 导出。

### B4-bug — 模型 dropdown 不展示其他 kind
**用户拍板**："就是有什么是什么"（**不展示**全部 + 不标黄）。当前严格过滤可能 OK，但要确认严格过滤的代码在哪、是否真的工作了——之前用户说"我配的 gpt-5.5 看不到"，这意味着 dropdown 可能本身就有 bug 没显示 text 模型。**先查清楚再决定**。

---

## 5. 关键代码地图

### 生成路径
```
用户点生成 → BaseGenerationNode.tsx (1196 行 WorkbenchButton onClick=handleGenerate)
→ runGenerationNode (runner/generationRunController.ts:62)
   → canRunGenerationNode (228) ← 这里返回 false 就灰
   → resolveGenerationReferences (runner/generationReferenceResolver.ts:46)
      ← 读 edges + node.meta.firstFrameUrl/lastFrameUrl/referenceImages
→ generateImage / generateVideo (runner/{image,video}Actions.ts)
→ runCatalogGenerationTask (runner/catalogTaskActions.ts)
→ runWorkbenchTaskByVendor (api/taskApi.ts:81)
→ desktop.tasks.run IPC (preload.ts)
→ runtime.runTask (electron/runtime.ts:~1100)
   → findExecutableModelForTask
   → 用 vendor mapping 拼 HTTP 请求
   → 发送 + poll
```

### Agent 路径（创作 / 拆镜头）
```
创作模式拆镜头按钮 → requestStoryboardPlanning (storyboardLauncher.ts)
→ window dispatch 'nomi:generation:storyboard:request'
→ CanvasAssistantPanel 监听 (line 298)
→ submitAgentMessage → sendWorkbenchAiMessage (workbenchAiClient.ts:47)
→ workbenchAgentsChat (api/server.ts:398)
→ desktop.agents.chat IPC
→ runtime.runAgentChat (electron/runtime.ts:2366)
   → chooseTextModel() ← 选 text kind 模型
   → endpoint(vendor, '/v1') ← 已修，不再双拼
   → buildAiSdkModel → generateText
```

### Onboarding 路径
```
点 + 添加模型 → OnboardingDrawer 里弹 OnboardingWizard Modal
→ user 粘 docs URL + key → 点开始
→ nomiDesktop.onboarding.start IPC (preload.ts)
→ main.ts onboarding handler (line 257)
→ runOnboardingTrial (electron/ai/onboarding/agent.ts)
   → 8 tools (tools.ts，已删 add_field_with_evidence + check_completeness)
   → curl-first prompt (systemPrompt.ts)
→ 成功 → commitOnboardedModelToCatalog (runtime.ts)
→ catalog.json 写入 + safeStorage 加密 key
```

---

## 6. 接下来的执行计划（**这就是你的 task list**）

### B0 — 修视频上传槽（30 min）
1. 读 `src/workbench/generationCanvas/nodes/NodeParameterControls.tsx` 的 `buildImageUrlSlots`
2. 读 `src/config/useModelOptions.ts` 看 `selectedModelOption.meta` 结构
3. 让 `buildImageUrlSlots` 也读 `selectedModelOption.onboarding?.fields`，把 `type === 'image-url'` 的字段转成 slot
4. 同步 `commitOnboardedModelToCatalog` 把 image-url 字段写一份到 `meta.imageUrlSlots`（双写，给老代码兜底）
5. build + repackage + reinstall

### B1 — onboarding.fields 暴露成参数控件（30 min）
1. 同样在 `NodeParameterControls`，找到读 `meta.parameters` 的地方
2. fallback 到 `onboarding.fields`，按 type 渲染（text 输入 / select 下拉 / number / boolean）
3. 验证 Kling 的 9 个参数（duration / aspect_ratio / mode / sound / image_urls / multi_shots / multi_prompt / kling_elements / callBackUrl）能在节点上显示
4. build + ship

### B2 — User Journey 真测（45-60 min）
**清空 catalog，从零跑一遍**：

```bash
# 0. 备份 catalog
cp "$HOME/Library/Application Support/nomi/model-catalog.json" /tmp/catalog-before-b2.json

# 1. 启动 Nomi（已装好）
pkill -9 -f Nomi.app; sleep 2
export NOMI_ONBOARDING_AGENT_BASE_URL=https://dm-fox.rjj.cc/codex/v1
export NOMI_ONBOARDING_AGENT_MODEL=gpt-5.5
export NOMI_ONBOARDING_AGENT_KEY="$(cat .secrets/agent.key)"
nohup /Applications/Nomi.app/Contents/MacOS/Nomi > /tmp/nomi-runtime.log 2>&1 & disown
```

然后**让用户**执行步骤（你不能直接驱动 GUI）：

| Step | 用户做 | 验证点 |
|---|---|---|
| 1 | 添加 GPT-5.5 模型（Wizard）| dropdown 文本类有 GPT-5.5 |
| 2 | 添加 Kling 3.0（Wizard, docs.kie.ai/api-reference/kling/v3-0/generate-video）| dropdown 视频类有 Kling |
| 3 | 添加 GPT Image 2（Wizard, docs.kie.ai/market/gpt/gpt-image-2-text-to-image）| dropdown 图片类有 |
| 4 | 进创作 → 写一段 → 拆镜头 | 画布出 storyboard，每个节点带 prompt |
| 5 | 切生成模式，选一个 shot 节点 → 选 GPT Image 2 → 点生成 | 出图（或诊断为什么失败）|
| 6 | 视频节点选 Kling → 上传 / 连边 → 点生成 | 出视频 |
| 7 | 拖视频到时间轴 | 出现 clip |
| 8 | 切预览 | 能播 |
| 9 | 点导出 | 出 MP4 |

**每步阻塞**：在文档里加一行"Step N 阻塞：xxx，根因 yyy"，修完再继续。

### B3 — 写 User Journey Checklist（15 min）
1. 把 B2 走通的步骤抄进 `docs/qa/user-journey-checklist.md`
2. 加到 CLAUDE.md 规则 5："每次大改 → 跑一遍 checklist 才能 ship"

### B4 — dropdown 验真假
1. 看 `useModelOptions` 怎么过滤
2. 验证当前是不是 strictly by kind
3. 如果有 bug 修，否则不改（用户说"有什么是什么"）

---

## 7. 给下一轮 Claude 的工作纪律

1. **必须用绝对路径 / cd 到 impl-v0.6.0**——cwd 会乱漂
2. **commit 用 heredoc**，避免引号 + Node 钩子崩
3. **每次 build + repackage 之后 pkill -9 -f Nomi.app 再启动**——旧进程不会自己退
4. **不要在 NomiStudioApp 之外加 Drawer 包裹 Wizard**——已经踩过
5. **遇到"点不到东西"先怀疑 Portal**（Mantine Modal / Drawer / Popover 在 body 顶层）

---

## 8. 用户对话风格备忘

- 中文，半技术半口语
- 不耐烦时给 a/b/c 选项
- 反复强调"不要新旧并存"、"用户视角"、"极简"、"真测"
- 不喜欢长技术解释，喜欢"对比表 + 一句话推荐"
- 写文档要"先把重要的东西写出来"避免上下文丢失（**这就是为什么有这份文档**）

---

## 9. 一个 mantra

> **Don't ask the user to do what the system should figure out. Don't tell the user there's only one way when both ways could work. Test before you ship.**
