Context
packages/loopover-miner/lib/run-state.js stores run state keyed by (api_base_url, repo_full_name)
specifically because, per its own migration comment (run-state.js:42), "two forge hosts serving a
same-named owner/repo must not share one 'current state' row." listRunStates() (run-state.js:125-134)
returns apiBaseUrl on every row for exactly this reason.
apps/loopover-miner-ui/vite-run-state-api.ts's hand-written RunStateModule type
(vite-run-state-api.ts:13-16) narrows the row shape to { repoFullName; state; updatedAt }, omitting
apiBaseUrl from the type the rest of the app is built against (the field is still present on the
wire since JSON.stringify serializes the real object regardless of this local type). Client-side,
apps/loopover-miner-ui/src/lib/run-history.ts's RunStateRow type and isRunStateRow validator
likewise only recognize repoFullName, state, and updatedAt — apiBaseUrl is dropped entirely
from what the UI is typed to see or display. apps/loopover-miner-ui/src/routes/run-history.tsx's
RunStateTable then keys each row with <TableRow key={row.repoFullName}> (run-history.tsx:92) and
renders only repoFullName in the "Repository" column (run-history.tsx:93) — nothing in the row
distinguishes two forges tracking the same owner/repo.
If the same miner instance ever tracks owner/repo on two different forge hosts (e.g. github.com
and a self-hosted GHE instance — the exact scenario the store's own schema comment calls out), the
run-history table receives two JSON rows with an identical repoFullName but different apiBaseUrl.
React's reconciliation on the duplicate key means one of the two rows is silently dropped/misrendered
instead of both being shown, and even if both rendered, there is no visible way to tell them apart —
they'd show identical "Repository" text with no forge-host indicator.
Requirements
apps/loopover-miner-ui/vite-run-state-api.ts's RunStateModule row type must include apiBaseUrl: string,
matching listRunStates()'s real return shape.
apps/loopover-miner-ui/src/lib/run-history.ts's RunStateRow type and isRunStateRow validator
must include and validate apiBaseUrl: string.
apps/loopover-miner-ui/src/routes/run-history.tsx's RunStateTable must key each row on a
composite of apiBaseUrl + repoFullName (not repoFullName alone), so two rows sharing a
repoFullName across different forges never collide.
- The "Repository" column must visibly disambiguate rows sharing a
repoFullName across different
apiBaseUrl values (e.g. showing the forge host alongside the repo name) so an operator can tell
them apart, not just avoid a React key collision.
- No change to
packages/loopover-miner/lib/run-state.js or the /api/run-state route's response
shape — the server already returns the needed field; this is a client-side type/render fix.
Deliverables
Test Coverage Requirements
apps/** is listed under ignore: in codecov.yml, so this app is not gated by Codecov's 99% patch
check — but the local npm run test:coverage invariant bar still applies, and the regression test
above is required. This changes the rendered "Repository" column, so a before/after screenshot is
required per this repo's UI-PR convention.
Expected Outcome
The run-history table can never silently drop or conflate a repo tracked under two different forge
hosts — every row is uniquely keyed and visibly distinguishable, matching the guarantee the store's
own schema was built to provide.
Links & Resources
packages/loopover-miner/lib/run-state.js (schema comment at line 42, listRunStates at 125-134),
apps/loopover-miner-ui/vite-run-state-api.ts (lines 13-16),
apps/loopover-miner-ui/src/lib/run-history.ts,
apps/loopover-miner-ui/src/routes/run-history.tsx (lines 86-103).
Context
packages/loopover-miner/lib/run-state.jsstores run state keyed by(api_base_url, repo_full_name)specifically because, per its own migration comment (run-state.js:42), "two forge hosts serving a
same-named owner/repo must not share one 'current state' row."
listRunStates()(run-state.js:125-134)returns
apiBaseUrlon every row for exactly this reason.apps/loopover-miner-ui/vite-run-state-api.ts's hand-writtenRunStateModuletype(vite-run-state-api.ts:13-16) narrows the row shape to
{ repoFullName; state; updatedAt }, omittingapiBaseUrlfrom the type the rest of the app is built against (the field is still present on thewire since
JSON.stringifyserializes the real object regardless of this local type). Client-side,apps/loopover-miner-ui/src/lib/run-history.ts'sRunStateRowtype andisRunStateRowvalidatorlikewise only recognize
repoFullName,state, andupdatedAt—apiBaseUrlis dropped entirelyfrom what the UI is typed to see or display.
apps/loopover-miner-ui/src/routes/run-history.tsx'sRunStateTablethen keys each row with<TableRow key={row.repoFullName}>(run-history.tsx:92) andrenders only
repoFullNamein the "Repository" column (run-history.tsx:93) — nothing in the rowdistinguishes two forges tracking the same
owner/repo.If the same
minerinstance ever tracksowner/repoon two different forge hosts (e.g. github.comand a self-hosted GHE instance — the exact scenario the store's own schema comment calls out), the
run-history table receives two JSON rows with an identical
repoFullNamebut differentapiBaseUrl.React's reconciliation on the duplicate key means one of the two rows is silently dropped/misrendered
instead of both being shown, and even if both rendered, there is no visible way to tell them apart —
they'd show identical "Repository" text with no forge-host indicator.
Requirements
apps/loopover-miner-ui/vite-run-state-api.ts'sRunStateModulerow type must includeapiBaseUrl: string,matching
listRunStates()'s real return shape.apps/loopover-miner-ui/src/lib/run-history.ts'sRunStateRowtype andisRunStateRowvalidatormust include and validate
apiBaseUrl: string.apps/loopover-miner-ui/src/routes/run-history.tsx'sRunStateTablemust key each row on acomposite of
apiBaseUrl+repoFullName(notrepoFullNamealone), so two rows sharing arepoFullNameacross different forges never collide.repoFullNameacross differentapiBaseUrlvalues (e.g. showing the forge host alongside the repo name) so an operator can tellthem apart, not just avoid a React key collision.
packages/loopover-miner/lib/run-state.jsor the/api/run-stateroute's responseshape — the server already returns the needed field; this is a client-side type/render fix.
Deliverables
RunStateModule/RunStateRowtypes includeapiBaseUrlisRunStateRowvalidatesapiBaseUrlapiBaseUrl+repoFullNamerepoFullNameand differentapiBaseUrlboth render as distinct rowsTest Coverage Requirements
apps/**is listed underignore:incodecov.yml, so this app is not gated by Codecov's 99% patchcheck — but the local
npm run test:coverageinvariant bar still applies, and the regression testabove is required. This changes the rendered "Repository" column, so a before/after screenshot is
required per this repo's UI-PR convention.
Expected Outcome
The run-history table can never silently drop or conflate a repo tracked under two different forge
hosts — every row is uniquely keyed and visibly distinguishable, matching the guarantee the store's
own schema was built to provide.
Links & Resources
packages/loopover-miner/lib/run-state.js(schema comment at line 42,listRunStatesat 125-134),apps/loopover-miner-ui/vite-run-state-api.ts(lines 13-16),apps/loopover-miner-ui/src/lib/run-history.ts,apps/loopover-miner-ui/src/routes/run-history.tsx(lines 86-103).