feat: ship v1.0.0 with character graph and writing analytics
Add P7 Cytoscape relationship graph with setting CRUD, P9 cockpit analytics driven by writing_sessions, chapter POV selection, and full test coverage. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# 笔临 P7 角色关系图谱 + P9 个人写作分析 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.
|
||||
> **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 (`- [x]`) syntax for tracking.
|
||||
|
||||
**Goal:** 交付 v1.0.0:§7 角色关系图谱(Cytoscape + 设定关系 CRUD)+ §9 个人写作分析(`writing_sessions` 驱动速度/时段热力 + POV/卷分布),驾驶舱内嵌展示。
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
- Modify: `src/shared/types.ts`
|
||||
- Modify: `src/shared/ipc-channels.ts`
|
||||
|
||||
- [ ] **Step 1: 扩展 types**
|
||||
- [x] **Step 1: 扩展 types**
|
||||
|
||||
```typescript
|
||||
export interface CharacterRelationship {
|
||||
@@ -97,7 +97,7 @@ export interface WritingAnalyticsSummary {
|
||||
|
||||
在 `SettingEntry.properties` 使用文档约定 `relationships?: CharacterRelationship[]`(JSDoc 或 inline 注释即可)。
|
||||
|
||||
- [ ] **Step 2: IPC 通道**
|
||||
- [x] **Step 2: IPC 通道**
|
||||
|
||||
```typescript
|
||||
GRAPH_GET_DATA: 'graph:getData',
|
||||
@@ -106,7 +106,7 @@ GRAPH_DELETE_RELATIONSHIP: 'graph:deleteRelationship',
|
||||
ANALYTICS_SUMMARY: 'analytics:summary',
|
||||
```
|
||||
|
||||
- [ ] **Step 3: build 验证**
|
||||
- [x] **Step 3: build 验证**
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
@@ -121,7 +121,7 @@ npm run build
|
||||
- Create: `src/main/db/repositories/writing-session.repo.ts`
|
||||
- Create: `tests/main/writing-session.test.ts`(repo 部分)
|
||||
|
||||
- [ ] **Step 1: writing-log.repo migrate 扩展**
|
||||
- [x] **Step 1: writing-log.repo migrate 扩展**
|
||||
|
||||
在 `migrate()` 的 `CREATE TABLE` 块追加:
|
||||
|
||||
@@ -139,7 +139,7 @@ CREATE INDEX IF NOT EXISTS idx_sessions_book_date ON writing_sessions (book_id,
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_book_started ON writing_sessions (book_id, started_at);
|
||||
```
|
||||
|
||||
- [ ] **Step 2: WritingSessionRepository**
|
||||
- [x] **Step 2: WritingSessionRepository**
|
||||
|
||||
```typescript
|
||||
export class WritingSessionRepository {
|
||||
@@ -153,7 +153,7 @@ export class WritingSessionRepository {
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: UT repo smoke**
|
||||
- [x] **Step 3: UT repo smoke**
|
||||
|
||||
```typescript
|
||||
it('UT-SESSION-03: insert and updateActivity accumulates word_delta', () => {
|
||||
@@ -165,7 +165,7 @@ it('UT-SESSION-03: insert and updateActivity accumulates word_delta', () => {
|
||||
})
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 运行**
|
||||
- [x] **Step 4: 运行**
|
||||
|
||||
```bash
|
||||
npm run test -- tests/main/writing-session.test.ts
|
||||
@@ -179,7 +179,7 @@ npm run test -- tests/main/writing-session.test.ts
|
||||
- Create: `src/main/services/writing-session.service.ts`
|
||||
- Modify: `tests/main/writing-session.test.ts`
|
||||
|
||||
- [ ] **Step 1: 实现**
|
||||
- [x] **Step 1: 实现**
|
||||
|
||||
```typescript
|
||||
const IDLE_MS = 5 * 60 * 1000
|
||||
@@ -217,7 +217,7 @@ export class WritingSessionService {
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: UT-SESSION-01 / UT-SESSION-02**
|
||||
- [x] **Step 2: UT-SESSION-01 / UT-SESSION-02**
|
||||
|
||||
使用 `vi.useFakeTimers()`:
|
||||
|
||||
@@ -226,7 +226,7 @@ it('UT-SESSION-01: within 5min merges into one session', () => { /* advance 2min
|
||||
it('UT-SESSION-02: after 6min creates two sessions', () => { /* advance 6min between calls */ })
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 运行**
|
||||
- [x] **Step 3: 运行**
|
||||
|
||||
```bash
|
||||
npm run test -- tests/main/writing-session.test.ts
|
||||
@@ -243,7 +243,7 @@ npm run test -- tests/main/writing-session.test.ts
|
||||
- Modify: `src/shared/types.ts`(UpdateChapterParams)
|
||||
- Modify: `src/main/ipc/register.ts`(trackerFor 注入 sessions)
|
||||
|
||||
- [ ] **Step 1: chapter.repo mapRow + update**
|
||||
- [x] **Step 1: chapter.repo mapRow + update**
|
||||
|
||||
```typescript
|
||||
// mapRow 增加
|
||||
@@ -253,7 +253,7 @@ povCharacterId: (row.pov_character_id as string) || null,
|
||||
// UPDATE SQL 增加 pov_character_id = ?
|
||||
```
|
||||
|
||||
- [ ] **Step 2: trackerFor 签名**
|
||||
- [x] **Step 2: trackerFor 签名**
|
||||
|
||||
```typescript
|
||||
export function trackerFor(
|
||||
@@ -279,7 +279,7 @@ supplementOnFinish(...) {
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: register.ts**
|
||||
- [x] **Step 3: register.ts**
|
||||
|
||||
```typescript
|
||||
const writingSessionRepo = new WritingSessionRepository(writingLogRepo.getDb())
|
||||
@@ -287,7 +287,7 @@ const writingSessions = new WritingSessionService(writingSessionRepo)
|
||||
// registerChapterHandlers(books, writingLogs, writingSessions)
|
||||
```
|
||||
|
||||
- [ ] **Step 4: build**
|
||||
- [x] **Step 4: build**
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
@@ -301,7 +301,7 @@ npm run build
|
||||
- Create: `src/main/services/character-graph.service.ts`
|
||||
- Create: `tests/main/character-graph.test.ts`
|
||||
|
||||
- [ ] **Step 1: getData 边去重**
|
||||
- [x] **Step 1: getData 边去重**
|
||||
|
||||
```typescript
|
||||
export class CharacterGraphService {
|
||||
@@ -319,7 +319,7 @@ export class CharacterGraphService {
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: upsertRelationship / deleteRelationship**
|
||||
- [x] **Step 2: upsertRelationship / deleteRelationship**
|
||||
|
||||
```typescript
|
||||
upsertRelationship(bookId, sourceId, input: Partial<CharacterRelationship> & { targetId: string; label: string; intimacy: number }): CharacterRelationship {
|
||||
@@ -331,11 +331,11 @@ upsertRelationship(bookId, sourceId, input: Partial<CharacterRelationship> & { t
|
||||
deleteRelationship(bookId, sourceId, relationshipId): void
|
||||
```
|
||||
|
||||
- [ ] **Step 3: UT-GRAPH-01 / UT-GRAPH-02 / UT-GRAPH-03**
|
||||
- [x] **Step 3: UT-GRAPH-01 / UT-GRAPH-02 / UT-GRAPH-03**
|
||||
|
||||
内存 DB + migrate + 创建 3 character settings + 2 relationships。
|
||||
|
||||
- [ ] **Step 4: 运行**
|
||||
- [x] **Step 4: 运行**
|
||||
|
||||
```bash
|
||||
npm run test -- tests/main/character-graph.test.ts
|
||||
@@ -349,7 +349,7 @@ npm run test -- tests/main/character-graph.test.ts
|
||||
- Create: `src/main/services/writing-analytics.service.ts`
|
||||
- Create: `tests/main/writing-analytics.test.ts`
|
||||
|
||||
- [ ] **Step 1: getSummary 实现**
|
||||
- [x] **Step 1: getSummary 实现**
|
||||
|
||||
```typescript
|
||||
export class WritingAnalyticsService {
|
||||
@@ -385,9 +385,9 @@ function computeWpm(sessions: { wordDelta: number; startedAt: string; endedAt: s
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: UT-ANALYTICS-01 / UT-ANALYTICS-02**
|
||||
- [x] **Step 2: UT-ANALYTICS-01 / UT-ANALYTICS-02**
|
||||
|
||||
- [ ] **Step 3: IT-ANALYTICS-01**
|
||||
- [x] **Step 3: IT-ANALYTICS-01**
|
||||
|
||||
```typescript
|
||||
it('IT-ANALYTICS-01: recordDelta feeds session then getSummary', () => {
|
||||
@@ -398,7 +398,7 @@ it('IT-ANALYTICS-01: recordDelta feeds session then getSummary', () => {
|
||||
})
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 运行**
|
||||
- [x] **Step 4: 运行**
|
||||
|
||||
```bash
|
||||
npm run test -- tests/main/writing-analytics.test.ts
|
||||
@@ -416,7 +416,7 @@ npm run test -- tests/main/writing-analytics.test.ts
|
||||
- Modify: `src/preload/index.ts`
|
||||
- Modify: `src/shared/electron-api.d.ts`
|
||||
|
||||
- [ ] **Step 1: graph.handler**
|
||||
- [x] **Step 1: graph.handler**
|
||||
|
||||
```typescript
|
||||
export function registerGraphHandlers(registry: BookRegistryService): void {
|
||||
@@ -429,7 +429,7 @@ export function registerGraphHandlers(registry: BookRegistryService): void {
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: analytics.handler**
|
||||
- [x] **Step 2: analytics.handler**
|
||||
|
||||
```typescript
|
||||
export function registerAnalyticsHandlers(
|
||||
@@ -443,7 +443,7 @@ export function registerAnalyticsHandlers(
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: register.ts 串联**
|
||||
- [x] **Step 3: register.ts 串联**
|
||||
|
||||
```typescript
|
||||
const writingSessionRepo = new WritingSessionRepository(writingLogRepo.getDb())
|
||||
@@ -453,7 +453,7 @@ registerAnalyticsHandlers(books, writingLogs, writingSessionRepo, pomodoroDailyR
|
||||
registerChapterHandlers(books, writingLogs, writingSessions)
|
||||
```
|
||||
|
||||
- [ ] **Step 4: preload**
|
||||
- [x] **Step 4: preload**
|
||||
|
||||
```typescript
|
||||
graph: {
|
||||
@@ -466,7 +466,7 @@ analytics: {
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 5: build**
|
||||
- [x] **Step 5: build**
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
@@ -484,14 +484,14 @@ npm run build
|
||||
- Modify: `src/renderer/components/layout/EditorLayout.tsx`(挂载模态)
|
||||
- Modify: `src/renderer/styles/layout.css`
|
||||
|
||||
- [ ] **Step 1: 安装依赖**
|
||||
- [x] **Step 1: 安装依赖**
|
||||
|
||||
```bash
|
||||
npm install cytoscape cytoscape-cose-bilkent --legacy-peer-deps
|
||||
npm install -D @types/cytoscape --legacy-peer-deps
|
||||
```
|
||||
|
||||
- [ ] **Step 2: useGraphStore**
|
||||
- [x] **Step 2: useGraphStore**
|
||||
|
||||
```typescript
|
||||
interface GraphStore {
|
||||
@@ -504,7 +504,7 @@ interface GraphStore {
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: CharacterGraphModal 骨架**
|
||||
- [x] **Step 3: CharacterGraphModal 骨架**
|
||||
|
||||
```tsx
|
||||
export function CharacterGraphModal(): React.JSX.Element | null {
|
||||
@@ -532,9 +532,9 @@ export function CharacterGraphModal(): React.JSX.Element | null {
|
||||
|
||||
`initCytoscape` 注册 `cytoscape.use(coseBilkent)`,边颜色按 intimacy 映射 spec §6.1。
|
||||
|
||||
- [ ] **Step 4: EditorLayout 挂载 `<CharacterGraphModal />`**
|
||||
- [x] **Step 4: EditorLayout 挂载 `<CharacterGraphModal />`**
|
||||
|
||||
- [ ] **Step 5: build**
|
||||
- [x] **Step 5: build**
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
@@ -549,7 +549,7 @@ npm run build
|
||||
- Modify: `src/renderer/components/editor/TipTapEditor.tsx`(character setting 下方嵌入)
|
||||
- Modify: `src/renderer/components/setting/SettingList.tsx`(setting-open-graph 按钮)
|
||||
|
||||
- [ ] **Step 1: SettingRelationshipsEditor**
|
||||
- [x] **Step 1: SettingRelationshipsEditor**
|
||||
|
||||
当 `target.kind === 'setting'` 且 entry.type === 'character':
|
||||
|
||||
@@ -566,7 +566,7 @@ npm run build
|
||||
|
||||
保存调用 `window.electronAPI.graph.upsertRelationship`。
|
||||
|
||||
- [ ] **Step 2: SettingList 工具栏**
|
||||
- [x] **Step 2: SettingList 工具栏**
|
||||
|
||||
```tsx
|
||||
<button data-testid="setting-open-graph" onClick={() => useGraphStore.getState().openModal()}>
|
||||
@@ -574,7 +574,7 @@ npm run build
|
||||
</button>
|
||||
```
|
||||
|
||||
- [ ] **Step 3: build**
|
||||
- [x] **Step 3: build**
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
@@ -591,7 +591,7 @@ npm run build
|
||||
- Modify: `src/renderer/components/layout/EditorLayout.tsx`(chapter-pov-select)
|
||||
- Modify: `src/renderer/stores/useCockpitStore.ts`(可选:loadAnalytics)
|
||||
|
||||
- [ ] **Step 1: CockpitModal 并行加载**
|
||||
- [x] **Step 1: CockpitModal 并行加载**
|
||||
|
||||
```typescript
|
||||
useEffect(() => {
|
||||
@@ -603,7 +603,7 @@ useEffect(() => {
|
||||
}, [open, bookId, activeVolumeId])
|
||||
```
|
||||
|
||||
- [ ] **Step 2: CockpitAnalytics 组件**
|
||||
- [x] **Step 2: CockpitAnalytics 组件**
|
||||
|
||||
```tsx
|
||||
<div className="cockpit-analytics" data-testid="cockpit-analytics">
|
||||
@@ -618,7 +618,7 @@ useEffect(() => {
|
||||
</button>
|
||||
```
|
||||
|
||||
- [ ] **Step 3: chapter-pov-select**
|
||||
- [x] **Step 3: chapter-pov-select**
|
||||
|
||||
在 `#editor-statusbar` 当 `isChapterTarget`:
|
||||
|
||||
@@ -635,7 +635,7 @@ useEffect(() => {
|
||||
|
||||
`updatePov` → `chapter.update(bookId, chapterId, { povCharacterId })`.
|
||||
|
||||
- [ ] **Step 4: build**
|
||||
- [x] **Step 4: build**
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
@@ -650,9 +650,9 @@ npm run build
|
||||
- Modify: `public/locales/en/translation.json`
|
||||
- Modify: `src/renderer/styles/layout.css`
|
||||
|
||||
- [ ] **Step 1: 添加 spec §7 全部 i18n 键(graph / relationship / analytics / chapter.pov / cockpit.openGraph)**
|
||||
- [x] **Step 1: 添加 spec §7 全部 i18n 键(graph / relationship / analytics / chapter.pov / cockpit.openGraph)**
|
||||
|
||||
- [ ] **Step 2: CSS**
|
||||
- [x] **Step 2: CSS**
|
||||
|
||||
```css
|
||||
.character-graph-modal .modal-content { max-width: 95vw; width: 1100px; }
|
||||
@@ -664,7 +664,7 @@ npm run build
|
||||
.pov-bar, .volume-bar { height: 8px; background: var(--accent); border-radius: 4px; }
|
||||
```
|
||||
|
||||
- [ ] **Step 3: build**
|
||||
- [x] **Step 3: build**
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
@@ -681,7 +681,7 @@ npm run build
|
||||
- Modify: `README.md`
|
||||
- Modify: `src/renderer/components/settings/SettingsPage.tsx`
|
||||
|
||||
- [ ] **Step 1: E2E-GRAPH-01~04**
|
||||
- [x] **Step 1: E2E-GRAPH-01~04**
|
||||
|
||||
```typescript
|
||||
// 创建书 → 新建 character 设定 A/B → SettingRelationshipsEditor 添加关系
|
||||
@@ -690,14 +690,14 @@ npm run build
|
||||
// → graph-layout-circle click
|
||||
```
|
||||
|
||||
- [ ] **Step 2: E2E-ANALYTICS-01~02**
|
||||
- [x] **Step 2: E2E-ANALYTICS-01~02**
|
||||
|
||||
```typescript
|
||||
// E2E-ANALYTICS-01: 写作保存 → topbar-cockpit → cockpit-speed-today 非空
|
||||
// E2E-ANALYTICS-02: chapter-pov-select 选角色 → 刷新驾驶舱 → cockpit-pov-chart 含角色名
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 全量测试**
|
||||
- [x] **Step 3: 全量测试**
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
@@ -705,11 +705,11 @@ npm run test
|
||||
npm run test:e2e -- e2e/character-graph.spec.ts e2e/writing-analytics.spec.ts
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 版本 1.0.0**
|
||||
- [x] **Step 4: 版本 1.0.0**
|
||||
|
||||
`package.json` / `README.md` / Settings 关于页 → `v1.0.0`
|
||||
|
||||
- [ ] **Step 5: 勾选本 plan 全部 checkbox**
|
||||
- [x] **Step 5: 勾选本 plan 全部 checkbox**
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user