15ee6d5f4f
Iteration 2 (serve half) of the visual face — spec 0056 / plan 0056:
read a persisted run's traces and serve them as a self-contained uPlot
HTML chart. Completes the goal "tap data from a graph, serve it as a
chart in the browser".
- chart-viewer.js (first-party): a pure buildCharts(traces, mode) that
maps the injected window.AURA_TRACES to uPlot configs (no DOM) — overlay
= one plot, every series on its own y-scale over the shared xs; panels =
one plot per series, all on the same timestamp x. A browser-only mount()
instantiates the vendored global uPlot. Guarded by a node .mjs DOM test
(+ a sparse/gaps variant) shelling out exactly like viewer_dot.rs.
- render_chart_html + ChartMode/ChartData/Series (aura-cli/render.rs):
mirrors render_html — self-contained page, uPlot (1.6.32, vendored
d534f10) + chart-viewer.js inlined via include_str!, data injected as
window.AURA_TRACES.
- emit_chart + build_chart_data (aura-cli/main.rs): the spec-§6 3-step
union-spine alignment — xs = sorted-dedup union of every tap's ts; a
synthetic empty-payload spine + ALL taps as symmetric join_on_ts sides
(no tap privileged, no point dropped); flatten each (tap,column) to a
Series of Option<f64>. New arms `aura chart <name> [--panels]`; a
missing run is stderr + exit 2 (TraceStoreError::NotFound), never a
panic. USAGE advertises the verb in lockstep.
Verified by the orchestrator: `cargo test --workspace` green (incl. the
node guards, render_chart_html, and the chart e2e); `cargo clippy
--workspace --all-targets -D warnings` exit 0; and a real end-to-end —
`aura run --trace demo && aura chart demo` emits a 57KB self-contained
page (doctype + inlined uPlot + AURA_TRACES carrying the equity/exposure
series), `--panels` reflects the panels mode, `aura chart nope` exits 2.
Implementer deviation folded in (compiler-prescribed): serde added to
aura-cli/Cargo.toml for the Series derive (Cargo.lock updated).
refs #101
49 lines
2.7 KiB
JavaScript
49 lines
2.7 KiB
JavaScript
// Headless guard for the trace-chart viewer (chart-viewer.js) gap-handling.
|
|
// Property protected: when taps fire at DIFFERENT cadences, the CLI's union-spine
|
|
// alignment (spec 0056 §6) null-fills the timestamps a tap did NOT fire at, and
|
|
// buildCharts must hand those nulls to uPlot VERBATIM as the data array — a null is
|
|
// a rendered gap, never dropped, never coerced to 0. This is the observable end of
|
|
// the "no tap's points dropped, no privileged spine" contract: a positional shift or
|
|
// a `null -> 0` coercion would silently misalign every series past the first gap.
|
|
//
|
|
// It loads the real viewer module (a regression there is observed here) and drives
|
|
// the exported pure `buildCharts` over a fixture whose two series have nulls at
|
|
// DIFFERENT positions; uPlot itself (which needs `document`) is never instantiated.
|
|
|
|
import { createRequire } from "node:module";
|
|
import { readFileSync } from "node:fs";
|
|
import { fileURLToPath } from "node:url";
|
|
import { dirname, join } from "node:path";
|
|
|
|
const require = createRequire(import.meta.url);
|
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
const traces = JSON.parse(readFileSync(join(here, "fixtures", "sparse-traces.json"), "utf8"));
|
|
|
|
const fail = (msg) => { console.error("FAIL: " + msg); process.exit(1); };
|
|
const sameArr = (a, b, msg) => {
|
|
// strict identity per slot, so a null that became 0 (or a shifted point) fails.
|
|
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length || a.some((v, i) => v !== b[i])) fail(msg);
|
|
};
|
|
|
|
// No `document` global -> the browser mount is skipped; we get only the pure export.
|
|
global.window = { AURA_TRACES: traces };
|
|
const { buildCharts } = require(join(here, "..", "assets", "chart-viewer.js"));
|
|
|
|
const eqPoints = traces.series[0].points; // [0.0, 0.001, null, 0.004]
|
|
const exPoints = traces.series[1].points; // [1.0, null, null, -1.0]
|
|
|
|
// overlay: the data array is [xs, ...each series' points], nulls preserved per slot.
|
|
const overlay = buildCharts(traces, "overlay")[0];
|
|
sameArr(overlay.data[0], traces.xs, "overlay x is the shared union xs");
|
|
sameArr(overlay.data[1], eqPoints, "overlay equity points carry their null gap verbatim");
|
|
sameArr(overlay.data[2], exPoints, "overlay exposure points carry their null gaps verbatim");
|
|
|
|
// panels: each panel is [xs, its own series' points] — same nulls, same shared xs.
|
|
const panels = buildCharts(traces, "panels");
|
|
sameArr(panels[0].data[0], traces.xs, "panel 0 shares the union xs");
|
|
sameArr(panels[0].data[1], eqPoints, "panel 0 carries equity's null gap verbatim");
|
|
sameArr(panels[1].data[1], exPoints, "panel 1 carries exposure's null gaps verbatim");
|
|
|
|
console.log("OK — null gaps preserved verbatim across overlay + panels, shared union xs.");
|
|
process.exit(0);
|