Skip to content

feat(v0.14.2): live data panel polish — polling rate, peaks, range bar, snapshot, NRC banner - #184

Closed
ohgeeceee wants to merge 1 commit into
mainfrom
v0.14.2-slice2-live-data-polish
Closed

feat(v0.14.2): live data panel polish — polling rate, peaks, range bar, snapshot, NRC banner#184
ohgeeceee wants to merge 1 commit into
mainfrom
v0.14.2-slice2-live-data-polish

Conversation

@ohgeeceee

Copy link
Copy Markdown
Owner

Slice 2 of v0.14.2 — "Live Data on the Bench"

Cycle plan: docs/v0.14.2_plan.md. Tier A only (frontend + docs + tests). No transport/** / protocol/** changes, no new crate, no new Tauri command. Reuses the existing read_live_data and export_text IPC paths.

What this slice ships

Polling-rate selector. Poll @ : 100 / 250 / 500 / 1000 ms. Persisted in workspace.json under livePollRateMs. Changing the rate while polling restarts the setInterval at the new delay. Default 250 ms (the pre-slice behaviour); resolvePollRateMs() rejects out-of-range or unknown values. Per-gauge peak tracking. Current peaks side table + small label under each gauge. applyValuesToPeaks() is the pure reducer — same immutability + text-skip discipline as live_gauges.js. Side table renders in sweep order so the user's mental model matches the panel layout. Reset peaks button clears the session. Range bar under each gauge. Thin strip showing the current value's position between min and max. Hot when above 85%, over when above 100%. Pure CSS transition. At-a-glance complement to the dial. Save snapshot button. Writes the current values to ~/beeemuu-exports/beeemuu-live-snapshot-<stamp>.csv via the existing export_text Tauri command. Filename matches the project's beeemuu-<kind>-<stamp>.csv convention. CSV shape mirrors the v0.11.0 logging export (metadata header, column header row, one data row per value). NRC error surface. parseNrcError() extracts the (sid, nrc) pair from the protocol::service error format ("ECU rejected service 22: conditionsNotCorrect (NRC 22)"). When the NRC matches the unsupported set ({0x11, 0x12, 0x14, 0x31}), the panel shows a banner — not a stop-polling — and dims the affected gauges with .unsupported styling. The banner auto-clears on the next clean sweep. A clean stop-polling path remains for non-NRC errors. Files src/js/live_data_panel.js (new): pure helpers — polling rate, peak reducer, snapshot CSV serialiser, NRC parser. Dual export (CommonJS + window.beeemuuLiveDataPanel). src/js/live_data_panel.test.js (new): 15 tests covering every public function, including the pinned UNSUPPORTED_NRCS set so a future silent edit gets caught by CI. src/index.html: panel-head controls (Poll @ selector, Snapshot, Reset peaks buttons), NRC banner, side peaks table, gauge-cell peak + range-bar slots (added at ensureGauge() time). src/css/app.css: .live-head-controls, .live-nrc-banner, .live-body grid (1fr gauge grid + 220px peaks column with mobile collapse at 720px), .live-peaks-table, .gauge-peak, .gauge-range, .gauge-cell.unsupported — all driven by existing semantic tokens so both light and dark themes work without per-panel overrides. src/js/main.js: ensureGauge() stamps data-id on the cell + appends .gauge-peak + .gauge-range DOM so the polling loop can address them by LiveValue.id. pollOnce() feeds the peak reducer, paints the range bar, clears the NRC banner on success, and dispatches the catch branch to the NRC parser. startPolling() reads the rate from currentPollRateMs(). New click handlers for the snapshot + reset buttons. live-profile change handler clears the peak state, hides the NRC banner, and disables the snapshot + reset buttons (a new profile is a fresh session). workspace persistence: livePollRateMs saved in saveSettings() and restored in loadSettings(). ROADMAP.md: v0.14.2 slice-1 row already marks PR #175 done; no change needed here beyond what was committed. Verification [x] node --test src/js/**/*.test.{js,cjs} — 221/221 pass (baseline 206 + 15 new). [x] node scripts/lint-toml.js — 37 / 0. [x] cargo check --offline (src-tauri/) — clean. No Rust changes; this is the vendored-dep check. [x] node --check src/js/main.js — clean. [x] No transport/**, protocol/**, commands.rs changes. No new crate. No new Tauri command. Tier A. Self-merge when CI is green.

…r, snapshot, NRC banner

Slice 2 of v0.14.2 'Live Data on the Bench'. Tier A only — frontend +
docs, no transport/protocol changes, no new crate, no new Tauri
command. The existing `read_live_data` and `export_text` IPC paths
are reused.

What this slice ships:

- **Polling-rate selector** (`Poll @` <select>): 100 / 250 / 500 /
  1000 ms. Persisted in workspace.json under `livePollRateMs`.
  Changing the rate while polling restarts the `setInterval` at the
  new delay (no need to stop and start). Default 250 ms (the
  pre-slice behaviour); the resolvePollRateMs() helper rejects
  out-of-range or unknown values.

- **Per-gauge peak tracking** (`Current peaks` side table + small
  label under each gauge): `applyValuesToPeaks()` is the pure
  reducer — same immutability + text-skip discipline as
  `live_gauges.js`. The side table renders in sweep order so the
  user's mental model matches the panel layout. A `Reset peaks`
  button clears the session.

- **Range bar** under each gauge: thin strip showing the current
  value's position between `min` and `max`. Hot when above 85%,
  over when above 100%. Pure CSS transition. Cheap at-a-glance
  'how close to the top am I' indicator that complements the dial.

- **Save snapshot** button: writes the current values to
  `~/beeemuu-exports/beeemuu-live-snapshot-<stamp>.csv` via the
  existing `export_text` Tauri command. Filename matches the
  project's `beeemuu-<kind>-<stamp>.csv` convention used by
  `beeemuu-log-`, `beeemuu-dtcs-`, `beeemuu-session-`, etc.
  CSV shape mirrors the v0.11.0 logging export (metadata header,
  column header row, one data row per value).

- **NRC error surface**: the existing `catch (e) { log(...);
  stopPolling() }` is now NRC-aware. `parseNrcError()` extracts
  the (sid, nrc) pair from the `protocol::service` error format
  (`"ECU rejected service 22: conditionsNotCorrect (NRC 22)"`).
  When the NRC matches the unsupported set ({0x11, 0x12, 0x14,
  0x31}), the panel shows a banner — *not* a stop-polling — and
  dims the affected gauges with `.unsupported` styling. The
  banner auto-clears on the next clean sweep. A clean stop-polling
  path remains for non-NRC errors (timeout, no connection, etc.).

Files:

- `src/js/live_data_panel.js` (new): pure helpers — polling
  rate, peak reducer, snapshot CSV serialiser, NRC parser. Dual
  export (CommonJS + `window.beeemuuLiveDataPanel`).
- `src/js/live_data_panel.test.js` (new): 15 tests covering
  every public function, including the pinned UNSUPPORTED_NRCS set
  so a future silent edit gets caught by CI. Baseline 206 + 15 =
  221/221 pass.
- `src/index.html`: panel-head controls (Poll @ selector,
  Snapshot, Reset peaks buttons), NRC banner, side peaks table,
  gauge-cell peak + range-bar slots (added at ensureGauge() time).
- `src/css/app.css`: `.live-head-controls`, `.live-nrc-banner`,
  `.live-body` grid (1fr gauge grid + 220px peaks column with
  mobile collapse at 720px), `.live-peaks-table`,
  `.gauge-peak`, `.gauge-range`, `.gauge-cell.unsupported` —
  all driven by existing semantic tokens so both light and dark
  themes work without per-panel overrides.
- `src/js/main.js`:
  - ensureGauge() now stamps `data-id` on the cell + appends
    `.gauge-peak` + `.gauge-range` DOM so the polling loop can
    address them by `LiveValue.id`.
  - pollOnce() now feeds the peak reducer, paints the range bar,
    clears the NRC banner on success, and dispatches the catch
    branch to the NRC parser.
  - startPolling() reads the rate from `currentPollRateMs()`.
  - new click handlers for the snapshot + reset buttons.
  - `live-profile` change handler now also clears the peak
    state, hides the NRC banner, and disables the snapshot +
    reset buttons (a new profile is a fresh session).
  - workspace persistence: `livePollRateMs` saved in
    saveSettings() and restored in loadSettings().

Verification:

- [x] `node --test src/js/**/*.test.{js,cjs}` — 221/221 pass
      (baseline 206 + 15 new).
- [x] `node scripts/lint-toml.js` — 37 / 0.
- [x] `cargo check --offline` (src-tauri/) — clean. No Rust
      changes; this is the dependency-vendored check.
- [x] `node --check src/js/main.js` — clean.
- [x] No `transport/**`, `protocol/**`, `commands.rs` changes.
      No new crate. No new Tauri command.

What this slice does NOT ship (intentionally, to keep Tier A scope
tight):

- A persistent 'remove this PID from the profile' affordance —
  that requires a write path the user owns via the contribution
  guide, not a one-click. The unsupported dim is session-local.
- A 'reset range bar / start from idle' control. The bar is
  derived from the same numeric range as the gauge itself, so it
  inherits the gauge's min/max; there is nothing to reset.
- v0.14.0 Live Gauges panel integration. Deferred per the
  cycle-pick conversation (Option B).
@ecc-tools

ecc-tools Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

@ohgeeceee

Copy link
Copy Markdown
Owner Author

Closing as duplicate. PR #177 (merged 2026-07-29 at 22:52 UTC) implemented the same v0.14.2 slice 2 — polling rate selector, peak tracking, range bar, snapshot CSV, NRC error surface — with the same live_data_panel.js helper module + 15 tests. Slice 3 (docs/validation/n62-real-car.md) also already shipped via PR #178. The v0.14.2 cycle is closed on main; this PR was developed without visibility of the intervening activity and would conflict on merge.

@ohgeeceee ohgeeceee closed this Jul 30, 2026
@ohgeeceee
ohgeeceee deleted the v0.14.2-slice2-live-data-polish branch July 30, 2026 01:38

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 65db3fe901

ℹ️ 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".

Comment thread src/js/main.js
Comment on lines +1466 to +1468
const msg = (e && e.message) ? e.message : String(e);
const parsed = window.beeemuuLiveDataPanel.parseNrcError(msg);
if (parsed && window.beeemuuLiveDataPanel.isUnsupportedNrc(parsed)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Propagate NRC failures to the new handler

For any unsupported PID, this handler is unreachable because src-tauri/src/commands.rs:259-302 handles protocol results with if let Ok(data) and then always returns Ok(out), silently omitting failed parameters. Consequently parseNrcError never receives an ECU NRC, the successful path instead clears the banner, and the advertised unsupported-gauge warning never appears on real-car sweeps.

Useful? React with 👍 / 👎.

Comment thread src/js/main.js
// resolvePollRateMs() — so writing an old workspace without the
// key (or with a future / removed value) just means we use the
// default until the user picks one.
ws.livePollRateMs = window.beeemuuLiveDataPanel.resolvePollRateMs($("live-poll-rate")?.value);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the polling rate during workspace serialization

Although this assigns livePollRateMs, flushWorkspace() passes the state through Workspace.serializeWorkspace(), whose allow-list in src/js/workspace.js:37-71 does not include this key. The serializer therefore drops the selected rate from workspace.json, so every restart restores the default 250 ms instead of the user's choice.

Useful? React with 👍 / 👎.

Comment thread src/js/main.js
Comment on lines +1446 to +1449
const frac = Math.max(0, Math.min(1, (v.value - v.min) / (v.max - v.min)));
fill.style.width = (frac * 100).toFixed(1) + "%";
fill.classList.toggle("hot", frac > 0.85 && frac <= 1);
fill.classList.toggle("over", frac > 1);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Test the unclamped range fraction for overflow

When a live value exceeds its configured maximum, frac is clamped to 1 here before the class checks. Therefore frac > 1 can never be true and the new red .over state never activates; above-range values are instead marked .hot, obscuring the distinction the range bar is intended to show.

Useful? React with 👍 / 👎.

Comment thread src/js/main.js
Comment on lines +1358 to +1361
function resetLivePeaks() {
livePeakState = {};
liveLastValues = [];
renderLivePeaks();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep snapshot data when resetting peaks

Resetting peaks also clears liveLastValues, which is the cache consumed by saveLiveSnapshot(). If the user stops polling after a valid sweep, clicks Reset peaks, and then clicks the still-enabled Save snapshot button, the snapshot fails with “No live values yet” until another sweep runs, even though resetting peaks should not discard the current readings.

Useful? React with 👍 / 👎.

Comment thread src/js/main.js
Comment on lines +1483 to 1485
const delay = currentPollRateMs();
pollTimer = setInterval(pollOnce, delay);
pollOnce();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Prevent overlapping polling sweeps

When a sweep takes longer than the selected interval—especially the new 100 ms option with profiles containing 10–30 serial ECU reads—setInterval starts additional pollOnce() invocations without waiting for the prior one. The backend serializes these through the transport mutex and K+DCAN reads can wait up to a one-second deadline, so requests can accumulate faster than they complete; unchecking Poll only clears future timer callbacks, leaving the queued sweeps to keep the transport busy.

Useful? React with 👍 / 👎.

Comment thread src/js/main.js
Comment on lines 1433 to +1434
const values = await invoke("read_live_data", { profile: $("live-profile").value });
liveLastValues = values;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Discard responses from a previously selected profile

If the user changes profiles while read_live_data is in flight, the change handler clears the session, but the old request can subsequently assign its response to liveLastValues here. saveLiveSnapshot() then labels those readings using the newly selected profile, allowing a snapshot containing one profile's values to be exported as another profile until the next sweep completes.

Useful? React with 👍 / 👎.

Comment thread src/js/main.js
Comment on lines +1358 to +1361
function resetLivePeaks() {
livePeakState = {};
liveLastValues = [];
renderLivePeaks();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clear gauge peak labels when resetting peaks

With polling stopped after at least one sweep, clicking Reset peaks empties the side table but leaves every .gauge-peak element showing its previous value. renderLivePeaks() only updates IDs present in the current peak state, so after this state is emptied it never writes the reset placeholder to the existing gauge cells, making the reset visibly incomplete until another sweep arrives.

Useful? React with 👍 / 👎.

Comment thread src/js/live_data_panel.js
Comment on lines +111 to +113
JSON.stringify(v.id || ""),
JSON.stringify(v.label || ""),
JSON.stringify(v.unit || ""),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use CSV escaping instead of JSON escaping

For an imported or user-added parameter whose label contains a double quote, JSON.stringify() emits a backslash-escaped quote such as ", but CSV requires embedded quotes to be doubled as "". The resulting snapshot row is malformed for standard CSV readers and can split or corrupt subsequent fields; the same problem affects enum text serialized above.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant