From 255f1a99a016f646a9ffc614b64f92329b6f0cae Mon Sep 17 00:00:00 2001 From: kun1h Date: Tue, 7 Jul 2026 18:53:49 +0800 Subject: [PATCH] docs: add P5.3 and P6.1 goals and injection history implementation plan for v0.9.0 Break down pomodoro, milestones, notifications, and knowledge injection logging into 11 tasks. Co-authored-by: Cursor --- ...026-07-07-bilin-p53-p61-goals-injection.md | 685 ++++++++++++++++++ 1 file changed, 685 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-07-bilin-p53-p61-goals-injection.md diff --git a/docs/superpowers/plans/2026-07-07-bilin-p53-p61-goals-injection.md b/docs/superpowers/plans/2026-07-07-bilin-p53-p61-goals-injection.md new file mode 100644 index 0000000..1e37758 --- /dev/null +++ b/docs/superpowers/plans/2026-07-07-bilin-p53-p61-goals-injection.md @@ -0,0 +1,685 @@ +# 笔临 P5.3 + P6.1 写作目标 + 知识注入历史 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.9.0:§8 番茄钟(15/25/45)、连更里程碑徽章(7/30/100)、日更/番茄/里程碑通知;P6.1 写作流三模式知识注入历史记录与知识库反向链接 UI。 + +**Architecture:** 主进程 `PomodoroService` + `AchievementService` + `GoalNotificationService` 操作全局 `writing_logs.sqlite` 扩展表;每书 schema v8 增加 `knowledge_injection_logs`;`interactive/auto/wizard` 的 `confirmContext` 成功后异步写 injection log;渲染进程状态栏番茄控件 + 驾驶舱成就区 + 知识库注入预览。番茄 tick 采用主进程 push `GOAL_NOTIFICATION` / `POMODORO_TICK` 事件。 + +**Tech Stack:** Electron 36、electron-vite、React 18、TypeScript、node:sqlite、Zustand、i18next、Vitest、Playwright + +**Spec:** `docs/superpowers/specs/2026-07-07-bilin-p53-p61-goals-injection-design.md` + +**E2E 番茄加速:** `BILIN_E2E=1` 且 `POMODORO_TEST_SEC=3` 时覆盖倒计时(仅测试,不暴露生产 UI) + +--- + +## File Map + +| 文件 | 职责 | +|------|------| +| `src/shared/types.ts` | `PomodoroState`、`WritingAchievement`、`KnowledgeInjectionLog`;扩展 `CockpitSummary`、`GlobalSettings` | +| `src/shared/ipc-channels.ts` | pomodoro / achievement / injection / push 通道 | +| `src/main/db/schema-v8.sql` | `knowledge_injection_logs` | +| `src/main/db/migrate.ts` | v8 | +| `src/main/db/repositories/writing-log.repo.ts` | 扩展 migrate:milestones + pomodoro_daily | +| `src/main/db/repositories/writing-milestone.repo.ts` | 成就 CRUD | +| `src/main/db/repositories/pomodoro-daily.repo.ts` | 番茄日统计 | +| `src/main/db/repositories/knowledge-injection.repo.ts` | 每书 injection logs | +| `src/main/services/pomodoro.service.ts` | 番茄状态机 | +| `src/main/services/achievement.service.ts` | 里程碑检测 | +| `src/main/services/goal-notification.service.ts` | Toast push + Notification | +| `src/main/services/knowledge-injection.service.ts` | 注入记录 | +| `src/main/services/writing-log.service.ts` | addToday 后触发 goal-met + milestones | +| `src/main/services/cockpit.service.ts` | achievements + pomodoroTodayCount | +| `src/main/ipc/handlers/pomodoro.handler.ts` | 番茄 IPC | +| `src/main/ipc/handlers/achievement.handler.ts` | 成就 IPC | +| `src/main/ipc/handlers/knowledge.handler.ts` | listInjections | +| `src/main/ipc/handlers/interactive.handler.ts` | confirmContext 挂钩 | +| `src/main/ipc/handlers/auto.handler.ts` | 同上 | +| `src/main/ipc/handlers/wizard.handler.ts` | 同上 | +| `src/main/ipc/register.ts` | 注入新服务 | +| `src/preload/index.ts` + `electron-api.d.ts` | 新 API + push 监听 | +| `src/renderer/components/layout/EditorLayout.tsx` | 番茄控件 + goal 通知 | +| `src/renderer/components/cockpit/CockpitModal.tsx` | 成就区 | +| `src/renderer/components/knowledge/KnowledgePanel.tsx` | 注入预览 | +| `src/renderer/components/knowledge/KnowledgeEditorDialog.tsx` | 完整历史 | +| `src/renderer/components/settings/SettingsPage.tsx` | 番茄/通知设置 | +| `src/renderer/stores/usePomodoroStore.ts` | 番茄 UI 状态 | +| `public/locales/*/translation.json` | P5.3/P6.1 文案 | +| `tests/main/pomodoro.test.ts` | UT-POM-* | +| `tests/main/achievement.test.ts` | UT-ACH-* / IT-GOAL-01 | +| `tests/main/knowledge-injection.test.ts` | UT/IT-INJ-* | +| `tests/main/migrate-v8.test.ts` | UT-MIG-08 | +| `e2e/writing-goals.spec.ts` | E2E-GOAL-* | +| `e2e/knowledge-injection-history.spec.ts` | E2E-INJ-01 | + +--- + +## Task 1: 类型 + IPC + GlobalSettings + +**Files:** +- Modify: `src/shared/types.ts` +- Modify: `src/shared/ipc-channels.ts` +- Modify: `src/main/services/global-settings.ts` +- Modify: `src/renderer/stores/useSettingsStore.ts` + +- [ ] **Step 1: 扩展 types** + +```typescript +export type PomodoroDuration = 15 | 25 | 45 +export type WritingMilestone = 'streak_7' | 'streak_30' | 'streak_100' +export type InjectionFlowMode = 'interactive' | 'auto' | 'wizard' + +export interface PomodoroState { + running: boolean + paused: boolean + remainingSec: number + bookId: string | null +} + +export interface WritingAchievement { + milestone: WritingMilestone + unlockedAt: string +} + +export interface KnowledgeInjectionLog { + id: string + knowledgeEntryId: string + flowMode: InjectionFlowMode + flowId: string + chapterId?: string + injectedAt: string +} + +export interface GoalNotificationPayload { + kind: 'daily_met' | 'milestone' | 'pomodoro_complete' | 'pomodoro_cancelled_book_switch' + messageKey: string + messageParams?: Record +} +``` + +扩展 `CockpitSummary`: + +```typescript +achievements?: WritingAchievement[] +pomodoroTodayCount?: number +``` + +扩展 `GlobalSettings`: + +```typescript +pomodoroDurationMinutes?: PomodoroDuration +enableSystemNotifications?: boolean +enableGoalNotifications?: boolean +dailyGoalNotifiedDate?: string // YYYY-MM-DD,防重复达标 Toast +``` + +- [ ] **Step 2: IPC 通道** + +```typescript +POMODORO_START: 'pomodoro:start', +POMODORO_PAUSE: 'pomodoro:pause', +POMODORO_RESUME: 'pomodoro:resume', +POMODORO_CANCEL: 'pomodoro:cancel', +POMODORO_GET_STATE: 'pomodoro:getState', +ACHIEVEMENT_LIST: 'achievement:list', +KNOWLEDGE_LIST_INJECTIONS: 'knowledge:listInjections', +POMODORO_TICK: 'pomodoro:tick', // main → renderer push +GOAL_NOTIFICATION: 'goal:notification', // main → renderer push +``` + +- [ ] **Step 3: global-settings 默认值** + +```typescript +pomodoroDurationMinutes: 25, +enableSystemNotifications: false, +enableGoalNotifications: true, +``` + +- [ ] **Step 4: useSettingsStore 同步** + +- [ ] **Step 5: build** + +```bash +npm run build +``` + +--- + +## Task 2: 全局 sqlite 扩展 + Milestone/Pomodoro Repos + +**Files:** +- Modify: `src/main/db/repositories/writing-log.repo.ts` +- Create: `src/main/db/repositories/writing-milestone.repo.ts` +- Create: `src/main/db/repositories/pomodoro-daily.repo.ts` +- Create: `tests/main/achievement.test.ts`(先写 milestone repo 部分) + +- [ ] **Step 1: writing-log.repo migrate 扩展** + +在 `migrate()` 追加: + +```sql +CREATE TABLE IF NOT EXISTS writing_milestones ( + book_id TEXT NOT NULL, milestone TEXT NOT NULL, unlocked_at TEXT NOT NULL DEFAULT (datetime('now')), + PRIMARY KEY (book_id, milestone) +); +CREATE TABLE IF NOT EXISTS pomodoro_daily ( + date TEXT NOT NULL, book_id TEXT NOT NULL, + completed_count INTEGER NOT NULL DEFAULT 0, total_words INTEGER NOT NULL DEFAULT 0, + PRIMARY KEY (date, book_id) +); +``` + +- [ ] **Step 2: WritingMilestoneRepository** + +```typescript +export class WritingMilestoneRepository { + constructor(private db: DatabaseSync) {} + list(bookId: string): WritingAchievement[] { /* SELECT */ } + has(bookId: string, milestone: WritingMilestone): boolean { /* ... */ } + unlock(bookId: string, milestone: WritingMilestone): WritingAchievement { /* INSERT OR IGNORE */ } +} +``` + +- [ ] **Step 3: PomodoroDailyRepository** + +```typescript +recordComplete(bookId: string, wordDelta: number, date?: string): void { + // INSERT ... ON CONFLICT DO UPDATE completed_count += 1, total_words += wordDelta +} +getTodayCount(bookId: string): number +``` + +- [ ] **Step 4: UT-ACH repo smoke** + +```typescript +it('UT-ACH-01: unlock streak_7 once', () => { + const repo = new WritingMilestoneRepository(db) + repo.unlock('b1', 'streak_7') + expect(repo.has('b1', 'streak_7')).toBe(true) +}) +``` + +- [ ] **Step 5: 运行** + +```bash +npm run test -- tests/main/achievement.test.ts -t "UT-ACH-01" +``` + +--- + +## Task 3: schema v8 + KnowledgeInjectionRepository + +**Files:** +- Create: `src/main/db/schema-v8.sql` +- Modify: `src/main/db/migrate.ts` +- Create: `src/main/db/repositories/knowledge-injection.repo.ts` +- Create: `tests/main/migrate-v8.test.ts` +- Create: `tests/main/knowledge-injection.test.ts` + +- [ ] **Step 1: schema-v8.sql**(见 spec §3.2) + +- [ ] **Step 2: migrate.ts** + +```typescript +import schemaV8 from './schema-v8.sql?raw' +const CURRENT_VERSION = 8 + +if (current < 8) { + db.exec(schemaV8) + db.prepare('INSERT INTO schema_version (version, description) VALUES (?, ?)').run(8, 'P5.3 P6.1 goals injection') +} +``` + +- [ ] **Step 3: KnowledgeInjectionRepository** + +```typescript +insertBatch(rows: Array>): void +listForEntry(entryId: string, limit = 50): KnowledgeInjectionLog[] +``` + +- [ ] **Step 4: UT-MIG-08 + UT-INJ-01** + +```typescript +it('UT-MIG-08: v7 database migrates to v8 with injection table', () => { /* ... */ }) +it('UT-INJ-01: insertBatch creates 3 rows', () => { /* ... */ }) +``` + +- [ ] **Step 5: 运行** + +```bash +npm run test -- tests/main/migrate-v8.test.ts tests/main/knowledge-injection.test.ts +``` + +--- + +## Task 4: PomodoroService + +**Files:** +- Create: `src/main/services/pomodoro.service.ts` +- Create: `tests/main/pomodoro.test.ts` + +- [ ] **Step 1: PomodoroService 实现** + +```typescript +export class PomodoroService { + private state: PomodoroState = { running: false, paused: false, remainingSec: 0, bookId: null } + private wordSnapshot = 0 + private timer: NodeJS.Timeout | null = null + + constructor( + private writingLogs: WritingLogService, + private pomodoroDaily: PomodoroDailyRepository, + private settings: GlobalSettingsService, + private notify: GoalNotificationService, + private pushTick: (state: PomodoroState) => void + ) {} + + start(bookId: string): PomodoroState { + if (this.state.running && this.state.bookId !== bookId) this.cancel() + const duration = this.resolveDurationSec() + this.wordSnapshot = this.writingLogs.getStats(bookId).todayWords + this.state = { running: true, paused: false, remainingSec: duration, bookId } + this.startTimer() + return this.state + } + + private resolveDurationSec(): number { + if (process.env.BILIN_E2E === '1' && process.env.POMODORO_TEST_SEC) { + return Number(process.env.POMODORO_TEST_SEC) || 3 + } + const mins = this.settings.get().pomodoroDurationMinutes ?? 25 + return mins * 60 + } + + private complete(): void { + const bookId = this.state.bookId! + const delta = Math.max(0, this.writingLogs.getStats(bookId).todayWords - this.wordSnapshot) + this.pomodoroDaily.recordComplete(bookId, delta) + this.notify.notifyPomodoroComplete(bookId, delta) + this.reset() + } + // pause, resume, cancel, getState, tick... +} +``` + +- [ ] **Step 2: UT-POM-01 / UT-POM-02** + +```typescript +it('UT-POM-01: complete records word delta', () => { /* mock writingLogs */ }) +it('UT-POM-02: start different book cancels previous', () => { /* ... */ }) +``` + +- [ ] **Step 3: 运行** + +```bash +npm run test -- tests/main/pomodoro.test.ts +``` + +--- + +## Task 5: GoalNotificationService + AchievementService + +**Files:** +- Create: `src/main/services/goal-notification.service.ts` +- Create: `src/main/services/achievement.service.ts` +- Modify: `src/main/services/writing-log.service.ts` +- Modify: `tests/main/achievement.test.ts` + +- [ ] **Step 1: GoalNotificationService** + +```typescript +export class GoalNotificationService { + constructor( + private settings: GlobalSettingsService, + private getMainWindow: () => BrowserWindow | null + ) {} + + pushToast(payload: GoalNotificationPayload): void { + this.getMainWindow()?.webContents.send(IPC.GOAL_NOTIFICATION, payload) + } + + maybeSystemNotify(title: string, body: string): void { + if (!this.settings.get().enableSystemNotifications) return + try { + new Notification({ title, body }).show() + } catch { /* ignore permission denied */ } + } + + notifyDailyGoalMet(bookId: string, current: number, goal: number): void { /* ... */ } + notifyMilestone(milestone: WritingMilestone): void { /* ... */ } + notifyPomodoroComplete(bookId: string, words: number): void { /* ... */ } +} +``` + +- [ ] **Step 2: AchievementService** + +```typescript +checkMilestones(bookId: string): WritingAchievement[] { + const { dailyWordGoal } = this.settings.get() + if (dailyWordGoal <= 0) return [] + const streak = this.writingLogs.getStats(bookId).streakDays + const unlocked: WritingAchievement[] = [] + for (const [threshold, m] of [[7,'streak_7'],[30,'streak_30'],[100,'streak_100']] as const) { + if (streak >= threshold && !this.repo.has(bookId, m)) { + const a = this.repo.unlock(bookId, m) + this.notify.notifyMilestone(m) + unlocked.push(a) + } + } + return unlocked +} +``` + +- [ ] **Step 3: WritingLogService.addToday 扩展** + +`addToday` 返回后: + +```typescript +const stats = this.getStats(bookId) +if (stats.dailyGoal > 0 && stats.todayWords >= stats.dailyGoal) { + this.goalNotifications.maybeNotifyDailyMet(bookId, stats) // 内部检查 dailyGoalNotifiedDate +} +this.achievementService.checkMilestones(bookId) +``` + +- [ ] **Step 4: UT-ACH-02 + IT-GOAL-01** + +- [ ] **Step 5: 运行** + +```bash +npm run test -- tests/main/achievement.test.ts +``` + +--- + +## Task 6: KnowledgeInjectionService + confirmContext 挂钩 + +**Files:** +- Create: `src/main/services/knowledge-injection.service.ts` +- Modify: `src/main/ipc/handlers/interactive.handler.ts` +- Modify: `src/main/ipc/handlers/auto.handler.ts` +- Modify: `src/main/ipc/handlers/wizard.handler.ts` +- Modify: `tests/main/knowledge-injection.test.ts` + +- [ ] **Step 1: KnowledgeInjectionService** + +```typescript +export class KnowledgeInjectionService { + record(bookDb: SqliteDb, input: { + knowledgeIds: string[] + flowMode: InjectionFlowMode + flowId: string + chapterId?: string + }): void { + if (input.knowledgeIds.length === 0) return + const knowledgeRepo = new KnowledgeRepository(bookDb) + const injectionRepo = new KnowledgeInjectionRepository(bookDb) + const rows = input.knowledgeIds + .filter((id) => knowledgeRepo.get(id)?.status === 'approved') + .map((id) => ({ knowledgeEntryId: id, flowMode: input.flowMode, flowId: input.flowId, chapterId: input.chapterId })) + injectionRepo.insertBatch(rows) + } +} +``` + +- [ ] **Step 2: interactive.handler 挂钩** + +在 `INTERACTIVE_CONFIRM_CONTEXT` 的 `buildFlowContextPayload` 成功后: + +```typescript +try { + new KnowledgeInjectionService().record(registry.getDb(bookId), { + knowledgeIds: binding.knowledgeIds, + flowMode: 'interactive', + flowId, + chapterId: registry.getMeta(bookId)?.lastChapterId ?? undefined + }) +} catch (err) { + console.error('[knowledge-injection]', err) +} +``` + +- [ ] **Step 3: auto / wizard 同理** + +- [ ] **Step 4: IT-INJ-01** + +直接调用 handler 或 service + 内存 DB,断言 `knowledge_injection_logs` 行数。 + +- [ ] **Step 5: 运行** + +```bash +npm run test -- tests/main/knowledge-injection.test.ts +``` + +--- + +## Task 7: IPC + register + preload + Cockpit 扩展 + +**Files:** +- Create: `src/main/ipc/handlers/pomodoro.handler.ts` +- Create: `src/main/ipc/handlers/achievement.handler.ts` +- Modify: `src/main/ipc/handlers/knowledge.handler.ts` +- Modify: `src/main/ipc/register.ts` +- Modify: `src/main/services/cockpit.service.ts` +- Modify: `src/preload/index.ts` +- Modify: `src/shared/electron-api.d.ts` + +- [ ] **Step 1: pomodoro.handler + achievement.handler** + +- [ ] **Step 2: knowledge.handler** + +```typescript +ipcMain.handle(IPC.KNOWLEDGE_LIST_INJECTIONS, (_e, { bookId, entryId, limit }) => + wrap(() => new KnowledgeInjectionService(registry.getDb(bookId)).listForEntry(entryId, limit)) +) +``` + +- [ ] **Step 3: register.ts 注入** + +```typescript +const goalNotify = new GoalNotificationService(settings, () => getMainWindow()) +const achievementService = new AchievementService(writingLogs, milestoneRepo, settings, goalNotify) +writingLogs.setAchievementHooks(achievementService, goalNotify) +const pomodoro = new PomodoroService(writingLogs, pomodoroDailyRepo, settings, goalNotify, pushTick) +registerPomodoroHandlers(pomodoro) +registerAchievementHandlers(achievementService) +``` + +- [ ] **Step 4: preload** + +```typescript +pomodoro: { + start: (bookId: string) => ipcRenderer.invoke(IPC.POMODORO_START, { bookId }), + pause: () => ipcRenderer.invoke(IPC.POMODORO_PAUSE), + resume: () => ipcRenderer.invoke(IPC.POMODORO_RESUME), + cancel: () => ipcRenderer.invoke(IPC.POMODORO_CANCEL), + getState: () => ipcRenderer.invoke(IPC.POMODORO_GET_STATE) +}, +onPomodoroTick: (cb) => { ipcRenderer.on(IPC.POMODORO_TICK, cb); return () => ... }, +onGoalNotification: (cb) => { ipcRenderer.on(IPC.GOAL_NOTIFICATION, cb); return () => ... }, +``` + +- [ ] **Step 5: CockpitService 扩展** + +```typescript +achievements: this.achievementService.listAchievements(bookId), +pomodoroTodayCount: this.pomodoroDaily.getTodayCount(bookId), +``` + +- [ ] **Step 6: build** + +```bash +npm run build +``` + +--- + +## Task 8: 状态栏番茄 + Goal 通知监听 + +**Files:** +- Create: `src/renderer/stores/usePomodoroStore.ts` +- Modify: `src/renderer/components/layout/EditorLayout.tsx` +- Modify: `src/renderer/App.tsx`(注册 onGoalNotification 全局 Toast) +- Modify: `src/renderer/styles/layout.css` + +- [ ] **Step 1: usePomodoroStore** + +订阅 `onPomodoroTick`;actions:`start/pause/resume/cancel`。 + +- [ ] **Step 2: EditorLayout 状态栏控件** + +```tsx +
+ {!running ? ( + + ) : ( + <> + + {formatMmSs(remainingSec)} + + + )} +
+``` + +- [ ] **Step 3: App.tsx goal notification** + +```typescript +useEffect(() => { + return window.electronAPI.onGoalNotification((payload) => { + showToast(t(payload.messageKey, payload.messageParams)) + }) +}, []) +``` + +- [ ] **Step 4: build 验证** + +```bash +npm run build +``` + +--- + +## Task 9: 驾驶舱成就区 + 设置页 + +**Files:** +- Modify: `src/renderer/components/cockpit/CockpitModal.tsx` +- Modify: `src/renderer/components/settings/SettingsPage.tsx` +- Modify: `public/locales/zh-CN/translation.json` +- Modify: `public/locales/en/translation.json` + +- [ ] **Step 1: CockpitModal 成就区** + +```tsx +{summary.achievements && summary.achievements.length > 0 && ( +
...
+)} +
+ {t('cockpit.pomodoroToday', { count: summary.pomodoroTodayCount ?? 0 })} +
+``` + +- [ ] **Step 2: SettingsPage 三项设置** + +`settings-pomodoro-duration` / `settings-system-notifications` / `settings-goal-notifications` + +- [ ] **Step 3: i18n**(spec §7 全部键,中英) + +- [ ] **Step 4: build** + +```bash +npm run build +``` + +--- + +## Task 10: 知识库注入历史 UI + +**Files:** +- Modify: `src/renderer/components/knowledge/KnowledgePanel.tsx` +- Modify: `src/renderer/components/knowledge/KnowledgeEditorDialog.tsx` +- Modify: `src/renderer/stores/useKnowledgeStore.ts` + +- [ ] **Step 1: KnowledgePanel 加载注入预览** + +条目 render 时调用 `knowledge.listInjections(bookId, entry.id, 3)`(或 batch 加载 map)。 + +```tsx +
+ {injections.map((log) => ( +
{t('knowledge.injectionEntry', { date, mode, chapter })}
+ ))} +
+``` + +- [ ] **Step 2: KnowledgeEditorDialog 完整历史** + +`data-testid="knowledge-injection-history"`;章节名点击 `switchTarget({ kind:'chapter', id })`。 + +- [ ] **Step 3: build** + +```bash +npm run build +``` + +--- + +## Task 11: E2E + v0.9.0 交付 + +**Files:** +- Create: `e2e/writing-goals.spec.ts` +- Create: `e2e/knowledge-injection-history.spec.ts` +- Modify: `package.json` +- Modify: `README.md` +- Modify: `tests/main/migrate-v7.test.ts` 等(若 CURRENT_VERSION→8,期望版本改为 8) + +- [ ] **Step 1: E2E-GOAL-01~03** + +- E2E-GOAL-01:设 dailyWordGoal=100,打字保存,断言 toast / status-daily-goal +- E2E-GOAL-02:通过测试 hook 或 seed API 写入 streak_7 milestone,打开驾驶舱断言 `cockpit-achievements` +- E2E-GOAL-03:`POMODORO_TEST_SEC=3`,启动番茄,等待完成 Toast + +- [ ] **Step 2: E2E-INJ-01** + +向导 confirm 知识上下文(可 stub 空 AI),断言 `knowledge-injection-preview-*` 可见。 + +- [ ] **Step 3: 全量测试** + +```bash +npm run build +npm run test +npm run test:e2e -- e2e/writing-goals.spec.ts e2e/knowledge-injection-history.spec.ts +``` + +- [ ] **Step 4: 版本 0.9.0** + +`package.json`、`README.md`、`SettingsPage` about 版本号。 + +- [ ] **Step 5: 更新 migrate-v2~v7 测试期望版本 → 8**(与 P5.2 v7 时同样处理) + +--- + +## Spec Coverage Checklist + +| Spec 要求 | Task | +|-----------|------| +| 番茄钟 15/25/45 + 状态栏 UI | Task 1, 4, 8 | +| 番茄完成字数 + pomodoro_daily | Task 2, 4 | +| 7/30/100 里程碑 + 成就区 | Task 2, 5, 9 | +| Toast + 系统通知 + 设置开关 | Task 1, 5, 8, 9 | +| 日更首次达标通知 | Task 5 | +| knowledge_injection_logs v8 | Task 3 | +| 三模式 confirmContext 挂钩 | Task 6 | +| 知识库注入预览 + 完整历史 | Task 10 | +| UT/IT/E2E | Task 2–6, 11 | +| v0.9.0 DoD | Task 11 | + +--- + +## Execution Notes + +- `WritingLogService` 需轻量 refactor 接受 `AchievementService` / `GoalNotificationService` 回调(constructor 注入或 setter,避免循环依赖:Achievement 依赖 WritingLog,WritingLog 回调 Achievement) +- 切换书籍时:`useBookStore` 订阅 `currentBookId` 变化 → 若 pomodoro running 且 bookId 不同 → `cancel()` + toast `pomodoro.switchedBook` +- 注入历史 E2E **禁止 Mock AI** 指生成类测试;confirmContext 本身不调用 AI,可纯 UI 流程