|
|
Donner 0.5.1
Embeddable browser-grade SVG2 engine
|
Files | |
| ReproFile.h | |
| Data model for a .donner-repro file: a recorded sequence of editor UI inputs (mouse events, keyboard events, wheel events, window resizes) plus enough session metadata to re-instantiate the editor and replay the events deterministically. | |
| ReproRecorder.h | |
| Live UI-input recorder. Install one instance in EditorShell when the user passes --save-repro <path>; call snapshotFrame() once per editor frame (before any UI widgets have consumed input events — right after window_.beginFrame() / ImGui::NewFrame()). On process exit, call flush() to serialize the recording to the destination path. | |
A .donner-repro file is a recording of the ImGui input state captured at the top of every editor frame. Because the recording happens at the raw input boundary — below menu action dispatch, below tool dispatch, below the compositor — a replay exercises every stage of the stack. Whether the bug lives in the renderer, the DOM, the RIC, the event bridge, or somewhere in between, a recording made in the live editor reproduces it.
Pass --save-repro <path> when launching the editor:
Do whatever produces the bug: click, drag, scroll, type, toggle menu items, resize the window. Every ImGui frame captures the full input state plus any discrete events that fired during that frame.
Close the editor (window X button or Cmd+Q) to flush the recording to disk. You'll see on stderr:
If the editor crashes before flush, the recording is lost — the in-memory buffer isn't streamed to disk during recording. (Reason: streaming would complicate the atomic-rename pattern we use for crash-safe writes. If this becomes a problem in practice, we'll revisit.)
.donner-repro is NDJSON — one JSON object per line. head, cat, jq, and your text editor all work:
Because the format is line-oriented, you can trim or annotate a recording in your favorite editor.
Line-level edits are safe: each line parses independently. If you break the JSON on one line, the loader rejects the whole file with a specific error.
A .donner-repro + a description of what you expected is a high- fidelity bug report that's easier to act on than a screenshot. Suggested template:
With the .donner-repro in hand, the maintainer can:
See ReproFile.h for the data model. Compact summary:
Button mask bits: 1<<0=left, 1<<1=right, 1<<2=middle.
Modifier mask bits: 1<<0=Ctrl, 1<<1=Shift, 1<<2=Alt, 1<<3=Super.
Event kinds (tag in k field):
| Kind | Fields | Notes |
|---|---|---|
| mdown / mup | b (button 0-4) | Edge of a bit in the frame's button mask |
| kdown / kup | key (ImGuiKey int), m (modifier mask) | See the watchlist in ReproRecorder.cc |
| chr | c (UTF-32 code point) | From io.InputQueueCharacters |
| wheel | dx, dy (float) | Per-frame wheel delta when non-zero |
| resize | w, h (int) | ImGuiIO::DisplaySize changed |
| focus | on (0/1) | Window focus gained/lost |
The continuous signal (mx, my, btn, mod) is captured every frame; discrete events are emitted only on transitions. Replay logic can rely on the continuous signal as the source of truth and treat discrete events as hints — the next frame's state wins regardless.
IS recorded:
IS NOT recorded:
Stage 2 of this design. See docs/design_docs/0029-ui_input_repro.md for the architecture and open questions. Summary: a headless driver that stands up an ImGui context + a mock EditorWindow, injects the recorded input events frame-by-frame, and compares the final render-pane bitmap against a committed golden PNG.
Until Stage 2 lands, recordings are still useful as high-fidelity bug reports. Share the .donner-repro file with the maintainer; they can read the event stream, understand your exact sequence, and either build a synthetic test from it or run it through the player once it's built.
Why record raw ImGui input, not higher-level events? Because you don't know where the bug lives. Recording at SelectTool::onMouseDown misses menu clicks, keyboard shortcuts, sidebar interactions, text editor input, pinch gestures, and everything ImGui handles before dispatch reaches tools. Recording at the GLFW layer is even better in principle but requires platform-specific marshalling at replay. ImGui input is the sweet spot: below all dispatch, above platform variance.
Why NDJSON instead of a binary format? Human-readable. Hand-editable. Diffable. Line-oriented so you can trim one interaction out of a long session with sed. The overhead vs binary is ~3-5x in file size, which matters zero for a tool that produces 5 MB files.
Why flush on shutdown instead of streaming to disk? The atomic-rename pattern that protects against crash-mid-write doesn't compose cleanly with streaming. We could add an "append mode" later if someone records a bug that crashes the editor before flush; for now, crashes-before-flush lose the recording and that's understood.
See the design doc (docs/design_docs/0029-ui_input_repro.md) for the full set of alternatives considered and open questions.
--save-repro flag rejected. You need a filename argument immediately after the flag: --save-repro /tmp/foo.donner-repro, not --save-repro alone.
No [repro] wrote N frames … message on exit. Check that you actually passed the flag and that the editor didn't crash. If it crashed, the buffer wasn't flushed.
Loader rejects the file. Check the stderr output — it prints the specific line and error. Common causes: file truncation (flush didn't complete), version mismatch (file from an older format), hand-edit broke a line's JSON syntax.
Frame count is huge for a short session. Expected: the recorder snapshots every frame the editor renders, which includes idle-animation-repaint frames. 60 fps × 10 sec = 600 frames even if you just moved the mouse once.
Recording file is empty. The metadata-only case (recording started but zero frames captured) can happen if you close the editor before it draws one frame. Try recording again and ensure at least one render happens before close.