diff --git a/src/core/services/ipc.test.ts b/src/core/services/ipc.test.ts index 6806f32..e7013bf 100644 --- a/src/core/services/ipc.test.ts +++ b/src/core/services/ipc.test.ts @@ -439,6 +439,51 @@ test('registerIpcHandlers validates and clamps stats request limits', async () = ]); }); +test('registerIpcHandlers requests the full timeline when no limit is provided', async () => { + const { registrar, handlers } = createFakeIpcRegistrar(); + const calls: Array<[string, number | undefined, number]> = []; + + registerIpcHandlers( + createRegisterIpcDeps({ + immersionTracker: { + recordYomitanLookup: () => {}, + getSessionSummaries: async () => [], + getDailyRollups: async () => [], + getMonthlyRollups: async () => [], + getQueryHints: async () => ({ + totalSessions: 0, + activeSessions: 0, + episodesToday: 0, + activeAnimeCount: 0, + totalCards: 0, + totalActiveMin: 0, + activeDays: 0, + totalEpisodesWatched: 0, + totalAnimeCompleted: 0, + }), + getSessionTimeline: async (sessionId: number, limit?: number) => { + calls.push(['timeline', limit, sessionId]); + return []; + }, + getSessionEvents: async () => [], + getVocabularyStats: async () => [], + getKanjiStats: async () => [], + getMediaLibrary: async () => [], + getMediaDetail: async () => null, + getMediaSessions: async () => [], + getMediaDailyRollups: async () => [], + getCoverArt: async () => null, + markActiveVideoWatched: async () => false, + }, + }), + registrar, + ); + + await handlers.handle.get(IPC_CHANNELS.request.statsGetSessionTimeline)!({}, 7, undefined); + + assert.deepEqual(calls, [['timeline', undefined, 7]]); +}); + test('registerIpcHandlers ignores malformed fire-and-forget payloads', () => { const { registrar, handlers } = createFakeIpcRegistrar(); const saves: unknown[] = []; diff --git a/src/core/services/ipc.ts b/src/core/services/ipc.ts index 8fca6be..cc7749d 100644 --- a/src/core/services/ipc.ts +++ b/src/core/services/ipc.ts @@ -517,7 +517,7 @@ export function registerIpcHandlers(deps: IpcServiceDeps, ipc: IpcMainRegistrar async (_event, sessionId: unknown, limit: unknown) => { const parsedSessionId = parsePositiveInteger(sessionId); if (parsedSessionId === null) return []; - const parsedLimit = parsePositiveIntLimit(limit, 200, 1000); + const parsedLimit = limit === undefined ? undefined : parsePositiveIntLimit(limit, 200, 1000); return deps.immersionTracker?.getSessionTimeline(parsedSessionId, parsedLimit) ?? []; }, ); diff --git a/src/core/services/stats-server.ts b/src/core/services/stats-server.ts index c458e39..3d88b01 100644 --- a/src/core/services/stats-server.ts +++ b/src/core/services/stats-server.ts @@ -189,7 +189,8 @@ export function createStatsApp( app.get('/api/stats/sessions/:id/timeline', async (c) => { const id = parseIntQuery(c.req.param('id'), 0); if (id <= 0) return c.json([], 400); - const limit = parseIntQuery(c.req.query('limit'), 200, 1000); + const rawLimit = c.req.query('limit'); + const limit = rawLimit === undefined ? undefined : parseIntQuery(rawLimit, 200, 1000); const timeline = await tracker.getSessionTimeline(id, limit); return c.json(timeline); }); diff --git a/stats/src/lib/api-client.ts b/stats/src/lib/api-client.ts index 1e0685f..1919022 100644 --- a/stats/src/lib/api-client.ts +++ b/stats/src/lib/api-client.ts @@ -70,8 +70,12 @@ export const apiClient = { getMonthlyRollups: (limit = 24) => fetchJson(`/api/stats/monthly-rollups?limit=${limit}`), getSessions: (limit = 50) => fetchJson(`/api/stats/sessions?limit=${limit}`), - getSessionTimeline: (id: number, limit = 200) => - fetchJson(`/api/stats/sessions/${id}/timeline?limit=${limit}`), + getSessionTimeline: (id: number, limit?: number) => + fetchJson( + limit === undefined + ? `/api/stats/sessions/${id}/timeline` + : `/api/stats/sessions/${id}/timeline?limit=${limit}`, + ), getSessionEvents: (id: number, limit = 500) => fetchJson(`/api/stats/sessions/${id}/events?limit=${limit}`), getSessionKnownWordsTimeline: (id: number) => diff --git a/stats/src/lib/ipc-client.ts b/stats/src/lib/ipc-client.ts index 083411a..bc46df7 100644 --- a/stats/src/lib/ipc-client.ts +++ b/stats/src/lib/ipc-client.ts @@ -83,7 +83,7 @@ export const ipcClient = { getDailyRollups: (limit = 60) => getIpc().getDailyRollups(limit), getMonthlyRollups: (limit = 24) => getIpc().getMonthlyRollups(limit), getSessions: (limit = 50) => getIpc().getSessions(limit), - getSessionTimeline: (id: number, limit = 200) => getIpc().getSessionTimeline(id, limit), + getSessionTimeline: (id: number, limit?: number) => getIpc().getSessionTimeline(id, limit), getSessionEvents: (id: number, limit = 500) => getIpc().getSessionEvents(id, limit), getVocabulary: (limit = 100) => getIpc().getVocabulary(limit), getWordOccurrences: (headword: string, word: string, reading: string, limit = 50, offset = 0) =>