M web/src/api/adapters.test.ts => web/src/api/adapters.test.ts +45 -2
@@ 1,6 1,6 @@
import { describe, it, expect } from 'vitest'
-import { adaptSession, adaptProject, adaptStats } from './adapters'
-import type { SessionDTO, ProjectDTO, StatsDTO } from './adapters'
+import { adaptSession, adaptProject, adaptStats, adaptSavedSearch } from './adapters'
+import type { SessionDTO, ProjectDTO, StatsDTO, SavedSearchDTO } from './adapters'
function makeDTO(overrides: Partial<SessionDTO> = {}): SessionDTO {
return {
@@ 56,6 56,19 @@ describe('adaptSession', () => {
const s = adaptSession(makeDTO({ started_at: startedAt, ended_at: endedAt }))
expect(s.ended).toBe(new Date(endedAt * 1000).toISOString())
})
+
+ it('sessionId === d.session_id (bare)', () => {
+ const s = adaptSession(makeDTO())
+ expect(s.sessionId).toBe('abc123')
+ const s2 = adaptSession(makeDTO({ session_id: 'xyz789' }))
+ expect(s2.sessionId).toBe('xyz789')
+ })
+
+ it('composite id and bare sessionId coexist (IV6)', () => {
+ const s = adaptSession(makeDTO({ tool: 'opencode', host: 'workpc', session_id: 'xyz789' }))
+ expect(s.id).toBe('opencode/workpc/xyz789')
+ expect(s.sessionId).toBe('xyz789')
+ })
})
function makeProjectDTO(overrides: Partial<ProjectDTO> = {}): ProjectDTO {
@@ 178,3 191,33 @@ describe('adaptStats', () => {
expect(s.perTool[0].dailySparkline).toEqual(sparkline)
})
})
+
+// ── adaptSavedSearch ─────────────────────────────────────────────────────────
+
+function makeSavedSearchDTO(overrides: Partial<SavedSearchDTO> = {}): SavedSearchDTO {
+ return {
+ name: 'recent-errors',
+ query: 'level:error',
+ created_at: 1700000000,
+ updated_at: 1700000000,
+ ...overrides,
+ }
+}
+
+describe('adaptSavedSearch', () => {
+ it('created_at=1700000000 → createdAt === "2023-11-14T22:13:20.000Z"', () => {
+ const ss = adaptSavedSearch(makeSavedSearchDTO())
+ expect(ss.createdAt).toBe('2023-11-14T22:13:20.000Z')
+ })
+
+ it('updated_at=1700000050 → updatedAt is the ISO of that unix epoch', () => {
+ const ss = adaptSavedSearch(makeSavedSearchDTO({ updated_at: 1700000050 }))
+ expect(ss.updatedAt).toBe(new Date(1700000050 * 1000).toISOString())
+ })
+
+ it('name and query pass through unchanged', () => {
+ const ss = adaptSavedSearch(makeSavedSearchDTO({ name: 'my-search', query: 'tool:opencode' }))
+ expect(ss.name).toBe('my-search')
+ expect(ss.query).toBe('tool:opencode')
+ })
+})
M web/src/api/adapters.ts => web/src/api/adapters.ts +27 -0
@@ 20,6 20,7 @@ export interface SessionDTO {
export interface Session {
id: string // `${tool}/${host}/${session_id}`
+ sessionId: string // bare session_id (IV6)
tool: string
host: string
cwd: string // working_dir ?? ''
@@ 36,6 37,7 @@ export interface Session {
export function adaptSession(d: SessionDTO): Session {
return {
id: `${d.tool}/${d.host}/${d.session_id}`,
+ sessionId: d.session_id,
tool: d.tool,
host: d.host,
cwd: d.working_dir ?? '',
@@ 201,3 203,28 @@ export function adaptStats(d: StatsDTO): Stats {
hostSplit: d.host_split,
}
}
+
+// ── Saved searches ──────────────────────────────────────────────────────────
+
+export interface SavedSearchDTO {
+ name: string
+ query: string
+ created_at: number
+ updated_at: number
+}
+
+export interface SavedSearch {
+ name: string
+ query: string
+ createdAt: string // ISO 8601
+ updatedAt: string // ISO 8601
+}
+
+export function adaptSavedSearch(d: SavedSearchDTO): SavedSearch {
+ return {
+ name: d.name,
+ query: d.query,
+ createdAt: new Date(d.created_at * 1000).toISOString(),
+ updatedAt: new Date(d.updated_at * 1000).toISOString(),
+ }
+}
M web/src/routes/index.tsx => web/src/routes/index.tsx +1 -1
@@ 45,7 45,7 @@ function HomeRoute(): React.JSX.Element {
const { data: sessions, isLoading, error } = useSessions(filters)
const handleOpen = useCallback((s: Session) => {
- void navigate({ to: '/session/$tool/$host/$id', params: { tool: s.tool, host: s.host, id: s.id } })
+ void navigate({ to: '/session/$tool/$host/$id', params: { tool: s.tool, host: s.host, id: s.sessionId } })
}, [navigate])
const { cursor, move, activate, jumpTo } = useHomeCursor(
M web/src/routes/project.$.tsx => web/src/routes/project.$.tsx +1 -1
@@ 30,7 30,7 @@ function ProjectRoute(): React.JSX.Element {
const error = sessionsError
const handleOpen = useCallback((s: Session) => {
- void navigate({ to: '/session/$tool/$host/$id', params: { tool: s.tool, host: s.host, id: s.id } })
+ void navigate({ to: '/session/$tool/$host/$id', params: { tool: s.tool, host: s.host, id: s.sessionId } })
}, [navigate])
const { cursor, move, activate, jumpTo } = useHomeCursor(