thcThought Control

Elm time-travel debugger

Built-in Elm debugger: message history, jump back, export a session.

elm-lang.org ↗repo ↗docs ↗Debugger source (elm/browser src/Debugger) ↗Time Travel made Easy (2014, Elm Reactor) ↗Decoding an Elm 0.19 history export ↗Another step towards Elm 1.0 (2026-10-05) ↗elm/compiler ↗

license
BSD-3-Clause · elm/browser and elm/compiler are BSD-3-Clause. Development is BDFL-controlled (Evan Czaplicki); outside contributions rarely merged.
language
Elm (debugger UI) + Haskell (compiler emits --debug instrumentation)
backing
Evan Czaplicki; funded via Acadia (described in the 2026-10-05 post as 'the Patreon for Elm' and the server-side Elm effort).
stars
331 (as of 2026-10-09)
downloads
35,523/week npm 'elm' installer (2026-10-01..07); elm/compiler has 7,913 stars
latest release
elm/compiler 0.19.3 (debugger code unchanged since elm/browser 1.0.2), 2026-10-02
first release
2016-11
borrow

Same architecture as scene and the cleanest import/export design, but unusable outside Elm in a browser and closed to agents.

Borrow from it: take a specific idea, API or format.

the steelman: the best honest case for it

Elm's debugger is the purest demonstration that time travel is a property of the architecture, not a feature you build: because every change is a typed message through a pure update, the debugger is a small Elm program (six modules in elm/browser) that keeps a snapshot every 31 messages and replay the rest, and Evan's 2016 insight still holds — import/export is THE feature. A QA tester presses Export and the developer gets 'the perfect bug report', the exact session from clicks to HTTP responses, replayable in another browser. Better still, the export carries the program's message types, so an incompatible history is rejected with a friendly explanation instead of corrupting state — something JS tools cannot guarantee. And it doubles as a teaching tool: beginners literally watch messages arrive and the model change. A fan would say thc-scene needs nothing more than this, plus an agent-facing API.

scores
datadriveseeedittimeratetermpxteachmatureopen
UI as data1 scene 5View is Elm code; only the message history is exported as data.
Agent can drive it1 scene 5Agent can write code or craft an export file; nothing live.
Agent can see it1 scene 5Export file (messages only) or screenshots.
Small, targeted edits0 scene 5
Time travel3 scene 0Human seek/jump/scrub + import/export; no fork, no agent access.
Live data rate1 scene 5
Terminal native0 scene 5
Pixel graphics0 scene 4
Teaching3 scene 3Evan explicitly positions it as a tool for beginners to watch messages arrive and the model evolve; no tours/narration.
Maturity3 scene 1Shipped since 2016 and used in production Elm shops, but frozen since 2019 and Elm is niche.
Openness4 scene 4BSD-3, but single-person control and a closed contribution model.

Elm time-travel debugger scene now scene planned

how agents use it

How

None directly. The debugger is an in-page UI; there is no API, socket or MCP. An agent can only (a) generate Elm code, (b) read or write a history export JSON file, which a human imports in the browser, or (c) drive the page with a generic browser tool.

Wiring it in

High: would need a custom port/JS bridge or browser automation; the debugger's internal Msg type is not exposed.

Seeing the result

Only via an exported JSON file (messages + type metadata, not the model) or screenshots of the page.

Small edits

None. An agent could hand-edit an export (remove or alter messages) and have it imported, which is a crude edit-and-replay.

History

Human: jump to any message, slider scrub, resume, import, export. Agent: none beyond the export file.

In short

A beautifully minimal human debugger whose one agent-relevant artifact is the message-only export file.

architecture

Compiling with --debug wraps the program: every Msg passes through the debugger, which keeps History = {snapshots: Array {model, messages}, recent: {model, messages}, numMessages}. A model snapshot is kept every 31 messages (maxSnapshotSize); jumping to message n restores the nearest earlier snapshot and replays the pure update for the remaining messages. While paused, the app is frozen behind an overlay; Resume returns to the latest state (no branching). Export writes {metadata: {versions, types of Msg and aliases/unions}, history: [msgs]} as JSON; import checks the type metadata against the current program and explains incompatibilities before replaying.

performance

No published numbers. The only design budget in source is one snapshot per 31 messages.

adoption and upkeep

Adoption

Every Elm developer has it (on by default in elm reactor, --debug elsewhere), but Elm itself is niche. Stars above are for elm/browser, the package containing the debugger.

Used by: NoRedInk, Rakuten, IBM, Microsoft (listed on elm-lang.org)

Maintainability

Elm woke up in 2026 (two compiler releases, roadmap toward 1.0 aligned with the Acadia compiler) but the debugger has not changed in nearly seven years.

Releases: Compiler: 0.19.0 (2018-08-21), 0.19.1 (2019-10-21), then 0.19.2 (2026-07-06, faster builds) and 0.19.3 (2026-10-02, 'correct by construction' internals); post says a faster release cycle is planned. elm/browser (debugger): last commit 2019-11-06, tag 1.0.2. · Contributors: elm/browser 19; elm/compiler 106 (GitHub contributors API) · Recent: elm/compiler ~74 commits in last 90 days; elm/browser none since 2019. · Bus factor: low: one designer/maintainer decides everything

weaknesses
and thc-scene

Overlap

Identical architecture (serializable model, typed messages, pure update, pure view). scene's planned export/import and seek are this debugger's core features.

What scene would be reinventing

Snapshot-every-N + replay seek, and import/export of a message log. These are solved and small; scene should copy them, not design them afresh.

The gap it leaves

No agent API, no fork, no rendered-frame-at-time readback, no live data, no terminal, no narration/takeover.

What to borrow

  • Snapshot every N messages (Elm uses 31) and seek by replaying from the nearest snapshot.
  • Export = metadata (program/format version + message schema) + message list; validate the schema on import and explain incompatibilities in plain words.
  • Decide explicitly what an export omits (Elm omits model, effects, flags) — scene should include its initial pushed UI and data-source outputs so replay is deterministic.
  • Freeze input and show an overlay while paused in the past.
  • Message-level redaction hook before export (Elm lacked one; eSpark had to build it).
sources
  1. The Perfect Bug Report — Elm 0.18 (2016-11-14) elm-lang.org
  2. Time Travel made Easy (Elm Reactor, 2014) elm-lang.org
  3. elm/browser Debugger/History.elm (maxSnapshotSize, History type) github.com
  4. elm/browser Debugger/Main.elm (Msg type) github.com
  5. Decoding an Elm 0.19 History Export (eSpark) medium.com
  6. Another step towards Elm 1.0 (2026-10-05) elm-lang.org
  7. elm/compiler releases 0.19.2 / 0.19.3 github.com
  8. elm-lang.org home (company logos) elm-lang.org