docs: 添加笔临 P2 设计规格

覆盖大纲/设定/灵感持久化、引用面板、版本历史、全局搜索、地标、用词分析与 P2.0 主题修复;严格对齐 design/readme §5.3.9–§5.6。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-06 10:49:12 +08:00
parent 011bd6d4ca
commit c772b7d703
@@ -0,0 +1,558 @@
# 笔临 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 实装**(用户确认) |