feat: ship v0.9.0 with pomodoro, achievements, and injection history
Add pomodoro timer with goal notifications, writing streak milestones, and knowledge injection logging with UI previews across AI writing flows. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import { create } from 'zustand'
|
||||
import type {
|
||||
CreateKnowledgeInput,
|
||||
KnowledgeEntry,
|
||||
KnowledgeInjectionLog,
|
||||
KnowledgeStatus,
|
||||
KnowledgeType
|
||||
} from '@shared/types'
|
||||
@@ -17,7 +18,9 @@ interface KnowledgeState {
|
||||
forgottenOnly: boolean
|
||||
editorOpen: boolean
|
||||
editingId: string | null
|
||||
injectionPreviews: Record<string, KnowledgeInjectionLog[]>
|
||||
load: (bookId: string) => Promise<void>
|
||||
loadInjectionPreviews: (bookId: string, entryIds: string[]) => Promise<void>
|
||||
setTab: (tab: KnowledgeTab) => void
|
||||
openForgottenFilter: () => void
|
||||
openEditor: (id?: string | null) => void
|
||||
@@ -46,6 +49,7 @@ export const useKnowledgeStore = create<KnowledgeState>((set, get) => ({
|
||||
forgottenOnly: false,
|
||||
editorOpen: false,
|
||||
editingId: null,
|
||||
injectionPreviews: {},
|
||||
load: async (bookId) => {
|
||||
const [entries, stats] = await Promise.all([
|
||||
ipcCall(() => window.electronAPI.knowledge.list(bookId)),
|
||||
@@ -53,6 +57,21 @@ export const useKnowledgeStore = create<KnowledgeState>((set, get) => ({
|
||||
])
|
||||
set({ entries, stats })
|
||||
},
|
||||
loadInjectionPreviews: async (bookId, entryIds) => {
|
||||
if (entryIds.length === 0) {
|
||||
set({ injectionPreviews: {} })
|
||||
return
|
||||
}
|
||||
const pairs = await Promise.all(
|
||||
entryIds.map(async (id) => {
|
||||
const logs = await ipcCall(() =>
|
||||
window.electronAPI.knowledge.listInjections(bookId, id, 3)
|
||||
)
|
||||
return [id, logs] as const
|
||||
})
|
||||
)
|
||||
set({ injectionPreviews: Object.fromEntries(pairs) })
|
||||
},
|
||||
setTab: (tab) => set({ tab, forgottenOnly: tab === 'foreshadow' ? get().forgottenOnly : false }),
|
||||
openForgottenFilter: () => {
|
||||
useAppStore.getState().setRightPanel('knowledge')
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import { create } from 'zustand'
|
||||
import type { PomodoroState } from '@shared/types'
|
||||
import { ipcCall } from '@renderer/lib/ipc-client'
|
||||
|
||||
interface PomodoroStore extends PomodoroState {
|
||||
tickUnsub: (() => void) | null
|
||||
init: () => Promise<void>
|
||||
start: (bookId: string) => Promise<void>
|
||||
pause: () => Promise<void>
|
||||
resume: () => Promise<void>
|
||||
cancel: () => Promise<void>
|
||||
applyState: (state: PomodoroState) => void
|
||||
}
|
||||
|
||||
const idle: PomodoroState = { running: false, paused: false, remainingSec: 0, bookId: null }
|
||||
|
||||
export const usePomodoroStore = create<PomodoroStore>((set, get) => ({
|
||||
...idle,
|
||||
tickUnsub: null,
|
||||
init: async () => {
|
||||
const state = await ipcCall(() => window.electronAPI.pomodoro.getState())
|
||||
set(state)
|
||||
if (!get().tickUnsub) {
|
||||
const unsub = window.electronAPI.onPomodoroTick((next) => {
|
||||
set(next)
|
||||
})
|
||||
set({ tickUnsub: unsub })
|
||||
}
|
||||
},
|
||||
applyState: (state) => set(state),
|
||||
start: async (bookId) => {
|
||||
const state = await ipcCall(() => window.electronAPI.pomodoro.start(bookId))
|
||||
set(state)
|
||||
},
|
||||
pause: async () => {
|
||||
const state = await ipcCall(() => window.electronAPI.pomodoro.pause())
|
||||
set(state)
|
||||
},
|
||||
resume: async () => {
|
||||
const state = await ipcCall(() => window.electronAPI.pomodoro.resume())
|
||||
set(state)
|
||||
},
|
||||
cancel: async () => {
|
||||
const state = await ipcCall(() => window.electronAPI.pomodoro.cancel())
|
||||
set(state)
|
||||
}
|
||||
}))
|
||||
|
||||
export function formatPomodoroTime(sec: number): string {
|
||||
const m = Math.floor(sec / 60)
|
||||
const s = sec % 60
|
||||
return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`
|
||||
}
|
||||
@@ -26,6 +26,9 @@ export const useSettingsStore = create<SettingsState>((set, get) => ({
|
||||
knowledgeSuggestTopN: 8,
|
||||
knowledgeAutoExtract: true,
|
||||
knowledgeExtractConfidenceThreshold: 0.8,
|
||||
pomodoroDurationMinutes: 25,
|
||||
enableSystemNotifications: false,
|
||||
enableGoalNotifications: true,
|
||||
loaded: false,
|
||||
load: async () => {
|
||||
const data = await ipcCall(() => window.electronAPI.settings.get())
|
||||
|
||||
Reference in New Issue
Block a user