` 里,不抽组件——内容简单(spinner + 文案 + 一个数据卡),无复用价值。
## 6. 待清理的死代码
启用 v-if 早分支后,control zone 里的 `'finalizing'` 状态层永远不会被渲染(外层提前替换),属于死代码:
- 删 `state` computed 内 `'finalizing'` 分支:
```ts
if (engine.isComplete.value) return reportFetchInflight.value ? 'finalizing' : 'done'
```
改成:
```ts
if (engine.isComplete.value) return 'done'
```
(`reportFetchInflight === true` 时根级 v-if 已经接管,不再走 state machine。)
- 删 `state` 类型联合中的 `'finalizing'` literal。
- 删 control zone 里整个 `` 块及其内容。
## 7. 错误处理
**完全不在 finalizing 屏内处理**。`fetchReportSafe` 现有逻辑:
```ts
async function fetchReportSafe() {
reportFetchInflight.value = true
try {
await engine.completeSession()
return await engine.getReport()
} catch (err) {
console.warn('[speaking] getReport failed:', err)
return null
} finally {
reportFetchInflight.value = false
}
}
```
无论成功 / 失败 / 超时,`reportFetchInflight` 都会归 false → finalizing 屏自动消失 → 父组件 `complete` 事件接管。父组件 `handleDialogueComplete(null)` 已经在 `'completed'` 阶段渲染 `reportError` 文案(TopicDiscussionPreview.vue:443-449),覆盖三种失败语义(超时、failed、incomplete)。
**不增加重试按钮**——重试入口在父组件 report-stage 已隐含(用户看到错误后可走 `restart` / 关闭重开),finalizing 屏只负责"等待中"语义。
## 8. 不可取消
finalizing 屏不提供"取消"按钮:
- `completeSession` 已经触发后端状态变更,回退无意义。
- `getReport` 即使打断,下次进同一 session 仍会拉到(或拉不到)同样的报告。
- 减少误触离开。
## 9. 范围外
- **后端不动**。
- **不改 `useDialogueEngine`**——`reportFetchInflight` 是 view-local state,不下沉到 composable。
- **不改父组件 `TopicDiscussionPreview`**——`complete` 事件契约不变,错误态渲染逻辑不变。
- **不动现有自动播放 / 沉默检测 / 徽章 watcher**——这些 watcher 在 v-if 早分支生效后仍会订阅,但因聊天区 DOM 不渲染、用户无法触发新消息,自然静默。
## 10. 验收
人工验证三条路径:
| 场景 | 操作 | 期望 |
|---|---|---|
| 自动完成 | 完成最后一轮 → 看 finalizing | 立即出现全屏 loading,原 header / 控制条消失;报告拉到后父组件切到报告页 |
| 倒计时归零 | 等 countdown 到 0 | 同上 |
| 手动结束 | 点三点 → 结束并查看报告 | 弹窗关闭后**立即**出现全屏 loading(不再是空白等待) |
| 报告失败 | 模拟 `getReport` reject | finalizing 屏消失,父组件报告页显示错误文案 |
| 数据卡 0 句场景 | 极端:刚开 session 就立刻结束 | 卡片显示"0 轮 · 0 句 · 0:00",不报错 |