Skip to content

release: v1.4.2 — Address Book, THORName/MAYAName, swap fixes, firmware 7.14.1#225

Merged
BitHighlander merged 53 commits into
masterfrom
release/1.4.2
Jun 7, 2026
Merged

release: v1.4.2 — Address Book, THORName/MAYAName, swap fixes, firmware 7.14.1#225
BitHighlander merged 53 commits into
masterfrom
release/1.4.2

Conversation

@BitHighlander

Copy link
Copy Markdown
Collaborator

release: v1.4.2

Brings master in line with the published v1.4.2 prerelease.

Merged back to develop in 41010aae (Merge branch 'release/1.4.2' into develop).

Included since v1.4.1

Features

Firmware

Fixes

Verification

  • arm64 + x64 DMGs: signed (Developer ID KEY HODLERS LLC), notarized, stapled
  • Gatekeeper accepted on both; backend smoke-test passed (no missing externals)
  • Signed x64 tar.zst hash verified against remote; SHA256SUMS regenerated from on-wire bytes
  • CI green on release/1.4.2

Windows installer is built + signed separately on the Windows box and folded into the same release.

BitHighlander and others added 30 commits May 27, 2026 21:21
PR #186 added Hive to chains.ts with minFirmware 7.14.0 (wrong).
The fix in ae70c35 (PR #187) was not carried through because develop
already had the Hive entry — no conflict was surfaced during merge.

- chains.ts: bump Hive minFirmware 7.14.0 → 7.16.0 (no released
  firmware supports Hive until 7.16.0)
- CommandPalette: add firmwareVersion prop + filter with isChainSupported
  so ⌘K results also respect firmware gates
- App.tsx: pass deviceState.firmwareVersion to CommandPalette
Trigger rebuildActivityHistory in the background (3s after ready) every
time the state-change handler sees state='ready'. Covers both initial
app startup and device reconnects. Passphrase wallets are skipped for
privacy, matching the existing scanChainHistory guard.
Only the X button can now dismiss the swap dialog — prevents accidental
closure mid-flow when clicking outside the modal.
v1.4.1 published as prerelease.
v7.14.1 was registered with the CI-built payload hash e0c05185 (built from
branch commit 9e057c2 and mislabeled under the -5482e73- asset name). That
binary does not reproduce from the public v7.14.1 tag: a clean
`git checkout v7.14.1 && scripts/build/docker/device/release.sh`
deterministically produces payload 73a88068 (verified on two clean builds).

The two binaries are code-identical -- they differ only in the embedded
40-char git commit stamp (36 bytes at code offset 313718). Net source delta
between the commits is CI/emulator/test-client only; all device sources are
identical. Both carry valid 3-of-5 signatures (slots 1/2/3).

Switch the registered + bundled artifact to the tag-reproducible build so the
documented verify-by-rebuild flow passes:
- firmware/releases.json, firmware/manifest.json: e0c05185 -> 73a88068
- firmware-bundle/releases.json: e0c05185 -> 73a88068
- firmware-bundle/v7.14.1/firmware.keepkey.bin: swap to 73a88068 build
- docs/HANDOFF-firmware-7.14.1-release.md: provenance note

Stays in the beta key (the alpha-opt-in channel); not promoted to latest.
The GitHub firmware release v7.14.1 has been updated to the 73a88068 asset +
corrected HASHES.txt and marked prerelease.
MAX CACAO swaps swept 100% of the wallet balance into a MsgDeposit,
leaving nothing for MAYAChain's 0.2 CACAO NativeTransactionFee. The
network charges that fee on top of the deposited amount at the bank
layer (the tx fee.amount is '0'), so the deposit reverted with
"insufficient funds" and the swap failed with no refund. Observed
repeatedly on maya13d4… (e.g. block 16883821).

cosmos.ts FEES.mayachain was 0, so the isMax reserve was zero. Set it
to 0.2 CACAO to match the native fee, so MAX = balance − 0.2. THORChain
already reserved its 0.02 RUNE native_tx_fee correctly. This also fixes
MAX MsgSend on Maya, which incurs the same native fee.

Adds cosmos-max-send.test.ts reproducing the failing numbers.
…-desktop

The firmware manifest was migrated to keepkey-vault, but the engine still
fetched the remote floor from keepkey-desktop/master, which is stale (it
advertises only v7.10.0 -- no 7.14.x). The dev download tool pointed at the
keepkey-vault `main` branch, which 404s (default branch is `master`).

- engine-controller.ts: MANIFEST_URL -> keepkey-vault/master
- firmware/download.ts: RELEASES_URL + FW_BASE_URL main -> master

Binary loading is unaffected for bundled versions (resolveBinarySource prefers
the DMG-bundled file); this only corrects manifest/version detection and the
HTTP fallback base.
- HANDOFF: the GitHub release is prerelease (alpha), not "Latest". Add a
  Release channel note: 7.14.1 lives under manifest.beta (the vault's alpha
  opt-in), so only users who enable Settings -> Alpha firmware are prompted;
  default users stay on latest (7.10.0).
- firmware-bundle/README: bundled alpha firmware is 7.14.1 (was 7.14.0);
  remote manifest fetched from keepkey-vault/master (was stale keepkey-desktop).
- firmware/README: manifest link main -> master.
…-hash

fix(firmware): register reproducible v7.14.1 build hash (73a88068)
…serve

fix(swap): reserve Maya native fee on MAX CACAO deposits
fix(chains): gate Hive behind firmware 7.16.0
feat(activity): auto-scan history when device becomes ready
…lose

fix(swap): block backdrop clicks from closing swap dialog
* fix(swap): retry failed initial Pioneer registration

If the initial CreatePendingSwap in trackSwap throws a transient 400/500
at broadcast time (Pioneer load spike, duplicate-key race), the swap is
never registered. GetPendingSwap then returns not_found forever and the
tracker sits on "waiting for confirmations" permanently — even though the
user's funds already moved on-chain.

Mark such txids and re-attempt registration on each refreshSwap (up to
10x). Swapper-agnostic: covers THORChain/Maya/NEAR memo swaps that the
existing Relay-id backfill path never touches.

Also surface the server's response body on a CreatePendingSwap HTTP error
so a load spike can be told apart from a schema rejection.

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

* fix(swap): drive Pioneer re-registration off not_found, not in-memory flag

Addresses review findings on the initial approach:

[P1] The in-memory retry marker was lost on restart — a swap whose
registration failed, then rehydrated from SQLite after a Vault restart,
had no marker and stayed not_found forever. Re-registration is now keyed
off the not_found signal in refreshSwap itself, so it triggers whenever
Pioneer has no row for an active local swap, restart or not. NEAR Intents
is excluded (it stays not_found by design; 1Click is authoritative).

[P3] Clear the attempt bookkeeping the moment GetPendingSwap returns a
real row. This covers the case where the initial call timed out *after*
Pioneer created the row (no more duplicate 409s / noisy reposts) and stops
the map from leaking one entry per swap — entries now exist only for swaps
that actually hit not_found, and are dropped on success.

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

* fix(swap): also re-register on thrown 404 from live pioneer-server

The previous revision only re-registered when GetPendingSwap returned a
200 body with status='not_found'. But the live pioneer-server returns HTTP
404 for that condition (pending-swaps.controller.ts:700,1226), and the
swagger-client throws on non-2xx — so refreshSwap landed in the existing
404 catch ("not indexed yet") and never re-registered. The restart/
missed-registration case still stuck forever against the real server.

Extract the bounded re-registration into reregisterIfMissing() and call it
from BOTH the 200-body not_found branch and the thrown-404 catch, so the
fix works regardless of how Pioneer signals "no row".

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rt detection to Pioneer

A MAX CACAO→ETH swap reverted on-chain with "insufficient funds" (no refund),
the SECOND time after PR #212. PR #212 set the MAX reserve to exactly the 0.2
CACAO native fee, but with zero headroom: Pioneer reported the balance as
778.25133011 while the real on-chain balance was ~301,100 base units lower, so
amount + fee overdrew. tx FAF55806… → tx_response.code=1.

Reserve 2× the native fee on MAX (cosmos.ts), mirroring the frontend EVM/Solana
MAX reserves which already leave a real buffer because Pioneer rounds balances.
~$0.025 of dust on Maya; the remainder stays in the wallet. Applies to the
staking-MAX builder too. Test rewritten to assert headroom (amount < balance −
bareFee), not a magic exact amount.

Detection is a separate, Pioneer-side gap: Pioneer's Broadcast returns code:0
(CheckTx/mempool) and never surfaces the in-block DeliverTx revert, so the swap
spins on the progress animation until the 24h stale cleanup. The vault must not
call chain nodes for this — handed off in
docs/handoff-pioneer-cosmos-tx-revert-detection.md. Retro in
docs/retro-maya-max-swap-revert.md.

The buffer lowers the frequency of this revert; only the Pioneer change detects
it (and covers bad-memo / pool-halt / slippage failures the buffer can't prevent).
…c FEES value

Review catch (P2): for cosmos/osmosis the tx fee is paid via fee.amount, and the
default dispatcher path uses feeLevel=5, whose gas multiplier doubles the 5000
uatom template fee to 10000. Reserving FEES.cosmos*2 (=10000) before that left
amount + fee == balance exactly (1.0 ATOM → amount 990000 + fee 10000), so a
stale-high balance still overdraws — the same zero-headroom bug.

Compute the fee BEFORE the MAX amount and reserve against the real adjusted fee:
maxFeeReserveBase now takes the live fee.amount and reserves max(adjustedTxFee,
nativeBankFee) × 2. cosmos/osmosis reserve against their ante fee; thor/maya
(fee.amount '0') reserve against the bank-layer native fee as before — their MAX
amounts are unchanged. 1.0 ATOM now reserves 20000 → amount 980000, leaving a
full fee of headroom.

Adds an ATOM MsgSend MAX test asserting amount + actualFee < balance.
fix(swap): fee headroom on Maya/THOR MAX deposits + revert-detection handoff
…tacked breakdown

Replace the top-N legend list under the portfolio donut with a single
"selected key" that reflects the hovered/selected slice. When the hovered
chain holds more than one token, the key expands into a horizontal stacked
bar (segmented by token) plus chip labels for an at-a-glance breakdown.

- DonutChart: replace ChartLegend with SelectedSlice (single key + optional
  token stack). Bar segments and chips are click-through to each asset.
- Dashboard: extract buildChainTokenItems() — shared by the stacked-bar view
  and the new donut breakdown — and feed the active chain's tokens into the
  selected key.
Replace the dead empty canvas shown after the splash clears but before any
balances arrive (loadingBalances && no usable snapshot, which previously
rendered null) with a branded loader: the same 6-face CSS-3D SpinningDevice
the swap controller uses, wrapped in expanding radar rings, a breathing
gold/teal glow, an OLED "syncing portfolio" scan screen, a gradient
"Loading dashboard" headline, a cycling status line, and an indeterminate
shimmer bar.

- New DashboardLoading component; all motion via scoped CSS keyframes and
  honored prefers-reduced-motion (rings disabled).
- Status text describes the real pipeline (connect / sync balances / fetch
  prices) — no fake counts or USD values; the bar stays indeterminate.
- Wired into the Dashboard hero ternary as the loading branch, ahead of the
  existing empty-wallet welcome state. Pioneer-error path unchanged.
- i18n keys added with English fallbacks so all locales degrade gracefully.
feat(dashboard): single selected key on donut hover + per-token stacked breakdown
feat(dashboard): branded "loading dashboard" state with spinning KeepKey
Adds a "name registration" panel to the THORChain (RUNE) and Maya (CACAO)
asset pages, mirroring the cosmos staking pattern. Registration is a
MsgDeposit with a `~:name:chain:address` memo routed to the chain's name
service; the deposited amount covers the one-time register fee plus N years
of per-block rent.

- shared: NameInfo / NameQuote / BuildNameRegTxParams types; lookupName,
  getNameQuote, buildNameRegistrationTx RPC methods.
- bun: buildCosmosNameRegTx (quote-driven amount, `~:` memo, MsgDeposit) +
  three engine handlers. Name lookup and live cost constants come from
  Pioneer (GetName / GetNameRegistrationQuote); sign + broadcast reuse the
  existing thorchain/mayachain paths unchanged.
- ui: NameRegistrationPanel with debounced availability check, duration
  selector, live cost estimate, and the standard build→sign→broadcast flow;
  gated into AssetPage for thorchain/mayachain. New `names` i18n namespace.

Pioneer reads are documented in docs/handoff-thorname-mayaname-pioneer.md
(implemented in pioneer#95).
…e native fee

Addresses PR review:

- P1: Pioneer GetName / GetNameRegistrationQuote return a `{ data }` envelope
  (like GetStakingPositions). Unwrap `resp?.data?.data ?? resp?.data` in both
  engine handlers and the tx builder — previously quotes failed validation and
  taken names read as found:false.
- P2: expired names are claimable/renewable. Availability now also allows
  found===true when expireBlockHeight <= currentBlockHeight (from the quote),
  with an "expired — available to claim" hint.
- P3: THOR/Maya charge the native tx fee at the bank layer (tx.fee.amount is
  '0'). The builder now reports the real native fee instead of 0, and the panel
  shows registration cost + network fee + total and blocks the build when the
  balance can't cover cost + fee (prevents exact-balance insufficient-funds).
feat(names): THORName / MAYAName registration on the asset page
The app's triangular-grid background is the splash-bg.png set on document.body
in main.tsx. The main app container painted bg="kk.bg" (#0b0b0e, opaque) over
it, so the grid was only visible in the empty/loading state (where the
container is hidden) and vanished once the dashboard loaded. Restore
bg="transparent" on both top-level containers (main + watch-only) so the grid
shows through in every state, matching the intended look.
The name-registration panel is a niche feature but took a full-size
section on every THORChain/Maya asset page. Wrap it in a compact,
collapsed-by-default toggle (small header row with the THORName/MAYAName
label + chevron) so it stays out of the main flow and only mounts the
lazy panel when expanded.
A single contact + own-wallet book that supersedes the per-wallet
collection drilldown as the canonical place to browse and label addresses.

- New top-level "Addresses" nav tab with a full-page view: search, chain
  filter, GitHub-squares identicons, per-address outbound-history drilldown.
- Auto-seeds every connected wallet's addresses (own EVM one row per
  eip155 network for exact-match picking; BTC xpubs; non-EVM addresses)
  from the getBalances derivation — fire-and-forget, passphrase-gated,
  device-switch safe.
- Send screen: address-book picker (exact networkId match) beside the
  recipient input, plus an inline "save this recipient?" prompt after a
  successful broadcast.
- Captures manual outbounds (recipient + amount + asset + from address)
  into a dedicated history table; broadcastTx params extended optionally.
- SQLite: additive `addressbook` + `addressbook_tx` tables (no
  SCHEMA_VERSION bump; added to the deleteDeviceSnapshot cascade).

Outbound capture covers manual SendForm sends; swap/WalletConnect are a
follow-up. Identicon is dependency-free (FNV-1a inline SVG).
…se correctness)

- SendForm: MAX sends now persist a real amount in outbound history
  (token MAX = full token balance; native MAX = balance − fee) instead
  of dropping it.
- deleteAddressBook handler: add the passphrase-wallet guard the other
  handlers use, so a hidden-wallet session can't delete standard-wallet
  address-book rows.
- AddressBookView: collapsed own-EVM rows now retain every member id and
  chain. Chain filtering matches any member chain; edit/delete apply to
  all members; outbound history aggregates across networks and resolves
  each tx's explorer link from its own CAIP.
Blockchair (Pioneer's UTXO tx-data backend) reports block_id:-1 for txs
still in the mempool; Pioneer passes it through as blockchainTxData.blockNumber,
so an unconfirmed BTC swap input rendered as "Block #-1". A real height is
always >= 1 — treat any non-positive value as not-yet-mined.

- swap-tracker.ts: only adopt inboundBlockNumber when > 0.
- SwapDialog.tsx: guard the three state-set sites (live snapshot, push
  update, resume seed) so a -1 already persisted in the local swap DB
  never paints "Block #-1".
- SwapHistoryDialog.tsx: guard the inbound-block row on > 0.
- chains.ts: switch the BTC explorer to mempool.space (away from the
  flaky Blockchair links).
- Also bump getSwapQuote timeouts 30s -> 60s for slow quote paths.

Vault-side defensive fix; the authoritative fix belongs in Pioneer's
UTXO tx-data normalization (see docs/handoff-pioneer-blockchair-mempool-sentinel.md).
fix(swap): don't render Blockchair's block_id:-1 mempool sentinel
BitHighlander and others added 23 commits June 6, 2026 15:15
Own-wallet entries are seeded inside getBalances' device derivation, but
the app serves the cached portfolio on launch and skips getBalances when
the cache is fresh — so the derive (and the seed) never runs and the book
shows "No saved addresses yet" despite a connected wallet.

useAddressBook now triggers one getBalances(forceRefresh) when the book
loads empty (guarded to fire at most once per mount); the seed emits
addressbook-changed, which refetches. Shows a "Loading your wallet
addresses…" state while the derive runs.
…vice

Addresses feedback: the book showed only the connected wallet as a generic
"My wallet" and omitted other (watch-only) devices.

- Cross-device own entries: listAddressBook now seeds own rows for EVERY
  known device from the persisted watch-only balance cache (one primary
  address per chain per device, incl. each EVM network) — cheap, idempotent,
  no device calls. walletId is reconstructed as `${deviceId}:${ethAddr}` so a
  device's cache-seed dedupes against its live getBalances seed.
- Device attribution: entries carry a deviceLabel resolved from
  device_snapshot; own entries are grouped under their device name (replacing
  the generic "My wallet" pill), with a separate "Saved recipients" section
  for external contacts. Header shows address + device counts; search now
  matches device name too.
- own listing is cross-device (no walletId filter); external stays
  wallet-scoped; passphrase sessions still return [] (gated before seeding).

Devices without cached balances simply don't appear until connected/viewed.
…dges

- Identicon is now seeded per DEVICE for own entries (deviceId), so every
  address on a device shares one avatar — easy to tell which device an
  address belongs to. External contacts stay seeded by address.
- Connected state: a green dot on own avatars + a "Connected/Watch-only"
  marker on each device section. "Connected" = device attached & identified
  (any state but disconnected/error, with a deviceId) so it doesn't read
  "Watch-only" during PIN/passphrase entry.
- External entries get a rose "External" warning badge in both the book and
  the Send recipient picker (not gold — avoids clashing with primary-action
  gold). The picker's own entries now show their device name too.

Folds in adversarial-review fixes: broadened connected check, picker
external badge, higher-contrast watch-only, warning-colored external badge.
…full addresses

- Tabbed layout: one tab per device (own wallets, with a connected/watch-only
  dot) + a "Saved recipients" tab for external contacts. Per-tab chain filter
  + search.
- "Add address" button + dialog: pick a network from a searchable dropdown,
  paste an address (validated client-side against that chain via
  validateAddress), and label it. New RPC addAddressBook -> db.addExternalEntry
  (passphrase-gated, scoped to the current wallet, upserts on the dedupe key).
  Adding switches to the Saved recipients tab so the new entry is visible.
- Rows: bigger right-aligned chain logo (AssetIcon 30), larger identicon (36),
  and the FULL address (no middle ellipsis; wraps for long xpubs).

Folds in adversarial-review fixes: switch to External tab after add; warn on
unknown networkId.
…s, polish

- Copy-to-clipboard icon on every row (click-to-copy the full address, with a
  check confirmation; stops row toggle).
- Network filter moved ABOVE the tabs and made global — the selected network
  persists across tab switches and filters every tab. Pills are built from all
  entries (every device + external), not just the active tab.
- "Saved recipients" tab is always shown (even empty) so it's discoverable, with
  a "no recipients yet" prompt pointing at Add Address.
- Polished the Add Address button (outlined gold, clearer label + tooltip).
feat(addressbook): unified top-level Address Book across all wallets
The $100 auto-default converted USD->crypto without the fee/rent reserve
that MAX already applies. When a wallet's balance was just over $100, the
default exceeded the safe-spendable max (balance - 0.01 SOL on Solana),
draining the reserve so the Relay/Jupiter route reverted on-chain after
ATA rent + signature fees ("insufficient lamports").

- Auto-default now flips to MAX when the $100-equivalent meets/exceeds the
  reserve-aware safe max. No-op for reserve-free chains (UTXO/Cosmos/tokens),
  where safeMax == full balance.
- Add an exceedsSafeMax guard so manually-typed and deeplink-seeded amounts
  in the (safeMax, balance] window block the quote with an actionable
  "leave ~X for network fees" warning instead of a silent on-chain revert.

Frontend-only: the backend does not trim standard (non-deposit-channel)
Relay SOL routes, so the clamp must live in the dialog. The 0.01 SOL
reserve itself is unchanged and adequate.
Bring every non-English locale from 61-69% to 100% against the en source.

EN source hygiene (prerequisite, language-agnostic):
- Promote 94 inline-only defaultValue strings into en/*.json (SwapDialog
  tracker/blind-sign/maxReserve, SigningApproval Solana/ETH signing, etc.)
  so they become canonical and translatable. No component changes — the
  inline defaultValue stays as a redundant fallback.
- Add 2 keys that rendered the raw key string (findingBestRoute, custom)
  and exceedsSafeMax (mirrors PR #221).
- Delete orphan zcashCliRequired from 5 locales (already removed from en).

Gap-fill (6567 strings):
- New namespaces: addressbook + names (all 14 langs), swap (de/es/fr/it/
  ja/ko/pt/ru/zh), staking (nl/pl/th/tr/vi).
- In-file drift filled in settings/asset/device/dashboard/nav.

All {{placeholders}} preserved verbatim; brand/protocol names kept canonical
(KeepKey, Solana, THORChain, BIP85, Zcash, tickers). Each file keeps its
original indentation. Validated: every language 100% complete, 0 placeholder
mismatches, 0 orphans, valid JSON, tsc clean.

AI-generated translations — pending native-speaker review before release.
fix(names): collapse THORName/MAYAName panel by default
fix(swap): clamp default & manual amounts to the reserve-aware max
Add swap.sent, swap.gas, settings.restartToUpdate, update.checkFailed and
asset.invalidAddress to their source namespaces so the code's t() calls are
backed by en/<ns>.json (not just inline defaultValue) and get translated
instead of staying English. Added to en + all 14 locales to keep coverage
at 100%.
i18n: complete translation coverage for all 14 languages (61-69% → 100%)
…mall)

PR #192 divided CACAO swap outputs by 100 because Pioneer was then
normalising CACAO with RUNE's 8 decimals (10^(10-8)=100x too large).
Pioneer has since corrected this to CACAO's native 10 decimals, so the
workaround now makes outputs 100x too SMALL.

Symptom: an ETH->CACAO quote of $36.98 in showed 3.2616 CACAO ($0.33) out
with rate "1 ETH = 138.38 CACAO", tripping the dust-fee warning. CACAO is
~$0.10, so the correct output is ~326 CACAO (~$33) and the correct rate
~13,838 CACAO/ETH. Pioneer's raw value was right; the /100 broke it.

Removes the rescale in both the quote parser (expectedOutput + amountOutMin)
and the swap tracker (receivedOutput), trusting Pioneer's value directly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(maya): CACAO swap quotes 100x too small (obsolete rescale)
Bundled v7.14.1 firmware (hash 73a8806...) was only on the beta channel.
Promote it to latest so connected devices on older firmware are offered the
update. Binary already bundled; header-stripped hash verified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7.14.1 is now the latest channel and is the firmware devices on older
versions upgrade to. The old 'Stability and TON improvements' headline
undersold it — the upgrade delivers the full Solana/Tron/TON chain set.
Feature cards already aggregate all three via getUpgradeFeatures.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The aggregated 'Others' tile has no asset icon, so the <Image src=''>
rendered as a plain black circle. Render a 2x2 dot grid glyph themed to
the tile color for the __others__ tiles; real assets keep their image.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The OOB wizard already rendered a 'Skip for now' button, but skipping was a
dead end: handleSkipFirmware routed to onComplete (wizardComplete=true) while
the device state stays 'needs_firmware' on the old firmware. App.tsx's phase
logic only allows 'ready' when state === 'ready', so the app fell through to
'splash' — the user could never reach the dashboard.

Add a session-only firmwareSkipped flag:
- handleSkipFirmware on an already-initialized device now calls a new
  onSkipFirmware prop instead of the setup-complete path.
- App routes needs_firmware -> ready while firmwareSkipped is set, so the
  user gets into the app on the older firmware. TopNav still shows the
  'vX -> vY' update reminder.
- The flag is never persisted: it resets when the device disconnects, so the
  update is re-offered on the next connect (initialized devices only; an
  uninitialized device still routes to init-choose).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rename the 'Apps' nav tab to 'Explore' (TopNav NavTab, App route, nav.json
across all 14 languages).

Add six tiles to the Explore grid: Browser Extension, Support, Docs,
Affiliates (local SVG icons in assets/apps/), plus CoinCap and Venice
(remote icons). Point the KeepKey tile at keepkey.com and drive the heading
from i18n.

appstore.json gains heading + name/description keys for the new apps,
translated across all 14 languages.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@BitHighlander BitHighlander requested a review from pastaghost as a code owner June 7, 2026 06:32
@BitHighlander BitHighlander merged commit 667be58 into master Jun 7, 2026
7 checks passed
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