覆盖大纲/设定/灵感持久化、引用面板、版本历史、全局搜索、地标、用词分析与 P2.0 主题修复;严格对齐 design/readme §5.3.9–§5.6。 Co-authored-by: Cursor <cursoragent@cursor.com>
20 KiB
笔临 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 验收标准
用户能完成:
- 在左栏四 Tab 中 CRUD 大纲/设定/灵感,并在中央编辑器统一编辑、自动保存、重启恢复
- 打开引用面板,搜索/钉住/拖拽插入设定或大纲内容到正文
- 使用全局搜索(含正则、替换预览)跨章节/大纲/设定/灵感/地标检索并跳转
- 创建/浏览/对比/回滚章节版本历史(自动/手动/持久快照)
- 插入/管理写作地标,通过清单或搜索跳转
- 查看用词分析(高频词、过度用词高亮)并跳转编辑器定位
- 切换 3 套浅色主题,所有关键页面文字清晰可读
1.4 本规格不实现(明确排除)
| 项 | 归属 |
|---|---|
| §5.6.2 AI 命名一致性检查 | P3(UI 占位) |
§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(每书 .sqlite,schema v2) │
│ · FTS5 虚拟表(search_fts) │
│ · SnapshotService(定时/退出快照) │
│ · SearchService / WordFreqService │
│ · IPC Handlers(IpcResult 信封) │
└───────────────────────────┬─────────────────────────────┘
│ contextBridge
┌───────────────────────────▼─────────────────────────────┐
│ 渲染进程 │
│ · useEditStore(EditTarget 统一编辑) │
│ · Zustand + Jotai(延续 P1) │
│ · TipTap(章节完整 / 其他简化模式) │
│ · 模态:搜索 / 版本 / 灵感捕获 / 地标清单 │
└─────────────────────────────────────────────────────────┘
2.2 统一编辑内核(EditSession)
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 扩展:
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):
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):
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 扩展
interface GlobalSettings {
// ...P0/P1 字段
snapshotIntervalMs: number // 默认 300000(5 分钟)
snapshotMaxAuto: number // 默认 20
snapshotMaxPersist: number // 默认 3
searchHistory: string[] // 最近 10 条
}
4. IPC 契约
所有通道返回 IpcResult<T>。参数 camelCase,Repository 映射 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→plainText,CJK 单字 + 拉丁词分词,Top N 统计 |
FtsSyncService |
CRUD 后增量同步;rebuildFts 全量重建 |
4.3 book:open 响应扩展
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;其他=simplified(Paragraph+Bold+Italic+Placeholder) |
StatusBar |
保存状态 + 字数(章节三维 / 其他单维) |
| 粘贴 | 纯文本插入(Shift 保留格式,延续 P1) |
5.3 引用面板(§5.3.8)
- DOM:
#reference-panel,宽 240px,.open切换 - 与 AI 右栏互斥
- Tab:设定 / 大纲 / 灵感;顶部搜索框
- 列表项:名称 + 摘要前 40 字;点击展开详情
- 钉住 checkbox:切换章节时保持选中条目
- 拖拽:插入 plainText 到光标
- Mention(P2.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)且内容变更 | 20,FIFO |
| 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 移植残留)。
修复项:
- 补全
ricepaper/mist/teagarden全部 token(--bg-surface、--text-dim、--accent-glow、--border-light等) - 新增语义 token:
--text-on-accent:主按钮/激活 Tab 上的文字--input-text/--input-bg:表单控件
- 替换
layout.css、组件内联 style 中color: #fff→ 语义 token select/option/input统一使用--input-*token
验收 E2E(e2e/themes-contrast.spec.ts):
- 主题:
ricepaper、mist、teagarden - 页面:主页、编辑器、设置、搜索模态、引用面板
- 断言:关键文字元素
color与background对比度 ≥ 4.5:1(WCAG 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 E2E(Playwright)
| 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 强制 await;E2E 覆盖切换序列 |
| 版本 diff 大章节性能 | plainText diff;>100KB 时警告并限制词级粒度 |
| P2 范围大、工期膨胀 | 严格按 P2.0–P2.7 里程碑交付;每阶段 E2E 门禁 |
| 浅色主题回归 | P2.0 建立 token 规范 + contrast E2E 纳入 CI |
10. Definition of Done(v0.2.0)
- P2.0–P2.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 实装(用户确认) |