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

18 KiB
Raw Permalink Blame History

笔临 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 已确认决策

维度 决策
总体目标 全量实现,分 P0P7 阶段交付
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 信封

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 形状

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

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 不实现:aiBackendsyncConfigchapterTemplates(留字段或 P2+ 扩展)。

7.3 books_registry.json

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(每书)

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_itemssettingsinspirationsnapshotsundo_stackbookmarks 等(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:updatecontent、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. 新建 TablastChapterId 或第一卷第一章
  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 + ElectronE2E

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 打包与签名
  • 无障碍完整 E2EP7 补全)

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 或在本文档增量修订。