AGENTS.md
What this is
Hecatomb (heca) is an AG Grid debugger. One crate builds two artifacts:
- a wasm32
cdylib(src/lib.rs→src/wasm/) — the debugger UI/logic injected into a host page; scans for AG Grid tables and renders into every.hecatombmount point. - a native
hecabin (src/main.rs) — packages the wasm into a JS bundle and serves a local dev page.
Distribution model: the build output target/hecatomb.js is a single self-contained file (base64 wasm + inlined CSS, no external assets) that a user drops into any page via one <script> tag to get a full debugger — including man-in-the-middle injection / data overwrite and analysis. Every feature must preserve this single-file, zero-runtime-dep property; this is why the wasm is base64-inlined and t.css is inlined into the view.
Build & run (read this first)
.cargo/config.toml sets the default target to wasm32-unknown-unknown, so plain cargo build/check/run target wasm.
- The
hecabin MUST be run with--target x86_64-unknown-linux-gnu, e.g.cargo run --bin heca --target x86_64-unknown-linux-gnu. Omitting the target builds the bin for wasm and fails. - Run the bin via
cargofrom the repo root: paths resolve fromCARGO_MANIFEST_DIR(falls back to cwd), so running the raw binary elsewhere breakstarget/lookup.
Use the justfile:
just build— compiles the wasm lib (--release --target wasm32-unknown-unknown --lib), then runs the bin to base64-inlinetarget/wasm32-unknown-unknown/release/hecatomb.wasmintotemplate.js, writingtarget/hecatomb.js+target/index.html.just serve— builds, then serves http://127.0.0.1:10000. Override port:... --bin heca --target x86_64-unknown-linux-gnu -- serve <port>(or-- serve --port N).
target/ is gitignored and regenerated by just build. No tests, lint, or CI exist.
Architecture / FFI (non-obvious)
- No wasm-bindgen / web-sys / js-sys. The wasm is loaded raw via
WebAssembly.instantiateinsrc/template.js. Do not introduce bindgen tooling. - All JS/DOM access crosses a hand-rolled FFI:
- Rust imports host fns via
unsafe extern "C"(console_log,mount_view,ag_grid_count,ag_grid_label,ag_grid_schema,ag_grid_inject,download_text,config_text) and exportsrun/rescan/generate/save_config/apply_configvia#[unsafe(no_mangle)] pub extern "C"(edition 2024 syntax). - Strings pass as
ptr + leninto wasm linear memory; JS reads/writeswasm.instance.exports.memory.buffer. No serde/JSON. Structured data (schemas, generated rows) is encoded by hand with ASCII Unit/Record separators — seesrc/wasm/wire.rsand the matching constants intemplate.js. Variable-length reads use the "needed length" retry pattern (wire::pull): the JS importer returns the full byte length and Rust grows its buffer and calls again. - To add a JS capability: add an
unsafe extern "C"import in Rust and the matchingenv.<name>fn insrc/template.js.
- Rust imports host fns via
- DOM scanning and grid-API reads live in JS (
template.js:scanGrids/describevia.ag-root-wrapper; real row data viaagGrid.getGridApi(...)+getDisplayedRowAtIndex/node.data);src/wasm/grid.rspulls results across FFI. Synthetic-data inference + generation lives in Rust (src/wasm/model.rs), config save/load text format insrc/wasm/config.rs, PRNG insrc/wasm/rng.rs. src/template.jsandsrc/template.htmlareinclude_str!'d intomain.rs;{{}}intemplate.jsis replaced with the base64 wasm. Editing templates requires re-runningjust build.- wasm modules are gated behind
#[cfg(target_arch = "wasm32")](lib.rs,external.rs); the native bin build compiles no wasm code. - UI markup is built with
horrorshowinsrc/wasm/view.rs; styles come fromsrc/t.css(Tachyons),include_str!'d and inlined so the panel is self-contained. The UI is a fixed bottom-right flyout: a small square launcher toggles a panel (open state lives in JS —panelOpen/hecatomb-open— and is re-applied after everymount_view, since each render replacesinnerHTML). Flyout chrome + the colour theme are a scopedFLYOUT_CSSblock inview.rs(positioning/animation Tachyons can't express, plus a documented warm "Daily Stoic" parchment/ink/amber palette). Theme colours hang off the functional element classes (.hecatomb-rescan,.heca-generate,.heca-card, ...) so the markup carries only layout utilities; the palette table is the doc comment aboveFLYOUT_CSS.
Conventions / constraints
- Minimal dependencies: target 0–2 crates, prefer zero. Currently exactly two:
base64(bin packaging) +horrorshow(wasm-side HTML). A new dep is a deliberate decision, not a default. - Put performance-critical work (e.g. scanning and generating row data) in Rust/wasm, not JS.
Features
Reads a grid's row data via the grid API, generates similar synthetic data, and saves/reloads a per-table config file — all reachable from the per-grid controls in the panel (Generate / Save config / Load config). Both client-side and server-side row models are supported: reads sample loaded rows through getDisplayedRowAtIndex/node.data; injection sets rowData for client-side and swaps the serverSideDatasource (and datasource for infinite) for server-side. Column types are inferred in Rust (model.rs: bool/int/float/date/enum/free-text) and rows are generated with a seeded PRNG (rng.rs). Config files are a readable tab-separated format (config.rs), saved via download_text and reloaded via a hidden file input → config_text → apply_config.
src/template.html is only a local test fixture (copied to target/index.html by the build, served by just serve) and is not part of the distributed artifact; it holds one client-side (#example-grid) and one server-side (#server-grid) ag-grid-enterprise example grid.