diff --git a/apps/gittensory-miner-ui/src/lib/run-history.ts b/apps/gittensory-miner-ui/src/lib/run-history.ts new file mode 100644 index 0000000000..57a0db443a --- /dev/null +++ b/apps/gittensory-miner-ui/src/lib/run-history.ts @@ -0,0 +1,42 @@ +// Read-only client for the local run-state API (#4305). The dashboard is a browser app and the miner's stores +// are `node:sqlite` files on disk, so the view never touches SQL — it fetches the dev server's local read-only +// endpoint (see `vite-run-state-api.ts`), which itself calls into `packages/gittensory-miner/lib/run-state.js`'s +// existing exports. + +export const RUN_STATE_API_PATH = "/api/run-state"; + +/** One `miner_run_state` row as served by the local API — mirrors `run-state.js`'s row shape. */ +export type RunStateRow = { + repoFullName: string; + state: "idle" | "discovering" | "planning" | "preparing"; + updatedAt: string; +}; + +export type RunHistoryResult = { ok: true; rows: RunStateRow[] } | { ok: false; error: string }; + +function isRunStateRow(value: unknown): value is RunStateRow { + if (typeof value !== "object" || value === null) return false; + const row = value as Record; + return ( + typeof row.repoFullName === "string" && + typeof row.updatedAt === "string" && + (row.state === "idle" || row.state === "discovering" || row.state === "planning" || row.state === "preparing") + ); +} + +/** Fetch the local run-state rows. Failures (server down, malformed payload) surface as a typed error result — + * the view renders them as a message, never a crash. `fetchImpl` is injectable for tests. */ +export async function fetchRunStates(fetchImpl: typeof fetch = fetch): Promise { + try { + const response = await fetchImpl(RUN_STATE_API_PATH); + if (!response.ok) return { ok: false, error: `local run-state API responded ${response.status}` }; + const payload: unknown = await response.json(); + const rows = (payload as { rows?: unknown }).rows; + if (!Array.isArray(rows) || !rows.every(isRunStateRow)) { + return { ok: false, error: "local run-state API returned an unexpected payload shape" }; + } + return { ok: true, rows }; + } catch (error) { + return { ok: false, error: error instanceof Error ? error.message : "failed to reach the local run-state API" }; + } +} diff --git a/apps/gittensory-miner-ui/src/routeTree.gen.ts b/apps/gittensory-miner-ui/src/routeTree.gen.ts index 0c0e4c51a8..133bd284a8 100644 --- a/apps/gittensory-miner-ui/src/routeTree.gen.ts +++ b/apps/gittensory-miner-ui/src/routeTree.gen.ts @@ -8,50 +8,70 @@ // You should NOT make any changes in this file as it will be overwritten. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. -import { Route as rootRouteImport } from "./routes/__root"; -import { Route as IndexRouteImport } from "./routes/index"; +import { Route as rootRouteImport } from './routes/__root' +import { Route as RunHistoryRouteImport } from './routes/run-history' +import { Route as IndexRouteImport } from './routes/index' +const RunHistoryRoute = RunHistoryRouteImport.update({ + id: '/run-history', + path: '/run-history', + getParentRoute: () => rootRouteImport, +} as any) const IndexRoute = IndexRouteImport.update({ - id: "/", - path: "/", + id: '/', + path: '/', getParentRoute: () => rootRouteImport, -} as any); +} as any) export interface FileRoutesByFullPath { - "/": typeof IndexRoute; + '/': typeof IndexRoute + '/run-history': typeof RunHistoryRoute } export interface FileRoutesByTo { - "/": typeof IndexRoute; + '/': typeof IndexRoute + '/run-history': typeof RunHistoryRoute } export interface FileRoutesById { - __root__: typeof rootRouteImport; - "/": typeof IndexRoute; + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/run-history': typeof RunHistoryRoute } export interface FileRouteTypes { - fileRoutesByFullPath: FileRoutesByFullPath; - fullPaths: "/"; - fileRoutesByTo: FileRoutesByTo; - to: "/"; - id: "__root__" | "/"; - fileRoutesById: FileRoutesById; + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' | '/run-history' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/run-history' + id: '__root__' | '/' | '/run-history' + fileRoutesById: FileRoutesById } export interface RootRouteChildren { - IndexRoute: typeof IndexRoute; + IndexRoute: typeof IndexRoute + RunHistoryRoute: typeof RunHistoryRoute } -declare module "@tanstack/react-router" { +declare module '@tanstack/react-router' { interface FileRoutesByPath { - "/": { - id: "/"; - path: "/"; - fullPath: "/"; - preLoaderRoute: typeof IndexRouteImport; - parentRoute: typeof rootRouteImport; - }; + '/run-history': { + id: '/run-history' + path: '/run-history' + fullPath: '/run-history' + preLoaderRoute: typeof RunHistoryRouteImport + parentRoute: typeof rootRouteImport + } + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } } } const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, -}; -export const routeTree = rootRouteImport._addFileChildren(rootRouteChildren)._addFileTypes(); + RunHistoryRoute: RunHistoryRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/apps/gittensory-miner-ui/src/routes/__root.tsx b/apps/gittensory-miner-ui/src/routes/__root.tsx index 870d7c8777..8b59bfbe61 100644 --- a/apps/gittensory-miner-ui/src/routes/__root.tsx +++ b/apps/gittensory-miner-ui/src/routes/__root.tsx @@ -13,10 +13,13 @@ function RootLayout() {

Gittensory Miner

Local dashboard shell

-