Skip to content

feat(debug): rolling debug-history snapshot buffer with automation hooks - #4438

Open
chalfontchubby wants to merge 14 commits into
mainfrom
feat/rolling-debug-history
Open

feat(debug): rolling debug-history snapshot buffer with automation hooks#4438
chalfontchubby wants to merge 14 commits into
mainfrom
feat/rolling-debug-history

Conversation

@chalfontchubby

@chalfontchubby chalfontchubby commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes #4417.

create_debug_yaml() (what bug reporters attach so a maintainer can replay their exact plan computation via unit_test.py --debug_file --redo) only ever wrote when switch.predbat_debug_enable was already on, on every ~5-minute cycle with no pruning at all - unbounded disk growth if left on, and no history to look back at for anyone who didn't already have it on before something went wrong (originally raised in #1745, never actually built).

Replaces that with a rolling buffer:

  • debug_history.py (new) - capture/list/load/prune snapshots, mirroring the existing annual_store.py ring-buffer pattern and going through the Storage component rather than the filesystem directly. Snapshots are saved with format="text" (the payload is already-rendered YAML text from create_debug_yaml(write_file=False), not a Python object - format="yaml" would double-serialise it into something --debug_file couldn't parse).
  • Three new config items: debug_history_count (default 15, 0 disables the routine capture), debug_history_interval (default 3h - 15x3h matches the History tab's worst-case "yesterday + today so far" coverage), and debug_history_force_capture - a self-resetting switch so an automation that notices something worth investigating can trigger an immediate, precisely-timed capture rather than waiting on the routine interval, then fetch it via ?id=latest without a separate lookup round-trip. Still honoured even when debug_history_count=0 (an explicit "give me one right now" is a different intent than "keep a rolling background history").
  • predbat.py: _capture_debug_history(), throttled in-memory, wired alongside the existing debug_enable capture call. Independent of that switch entirely.
  • web.py: /debug_history_list and /debug_history_download routes (reusing the existing html_file() attachment-serving helper the manual predbat_debug.yaml download already uses), plus a dropdown on the dashboard's Debug panel.
  • web_helper.py: a Debug column on the plan's History/Yesterday view only (its rows are the ones with a real past timestamp to match a snapshot against - the live Plan view is mostly future predictions with nothing to match). Fetched and matched client-side by wall-clock time rather than threaded through publish_html_plan(), which is shared by three other call sites (live plan, Annual results viewer, Compare) with no relationship to when a snapshot was actually captured.
  • Docs - customisation.md (new "Debug history" subsection), web-interface.md (dashboard dropdown), predbat-plan-card.md (new Debug column in the plan column reference).

Test plan

  • apps/predbat/tests/test_debug_history.py - capture/list/load round-trip, pruning to a configured count on both Storage eviction paths (delete-capable and expiry-fallback), annotate_steps_back, corrupt-index handling, latest/falsy-id resolution, and a real-StorageLocalFiles round-trip of genuine YAML-syntax text (guards the format="text" vs "yaml" trap directly)
  • apps/predbat/tests/test_debug_history_capture.py - interval throttling, count<=0 disabling routine capture, force-capture bypassing both the count and interval, the switch resetting itself, and graceful handling with no storage component available
  • ./run_all --quick passes with no regressions
  • ./run_pre_commit passes (ruff, black, cspell, markdownlint, full test suite)

🤖 Generated with Claude Code

chalfontchubby and others added 7 commits August 6, 2026 12:17
…oks (#4417)

Predbat's create_debug_yaml() (what bug reporters attach for a --redo
replay) only ever wrote when switch.predbat_debug_enable was already
on, on every ~5-minute cycle with no pruning at all - unbounded disk
growth if left on, and no history to look back at for anyone who
didn't already have it on before something went wrong.

Adds a rolling buffer instead, mirroring the existing annual_store.py
ring-buffer pattern and the Storage component's save/expiry/cleanup:

- debug_history.py: capture/list/load/prune snapshots, keyed by
  timestamp, independent of debug_enable.
- Three new config items: debug_history_count (default 15, 0 = off),
  debug_history_interval (default 3h; 15x3h matches the History tab's
  worst-case "yesterday + today so far" coverage), and
  debug_history_force_capture - a self-resetting switch so an
  automation that notices something worth investigating can trigger an
  immediate, precisely-timed capture rather than waiting on the
  routine interval, and fetch it via ?id=latest without a separate
  lookup round-trip.
- predbat.py: _capture_debug_history(), throttled in-memory, wired
  alongside the existing debug_enable capture call.
- web.py: /debug_history_list and /debug_history_download routes,
  reusing the existing html_file() attachment-serving helper, plus a
  dropdown on the dashboard Debug panel.
- web_helper.py: a Debug column on the plan's History/Yesterday view
  (only there - its rows are the ones with a real past timestamp to
  match against), fetched and matched client-side by wall-clock time
  rather than threaded through publish_html_plan(), which is shared by
  three other call sites with no relationship to a captured snapshot's
  timing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds a "Debug history" subsection to customisation.md covering the
three new config items, documents the dashboard's new History
dropdown in web-interface.md, and adds the plan History view's new
Debug column to predbat-plan-card.md's column reference alongside the
other conditionally-shown columns.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… loading

Rebuilding the <select>'s options via appendChild doesn't reliably
repaint the closed box's displayed label - confirmed live, it kept
showing the "Click to load..." placeholder text until the dropdown
was manually opened once, even though the options had already
populated correctly underneath. Explicitly setting selectedIndex = 0
after populating forces a real value-change the browser has to
redraw for.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ot symmetric

A snapshot captures Predbat's full live state (including its current
plan) at one moment - it can describe a row shortly at or after that
moment, but tells you nothing reliable about a row well after it,
since Predbat replans every cycle and the plan may have moved on by
then. The previous +/-90 minute symmetric window treated a snapshot
25 minutes before a row the same as one 35 minutes after it, so two
rows 30 minutes apart could both point at the same earlier snapshot
as if it still described the later one - confirmed live, dogfooding
this against a real instance.

Replaces it with a small forward grace (30 min, covers a snapshot
landing a little into a row's own slot) plus a much larger backward
window (3.5h, comfortably covers the default 3-hourly interval),
picking the closest snapshot that satisfies that asymmetric bound
rather than the closest by absolute distance in either direction.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…steps-back labels

Two related UX problems in the plan History view's Debug column (#4417):

1. Snapshots were captured with a raw self.now_utc timestamp - whatever
   moment the ~5-minute cycle happened to trigger the capture, not aligned
   to the plan's own 30-minute (plan_interval_minutes) grid. The row-to-
   snapshot matching in web_helper.py therefore needed a fuzzy
   nearest-within-a-window search, which let several adjacent rows all
   claim the same snapshot - not the one row it most neatly corresponds to.

2. The dropdown and the plan column link both labelled a snapshot by how
   many captures back it was ("3 steps back") rather than its actual time.

Fixed both together: _capture_debug_history() now floors the capture
timestamp to self.midnight_utc + N * plan_interval_minutes - the exact same
anchor and step output.py uses to build every plan row's own row.time. A
snapshot's timestamp is then either an exact match for one row or it isn't
a match at all, which also gives each snapshot at most one owning row for
free (two rows plan_interval_minutes apart can never both equal the same
floored capture instant). findNearestDebugSnapshot() simplified from an
asymmetric forward-grace/backward-window search down to a direct timestamp
comparison (1s tolerance, only for formatting noise). The throttle-tracking
timestamp (debug_history_last_capture) deliberately still uses the real
capture moment, not the floored one, so routine-interval spacing tracks
actual elapsed time regardless of how the snapshot itself gets labelled.

Both the dashboard dropdown and the plan column link now show the
snapshot's absolute time instead of "N steps back" - the dropdown already
had the absolute time alongside the steps-back suffix, so that part is
just dropping the suffix; the plan column link switches from "N back" to a
short local time. debug_history.py's annotate_steps_back()/steps_back
field is untouched - still useful for API consumers, just no longer the
primary UI label.

Added a dedicated regression test for the slot-flooring behaviour
(debug_history_capture_alignment) and fixed an existing test
(test 6b in debug_history_capture) that advanced now_utc by only 1 minute
between two forced captures - previously enough for a distinct snapshot id,
now floors back to the same slot and would collide.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…nload

Chasing a user through the per-snapshot picker to find the right moment is
exactly the back-and-forth this buffer was meant to avoid. One link now
downloads every retained snapshot as a gzip tarball; the plan page's own
History/Yesterday view (a separate consumer of the same snapshot index) is
untouched.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…position

steps_back is the snapshot's current position in the retention ring, not a
property of the snapshot itself - it shifts as newer captures push it back.
Baking it into the filename meant the same real capture got a different name
in every archive downloaded after it, making archives from different times
impossible to merge/deduplicate by filename. snapshot_id (its capture
timestamp) is already unique and stable; that's all the filename needs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@chalfontchubby
chalfontchubby force-pushed the feat/rolling-debug-history branch from 30b85ff to 5625a57 Compare August 6, 2026 13:29
…tention window

Two captures landing in the same calendar minute (a routine capture and a
close-by force-capture) used to both stay in the ring, reaching the UI as
two identically-named chart series and breaking "Deselect all", which
keys off series name. capture_snapshot() now discards the older of any
same-minute pair, keeping the newer.

Also add an optional max_age prune so a burst of close-together captures
can't leave something far older than the buffer's intended window sitting
there just because the count cap alone hasn't caught up to it - wired
through from predbat.py as interval_hours * count.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@chalfontchubby

Copy link
Copy Markdown
Collaborator Author
Screenshot 2026-08-06 at 17 34 35

Adds the ability to download debug files for a defined subset of points in the history without the ever expanding set of debug files enabled with "debug". This screenshot covers a period with multiple restarts, so the snapshot files are not at the expected regular intervals.

A follow on branch adds a new chart showing how these "historical plans" evolve over time (the planned soc overlaid for each historical snapshot) so it is possible to find out "what caused that weird thing I see in the history" and "why did the plan suddenly do something different"

@springfall2008

Copy link
Copy Markdown
Owner

Review

Nice feature — went through the diff in detail. A few correctness issues worth fixing before merge, plus some lower-priority cleanup.

Correctness bugs

  1. predbat.py _capture_debug_history()self.debug_history_last_capture = self.now_utc runs unconditionally after the try/except. A failed capture (exception caught and logged as a "Warning") still resets the throttle clock, so the next attempt is silently deferred for a full debug_history_interval (default 3h) instead of retrying next cycle.
  2. Same function, storage-unavailable pathif not storage: return exits before that line, so the throttle timestamp never advances at all. If Storage is ever down, every ~5-minute cycle re-enters and re-logs the warning indefinitely.
  3. Force-capture switch resets before the outcome is knownexpose_config("debug_history_force_capture", False) fires immediately on trigger, before storage is checked or the capture is attempted. docs/customisation.md says "Predbat resets the switch back off itself once the snapshot has been taken" — the code resets it on trigger, not completion. An automation polling the switch for success has no reliable signal if the capture actually failed.
  4. web.py html_debug_history_download, id=latest caseload_snapshot(storage, "latest") resolves "latest" internally and discards the resolved id; the route then calls list_snapshots() a second, independent time just to rebuild the filename. A capture landing between the two calls can serve one snapshot's bytes under another snapshot's filename.
  5. debug_history.py capture_snapshot() max_age pruning — the try/except (KeyError, ValueError, TypeError) only wraps the fromisoformat parse, not the later existing_time < cutoff comparison. A naive (non-tz-aware) stored timestamp would raise an uncaught TypeError, aborting the index write and orphaning the just-captured snapshot in storage. Low likelihood today since now_utc is always tz-aware, but the guard doesn't cover what its comment claims it does.
  6. web.py"Snapshot {} not found".format(snapshot_id) reflects the raw ?id= query param unescaped into a text/html response. Reflected-XSS shape on a newly-added, unauthenticated route (this mirrors an existing pattern in html_file(), but it's new attack surface worth escaping while we're here).
  7. Same-slot force-captures silently overwrite each other — the snapshot id is floored to the plan-slot grid, so firing debug_history_force_capture twice within one slot produces the same id and the second storage.save() silently clobbers the first — no log, no dedup/discard path.

Docs no longer match shipped behavior

  1. docs/web-interface.md and docs/customisation.md describe a dropdown snapshot picker on the dashboard Debug panel. The merged code only adds one link, Download all (.tgz) — an earlier commit in this PR replaced the dropdown, docs weren't updated to match.
  2. docs/predbat-plan-card.md still says snapshots are matched "nearest" to a slot and labelled by "how many snapshots ago" — the shipped JS does exact floored-slot matching and labels with absolute time instead.

Efficiency

  1. create_debug_yaml() runs twice per cycle when debug_enable is on and a capture is due — update_pred() calls it directly, then _capture_debug_history() calls it again internally.
  2. html_debug_history_download's id=latest path does two full index round-trips (see solcast entity nomenclature  #4).
  3. load_all_snapshots awaits snapshot bodies sequentially instead of asyncio.gather (already used elsewhere, e.g. annual_job.py).
  4. The bulk .tgz route buffers every snapshot body plus the full archive in memory rather than streaming — worth a thought given Predbat commonly runs on Pi-class hardware.
  5. findNearestDebugSnapshot does an O(rows × snapshots) linear scan with repeated Date parsing per row, on every 5s poll of the Yesterday view — could build a Map once when debugHistoryData is fetched.
  6. The plan table renders once synchronously, then again after loadDebugHistoryData() resolves — a visible flicker on first entry to the History/Yesterday view.

Reuse / duplication

  1. web.py's new _storage() helper duplicates ComponentBase.storage, which WebInterface already inherits — could just call self.storage.
  2. debug_history.py's ring-buffer (list/discard/prune) logic duplicates annual_store.py's equivalent rather than sharing it, and has already diverged (dedup + max_age only added to one side).
  3. The plan-slot flooring math in _capture_debug_history() re-derives from scratch what output.py already computes via self.minutes_now.

Simplification

  1. annotate_steps_back()/steps_back is computed and shipped on every /debug_history_list response, but the final JS only reads .id/.timestamp — looks like a leftover from an earlier design (before the move from "N steps back" labels to absolute time).
  2. Three near-identical prune loops (same-minute dedup, max_age, max_count) could collapse into one parametrized helper.

Test coverage

Per this repo's usual bar, a couple of gaps:

  1. The three new web.py routes and _storage() have no test coverage — tests/test_web_annual.py has an established pattern for this (build a FakeRequest, call the handler directly) that isn't applied here.
  2. The new client-side JS (findNearestDebugSnapshot, loadDebugHistoryData, the Debug column rendering) is untested — tests/test_plan_why_reason.py has a precedent for testing this kind of embedded JS via a Python mirror of the function.

Minor

  1. Dead .cspell dictionary entry (onfocus) left over from the removed dropdown markup.
  2. Same-minute dedup keys on local-time strings, so a UK DST fall-back's repeated HH:MM could in theory collide and silently drop a real, distinct capture.

Most worth fixing before merge: #1#4 and #6 (correctness/security), #8#9 (docs actively mislead users right now), and #21#22 (test coverage). The rest is good-to-have cleanup, happy to see it split into a follow-up if you'd rather keep this PR focused.

@springfall2008

Copy link
Copy Markdown
Owner

Suggestion: split debug_enable's file-write behavior from this feature, and give the rotating buffer its own switch

Two related follow-ups worth doing in this same PR while we're touching this area:

1. debug_enable still causes unbounded disk growth — the original problem isn't actually fixed yet

predbat.py's if self.debug_enable: self.create_debug_yaml() (still present, right next to the new _capture_debug_history() call) writes a brand-new predbat_debug_HH_MM_SS.yaml straight to config_root via raw os.makedirs/open() every ~5-minute cycle, with no cap and no pruning, whenever a user leaves the switch on. That's exactly the "unbounded disk growth if left on" issue this PR's own commit message cites as the motivation for the rotating buffer — but this PR adds the new bounded buffer alongside it rather than replacing it, so the original bug ships unchanged.

Suggest removing that call site entirely. Nothing is lost:

  • The /debug_yaml route already serves the same content on demand without writing to disk (write_file=False).
  • The new rotating buffer now covers "what was Predbat doing recently" automatically.

debug_enable's other ~40 usages (verbose logging in plan.py/execute.py/octopus.py, and the cache/kernel-reuse bypass in prediction.py/prediction_kernel.py for more accurate step-by-step debugging) are a separate concern and should stay as-is — only the disk-write coupling should go.

2. Give the rotating buffer its own on/off switch, rather than overloading debug_history_count == 0

Right now the feature's only off-switch is setting the debug_history_count input_number to 0. That's workable but inconsistent with how this codebase normally does on/off — debug_enable and debug_history_force_capture are both real switch CONFIG_ITEMS. Suggest adding switch.predbat_debug_history_enable (defaulting either way, your call) so "is this feature on" and "how many snapshots do I keep" are independent settings, matching the existing pattern instead of a magic-zero convention.

chalfontchubby and others added 6 commits August 9, 2026 13:18
…ded raw disk writes

Per @springfall2008's review on #4438: "debug_enable still causes
unbounded disk growth - the original problem isn't actually fixed yet."
The rolling debug-history buffer (#4417) shipped alongside the original
raw create_debug_yaml() disk write, not in place of it, so leaving the
switch on still wrote an uncapped predbat_debug_HH_MM_SS.yaml every
~5-minute cycle.

Rather than remove the raw write outright: debug_enable also gates
verbose logging and the C++ kernel bypass (the more accurate but far
slower prediction path, see #4453's investigation), both genuinely useful
for watching a live issue develop cycle to cycle - finer-grained than the
history buffer's coarsest 1-hour interval floor (debug_history_interval's
min is 1, unit is hours). Removing the write entirely would be a real
capability loss for exactly the kind of live debugging this session has
been doing.

Instead, track when debug_enable was turned on (debug_enable_started) and
auto-disable it (expose_config, so it's visible in HA too) after
DEBUG_ENABLE_MAX_HOURS (2h). Bounds both the disk growth and the standing
performance cost of leaving the slow path on by accident, while preserving
full-resolution capture for someone actively watching a problem.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…imum raised to 1

Per @springfall2008's review on #4438: "give the rotating buffer its own
on/off switch, rather than overloading debug_history_count == 0" -
inconsistent with how the codebase normally does on/off (debug_enable and
debug_history_force_capture are both real switch CONFIG_ITEMS already).

Defaults on, preserving existing behaviour for anyone already relying on
debug_history_count=15 as "on". debug_history_force_capture still fires
regardless of this switch, matching the existing "explicit capture now
request is a different intent to the rolling background history" design.

Also raises debug_history_count's config-schema minimum from 0 to 1 - with
a dedicated switch, count=0 no longer needs to double as an off-switch, so
removing it as a valid value avoids "switch is on but count is 0" becoming
a second, silently-conflicting way to end up disabled.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…on genuine success

Per @springfall2008's review on #4438 (items 1-3):

1. debug_history_last_capture was reset unconditionally after the
   try/except, so a failed capture (an exception, caught and logged as a
   Warning) was silently treated as if it had succeeded - deferring the
   next routine retry a full debug_history_interval instead of leaving it
   to retry at the normal cadence.
2. The storage-unavailable path returned before that reset, so it was
   never throttled at all - every ~5-minute cycle re-entered and re-logged
   the same warning indefinitely. Fixed with a separate
   debug_history_storage_warned timestamp, deliberately independent of the
   capture throttle, so a later genuine capture attempt is never skipped
   just because the warning was recently logged.
3. debug_history_force_capture reset on trigger, before storage was even
   checked or a capture attempted - contradicting docs/customisation.md's
   "Predbat resets the switch back off itself once the snapshot has been
   taken." A failed forced capture now leaves the switch on, so it retries
   every cycle until it genuinely succeeds (or the switch is turned off),
   rather than the request being silently swallowed by an early reset.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…hot id

Per @springfall2008's review on #4438:

Item 4 (race condition): html_debug_history_download's id=latest case
called load_snapshot(storage, "latest") - which resolves "latest"
internally but discards the resolved id - then called list_snapshots() a
second, independent time just to rebuild the filename. A capture landing
between the two calls could serve one snapshot's bytes under a different
snapshot's filename. Added resolve_and_load_snapshot(), returning
(resolved_id, text) from a single resolution, and used it in the route so
the id used for the filename is always the same one the data actually
came from. load_snapshot() now delegates to it, unchanged for existing
callers.

Item 6 (reflected XSS): "Snapshot {} not found".format(snapshot_id)
reflected the raw ?id= query param unescaped into a text/html response.
Escaped via html.escape(), matching the existing pattern used elsewhere
in web.py (e.g. the plan-log highlighting).

Item 21 (test coverage): the three debug-history web routes had zero
tests. Added test_web_debug_history_routes.py covering all three
(list/download/download-all) against a real StorageLocalFiles backend,
including the specific race (item 4) and XSS (item 6) fixes above.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…havior

Per @springfall2008's review on #4438 (items 8-9): docs described the
dropdown snapshot picker and "nearest"/"how many snapshots ago" matching
from an earlier design in this PR, replaced during development by a
single "Download all (.tgz)" link and exact floored-slot matching
(confirmed against the shipped JS - findNearestDebugSnapshot's tolerance
is 1000ms, guarding only sub-second formatting noise, not a real nearest-
within-a-window search; labels use absolute local time, not "N ago").

Updated docs/web-interface.md, docs/predbat-plan-card.md, and
docs/customisation.md to describe what actually ships.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Per @springfall2008's review on #4438 (item 22): findNearestDebugSnapshot,
loadDebugHistoryData, and the plan table's Debug column were untested.
Follows test_plan_why_reason.py's established precedent for this kind of
embedded-JS test - assert on the JS source text's structure directly
(there's no JS engine in this suite), rather than executing it.

Covers: the exact/tight-tolerance (not nearest-of-several) snapshot match,
the fetch being wired to the Yesterday view switch rather than the
frequent plan poll, and the Debug column linking by the matched
snapshot's own id with a real time label - locking in the same shipped
behaviour docs were just corrected to describe.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@chalfontchubby

Copy link
Copy Markdown
Collaborator Author

Thanks for the thorough review - went through it in priority order. Everything flagged as "most worth fixing before merge" is addressed, split into separate commits so each is reviewable on its own:

Correctness (items 1-4, 6)

  • 50d33564 - debug_enable still caused unbounded disk growth even after this PR - the rotating buffer shipped alongside the original raw write, not in place of it. Rather than remove the write outright (it, and the verbose logging/kernel-bypass path it also gates, are genuinely useful at finer resolution than the history buffer's 1-hour floor for someone actively watching a live issue), it now auto-disables after 2 hours if left on.
  • f1de7ed0 - items 1-3: debug_history_last_capture and the force-capture switch now only reset on a genuine successful capture, not unconditionally. A failed attempt no longer silently defers the next routine retry a full interval, the storage-unavailable warning is throttled instead of re-logging every ~5 minutes, and a failed forced capture leaves the switch on to retry rather than resetting before the snapshot was actually taken.
  • 50706e09 - items 4 and 6: added resolve_and_load_snapshot() so id=latest resolves and loads in one call instead of two independent ones (closing the race where a capture landing in between could serve mismatched bytes/filename), and escaped the reflected ?id= query param in the 404 body.

Docs (items 8-9)

  • 60b6af40 - updated web-interface.md, predbat-plan-card.md, customisation.md to describe the single "Download all (.tgz)" link and exact floored-slot matching that actually ships, not the earlier dropdown/nearest-match design.

Tests (items 21-22)

  • 50706e09 also added test_web_debug_history_routes.py (all three routes, including the item 4/6 fixes, against a real StorageLocalFiles backend).
  • 1134c514 added test_debug_history_client_js.py for findNearestDebugSnapshot/loadDebugHistoryData/the Debug column - following test_plan_why_reason.py's existing precedent for this kind of embedded-JS test (asserting on the JS source structure, since there's no JS engine in this suite).

Design suggestions in your second comment

  • Went with your switch.predbat_debug_history_enable suggestion (7e790fc2) - also raised debug_history_count's minimum from 0 to 1, so it can no longer double as a second, conflicting off-switch.
  • Kept the raw debug_enable disk write rather than removing it outright, for the reason in 50d33564 above - open to discussing further if you'd still rather see it gone entirely.

Full suite + pre-commit clean after every commit. The remaining items (#5, #7, #10-20, #23-24) are left as follow-up per your own note that they're good-to-have cleanup rather than blocking - happy to pick any of them up next if you'd like them in this PR instead.

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.

Rolling debug-snapshot buffer with configurable interval/count, and a download UI

2 participants