Files
bilin/docs/superpowers/plans/2026-07-08-bilin-w1-foundation.md
T
bing fe127ec3ed feat: ship v1.2.0 Wave 1 foundation with book settings, chapter management, and focus mode
Deliver schema v9, book/chapter IPC extensions, BookSettingsTab, chapter meta/tags,
AI session menu, focus mode and TTS, template context enrich, and Wave 1 E2E coverage.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-08 15:22:58 +08:00

453 lines
16 KiB
Markdown
Raw 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.
# 笔临 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=9published_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`
- [x] **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
);
```
- [x] **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')
}
```
- [x] **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 接受它们
```
- [x] **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',
```
- [x] **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`
- [x] **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 */ }
}
```
- [x] **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)
}
```
- [x] **Step 3: book-registry.updateMeta** — 签名改为 `Partial<Pick<BookMeta, 'name' | 'category' | 'targetWordCount' | 'coverPath' | 'synopsis' | 'lastOpenedAt' | 'lastChapterId' | 'status'>>`
- [x] **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'
}
```
- [x] **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`
- [x] **Step 1: book.handler**`BOOK_UPDATE_META` 接受 `UpdateBookMetaParams`
- [x] **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 同理
```
- [x] **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) }))
```
- [x] **Step 4: ai.handler**`AI_SESSION_EXPORT`:读 messages,格式化 markdown`dialog.showSaveDialog`,写文件
- [x] **Step 5: preload**`book.updateMeta` 扩展;`bookPrefs.get/set``chapterTag.list/set/remove``ai.session.export(bookId, sessionId, format)`
- [x] **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`
- [x] **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
}
}
```
- [x] **Step 2: TemplateQuickCreateModal** — 用 `buildTemplateContext` 替换内联 `buildContext`
- [x] **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`
- [x] **Step 1: BookSettingsTab** — 表单字段:
- 书名、分类、目标字数、简介(textarea)
- 封面:按钮选文件 → 存 `userData/covers/{bookId}.jpg` 路径到 meta.coverPath
- 「每次打开显示驾驶舱」checkbox → `bookPrefs.set('alwaysShowCockpit', 'true'|'false')`
- 危险区:删书 → confirm → `book.delete``loadBooks``setView('home')`
- [x] **Step 2: RightPanel**`book-settings` panel 渲染 `<BookSettingsTab />`
- [x] **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`
- [x] **Step 1: VolumeContextMenu** — 绑定分卷标题 `onContextMenu`
- 重命名:prompt → `volume.update`
- 编辑简述:prompt → `volume.update({ description })`
- 删除:若卷内无章 → `volume.delete`;有章则 toast 错误
- [x] **Step 2: 章节双击改名**`chapter-item` `onDoubleClick` → input inline → `chapter.update({ title })`
- [x] **Step 3: 章节删除** — 右键或 hover 图标 → confirm → `chapter.delete``refreshChapters`
- [x] **Step 4: ChapterMetaPanel** — 侧栏或状态栏按钮打开:
- summary、story_time 输入
- 标签 chips + 添加输入
- word_count_threshold 数字
- [x] **Step 5: 发布日期**`setPublishStatus` 后本地 chapter 显示 `published_at`(只读)
- [x] **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`
- [x] **Step 1: AiSessionMenu** — 会话选择器旁「⋯」按钮:
- 重命名 → `ai.session.update({ title })`
- 归档/取消归档 → `{ archived: true/false }`
- 导出 → 子菜单 txt/md → `ai.session.export`
- 删除 → confirm → `ai.session.delete`
- [x] **Step 2: useAiStore**`loadSessions(bookId, true)` 加载含归档;归档会话 optgroup 或折叠区
- [x] **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`
- [x] **Step 1: FocusModeOverlay**`useAppStore.focusMode` boolean
- true 时 `#app``focus-mode` classCSS 隐藏 sidebars/right-panel/topbar 非必要按钮)
- Esc 或按钮退出
- [x] **Step 2: App.tsx**`focusMode` 动作调用 `setFocusMode(true)`;再次按退出
- [x] **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()
}
```
- [x] **Step 4: App.tsx**`toggleTTS`:若正在朗读则 stop;否则读当前章 `editor.getText()` 或选区
- [x] **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`
- [x] **Step 1: WordFreqPanel** — 底部按钮 `onClick` 改为 `setNamingOpen(true)` 或触发 KnowledgePanel 命名检查(与 `NamingCheckModal` 联动)
- [x] **Step 2: TopBar** — 在 `top-spacer` 前加细进度条:
-`useWritingStatsStore` + `dailyWordGoal`
- `goalProgress` 映射 width%;≥100% 绿色,≥80% 橙色
- [x] **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`
- [x] **Step 1: e2e/helpers.ts**`openBookSettings(page)` helper
- [x] **Step 2: E2E-BOOK-01** — 改书名、勾选 always cockpit、删书
- [x] **Step 3: E2E-CHAP-01** — 双击改标题、删除章
- [x] **Step 4: E2E-FOCUS-01** — Ctrl+Alt+F 进入专注,Esc 退出
- [x] **Step 5: E2E-TEMPL-05** — 模板含 `{characterList}` 的自定义模板(设置页创建)→ 快速新建 → 编辑器含角色名
- [x] **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
```
- [x] **Step 7: 版本**`package.json` / README / Settings 关于页 → **v1.2.0**
- [x] **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 |