Skip to content

Multi-client time travel debugger for lamdera live#104

Draft
CharlonTank wants to merge 1 commit into
lamdera:lamdera-nextfrom
CharlonTank:time-travel-debugger
Draft

Multi-client time travel debugger for lamdera live#104
CharlonTank wants to merge 1 commit into
lamdera:lamdera-nextfrom
CharlonTank:time-travel-debugger

Conversation

@CharlonTank

@CharlonTank CharlonTank commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

What

A full-stack, multi-client time travel debugger built into lamdera live, opened from the devbar ("Time Travel").

  • Unified event timeline — every client broadcasts its state mutations (frontend msgs, backend msgs, ToBackend/ToFrontend) with the resulting wire-encoded model over the live websocket. Any tab can inspect every client's FrontendModel and the BackendModel at any recorded instant, with per-client colour badges and ToBackend session/client provenance.
  • Master-tab scrubbing — selecting a past frame (or dragging the slider) makes every connected client display its own state at that instant, with a banner and interactions neutralised. Works across profiles, incognito windows and devices since the bus rides the existing websocket (the server already re-broadcasts unrecognised messages, so no server changes were needed for transport). Broadcasts are debounced so fast slider drags only land the final position, and clients that didn't exist yet show their init state.
  • Global restore — "Restore all here" forks the whole system (all frontends + backend) back to a chosen point; timeline truncated, app keeps working from there.
  • Detached popup viewer — "⧉ Pop out" opens a real popup window that takes over from the inline panel (which hides, and comes back when the popup closes). It receives the full history on open via its opener, and acts as a remote control. It deliberately has no websocket, so it never counts as an app client (no onConnect/onDisconnect noise).

Recording is O(1) per event (frames hold references to the immutable models; Debug.toString only runs lazily for rendered rows/sections), history is capped at 1000 frames.

Implementation notes

  • New extra/LocalDev/runtime-src/Lamdera/TimeTravel.elm module + integration in Live.elm (record points at the four state mutations, bus handling, viewer mode).
  • extra/live.js: websocket bus relay (base64 for bytes), popup open/close lifecycle, opener→viewer postMessage relay, history dump.
  • Embedded-asset staleness fix (extra-source-files + addDependentFile + dev-build.sh content stamp): editing live.js or the runtime harness now actually rebuilds the embedded copies — plain cabal build used to say "Up to date" and silently ship stale assets.
  • test/scenario-time-travel/: demo app (backend-synced counter + purely local counter + onConnect sync) exercising all five event kinds.

How to test

./dev-build.sh
cd test/scenario-time-travel
$(cabal list-bin lamdera) live

Open a couple of tabs plus an incognito window, click both counters around, then hover the devbar → "Time Travel": scrub the slider (all windows follow), pop the panel out, restore to a past frame.

Verified with Playwright end-to-end suites covering single-tab (26 checks) and multi-client incl. incognito context, popup lifecycle, fast-scrub stress and global restore (27 checks).

- Unified event timeline: every client broadcasts its state mutations
  (frontend msgs, backend msgs, ToBackend/ToFrontend) with the resulting
  wire-encoded model over the live websocket, so every tab can inspect
  every client's FrontendModel plus the BackendModel at any recorded
  instant
- Master-tab scrubbing: selecting a past frame makes every connected
  client (other profiles, incognito windows and devices included)
  display its own state at that instant; debounced so fast slider drags
  only broadcast the final position, and clients that did not exist yet
  show their init state
- Global restore: fork the whole system (all frontends + backend) back
  to a chosen point in the timeline
- Detached popup viewer: real window.open popup that takes over from
  the inline panel, receives the full history on open via its opener,
  and hands back to the inline panel when closed; it has no websocket
  so it never counts as an app client
- Fix embedded-asset staleness: extra-source-files + addDependentFile +
  dev-build.sh content stamp so editing live.js or the runtime harness
  actually rebuilds the embedded copies instead of 'Up to date'
- test/scenario-time-travel: demo app with a backend-synced counter and
  a purely local counter to exercise both event paths, plus onConnect
  sync producing real BackendMsg events
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