~bigbes/lethe

12df3f43091141353457bf5058e9524e6d7a910c — Eugene Blikh a month ago ddd7c1f
web: adapter — add Session.sessionId, SavedSearch DTO; fix composite-id call sites
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(