drindr
dsh-rerun
embed Rerun into DSH
- Stars
- 0
- Language
- TypeScript
- Created
- Aug 17, 2026
- Updated
- Aug 17, 2026
Introduction
dsh-rerun
Embed the Rerun web viewer in the dsh web GUI: browse .rrd
recordings from the workspace root and connect to live rerun --serve streams —
all inside the harness, fully offline.
What it does
- A Rerun button in the sidebar footer opens a full-window overlay.
- Recordings mode lists
.rrd/.rrd.zstfiles under the workspace root (env override:DSH_RERUN_RECORDINGS_DIR) and renders the selected one. - Live mode connects the viewer to a running Rerun server, e.g. started
with
rerun --serve-web(note: the flag is--serve-web, not--serve). The default URL is shown/edited in the namespaced alias formrerun+<page-scheme>://<page-host>/dsh-rerun/live— the same origin as the GUI. Because it is same-origin there is no CORS and no mixed content, so Live works on any GUI access scheme —http://127.0.0.1:3080,http://<host>, orhttps://<host>. How the 0.34.1 viewer actually connects (verified via Network capture):- the URL is only a parse token: the viewer accepts live URLs whose
path is exactly
/proxy(host/scheme free; any other path — including the/dsh-rerun/livealias — fails as "Failed to parse URL"), - the real connection is grpc-web to
<origin>/rerun.sdk_comms.v1alpha1.MessageProxyService/<Method>(OPTIONS preflight + POST streaming) —/proxyitself is never fetched. The panel therefore speaks the alias and maps it to the canonical/proxypath before the viewer sees it (canonicalLiveUrlinsrc/client/store.ts); typing the canonical form directly also works. The host half registers a passthrough to the rerun server (http://127.0.0.1:9876, env override:DSH_RERUN_LIVE_ORIGIN) at both/rerun.sdk_comms.v1alpha1.MessageProxyService/*(the actual live stream) and/proxy//dsh-rerun/live/*(parse-token safety + legacy URLs). On an https page the one remaining prerequisite is that the browser trusts the GUI's TLS certificate — fetch has no "proceed anyway" path (page loads can be bypassed, fetches cannot). Until it is trusted the panel shows a 证书不受信任 hint. One-time trust for Caddy's internal CA:
(or importsudo cp ~/.local/share/caddy/pki/authorities/local/root.crt /usr/local/share/ca-certificates/ sudo update-ca-certificatesroot.crtinto the browser's CA store), then restart the browser. - the URL is only a parse token: the viewer accepts live URLs whose
path is exactly
How it stays light (lazy load + local cache)
The viewer is a 48 MB WASM binary. This plugin never ships it in the client bundle and never hits a CDN:
- The host half serves the viewer straight out of the installed
@rerun-io/web-viewernpm package (a plain dependency → package-manager cache = local disk cache). Seesrc/index.ts→findViewerRoot. - The client half only carries an 8 KB panel UI. The viewer iframe — and therefore every viewer byte — is fetched only when the overlay opens.
- Versioned asset URLs (
/dsh-rerun/viewer/0.34.1/…) are served withCache-Control: immutable, so after the one-time 48 MB fetch every re-open is instant from the browser cache. The wasm response also setsrerun-final-lengthso the viewer shows a download progress bar.
Version alignment
@rerun-io/web-viewer@0.34.1 matches the local rerun-cli 0.34.1. Keep the
dependency in lockstep with the Rerun SDK/CLI that produces your .rrd files
(the viewer reads files from the previous minor version and speaks the matching
gRPC protocol for live streaming).
Live operations & troubleshooting
Quick start for a live demo with data streaming in real time:
rerun --serve-web # gRPC server on :9876 (+ web viewer on :9090)
python feed-live.py # any rerun SDK: connect_grpc + rr.log in a loop
# or: rerun --serve-web demo-dna.rrd (pre-loaded file)
rerun-sdk 0.36 API changes vs older docs: connect → connect_grpc,
set_time_sequence → set_time(..., sequence=), Scalar → Scalars.
Stream connection hygiene — the /proxy passthrough is a long-lived
stream, and the viewer drops it whenever the overlay closes. The host half
destroys the upstream connection on client close
(res.on('close') → proxyReq.destroy()); without that, every closed viewer
leaks one idle TCP connection into the rerun server. Symptoms of the leak:
the viewer renders an empty light screen and the rerun server log fills with
re_quota_channel: Sender has been blocked for over 5 seconds…. Diagnosis:
ss -tn | grep 9876 shows a client socket with a multi-MB Send-Q/Recv-Q
that never drains. Leaked sockets live in the dsh web process, so they
survive rerun server restarts — clearing them requires a dsh web restart
(which also loads any rebuilt lib/index.js).
Note that the rerun server and the data feeder are external processes, not
owned by the plugin — if Live shows an empty screen, check they are still up
(ss -tln | grep 9876) before anything else.
Host routes
| Route | Purpose |
|---|---|
GET /dsh-rerun/viewer/<version>/ | iframe entry page |
GET /dsh-rerun/viewer/<version>/index.js, re_viewer.js, re_viewer_bg.wasm | viewer assets (immutable) |
GET /dsh-rerun/recordings/<rel>.rrd | workspace recording (traversal-safe) |
GET /dsh-rerun/api/recordings | { recordings[], viewerVersion } |
GET /dsh-rerun/api/health | { ok, viewerVersion, recordingsRoot } |
Routes mount on the existing web server (ctx.webServer.register), so there is
no second port and no CORS. Same-origin iframe → no CORS shenanigans.
Build
pnpm install --store-dir <writable-store> --cache-dir <writable-cache> # sandbox note below
node build.mjs # -> lib/index.js (host ESM) + lib/client.js (browser CJS)
node watch.mjs # dev watcher (optional)
Sandbox note: pnpm's default store sqlite cannot open under the harness file sandbox; point
--store-dir/--cache-dirat a writable location (the repo uses.pnpm-store/.pnpm-cache).
Wiring into a profile
ln -s <repo>/dsh-rerun $DSH_HOME/plugins/dsh-rerun- Add
"dsh-rerun": "link:../../plugins/dsh-rerun"to the profile'spackage.jsondependencies andpnpm install. - Add a loader entry to the profile's
cordis.patch.yml:- insert: - id: rerun name: dsh-rerun - Restart
dsh web— new plugins are loader entries fixed at boot.
Dev iteration after restart
lib/client.jschanges hot-swap without a page refresh (dsh-client-hmr polls the bundle).lib/index.jschanges need anhmrwatch row covering this package's real path in the profile'scordis.patch.yml, otherwise adsh webrestart.