Skip to content

feat: stop reloading the page out from under people - #41

Merged
ralyodio merged 1 commit into
masterfrom
feat/live-ui
Aug 10, 2026
Merged

feat: stop reloading the page out from under people#41
ralyodio merged 1 commit into
masterfrom
feat/live-ui

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

"we just need to make the page not refresh and use ajax/realtime event updates and logging and status bars or spinners and shit. its hard to tell when i should wait or take action."

What was actually there

No client router — I checked, zero hits for pushState/popstate. The app is Hono SSR plus five islands (upload, jobs, identify, overlay, review), navigation is full page loads, and mutations are form POST → 302.

The only "liveness" was:

  • jobs.tsx:109window.location.reload() 1.5s after any job completes
  • upload.tsx:306 and :405 — two more reloads
  • pages.tsx"Analysis is running. Refresh for progress."

Four hard reloads and an instruction to do a fifth by hand. That's the whole reason it's unclear whether to wait or act. It's also the same bug as #40 from the other end: a click landing before a reload arrived with no athlete loaded, so the server minted another one. The reloads were racing the clicks.

Approach: not a router

A SPA router would fight the explicit commitment in actions.ts that pages "must keep working without JavaScript". Instead, client/live.ts fetches the URL the user is already on and swaps only the regions marked data-live — footage list, athlete table, suggested moments.

  • Server stays the single source of truth; no client view model to drift
  • No-JS keeps working, byte for byte
  • Nothing scrolls, nothing collapses, no selection is lost
  • Coalesced, so a save plus two finishing jobs costs one fetch

Islands that own state stay outside every region: the job log keeps its EventSource, the identify grid keeps a half-made selection. #moment-review is inside one deliberately — it re-reads from a data-moments attribute and is re-mounted after a swap.

Identifying an athlete no longer navigates at all: it reports in place, refreshes its own grid, and lets the page catch up. The "Refresh for progress" line is now no-js-only and hidden once the bundle runs.

Test

apps/web/src/liveregions.test.ts pins the invariant that makes this safe: job log, identify grid and uploader are outside every swappable region. Move one inside and the test fails, rather than the analysis feed dying silently in production.

576 tests pass, 8 skipped. Lint and build clean.

Note

This is one of three from the same report. The other two — jersey/team in the picker, and ball detection recall — are separate branches.

🤖 Generated with Claude Code

"we just need to make the page not refresh and use ajax/realtime event updates
and logging and status bars or spinners and shit. its hard to tell when i should
wait or take action."

There is no client router and there should not be one: the pages are
server-rendered and the project commits to working without JavaScript. What
there was instead was `window.location.reload()` — fired 1.5s after any job
finished, twice more after uploads — and a "Analysis is running. Refresh for
progress." link for the gaps in between. Four hard reloads and an instruction to
do a fifth by hand.

That is what made it impossible to tell whether to wait or to click. It also
raced the user: a click landing before a reload arrived with no athlete loaded,
and the server minted another one. Seven duplicate athletes came from that race,
so this and the identify fix are the same bug seen from two ends.

`client/live.ts` fetches the URL the user is already on and swaps only the
regions marked `data-live` — the footage list, the athlete table, the suggested
moments. The server stays the single source of truth, no-JavaScript keeps
working unchanged, nothing scrolls, and the islands that own state are outside
every region: the job log keeps its EventSource, the identify grid keeps a
half-finished selection. `#moment-review` sits inside one deliberately, because
it re-reads its data from an attribute, and is re-mounted after a swap.

Identifying an athlete no longer navigates at all. It reports what happened in
place, refreshes its own grid, and lets the rest of the page catch up.

apps/web/src/liveregions.test.ts pins the invariant that makes this safe: the
job log, the identify grid and the uploader are outside every swappable region.
Move one inside and the test fails rather than the feed dying silently in
production.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ralyodio
ralyodio merged commit a466ad6 into master Aug 10, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant