Skip to content

Add time-travel debugging (record button + timeline slider)#2774

Draft
NullVoxPopuli-ai-agent wants to merge 1 commit into
emberjs:mainfrom
NullVoxPopuli-ai-agent:time-travel-debugging
Draft

Add time-travel debugging (record button + timeline slider)#2774
NullVoxPopuli-ai-agent wants to merge 1 commit into
emberjs:mainfrom
NullVoxPopuli-ai-agent:time-travel-debugging

Conversation

@NullVoxPopuli-ai-agent

Copy link
Copy Markdown

Time-travel debugging

This PR adds a Time Travel tab to the inspector: a record button and a timeline slider that scrubs the inspected app's state back and forth.

Time travel demo: recording state changes in an app, then scrubbing the timeline slider back and forth while the app's UI rewinds and replays

(mp4 / webm versions of the demo)

How it works

Ember has no single state atom, but long-lived app state overwhelmingly lives in tracked/settable properties on container-managed singletons. The new ember-debug/time-travel-debug.js module:

  • Record: while recording, hooks _backburner.on('end') (the same "runloop settled" signal render-debug uses) and captures a snapshot of every serializable property of all service:/controller: singletons in the container cache. Consecutive identical states are deduplicated; the buffer is capped at 200 snapshots.
  • Travel: writes a snapshot's values back through set() / tracked setters (only properties that actually differ), which triggers Glimmer revalidation — the app re-renders itself at that point in time. A guard prevents the restore's own runloops from being recorded.
  • Captured properties: own enumerable data properties, plus prototype-chain accessors that have both a getter and a setter (the shape @tracked produces). Getter-only accessors are derived state and are skipped. Only plainly serializable values (primitives, dates, plain arrays/objects) are recorded — class instances are rejected so restoring can never corrupt identity-bearing objects.

The UI side is a standard tab (route/controller/template + nav entry) talking over the existing port protocol (timeTravel:* messages). The record button and clear button live in the toolbar; the slider sends timeTravel:travel on input, so scrubbing restores state live as you drag. The timeline shows the snapshot index, capture time, and the URL it was captured at.

Known limitations (also explained in the tab's empty state)

  • Component-local state, Ember Data records, and non-serializable objects are not restored.
  • The router URL is captured for display but not restored (transitioning would re-run model hooks).

Testing

  • New tests/ember_debug/time-travel-debug-test.js: records tracked controller state, travels back/forward and asserts values are restored, verifies dedup of unchanged runloops and clear.
  • Full Ember Debug suite passes (84 tests); eslint/template-lint/build clean.

Notes from building the demo

The demo runs the bookmarklet build injected into classic-test-app (Ember 7). Two pre-existing issues bite late injection into webpack-built Ember 7 apps and had to be worked around in the demo harness (not addressed in this PR):

  1. entrypoints/utils/has-ember.js never resolves: there is no ember barrel module and no window.Ember in Ember 7, and the Ember window event fired long before injection.
  2. object-inspector.js's GlimmerValidator.tagFor/trackedData patching throws (Cannot set property tagFor of #<Object> which has only a getter) because the webpack module namespace is getter-only — same problem the Vite guard (globalThis.emberInspectorApps) already dodges.

Happy to file these separately.

🤖 Generated with Claude Code

Adds a Time Travel tab to the inspector with a record button and a
timeline slider. While recording, ember_debug captures a snapshot of
every serializable property of the app's services and controllers at
the end of each runloop (deduplicating unchanged states). Dragging the
slider writes a snapshot's values back through set()/tracked setters,
which triggers Glimmer revalidation and re-renders the app at that
point in time.

Only plainly serializable values (primitives, dates, plain arrays and
objects) are recorded and restored. Component-local state, Ember Data
records, and the router URL are not restored; the URL is captured for
display in the timeline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

2 participants