91c93954df
18 个 Task 覆盖主题修复、schema v2、统一编辑、引用/搜索/版本/地标/用词分析至 v0.2.0。 Co-authored-by: Cursor <cursoragent@cursor.com>
732 lines
21 KiB
Markdown
732 lines
21 KiB
Markdown
# 笔临 P2 Implementation Plan
|
||
|
||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||
|
||
**Goal:** 交付 v0.2.0:大纲/设定/灵感持久化、统一中央编辑、引用面板、版本历史、FTS 全局搜索、地标、用词分析,并修复 3 套浅色主题对比度问题。
|
||
|
||
**Architecture:** 延续 electron-vite 单体仓库;主进程 `node:sqlite` schema v2 + FTS5 + Snapshot/Search/WordFreq 服务;渲染进程新增 `useEditStore`(EditTarget)统一 TipTap 编辑;IPC 延续 `IpcResult` 信封;UI 对齐 `design/ui_pc.html` 模态与引用面板。
|
||
|
||
**Tech Stack:** Electron 36、electron-vite、React 18、TypeScript、TipTap、node:sqlite、Zustand、Jotai、Radix UI、i18next、diff-match-patch、Vitest、Playwright
|
||
|
||
**Spec:** `docs/superpowers/specs/2026-07-06-bilin-p2-design.md`
|
||
|
||
---
|
||
|
||
## File Map(P2 新增/修改核心文件)
|
||
|
||
| 文件 | 职责 |
|
||
|------|------|
|
||
| `src/renderer/styles/themes.css` | 补全浅色 token、`--text-on-accent`、`--input-*` |
|
||
| `src/main/db/schema-v2.sql` | v2 表 + FTS5 |
|
||
| `src/main/db/migrate.ts` | version 1→2 增量迁移 |
|
||
| `src/main/db/repositories/outline.repo.ts` | 大纲 CRUD/树/move |
|
||
| `src/main/db/repositories/setting.repo.ts` | 设定 CRUD + chapterRefs |
|
||
| `src/main/db/repositories/inspiration.repo.ts` | 灵感 CRUD/convert |
|
||
| `src/main/db/repositories/snapshot.repo.ts` | 快照 CRUD/FIFO |
|
||
| `src/main/db/repositories/bookmark.repo.ts` | 地标 CRUD |
|
||
| `src/main/services/fts-sync.service.ts` | FTS5 增量同步 |
|
||
| `src/main/services/search.service.ts` | 查询 + replace dryRun |
|
||
| `src/main/services/snapshot.service.ts` | 定时/退出快照 |
|
||
| `src/main/services/wordfreq.service.ts` | 词频统计 |
|
||
| `src/shared/types.ts` | P2 类型扩展 |
|
||
| `src/shared/ipc-channels.ts` | P2 IPC 常量 |
|
||
| `src/renderer/stores/useEditStore.ts` | EditTarget 切换 |
|
||
| `src/renderer/components/outline/OutlineTree.tsx` | 大纲树 UI |
|
||
| `src/renderer/components/reference/ReferencePanel.tsx` | 引用面板 |
|
||
| `src/renderer/components/search/SearchModal.tsx` | 全局搜索 |
|
||
| `src/renderer/components/version/VersionModal.tsx` | 版本历史 + diff |
|
||
| `src/renderer/components/landmark/LandmarkModal.tsx` | 地标清单 |
|
||
| `src/renderer/components/wordfreq/WordFreqPanel.tsx` | 用词分析 |
|
||
| `e2e/themes-contrast.spec.ts` | 浅色主题对比度 E2E |
|
||
|
||
---
|
||
|
||
## Task 1: P2.0 主题 Token 修复
|
||
|
||
**Files:**
|
||
- Modify: `src/renderer/styles/themes.css`
|
||
- Modify: `src/renderer/styles/layout.css`
|
||
- Modify: `src/renderer/styles/globals.css`
|
||
- Modify: `src/renderer/components/settings/SettingsPage.tsx`
|
||
- Create: `e2e/themes-contrast.spec.ts`
|
||
|
||
- [ ] **Step 1: 在 `:root` 新增语义 token**
|
||
|
||
```css
|
||
/* themes.css :root 追加 */
|
||
--text-on-accent: #ffffff;
|
||
--input-text: var(--text-primary);
|
||
--input-bg: var(--bg-surface);
|
||
--input-border: var(--border);
|
||
```
|
||
|
||
为 `[data-theme='ricepaper']`、`mist`、`teagarden` 各补全:`--bg-surface`、`--bg-active`、`--text-dim`、`--accent-glow`、`--border-light`、`--green`、`--orange`、`--red`。
|
||
|
||
- [ ] **Step 2: 替换硬编码 `#fff`**
|
||
|
||
在 `layout.css` 中将 `.btn-large.primary`、`.tab.active` 的 `color: #fff` 改为 `color: var(--text-on-accent)`。
|
||
|
||
浅色主题覆盖:
|
||
|
||
```css
|
||
[data-theme='ricepaper'],
|
||
[data-theme='mist'],
|
||
[data-theme='teagarden'] {
|
||
--text-on-accent: #ffffff;
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 3: 统一表单控件样式**
|
||
|
||
`globals.css` 追加:
|
||
|
||
```css
|
||
input, select, textarea {
|
||
color: var(--input-text);
|
||
background: var(--input-bg);
|
||
border: 1px solid var(--input-border);
|
||
}
|
||
```
|
||
|
||
删除 `SettingsPage.tsx` 内联 `color: inherit` / 硬编码 border,改用 class `form-control`(在 globals 定义)。
|
||
|
||
- [ ] **Step 4: 写 contrast E2E**
|
||
|
||
`e2e/themes-contrast.spec.ts`:对 `ricepaper`/`mist`/`teagarden`,在主页、设置页、编辑器断言 `#app` 内 `h1,h2,button,.setting-item` 可见(`toBeVisible`)且 computed color 非透明/非背景同色(用 `page.evaluate` 比较 RGB)。
|
||
|
||
- [ ] **Step 5: 运行验证**
|
||
|
||
```bash
|
||
npm run build
|
||
npm run test:e2e -- e2e/themes-contrast.spec.ts
|
||
```
|
||
|
||
Expected: PASS
|
||
|
||
- [ ] **Step 6: Commit**
|
||
|
||
```bash
|
||
git add src/renderer/styles e2e/themes-contrast.spec.ts src/renderer/components/settings/SettingsPage.tsx
|
||
git commit -m "fix: repair light theme tokens and contrast"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 2: 共享类型与 IPC 通道
|
||
|
||
**Files:**
|
||
- Modify: `src/shared/types.ts`
|
||
- Modify: `src/shared/ipc-channels.ts`
|
||
- Modify: `src/main/services/global-settings.ts`
|
||
|
||
- [ ] **Step 1: 扩展 types.ts**
|
||
|
||
追加(完整定义见 spec §3.2):
|
||
|
||
```typescript
|
||
export type OutlineStatus = 'pending' | 'writing' | 'done' | 'abandoned'
|
||
export type SettingType = 'character' | 'location' | 'item' | 'skill' | 'faction' | 'custom'
|
||
export type SnapshotType = 'auto' | 'manual' | 'persist' | 'ai'
|
||
export type LandmarkType = 'todo' | 'foreshadow' | 'research' | 'custom'
|
||
export type EditTargetKind = 'chapter' | 'outline' | 'setting' | 'inspiration'
|
||
export type EditTarget = { kind: EditTargetKind; id: string }
|
||
export type SearchScope = 'all' | 'volume' | 'chapter'
|
||
export type SearchSourceKind = 'chapter' | 'outline' | 'setting' | 'inspiration' | 'bookmark'
|
||
|
||
export interface OutlineItem { /* spec §3.2 字段 */ }
|
||
export interface SettingEntry { /* ... */ }
|
||
export interface InspirationEntry { /* ... */ }
|
||
export interface Snapshot { /* ... */ }
|
||
export interface Bookmark { /* ... */ }
|
||
export interface SearchResult {
|
||
kind: SearchSourceKind
|
||
id: string
|
||
title: string
|
||
snippet: string
|
||
chapterId?: string
|
||
position?: number
|
||
}
|
||
```
|
||
|
||
扩展 `GlobalSettings`:
|
||
|
||
```typescript
|
||
snapshotIntervalMs: number
|
||
snapshotMaxAuto: number
|
||
snapshotMaxPersist: number
|
||
searchHistory: string[]
|
||
```
|
||
|
||
扩展 `BookOpenResult`:`outlines`, `settings`, `inspirations`。
|
||
|
||
- [ ] **Step 2: 扩展 ipc-channels.ts**
|
||
|
||
```typescript
|
||
OUTLINE_LIST: 'outline:list',
|
||
OUTLINE_CREATE: 'outline:create',
|
||
// ... 全部见 spec §4.1
|
||
SEARCH_QUERY: 'search:query',
|
||
WORDFREQ_ANALYZE: 'wordfreq:analyze',
|
||
```
|
||
|
||
- [ ] **Step 3: global-settings defaults**
|
||
|
||
```typescript
|
||
function defaults(): GlobalSettings {
|
||
return {
|
||
// ...existing
|
||
snapshotIntervalMs: 300_000,
|
||
snapshotMaxAuto: 20,
|
||
snapshotMaxPersist: 3,
|
||
searchHistory: []
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 4: Commit**
|
||
|
||
```bash
|
||
git commit -am "feat: add P2 shared types and IPC channels"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 3: Schema v2 迁移
|
||
|
||
**Files:**
|
||
- Create: `src/main/db/schema-v2.sql`
|
||
- Modify: `src/main/db/migrate.ts`
|
||
- Create: `tests/main/migrate-v2.test.ts`
|
||
|
||
- [ ] **Step 1: 写 schema-v2.sql**
|
||
|
||
包含 spec §3.1 全部 CREATE TABLE + FTS5:
|
||
|
||
```sql
|
||
CREATE VIRTUAL TABLE IF NOT EXISTS search_fts USING fts5(
|
||
source_kind, source_id, title, body, tokenize='unicode61'
|
||
);
|
||
```
|
||
|
||
- [ ] **Step 2: 更新 migrate.ts**
|
||
|
||
```typescript
|
||
import schemaV2 from './schema-v2.sql?raw'
|
||
|
||
const CURRENT_VERSION = 2
|
||
|
||
export function migrate(db: SqliteDb): void {
|
||
// ... v1 逻辑保留
|
||
if (current >= CURRENT_VERSION) return
|
||
if (current < 1) { /* apply schemaV1 */ }
|
||
if (current < 2) {
|
||
db.exec(schemaV2)
|
||
db.prepare('INSERT INTO schema_version (version, description) VALUES (?, ?)').run(2, 'P2 schema')
|
||
}
|
||
}
|
||
```
|
||
|
||
v2 SQL 中对已有库使用 `ALTER TABLE chapters ADD COLUMN ...` 需单独 `schema-v2-alter.sql` 或在 migrate 中 try/catch 忽略 duplicate column。
|
||
|
||
- [ ] **Step 3: 写 migrate 测试**
|
||
|
||
```typescript
|
||
// tests/main/migrate-v2.test.ts
|
||
it('migrates v1 database to v2 with new tables', () => {
|
||
const db = new DatabaseSync(':memory:')
|
||
// apply v1 only, then migrate full
|
||
expect(db.prepare("SELECT name FROM sqlite_master WHERE name='outline_items'").get()).toBeTruthy()
|
||
})
|
||
```
|
||
|
||
- [ ] **Step 4: 运行**
|
||
|
||
```bash
|
||
npm run test -- tests/main/migrate-v2.test.ts
|
||
```
|
||
|
||
- [ ] **Step 5: Commit**
|
||
|
||
```bash
|
||
git commit -am "feat: add schema v2 migration"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 4: Outline Repository
|
||
|
||
**Files:**
|
||
- Create: `src/main/db/repositories/outline.repo.ts`
|
||
- Create: `tests/main/outline.repo.test.ts`
|
||
|
||
- [ ] **Step 1: 写失败测试**
|
||
|
||
```typescript
|
||
it('creates nested outline items', () => {
|
||
const parent = repo.create(null, '第一幕', 0)
|
||
const child = repo.create(parent.id, '开端', 0)
|
||
expect(repo.listTree()).toHaveLength(1)
|
||
expect(repo.listTree()[0].children).toHaveLength(1)
|
||
})
|
||
```
|
||
|
||
- [ ] **Step 2: 实现 outline.repo.ts**
|
||
|
||
方法:`create(parentId, title, sortOrder)`、`get`、`listFlat`、`update`、`delete`(级联)、`move`、`listUncovered()`。
|
||
|
||
- [ ] **Step 3: 运行 PASS + Commit**
|
||
|
||
```bash
|
||
git commit -am "feat: add outline repository"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 5: Setting + Inspiration Repository
|
||
|
||
**Files:**
|
||
- Create: `src/main/db/repositories/setting.repo.ts`
|
||
- Create: `src/main/db/repositories/inspiration.repo.ts`
|
||
- Create: `tests/main/setting.repo.test.ts`
|
||
- Create: `tests/main/inspiration.repo.test.ts`
|
||
|
||
- [ ] **Step 1: setting.repo** — CRUD + `syncChapterRefs(settingId, chapterIds[])`
|
||
|
||
- [ ] **Step 2: inspiration.repo** — CRUD + `convertToOutline(id)` / `convertToSetting(id, type)`
|
||
|
||
- [ ] **Step 3: 测试 PASS + Commit**
|
||
|
||
```bash
|
||
git commit -am "feat: add setting and inspiration repositories"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 6: Snapshot + Bookmark Repository
|
||
|
||
**Files:**
|
||
- Create: `src/main/db/repositories/snapshot.repo.ts`
|
||
- Create: `src/main/db/repositories/bookmark.repo.ts`
|
||
- Create: `tests/main/snapshot.repo.test.ts`
|
||
- Create: `tests/main/bookmark.repo.test.ts`
|
||
|
||
- [ ] **Step 1: snapshot.repo**
|
||
|
||
```typescript
|
||
create(chapterId, type, content, name?): Snapshot
|
||
list(chapterId): Snapshot[]
|
||
pruneAuto(chapterId, max: number): void // FIFO 删除最旧 auto
|
||
restore(snapshotId): string // 返回 content
|
||
```
|
||
|
||
- [ ] **Step 2: bookmark.repo** — CRUD + `listByChapter` + `resolve(id, boolean)`
|
||
|
||
- [ ] **Step 3: 测试 + Commit**
|
||
|
||
```bash
|
||
git commit -am "feat: add snapshot and bookmark repositories"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 7: FTS Sync + Search Service
|
||
|
||
**Files:**
|
||
- Create: `src/main/services/fts-sync.service.ts`
|
||
- Create: `src/main/services/search.service.ts`
|
||
- Create: `tests/main/search.service.test.ts`
|
||
|
||
- [ ] **Step 1: fts-sync.service.ts**
|
||
|
||
```typescript
|
||
export class FtsSyncService {
|
||
upsert(db: SqliteDb, kind: SearchSourceKind, id: string, title: string, body: string): void {
|
||
db.prepare('DELETE FROM search_fts WHERE source_kind=? AND source_id=?').run(kind, id)
|
||
db.prepare('INSERT INTO search_fts(source_kind, source_id, title, body) VALUES (?,?,?,?)').run(kind, id, title, body)
|
||
}
|
||
rebuildAll(db: SqliteDb, book: BookRegistryService): void { /* 全量 */ }
|
||
}
|
||
```
|
||
|
||
HTML → plainText 用现有 `word-count` 同款 strip 或简单 `replace(/<[^>]+>/g,'')`。
|
||
|
||
- [ ] **Step 2: search.service.ts**
|
||
|
||
```typescript
|
||
query(db, { text, scope, regex, caseSensitive }): SearchResult[]
|
||
replace(db, { text, replacement, dryRun }): { count: number; previews: SearchResult[] }
|
||
```
|
||
|
||
regex 模式:FTS 不支持时 fallback 遍历 `search_fts` body LIKE。
|
||
|
||
- [ ] **Step 3: 测试 + Commit**
|
||
|
||
```bash
|
||
git commit -am "feat: add FTS sync and search service"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 8: Snapshot Service + WordFreq Service
|
||
|
||
**Files:**
|
||
- Create: `src/main/services/snapshot.service.ts`
|
||
- Create: `src/main/services/wordfreq.service.ts`
|
||
- Modify: `src/main/index.ts`(app.on('before-quit') 触发 persist 快照)
|
||
- Create: `tests/main/wordfreq.service.test.ts`
|
||
|
||
- [ ] **Step 1: 安装 diff 库**
|
||
|
||
```bash
|
||
npm install diff-match-patch
|
||
npm install -D @types/diff-match-patch
|
||
```
|
||
|
||
- [ ] **Step 2: snapshot.service.ts**
|
||
|
||
```typescript
|
||
export class SnapshotService {
|
||
startAutoSave(bookId: string, chapterId: string, getContent: () => string): void {
|
||
this.timer = setInterval(() => { /* if changed → auto snapshot + prune */ }, settings.snapshotIntervalMs)
|
||
}
|
||
onPersistSave(bookId, chapterId, content): void { /* type=persist, prune to max 3 */ }
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 3: wordfreq.service.ts**
|
||
|
||
CJK:`text.match(/[\u4e00-\u9fff]/g)` 计数;Latin:`/\b[a-zA-Z]{2,}\b/g`;返回 Top 100。
|
||
|
||
- [ ] **Step 4: 测试 + Commit**
|
||
|
||
```bash
|
||
git commit -am "feat: add snapshot and word frequency services"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 9: IPC Handlers + Preload
|
||
|
||
**Files:**
|
||
- Create: `src/main/ipc/handlers/outline.handler.ts`(及 setting/inspiration/snapshot/bookmark/search/wordfreq)
|
||
- Modify: `src/main/ipc/register.ts`
|
||
- Modify: `src/preload/index.ts`
|
||
- Modify: `src/shared/electron-api.d.ts`
|
||
|
||
- [ ] **Step 1: 每个 handler 遵循现有 chapter.handler 模式**
|
||
|
||
```typescript
|
||
export function registerOutlineHandlers(books: BookRegistryService): void {
|
||
ipcMain.handle(IPC.OUTLINE_LIST, (_e, { bookId }) =>
|
||
wrap(() => books.getOutlineRepo(bookId).listFlat())
|
||
)
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: BookRegistryService 扩展**
|
||
|
||
```typescript
|
||
getOutlineRepo(bookId): OutlineRepository
|
||
getSettingRepo(bookId): SettingRepository
|
||
// ...
|
||
open(bookId): BookOpenResult // 附加 outlines/settings/inspirations
|
||
```
|
||
|
||
- [ ] **Step 3: preload 暴露新 API 命名空间**
|
||
|
||
```typescript
|
||
outline: { list, create, update, delete, move },
|
||
setting: { list, create, update, delete },
|
||
// ...
|
||
search: { query, replace, getHistory, saveHistory },
|
||
wordfreq: { analyze },
|
||
```
|
||
|
||
- [ ] **Step 4: `npm run build` + Commit**
|
||
|
||
```bash
|
||
git commit -am "feat: add P2 IPC handlers and preload API"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 10: useEditStore + 统一 TipTap 编辑
|
||
|
||
**Files:**
|
||
- Create: `src/renderer/stores/useEditStore.ts`
|
||
- Modify: `src/renderer/components/editor/TipTapEditor.tsx`
|
||
- Modify: `src/renderer/components/layout/EditorLayout.tsx`
|
||
- Modify: `src/renderer/stores/useBookStore.ts`
|
||
|
||
- [ ] **Step 1: useEditStore.ts**
|
||
|
||
```typescript
|
||
interface EditState {
|
||
target: EditTarget | null
|
||
switchTarget: (next: EditTarget) => Promise<void>
|
||
saveCurrent: () => Promise<void>
|
||
}
|
||
|
||
switchTarget: async (next) => {
|
||
await flushEditorSave()
|
||
const data = await loadTargetContent(next) // IPC by kind
|
||
set({ target: next })
|
||
// TipTapEditor reacts via target prop
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: TipTapEditor 接受 target + contentLoader**
|
||
|
||
```typescript
|
||
interface TipTapEditorProps {
|
||
target: EditTarget | null
|
||
}
|
||
```
|
||
|
||
`useEditor` extensions:target.kind === 'chapter' ? fullStarterKit : simplifiedKit。
|
||
|
||
save 回调按 kind 分发 IPC:`chapter.update` / `outline.update` / etc.
|
||
|
||
- [ ] **Step 3: EditorLayout 四 Tab 选中 → switchTarget**
|
||
|
||
删除 `activePanel !== 'chapters'` 占位,替换为 OutlineTree / SettingList / InspirationList。
|
||
|
||
- [ ] **Step 4: Commit**
|
||
|
||
```bash
|
||
git commit -am "feat: add unified EditSession and TipTap targets"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 11: 左栏 CRUD UI(大纲/设定/灵感)
|
||
|
||
**Files:**
|
||
- Create: `src/renderer/components/outline/OutlineTree.tsx`
|
||
- Create: `src/renderer/components/setting/SettingList.tsx`
|
||
- Create: `src/renderer/components/inspiration/InspirationList.tsx`
|
||
- Modify: `public/locales/zh-CN/translation.json`(及 en)
|
||
|
||
- [ ] **Step 1: OutlineTree** — 递归渲染;双击标题 inline 编辑;底部「+ 新建大纲」;未覆盖橙色边框
|
||
|
||
- [ ] **Step 2: SettingList** — 按 type 分组;底部新建 Dialog(type select + name)
|
||
|
||
- [ ] **Step 3: InspirationList** — 列表 + 新建;`InspirationModal` 快速捕获(Ctrl+Shift+I)
|
||
|
||
- [ ] **Step 4: E2E outline 持久化**
|
||
|
||
`e2e/outline.spec.ts`:建书 → 新建大纲 → 输入内容 → 重启 → 内容保留。
|
||
|
||
- [ ] **Step 5: Commit**
|
||
|
||
```bash
|
||
git commit -am "feat: add outline setting inspiration sidebar UI"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 12: 大纲对照视图(P2.3)
|
||
|
||
**Files:**
|
||
- Create: `src/renderer/components/outline/OutlineCompareView.tsx`
|
||
- Modify: `src/renderer/stores/useOutlineStore.ts`
|
||
|
||
- [ ] **Step 1: useOutlineStore** — `compareMode: boolean`、`filter: 'all'|'uncovered'|'covered'`
|
||
|
||
- [ ] **Step 2: OutlineCompareView** — 35/65 分屏;右侧只读章节 HTML;底部偏差条
|
||
|
||
- [ ] **Step 3: E2E E2E-COV-01/02**(spec §5.5.3)
|
||
|
||
- [ ] **Step 4: Commit**
|
||
|
||
```bash
|
||
git commit -am "feat: add outline compare view and coverage filter"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 13: 引用面板(P2.4)
|
||
|
||
**Files:**
|
||
- Create: `src/renderer/components/reference/ReferencePanel.tsx`
|
||
- Create: `src/renderer/stores/useReferenceStore.ts`
|
||
- Modify: `src/renderer/styles/layout.css`(`#reference-panel`)
|
||
- Create: `src/renderer/extensions/mention.ts`
|
||
- Modify: `src/renderer/App.tsx`(openReference 快捷键)
|
||
|
||
- [ ] **Step 1: ReferencePanel** — Tab + 搜索 + 钉住 + 拖拽(`onDragStart` setData text/plain)
|
||
|
||
- [ ] **Step 2: 与 AI 右栏互斥** — `useAppStore` 或 layout state:`referenceOpen` 时隐藏 RightPanel
|
||
|
||
- [ ] **Step 3: Mention 扩展(简化)** — 输入 `@` 弹出设定名列表;点击跳转 `switchTarget({kind:'setting',id})`
|
||
|
||
- [ ] **Step 4: E2E E2E-P2-REF-01**
|
||
|
||
- [ ] **Step 5: Commit**
|
||
|
||
```bash
|
||
git commit -am "feat: add reference panel with drag insert and mentions"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 14: 版本历史模态(P2.5)
|
||
|
||
**Files:**
|
||
- Create: `src/renderer/components/version/VersionModal.tsx`
|
||
- Modify: `src/renderer/components/layout/EditorLayout.tsx`(工具栏入口)
|
||
- Modify: `src/renderer/App.tsx`(Ctrl+S 触发 persist snapshot)
|
||
|
||
- [ ] **Step 1: VersionModal UI** — 列表 + 选中版本;双栏 diff 用 `diff-match-patch`
|
||
|
||
```typescript
|
||
import DiffMatchPatch from 'diff-match-patch'
|
||
const dmp = new DiffMatchPatch()
|
||
const diffs = dmp.diff_main(oldPlain, newPlain)
|
||
dmp.diff_cleanupSemantic(diffs)
|
||
```
|
||
|
||
渲染:DIFF_INSERT 绿色背景,DIFF_DELETE 红色删除线。
|
||
|
||
- [ ] **Step 2: 回滚** — 调用 `snapshot:restore`;确认 Dialog
|
||
|
||
- [ ] **Step 3: SnapshotService wired** — EditorLayout mount 章节时 `startAutoSave`
|
||
|
||
- [ ] **Step 4: E2E E2E-P2-SNAP-01**
|
||
|
||
- [ ] **Step 5: Commit**
|
||
|
||
```bash
|
||
git commit -am "feat: add chapter version history with diff and restore"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 15: 全局搜索模态(P2.6)
|
||
|
||
**Files:**
|
||
- Create: `src/renderer/components/search/SearchModal.tsx`
|
||
- Create: `src/renderer/stores/useSearchStore.ts`
|
||
- Modify: `src/renderer/App.tsx`(globalSearch 快捷键)
|
||
|
||
- [ ] **Step 1: SearchModal** — 对齐 `#searchModal`;范围 select;高级选项折叠(regex/case)
|
||
|
||
- [ ] **Step 2: 结果跳转** — chapter → switchTarget + scroll;bookmark → chapter + position
|
||
|
||
- [ ] **Step 3: 替换预览** — 调用 `search:replace` dryRun → 确认 → 执行
|
||
|
||
- [ ] **Step 4: searchHistory** — 下拉最近 10 条
|
||
|
||
- [ ] **Step 5: E2E E2E-P2-SEARCH-01 + Commit**
|
||
|
||
```bash
|
||
git commit -am "feat: add global search modal with FTS and replace"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 16: 地标全量(P2.6)
|
||
|
||
**Files:**
|
||
- Create: `src/renderer/extensions/landmark-mark.ts`
|
||
- Create: `src/renderer/components/landmark/LandmarkModal.tsx`
|
||
- Modify: `src/renderer/components/editor/TipTapEditor.tsx`
|
||
|
||
- [ ] **Step 1: TipTap Mark** — 行内 `[TODO: label]` 样式;insertLandmark 快捷键打开输入
|
||
|
||
- [ ] **Step 2: LandmarkModal** — 按类型/章节分组;跳转 `switchTarget chapter` + `editor.commands.focus(pos)`
|
||
|
||
- [ ] **Step 3: FTS 同步 bookmark label**
|
||
|
||
- [ ] **Step 4: E2E E2E-P2-LAND-01 + Commit**
|
||
|
||
```bash
|
||
git commit -am "feat: add writing landmarks and list modal"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 17: 用词分析(P2.7)
|
||
|
||
**Files:**
|
||
- Modify: `src/renderer/components/layout/RightPanel.tsx`
|
||
- Create: `src/renderer/components/wordfreq/WordFreqPanel.tsx`
|
||
|
||
- [ ] **Step 1: WordFreqPanel** — 调用 `wordfreq:analyze`;Top 100 列表;点击 → 编辑器 `scrollIntoView` + 临时 highlight Mark
|
||
|
||
- [ ] **Step 2: 过度用词** — 阈值:每千字 >5 次;TipTap Decoration 淡黄色
|
||
|
||
- [ ] **Step 3: §5.6.2 占位** — 面板底部按钮 → `showToast(t('feature.comingSoon'))`
|
||
|
||
- [ ] **Step 4: E2E E2E-P2-WORD-01 + Commit**
|
||
|
||
```bash
|
||
git commit -am "feat: add word frequency panel and overuse highlight"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 18: 全量测试与 v0.2.0 收尾
|
||
|
||
**Files:**
|
||
- Modify: `package.json`(version 0.2.0)
|
||
- Modify: `README.md`
|
||
|
||
- [ ] **Step 1: 运行全量测试**
|
||
|
||
```bash
|
||
npm run test
|
||
npm run build
|
||
npm run test:e2e
|
||
```
|
||
|
||
Expected: 全部 PASS
|
||
|
||
- [ ] **Step 2: 手动验收** — spec §1.3 七项 + §10 DoD
|
||
|
||
- [ ] **Step 3: 更新 README** — v0.2.0 功能列表、测试命令
|
||
|
||
- [ ] **Step 4: Commit + tag**
|
||
|
||
```bash
|
||
git commit -am "chore: prepare v0.2.0 release"
|
||
git tag v0.2.0
|
||
```
|
||
|
||
---
|
||
|
||
## Plan Self-Review
|
||
|
||
| Spec 章节 | 对应用 Task |
|
||
|-----------|-------------|
|
||
| §5.9 主题 P2.0 | Task 1 |
|
||
| §3 Schema v2 | Task 3 |
|
||
| §4 IPC | Task 2, 9 |
|
||
| §5.1 四 Tab | Task 10, 11 |
|
||
| §5.5 大纲对照/覆盖度 | Task 12 |
|
||
| §5.3.8 引用面板 | Task 13 |
|
||
| §5.4 版本历史 | Task 8, 14 |
|
||
| §5.6.1 搜索 | Task 7, 15 |
|
||
| §5.3.9 地标 | Task 16 |
|
||
| §5.6.3 用词分析 | Task 17 |
|
||
| §5.6.2 AI 命名 | Task 17 Step 3 占位 |
|
||
| §7 测试 | Task 1–17 各 E2E + Task 18 |
|
||
| §10 DoD | Task 18 |
|
||
|
||
**Placeholder 扫描:** 无 TBD;章节拖拽明确未含;语音灵感为占位。
|
||
|
||
**类型一致性:** `EditTarget`、`SearchSourceKind`、`SnapshotType` 在 Task 2 定义,后续任务一致引用。
|
||
|
||
---
|
||
|
||
## 执行方式
|
||
|
||
Plan 已保存至 `docs/superpowers/plans/2026-07-06-bilin-p2.md`。
|
||
|
||
**两种执行选项:**
|
||
|
||
1. **Subagent-Driven(推荐)** — 每个 Task 派发独立 subagent,任务间 review,迭代快
|
||
2. **Inline Execution** — 在本会话按 Task 顺序直接实现,批次间设检查点
|
||
|
||
你选哪种?
|