Files
bilin/docs/superpowers/specs/2026-07-06-bilin-p2-design.md
T
bing c772b7d703 docs: 添加笔临 P2 设计规格
覆盖大纲/设定/灵感持久化、引用面板、版本历史、全局搜索、地标、用词分析与 P2.0 主题修复;严格对齐 design/readme §5.3.9–§5.6。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-06 10:49:12 +08:00

559 lines
20 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.
# 笔临 P2 设计规格
**版本**1.0
**日期**2026-07-06
**范围**:第二阶段——内容扩展、引用与检索、版本追溯、主题修复
**前置**P0/P1 已交付(v0.1.0`011bd6d`
**参考**`design/readme.md` v1.1 §5.3.9 / §5.4–§5.6 / §5.5、`design/ui_pc.html`
---
## 1. 背景与目标
### 1.1 阶段定位
P2 在 P1「纯写作闭环」之上,补齐长篇创作的信息管理能力:大纲/设定/灵感持久化、写作引用、全文检索、版本历史、地标待办、用词分析,并修复浅色主题控件不可见问题。
### 1.2 已确认决策(Brainstorming 2026-07-06
| 维度 | 决策 |
|------|------|
| 范围 | **完整 P2** + 主题修复 |
| 对齐深度 | 严格对齐 `design/readme.md` §5.3.9 / §5.4–§5.6 / §5.5 |
| 编辑器 | **完全统一中央 TipTap**(章节/大纲/设定/灵感) |
| 搜索 | §5.6.1 全量;§5.6.3 用词分析实装;§5.6.2 AI 命名检查 **占位至 P3** |
| 地标 | §5.3.9 **全量** + 纳入全局搜索 |
| 主题 | **P2.0 先行**~3 天),再进入功能开发 |
| 实现策略 | **方案 A**:分层基础 + 功能切片 |
| 平台 | Windows 优先(延续 P0/P1 |
### 1.3 P2 验收标准
用户能完成:
1. 在左栏四 Tab 中 CRUD 大纲/设定/灵感,并在中央编辑器统一编辑、自动保存、重启恢复
2. 打开引用面板,搜索/钉住/拖拽插入设定或大纲内容到正文
3. 使用全局搜索(含正则、替换预览)跨章节/大纲/设定/灵感/地标检索并跳转
4. 创建/浏览/对比/回滚章节版本历史(自动/手动/持久快照)
5. 插入/管理写作地标,通过清单或搜索跳转
6. 查看用词分析(高频词、过度用词高亮)并跳转编辑器定位
7. 切换 3 套浅色主题,所有关键页面文字清晰可读
### 1.4 本规格不实现(明确排除)
| 项 | 归属 |
|----|------|
| §5.6.2 AI 命名一致性检查 | P3UI 占位) |
| §5.4.1 `ai` 类型快照业务触发 | P3(DB 字段预留) |
| 灵感语音输入 | P2.1 占位按钮 |
| `ai_sessions` / `knowledge_entries` / `timeline_events` 表 | P3/P5/P6 |
| 章节拖拽排序(同卷/跨卷) | P2.1(P2 先用按钮排序) |
| TipTap `@提及` 反向跳转 | P2.4 实现 Mention 扩展 |
---
## 2. 架构
### 2.1 进程模型(延续 P0/P1
```
┌─────────────────────────────────────────────────────────┐
│ 主进程 │
│ · node:sqlite(每书 .sqliteschema v2
│ · FTS5 虚拟表(search_fts
│ · SnapshotService(定时/退出快照) │
│ · SearchService / WordFreqService │
│ · IPC HandlersIpcResult 信封) │
└───────────────────────────┬─────────────────────────────┘
│ contextBridge
┌───────────────────────────▼─────────────────────────────┐
│ 渲染进程 │
│ · useEditStoreEditTarget 统一编辑) │
│ · Zustand + Jotai(延续 P1
│ · TipTap(章节完整 / 其他简化模式) │
│ · 模态:搜索 / 版本 / 灵感捕获 / 地标清单 │
└─────────────────────────────────────────────────────────┘
```
### 2.2 统一编辑内核(EditSession
```typescript
type EditTarget =
| { kind: 'chapter'; id: string }
| { kind: 'outline'; id: string }
| { kind: 'setting'; id: string }
| { kind: 'inspiration'; id: string }
```
**`useEditStore`Zustand)职责**
- 持有 `currentTarget: EditTarget | null`
- `switchTarget(next)`flush 当前 → IPC get → 更新 TipTap 内容与 cursor
-`useBookStore.selectedChapterId` 协同:选中章节时 `kind='chapter'`
**Jotai atoms**(延续并扩展):
- `editorDirtyAtom` / `editorSavingAtom` / `lastSavedAtAtom` — 所有 target 共用
- `wordCountAtom` — 章节时显示卷/章/全书;其他 target 仅条目字数
**自动保存**debounce 2s(与 P1 一致);切换 target / 关闭 Tab 前 `flushEditorSave()`
### 2.3 里程碑
| 阶段 | 交付 | 周期 |
|------|------|------|
| **P2.0** | 主题 token 补全、消除硬编码色值、对比度 E2E | ~3 天 |
| **P2.1** | schema-v2 迁移、Repository、IPC 扩展 | ~4 天 |
| **P2.2** | EditSession + 左栏四 Tab CRUD + 统一编辑 | ~5 天 |
| **P2.3** | 大纲对照视图、覆盖度标记/筛选 | ~4 天 |
| **P2.4** | 引用面板(Tab/钉住/拖拽/Mention | ~4 天 |
| **P2.5** | 版本历史(快照/diff/回滚) | ~5 天 |
| **P2.6** | 全局搜索 FTS5 + 替换 + 地标全量 | ~5 天 |
| **P2.7** | 用词分析 + §5.6.2 占位 | ~3 天 |
**目标版本**v0.2.0
---
## 3. 数据模型
### 3.1 Schema v2 迁移
`schema_version` 1 → 2。新安装书籍直接应用 v1+v2;已有书籍运行 `migrate.ts` 增量 ALTER。
**chapters 扩展**
```sql
ALTER TABLE chapters ADD COLUMN publish_status TEXT NOT NULL DEFAULT 'draft';
ALTER TABLE chapters ADD COLUMN pov_character_id TEXT DEFAULT NULL;
ALTER TABLE chapters ADD COLUMN summary TEXT DEFAULT '';
ALTER TABLE chapters ADD COLUMN story_time TEXT DEFAULT NULL;
-- pov_character_id FK → settings(id) 在 v2 表创建后添加
```
**新增表**(对齐 design §19):
```sql
CREATE TABLE outline_items (
id TEXT PRIMARY KEY,
parent_id TEXT,
title TEXT NOT NULL,
description TEXT DEFAULT '',
status TEXT NOT NULL DEFAULT 'pending',
expected_word_count INTEGER DEFAULT NULL,
chapter_id TEXT,
sort_order INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (parent_id) REFERENCES outline_items(id) ON DELETE CASCADE,
FOREIGN KEY (chapter_id) REFERENCES chapters(id) ON DELETE SET NULL
);
CREATE TABLE settings (
id TEXT PRIMARY KEY,
type TEXT NOT NULL,
name TEXT NOT NULL,
description TEXT DEFAULT '',
properties TEXT DEFAULT '{}',
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE setting_chapter_refs (
setting_id TEXT NOT NULL,
chapter_id TEXT NOT NULL,
PRIMARY KEY (setting_id, chapter_id),
FOREIGN KEY (setting_id) REFERENCES settings(id) ON DELETE CASCADE,
FOREIGN KEY (chapter_id) REFERENCES chapters(id) ON DELETE CASCADE
);
CREATE TABLE inspiration (
id TEXT PRIMARY KEY,
title TEXT NOT NULL DEFAULT '',
content TEXT NOT NULL DEFAULT '',
tags TEXT DEFAULT '[]',
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE snapshots (
id TEXT PRIMARY KEY,
chapter_id TEXT NOT NULL,
type TEXT NOT NULL,
name TEXT DEFAULT '',
content TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (chapter_id) REFERENCES chapters(id) ON DELETE CASCADE
);
CREATE TABLE bookmarks (
id TEXT PRIMARY KEY,
chapter_id TEXT NOT NULL,
position INTEGER NOT NULL,
label TEXT NOT NULL,
landmark_type TEXT NOT NULL DEFAULT 'todo',
resolved INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (chapter_id) REFERENCES chapters(id) ON DELETE CASCADE
);
```
**FTS5**(每书 DB):
```sql
CREATE VIRTUAL TABLE search_fts USING fts5(
source_kind,
source_id,
title,
body,
tokenize='unicode61'
);
```
触发器:chapters / outline_items / settings / inspiration / bookmarks 的 INSERT/UPDATE/DELETE 同步 `search_fts`。迁移完成后执行一次 `rebuildFts(bookId)`
### 3.2 枚举值
| 字段 | 值 |
|------|-----|
| `outline_items.status` | `pending` / `writing` / `done` / `abandoned` |
| `settings.type` | `character` / `location` / `item` / `skill` / `faction` / `custom` |
| `snapshots.type` | `auto` / `manual` / `persist` / `ai` |
| `bookmarks.landmark_type` | `todo` / `foreshadow` / `research` / `custom` |
| `chapters.publish_status` | `draft` / `ready` / `published` |
### 3.3 global_settings.json 扩展
```typescript
interface GlobalSettings {
// ...P0/P1 字段
snapshotIntervalMs: number // 默认 3000005 分钟)
snapshotMaxAuto: number // 默认 20
snapshotMaxPersist: number // 默认 3
searchHistory: string[] // 最近 10 条
}
```
---
## 4. IPC 契约
所有通道返回 `IpcResult<T>`。参数 camelCaseRepository 映射 snake_case。
### 4.1 新增通道
| 通道 | 说明 |
|------|------|
| `outline:list` | `{ bookId }``OutlineItem[]`(树形扁平化或嵌套) |
| `outline:create` | `{ bookId, parentId?, title, sortOrder }` |
| `outline:update` | `{ bookId, id, patch }` |
| `outline:delete` | `{ bookId, id }`(级联子节点) |
| `outline:move` | `{ bookId, id, parentId?, sortOrder }` |
| `setting:list` | `{ bookId, type? }` |
| `setting:create` | `{ bookId, type, name }` |
| `setting:update` | `{ bookId, id, patch }` |
| `setting:delete` | `{ bookId, id }` |
| `setting:syncRefs` | `{ bookId, id, chapterIds[] }` |
| `inspiration:list` | `{ bookId }` |
| `inspiration:create` | `{ bookId, title?, content?, tags? }` |
| `inspiration:update` | `{ bookId, id, patch }` |
| `inspiration:delete` | `{ bookId, id }` |
| `inspiration:convert` | `{ bookId, id, targetKind: 'outline' \| 'setting' }` |
| `snapshot:list` | `{ bookId, chapterId }` |
| `snapshot:create` | `{ bookId, chapterId, type, name? }` |
| `snapshot:restore` | `{ bookId, chapterId, snapshotId }` |
| `snapshot:delete` | `{ bookId, snapshotId }` |
| `bookmark:list` | `{ bookId, chapterId?, resolved? }` |
| `bookmark:create` | `{ bookId, chapterId, position, label, landmarkType }` |
| `bookmark:update` | `{ bookId, id, patch }` |
| `bookmark:resolve` | `{ bookId, id, resolved: boolean }` |
| `bookmark:delete` | `{ bookId, id }` |
| `search:query` | `{ bookId, query, scope, options }``SearchResult[]` |
| `search:replace` | `{ bookId, query, replacement, scope, dryRun }` |
| `search:getHistory` / `search:saveHistory` | 读写 global_settings.searchHistory |
| `wordfreq:analyze` | `{ bookId, scope }``{ words: { token, count }[] }` |
### 4.2 主进程服务
| 服务 | 职责 |
|------|------|
| `SnapshotService` | 定时 auto 快照、Ctrl+S/退出 persist 快照、FIFO 淘汰 |
| `SearchService` | FTS5 查询;regex 模式 fallback 到 JS 扫描 |
| `WordFreqService` | HTML→plainTextCJK 单字 + 拉丁词分词,Top N 统计 |
| `FtsSyncService` | CRUD 后增量同步;`rebuildFts` 全量重建 |
### 4.3 book:open 响应扩展
```typescript
interface BookOpenResult {
meta: BookMeta
volumes: Volume[]
chapters: Chapter[]
outlines: OutlineItem[] // 新增
settings: SettingEntry[] // 新增
inspirations: InspirationEntry[] // 新增
}
```
---
## 5. UI 规格
### 5.1 左栏四 Tab
| Tab | 组件 | P2 行为 |
|-----|------|---------|
| 章节 | `ChapterTree` | 双击改标题(已有);status badge |
| 大纲 | `OutlineTree` | 无限层级;新建/删除;覆盖度橙色边框;筛选下拉 |
| 设定 | `SettingList` | 按 type 分组折叠;新建 |
| 灵感 | `InspirationList` | 列表 + 底部新建 |
各 Tab 底部「+ 新建」;选中条目 → `useEditStore.switchTarget`
**大纲覆盖度**(§5.5.2):
-`chapter_id` 关联 → 橙色左边框 + ⚠ 图标
- 筛选:全部 / 未覆盖 / 已覆盖
- Tooltip:预期字数 vs 关联章节实际字数,偏差 >20% 标红
### 5.2 中央编辑器
| 元素 | 规格 |
|------|------|
| `DocumentTitle` | 双击 inline 编辑;Enter 确认 / Esc 取消 |
| `EditorToolbar` | 加粗/斜体/下划线;引用按钮;版本历史;插入地标 |
| `TipTapEditor` | chapter=StarterKit+Underline;其他=simplifiedParagraph+Bold+Italic+Placeholder |
| `StatusBar` | 保存状态 + 字数(章节三维 / 其他单维) |
| 粘贴 | 纯文本插入(Shift 保留格式,延续 P1) |
### 5.3 引用面板(§5.3.8
- DOM`#reference-panel`,宽 240px`.open` 切换
- 与 AI 右栏互斥
- Tab:设定 / 大纲 / 灵感;顶部搜索框
- 列表项:名称 + 摘要前 40 字;点击展开详情
- 钉住 checkbox:切换章节时保持选中条目
- 拖拽:插入 plainText 到光标
- MentionP2.4):`@设定名` 高亮,点击跳转设定 EditTarget
### 5.4 模态层
| ID | 快捷键 | 功能 |
|----|--------|------|
| `#searchModal` | Ctrl+Shift+F | 搜索 + 范围 + 高级选项 + 替换预览 |
| `#versionModal` | 工具栏 | 版本列表 + diff 双栏 + 回滚 |
| `#inspirationModal` | Ctrl+Shift+I | 快速捕获(语音按钮占位) |
| `#landmarksModal` | Ctrl+Shift+L | 地标清单,按类型/章节分组 |
搜索结果:按类型分组;↑↓ 选择 + Enter 跳转;上下文前后 20 字。
### 5.5 大纲对照视图(§5.5.6
- 入口:大纲 Tab「对照模式」
- 分屏:左 35% 大纲树,右 65% 关联章节正文(只读)
- 底部对比条:预期 vs 实际 vs 偏差%
- 无关联:「尚未写作」占位
### 5.6 版本历史(§5.4
| 类型 | 触发 | 上限 |
|------|------|------|
| auto | 定时(默认 5min)且内容变更 | 20FIFO |
| manual | 用户「创建快照」 | 无上限 |
| persist | Ctrl+S / app quit | 3,轮替 |
| ai | 预留 | P3 |
- Diff`diff-match-patch` 行级对比;绿新增/红删除/黄修改
- 回滚:restore 前自动 manual 备份
- 列表:版本号、时间、类型、摘要(manual 名称)
### 5.7 地标(§5.3.9
- 插入:编辑器右键 / 工具栏 / Ctrl+Shift+M
- 正文行内标记:`[TODO: ...]` 样式(TipTap Mark 或 inline Node
- 不进入导出正文(export 逻辑留 P7,P2 仅存储)
- 清单模态:按类型/章节分组;点击跳转章节+position
- 完成:勾选 resolved,可选归档
### 5.8 用词分析(§5.6.3
- 入口:右栏「用词」Tab(原占位 → 实装)
- 高频词 Top 100,频次降序
- 点击词条 → 编辑器跳转并高亮所有出现
- 过度用词:每千字 >5 次的非功能词,编辑器淡黄色 Mark
- §5.6.2 命名一致性:同面板底部「即将推出(需 AI)」
### 5.9 主题修复(P2.0
**问题根因**:浅色主题 CSS 变量不完整;多处硬编码 `#fff`(含 `ui_pc.html` 移植残留)。
**修复项**
1. 补全 `ricepaper` / `mist` / `teagarden` 全部 token`--bg-surface``--text-dim``--accent-glow``--border-light` 等)
2. 新增语义 token
- `--text-on-accent`:主按钮/激活 Tab 上的文字
- `--input-text` / `--input-bg`:表单控件
3. 替换 `layout.css`、组件内联 style 中 `color: #fff` → 语义 token
4. `select` / `option` / `input` 统一使用 `--input-*` token
**验收 E2E**`e2e/themes-contrast.spec.ts`):
- 主题:`ricepaper``mist``teagarden`
- 页面:主页、编辑器、设置、搜索模态、引用面板
- 断言:关键文字元素 `color``background` 对比度 ≥ 4.5:1WCAG AA
---
## 6. 状态管理
### 6.1 新增 Stores
| Store | 职责 |
|-------|------|
| `useEditStore` | EditTarget 切换、flush、load |
| `useOutlineStore` | 大纲树、筛选、对照模式 |
| `useReferenceStore` | 引用面板 open/pin/activeTab/selectedItem |
| `useSearchStore` | 搜索模态、query、results、replacePreview |
`useBookStore` 扩展:缓存 outlines/settings/inspirations`openBook` 一次性加载。
### 6.2 快捷键绑定(P2 实装)
| 动作 | 默认 | P2 行为 |
|------|------|---------|
| globalSearch | Ctrl+Shift+F | 打开搜索模态 |
| openReference | Ctrl+Shift+P | 切换引用面板 |
| insertLandmark | Ctrl+Shift+M | 插入地标 |
| openLandmarks | Ctrl+Shift+L | 地标清单 |
| captureInspiration | Ctrl+Shift+I | 灵感捕获模态 |
| saveChapter | Ctrl+S | 保存 + 创建 persist 快照 |
---
## 7. 测试策略
### 7.1 单元/集成(Vitest
| 模块 | 用例 |
|------|------|
| `outline.repo` | CRUD、层级、move、覆盖度查询 |
| `setting.repo` | CRUD、chapterRefs 同步 |
| `inspiration.repo` | CRUD、convert |
| `snapshot.repo` | 创建、FIFO 淘汰、restore |
| `bookmark.repo` | CRUD、resolve |
| `search.service` | FTS 索引、query、replace dryRun |
| `wordfreq.service` | CJK/拉丁分词、Top N |
| `migrate v1→v2` | 旧库升级后数据完整 |
### 7.2 E2EPlaywright
| ID | 场景 |
|----|------|
| E2E-P2-OUTLINE-01 | 创建层级大纲 → 重启 → 树结构保留 |
| E2E-P2-EDIT-01 | 切换章节→大纲→设定,内容互不覆盖 |
| E2E-P2-REF-01 | 引用面板拖拽设定到正文 |
| E2E-P2-SEARCH-01 | 全局搜索跨类型 → Enter 跳转 |
| E2E-P2-SNAP-01 | 编辑 → 手动快照 → diff → 回滚 |
| E2E-P2-LAND-01 | 插入地标 → 清单可见 → 跳转 |
| E2E-P2-WORD-01 | 用词分析 Top 词 → 点击跳转高亮 |
| E2E-THEME-01 | 3 浅色主题关键页面文字可见 |
---
## 8. 目录结构增量
```
src/main/
├── db/
│ ├── schema-v2.sql
│ ├── migrations/migrate-v2.ts
│ └── repositories/
│ ├── outline.repo.ts
│ ├── setting.repo.ts
│ ├── inspiration.repo.ts
│ ├── snapshot.repo.ts
│ └── bookmark.repo.ts
├── services/
│ ├── snapshot.service.ts
│ ├── search.service.ts
│ ├── fts-sync.service.ts
│ └── wordfreq.service.ts
└── ipc/handlers/
├── outline.handler.ts
├── setting.handler.ts
├── inspiration.handler.ts
├── snapshot.handler.ts
├── bookmark.handler.ts
├── search.handler.ts
└── wordfreq.handler.ts
src/renderer/
├── stores/useEditStore.ts
├── stores/useOutlineStore.ts
├── stores/useReferenceStore.ts
├── stores/useSearchStore.ts
├── components/
│ ├── outline/OutlineTree.tsx
│ ├── outline/OutlineCompareView.tsx
│ ├── setting/SettingList.tsx
│ ├── inspiration/InspirationList.tsx
│ ├── reference/ReferencePanel.tsx
│ ├── search/SearchModal.tsx
│ ├── version/VersionModal.tsx
│ ├── landmark/LandmarkModal.tsx
│ ├── landmark/LandmarkMark.ts
│ └── wordfreq/WordFreqPanel.tsx
└── extensions/mention.ts
e2e/
├── themes-contrast.spec.ts
├── outline.spec.ts
├── search.spec.ts
├── snapshot.spec.ts
└── landmarks.spec.ts
```
---
## 9. 风险与缓解
| 风险 | 缓解 |
|------|------|
| FTS5 在 node:sqlite 不可用 | 启动时检测;fallback JS 全表扫描(性能可接受于单书规模) |
| 统一 EditTarget 切换丢数据 | flush 强制 awaitE2E 覆盖切换序列 |
| 版本 diff 大章节性能 | plainText diff>100KB 时警告并限制词级粒度 |
| P2 范围大、工期膨胀 | 严格按 P2.0–P2.7 里程碑交付;每阶段 E2E 门禁 |
| 浅色主题回归 | P2.0 建立 token 规范 + contrast E2E 纳入 CI |
---
## 10. Definition of Donev0.2.0
- [ ] P2.0P2.7 全部里程碑完成
- [ ] §7 测试全部 PASS
- [ ] 3 套浅色主题 contrast E2E PASS
- [ ] §5.6.2 AI 命名检查为占位 Toast,不 crash
- [ ] `npm run build` 无 error
- [ ] Windows 手动验收:§1.3 七项流程
---
## Spec Self-Review
| 检查项 | 结果 |
|--------|------|
| Placeholder 扫描 | §5.6.2 / 语音 / ai 快照 均明确标注归属,无 TBD |
| 内部一致性 | EditTarget 统一编辑贯穿 IPC/UI/测试;FTS 与五类 source 对齐 |
| 范围 | 单规格可支撑一份 implementation plan;章节拖拽明确 P2.1 |
| 歧义 | snapshot 类型 `persist` vs design「退出/手动保存版本」已映射;status 枚举与 design §19 对齐 |
---
## 附录:与 P0/P1 规格差异
| 项 | P0/P1 规格 | 当前/P2 规格 |
|----|-----------|-------------|
| 数据库 | better-sqlite3 | **node:sqlite**(已迁移) |
| P5 地标 | 原归 P5 | **P2 全量**(用户确认) |
| P6 用词分析 | 原归 P6 | **P2 实装**(用户确认) |