Skip to content

fix: clear the attention banner after a passing run and make source removal backend-aware - #310

Merged
pmaxhogan merged 1 commit into
mainfrom
wave-1-misc-fixes
Aug 17, 2026
Merged

fix: clear the attention banner after a passing run and make source removal backend-aware#310
pmaxhogan merged 1 commit into
mainfrom
wave-1-misc-fixes

Conversation

@pmaxhogan

Copy link
Copy Markdown
Owner

Summary

Two independent, unrelated bug fixes (PR6 of the v2.12.0 wave).

Issue #271 - drill/scrub attention banner stuck red

needsAttention (and the count it displays) summed failures across the whole loaded 10-run history window. At the monthly drill cadence, one bad run kept the red "N files could not be restored" banner up for ~10 subsequent passing drills, with no way for a later success to ever clear it (each drill draws a fresh time-seeded sample, so it can never "prove" an old failure is fine again by evidence). Applied the fix the linked review recommended: key the banner on the NEWEST loaded run only. The scrub panel used the identical sum-across-window pattern, so it got the same fix per the issue's explicit note that it applies there too.

  • ui/src/stores/drill.ts, ui/src/stores/scrub.ts: failedTotal/unrecoverableTotal (and therefore needsAttention) now read off latest only, not a .reduce() over the whole window.
  • Tests extended in ui/src/__tests__/drill-panel.test.ts and ui/src/__tests__/scrub-panel.test.ts covering both the panel and the store layer, including the exact failed-then-succeeded regression sequence from the issue.

Issue #227 - source removal + "delete backed-up files" broke on every non-Drive destination

remove_source(delete_remote: true) unconditionally rejected the request with drive.unreachable and Drive-specific wording ("Remove it from Google Drive directly"), on EVERY destination, not just S3 as reported - the feature was simply never implemented. Every RemoteStore implementation (Google Drive, S3, the local folder, SFTP) already provides list_source_object_ids + trash - the same primitives the integrity scrub and the executor's remote-existence audit already use - so this is now implemented for every backend rather than gated behind a capability flag; there was nothing backend-specific left to gate.

  • src-tauri/src/commands/sources.rs: remove_source now deletes the source's live remote objects FIRST (before any local row is touched) when delete_remote is set. Enumeration failure aborts with zero deletions (mirrors the audit's "abort with zero writes" rule); trash is idempotent, so a retry after a partial failure only re-deletes what's still actually there. The core logic (delete_source_remote_objects_via) is decoupled from AppState so it's directly unit-testable against a RemoteStore fake.
  • ui/src/components/SourceTable.vue: the remove-confirm flow previously failed silently on error (the promise rejection was simply unhandled). It now surfaces the stable error code inline and keeps the confirm panel open with the checkbox still ticked for a one-click retry, instead of leaving the user staring at an unremoved source with no explanation.
  • The checkbox label (settings.sources.deleteRemoteLabel, "Also delete the backed-up files from the destination") was already backend-neutral from an earlier pass; no copy change needed there.

Test plan

  • cargo test -p driven-app --lib - 445 passed, including 3 new tests covering: skip-when-nothing-recorded, delete-every-live-object across all four BackendKind values against a RemoteStore fake, and abort-with-zero-deletions on an enumeration failure.
  • cargo clippy -p driven-app --all-targets --all-features -- -D warnings - clean.
  • cargo fmt -p driven-app -- --check - clean.
  • pnpm -C ui run test:unit - 794 passed (up from 793), including 3 new/extended tests for the banner fix and 1 new test for the removal-failure UI surface.
  • pnpm -C ui exec vue-tsc --noEmit - clean.
  • pnpm -C ui run lint - 0 errors (35 pre-existing unused-i18n-key warnings, unrelated to this change).
  • pnpm -C ui run format:check - clean.

README: checked, no changes needed (no user-visible feature list, destination table, or setting-location claim referenced either bug; the checkbox copy was already backend-neutral).

Closes #271, closes #227

Co-Authored-By: Claude Fable 5 noreply@anthropic.com

https://claude.ai/code/session_019xKUm9vH4ifb5LHR5szy1v

…emoval backend-aware

Two independent bug fixes for v2.12.0:

- Issue #271: the drill/scrub attention banner summed failures across the
  whole loaded history window, so one bad monthly drill (or weekly scrub)
  kept the red banner up for ~10 subsequent passing runs with no way for a
  later success to ever clear it. `needsAttention` (and the count it
  displays) is now keyed on the NEWEST loaded run only, per the review's
  recommended fix on the issue thread. Applied identically to both the
  drill and the scrub stores, per the issue's note that the scrub panel
  uses the same pattern.

- Issue #227: removing a source with "also delete the backed-up files"
  unconditionally rejected the request on every destination (not just
  Drive) with a Drive-specific error message ("Remove it from Google
  Drive directly"). Every RemoteStore implementation (Google Drive, S3,
  the local folder, SFTP) already provides list_source_object_ids + trash,
  the same primitives the integrity scrub and the remote-existence audit
  use, so remote deletion on source removal is now implemented for every
  backend rather than gated behind a capability flag. Enumeration happens
  before any local row is touched and aborts with zero deletions on
  failure (mirrors the remote-existence audit's "abort with zero writes"
  rule); trash is idempotent so a retry after a partial failure is safe.
  The UI also now surfaces a failed removal inline (it previously failed
  silently with no visible error) and keeps the confirm panel open with
  the checkbox still ticked for an easy retry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019xKUm9vH4ifb5LHR5szy1v
@github-actions

Copy link
Copy Markdown
Contributor

Coverage

Area main this PR delta
Rust (lib crates) 84.56% 84.59% +0.03 (OK)
UI (vue/ts) 93.04% 93.05% +0.01 (OK)

Gate: passed - no coverage regression (epsilon 0.1 pp).

@pmaxhogan
pmaxhogan merged commit 6d8e1ab into main Aug 17, 2026
18 checks passed
@pmaxhogan
pmaxhogan deleted the wave-1-misc-fixes branch August 17, 2026 19:15
@github-project-automation github-project-automation Bot moved this from Todo to Done in Driven Aug 17, 2026
pmaxhogan added a commit that referenced this pull request Aug 18, 2026
…quit drain

Rebases wave-1-debug-diag onto origin/main now that #310-#316 merged
(#312's quit-path restructuring in particular). Structural follow-up:

- The debug-logging-mode watchdog (debug_mode.rs) previously used the
  detached memlog.rs pattern (no shutdown tracking). #312 replaced the
  old shutdown_orchestrators() with a proper ShutdownHandles/
  drain_shutdown_handles structure that every other periodic background
  task (updater, telemetry, iostat, and now #311's bottleneck sampler)
  registers into for a no-orphan quit drain. Re-homed the watchdog into
  that same structure: a new DebugModeRuntime (task + shutdown watch,
  no shared hub - the watchdog only reads/writes settings directly)
  on AppState, set_debug_mode_task/shutdown_debug_mode_task mirroring
  set_bottleneck_task/shutdown_bottleneck_task exactly, a debug_mode
  field on ShutdownHandles, and spawn_watchdog now runs the same
  select!-on-shutdown-or-tick loop bottleneck_hub/iostat_hub use
  instead of a bare loop.
- Added app_state::tests::debug_mode_runtime_task_and_shutdown_round_trip,
  mirroring bottleneck's round-trip test.
- privacy.png (light+dark) and 9 shell.spec.ts baselines (light+dark)
  regenerated via `just visual-update` (Docker) - the shell baselines
  drifted independently of this PR's own diff (same delta across every
  scenario in both themes), consistent with normal headless-Chromium
  rendering drift between visual-update runs; all 106 visual specs pass
  against the regenerated set.

No other conflicts: README.md, dtos.rs, settings.rs's redaction code,
en-US.json, Activity.vue, and fixtures.ts all auto-merged cleanly with
#311's bottleneck-tile additions coexisting alongside this PR's debug
logging toggle and diagnostic-bundle changes.

Verified after rebase: cargo test -p driven-app --lib (494 passed),
cargo clippy --workspace --all-targets -- -D warnings (clean),
cargo fmt --all --check (clean), pnpm lint (0 errors), pnpm format:check
(clean), pnpm test:unit (861 passed, 64 files), pnpm build / vue-tsc
(clean).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019xKUm9vH4ifb5LHR5szy1v
pmaxhogan added a commit that referenced this pull request Aug 18, 2026
…dles (#314)

## Summary

Closes #309, closes #204. Part of the v2.12.0 wave (PR5).

**#204 (diagnostic bundle PII/secret leaks)** - `redact_settings()` used
to clone `GlobalSettings` verbatim and patch only `proxy_url`. It now
builds a field-for-field `RedactedGlobalSettings` struct, so a future
secret-bearing field added to `GlobalSettings` fails to *compile* here
until someone decides how to redact it, instead of leaking silently.
Fixes the three concrete leaks the issue named:
- `pre_backup_hook` / `post_backup_hook` (command lines - a classic home
for embedded secrets) are now redacted wholesale (`<hook-redacted: N
chars>`), not shipped raw.
- `custom_root_ca_path` is now hashed through the same `<path:hash>`
scheme the rest of the bundle already uses.
- `proxy_url` in PAC mode (a local file path, not a URL) is now hashed
instead of only having userinfo-stripping applied (which never matched a
bare path).
- The issue's "also worth fixing" item (`ProxyError`'s `Display`
embedding raw userinfo) was already fixed by #208 - verified via `git
blame`, not touched again.

**#309 (debug logging mode)** - a new Settings > Privacy & Data toggle
("Debug logging") with an always-visible amber warning panel (shown
before the toggle is ever switched on, per the approved mockup), backed
by:
- A real runtime-reloadable tracing filter (`logging.rs`,
`tracing_subscriber::reload`) - flipping the toggle now actually changes
the live process's verbosity, no restart needed. This also closes a
long-documented gap where `global.log_level` only ever exported
`RUST_LOG` for the *next* launch; it now reloads the live filter too
(deferred while debug mode is active, so it doesn't undo the debug-mode
filter).
- A persisted epoch-ms expiry + a boot-time reconcile and periodic
watchdog (`debug_mode.rs`) that auto-turns the toggle off 24h after
enabling - honoured across a restart, not just while the app keeps
running. The watchdog is registered on `AppState` and joined by #312's
no-orphan quit drain (`ShutdownHandles`/`drain_shutdown_handles`), the
same pattern #311's bottleneck sampler uses.
- A rolling log cap that widens from 25 MB to 250 MB while debug mode is
on.
- The diagnostic bundle gains `DEBUG_MODE.txt` and an unredacted
`debug/engine_state.txt` while debug mode is on - the one deliberate
exception to the #204 redaction rules, gated on the user's explicit
opt-in (every other bundle file stays redacted regardless).
- Every bundle now also ships `manifest.txt` (entry name + size), a
small bundle-usefulness improvement.
- Activity's "Export diagnostic bundle" button shows an amber "Debug
data included" chip while debug mode is on.

## Also in this PR

- **Rebased onto `main`** after #310-#316 merged. Re-homed the
debug-mode watchdog from a detached `memlog.rs`-style task into #312's
`ShutdownHandles`/`drain_shutdown_handles` no-orphan quit drain (new
`DebugModeRuntime` on `AppState`,
`set_debug_mode_task`/`shutdown_debug_mode_task` mirroring
`set_bottleneck_task`/`shutdown_bottleneck_task`).
- **CodeQL `rust/path-injection` fix** (not a dismissal): two test
helpers (`settings.rs`'s pre-existing `seeded_repo()` and this PR's new
`debug_mode.rs` one) hand-rolled a temp dir via
`std::env::temp_dir().join(format!(...))` before feeding it to
`SqliteStateRepo::open` - exactly the pattern this repo's CodeQL rule
flags (see the `tempfile` dependency comment in `src-tauri/Cargo.toml`,
and PR 151 precedent). Switched both to `tempfile::tempdir().keep()`, an
opaque external call CodeQL's dataflow can't see into, so the taint
chain never forms.
- **Also carries the h2 advisory fix** (RUSTSEC-2026-0258, low severity,
unbounded empty DATA frames) - `cargo update -p h2` (0.4.15 -> 0.4.16),
lockfile-only, no `Cargo.toml` changes. This advisory is unrelated to
this PR's own diff (`git diff` against the pre-PR base shows zero
`Cargo.lock` changes before this commit) and would fail `cargo deny`
repo-wide on `main` too; landing it here unblocks this PR's `cargo deny`
check and delivers the fix to `main` in the same step.

## Test plan

- [x] `cargo test -p driven-app --lib` - 494 passed (18 #204 redaction
tests with leak-shaped fixtures, incl. one asserting the full serialized
bundle JSON end-to-end; 5 debug-mode watchdog/expiry tests; 6
settings-persistence round-trip tests; 1 `AppState` debug-mode
task/shutdown round-trip test)
- [x] `cargo clippy --workspace --all-targets -- -D warnings` - clean
- [x] `cargo fmt --all -- --check` - clean
- [x] `cargo build --workspace --tests` - clean
- [x] `cargo deny check` - clean (advisories ok, bans ok, licenses ok,
sources ok)
- [x] `pnpm lint` / `pnpm format:check` / `pnpm test:unit` (861 passed,
64 files) / `pnpm build` (vue-tsc + vite) - all clean, run in the CI
job's exact order
- [x] Linux visual baselines regenerated via `just visual-update`
(Docker) - `privacy.png` (light+dark) plus 9 `shell.spec.ts` baselines
(light+dark) that had drifted independently of this PR; all 106 visual
specs pass
- [x] README updated (Features list + comparison-table footnote ³⁴)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_019xKUm9vH4ifb5LHR5szy1v

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
pmaxhogan added a commit that referenced this pull request Aug 18, 2026
🤖 I have created a release *beep* *boop*
---


##
[2.12.0](v2.11.1...v2.12.0)
(2026-08-18)


### Features

* **app:** opt-in debug logging mode and safer, richer diagnostic
bundles ([#314](#314))
([33c281c](33c281c))
* **core:** allow nested backup sources when the parent excludes the
child ([#294](#294))
([0b62df9](0b62df9))
* **core:** live exclusion pickup and a visible pending-work queue
([#313](#313))
([e6427c7](e6427c7))
* live bottleneck indicator on the Activity dashboard
([#311](#311))
([2d9d763](2d9d763))
* **ui:** folder picker sort/filter/create/rename and exclusions size
rollups ([#315](#315))
([7e87341](7e87341))


### Bug Fixes

* **app:** never freeze on tray quit during a backup; quitting tray
state; honest recovery status
([#312](#312))
([f951cde](f951cde))
* clear the attention banner after a passing run and make source removal
backend-aware ([#310](#310))
([6d8e1ab](6d8e1ab))
* **s3:** honest per-source versioning across backends and
multipart-upload leak sweep
([#316](#316))
([d462592](d462592))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

1 participant