Files
bilin/docs/superpowers/specs/2026-07-05-bilin-p0-p1-design.md
bing a06038035b docs: 添加笔临 P0/P1 设计规格
定义 Electron+React 地基与写作核心的架构、IPC、数据模型与验收标准,作为全量实现的第一阶段依据。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-05 16:47:15 +08:00

551 lines
18 KiB
Markdown
Raw Permalink 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.
# 笔临 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 或在本文档增量修订。*