64da95f7e1
Covers schema v9, book settings, volume/chapter management UI, template context, AI session menu, focus mode, and TTS per master completion spec. Co-authored-by: Cursor <cursoragent@cursor.com>
453 lines
16 KiB
Markdown
453 lines
16 KiB
Markdown
# 笔临 Wave 1 基础治理 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:** 交付 v1.2.0:清除 Wave 1 范围内全部 `comingSoon` 占位,补全书籍/分卷/章节管理 UI,模板变量自动填充,AI 会话管理,专注模式与 TTS 快捷键。
|
||
|
||
**Architecture:** schema v9 扩展 `chapter_tags` / `published_at` / `book_preferences.always_show_cockpit`;扩展 `BookMeta` 与 `BOOK_UPDATE_META`;渲染进程新增 `BookSettingsTab`、`FocusMode` overlay、`ChapterMetaPanel`;复用已有 `volume.*` / `chapter.delete` IPC;`chapter-template` 上下文从 outlines/settings 聚合。
|
||
|
||
**Tech Stack:** Electron 36、electron-vite、React 18、TypeScript、node:sqlite、Zustand、i18next、Vitest、Playwright
|
||
|
||
**Spec:** `docs/superpowers/specs/2026-07-08-bilin-master-completion-design.md` §5
|
||
|
||
**Master Program:** Wave 1/5 → v1.2.0
|
||
|
||
---
|
||
|
||
## File Map
|
||
|
||
| 文件 | 职责 |
|
||
|------|------|
|
||
| `src/main/db/schema-v9.sql` | chapter_tags 表 |
|
||
| `src/main/db/migrate.ts` | CURRENT_VERSION=9,published_at/word_count_threshold |
|
||
| `src/main/db/repositories/chapter-tag.repo.ts` | 章节标签 CRUD |
|
||
| `src/main/db/repositories/chapter.repo.ts` | published_at、summary、story_time、threshold 读写 |
|
||
| `src/main/services/book-registry.ts` | updateMeta 扩展 name/category/targetWordCount/coverPath/synopsis |
|
||
| `src/main/services/cockpit.service.ts` | always_show_cockpit 逻辑 |
|
||
| `src/main/ipc/handlers/book.handler.ts` | BOOK_UPDATE_META 扩展 |
|
||
| `src/main/ipc/handlers/chapter.handler.ts` | CHAPTER_TAG_*、published_at on setPublishStatus |
|
||
| `src/main/ipc/handlers/ai.handler.ts` | AI_SESSION_EXPORT |
|
||
| `src/shared/types.ts` | BookMeta、UpdateBookMetaParams、ChapterTag |
|
||
| `src/shared/ipc-channels.ts` | 新 IPC 常量 |
|
||
| `src/preload/index.ts` + `electron-api.d.ts` | bookPrefs、chapterTags、sessionExport |
|
||
| `src/shared/chapter-template.ts` | `buildTemplateContext()` 含 outlineItem/characterList |
|
||
| `src/renderer/components/book/BookSettingsTab.tsx` | 右栏书籍设置 |
|
||
| `src/renderer/components/chapter/ChapterMetaPanel.tsx` | summary/story_time/tags |
|
||
| `src/renderer/components/volume/VolumeContextMenu.tsx` | 分卷右键 |
|
||
| `src/renderer/components/focus/FocusModeOverlay.tsx` | 专注模式 |
|
||
| `src/renderer/lib/tts-controller.ts` | TTS 朗读 |
|
||
| `src/renderer/components/layout/RightPanel.tsx` | 接入 BookSettingsTab |
|
||
| `src/renderer/components/layout/EditorLayout.tsx` | 章双击改名、删除、meta、阈值变色 |
|
||
| `src/renderer/components/layout/TopBar.tsx` | 日更进度条(细条) |
|
||
| `src/renderer/components/ai/AiSessionMenu.tsx` | 会话重命名/归档/导出/删除 |
|
||
| `src/renderer/components/ai/AiPanel.tsx` | 接入 AiSessionMenu |
|
||
| `src/renderer/components/wordfreq/WordFreqPanel.tsx` | 命名检查入口 |
|
||
| `src/renderer/App.tsx` | focusMode / toggleTTS 快捷键 |
|
||
| `src/renderer/stores/useBookPrefsStore.ts` | alwaysShowCockpit |
|
||
| `public/locales/*/translation.json` | bookSettings / chapterMeta / focus / tts |
|
||
| `tests/main/chapter-template-context.test.ts` | UT outlineItem/characterList |
|
||
| `tests/main/chapter-tag.repo.test.ts` | IT 标签 |
|
||
| `e2e/book-settings.spec.ts` | E2E 书籍设置+删书 |
|
||
| `e2e/chapter-management.spec.ts` | E2E 章改名+删除 |
|
||
| `e2e/focus-tts.spec.ts` | E2E 专注+TTS |
|
||
| `e2e/chapter-template.spec.ts` | 扩展 E2E-TEMPL-05 |
|
||
|
||
---
|
||
|
||
## Task 1: Schema v9 + 类型扩展
|
||
|
||
**Files:**
|
||
- Create: `src/main/db/schema-v9.sql`
|
||
- Modify: `src/main/db/migrate.ts`
|
||
- Modify: `src/shared/types.ts`
|
||
- Modify: `src/shared/ipc-channels.ts`
|
||
|
||
- [ ] **Step 1: schema-v9.sql**
|
||
|
||
```sql
|
||
CREATE TABLE IF NOT EXISTS chapter_tags (
|
||
chapter_id TEXT NOT NULL,
|
||
tag TEXT NOT NULL,
|
||
color TEXT DEFAULT '',
|
||
PRIMARY KEY (chapter_id, tag),
|
||
FOREIGN KEY (chapter_id) REFERENCES chapters(id) ON DELETE CASCADE
|
||
);
|
||
```
|
||
|
||
- [ ] **Step 2: migrate.ts** — `CURRENT_VERSION = 9`,新增 block:
|
||
|
||
```typescript
|
||
if (current < 9) {
|
||
tryAlter(db, 'ALTER TABLE chapters ADD COLUMN published_at TEXT DEFAULT NULL')
|
||
tryAlter(db, 'ALTER TABLE chapters ADD COLUMN word_count_threshold INTEGER DEFAULT NULL')
|
||
db.exec(schemaV9)
|
||
db.prepare('INSERT INTO schema_version (version, description) VALUES (?, ?)').run(9, 'W1 foundation')
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 3: types.ts** — 扩展:
|
||
|
||
```typescript
|
||
export interface BookMeta {
|
||
// existing...
|
||
coverPath?: string | null
|
||
synopsis?: string
|
||
}
|
||
|
||
export interface UpdateBookMetaParams {
|
||
name?: string
|
||
category?: string
|
||
targetWordCount?: number | null
|
||
coverPath?: string | null
|
||
synopsis?: string
|
||
lastChapterId?: string | null
|
||
status?: BookStatus
|
||
}
|
||
|
||
export interface ChapterTag {
|
||
chapterId: string
|
||
tag: string
|
||
color: string
|
||
}
|
||
|
||
// Chapter 已有 summary?, storyTime? — 确保 chapter.update IPC 接受它们
|
||
```
|
||
|
||
- [ ] **Step 4: ipc-channels.ts**
|
||
|
||
```typescript
|
||
CHAPTER_TAG_LIST: 'chapter:tagList',
|
||
CHAPTER_TAG_SET: 'chapter:tagSet',
|
||
CHAPTER_TAG_REMOVE: 'chapter:tagRemove',
|
||
BOOK_PREFS_GET: 'book:prefsGet',
|
||
BOOK_PREFS_SET: 'book:prefsSet',
|
||
AI_SESSION_EXPORT: 'ai:sessionExport',
|
||
```
|
||
|
||
- [ ] **Step 5: 运行** `npm run build` — 应通过
|
||
|
||
---
|
||
|
||
## Task 2: Repository + BookRegistry 扩展
|
||
|
||
**Files:**
|
||
- Create: `src/main/db/repositories/chapter-tag.repo.ts`
|
||
- Modify: `src/main/db/repositories/chapter.repo.ts`
|
||
- Modify: `src/main/services/book-registry.ts`
|
||
- Modify: `src/main/services/cockpit.service.ts`
|
||
|
||
- [ ] **Step 1: ChapterTagRepository**
|
||
|
||
```typescript
|
||
export class ChapterTagRepository {
|
||
constructor(private db: SqliteDb) {}
|
||
list(chapterId: string): ChapterTag[] { /* SELECT */ }
|
||
set(chapterId: string, tag: string, color = ''): void { /* INSERT OR REPLACE */ }
|
||
remove(chapterId: string, tag: string): void { /* DELETE */ }
|
||
listAll(): ChapterTag[] { /* for book open */ }
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: chapter.repo update** — `update()` 与 `setPublishStatus()` 支持 `summary`, `storyTime`, `wordCountThreshold`, `publishedAt`:
|
||
|
||
```typescript
|
||
setPublishStatus(id: string, publishStatus: PublishStatus): Chapter {
|
||
const patch: Parameters<typeof this.update>[1] = { publishStatus }
|
||
if (publishStatus === 'published') {
|
||
patch.publishedAt = new Date().toISOString()
|
||
}
|
||
return this.update(id, patch)
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 3: book-registry.updateMeta** — 签名改为 `Partial<Pick<BookMeta, 'name' | 'category' | 'targetWordCount' | 'coverPath' | 'synopsis' | 'lastOpenedAt' | 'lastChapterId' | 'status'>>`
|
||
|
||
- [ ] **Step 4: cockpit.service**
|
||
|
||
```typescript
|
||
shouldShowOnOpen(): boolean {
|
||
const prefs = new BookPrefsRepository(this.db)
|
||
if (prefs.get('alwaysShowCockpit') === 'true') return true
|
||
return prefs.get('cockpitSeen') !== 'true'
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 5: UT** `tests/main/chapter-tag.repo.test.ts` — 创建章+标签+列表
|
||
|
||
Run: `npm run test -- --run tests/main/chapter-tag.repo.test.ts`
|
||
|
||
---
|
||
|
||
## Task 3: IPC handlers + preload
|
||
|
||
**Files:**
|
||
- Modify: `src/main/ipc/handlers/book.handler.ts`
|
||
- Modify: `src/main/ipc/handlers/chapter.handler.ts`
|
||
- Create: `src/main/ipc/handlers/book-prefs.handler.ts`
|
||
- Modify: `src/main/ipc/handlers/ai.handler.ts`
|
||
- Modify: `src/main/ipc/register.ts`
|
||
- Modify: `src/preload/index.ts`
|
||
- Modify: `src/shared/electron-api.d.ts`
|
||
|
||
- [ ] **Step 1: book.handler** — `BOOK_UPDATE_META` 接受 `UpdateBookMetaParams`
|
||
|
||
- [ ] **Step 2: chapter.handler** — 扩展 `CHAPTER_UPDATE` 接受 `summary`, `storyTime`, `wordCountThreshold`;新增:
|
||
|
||
```typescript
|
||
ipcMain.handle(IPC.CHAPTER_TAG_LIST, (_e, { bookId, chapterId }) =>
|
||
wrap(() => new ChapterTagRepository(registry.getDb(bookId)).list(chapterId)))
|
||
// TAG_SET / TAG_REMOVE 同理
|
||
```
|
||
|
||
- [ ] **Step 3: book-prefs.handler**
|
||
|
||
```typescript
|
||
ipcMain.handle(IPC.BOOK_PREFS_GET, (_e, { bookId, key }) =>
|
||
wrap(() => new BookPrefsRepository(registry.getDb(bookId)).get(key)))
|
||
ipcMain.handle(IPC.BOOK_PREFS_SET, (_e, { bookId, key, value }) =>
|
||
wrap(() => { new BookPrefsRepository(registry.getDb(bookId)).set(key, value) }))
|
||
```
|
||
|
||
- [ ] **Step 4: ai.handler** — `AI_SESSION_EXPORT`:读 messages,格式化 markdown,`dialog.showSaveDialog`,写文件
|
||
|
||
- [ ] **Step 5: preload** — `book.updateMeta` 扩展;`bookPrefs.get/set`;`chapterTag.list/set/remove`;`ai.session.export(bookId, sessionId, format)`
|
||
|
||
- [ ] **Step 6: build** `npm run build`
|
||
|
||
---
|
||
|
||
## Task 4: 模板上下文 enrich
|
||
|
||
**Files:**
|
||
- Modify: `src/shared/chapter-template.ts`
|
||
- Modify: `src/renderer/components/chapter/TemplateQuickCreateModal.tsx`
|
||
- Create: `tests/main/chapter-template-context.test.ts`
|
||
|
||
- [ ] **Step 1: 新增 `buildTemplateContext()`**
|
||
|
||
```typescript
|
||
export function buildTemplateContext(input: {
|
||
chapterTitle: string
|
||
penName: string
|
||
volumes: Volume[]
|
||
chapters: Chapter[]
|
||
outlines: OutlineItem[]
|
||
settings: SettingEntry[]
|
||
activeVolumeId: string | null
|
||
selectedChapterId?: string | null
|
||
}): TemplateContext {
|
||
const volChapters = input.chapters
|
||
.filter((c) => c.volumeId === input.activeVolumeId)
|
||
.sort((a, b) => a.sortOrder - b.sortOrder)
|
||
const chapterNumber = volChapters.length + 1
|
||
const outlineItem =
|
||
input.outlines.find((o) => o.chapterId === input.selectedChapterId)?.title ?? ''
|
||
const characterList = input.settings
|
||
.filter((s) => s.type === 'character')
|
||
.slice(0, 8)
|
||
.map((s) => s.name)
|
||
.join('、')
|
||
return {
|
||
chapterNumber,
|
||
chapterTitle: input.chapterTitle,
|
||
penName: input.penName,
|
||
date: new Date().toLocaleDateString('zh-CN'),
|
||
volumeName: input.volumes.find((v) => v.id === input.activeVolumeId)?.name ?? '',
|
||
previousChapterTitle: volChapters.at(-1)?.title ?? '',
|
||
outlineItem,
|
||
characterList
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: TemplateQuickCreateModal** — 用 `buildTemplateContext` 替换内联 `buildContext`
|
||
|
||
- [ ] **Step 3: UT**
|
||
|
||
```typescript
|
||
it('UT-TEMPL-05: characterList from settings', () => {
|
||
const ctx = buildTemplateContext({ /* 2 characters */ })
|
||
expect(ctx.characterList).toContain('林远')
|
||
})
|
||
```
|
||
|
||
Run: `npm run test -- --run tests/main/chapter-template-context.test.ts`
|
||
|
||
---
|
||
|
||
## Task 5: BookSettingsTab
|
||
|
||
**Files:**
|
||
- Create: `src/renderer/components/book/BookSettingsTab.tsx`
|
||
- Create: `src/renderer/stores/useBookPrefsStore.ts`
|
||
- Modify: `src/renderer/components/layout/RightPanel.tsx`
|
||
|
||
- [ ] **Step 1: BookSettingsTab** — 表单字段:
|
||
- 书名、分类、目标字数、简介(textarea)
|
||
- 封面:按钮选文件 → 存 `userData/covers/{bookId}.jpg` 路径到 meta.coverPath
|
||
- 「每次打开显示驾驶舱」checkbox → `bookPrefs.set('alwaysShowCockpit', 'true'|'false')`
|
||
- 危险区:删书 → confirm → `book.delete` → `loadBooks` → `setView('home')`
|
||
|
||
- [ ] **Step 2: RightPanel** — `book-settings` panel 渲染 `<BookSettingsTab />`
|
||
|
||
- [ ] **Step 3: i18n** — `bookSettings.*` 键(中英)
|
||
|
||
---
|
||
|
||
## Task 6: 分卷右键 + 章节管理 UI
|
||
|
||
**Files:**
|
||
- Create: `src/renderer/components/volume/VolumeContextMenu.tsx`
|
||
- Create: `src/renderer/components/chapter/ChapterMetaPanel.tsx`
|
||
- Modify: `src/renderer/components/layout/EditorLayout.tsx`
|
||
|
||
- [ ] **Step 1: VolumeContextMenu** — 绑定分卷标题 `onContextMenu`:
|
||
- 重命名:prompt → `volume.update`
|
||
- 编辑简述:prompt → `volume.update({ description })`
|
||
- 删除:若卷内无章 → `volume.delete`;有章则 toast 错误
|
||
|
||
- [ ] **Step 2: 章节双击改名** — `chapter-item` `onDoubleClick` → input inline → `chapter.update({ title })`
|
||
|
||
- [ ] **Step 3: 章节删除** — 右键或 hover 图标 → confirm → `chapter.delete` → `refreshChapters`
|
||
|
||
- [ ] **Step 4: ChapterMetaPanel** — 侧栏或状态栏按钮打开:
|
||
- summary、story_time 输入
|
||
- 标签 chips + 添加输入
|
||
- word_count_threshold 数字
|
||
|
||
- [ ] **Step 5: 发布日期** — `setPublishStatus` 后本地 chapter 显示 `published_at`(只读)
|
||
|
||
- [ ] **Step 6: 字数阈值变色** — 状态栏 `wordCount.chapter` 与 `currentChapter.wordCountThreshold` 比较,超则橙色 class
|
||
|
||
---
|
||
|
||
## Task 7: AI 会话菜单
|
||
|
||
**Files:**
|
||
- Create: `src/renderer/components/ai/AiSessionMenu.tsx`
|
||
- Modify: `src/renderer/components/ai/AiPanel.tsx`
|
||
- Modify: `src/renderer/stores/useAiStore.ts`
|
||
|
||
- [ ] **Step 1: AiSessionMenu** — 会话选择器旁「⋯」按钮:
|
||
- 重命名 → `ai.session.update({ title })`
|
||
- 归档/取消归档 → `{ archived: true/false }`
|
||
- 导出 → 子菜单 txt/md → `ai.session.export`
|
||
- 删除 → confirm → `ai.session.delete`
|
||
|
||
- [ ] **Step 2: useAiStore** — `loadSessions(bookId, true)` 加载含归档;归档会话 optgroup 或折叠区
|
||
|
||
- [ ] **Step 3: i18n** — `ai.session.*`
|
||
|
||
---
|
||
|
||
## Task 8: 专注模式 + TTS
|
||
|
||
**Files:**
|
||
- Create: `src/renderer/components/focus/FocusModeOverlay.tsx`
|
||
- Create: `src/renderer/lib/tts-controller.ts`
|
||
- Modify: `src/renderer/App.tsx`
|
||
- Modify: `src/renderer/styles/layout.css`
|
||
|
||
- [ ] **Step 1: FocusModeOverlay** — `useAppStore.focusMode` boolean:
|
||
- true 时 `#app` 加 `focus-mode` class(CSS 隐藏 sidebars/right-panel/topbar 非必要按钮)
|
||
- Esc 或按钮退出
|
||
|
||
- [ ] **Step 2: App.tsx** — `focusMode` 动作调用 `setFocusMode(true)`;再次按退出
|
||
|
||
- [ ] **Step 3: tts-controller.ts**
|
||
|
||
```typescript
|
||
export function speakText(text: string, lang = 'zh-CN'): void {
|
||
const u = new SpeechSynthesisUtterance(text)
|
||
u.lang = lang
|
||
window.speechSynthesis.cancel()
|
||
window.speechSynthesis.speak(u)
|
||
}
|
||
export function stopSpeaking(): void {
|
||
window.speechSynthesis.cancel()
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 4: App.tsx** — `toggleTTS`:若正在朗读则 stop;否则读当前章 `editor.getText()` 或选区
|
||
|
||
- [ ] **Step 5: i18n** — `focus.*` / `tts.*`
|
||
|
||
---
|
||
|
||
## Task 9: 占位清零 + 顶栏进度
|
||
|
||
**Files:**
|
||
- Modify: `src/renderer/components/wordfreq/WordFreqPanel.tsx`
|
||
- Modify: `src/renderer/components/layout/TopBar.tsx`
|
||
- Modify: `src/renderer/stores/useAppStore.ts`
|
||
|
||
- [ ] **Step 1: WordFreqPanel** — 底部按钮 `onClick` 改为 `setNamingOpen(true)` 或触发 KnowledgePanel 命名检查(与 `NamingCheckModal` 联动)
|
||
|
||
- [ ] **Step 2: TopBar** — 在 `top-spacer` 前加细进度条:
|
||
- 读 `useWritingStatsStore` + `dailyWordGoal`
|
||
- `goalProgress` 映射 width%;≥100% 绿色,≥80% 橙色
|
||
|
||
- [ ] **Step 3: 全局 grep** `feature.comingSoon` — Wave 1 范围内应无功能入口残留
|
||
|
||
---
|
||
|
||
## Task 10: E2E + 版本 v1.2.0
|
||
|
||
**Files:**
|
||
- Create: `e2e/book-settings.spec.ts`
|
||
- Create: `e2e/chapter-management.spec.ts`
|
||
- Create: `e2e/focus-tts.spec.ts`
|
||
- Modify: `e2e/chapter-template.spec.ts`
|
||
- Modify: `e2e/helpers.ts`
|
||
- Modify: `package.json`, `README.md`, `SettingsPage.tsx`
|
||
|
||
- [ ] **Step 1: e2e/helpers.ts** — `openBookSettings(page)` helper
|
||
|
||
- [ ] **Step 2: E2E-BOOK-01** — 改书名、勾选 always cockpit、删书
|
||
|
||
- [ ] **Step 3: E2E-CHAP-01** — 双击改标题、删除章
|
||
|
||
- [ ] **Step 4: E2E-FOCUS-01** — Ctrl+Alt+F 进入专注,Esc 退出
|
||
|
||
- [ ] **Step 5: E2E-TEMPL-05** — 模板含 `{characterList}` 的自定义模板(设置页创建)→ 快速新建 → 编辑器含角色名
|
||
|
||
- [ ] **Step 6: 全量验证**
|
||
|
||
```bash
|
||
npm run build
|
||
npm run test
|
||
npm run test:e2e -- e2e/book-settings.spec.ts e2e/chapter-management.spec.ts e2e/focus-tts.spec.ts e2e/chapter-template.spec.ts
|
||
```
|
||
|
||
- [ ] **Step 7: 版本** — `package.json` / README / Settings 关于页 → **v1.2.0**
|
||
|
||
- [ ] **Step 8: 勾选本 plan 全部 checkbox**
|
||
|
||
---
|
||
|
||
## Plan 自检
|
||
|
||
| Spec §5 要求 | Task |
|
||
|-------------|------|
|
||
| 5.1 书籍设置 Tab | Task 5 |
|
||
| 5.2 分卷管理 | Task 6 |
|
||
| 5.3 章节管理 | Task 6 |
|
||
| 5.4 模板变量 | Task 4 |
|
||
| 5.5 AI 会话 UI | Task 7 |
|
||
| 5.6 快捷键 | Task 8 |
|
||
| 5.7 用词分析 | Task 9 |
|
||
| 5.8 验收 E2E | Task 10 |
|
||
|
||
无 TBD 占位。
|
||
|
||
---
|
||
|
||
## 后续波次计划(待 W1 完成后编写)
|
||
|
||
| 文件 | 版本 |
|
||
|------|------|
|
||
| `docs/superpowers/plans/2026-07-08-bilin-w2-project-pack.md` | v1.3.0 |
|
||
| `docs/superpowers/plans/2026-07-08-bilin-w3-export-read.md` | v1.4.0 |
|
||
| `docs/superpowers/plans/2026-07-08-bilin-w4-creative-depth.md` | v1.5.0 |
|
||
| `docs/superpowers/plans/2026-07-08-bilin-w5-platform.md` | v2.0.0 |
|