Live Cast public viewer web app
Live Cast epic — child C6 (viewer web at live.jaah.ai: `live.html` entry reusing the console task list + task detail, path rewriter, ViewerMode, picker/switcher, reCAPTCHA client) AND the epic's end-to-end verification on dev. Source documents: PRD = Task 6085, Architecture = Task 6086 (§5.1, and §4 for the public API contract C5 shipped). Runs after C3 (verdicts), C4 (admin tab), C5 (public API) and C7 (ALB/nginx 3053) have merged.
## Original requirement (verbatim, epic body)
- "A separate ALB will allow unauthenticated access to Jaah - [live.jaah.ai]" (A3)
- "[live.jaah.ai/{org_slug}/{project_slug}] - project slug is a sha hash (6 symbols) of project_id" (A4)
- "Viewers can see tasks and all their details and artifacts" (A5)
- "Viewers can select the project to watch (from those enabled for Live Cast)" (A6)
- "No Web Console" (A7)
- "Minimum prose on pages, minimalistic controls" (A19)
- Follow-up (A20): "Also we need to protect against bots, so we should use reCaptcha"
Decisions: D-2 "It must 100% reuse existing UI, just hide a few things." D-3 "Everything. Same UI as regular task details." D-8 the root lists every published project across orgs. D-10 sessions/terminal not shown. D-15 invisible, once per visit.
## Acceptance (PRD, verbatim)
- AC6.1 (viewer half). `curl -sI https://live.dev.jaah.ai/<org>/<slug>` gives 200 with no cookie.
- AC6.3. Project picker (D-8). `live.jaah.ai/` lists all currently published projects across all orgs (org name + project name). Selecting one opens its viewer. The viewer page also lets the user switch project. Unpublished projects never appear.
- AC6.4. Same UI (D-2, D-3). The viewer reuses the existing console task list and task detail UI, not a re-implementation. It shows everything the regular task view shows for published tasks and artifacts: title, state, body, outcome, epic children/pipeline (each child subject to its own published state), comments and Q&A, PR link, artifacts and their previews. It hides all write controls and all console chrome (A7 "No Web Console"): no sidebar or navigation to other console areas; no edit, start, stop, comment composer, answer controls, upload, delete or settings; no override toggles; no terminal; no sessions or transcripts (D-10).
- AC6.5. Live updates (D-2). The viewer stays as live as the console: status changes, new published tasks and artifacts appear without a reload, within a few seconds.
- AC6.7 (meta half). Meta noindex on the live page.
- AC6.8. Quality. On the viewer, titles are readable (never truncated to a few characters), the layout is identical in quality to the console, it works at mobile widths with no horizontal scroll, and the page shows minimal prose.
- AC8.1 (UI). On a visitor's first load of any live page, an invisible reCAPTCHA Enterprise score check runs … There is no checkbox and no visible challenge for humans.
- AC8.2 (UI). A low score shows a plain, short "blocked" page and no data.
- AC8.3 (UI). Fail closed. If reCAPTCHA is unconfigured or unreachable, live data is not served. The page shows a one-line unavailable message.
- AC8.4. The token survives a normal browsing visit (navigating between projects and tasks, live updates) without re-challenging. When it expires, it refreshes invisibly.
- AC7.1 (end to end). As an operator on `dev.jaah.ai`, add Live Cast to a test org. As that org's admin, tick, publish and open the link on `live.dev.jaah.ai` in a browser from outside the tailnet. The invisible reCAPTCHA passes, and the task list renders and updates live.
- Viewer-observed halves moved here from C3/C4 by the plan critic: AC3.5 ("click the link and the viewer page opens. Progress reaches N/N"); AC5.1 ("toggle a hidden task to Published. The viewer shows it within about one minute"); AC4.5/AC4.6/AC4.9 (a new/edited task appears or disappears per the rules; a non-text artifact on a published task is visible and on a hidden task is not; the 101st-oldest task is not visible).
## Approach (architecture §5.1)
- Second Vite input `rollupOptions.input = {main: 'index.html', live: 'live.html'}`; `live.html` mounts `src/live/main.tsx` reusing `ThemeProvider`, `QueryClientProvider`, `BrowserRouter` as `main.tsx:17-70`; does NOT mount `AuthProvider`, `RequireAuth`, `AuthLayout`, sidebar, support chat or onboarding widget. Routes: `/` picker, `/:org/:slug` task list, `/:org/:slug/tasks/:id` task detail; header carries only a project switcher (select over `/projects`).
- Seam 1 — path rewriter: `api.ts` gains `setApiPathRewriter(fn)`; live entry maps `/api/tasks…` → `/api/public/live/{org}/{slug}/tasks…`, `/api/stream` → `/api/public/live/{org}/{slug}/stream?vt=…`, and the artifact/attachment URL builders (`TaskArtifacts.tsx:665`, `useCommentImages.ts:36`, `attachmentResolver.ts:104`); adds `X-Live-Token`; on 401 `{"detail":"token"}` re-mints invisibly and retries once. Every other `/api/*` path the reused components call (`/api/worktrees`, `/api/projects`, `/api/tags/groups`, body-versions, compliance-manifest, persona-registry, …) resolves to an empty value locally — one list in `src/live/apiMap.ts`. `useEventStream` reads the same rewriter. Console mode: identity rewriter, console behaviour unchanged.
- Seam 2 — `ViewerMode` context (`src/live/viewerMode.ts`, default false): stub permission source so `useCan()` is false except `tasks:view`, `useTaskAccess` false; `TaskDetailPage`, `Tasks`, `TaskComments` (composer), `GateForm` (answers), `TaskArtifacts`, `TaskAttachmentsField` (upload/delete) and C4's Live Cast override controls read `useViewerMode()` to hide session/transcript/terminal links, worktree panels, costs, TaskVersions and override toggles — one condition at each control, no copied component.
- reCAPTCHA client `src/live/recaptcha.ts`: copy of `apps/site-widget/src/recaptcha.ts` (apps never cross-import; 2 copies within the ceiling). On load execute action `live_view`, mint the token, re-mint 2 minutes before `expires_at`; 403 ⇒ one-line "Blocked.", 503 ⇒ one-line "Unavailable.", no data.
- `live.html` carries `<meta name="robots" content="noindex,nofollow">`.
- Quality: titles use the console's existing ellipsis truncation at normal widths, never shrink-to-fit; e2e screenshots at desktop and mobile widths.
- Project instruction: while testing the UI, note anything broken, inconvenient or worse than elsewhere in the system, and file it as itemized 1–2 sentence suggestions per your charter.
Credentials & reach:
- AC7.1 console steps — `jaah/debug/main/operator-login` — verified.
- AC6.x / AC7.1 on `live.dev.jaah.ai` from outside the tailnet — no credential; public internet — deferred at design time until C7 merged; C7 is a prerequisite of this child, so run it now (prove "outside the tailnet" by the host resolving to a public AWS address and answering over it).
- AC8.1, AC8.4 invisible reCAPTCHA passing on `live.dev.jaah.ai` — existing `site_enquiry_recaptcha_*` operator settings — deferred: the operator committed (Q-4) to adding both live domains to the key; if absent, the page shows "Unavailable." by design — report AC8.1/AC8.4/AC7.1's reCAPTCHA step as deferred with this reason and prove the rest with a stubbed token in e2e.
- AC6.4, AC6.8 screenshots / side-by-side — Playwright Chromium on the runner node — verified.
Out of scope:
- NC-4 (verbatim): "Do not fork or re-implement the task list and detail UI for the viewer. Reuse the existing components with a read-only/viewer mode. Do not restyle the console as a side effect."
- NC-2 (verbatim): "No write endpoint, cookie session, login, signup or console navigation reachable from the live host."
- NC-3 (verbatim): "No exposure of sessions, transcripts, the terminal, credentials, env vars, node data, user emails, billing data, or other projects' data on the live surface."
- NC-10 (verbatim): "Do not change the site widget's or site chat's existing reCAPTCHA behaviour. Reuse it; do not fork it." (copying the 46-line client into `src/live/` is the sanctioned reuse.)
- NC-8: no feature flag that ships it off.
- Do NOT change public API semantics (C5), visibility (C2), evaluation (C3) or terraform (C7) beyond a minimal, tested fix named in the PR.
- Deferred, not built: per-viewer analytics, custom domains, embedding/iframe.
Done means:
- Request fence — e2e asserts every request URL the viewer issues starts with `/api/public/live/` (or is a static asset / the reCAPTCHA script); no request carries a console cookie or needs auth.
- AC6.1 (viewer) — `curl -sI https://live.dev.jaah.ai/<org>/<slug>` ⇒ 200 with no cookie.
- AC6.3 — publish projects in two dev orgs: both appear at `live.dev.jaah.ai/`; selecting one opens it; the header switcher changes project; unpublish one ⇒ it disappears from the root and its URL ⇒ 404.
- AC6.4 — side-by-side screenshots (console vs viewer) of a published task's detail and the task list: same layout and components; absent in the viewer: sidebar, edit/start/stop, comment composer, answer controls, upload/delete, settings, override toggles, terminal, session/transcript links; present: title, state, body, outcome, epic children (hidden child absent), comments and Q&A, PR link, artifacts with previews.
- AC6.5 — with the viewer open, start or settle a task in the console: the viewer reflects it within a few seconds with no reload (e2e against the stream + dev observation).
- AC6.7 (meta) — `live.html` source contains `<meta name="robots" content="noindex,nofollow">`.
- AC6.8 — screenshots at desktop and phone widths: full readable task titles (not 1–4 character fragments), no horizontal scroll, no prose blocks; quality comparable to the console's own task pages.
- AC8.1 (UI) — fresh browser: page renders with no visible challenge; `curl` a live data endpoint without the token ⇒ 401.
- AC8.2 (UI) — with a stubbed 403 the page shows only a one-line "Blocked." and no task data.
- AC8.3 (UI) — with a stubbed 503 (or reCAPTCHA unset on dev) the page shows only a one-line "Unavailable." and no data.
- AC8.4 — e2e with a short-TTL stub: navigating projects/tasks and live updates continue across an expiry with an invisible re-mint visible in network requests; on dev, browse for 30 minutes with no visible challenge (or deferred per Credentials & reach).
- Viewer-observed checks on dev: AC3.5 the tab's link opens the viewer and progress reaches N/N; AC5.1 toggling a hidden task to Published shows it on the viewer within ~1 minute; AC4.5 a new task appears (or not) per the rules and editing its body to match a hide rule removes it; AC4.6 an image/PDF on a published task is visible and on a hidden task is not; AC4.9 the 101st-oldest task is not visible.
- AC7.1 (end to end) — as operator on dev add Live Cast to a test org; as that org's admin tick, publish, open the link on `live.dev.jaah.ai` from a public address: invisible reCAPTCHA passes (or deferred per Credentials & reach), the task list renders and updates live.
- Console unchanged: existing web unit + e2e suites pass; the console bundle's boot path is unaffected; `./dc check full` green; Jaah Deploy of the merged PR succeeds.
Outcome
Live Cast viewer merged (#5303). Full gate, CI and dev Jaah Deploy all green; live.dev.jaah.ai serves it (200, noindex, 401 without token). Deferred: a human visit to confirm the real reCAPTCHA pass and dev viewer checks (headless gets Blocked). UI follow-ups are in the report.
Artifacts
Activity
- Draft
- Ready
- Runner started
- Ready
- Runner started
- Ready
- Runner started
- Ready
- Runner started
- Ready
- Runner started
- Runner ended (done)