a native debugger that lives in your browser — built in Rust, UI in Vue
Attach to a running process (or launch one, or open its core dump) and watch it: image buffers render as actual images while the program runs, container internals decode to their real values, and every number on screen is a live watch — re-resolved from the variable's name on every read, so nothing goes stale.
see your data, not your bytes
Attached to a running C++ path tracer. An image watch on
s_Instance.m_LayerStack[0].m_Renderer.m_ImageData pulls the framebuffer out of
the live process and renders it — an 820×614 viewport streaming at 10 Hz, next to the
variable tree that produced it. No instrumentation in the target, no snapshot button: the
pixels are read straight out of its memory.

follow the pointer to the real thing
That buffer lives four hops deep — a std::vector<std::shared_ptr<Layer>>,
a unique_ptr<Renderer>, another shared_ptr. debugr decodes
libstdc++ containers with use counts, and follows a shared_ptr<Layer> to the
real MainLayer behind it — vtable-resolved, print object done automatically —
so the path lands on the derived fields instead of a base-class stub.

the whole watch story on one screen
Every panel — source, disassembly, charts, images, symbol tables — is a widget on a grid surface you arrange yourself. Right-click a number and chart it while the process runs; right-click a buffer and pin it as an image. Open as many as you like, tab multiple surfaces, and put one on each monitor: two browser windows share one session.

write, don't just read
A watch is not read-only. Double-click a value to edit it: g_pixels[2] = 0xdeadbeef
resolves through the same pointer path, encodes per the DWARF type, size-checks it, and
pokes the live process — the next read shows the change. Leaves only; a type mismatch or an
out-of-bounds index fails loudly without touching memory.
teach it your types
The libstdc++ decoding ships in-tree for speed, but it's just the built-in of a plugin interface. A data-model plugin makes a container iterable, a handle dereferenceable, an element addressable — always returning real addresses and types, never pre-rendered text. That one invariant is why traversal, image watches, and the write path all compose on top of a type debugr has never seen — the CV north star: your proprietary image buffers, first-class.
// what a value IS, structurally — addresses, never rendered text interface data-model { summarize: func(value: value-info) -> option<string>; children: func(value: value-info) -> option<child-set>; deref: func(value: value-info) -> option<target>; } // child-set = sequence{addr, count, elem-type} | named{(name, addr, type)…}
tracepoints — debugging without stopping
A tracepoint is a breakpoint that doesn't stop: each hit captures the values you asked for
and streams them over a WebSocket while the process runs at full speed. The XY view draws
the captured path, and the ledger says exactly what you got —
captured · hits · dropped, never a silent gap. One click records the
unthrottled stream to compressed parquet and loads it back for replay.
one place for the whole binary
Symbols, types, globals, namespaces, and a section-size treemap — the ELF/DWARF explorer, as widgets on one surface. Every name is a demangled entity with the same right-click menu wherever it appears: a function goes to source or takes a breakpoint, a global becomes a watch, an address opens in hex. If you can read it, you can act on it.

under the hood