feat(wasm-viewer): Save as Report works offline#924
Merged
thinkingfish merged 2 commits intoMay 13, 2026
Conversation
…e crate
The static-site WASM viewer's Save as Report button was POSTing to
`/api/v1/save_with_selection`. On rezolus.com (a static deployment
with no backend) the CDN returned 405 Method Not Allowed. Port the
trim + tarball-repack flow into a shared workspace crate so both the
server viewer and the WASM viewer run the same projection logic.
## Shared crate: `crates/report-save/`
Single source of truth for the trim/embed/tarball flow:
- `ReportPayload` / `ReportEntry` / `Side` payload types
- `resolve_kept_columns` (generic over `T: Deref<Target = Tsdb>`)
- `save_single_parquet(Bytes, …) -> Vec<u8>`
- `save_combined_ab_tarball(Bytes, Bytes, manifest_bytes: &[u8], …) -> Vec<u8>`
- All operating on `metriken_query::Bytes` — the unified primitive
the server reads paths into and the WASM viewer already holds.
- 10 unit tests covering kept-column resolution + the full round-trip
for both trim/no-trim and single/AB paths.
The shared crate is manifest-agnostic: it takes pre-serialized
manifest bytes for the AB tar entry, so it doesn't need to know about
either crate's `AbContainers` struct.
## Server-side shim: `src/viewer/report_save.rs`
Reads paths into `Bytes`, serializes the binary crate's `AbContainers`,
delegates to `report-save`. ~70 LOC (was ~250 LOC of trim logic + tests).
## WASM-side shim: `crates/viewer/src/report_save.rs`
Synthesizes an `AbContainers` manifest from the two attached viewers
(compare mode loads two separate parquets — there's no pre-existing
tar manifest), serializes it, delegates to `report-save`. The WASM
crate keeps its own `AbContainers` definition since it doesn't depend
on the rezolus binary crate.
`Viewer` now keeps the original parquet `Bytes` alongside the Tsdb so
the trim writer can project columns from the source schema (the Tsdb
has lost Arrow field metadata like the `metric` key that
`parquet filter`'s keep-field predicate needs).
`WasmCaptureRegistry::save_with_selection(payload_json)` dispatches
based on slot attachment.
## Unified JS surface
Both `site/viewer/lib/viewer_api.js` (WASM) and
`src/viewer/assets/lib/viewer_api.js` (server) expose
`saveWithSelection(payload) -> { bytes, mime, extension }`.
`saveToParquet` in `selection.js` calls through `ViewerApi` instead of
raw XHR — swaps the filename's `.parquet` suffix for the reported
extension so AB tarballs land as `*.parquet.ab.tar`.
## Cost
WASM bundle: 3.6 MB → 4.3 MB (parquet writer + tar). The `no_asm`
zstd-sys feature is target-scoped — resolver 2 doesn't propagate it
to `cargo build --bin rezolus`, so the standalone binary's zstd
keeps its x86 asm path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5c35b42 to
29fae38
Compare
Regenerated via the new WASM-side save path to exercise it end-to-end in the static site. 125799 → 126004 bytes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The static-site WASM viewer's Save as Report button was POSTing to
/api/v1/save_with_selection. Onrezolus.com(a static deployment with no backend) the CDN returned 405 Method Not Allowed. Port the trim + tarball-repack flow into the WASM crate so the save runs entirely in-browser against the bytes already loaded.Changes
crates/viewer/src/report_save.rs(new) — mirror ofsrc/viewer/report_save.rsoperating onBytes(which the WASM Tsdb already holds) instead of file paths. Same trim/embed semantics, sameKEY_REPORTmarker, same AB tar layout. Duplicates rather than extracts a shared crate; the surface is small enough that the drift risk is manageable. We can refactor into a workspace crate later if the surface grows.crates/viewer/src/lib.rs—Viewernow keeps the original parquetBytesalongside the Tsdb so the trim writer can project columns from the source schema (the Tsdb has lost Arrow field metadata like themetrickey thatparquet filter's keep-field predicate needs).WasmCaptureRegistry::save_with_selection(payload_json)dispatches to single-parquet or combined-A/B based on slot attachment. For AB, anAbContainersmanifest is synthesized from each slot's alias + source field (the static site loads two separate parquets, never a real tar manifest).Unified JS surface — both
site/viewer/lib/viewer_api.js(WASM) andsrc/viewer/assets/lib/viewer_api.js(server) gainsaveWithSelection(payload) -> { bytes, mime, extension }.saveToParquetinselection.jscalls throughViewerApiinstead of raw XHR, swapping the filename's.parquetsuffix for the reported extension (so AB tarballs land as*.parquet.ab.tar).Cost
WASM bundle: 3.6 MB → 4.3 MB (parquet writer + tar). The
no_asmzstd-sys feature is target-scoped — feature resolution under resolver 2 doesn't propagate it tocargo build --bin rezolus, so the standalone binary's zstd compression keeps its x86 asm path (no native perf regression).Test plan
cargo clippy --all-targets -- -D warningscleancargo test— 169 passed (server-side helpers untouched)node --test tests/*.mjs— 82 passedbash tests/viewer_smoke.sh— full pass (server viewer unaffected, still POSTs)wasmcaptureregistry_save_with_selection🤖 Generated with Claude Code