Claude Transcripts docs GitHub
Work in progressUnder active development — not tested as ready for use. Breaking changes land without notice, stored data may need to be discarded between revisions, and there is no auth or security model. These docs describe the intended design as much as the current state.

webui — codebase reference

The viewer: a React single-page app for browsing session history. It is a thin read client over the webapi — list, detail, and a transcript viewer — and is deliberately minimal in Tier 1 (functional, lightly styled; a visual rework is future scope, #8). It stays optional — everything it does is reachable via the CLI/API (tiers.md).

What's built (Tier 1)

Still planned

File layout

packages/webui/
├── index.html                 # Vite HTML entry (#root + module script)
├── vite.config.ts             # React plugin, base "/app/", dev server, /api proxy
├── dev/
│   └── webapi-target.ts       # dev-only (Node): find the webapi, explain it if absent
└── src/
    ├── main.tsx               # React root: QueryClient + ColorModeProvider + Router
    ├── color-mode.tsx         # color-mode state (light/dark/system) + ThemeProvider
    ├── router.tsx             # code-based TanStack Router tree (basepath "/app")
    ├── theme.ts               # createAppTheme(mode) + codeBg(mode) + MONO stack
    ├── format.ts              # pure presentation helpers (no React)
    ├── search-query.ts        # pure: /search URL state → API params, paging maths
    ├── sessions-view.ts       # pure: interval/day/month maths for timeline + calendar
    ├── transcript-entry.ts    # raw JSONL entry → compact EntryView
    ├── api/
    │   ├── generated.ts       # orval snapshot: types + fetchers + query hooks
    │   ├── http.ts            # orval mutator: unwrap + throw on non-2xx
    │   └── model.ts           # hand-written GET /api/model hook (header title/version)
    ├── routes/
    │   ├── root.tsx           # RootLayout app shell (Header + Outlet)
    │   ├── sessions-list.tsx  # SessionsListPage — "/", switches the three projections
    │   ├── session-detail.tsx # SessionDetailPage — "/sessions/$id" (+ ?q= highlight)
    │   └── search-results.tsx # SearchResultsPage — "/search" (filters + paging)
    └── components/
        ├── Header.tsx         # thin top bar (title/version, search, settings, links)
        ├── SearchBox.tsx      # header search input → GET /api/search (sessions + content)
        ├── HighlightedText.tsx# renders marked snippets / query terms as <mark>
        ├── SettingsMenu.tsx   # primary menu: theme toggle (+ config later)
        ├── LinksMenu.tsx      # secondary menu: services / API / GitHub / docs links
        ├── TranscriptView.tsx # incrementally-paged transcript accordion
        ├── SpeakerTurnsView.tsx # one side of the conversation (You / Claude)
        ├── StatusChip.tsx     # session lifecycle chip (live / abandoned / ended)
        ├── SourceChip.tsx     # recording provenance chip (live / backfilled)
        ├── TokenUsageChips.tsx# token breakdown chips
        ├── states.tsx         # Loading / ErrorState / EmptyState
        └── sessions/          # the three projections of the session list
            ├── SessionsTable.tsx     # dense comparative table
            ├── SessionsTimeline.tsx  # vertical timeline, three densities
            └── SessionsCalendar.tsx  # month grid (day-spanning bars) + day time-grid

The pure modules (format.ts, search-query.ts, sessions-view.ts, transcript-entry.ts) hold the logic worth unit-testing, out of the components: paging offsets and calendar placement are where the silent bugs live, and a component test would not catch a session drawn on the wrong day.

Bootstrap & routing

src/main.tsx mounts the app into #root under StrictMode: a QueryClientProvider (30s staleTime, no refetch-on-focus, retry: 1), the ColorModeProvider (which supplies the MUI ThemeProvider + CssBaseline for the active mode and persists the user's light/dark/system preference in localStorage), and a RouterProvider.

src/router.tsx builds a code-based TanStack Router tree (no file-based plugin): a RootLayout root route with three children — /SessionsListPage, /sessions/$idSessionDetailPage, and /searchSearchResultsPage. Each validates the query-string state it owns (the list's view/density/month/day, the detail's q, the results page's q/filters/page), falling back to defaults rather than rendering nothing — that state arrives from whatever was pasted into the address bar. The router is created with basepath: "/app" because the SPA is served under /app in production (ADR 0002), matching Vite's base: "/app/". RootLayout (routes/root.tsx) is the shell: the sticky Header over a Container that renders the routed <Outlet />.

API layer (api/generated.ts)

The generated snapshot is the single source of client types and data hooks. It is overwritten by bun run gen:clientsdo not edit by hand. Transport lives in api/http.ts, the orval mutator: it unwraps orval's {data, status, headers} envelope and throws an ApiRequestError (message + status) on a non-2xx, so react-query's isError/error work as they should. Requests are same-origin — the webui is served under /app with /api proxied to the webapi — so nothing is prepended to the spec's own /api/... paths.

It exports:

Hook options are nested: react-query options go under query, per-call fetch options under requestuseListSessions(params, { query: { placeholderData: … } }).

All requests are relative (/api/...); in dev Vite proxies them to the webapi.

The one hand-written client is api/model.ts (useAppModelGET /api/model): that endpoint is a plain Hono route, not part of the OpenAPI contract, so it isn't in the generated snapshot. The header uses it for the title + build version.

Views

Presentation helpers

Build & dev (vite.config.ts)