Web Run Reports in ForgeGraph Draft

2026-08-14 · ForgeGraph @ feat/consume-runreport-core · consumes @preflight/runreport

Goal: render ForgeGraph web/CI test runs (Playwright/veritas) as first-class run reports — step timeline, screenshots, video, share links — reusing the portable @preflight/runreport core that already powers Preflight's mobile Maestro reports. Most of the substrate already exists in ForgeGraph; this is mainly wiring it into the shared report surface.
Reality check (2026-08-15): the consumer chain is now fully wired and typechecked — provider (incl. P1 artifacts, done), page, styling — and /api/evidence already accepts artifacts:[{kind:screenshot|video,url}] and writes test_artifacts. But the prod DB has 3 test_runs (all gitea_ci "Unit tests") and 0 test_artifacts. So the value-critical path is no longer the wiring — it's a browser E2E producer (veritas/Playwright) that captures screenshots+video, uploads them somewhere servable, and POSTs them to /api/evidence. Without it, reports render status+steps but no media.
~70%
substrate already present
3
surfaces done (provider/page/styling)
5
phases to ship
0
new tables needed

What already exists (don't rebuild)

PieceWhereFit
test_runs (status, source gitea_ci|veritas, counts, timing, changeset/build FKs)packages/db/schema/test-run.tsmaps to RunDetail head
test_artifacts (kind screenshot|video|trace|…, url, mimeType, sizeBytes, FK test_run)packages/db/schema/test-artifact.tsexactly RunArtifact
Evidence ingest (writes test_runs + test_artifacts from veritas)apps/web/.../api/evidence/route.tsthe producer already exists
R2 bucket PUBLIC_FEED_MEDIA + a blob-serve route patternwrangler.toml, api/public/feed/artifacts/[id]media storage/serve template
changesets → repositoriesschemarun naming

Already built on feat/consume-runreport-core

ItemFileStatus
Consume @preflight/runreport@0.1.0 from internal registry (npm.forgegraf.com); resolves to compiled dist.npmrc, apps/web/package.jsondone
createForgeGraphRunReportProvider(db) mapping test_runs → RunDetail (synthetic WEB_RUN_PIPELINE)apps/web/src/lib/run-report-provider.tsartifacts: []
/runs/[testRunId] page rendering <RunReport>apps/web/src/app/runs/[testRunId]/page.tsxdone
Tailwind @source for the package dist (arbitrary classes generate)apps/web/src/app/globals.cssdone

All typecheck clean; the tailwind-source-globs guard test stays green.

Phases

P0 — Browser E2E producer (the actual value gap)

TaskVerifyStatus
A veritas/Playwright web-E2E run that captures screenshots + a screen recording (and, ideally, per-step timings), uploads media to a servable place (ForgeGraph R2 PUBLIC_FEED_MEDIA or a dedicated bucket), and POSTs to /api/evidence as a testEvidence with artifacts:[{kind:video|screenshot,url}].a real web run lands rows in test_runs + test_artifacts; its report shows videotodo
Decide where veritas runs (existing CI runner? a new browser job?) and how media is stored/served — this drives P2.—decision
Everything downstream of the producer is already built (ingest → test_artifacts → provider → <RunReport>). P0 is the one missing link between "reports exist" and "reports show recordings." Prioritize it.

P1 — Wire real artifacts (the keystone, small)

TaskVerifyStatus
Provider loads test_artifacts for the run and maps → RunArtifact (kind, url→uri, mimeType→contentType, sizeBytes). Replace artifacts: [].renders whatever media a run has (0 today — see reality check)done f363759
Confirm the servable-media rule: the core hides non-/api/ artifacts on a share view. Decide whether test_artifacts.url is already public (use as-is) or needs a ForgeGraph blob route.video plays; screenshots loadtodo
P1 alone turns the existing report page from "steps only" into a full media report, because the data is already being ingested. This is the highest value / lowest effort step — do it first.

P2 — Serving + auth for run media

TaskVerifyStatus
If media isn't already served: add /api/runs/artifacts/[id]/blob mirroring the public-feed R2 route (PUBLIC_FEED_MEDIA), and have the evidence ingest upload bytes + set url to the blob route.authed fetch 200; correct content-typetodo
Access scoping: run reports are scoped to repo collaborators (mirror how other ForgeGraph pages gate).non-member 403todo

P3 — Fleet board

TaskVerifyStatus
Provider getFleet: latest test_run per repo/suite. NOTE: the core's RunFleetRow is a mobile ios/android matrix — for web, either bump @preflight/runreport with a generalized fleet row, or render a simple ForgeGraph list.a /runs board lists repos' latest runs with ▶ video indicatortodo

P4 — Share links

TaskVerifyStatus
ForgeGraph share-token model (mirror Preflight's pf_run_share: token, run, expiry, revoke) + resolveShare + public /share/runs/[token] page + token-gated blob serving. Reuses the core's shareToken URL threading.unauthenticated share URL renders + plays video; invalid/expired → 404/401todo

P5 — Surface + deploy

TaskVerifyStatus
Link the report from where runs live (changeset view / ci/[runId] / a repo's runs tab).a run in the UI deep-links to its reporttodo
Deploy the ForgeGraph web worker (agent-claimed CF Worker deploy) + verify live against a real veritas run.report renders in prod with real mediatodo

Risks & notes

RiskMitigation
Fleet type is mobile-shaped (ios/android)Generalize RunFleetRow in the core (a lane-keyed map) and republish; keeps Preflight + ForgeGraph on one shape. Or ForgeGraph renders its own list for v1.
Core package versioning across two reposAny core change = rebuild dist + republish a new @preflight/runreport version to npm.forgegraf.com; bump both consumers. Keep the workspace (Preflight) and published versions in lockstep.
Whether veritas emits step timelines / video todayP1 renders whatever's ingested; enrich the veritas/Playwright producer (per-step timings, screen recording) as a follow-up if steps are coarse.
Tailwind can't scan node_modulesAlready handled — @source for the package dist in globals.css, guarded by the source-globs test.

Verification

Open questions