feat: ship v1.2.0 Wave 1 foundation with book settings, chapter management, and focus mode

Deliver schema v9, book/chapter IPC extensions, BookSettingsTab, chapter meta/tags,
AI session menu, focus mode and TTS, template context enrich, and Wave 1 E2E coverage.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-08 15:22:58 +08:00
parent 64da95f7e1
commit fe127ec3ed
57 changed files with 1963 additions and 139 deletions
+23 -14
View File
@@ -1,6 +1,8 @@
import { ipcMain } from 'electron'
import { ipcMain, dialog, app } from 'electron'
import { copyFileSync, mkdirSync } from 'fs'
import { extname, join } from 'path'
import { IPC } from '../../../shared/ipc-channels'
import type { BookMeta, CreateBookParams } from '../../../shared/types'
import type { CreateBookParams, UpdateBookMetaParams } from '../../../shared/types'
import { BookRegistryService } from '../../services/book-registry'
import type { WritingSessionService } from '../../services/writing-session.service'
import { wrap } from '../result'
@@ -35,17 +37,24 @@ export function registerBookHandlers(
ipcMain.handle(
IPC.BOOK_UPDATE_META,
(
_event,
{
bookId,
...patch
}: {
bookId: string
lastChapterId?: string | null
lastOpenedAt?: string
status?: BookMeta['status']
}
) => wrap(() => registry.updateMeta(bookId, patch))
(_event, { bookId, ...patch }: { bookId: string } & UpdateBookMetaParams) =>
wrap(() => registry.updateMeta(bookId, patch))
)
ipcMain.handle(IPC.BOOK_PICK_COVER, (_event, { bookId }: { bookId: string }) =>
wrap(async () => {
const { canceled, filePaths } = await dialog.showOpenDialog({
filters: [{ name: 'Images', extensions: ['jpg', 'jpeg', 'png', 'webp'] }],
properties: ['openFile']
})
if (canceled || !filePaths[0]) return null
const src = filePaths[0]
const ext = extname(src).toLowerCase() || '.jpg'
const coversDir = join(app.getPath('userData'), 'covers')
mkdirSync(coversDir, { recursive: true })
const dest = join(coversDir, `${bookId}${ext}`)
copyFileSync(src, dest)
return registry.updateMeta(bookId, { coverPath: dest })
})
)
}