From fd0e28657d819868e82869bcf5e1adb17b4ee0e3 Mon Sep 17 00:00:00 2001 From: serversdown Date: Sat, 23 May 2026 19:39:18 +0000 Subject: [PATCH] sfm_webapp: default to Database view + sortable columns + inline waveform plot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three UX upgrades to the main SFM webapp at /, all reinforcing the 'browse stored events' flow as the primary entry point: 1. Default section is now Database, not Live Device. Most users land here to look at stored events; Live Device is opt-in (click the tab to talk to a unit). Initial history + units fetch fires on first paint so the table is populated when the page loads. 2. History table columns are sortable. Click any header to sort: timestamp, serial, per-channel PPV (Tran/Vert/Long), PVS, mic dB(L), project, client, type, key. Default direction varies by column type (desc for numbers + timestamps, asc for text). Sort arrows appear in the active column header. Headers are sticky so they stay visible while scrolling. 3. Click-event-to-see-waveform. The existing sidecar review modal now renders the 4-channel waveform plot inline at the top, fetched from /db/events/{id}/waveform.json in parallel with the sidecar fetch. Channels stacked MicL / Long / Vert / Tran (Instantel printout order), shared bottom time axis, dashed trigger line + triangle markers at t=0, zero baseline with "0.0" label on the right edge, peak callouts per channel. Charts cleaned up on modal close. Resolves the "where is the viewer" surprise — operators no longer need to know about the /events route to see waveforms. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 3 +- sfm/sfm_webapp.html | 339 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 312 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 886a0a8..03bf500 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,8 @@ All notable changes to seismo-relay are documented here. ### Added -- **Stored-event browser** — new standalone HTML page at `GET /events` (`sfm/event_browser.html`). Pick a serial from the unit dropdown, scroll through that unit's events (newest-first), click any event to render its decoded waveform via the existing `/db/events/{id}/waveform.json` endpoint. Dark-themed Chart.js viewer, channels stacked vertically (Tran / Vert / Long / MicL — Instantel printout order, designed PDF-export-ready), trigger line at t=0, peak labels, search/filter, false-trigger flag honored. Companion to the existing live-device viewer at `/waveform`; the two routes are now clearly delineated in their docstrings. +- **SFM webapp now opens to Database view by default** and the History table is fully interactive. Click any column header to sort ascending / descending (timestamp, serial, per-channel PPV, PVS, mic dB(L), project, client, record type, key — all sortable). Click any event row to open the event modal, which now renders a **4-channel waveform plot inline** (MicL / Long / Vert / Tran stacked, Instantel-printout order) alongside the existing sidecar review fields. Headers are sticky so the columns stay visible while scrolling long event lists. No more "where is the viewer" — pick a unit from the filter dropdown, scan the table, click the event, see the waveform. +- **Stored-event browser** — new standalone HTML page at `GET /events` (`sfm/event_browser.html`). Pick a serial from the unit dropdown, scroll through that unit's events (newest-first), click any event to render its decoded waveform via the existing `/db/events/{id}/waveform.json` endpoint. Dark-themed Chart.js viewer, channels stacked vertically (MicL / Long / Vert / Tran — Instantel printout order, designed PDF-export-ready), trigger line at t=0, peak labels, search/filter, false-trigger flag honored. Companion to the existing live-device viewer at `/waveform`; the two routes are now clearly delineated in their docstrings. The webapp's inline plot at `/` is the primary path; `/events` remains a useful diagnostic when you want just a viewer. - **Histogram body codec — uint8 peak count fix.** Per-channel peak fields at `block[6]/[10]/[14]/[18]` are `uint8`, not `uint16 LE` spanning `block[6:8]` etc. The original interpretation was byte-exact on the N844 fixture corpus only because every annotation byte (`block[7]/[11]/[15]/[19]`) in those fixtures was zero. On non-N844 events with non-zero annotation bytes (observed across BE9558 Tran-drift and BE18003 Histogram+Continuous units), the old interpretation produced peaks up to 268 in/s per channel and 35× inflated PVS sums when first deployed to prod (rolled back same day; properly fixed in this release). Cross-correlated against BW's per-interval ASCII export on K558 / T003 / N599 / N844 corpora — 100% byte-exact on T/V/L, 99%+ on M (sub-precision rounding). Annotation byte preserved on each record as `record["annotations"]` for future RE. Verified against ~3,500 blocks across 5 in-repo fixtures + a synthetic K558 interval-12 regression block. - **`apply_bw_report_dict_to_event` helper** in `minimateplus.event_file_io`. Mirror of `apply_report_to_event` for the projected sidecar dict shape — used by the backfill path, which has the preserved `bw_report` block but not the original `.TXT` file. BW's reported peaks (and `sample_rate` / `record_time`) now win over codec output during `--force` backfill, matching ingest-path behavior. - **`scripts/check_bw_report_preservation.py`** — two-step snapshot/diff tool to verify that `backfill_sidecars.py` doesn't wipe the `bw_report` block from existing sidecars. Classifies every sidecar as PRESERVED / CHANGED / WIPED / STILL_MISSING / NEW / ADDED / REMOVED. Exit code 1 if any WIPED or CHANGED entries are found, so it can gate a CI step or deploy script. diff --git a/sfm/sfm_webapp.html b/sfm/sfm_webapp.html index 576ae94..6c68b38 100644 --- a/sfm/sfm_webapp.html +++ b/sfm/sfm_webapp.html @@ -499,6 +499,20 @@ text-align: left; border-bottom: 1px solid var(--border); white-space: nowrap; + position: sticky; + top: 0; + z-index: 1; + } + table.db-table thead th[data-sort]:hover { + background: var(--border2); + color: var(--text); + } + table.db-table thead th .sort-arrow { + display: inline-block; + width: 10px; + color: var(--accent, #58a6ff); + font-weight: 900; + text-align: center; } table.db-table tbody tr { border-bottom: 1px solid var(--border2); } table.db-table tbody tr:last-child { border-bottom: none; } @@ -758,7 +772,9 @@ overflow: hidden; min-height: 0; } - #section-db { display: none; } + /* Default to Database view on page load — most users are here to + browse stored events, not connect to a live unit. */ + #section-live { display: none; } /* ── Live connect bar (host/port/connect, live section only) ── */ #live-connect-bar { @@ -792,8 +808,8 @@
- - + +