docs: 添加笔临 P0/P1 设计规格

定义 Electron+React 地基与写作核心的架构、IPC、数据模型与验收标准,作为全量实现的第一阶段依据。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-05 16:47:15 +08:00
parent ca2cd29f01
commit a06038035b
4 changed files with 961 additions and 26 deletions
@@ -0,0 +1,550 @@
# 笔临 P0/P1 设计规格
**版本**1.0
**日期**2026-07-05
**范围**Electron + React 全量实现的第一阶段(地基 + 写作核心)
**参考**`design/readme.md` v1.1、`design/ui_pc.html`
---
## 1. 背景与目标
### 1.1 产品定位
笔临是面向长篇连载作者的桌面写作平台,以「人本创作」为核心:AI 辅助可选,作者保留绝对控制权。本项目从设计文档与 UI 原型出发,实现完整桌面应用;交付采用分阶段策略,最终覆盖设计文档全部 20 章功能。
### 1.2 本规格范围(P0 + P1
| 阶段 | 目标 | 预估周期 |
|------|------|----------|
| **P0 地基** | 可启动、可持久化设置、主题/i18n/Onboarding/快捷键 | ~1 周 |
| **P1 写作核心** | 多书管理、分卷章节、TipTap 编辑、自动保存、字数统计 | ~2 周 |
**P1 验收标准**:用户能完成「首次引导 → 创建书籍 → 分卷分章 → 富文本写作 → 自动保存 → 重启后内容恢复」。AI 与高级面板为 UI 占位,不实现业务逻辑。
### 1.3 已确认决策
| 维度 | 决策 |
|------|------|
| 总体目标 | 全量实现,分 P0–P7 阶段交付 |
| P1 演示重点 | 纯写作体验,AI/高级功能占位 |
| UI 策略 | **混合方案 C**:布局/主题/编辑器区沿用 `ui_pc.html` CSS;模态/表单等用 Radix UI |
| 目标平台 | **Windows 优先**Win 10/11),macOS/Linux 在 P7 适配 |
| 状态管理 | **Zustand**(全局域)+ **Jotai**(编辑器高频局部状态) |
| 外围体验 | 与设计文档对齐:7 主题 + 双语 i18n + Onboarding + 可自定义快捷键 |
| 工程结构 | **electron-vite 单体仓库**(方案 A |
### 1.4 后续阶段预览(本规格不实现)
| 阶段 | 范围 |
|------|------|
| P2 | 大纲/设定/灵感持久化、引用面板、全局搜索、版本历史 |
| P3 | AI 会话、对话模式、上下文注入、离线降级 |
| P4 | 交互/自动/向导写作、知识抽取、文风学习 |
| P5 | 驾驶舱、地标、章间衔接、更新计划、章节模板 |
| P6 | 关系图谱、时间线、用词分析、连续阅读 |
| P7 | 导入导出、云同步、自动更新、崩溃报告、跨平台打包 |
---
## 2. 架构
### 2.1 进程模型
```
┌─────────────────────────────────────────────────────────┐
│ 主进程 (Node.js) │
│ · better-sqlite3(每书一个 .sqlite
│ · global_settings.json / books_registry.json │
│ · IPC HandlersIpcResult 信封) │
│ · globalShortcut 快捷键 │
└───────────────────────────┬─────────────────────────────┘
│ contextBridge
┌───────────────────────────▼─────────────────────────────┐
│ 渲染进程 (React 18 + TypeScript) │
│ · Zustand Stores(书籍/Tab/设置) │
│ · Jotai Atoms(编辑器 dirty/字数/光标) │
│ · TipTap 编辑器 │
│ · i18next + 7 主题 CSS 变量 │
└─────────────────────────────────────────────────────────┘
```
**约束**
- 禁止 `nodeIntegration`;所有 DB/FS 操作经 IPC
- 所有 IPC 返回值使用 `IpcResult<T>`(见 §6
- `better-sqlite3` 仅在主进程使用同步 API
### 2.2 技术栈
| 层次 | 技术 |
|------|------|
| 桌面框架 | Electronelectron-vite 脚手架) |
| 前端 | React 18、TypeScript strict |
| 编辑器 | TipTap (ProseMirror) |
| 数据库 | better-sqlite3 |
| 全局状态 | Zustand |
| 编辑器状态 | Jotai |
| 交互组件 | Radix UIDialog、Select、Tabs、Checkbox、Toast |
| 国际化 | i18next + react-i18next |
| 单元/集成测试 | Vitest |
| E2E | Playwright + Electron |
| 图标 | Font Awesome 6(与原型一致) |
---
## 3. 项目目录结构
```
bilin2/
├── design/ # 设计文档(只读参考)
├── docs/superpowers/
│ ├── specs/ # 本文件
│ └── plans/ # 实现计划(writing-plans 产出)
├── public/locales/
│ ├── zh-CN/translation.json
│ └── en/translation.json
├── src/
│ ├── main/
│ │ ├── index.ts
│ │ ├── db/
│ │ │ ├── connection.ts
│ │ │ ├── migrations/
│ │ │ ├── schema-v1.sql
│ │ │ └── repositories/
│ │ │ ├── volume.repo.ts
│ │ │ └── chapter.repo.ts
│ │ ├── ipc/
│ │ │ ├── register.ts
│ │ │ ├── result.ts
│ │ │ └── handlers/
│ │ ├── services/
│ │ │ ├── book-registry.ts
│ │ │ ├── global-settings.ts
│ │ │ └── word-count.ts
│ │ └── shortcuts/
│ │ └── manager.ts
│ ├── preload/
│ │ ├── index.ts
│ │ └── api.d.ts
│ └── renderer/
│ ├── styles/ # themes.css, layout.css(从 ui_pc.html 提取)
│ ├── components/
│ │ ├── layout/
│ │ ├── home/
│ │ ├── editor/
│ │ ├── settings/
│ │ ├── onboarding/
│ │ └── ui/ # Radix 封装
│ ├── stores/ # Zustand
│ ├── atoms/ # Jotai
│ ├── hooks/
│ ├── i18n/
│ └── lib/
├── electron.vite.config.ts
├── package.json
└── tsconfig.json
```
**分层约定**Repository 负责 SQLIPC Handler 负责校验 + 调用 Repository + 返回 `IpcResult`;渲染进程禁止直接访问数据库。
---
## 4. P0 交付清单
| 模块 | 内容 | 设计文档对应 |
|------|------|--------------|
| 工程脚手架 | electron-vite + React + TS strict + ESLint | §1.4 |
| 自定义标题栏 | drag 区域、最小化/最大化/关闭 | ui_pc.html #topbar |
| 主题系统 | 7 套主题 CSS 变量,`data-theme` 切换,持久化 | §3.1 |
| i18n | zh-CN / en,所有 UI 文本走 `t()` | §3.2 |
| 全局设置 | `global_settings.json` 读写 | §19 |
| IPC 基础 | `IpcResult` 信封 + 通道注册 | §16 |
| 数据库 | Schema v1 迁移框架 | §12.1 |
| Onboarding | 5 步向导,笔名必填,可选示例书 | §3.3 |
| 快捷键 | 默认绑定 + 设置页录制 + 冲突提示 | §3.4 |
| 三视图骨架 | 主页 / 编辑器 / 设置页 + Tab 管理 | ui_pc.html |
---
## 5. P1 交付清单
### 5.1 完整实现
| 模块 | 内容 |
|------|------|
| 主页 | 书籍网格、新建书籍(Radix Dialog)、最近编辑、连更徽章占位 |
| 多书 Tab | 打开/切换/关闭,Tab 标题为书名 |
| 分卷 | 创建、重命名、折叠/展开、删除(章节移入未分类或跟随删除) |
| 章节树 | 树状展示、选中、双击改标题、按当前卷新建章、状态徽章(草稿/定稿等) |
| TipTap 编辑器 | 加粗/斜体/下划线/分割线/缩进;默认 Ctrl+V 纯文本 |
| 自动保存 | 输入停顿 2 秒 debounce 写入 SQLite |
| 字数统计 | 本章/本卷/全书增量更新,状态栏展示 |
| 章节切换 | 切换前强制保存;加载 content + cursor_offset |
| Undo | 每章独立内存 undo 堆栈(TipTap 内置);**磁盘持久化放 P2** |
### 5.2 UI 占位(可见、不可用时 Toast)
| 功能 | 占位行为 |
|------|----------|
| 右栏 AI 助手 | 布局完整,输入框 disabled,文案 `t('feature.comingSoon')` |
| 右栏知识库/用词/书籍设置 | Tab 可见,点击内容区显示占位 |
| 引用面板 | 工具栏按钮可见,点击 Toast |
| 顶栏驾驶舱/图谱/时间线/专注/音效 | 按钮可见,点击 Toast 或空 modal 壳 |
| 侧栏大纲/设定/灵感 | Tab 可见,**mock 静态列表**,切换编辑器标签但不持久化 |
| 批量章节操作 | P2 |
| 导入导出 | P7 |
---
## 6. IPC 协议
### 6.1 IpcResult 信封
```typescript
type IpcResult<T> =
| { ok: true; data: T }
| { ok: false; error: IpcError };
interface IpcError {
code: ErrorCode;
message: string;
recoverable: boolean;
}
enum ErrorCode {
DB_READ_FAILED = 'DB_READ_FAILED',
DB_WRITE_FAILED = 'DB_WRITE_FAILED',
FILE_NOT_FOUND = 'FILE_NOT_FOUND',
IMPORT_INVALID = 'IMPORT_INVALID',
UNKNOWN = 'UNKNOWN',
}
```
### 6.2 通道列表(P0/P1
**设置**
| 通道 | 参数 | 返回 |
|------|------|------|
| `settings:get` | — | `GlobalSettings` |
| `settings:update` | `Partial<GlobalSettings>` | `GlobalSettings` |
**书籍**
| 通道 | 参数 | 返回 |
|------|------|------|
| `book:list` | — | `BookMeta[]` |
| `book:create` | `CreateBookParams` | `BookMeta` |
| `book:delete` | `{ bookId }` | `void` |
| `book:open` | `{ bookId }` | `{ meta, volumes, chapters }` |
**分卷**
| 通道 | 参数 | 返回 |
|------|------|------|
| `volume:create` | `{ bookId, name }` | `Volume` |
| `volume:update` | `{ bookId, volumeId, ... }` | `Volume` |
| `volume:delete` | `{ bookId, volumeId, moveChaptersTo? }` | `void` |
| `volume:reorder` | `{ bookId, orderedIds }` | `void` |
**章节**
| 通道 | 参数 | 返回 |
|------|------|------|
| `chapter:create` | `{ bookId, volumeId, title }` | `Chapter` |
| `chapter:get` | `{ bookId, chapterId }` | `Chapter` |
| `chapter:update` | `{ bookId, chapterId, title?, content?, status?, cursorOffset?, wordCount? }` | `Chapter` |
| `chapter:delete` | `{ bookId, chapterId }` | `void` |
| `chapter:reorder` | `{ bookId, volumeId, orderedIds }` | `void` |
**快捷键**
| 通道 | 参数 | 返回 |
|------|------|------|
| `shortcut:getAll` | — | `Record<ActionId, string>` |
| `shortcut:register` | `{ action, accelerator }` | `void` |
**主→渲染事件**
| 事件 | 用途 |
|------|------|
| `shortcut:triggered` | `{ action: ActionId }` |
### 6.3 Preload API 形状
```typescript
interface ElectronAPI {
settings: {
get(): Promise<IpcResult<GlobalSettings>>;
update(p: Partial<GlobalSettings>): Promise<IpcResult<GlobalSettings>>;
};
book: {
list(): Promise<IpcResult<BookMeta[]>>;
create(p: CreateBookParams): Promise<IpcResult<BookMeta>>;
delete(p: { bookId: string }): Promise<IpcResult<void>>;
open(p: { bookId: string }): Promise<IpcResult<BookOpenResult>>;
};
volume: { /* create, update, delete, reorder */ };
chapter: { /* create, get, update, delete, reorder */ };
shortcut: {
getAll(): Promise<IpcResult<Record<ActionId, string>>>;
register(p: { action: ActionId; accelerator: string }): Promise<IpcResult<void>>;
};
onShortcutTriggered(callback: (action: ActionId) => void): () => void;
}
```
---
## 7. 数据模型
### 7.1 文件布局
```
{userData}/
├── global_settings.json
├── books_registry.json
├── books/
│ └── {bookId}.sqlite
└── logs/bilin.log
```
### 7.2 global_settings.json
```typescript
interface GlobalSettings {
penName: string;
onboardingCompleted: boolean;
theme: 'default' | 'bamboo' | 'moonlit' | 'ricepaper' | 'mist' | 'teagarden' | 'twilight';
language: 'zh-CN' | 'en';
dailyWordGoal: number;
shortcuts: Record<ActionId, string>;
}
```
P0/P1 不实现:`aiBackend``syncConfig``chapterTemplates`(留字段或 P2+ 扩展)。
### 7.3 books_registry.json
```typescript
interface BookMeta {
id: string;
name: string;
category: string;
targetWordCount: number | null;
dbPath: string;
status: 'draft' | 'ongoing' | 'done';
lastOpenedAt: string;
lastChapterId: string | null;
createdAt: string;
}
```
### 7.4 SQLite Schema v1(每书)
```sql
CREATE TABLE schema_version (
version INTEGER PRIMARY KEY,
applied_at TEXT NOT NULL DEFAULT (datetime('now')),
description TEXT
);
CREATE TABLE volumes (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT DEFAULT '',
sort_order INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE chapters (
id TEXT PRIMARY KEY,
volume_id TEXT,
title TEXT NOT NULL DEFAULT '',
content TEXT NOT NULL DEFAULT '',
status TEXT NOT NULL DEFAULT 'draft',
word_count INTEGER NOT NULL DEFAULT 0,
sort_order INTEGER NOT NULL DEFAULT 0,
cursor_offset INTEGER DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (volume_id) REFERENCES volumes(id) ON DELETE SET NULL
);
```
**P1 不含**`outline_items``settings``inspiration``snapshots``undo_stack``bookmarks` 等(P2+ 通过迁移追加)。
### 7.5 创建书籍流程
1. 生成 UUID
2.`books/` 下创建 `{id}.sqlite`,执行 schema v1 迁移
3. 写入 `books_registry.json`
4. 默认创建分卷「第一卷」
5. 若 Onboarding 勾选示例书,额外创建 1 章示例正文
---
## 8. 状态管理
### 8.1 Zustand Stores
| Store | 职责 |
|-------|------|
| `useAppStore` | 当前视图(home/editor/settings)、侧栏激活 panel |
| `useTabStore` | openTabs、activeTabId |
| `useBookStore` | 书籍列表、当前书、分卷章节树、选中章节 ID |
| `useSettingsStore` | theme、language、penName、shortcuts |
| `useOnboardingStore` | 向导步骤、完成状态 |
写操作:调用 IPC → 成功后更新 Store。
### 8.2 Jotai Atoms(编辑器域)
| Atom | 用途 |
|------|------|
| `currentChapterIdAtom` | 当前编辑章节 |
| `editorContentAtom` | TipTap 文档内容 |
| `editorDirtyAtom` | 未保存标记 |
| `editorSavingAtom` | 保存中状态 |
| `wordCountAtom` | `{ chapter, volume, book }` |
| `cursorOffsetAtom` | 光标字符偏移 |
| `lastSavedAtAtom` | 上次保存时间 |
### 8.3 章节切换序列
1.`editorDirtyAtom` → 立即 `chapter:update`
2. `chapter:get` 加载新章节
3. TipTap 设置 content,恢复 cursor_offset
4. 重置 dirty
### 8.4 自动保存
- 监听 TipTap `onUpdate`
- debounce **2000ms**
- 调用 `chapter:update`content、cursorOffset、wordCount
- 主进程 `word-count.ts` 增量更新章节字数;卷/全书字数由 chapters 聚合或缓存
---
## 9. UI 规范
### 9.1 混合方案落地
| 区域 | 实现 |
|------|------|
| 顶栏、侧栏、编辑器区、状态栏 | 从 `ui_pc.html` 提取 CSS(变量 + 布局类) |
| 模态框、Select、Checkbox、Toast | Radix UI 封装于 `components/ui/` |
| 主题 | `[data-theme="xxx"]` 覆盖 CSS 变量,与原型 7 主题 ID 一致 |
### 9.2 三视图
- **主页** `#home-page`:Logo、操作按钮、书籍网格、最近编辑
- **编辑器** `#editor-layout`:左栏四 Tab + 中央编辑器 + 右栏占位
- **设置** `#settings-page`:侧栏导航 + 通用/编辑器/AI(占位)/快捷键/备份(占位)/关于
### 9.3 默认快捷键(P0/P1 实现子集)
| 功能 | 默认 | P0/P1 |
|------|------|-------|
| 保存章节 | Ctrl+S | ✅ |
| 新建章节 | Ctrl+Shift+N | ✅ |
| 切换专注模式 | Ctrl+Alt+F | 占位 Toast |
| 全局搜索 | Ctrl+Shift+F | 占位 Toast |
| 关闭 Tab | Ctrl+W | ✅ |
| 打开设置 | — | 顶栏齿轮 |
其余快捷键在设置页展示,P2+ 绑定实际功能。
### 9.4 i18n 要求
- 所有用户可见字符串使用 `t()`
- zh-CN 与 en 同步维护,不得混用语言
- 数字/日期格式随 locale 变化
---
## 10. 核心流程
### 10.1 启动
1. 主进程读取 `global_settings.json`,注册快捷键
2. 渲染进程 `settings:get` → 应用 theme + language
3.`onboardingCompleted === false` → 显示 Onboarding
4. 否则 → 主页 + `book:list`
### 10.2 打开书籍
1. `book:open` → 填充 useBookStore
2. 新建 Tab`lastChapterId` 或第一卷第一章
3. `chapter:get` → TipTap 加载
### 10.3 错误处理
| 场景 | code | recoverable | UI |
|------|------|-------------|-----|
| SQLite 写失败 | DB_WRITE_FAILED | true | Toast + 重试 |
| DB 文件丢失 | FILE_NOT_FOUND | false | 错误对话框 |
| IPC 异常 | UNKNOWN | true | Toast |
渲染进程统一 `ipcCall()` 封装处理。
---
## 11. 测试
### 11.1 工具
- **Vitest**:主进程 Repository、word-count、IPC handler(内存 SQLite
- **Playwright + Electron**E2E
### 11.2 P0/P1 必过用例
| ID | 场景 |
|----|------|
| E2E-THEME-01 | 依次切换 7 主题,UI 颜色正确 |
| E2E-I18N-03 | 中英文切换,无语言混杂 |
| E2E-ONBOARD-02 | 完成引导,笔名持久化 |
| E2E-KEY-02 | Ctrl+Shift+N 新建章节 |
| E2E-P1-SAVE | 写作 → 等 2s 自动保存 → 重启 → 内容完整 |
| E2E-P1-CURSOR | 切换章节再回来,光标偏移恢复 |
---
## 12. 非目标(P0/P1 明确排除)
- AI 任何后端集成
- 版本历史、undo 磁盘持久化
- 大纲/设定/灵感数据持久化
- 导入导出、云同步、PDF
- 驾驶舱、图谱、时间线业务逻辑
- macOS/Linux 打包与签名
- 无障碍完整 E2E(P7 补全)
---
## 13. 风险与缓解
| 风险 | 缓解 |
|------|------|
| better-sqlite3 原生模块 Electron 编译 | electron-vite 官方模板 + rebuild 脚本 |
| TipTap 大文档性能 | P1 单章编辑;全书虚拟滚动在 P2 |
| ui_pc.html CSS 迁移工作量大 | 先迁移 layout + themes,组件级样式按需提取 |
| P1 范围含完整 i18n/主题/Onboarding 导致工期膨胀 | 严格区分 P1 完整 vs 占位清单(§5.2) |
---
## 14. 完成定义(Definition of Done
P0/P1 合并发布为 **v0.1.0** 时:
- [ ] Windows 下 `npm run dev` 可启动,无控制台 error
- [ ] 新用户走完 Onboarding,可创建书籍并写作
- [ ] 7 主题、双语、快捷键自定义可用
- [ ] 自动保存与重启恢复通过 E2E
- [ ] §5.2 占位功能行为符合规范(可见 + Toast,无崩溃)
- [ ] Vitest 集成测试覆盖 chapter CRUD + settings 读写
---
*本规格为笔临全量实现的 Phase 0/1 子项目。后续阶段各自独立 spec 或在本文档增量修订。*