+
+ started
+ tool
+ host
+ summary
+ turns
+ tok
+ cwd
+
+ {sessions.map((s, i) => {
+ const isCursor = i === cursor
+ return (
+
{ onCursor(i); onOpen(s) }}
+ onMouseEnter={() => onCursor(i)}
+ >
+ {formatStarted(s.started)}
+
+
+ {' '}{s.tool}
+
+ {s.host}
+ {s.summary}
+ {s.turns}
+ {formatTok(s.tokensIn + s.tokensOut)}
+ {s.cwd}
+
+ )
+ })}
+
+ )
+}
diff --git a/web/src/features/home/useHomeCursor.ts b/web/src/features/home/useHomeCursor.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d4bcbaad2465b19f4908082c6ce699beec1675c6
--- /dev/null
+++ b/web/src/features/home/useHomeCursor.ts
@@ -0,0 +1,37 @@
+import { useState, useCallback } from 'react'
+
+export interface HomeCursor {
+ cursor: number
+ move(d: 1 | -1): void
+ activate(): void
+ jumpTo(i: number): void
+}
+
+export function useHomeCursor(
+ sessionCount: number,
+ onActivate: (idx: number) => void,
+): HomeCursor {
+ const [cursor, setCursor] = useState(0)
+
+ const move = useCallback(
+ (d: 1 | -1) => {
+ if (sessionCount === 0) return
+ setCursor(c => Math.max(0, Math.min(sessionCount - 1, c + d)))
+ },
+ [sessionCount],
+ )
+
+ const activate = useCallback(() => {
+ onActivate(cursor)
+ }, [cursor, onActivate])
+
+ const jumpTo = useCallback(
+ (i: number) => {
+ if (sessionCount === 0) return
+ setCursor(Math.max(0, Math.min(sessionCount - 1, i)))
+ },
+ [sessionCount],
+ )
+
+ return { cursor, move, activate, jumpTo }
+}
diff --git a/web/src/features/home/useSessions.ts b/web/src/features/home/useSessions.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a676c58a9737cc671f71144241c5a74501403b9c
--- /dev/null
+++ b/web/src/features/home/useSessions.ts
@@ -0,0 +1,54 @@
+import { useQuery } from '@tanstack/react-query'
+import type { UseQueryResult } from '@tanstack/react-query'
+import { apiFetch } from '../../api/client'
+import { adaptSession } from '../../api/adapters'
+import type { Session, SessionDTO, Tool, Host } from '../../api/adapters'
+
+export interface HomeFilters {
+ since?: '1d' | '7d' | '30d' | '90d' | 'all'
+ tool?: Tool
+ host?: Host
+}
+
+interface SessionsResponse {
+ sessions: SessionDTO[]
+ limit: number
+ offset: number
+}
+
+function sinceToEpoch(since: string): number {
+ const now = Math.floor(Date.now() / 1000)
+ switch (since) {
+ case '1d': return now - 1 * 86400
+ case '7d': return now - 7 * 86400
+ case '30d': return now - 30 * 86400
+ case '90d': return now - 90 * 86400
+ default: return 0
+ }
+}
+
+export function useSessions(filters: HomeFilters): UseQueryResult