# 笔临 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 命名一致性检查 | 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) ```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 // 默认 300000(5 分钟) snapshotMaxAuto: number // 默认 20 snapshotMaxPersist: number // 默认 3 searchHistory: string[] // 最近 10 条 } ``` --- ## 4. IPC 契约 所有通道返回 `IpcResult`。参数 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 响应扩展 ```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;其他=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` 移植残留)。 **修复项**: 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: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 实装**(用户确认) |