Skip to content

feat(uploads/downloads): live progress bar driven by ant-core events - #65

Merged
Nic-dorman merged 1 commit into
mainfrom
feat/upload-download-progress-events
Apr 30, 2026
Merged

feat(uploads/downloads): live progress bar driven by ant-core events#65
Nic-dorman merged 1 commit into
mainfrom
feat/upload-download-progress-events

Conversation

@Nic-dorman

Copy link
Copy Markdown
Contributor

Summary

  • Wires ant-core's UploadEvent / DownloadEvent channel API (already shipped in ant-core-v0.2.0) into the GUI for the direct-key wallet upload path and both download flows.
  • Adds a per-stage label (e.g. Quoting · 47%, Storing · 73%) under the existing status badge, and a global 0–100% progress bar built off (quoting * 0.5) + (storing * 0.5) so the bar moves continuously across both phases.
  • New ProgressLine.vue component handles the determinate / indeterminate cases (encryption has no total until it finishes — falls back to a pulsing bar).

What changed

Rust (src-tauri/src/autonomi_ops.rs)

  • spawn_upload_progress_forwarder / spawn_download_progress_forwarder drain a per-call mpsc channel and re-emit each event as a Tauri upload-progress / download-progress event keyed by transfer_id.
  • wallet_upload now calls file_upload_with_progress(_, PaymentMode::Auto, Some(tx)).
  • download_file / download_public / download_with_datamap take a transfer_id arg and call file_download_with_progress.

Frontend

  • stores/files.ts: TransferStage type, stage / stageDone / stageTotal on FileEntry, setupProgressListeners() action routing Tauri events back to the right row by parsing transfer_id as the entry id. wallet_upload invocation now passes String(id) as uploadId so events route correctly.
  • app.vue calls setupProgressListeners() on boot.
  • pages/files.vue: stageDetail() returns the per-stage percent label; showsProgressBar() gates rendering to active uploads/downloads.

What's NOT in this PR

  • External-signer (WalletConnect) flow is intentionally left dark. Client::file_prepare_upload_with_visibility and Client::finalize_upload[_merkle] don't accept a progress sender today, so threading them needs a small upstream ant-core PR. Tracked separately.
  • No ant-core dep bump — the UploadEvent / DownloadEvent API is already in the pinned ant-core-v0.2.0.

Test plan

  • cargo check, cargo clippy --all-targets -- -D warnings, cargo fmt --check clean.
  • npx vitest run — 21/21 pass.
  • Manual on local Anvil devnet (cargo run --release --example start-local-devnet --features devnet from ant-client) for 3-, 9-, 10-, and 28-chunk uploads. Quote phase percent climbs 0→100% in the label while bar climbs 0→50%; storage phase mirrors it 50→100%. Download by datamap shows resolving → downloading transitions.
  • Reviewer to verify behaviour against the production network on a personal funded wallet — direct-key path is the same code path, just a different EVM target.

Notes for reviewers

  • During the local-devnet manual test the saorsa-core bootstrap cache leaks mainnet peers from previous sessions (V2-211); wipe %LOCALAPPDATA%\saorsa\bootstrap\bootstrap_cache.json before launching the devnet to keep traffic loopback-only.
  • This PR's progress wiring re-uses the entry's row id as the transfer_id rather than a random UUID, so the listener can update the right row without an extra mapping table.

🤖 Generated with Claude Code

… the UI

Surface real per-chunk progress for the direct-key wallet upload path
(`wallet_upload`) and for both download flows. The plumbing is opt-in and
keyed by transfer_id so events route to the right row.

Rust (src-tauri/src/autonomi_ops.rs):
- New mpsc forwarders that drain UploadEvent / DownloadEvent and re-emit
  them as Tauri `upload-progress` / `download-progress` events.
- Quoting maps to the first 0-50% of the bar, storage to 50-100%, so the
  user sees continuous forward motion across both phases.
- wallet_upload swaps `file_upload` for `file_upload_with_progress`.
- download_file / download_public take a transfer_id arg and call
  `file_download_with_progress`.

Frontend:
- stores/files.ts gains TransferStage + stage/stageDone/stageTotal on
  FileEntry; setupProgressListeners() routes Tauri events back to the
  matching row.
- app.vue calls setupProgressListeners() on boot.
- pages/files.vue renders a per-stage label with percent inside the
  current step, paired with a global progress bar from the existing
  `progress` field.
- components/ProgressLine.vue is the small bar+label widget; falls back
  to an indeterminate pulse during encryption (total chunks unknown).

External-signer flow (start_upload / confirm_upload[_merkle]) is left
dark for now — it needs an upstream ant-core PR threading the sender
through `file_prepare_upload_with_visibility` and `finalize_upload[_merkle]`.

Verified locally on a 25-node Anvil devnet: 3-, 9-, 10-, and 28-chunk
uploads all show smooth quote/store transitions; download bars climb
during chunk fetch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Nic-dorman
Nic-dorman merged commit 49a5464 into main Apr 30, 2026
4 checks passed
@Nic-dorman
Nic-dorman deleted the feat/upload-download-progress-events branch April 30, 2026 09:35
Nic-dorman added a commit that referenced this pull request Apr 30, 2026
…external-signer

Builds on top of #65 (direct-path progress wiring) to also drive a live
progress bar through the WalletConnect / external-signer flow, plus a
small grab-bag of fixes that surfaced during end-to-end Sepolia testing.

The three external-signer Tauri commands now spawn the same forwarder
PR #65 introduced and pass it into ant-core's new `_with_progress`
upstream methods:

  start_upload          → file_prepare_upload_with_progress
  confirm_upload        → finalize_upload_with_progress
  confirm_upload_merkle → finalize_upload_merkle_with_progress

ant-core dep is pinned to the upstream PR's branch
(`feat/external-signer-progress-events`) until that lands and a tag
ships. Swap the dep back to a tagged `ant-core-vX.Y.Z` once #68 is
merged + tagged.

External-signer's transfer_id is the opaque `quote-...` string returned
by `start_upload`, not the row id. The progress listener gains a
`_transferIdToRowId` map, populated as soon as `getUploadQuote` knows
the id (BEFORE invoking start_upload, so quote-phase events route from
the very first event). The wallet/download paths still use `String(id)`
and fall through to the numeric parse — both flows share one listener.

- `showsProgressBar` widened to render during `quoting` / `paying` too,
  not just `uploading`. The bar otherwise vanished during the wallet
  popup and the bulk of the work on the external-signer path.
- `stageDetail()` switched to per-stage percent ("Storing · 67%") instead
  of fractions ("Storing · 6/9"). Bar = global progress, label = local.
- `StatusBadge` gets `max-w-xs truncate` + a `title` tooltip so long
  failure messages don't blow the table width.

- **V2-236** chain auto-switch — `ensureActiveChain()` calls
  `switchChain` before `writeContract` so the wallet is always on the
  target chain. Without it, a wallet on Arbitrum One signing for an
  Arbitrum Sepolia tx blew up with a chain-mismatch error and no clear
  recovery in the UI.
- **V2-231** EIP-1559 fee invariant — override BOTH `maxFeePerGas` and
  `maxPriorityFeePerGas` explicitly (1 gwei / 0.1 gwei). viem's
  partial auto-estimation produced an invalid pair on Arbitrum's tiny
  base fees, surfacing as either a base-fee race or a "User rejected"
  wrapper around the underlying invariant violation. Three sites
  (payForQuotes, payForMerkleTree, approve in ensureAllowance).

End-to-end on a Sepolia-EVM devnet (`start-devnet-sepolia` from
ant-client + MetaMask Mobile via WalletConnect, funded Sepolia wallet):

- 9-chunk file, full external-signer flow: quote → MM popup → store
- Bar climbs 0→50% during quoting (per-chunk ChunkQuoted), pauses at
  50% during the MM popup, climbs 50→100% during storage.
- Tx hash confirmed on chain, `External-signer upload finalized` logged
- 21/21 vitest pass, cargo check clean against the branch ant-core dep

Path-dep to upstream branch will fail any CI that pulls a registry-only
ant-core. Hold until #68 lands, then bump the dep to the new tag and
unmark draft.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Nic-dorman added a commit that referenced this pull request Apr 30, 2026
)

* feat(uploads): live progress + chain auto-switch + EIP-1559 fees for external-signer

Builds on top of #65 (direct-path progress wiring) to also drive a live
progress bar through the WalletConnect / external-signer flow, plus a
small grab-bag of fixes that surfaced during end-to-end Sepolia testing.

The three external-signer Tauri commands now spawn the same forwarder
PR #65 introduced and pass it into ant-core's new `_with_progress`
upstream methods:

  start_upload          → file_prepare_upload_with_progress
  confirm_upload        → finalize_upload_with_progress
  confirm_upload_merkle → finalize_upload_merkle_with_progress

ant-core dep is pinned to the upstream PR's branch
(`feat/external-signer-progress-events`) until that lands and a tag
ships. Swap the dep back to a tagged `ant-core-vX.Y.Z` once #68 is
merged + tagged.

External-signer's transfer_id is the opaque `quote-...` string returned
by `start_upload`, not the row id. The progress listener gains a
`_transferIdToRowId` map, populated as soon as `getUploadQuote` knows
the id (BEFORE invoking start_upload, so quote-phase events route from
the very first event). The wallet/download paths still use `String(id)`
and fall through to the numeric parse — both flows share one listener.

- `showsProgressBar` widened to render during `quoting` / `paying` too,
  not just `uploading`. The bar otherwise vanished during the wallet
  popup and the bulk of the work on the external-signer path.
- `stageDetail()` switched to per-stage percent ("Storing · 67%") instead
  of fractions ("Storing · 6/9"). Bar = global progress, label = local.
- `StatusBadge` gets `max-w-xs truncate` + a `title` tooltip so long
  failure messages don't blow the table width.

- **V2-236** chain auto-switch — `ensureActiveChain()` calls
  `switchChain` before `writeContract` so the wallet is always on the
  target chain. Without it, a wallet on Arbitrum One signing for an
  Arbitrum Sepolia tx blew up with a chain-mismatch error and no clear
  recovery in the UI.
- **V2-231** EIP-1559 fee invariant — override BOTH `maxFeePerGas` and
  `maxPriorityFeePerGas` explicitly (1 gwei / 0.1 gwei). viem's
  partial auto-estimation produced an invalid pair on Arbitrum's tiny
  base fees, surfacing as either a base-fee race or a "User rejected"
  wrapper around the underlying invariant violation. Three sites
  (payForQuotes, payForMerkleTree, approve in ensureAllowance).

End-to-end on a Sepolia-EVM devnet (`start-devnet-sepolia` from
ant-client + MetaMask Mobile via WalletConnect, funded Sepolia wallet):

- 9-chunk file, full external-signer flow: quote → MM popup → store
- Bar climbs 0→50% during quoting (per-chunk ChunkQuoted), pauses at
  50% during the MM popup, climbs 50→100% during storage.
- Tx hash confirmed on chain, `External-signer upload finalized` logged
- 21/21 vitest pass, cargo check clean against the branch ant-core dep

Path-dep to upstream branch will fail any CI that pulls a registry-only
ant-core. Hold until #68 lands, then bump the dep to the new tag and
unmark draft.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(uploads/downloads): segmented bar-dot-bar progress + clearer column layout

Layout changes (pages/files.vue):
- Status column moved one to the left in both upload and download tables.
- File name cell becomes the wider one: filename on top, progress bar below
  (only when active). Bar gets a min-w-[8rem] so flex-1 segments don't
  collapse to invisible widths in narrow rows.
- Status cell stacks the badge on top with the stage detail line below it,
  so badge + sub-text track together visually.

Progress bar (components/ProgressLine.vue + new BarSegment.vue + PayDot.vue):
- Upload bar is now segmented [quote] · [pay-dot] · [store], conveying the
  three-phase shape of the external-signer flow at a glance. Wallet-flow
  and downloads use the same component but the dot stays solid (wallet)
  or the bar collapses to a single fill (download).
- Bar fill is now stage-driven, not status-driven. Wallet-flow JS pre-flips
  status='uploading' before any quoting starts; the old logic took that
  literally and rendered the left segment full + dot solid before any work
  was done. New logic prefers the live `stage` from ant-core's events and
  uses status only as a "we're past quoting" fallback (paying / complete).
- PayDot's empty state uses bg-autonomi-surface (same as the bar tracks)
  instead of a transparent bordered ring, for tighter visual cohesion.

Status label (pages/files.vue + components/StatusBadge.vue):
- statusLabel() now consults `stage` when the outer status disagrees with
  the inner phase. Wallet-flow uploads under stage='quoting' now show
  "Quoting" instead of "Uploading"; downloads under stage='resolving'
  show "Resolving datamap" instead of "Downloading". Badge + sub-text
  stay in sync.
- StatusBadge: non-error labels render at natural width with
  whitespace-nowrap (no truncate, no max-w), so the column auto-sizes
  tight to its widest live label. Error labels keep `max-w-xs truncate`
  so a viem stacktrace can't blow the column out.
- New badge entries for "Encrypting…" and "Resolving datamap".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(uploads): persist gas_cost in upload_history.json so it survives reloads

Previously the runtime FileEntry tracked gas_cost (rendered as a small
"+ X gas" line under cost in the table) but the persisted UploadHistoryEntry
shape didn't carry it. On app reload / Files-page refresh, every completed
upload reloaded with cost intact but gas_cost dropped — the second line
silently vanished.

Adds optional gas_cost?: string | null on both the TS UploadHistoryEntry
and the Rust struct (with #[serde(default)] for backwards compat with
existing history files). loadHistory() and persistHistory() round-trip
the field. Legacy entries written before this change remain valid and
just render without the gas line, same as before.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(uploads/downloads): per-row Remove + Retry actions, move Clear History

V2-233: each settled (failed / complete / downloaded) row now reveals a
hover affordance on the right — `✕` for remove. Wires to the existing
`removeEntry()` store action.

V2-234: failed upload rows also show a `↻ Retry` button. Reuses
`entry.path` so the user doesn't re-pick the file from disk; the new
`retryUpload()` store action clears stale per-attempt fields (error,
progress, stage*, address, datamap, costs, gas), flips status to
`queued_for_upload`, and lets the page scheduler re-run
`startRealUpload` (fresh quote + fresh wagmi sign).

V2-232: bulk "Clear history" moves out of the Files page header and
into Settings as a confirmation-gated action. The per-row × covers the
common case of trimming one entry; bulk wipe is one click further away
to prevent accidental loss. The new Settings card shows the count of
settled entries that would be removed and is disabled when there's
nothing to clear.

Companion rename surfaced earlier in the session: external-signer's
`paying` status now renders as "Awaiting approval" instead of "Paying"
— more accurate while wagmi is awaiting the wallet signature, and
unambiguous since the wallet flow never enters this state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* style(rust): satisfy cargo fmt on autonomi_ops.rs

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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