Skip to content

fix(ui): make the backing-up bar a true determinate progress bar - #155

Merged
pmaxhogan merged 1 commit into
mainfrom
fix/bridge-exec-progress
Jul 25, 2026
Merged

pmaxhogan merged 1 commit into
mainfrom
fix/bridge-exec-progress

Conversation

@pmaxhogan

Copy link
Copy Markdown
Owner

Root cause

The top-of-app "Backing up" bar is meant to be determinate during upload, but in production it ran the indeterminate teal sweep for the entire run.

GlobalProgressBar.vue renders determinate when the progress store's percent is non-null, and percent derives from the executing OrchestratorState's ExecProgress totals. But the orchestrator transitions to Executing { progress: ExecProgress::zero() } exactly once per source (crates/driven-core/src/orchestrator.rs:1521) and then streams the moving counters as a separate OrchestratorEvent::Progress { source_id, progress }.

Those ticks never reached the webview: classify_bridge_event in src-tauri/src/assembly.rs mapped them to BridgeAction::Ignore, commented "not bridged to the webview in M5 (the progress DTO lands with a later milestone)". That milestone never landed. So the webview only ever saw the zeroed snapshot, percent stayed null, and the bar could never leave the indeterminate branch.

Worth noting: the executor calls on_progress with the real plan totals (files_total, bytes_total) on its very first call, before any op completes (crates/driven-core/src/executor.rs:3922) - so a single bridged tick is enough to make the bar determinate.

The fix

Bridge the ticks and fold them into the progress store.

Channel. Rather than invent a new event, this uses the SPEC s11.7 channel sync:source_progress, whose constant was already reserved (#[allow(dead_code)]) in src-tauri/src/events.rs and whose typed listener stub already sat unused in ui/src/ipc/events.ts.

Wire shape: { account_id, source_id, progress }, snake_case - matching the rest of the M5 sync DTOs (AccountSyncStatus, account:needs_reauth) and ExecProgress itself, so the store parses ticks with the same reader it uses for the executing state's embedded snapshot. The account_id is added by the bridge, which knows it; the core event carries only the source.

That pre-existing UI stub is also corrected here: it declared sourceId in camelCase and typed progress as unknown, so it would have silently mismatched the real payload.

Edge cases covered

  • A late tick cannot resurrect a finished bar. Ticks are gated on the account's current state tag being executing, checked both when storing and when summing. The executor's final snapshot can trail the state transition; it is dropped. A tick for an account with no known status is dropped too.
  • A new run never inherits the previous run's progress. Any status change for an account deletes that account's tick - including a fresh executing transition, which is the head of a new source's execution and carries zero(). Without this the bar would open at the prior run's 100% before falling back. An aggregate payload (what hydrate() returns) clears every tick.
  • Multi-account aggregation still sums, including a mix of ticked and un-ticked accounts, and the existing mixed upload+delete guard (op counts rather than bytes, so the bar cannot read 100% while trash ops are pending) still holds across ticks.

Also

The label now names the file counts once the ticks carry a real total: "Backing up - 50% (1,234 of 3,000 files)", via a new progress.backingUpPercentFiles key using the existing locale-aware Intl.NumberFormat. It falls back to the bare percent for delete-only plans, which upload no files. Aria attributes are unchanged in structure and now carry the richer label.

Tests

  • assembly.rs: new unit tests for the SourceProgress classification arm (the tick is forwarded unchanged) and for the payload's snake_case serialization with the account id.
  • progress-store.test.ts: 14 new tests covering the fold-in and all three edge-case families above, plus the two-listener subscribe/teardown and a partial-registration-failure cleanup.
  • global-progress-bar.test.ts: the indeterminate-to-determinate transition on the first live tick, and the new file-count label.

Two existing assertions were updated because the store now registers two listeners rather than one (the app-shell boot count, and the store's idempotency/teardown counts).

🤖 Generated with Claude Code

The orchestrator transitions to Executing { progress: ExecProgress::zero() }
exactly once per source and then streams the moving counters as separate
OrchestratorEvent::Progress ticks. The app-shell event bridge classified those
ticks as BridgeAction::Ignore ("not bridged to the webview in M5"), so the
webview only ever saw the zeroed snapshot embedded in the state: the progress
store's percent stayed null and the top-of-app bar ran the indeterminate teal
sweep for the entire upload.

Bridge the ticks on the SPEC s11.7 `sync:source_progress` channel (already
reserved in events.rs) as { account_id, source_id, progress }, and fold them
into the progress store over the state's embedded snapshot.

Edge cases: a tick is only stored/summed while its account is currently
executing (a late final snapshot cannot resurrect a finished bar); any state
change for an account drops its tick, so a new run never inherits the previous
run's 100%; an aggregate payload clears every tick; multi-account runs still sum.
@github-actions

Copy link
Copy Markdown
Contributor

Coverage

Area main this PR delta
Rust (lib crates) 79.51% 79.50% -0.01 (OK)
UI (vue/ts) 90.23% 90.39% +0.16 (OK)

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

@pmaxhogan
pmaxhogan merged commit eed80a6 into main Jul 25, 2026
18 checks passed
@pmaxhogan
pmaxhogan deleted the fix/bridge-exec-progress branch July 25, 2026 15:15
@github-project-automation github-project-automation Bot moved this from Todo to Done in Driven Jul 25, 2026
pmaxhogan added a commit that referenced this pull request Jul 25, 2026
🤖 I have created a release *beep* *boop*
---


## [2.3.0](v2.2.0...v2.3.0)
(2026-07-25)


### Features

* **core:** record a backup_done activity row when a run completes
([#160](#160))
([90cde5c](90cde5c))
* **ui:** files-uploaded stat card with sparkline and smoother Activity
load-in ([#157](#157))
([2d85c99](2d85c99))
* **ui:** live streaming folder-tree preview for the exclusion editor
([#158](#158))
([47ebe14](47ebe14))


### Bug Fixes

* **telemetry:** count bundled uploads in the anonymous aggregate
([#159](#159))
([11af7ea](11af7ea))
* **ui:** label bundle_upload and hook activity event types
([#154](#154))
([2998c76](2998c76))
* **ui:** make the backing-up bar a true determinate progress bar
([#155](#155))
([eed80a6](eed80a6))

---
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

Development

Successfully merging this pull request may close these issues.

1 participant