feat(updater): pre-release channel opt-in toggle - #68
Merged
Conversation
Nic-dorman
marked this pull request as ready for review
April 30, 2026 11:30
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>
Adds a Settings -> Advanced -> "Pre-release builds" toggle that persists to AppConfig.prerelease_channel. Off by default. Internal-tester convenience: when on, the next launch resolves the latest release including pre-releases (rc / beta) for auto-update. UI only at this stage -- the updater plugin still uses the static stable endpoint from tauri.conf.json. The endpoint-selection plumbing comes in follow-up commits. The toggle shows a "Restart the app to apply this change" hint banner whenever the live value differs from the boot snapshot, since the resolved endpoint URL is captured once at app init. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds two Tauri commands that bypass the plugin's compile-time static
endpoint and let the running app pick its updater endpoint based on the
persisted prerelease_channel flag:
check_for_update_custom - reads AppConfig, resolves endpoint URL
(stable redirect or prerelease tag URL via
the GitHub releases API), builds an
UpdaterBuilder with that endpoint, calls
check, stashes the resulting Update in
UpdaterChannelState for the install step.
install_pending_update - pulls the Update from state, runs
download_and_install with progress events
re-emitted as update-download-event with the
same Started/Progress/Finished shape the JS
plugin emits today.
The frontend store still uses the old plugin's check() in this commit; the
switch happens in the next one. UpdateMetadata carries an is_prerelease
flag so the dialog can show a "Pre-release" badge to testers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the @tauri-apps/plugin-updater check()/Update.downloadAndInstall()
calls with our own check_for_update_custom + install_pending_update Tauri
commands so the running app picks its endpoint based on the persisted
prerelease_channel flag instead of the static tauri.conf.json endpoint.
Behaviour parity with the previous flow:
- Started/Progress events drive downloadTotal/downloadedBytes the same
way; the Rust side re-emits them as update-download-event with the
same payload shape.
- Error mapping (humaniseUpdateError) is preserved -- the Rust command
surfaces tauri-plugin-updater's error strings verbatim, so the same
regexes match.
- markRaw() workaround for Update is no longer needed -- the Update
instance now lives in Rust app state instead of a Pinia field.
UpdateDialog gets a "Pre-release" badge next to the version when the
offered release is a pre-release, so testers running on the rc channel
can tell at a glance which channel surfaced the update.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds an inline button beside the "Restart the app to apply this change." hint that calls tauri-plugin-process's relaunch() to self-restart. The plugin is already registered and the process:default permission is granted in capabilities/default.json, so no infrastructure changes needed. Saves testers from having to close + reopen the app manually to apply a channel toggle. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Nic-dorman
force-pushed
the
feat/updater-prerelease-channel
branch
from
April 30, 2026 11:41
63b02fc to
a1c6e72
Compare
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an opt-in "Pre-release builds" toggle to Settings → Advanced so internal testers running on RCs get auto-updates between RC builds, not just at stable cuts. Off by default — zero impact on the stable-channel population.
Why
tauri-plugin-updaterreads its endpoint from compile-timetauri.conf.jsonconfig, and we point that atreleases/latest/download/latest.json. GitHub's/releases/latest/...redirect is defined to skip releases markedprerelease=true, so RC artefacts are invisible to the in-app updater even thoughrelease.ymlcorrectly publishes a signedlatest.jsonto every prerelease.Internal testers had to download every RC manually. This PR closes that gap.
How
The plugin's
Builderdoesn't accept runtime endpoints, so we bypass its frontendcheck()/Update.downloadAndInstall()entirely and own the flow ourselves viaapp.updater_builder().endpoints(...)(which the per-callUpdaterBuilderdoes support). Plugin stays registered for permissions / target detection.Three commits, each independently buildable:
feat(settings): add pre-release channel toggle (UI only)AppConfig.prerelease_channel: bool(default false), persisted via existingconfig.tomlround-trip.loadConfig. The snapshot represents the channel the running session resolved its endpoint URL from, so the hint only appears when a restart would actually change behaviour.feat(updater): channel-aware check + install commandssrc-tauri/src/updater_channel.rsmodule withUpdaterChannelState(managed) holding a cached resolved endpoint URL and the pendingUpdateinstance.check_for_update_customreads the channel flag, resolves the endpoint (stable redirect for off; latest tag including pre-releases via the GitHub releases API for on), builds anUpdaterBuilderwith that endpoint, calls.check(), stashes theUpdatefor later install. ReturnsUpdateMetadata { version, body, is_prerelease }.install_pending_updatepulls theUpdatefrom state, runsdownload_and_install, re-emits Started/Progress/Finished asupdate-download-eventwith the same payload shape the JS plugin emits today (frontend handler unchanged).feat(updater): switch frontend store to channel-aware Tauri commandsstores/updater.tsno longer imports from@tauri-apps/plugin-updater. Calls our custom commands instead. Subscribes toupdate-download-eventfor progress.markRaw(Update)workaround goes away — theUpdateinstance now lives in Rust state, not Pinia.UpdateDialogshows an amber "Pre-release" badge next to the version whenmeta.is_prereleaseis true.Notes
release.yml. The workflow's existingprerelease=truedetection (*-rc.*/*-beta.*/*-alpha.*) is correct.Test plan
cargo check --all-targetscleancargo clippy --all-targets -- -D warningscleancargo fmt --checkcleannpx nuxi buildclean (Nuxt + Vite)vitestlocally hits the same Windows + npm +@vue/test-utilsESM flake we've seen on every branch since #59 — pre-existing, CI on Linux is the source of truth.Closes
Linear ticket tracking this work (internal).