Files
teaching-design/docs/superpowers/plans/2026-06-15-fix-broken-lessons-implementation.md
2026-06-15 23:14:16 -06:00

372 lines
9.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Fix Broken Lessons Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 在工作区工具栏新增「修复 X 处提示」按钮,点击后弹出 `FixBrokenDialog`,依次重新生成所有有警告的教案并原位替换,完成后显示摘要。
**Architecture:**`useTeachingBook` 中新增 `regenerateLesson(id)` 用于原位替换单篇;新建 `FixBrokenDialog.vue` 负责进度显示confirm → running → done/error`WorkspaceToolbar` 新增 `fixBroken` emit 与条件显示按钮;`WorkspaceView` 协调状态并驱动循环。
**Tech Stack:** Vue 3 + TypeScript与现有 BatchGenerateDialog 模式保持一致。
---
## File Structure
| 文件 | 操作 |
|---|---|
| `src/composables/useTeachingBook.ts` | 修改:新增 `regenerateLesson`,接口加 `regenerateLesson` |
| `src/components/FixBrokenDialog.vue` | 新建:修复进度对话框 |
| `src/components/WorkspaceToolbar.vue` | 修改:新增 `fixBroken` emit 与按钮 |
| `src/components/WorkspaceView.vue` | 修改:新增 fix 状态与处理逻辑 |
---
## Task 1: `regenerateLesson` in `useTeachingBook.ts`
**Files:**
- Modify: `src/composables/useTeachingBook.ts`
- [ ] **Step 1: 在 `TeachingBookStore` 接口加入 `regenerateLesson`**
`generateLesson` 行下方追加:
```typescript
regenerateLesson: (id: DesignId) => Promise<GenerateLessonResult>
```
- [ ] **Step 2: 实现 `regenerateLesson` 函数**
`generateLesson` 函数后插入(`return {` 之前):
```typescript
async function regenerateLesson(id: DesignId): Promise<GenerateLessonResult> {
const existing = book.value.designs.find((d) => d.id === id)
if (!existing) return { ok: false, message: '找不到该教案。' }
const topic = existing.originalFilename.replace(/\.md$/i, '')
try {
const result = await booksApi.generateLesson(topic)
const newDesign = parseTeachingDesign(result.filename, result.markdown)
const index = book.value.designs.findIndex((d) => d.id === id)
if (index !== -1) {
book.value.designs.splice(index, 1, newDesign)
if (book.value.selectedId === id) {
book.value.selectedId = newDesign.id
}
}
touch()
return { ok: true }
} catch (error) {
return { ok: false, message: error instanceof Error ? error.message : '修复失败。' }
}
}
```
- [ ] **Step 3: 在 `return` 对象中暴露 `regenerateLesson`**
`generateLesson,` 行之后加一行:
```typescript
regenerateLesson,
```
- [ ] **Step 4: 运行测试确认无破坏**
```bash
bun run test -- src/composables/useTeachingBook.test.ts
```
期望:全部通过。
- [ ] **Step 5: Commit**
```bash
git add src/composables/useTeachingBook.ts
git commit -m "feat: add regenerateLesson to useTeachingBook"
```
---
## Task 2: 新建 `FixBrokenDialog.vue`
**Files:**
- Create: `src/components/FixBrokenDialog.vue`
- [ ] **Step 1: 创建组件文件**
内容如下:
```vue
<script setup lang="ts">
import { watch, ref } from 'vue'
type Phase = 'confirm' | 'running' | 'done' | 'error'
const props = defineProps<{
running: boolean
done: number
total: number
currentTopic: string
error: string | null
}>()
const emit = defineEmits<{
start: []
cancel: []
close: []
}>()
const phase = ref<Phase>('confirm')
watch(
() => props.running,
(val) => {
if (val) {
phase.value = 'running'
} else if (phase.value === 'running') {
phase.value = props.error ? 'error' : 'done'
}
},
)
function handleClose(): void {
phase.value = 'confirm'
emit('close')
}
</script>
<template>
<div class="dialog-overlay" role="dialog" aria-modal="true" aria-labelledby="fix-dialog-title">
<div class="dialog">
<h2 id="fix-dialog-title">修复问题教案</h2>
<!-- 确认 -->
<template v-if="phase === 'confirm'">
<p> <strong>{{ total }}</strong> 篇教案存在解析问题点击开始将重新生成并原位替换</p>
<div class="dialog-actions">
<button type="button" @click="emit('start')">开始修复</button>
<button type="button" @click="emit('close')">取消</button>
</div>
</template>
<!-- 修复中 -->
<template v-else-if="phase === 'running'">
<p class="batch-progress-label">
正在修复第 <strong>{{ done + 1 }}</strong> / {{ total }}
</p>
<p class="batch-current-topic">{{ currentTopic }}</p>
<div class="batch-progress-bar">
<div class="batch-progress-fill" :style="{ width: `${(done / total) * 100}%` }" />
</div>
<div class="dialog-actions">
<button type="button" @click="emit('cancel')">停止</button>
</div>
</template>
<!-- 出错 -->
<template v-else-if="phase === 'error'">
<p class="app-notice app-notice--error" role="alert">{{ error }}</p>
<p>已修复 {{ done }} / {{ total }} 修复中止</p>
<div class="dialog-actions">
<button type="button" @click="handleClose">关闭</button>
</div>
</template>
<!-- 完成 -->
<template v-else-if="phase === 'done'">
<p>已修复 <strong>{{ done }}</strong> / {{ total }} 篇教案</p>
<div class="dialog-actions">
<button type="button" @click="handleClose">关闭</button>
</div>
</template>
</div>
</div>
</template>
```
- [ ] **Step 2: Commit**
```bash
git add src/components/FixBrokenDialog.vue
git commit -m "feat: add FixBrokenDialog component"
```
---
## Task 3: WorkspaceToolbar — 新增修复按钮
**Files:**
- Modify: `src/components/WorkspaceToolbar.vue`
- [ ] **Step 1: 新增 `fixBroken` emit**
`defineEmits` 改为:
```typescript
defineEmits<{
upload: []
print: []
export: []
clear: []
generate: []
batchGenerate: []
fixBroken: []
back: []
}>()
```
- [ ] **Step 2: 在 `warningCount` span 前加修复按钮**
把:
```html
<span v-if="warningCount > 0" class="workspace-toolbar-warning">
{{ warningCount }} 处提示
</span>
```
替换为:
```html
<template v-if="warningCount > 0">
<button type="button" data-testid="fix-broken" @click="$emit('fixBroken')">
修复 {{ warningCount }} 处提示
</button>
</template>
```
- [ ] **Step 3: Commit**
```bash
git add src/components/WorkspaceToolbar.vue
git commit -m "feat: add fix-broken button to WorkspaceToolbar"
```
---
## Task 4: WorkspaceView — 协调 fix 流程
**Files:**
- Modify: `src/components/WorkspaceView.vue`
- [ ] **Step 1: import FixBrokenDialog**
在现有 import 列表里加(与 `BatchGenerateDialog` 相邻):
```typescript
import FixBrokenDialog from './FixBrokenDialog.vue'
```
- [ ] **Step 2: 解构 `regenerateLesson`**
`useTeachingBook` 解构对象中加:
```typescript
regenerateLesson,
```
- [ ] **Step 3: 新增 fix 状态 refs**
`batchCancelled` ref 之后插入:
```typescript
const showFixDialog = ref(false)
const fixRunning = ref(false)
const fixDone = ref(0)
const fixTotal = ref(0)
const fixCurrentTopic = ref('')
const fixError = ref<string | null>(null)
const fixCancelled = ref(false)
```
- [ ] **Step 4: 新增 fix 处理函数**
`closeBatchDialog` 函数之后插入:
```typescript
function openFixDialog(): void {
fixTotal.value = book.value.designs.filter((d) => d.warnings.length > 0).length
fixDone.value = 0
fixError.value = null
showFixDialog.value = true
}
async function handleFixStart(): Promise<void> {
const broken = book.value.designs.filter((d) => d.warnings.length > 0)
fixCancelled.value = false
fixRunning.value = true
for (const lesson of broken) {
if (fixCancelled.value) break
fixCurrentTopic.value = lesson.originalFilename.replace(/\.md$/i, '')
const result = await regenerateLesson(lesson.id)
if (!result.ok) {
fixError.value = result.message
break
}
fixDone.value++
}
fixRunning.value = false
}
function handleFixCancel(): void {
fixCancelled.value = true
}
function closeFixDialog(): void {
showFixDialog.value = false
fixDone.value = 0
fixTotal.value = 0
fixError.value = null
}
```
- [ ] **Step 5: 在 template 中挂载 FixBrokenDialog**
`<BatchGenerateDialog ... />` 之后加:
```html
<FixBrokenDialog
v-if="showFixDialog"
:running="fixRunning"
:done="fixDone"
:total="fixTotal"
:current-topic="fixCurrentTopic"
:error="fixError"
@start="handleFixStart"
@cancel="handleFixCancel"
@close="closeFixDialog"
/>
```
- [ ] **Step 6: 在 WorkspaceToolbar 上绑定 `@fix-broken`**
把:
```html
@batch-generate="showBatchDialog = true"
```
改为:
```html
@batch-generate="showBatchDialog = true"
@fix-broken="openFixDialog"
```
- [ ] **Step 7: 运行全量测试**
```bash
bun run test
```
期望:`markdownTable``markdownParser``useTeachingBook``PrintBook` 相关测试全部通过App.test.ts 的 2 个已知失败不影响)。
- [ ] **Step 8: Commit**
```bash
git add src/components/WorkspaceView.vue
git commit -m "feat: wire fix-broken flow in WorkspaceView"
```