feat(v0.14.2): Live Data panel UX polish — poll-rate, peaks, range bar, snapshot, NRC - #177
Conversation
…r, snapshot, NRC Slice 2 of the v0.14.2 'Live Data on the Bench' cycle. Five user-facing additions, all on the existing read_live_data Tauri command: - Polling-rate selector (100/250/500/1000 ms) wired via resolvePollRateMs, falls back to 250ms for unknown values. - Per-gauge peak tracking — applyValuesToPeaks reducer in pollOnce, formatPeakForLabel formatter, consolidated 'Current peaks' table under the gauge grid, 'Reset peaks' button. - Range bar under each gauge cell — fill width from (value-min)/(max-min), skipped for enum/text-mode gauges. - Save snapshot button — builds a v0.11.0-shape CSV via buildSnapshotCsv, writes to <HOME>/beeemuu/ via export_text. - NRC error surface — parseNrcError + isUnsupportedNrc, friendly log line on the four canonical 'unsupported' codes (0x11/0x12/0x14/0x31). Pure helpers in live_data_panel.js (CommonJS + window dual export), DOM wiring in main.js. No transport/protocol/commands.rs changes. No new Tauri command. Honest scope cut: per-PID dim + 'remove from profile' UI is deferred to v0.14.3 — the current protocol error string surfaces (sid, nrc) but not the DID. The UNSUPPORTED_NRCS set + the wire-shape of the panel are in place; the v0.14.3 slice just needs to thread the DID through the protocol layer and bind it to the local flag. Verification: 221/221 JS tests green (baseline 206 + 15 new in live_data_panel.test.js); node --check clean on all 3 JS files; TOML lint clean. No Rust changes, no async_commands allowlist risk. Co-authored-by: ohgeeceee <ohgeeceee@users.noreply.github.com>
|
ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR. |
…closeout (#178) Slice 3 of the v0.14.2 'Live Data on the Bench' cycle. Two doc-only changes: - New docs/validation/n62-real-car.md — chassis-specific bench-verification harness for the N62 / E70 X5 4.8L profile. Mirrors the shape of can-broadcast.md, dtc-history.md, injector-validation.md, etc. Five sections covering wire-up, cold readings, running readings, GitHub report template, and what we'll do with the report. Critical row is oil temp (0x5C) — if it returns -40 °C the slice 1 enrichment needs reverting for that PID only. - ROADMAP.md — v0.14.2 cycle header In Progress -> Shipped 2026-07-29; all four cycle-table rows marked ✅ Done (PR #171 / #175 / #177 / #178); last-updated line rewritten with the cycle closure summary and the explicit v0.14.3+ carry-over for the deferred PIDs (0x5E, 0x5F, 0x62) and the per-PID dim + remove-from-profile UI. Tier A, no transport/protocol/commands.rs touches. No test impact. EOL parity: new doc matches the existing 6 docs/validation/ files (CRLF); ROADMAP unchanged EOL. Co-authored-by: ohgeeceee <ohgeeceee@users.noreply.github.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1e23a4e015
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // startPolling so changing the dropdown restarts the interval. | ||
| function currentPollRateMs() { | ||
| const sel = $("live-poll-rate"); | ||
| return window.beeemuuLiveDataPanel.resolvePollRateMs(sel ? sel.value : 250); |
There was a problem hiding this comment.
Load the live-data helper before using it
src/index.html:642-658 loads the frontend as plain scripts and never includes js/live_data_panel.js, so every attempt to enable Live Data reaches this dereference with window.beeemuuLiveDataPanel undefined. The resulting ReferenceError occurs before setInterval and pollOnce, preventing the existing Live Data panel from polling at all; load the new helper before main.js.
Useful? React with 👍 / 👎.
| const nrc = window.beeemuuLiveDataPanel.parseNrcError(String(e)); | ||
| if (nrc && window.beeemuuLiveDataPanel.isUnsupportedNrc(nrc)) { | ||
| log("Live data: unsupported PID — NRC 0x" + nrc.nrc.toString(16).toUpperCase() + " (sid 0x" + (nrc.sid !== null ? nrc.sid.toString(16).toUpperCase() : "??") + ")."); |
There was a problem hiding this comment.
Propagate per-parameter NRCs to this handler
When a DID/PID returns an NRC, this catch block cannot receive the protocol error: src-tauri/src/commands.rs:259-300 handles each read with if let Ok(data) and always returns Ok(out), silently omitting failed parameters. Consequently unsupported PIDs never produce the new friendly log line even after the helper is loaded; read_live_data must return per-parameter failure information or otherwise propagate the NRC.
Useful? React with 👍 / 👎.
| const ms = currentPollRateMs(); | ||
| pollTimer = setInterval(pollOnce, ms); | ||
| pollOnce(); |
There was a problem hiding this comment.
Prevent overlapping reads at the new 100 ms rate
With the newly exposed 100 ms setting, any real-cable profile sweep taking longer than 100 ms starts another invoke before the previous one finishes. read_live_data holds the shared transport mutex for the entire multi-parameter sweep (src-tauri/src/commands.rs:86-92,257-303), so these calls queue behind one another and can continue returning stale sweeps after polling is stopped; schedule the next poll after the current promise settles or add an in-flight guard.
Useful? React with 👍 / 👎.
| if (pollTimer) { | ||
| stopPolling(); | ||
| startPolling(); |
There was a problem hiding this comment.
Keep the polling checkbox checked after a rate change
When the user changes rate during an active polling run, stopPolling() unchecks #live-poll, and startPolling() starts the replacement interval without checking it again. The UI therefore says polling is off while bus reads continue; clicking the unchecked control once merely checks it and leaves the existing interval running, requiring a second click to actually stop polling.
Useful? React with 👍 / 👎.
| id, | ||
| label: g.label, | ||
| unit: g.unit || "", | ||
| value: g.value, |
There was a problem hiding this comment.
Export raw readings instead of clamped gauge values
For a numeric reading outside its configured display range, Gauge.set() clamps g.value to min/max (src/js/gauges.js:73-77), so this snapshot silently records the boundary rather than the actual ECU value. Out-of-range readings are especially important diagnostic evidence; retain the latest raw LiveValue response and serialize that instead of the presentation-layer gauge state.
Useful? React with 👍 / 👎.
| const text = v.text !== undefined && v.text !== null ? JSON.stringify(String(v.text)) : ""; | ||
| const value = Number.isFinite(Number(v.value)) ? Number(v.value).toFixed(2) : ""; | ||
| rows.push( | ||
| [ | ||
| JSON.stringify(v.id || ""), | ||
| JSON.stringify(v.label || ""), | ||
| JSON.stringify(v.unit || ""), | ||
| value, | ||
| text, |
There was a problem hiding this comment.
Quote snapshot fields using CSV escaping rules
JSON.stringify escapes embedded quotes as \", but CSV requires a quote inside a quoted field to be doubled as "". When an imported profile label, unit, ID, or enum text contains a quote, standard CSV parsers split the generated row into the wrong number of columns, so the saved snapshot is malformed; use a dedicated CSV field encoder for every string field.
Useful? React with 👍 / 👎.
| gauges.clear(); | ||
| $("gauge-grid").innerHTML = ""; | ||
| // v0.14.2 slice 2 — peak state is per-profile. A new profile may | ||
| // have a different parameter set, so a stale peak for an id the | ||
| // new profile doesn't have would never be cleared by pollOnce | ||
| // (no sweep). Reset the peak state here. | ||
| livePeakState = {}; | ||
| renderPeakTable(); | ||
| updateSnapshotButton(); |
There was a problem hiding this comment.
Discard responses from the previously selected profile
If the profile is changed while a poll is in flight, this handler clears the gauges, but the pending request was already invoked with the old profile and can repopulate them after the clear; later sweeps then add the new profile's gauges to the same map. The new snapshot exporter labels the file with the currently selected profile while serializing the entire mixed map, so it can record values from two profiles; stop or invalidate outstanding polls when the selector changes.
Useful? React with 👍 / 👎.
| stopPolling(); | ||
| startPolling(); | ||
| } | ||
| saveSettings(); |
There was a problem hiding this comment.
Persist the selected polling rate
The rate-change handler calls saveSettings(), but that function only records liveProfile, logProfile, and the existing workspace fields (src/js/main.js:61-97); neither it nor loadSettings() stores or restores #live-poll-rate. Every application restart therefore silently resets the user's selected bus load to 250 ms despite the explicit save call and the workspace's role as the source of truth for UI preferences.
Useful? React with 👍 / 👎.
|
|
||
| /* ---------- v0.14.2 slice 2: Live Data panel UX polish ---------- */ | ||
| .poll-rate-select { | ||
| background: #1a2332; color: var(--fg); |
There was a problem hiding this comment.
Use a defined foreground color for the dark controls
--fg is not declared anywhere in app.css, so this color declaration is discarded. In the default light theme the dark #1a2332 selector therefore inherits the nearly black body text, making the selected polling rate effectively unreadable; the same undefined token at line 314 makes the peak values nearly invisible on the dark peaks panel. Use a defined light-on-dark foreground color for both elements.
Useful? React with 👍 / 👎.
| const csv = window.beeemuuLiveDataPanel.buildSnapshotCsv(values, profileLabel); | ||
| const filename = window.beeemuuLiveDataPanel.snapshotCsvFilename(); | ||
| try { | ||
| const path = await invoke("export_text", { filename, content: csv }); |
There was a problem hiding this comment.
Write live snapshots to the advertised directory
This passes only a basename to export_text, which always writes under <HOME>/beeemuu-exports and strips any directory components (src-tauri/src/commands.rs:716-731). The slice specification requires live snapshots under <HOME>/beeemuu/live-snapshots/<timestamp>.csv, so the button can never place them where users and downstream tooling are told to find them; use an export path/command that supports the intended live-snapshot directory.
Useful? React with 👍 / 👎.
Slice 3a of v0.14.3 'Finish the Bench'. Tier B (touches
src-tauri/src/protocol/** + commands.rs, adds one new Tauri
command). Backend half — slice 3b re-wires the frontend to
consume the new shape and surface the per-PID dim/remove UX.
## What this slice ships
**1. `protocol::nrc_from_error(msg: &str) -> Option<(u8, u8)>`**
(pub, in src-tauri/src/protocol/mod.rs). Pure parser that
extracts (sid, nrc) from the error string produced by
`service()`:
"ECU rejected service 22: Conditions not correct (NRC 22)"
^------^ ^-^
nrc parser sid parser
Co-located with `nrc_text` (the producer) so the format
contract has one source of truth. Case-insensitive on both
hex digits; tolerant of extra whitespace at the boundary.
4 unit tests pin the format and the failure modes (transport
timeouts, non-NRC errors, malformed hex).
**2. `read_live_data` return-type split.** Pre-v0.14.3, a single
per-PID failure short-circuited the whole sweep with
`Err(String)`; the frontend had no way to tell which PID
tripped the error and so couldn't offer a 'remove this
unsupported PID from the profile' affordance. Now returns
`LiveSweepResult { values, errors }`:
- `values`: `Vec<LiveValue>` — only the PIDs whose read AND
decode succeeded (matches pre-v0.14.3 semantics for the
happy path; no growth in the values array).
- `errors`: `Vec<LiveError>` — one entry per failed PID with
`{ id, label, sid, nrc, error }`. The structured `sid` +
`nrc` fields are populated via `nrc_from_error`; the
verbatim `error` string is preserved for the frontend's
fallback parser and for non-NRC errors (transport timeouts,
'Not connected', 'Unexpected ... response').
The whole sweep still returns `Err(_)` for systemic
problems (no transport, unknown profile, poisoned state lock)
— never for a single per-PID NRC.
**3. `live::remove_param_from_profile(profile_id, param_id)`**
(in src-tauri/src/data/live.rs). Mutates the in-memory profile
registry: removes the matching `LiveParam`, returns `true`
when something was removed / `false` for unknown ids.
Idempotent — calling twice on the same id is a no-op the
second time. 3 unit tests pin the happy path + idempotency +
unknown-profile failure mode.
**4. `remove_profile_pid` Tauri command.** New `async fn`
in src-tauri/src/commands.rs:
- Step 1: in-memory removal via `live::remove_param_from_profile`.
Returns an error to the frontend if the param id is unknown
(so the UI can distinguish 'removed' from 'already gone').
- Step 2: serialise the updated profile with `live::profile_to_toml`
(round-trips `[profile.theme]` blocks).
- Step 3: write to `<community>/profiles/<id>.toml` via
`tokio::fs::write`. `create_dir_all(parent)` first —
mirrors `save_freeze_schema`'s pattern.
- Returns the full file path the backend wrote, so the
frontend can show the user 'Removed from <path>'.
- Async because of the file I/O. NOT in the SYNC_ALLOWLIST
(per tests/async_commands.rs the allowlist gates the
*inverse*: sync commands are forbidden).
**5. Cargo.toml.** Adds `fs` to tokio's features so the
file write in `remove_profile_pid` can use `tokio::fs`.
No new crate enters the graph; tokio is already in the tree
via Tauri.
**6. lib.rs.** Registers `commands::remove_profile_pid` in
the `invoke_handler` so the frontend can call it.
## Why the frontend will need its own PR
The pre-v0.14.3 `main.js::pollOnce` does `for (const v of
values)` against the result, where `values` was a
`Vec<LiveValue>`. Slice 3a returns `{ values, errors }`
instead. The frontend has to switch to
`result.values.forEach(...) + result.errors.forEach(...) `,
which is a non-trivial rewire of the same area that landed
the v0.14.2 slice 2 PR (#177). That's slice 3b; this PR
lands the backend it consumes.
## Verification
- `cargo test --offline --lib data::live` — 35/35 pass
(was 32 + 3 new `remove_param_from_profile` tests).
- `cargo test --offline --lib protocol::` — 15/15 pass
(was 11 + 4 new `nrc_from_error` tests).
- `cargo test --offline --lib` — 149/149 pass (full
src-tauri suite; no regressions; +7 tests total).
- `cargo check --offline` — clean.
- `cargo clippy --offline --lib` — no new warnings on
src/protocol/mod.rs, src/data/live.rs, src/commands.rs.
- Pre-existing `cargo fmt` diff in src/analysis.rs is
unrelated to this PR.
- `node --test src/js/**/*.test.{js,cjs}` — 221/221 pass
(no JS changes; baseline unaffected).
- `node scripts/lint-toml.js` — 37/0 clean (no TOML changes).
## Tier
B. Touches `src-tauri/src/protocol/**` + `src-tauri/src/commands.rs`
+ adds a new Tauri command. `src-tauri/src/transport/**`
untouched. Will need a human merge per CLAUDE.md Tier B
rules; auto-merge disabled even if all checks pass.
Wait — actually this is the *cycle plan's* Tier B call.
Looking at the file paths: `protocol/**` + `commands.rs`
are both on the protected-path list. Slice 3b (frontend-only)
will land as Tier A after this. I haven't changed the
release tag or pushed to a production server, so no Tier C.
Co-authored-by: ohgeeceee <ohgeeceee@users.noreply.github.com>
Closes the CHANGELOG gap that PR #188 (v0.14.3 slice 4) flagged in the "Notes on the version surface" section. v0.14.1 and v0.14.2 shipped without CHANGELOG entries because each cycle's slice-closeout PR either forgot the version-surface sync step or deferred it as a separate housekeeping follow-up. This PR does the backfill from PR commit history: - ## [0.14.1] — 2026-07-27 - Tauri 2 `window.confirm()` auto-dismiss fix (PR #169, Tier B) - Simulator regenerate-on-identify (PR #169, Tier B) - Per-ECU freeze-schema split (PR #170, Tier A) Note: PR #169 shipped two slices in one PR (the dialog.js helper + the sim regenerate-on-identify). PR #170 is grouped under v0.14.1 because the ROADMAP treats the freeze-schema split as part of the v0.14.1 housekeeping arc (it was originally targeted at v0.14.0 but its tests-only refactor landed late and folded into the v0.14.1 PR #171 cycle-table retroactive close). - ## [0.14.2] — 2026-07-29 - Cycle plan + ROADMAP v0.14.2 header (PR #171, Tier A) - `community/profiles/n62.toml` enrichment — `0x5C` oil temp (PR #175, Tier A) - Live Data panel UX polish — poll-rate, peaks, range bar, snapshot-CSV, NRC error surface (PR #177, Tier A) - `docs/validation/n62-real-car.md` harness doc (PR #178, Tier A) - Claude review workflow repair — remove unsupported `Bash(gh pr review:*)` tool from `--allowedTools` (PR #176, Tier B) Note: the original v0.14.3 "Notes on the version surface" paragraph omitted PR #176 from the v0.14.2 PR list — fixed in this backfill. Also updates the v0.14.3 "Notes on the version surface" section to point at this backfill PR instead of flagging it as a backlog item, and includes PR #176 in the v0.14.2 PR list. Tier A — docs only. No code changes, no transport/** changes, no protocol/** changes. PR auto-merge eligible per CLAUDE.md once CI is green. Verification: - [x] CHANGELOG section order preserved: [0.14.0] (line 8) → [0.14.1] (line 71) → [0.14.2] (line 105) → [0.14.3] (line 168) → [0.13.0] (line 273) — chronological order matches merge order (verified via `gh pr list --state merged --json number,title,mergedAt`) - [x] Every PR number cited in a backfill entry exists and was actually merged to main (verified via `gh pr view N --json mergedAt` for PRs #169, #170, #171, #175, #176, #177, #178) - [x] All slice claims verified against each PR's actual body — no fabricated content per the data-over-invention rule - [x] `node --test src/js/*.test.js` — 163/163 pass (no code changes; 5 slice 3b tests absent because this branch is from origin/main pre-PR-190) - [x] `pytest backend/tests/` — 166/166 pass Cross-references: - PR #188 — v0.14.3 slice 4 (the cycle-closeout PR that flagged this backfill as the appropriate scope) - CLAUDE.md golden rule #5 — version-surface sync (the rule this PR enforces retroactively for v0.14.1 + v0.14.2) - docs/v0.14.3_plan.md — the cycle plan that calls out the forward-roadmap maintenance pattern this PR continues Co-authored-by: ohgeeceee <ohgeeceee@users.noreply.github.com>
…#190) Cycle plan: docs/v0.14.3_plan.md (slice 3 lines 103-124). Tier B (frontend + CSS; no transport/** changes, no protocol/** changes, no Rust changes — the backend contract was already shipped by PR #187). Per CLAUDE.md Tier B rules: PR is the review; no self-merge even if all checks pass. Awaiting human merge. ## What this slice ships ### 1. live_data_panel.js — new `classifyNrc(err)` helper Categorises each `LiveError` from `read_live_data`'s new `LiveSweepResult { values, errors }` shape into one of three buckets the UI acts on: - "unsupported" — the PID is structurally absent on this ECU (NRC 0x11 / 0x12 / 0x31 / 0x14); user should be offered the "remove from profile" affordance. - "transient" — ECU is unhappy right now (NRC 0x22 conditionsNotCorrect, NRC 0x33 securityAccessRequired, NRC 0x78 responsePending, etc.); no UI action — the next sweep will retry. - "unknown" — no structured sid/nrc to act on (transport timeout, "Not connected", "Unexpected DID response: ..."); same UI treatment as transient. Uses the structured (sid, nrc) fields populated by `protocol::nrc_from_error` (PR #187) as the fast path. Falls back to parsing `err.error` (the verbatim protocol error string) via `parseNrcError` when the structured fields are null, so legacy callers and any race where the backend populated `error` but not the structured fields still get a sensible classification. Exported from the public surface so main.js can call it as `window.beeemuuLiveDataPanel.classifyNrc(err)`. ### 2. live_data_panel.test.js — 5 new tests Pinned tests for classifyNrc covering: - structured (sid, nrc) fast path: unsupported for the four canonical NRCs - structured (sid, nrc) fast path: transient for non-unsupported NRCs - structured nrc=null falls back to parsing err.error - structured nrc=null + unparsable error string returns 'unknown' - defensive: null / non-object / missing fields never throw Total: 20 tests in this file (was 15). ### 3. main.js — three call sites rewired for `LiveSweepResult` The new return shape of `read_live_data` (PR #187) breaks the three call sites that did `const values = await invoke("read_live_data", ...)`. All three are updated: - pollOnce (Live Data tab, line 1365) — main consumer; iterates `result.values` for the gauge loop, then a NEW loop over `result.errors` that classifies each PID and either (a) logs the canonical "unsupported" line + dims the gauge cell + appends a one-click "Remove from profile" button that calls the async `remove_profile_pid` Tauri command, or (b) logs the transient / unknown case and skips UI affordance. Clears any prior dim state on a successful read (PID may recover after a re-key cycle). - buildLogParams (Logging tab channel list, line 2109) — iterates `result.values` only; the Logging tab doesn't render error UI. - logTick (Logging tab chart tick, line 2203) — iterates `result.values` only. New handler `removePidFromProfile(pidId, pidLabel)` — gated behind `window.beeemuuDialog.ask()` (the same `tauri-plugin-dialog` confirmation pattern the v0.14.1 issue-#161 fix uses). On success, removes the gauge cell from the DOM (the PID is gone from the in-memory profile + the on-disk TOML, so the next sweep won't return it). New panel-head count badge `#live-unsupported-count` — hidden when zero, shows "N unsupported on this ECU" otherwise. ### 4. index.html — `#live-unsupported-count` slot One-line addition in the Live Data panel head, next to the profile selector. `hidden` by default; populated by pollOnce. ### 5. app.css — dimmed state + remove button + count badge Three new rule blocks: - `.gauge-cell.dimmed` — opacity 0.45 + " (unsupported)" appended to the label via ::after pseudo-element. Cleared on next sweep. - `.pid-remove` — minimal inline button that sits inside the gauge cell below the peak label. Hover colour is the project's --err. - `.live-unsupported-count` — small muted text in the panel head. ## What this PR does NOT do - No Rust changes. PR #187 already shipped `protocol::nrc_from_error`, `LiveSweepResult { values, errors }`, `LiveError`, and the async `remove_profile_pid` Tauri command. This PR is the frontend consumer. - No transport/** changes (K+DCAN / ENET transports untouched). - No protocol/** changes. - No new crate / dependency. - No change to the v0.14.0 Live Gauges panel (still sim-only, per the v0.14.2 cycle-pick conversation). - No version bumps. The release cut (Cargo.toml + tauri.conf.json + git tag) is a separate Tier C step that follows slice 3b. ## Tier B per CLAUDE.md — `src/js/main.js` + `src/js/live_data_panel.js` + `src/index.html` + `src/css/app.css`. Per CLAUDE.md Tier B rules: "PR is the review; auto-merge disabled even if all checks pass. Awaiting human merge." ## Verification - [x] `node --check src/js/main.js` — rc=0 - [x] `node --check src/js/live_data_panel.js` — rc=0 - [x] `node --test src/js/*.test.js` — 168/168 pass (was 163, +5 new for classifyNrc) - [x] `cargo test --lib --offline protocol::` — 15/15 pass (no regression on the backend surface this slice consumes) - [x] `pytest backend/tests/` — 166/166 pass (one test, test_app_live_endpoint::test_endpoint_returns_ok, was order- dependent flaky on first run; passed on re-run — not introduced by this slice) ## Cross-references - v0.14.3 plan: docs/v0.14.3_plan.md (slice 3 lines 103-124) - Backend contract this PR consumes: PR #187 — feat(v0.14.3): per-PID NRC errors + remove_profile_pid command - Prior slice shipping the NRC-aware helpers + friendlier log line: PR #177 — feat(v0.14.2): Live Data panel UX polish (parseNrcError + isUnsupportedNrc were drafted there as dead-but-not-yet-usable helpers; this slice activates them via the per-PID error loop + classifyNrc) - Re-enabled orphan from v0.14.2 slice 2 WIP: docs/v0.14.3_plan.md §"Open question #2" — the `#live-unsupported-count` badge + the dim/remove UI together fulfil the deferred WIP Co-authored-by: ohgeeceee <ohgeeceee@users.noreply.github.com>
Summary
Slice 2 of v0.14.2 "Live Data on the Bench": the Live Data panel UX polish
that was specified in
docs/v0.14.2_plan.mdlines 56-73. Five user-facingadditions, all on the existing
read_live_dataTauri command, notransport/**orprotocol/**changes, no new crate, no new Tauricommand.
What's in this PR
<select id="live-poll-rate">with 100 / 250 / 500 / 1000 ms.startPolling()now reads the dropdown viabeeemuuLiveDataPanel.resolvePollRateMs, which falls back to 250 ms for unknown input. Changing the rate mid-session restarts the interval so the new rate takes effect immediately.src/index.html(DOM),src/js/main.js(currentPollRateMs, listener on#live-poll-rate).gauge-peaklabel under every gauge cell showing the highest numeric value seen sincestartPolling()began (orReset peakswas clicked). Numeric values only — enum / text-mode gauges are skipped. A consolidated "Current peaks" table below the gauge grid shows all active peaks in one place.src/index.html(#live-peakspanel),src/js/main.js(applyValuesToPeaksreducer inpollOnce,renderPeakTable,formatPeakForLabel),src/css/app.css(.gauge-peak,.live-peaks,.live-peaks-table).gauge-rangebar under each gauge label whose fill width tracks(value-min)/(max-min). Skipped for enum / text-mode gauges.src/index.html(DOM built inensureGauge),src/js/main.js(updateRangeBar),src/css/app.css(.gauge-range+.gauge-range-fill)#btn-live-snapshotwrites the current values to<HOME>/beeemuu/beeemuu-live-snapshot-<iso>.csvvia the sameexport_textTauri command the DTC + log exporters use. CSV shape mirrors the v0.11.0 log-export header (metadata line + column header + one data row per value) so it's loadable bysrc/js/csv_log_export.js. Button is disabled until the first successful poll populates a gauge.src/index.html(button),src/js/main.js(listener),src/js/live_data_panel.js(buildSnapshotCsv+snapshotCsvFilename)pollOnce's catch block parses the protocol-layer error string viabeeemuuLiveDataPanel.parseNrcErrorand, if the NRC is one of the four canonical "unsupported" codes (0x11 / 0x12 / 0x14 / 0x31), logs a friendly line likeLive data: unsupported PID — NRC 0x31 (sid 0x22). Transient / condition NRCs (0x22, 0x78) fall through to the existinglog()behavior.src/js/main.js(catch block),src/js/live_data_panel.js(parseNrcError,isUnsupportedNrc,UNSUPPORTED_NRCS)Why a separate
live_data_panel.jshelper moduleThe five features above all share pure helpers (polling-rate resolve,
peak reducer, snapshot-CSV serialiser, NRC parser). Following the
v0.14.0
live_gauges.js/live_can_source.jspattern, those helperslive in a CommonJS +
window.beeemuuLiveDataPaneldual-export modulethat's unit-testable under
node --testwithout a webview. The DOMwiring stays in
main.js. Module surface:Honest scope cut (deferred to v0.14.3)
The cycle plan listed "one-click removal of the offending PID from
the profile" as part of the NRC error surface. This PR delivers the
error log line but defers the per-PID dim + remove UI: the current
read_live_dataTauri error string surfaces the (sid, nrc) pair butnot the DID that was rejected. A clean per-PID UX needs the
protocol layer to surface the DID in the error, which is a Tier B
change in the protocol module. The plan and the
UNSUPPORTED_NRCSsetare already in place — the v0.14.3 follow-up just needs to thread the
DID through and bind it to the
addUnsupportedPid/removeUnsupportedPidhelpers. This is a small, well-scoped v0.14.3slice.
Verification
node --check src/js/main.js src/js/live_data_panel.js src/js/live_data_panel.test.js— cleannode --test "src/js/**/*.test.js" "src/js/**/*.test.cjs"— 221/221 green (baseline 206 + 15 new tests inlive_data_panel.test.js)node scripts/lint-toml.js— clean (no profile changes; 37 files / 0 problems)async_commandsallowlist concernDiff stat
Tier
A — frontend UI + a pure-helper module. No
transport/**,protocol/**,commands.rs, orops/**touches. Auto-merge on CIgreen per
CLAUDE.mdrule 2.Cross-references
docs/v0.14.2_plan.md(slice 2 lines 56-73, 132-141)ROADMAP.mdv0.14.2 cycle table (slice 2 row to be marked✅ Donein a follow-up docs commit)n62.tomlenrichment) + PR docs(v0.14.2): cycle plan + ROADMAP header — Live Data on the Bench #171 (slice 0, cycle plan)