Skip to content

Release v1.2.15 → master#109

Merged
BitHighlander merged 70 commits into
masterfrom
develop
Apr 10, 2026
Merged

Release v1.2.15 → master#109
BitHighlander merged 70 commits into
masterfrom
develop

Conversation

@BitHighlander
Copy link
Copy Markdown
Collaborator

Summary

Merge develop into master for v1.2.15 release.

Changes since last master merge

Release: https://github.com/keepkey/keepkey-vault/releases/tag/v1.2.15

BitHighlander and others added 30 commits April 3, 2026 23:18
Red gas pump icon appears on Dashboard chain cards when native
balance < $1 USD but tokens on that chain exceed $1 — alerting
users they need gas to move their tokens.

SendForm gas hint also turns red with icon when gas is low.
Pioneer's ListUnspent expects the CAIP-2 networkId (e.g.
bip122:000000000000000000651ef99cb9fcbe for BCH), not the short chain
symbol ("BCH"). This caused "No UTXOs found" for all non-BTC UTXO
chains despite balances displaying correctly.
fix: pass networkId to Pioneer ListUnspent for BCH UTXOs
feat: low-gas warning on EVM chain cards
LTC/DOGE/BCH/DASH/DGB were deriving a single xpub and finding 0 UTXOs
when funds sat on a different script type. Now mirrors BTC's multi-xpub
pattern: derive all applicable xpubs (LTC gets p2pkh + p2sh-p2wpkh +
p2wpkh; others get p2pkh) and pass allXpubs to buildUtxoTx for
aggregation.
Pioneer API returns "relay" integration quotes (e.g. AVAX→ETH) with
pre-built EVM transactions containing calldata — no THORChain-style
memo or inbound vault needed. Previously these swaps failed with
"Missing swap memo from quote" because the code assumed all swaps
use memo-based routing.

- Add RelayTxParams type for pre-built bridge transactions
- Extract relay tx params (to, data, value, gas) in parseQuoteResponse
- Skip memo/inboundAddress validation for relay integration
- Add buildRelaySwapTx() that signs pre-built tx with fresh nonce
- Pass integration + relayTx through frontend to executeSwap RPC
The let declaration was after the else block that assigns to it,
causing a TDZ (temporal dead zone) error. Move it up next to the
other buildTx locals (fromAddress, xpub).
…fallback

1. Guard relay chainId matches fromChain.chainId — prevents signing a tx
   for the wrong chain when a stale/mismatched quote is used.

2. Add Pioneer gas price fallback when RPC URL is absent, mirroring the
   normal EVM swap path (RPC → Pioneer → chain-specific gwei floor).
   Previously a relay tx could be built with no gas params at all.
getBalances and getBalance were still using a single xpub per
non-BTC UTXO chain, so LTC funds on p2pkh or p2sh-p2wpkh addresses
would show as 0 in the UI. Now derives all applicable xpubs in
both balance paths, matching the buildTx aggregation fix.
fix: support relay bridge integration for cross-chain swaps
fix: aggregate UTXOs from all xpubs for non-BTC UTXO chains
Dashboard: add "LOW GAS" text label under the red gas pump icon.
SendForm: replace inline hint with a full-width red warning banner
showing icon, bold title, explanation text, and current gas balance.
These tax export formats produce empty reports — hide from UI until
report data is populated. Backend code retained for future use.
…ubmodule pin

Pin firmware submodule to BitHighlander/keepkey-firmware release/7.14.0.
Add 3 selectable emulator channels:
- alpha: BitHighlander fork release/7.14.0 (latest dev)
- beta: BitHighlander fork release/7.14.0 (pre-release)
- release: upstream keepkey/keepkey-firmware master (stable)

Changes:
- manifest.json defines 3 channels with source repo/branch metadata
- emulator.ts supports channel-based dylib selection
- RPC schema adds emulatorGetChannels + channel param on emulatorInit
- EmulatorManager UI shows channel picker (alpha/beta/release buttons)
- Makefile adds build-emulator-{alpha,beta,release} targets
- download-emulators.ts script for fetching pre-built binaries
- docs/EMULATOR-CHANNELS.md SOP for release verification
When pairRawDevice times out on cold start, the retry loop spun forever
because the WebUSB device stayed in opened state. Three fixes:

1. Close webUsbDevice on pair failure — resets opened flag so retries
   get a fresh connection instead of "already-connected" forever.

2. Cap retries at 24 (~2 min) then transition to error state with a
   clear message. retryCount resets on successful pairing.

3. Add retryConnect RPC + UI: clicking the KeepKey logo on the splash
   screen calls retryConnect (clears stale state, resets counter, re-syncs).
   After 10s of searching, "Tap to retry" hint fades in under the logo.
1. Don't wire onLogoClick during needs_pin or needs_passphrase states —
   retryConnect would blow away a legitimate unlock flow.

2. Only arm the "Tap to retry" timer when onLogoClick is provided —
   prevents dead affordance on claimed-state splash screen.
1. Channel not propagated through import/switch/restore:
   - Added sticky `selectedChannel` that persists across stop/start cycles
   - initEmulator() without explicit channel reuses the user's last selection
   - Import, switch, and rollback flows now honor the selected channel

2. Beta is now a real pinned channel:
   - Beta builds from BETA_PIN_SHA (specific commit), not branch tip
   - Alpha tracks branch tip (moves), beta doesn't (manually promoted)
   - Removed unused `autoUpdate` and top-level `channels` map from manifest
   - manifest.json source entries now have { ref, type: "branch"|"commit" }

3. Download script verifies artifacts match declared source:
   - Resolves declared ref to exact SHA before searching artifacts
   - CI artifacts filtered by workflow_run.head_sha matching target SHA
   - Release assets verified against target_commitish
   - .build-sha recorded in channel dir for traceability
   - Status command shows which SHA each channel was built from
… release builds

1. Import flow now honors channel selection on first use:
   - Added channel param to emulatorImportWallet and emulatorSwitchWallet RPC schemas
   - UI passes selectedChannel on import, start, and switch calls
   - Backend forwards channel to initEmulator in all three paths
   - Rollback path relies on sticky selectedChannel (already set by the explicit call)

2. Release-channel build works on fresh clones:
   - _build-emu now ensures the keepkey remote exists before fetching
   - .gitmodules configures origin as BitHighlander fork, so keepkey/master
     would fail without this. The target adds the remote idempotently.
fix: USB pairing retry cap + manual retry via logo tap
feat: emulator channel selection (alpha/beta/release)
BIP-85 deterministic entropy is pushed back to the 7.15.0 firmware
release. Updates all version gates and moves the BIP-85 changelog
entry from 7.14.0 to a new 7.15.0 release section.

Files changed:
- firmware-versions.ts: move BIP-85 feature to 7.15.0 release entry
- index.ts: setBip85Enabled gate + auto-disable check → 7.15.0
- Dashboard.tsx: BIP-85 icon visibility gate → 7.15.0
- DeviceSettingsDrawer.tsx: toggle gate + help text → 7.15.0
DeviceGrid was calling emulatorSwitchWallet without a channel parameter,
causing initEmulator to fail with "No emulator dylib found for version
default". The EmulatorManager had a proper channel picker but DeviceGrid
bypassed it entirely.

Now clicking Start on any emulator wallet (or Add Emulator) shows an
inline channel picker (alpha/beta/release) before starting. Only
installed channels are shown. Same pattern as the delete confirmation
already used in the grid.
- Extract shared ChannelPicker into a local component used by both
  existing wallet cards and the "Add Emulator" card
- Move channel colors to a module-level CHANNEL_COLORS record
fix: emulator start requires firmware channel selection
getEmulatorsDir() used __dirname which doesn't resolve correctly in
Bun's bundled backend. Now tries multiple candidate paths (import.meta.dir
at various depths, cwd-relative) and picks the first one containing
manifest.json — same multi-fallback pattern as zcash-sidecar.
bundle-backend.ts marks @walletconnect/* as external (ESM/CJS dual-package
resolution breaks in Bun bundler), so require() happens at runtime. But
collect-externals.ts didn't include them, so the packages never made it
into the app bundle — runtime require('@walletconnect/core') failed,
crashing the backend before the logger initialized (black window).

This is why v1.2.11 had a black window while dev builds worked.
Adds an opt-in toggle that routes firmware/bootloader updates through
the manifest's `beta` channel (already present in releases.json but
previously unused). Separate from the existing preReleaseUpdates toggle
which controls vault app updates — firmware has a different blast
radius and deserves its own opt-in.

- shared/types.ts: AppSettings.alphaFirmware
- shared/rpc-schema.ts: setAlphaFirmware RPC method
- bun/index.ts: DB-persisted (alpha_firmware key), wired to engine at
  startup and on toggle; syncState() re-derives needs_firmware_update
  against the new channel target
- bun/engine-controller.ts: getChannelEntry() picks manifest.beta when
  opt-in (falls back to latest if beta missing); applyChannel()
  recomputes latestFirmware/latestBootloader; update URLs + hash
  verification now use the active channel
- mainview/DeviceSettingsDrawer.tsx: toggle row under pre-release
  updates with distinct pink (#EC4899) accent and 🚧 icon

Alpha testers flip the toggle → vault sees manifest.beta.firmware →
needs_firmware_update lights up → flashes through the existing update
flow with beta binary + hash verification.
BitHighlander and others added 28 commits April 4, 2026 22:18
1. Reconnect with pre-cached passphrase: if device reaches ready with
   passphraseProtection+passphraseCached but sendPassphrase() was not
   called this session, conservatively assume hidden wallet. Prevents
   disk leaks on app restart / reconnect to already-unlocked device.

2. hiddenWalletActive now set AFTER await sendPassphrase() succeeds —
   if user rejects on-device, the flag stays false (no misclassification).

3. passphraseSetThisSession flag distinguishes "we entered a passphrase"
   from "device had one pre-cached". Both cleared on disconnect.

4. isHiddenWallet exposed in DeviceStateInfo and getDeviceState() so
   frontend can react: TopNav badge only shows for actual hidden wallets,
   Reports button hidden when isHiddenWallet (backend already throws).

5. scanChainHistory error already displays inline in ActivityPanel via
   catch → setScanResult(e.message) — no additional UI change needed.
1. Reconnect hidden-wallet detection moved BEFORE this.emit('state-change')
   so getDeviceState().isHiddenWallet is accurate on the first emit.
   Frontend no longer gets stale isHiddenWallet: false on reconnect.

2. probeReconnectPassphraseState() resolves the false-positive for
   standard-wallet reconnects: derives ETH address and compares against
   stored seed_eth_{deviceId}. If it matches, reclassifies as standard
   wallet and re-emits state-change. No DB writes, no seed-changed emit.

   - Match → standard wallet (safe to cache, snapshot saved)
   - Mismatch → confirmed hidden wallet (stays conservative)
   - No stored identity → stays conservative (hidden wallet)
1. Session guard: probeReconnectPassphraseState() captures wallet ref +
   deviceId before the async ethGetAddress call and verifies both still
   match afterward. If the session changed (disconnect/reconnect/new
   passphrase flow), the probe result is discarded — no stale mutations.

2. Missing seed identity no longer stays conservative. When no
   seed_eth_{deviceId} is stored (fresh profile, DB reset, first run),
   the probe derives the ETH address, stores it as the seed identity,
   and reclassifies as standard wallet. This is safe because:
   - First-time devices go through sendPassphrase() (sets flags correctly)
   - Reconnect with cached empty passphrase = standard wallet by definition
   - If it were actually a hidden wallet, there WOULD be a stored identity
     from the original standard-wallet session that preceded it
…let leak

Reverts the "no stored = standard wallet" assumption. A missing
seed_eth_{deviceId} could mean first-time Vault launch with a device
already unlocked into a hidden wallet from another app. Writing the
hidden wallet's ETH address as canonical identity would both leak data
to disk and poison future standard/hidden classification.

Now: no stored identity = stay conservative (hidden wallet). Self-heals
on next normal connect cycle when sendPassphrase() sets flags correctly
and checkSeedIdentity() stores the standard wallet's ETH address.
…ase wallets

Three DB write paths leaked hidden wallet activity to disk:

1. insertApiLog() — 4 callsites: REST callback, broadcastTx, executeSwap,
   chain history scan. Now guarded with isPassphraseWallet check. UI still
   receives live log entries via RPC, just not persisted.

2. saveBip85Seed() — 2 callsites: getBip85Mnemonic (auto-save with label)
   and saveBip85SeedMeta (explicit save). First silently skips, second throws
   with a privacy explanation since it's a user-facing action.

3. insertSwapHistory() / updateSwapHistoryStatus() — swap-tracker.ts has no
   direct access to engine, so added { skipPersist } option to trackSwap().
   A noPersistSwaps Set tracks which txids should skip DB writes in the
   polling loop. Swaps still track in-memory for UI status updates.
…wallet

When a device reconnects with passphraseCached=true but no seed_eth_*
identity in the DB (fresh install or DB reset), the probe previously
returned early and stayed in hidden-wallet mode indefinitely. This
disabled caching, reports, chain history, and showed hidden-wallet UI
for ordinary empty-passphrase standard wallets.

Fix: the probe now always derives the ETH address. When no stored
identity exists, it bootstraps — stores the current address as the
identity and reclassifies as standard wallet. If this is actually a
hidden wallet (edge case: first Vault launch with device already in a
hidden wallet from another app), the next proper connect via
sendPassphrase("") will run checkSeedIdentity, detect the mismatch,
and emit 'seed-changed' to correct the stored identity.
The bootstrap approach (store address + reclassify as standard on fresh DB)
broke plausible deniability. A user under duress reconnecting with an
empty passphrase needs the app to prove the device is empty — any trace
written during an ambiguous session creates evidence.

Reverted to conservative behavior: no stored identity = stay in hidden
wallet mode, write nothing to disk. Standard wallet users on a fresh DB
must re-enter their passphrase through Vault once to establish identity.
This is a UX cost, not a privacy cost — and the right tradeoff for a
hardware wallet with passphrase protection.
Read-side privacy gap: hidden wallet sessions could display stale
standard-wallet data from the DB (cached balances, activity logs,
swap history, reports, BIP-85 seeds). Now all read RPCs return empty
results when isPassphraseWallet is true:

- getCachedBalances → null
- getApiLogs → []
- getRecentActivity → []
- listReports → []
- getSwapByTxid → null
- getSwapHistory → []
- getSwapHistoryStats → zeroed
- exportSwapReport → throws
- listBip85Seeds → []

Dashboard shows a purple "passphrase wallet active" banner explaining
that reports, history, and caching are disabled for privacy, with
instructions to lock device and re-enter passphrase to restore features.
…ions; drop banner

Remaining read-side gaps:
- getReport / saveReportFile: report contents were accessible by ID even
  though listReports was blanked. Now return null / throw.
- deleteReport: guarded to prevent modifying standard-wallet reports.
- getPendingSwaps: rehydrated from SQLite, leaked standard-wallet pending
  swaps into hidden sessions. Now returns [].

Removed the privacy banner — users who enabled passphrase protection in
device settings understand the security model.
feat: passphrase wallet cache exemption
v1.2.11 Intel notarization failed because the x64 core downloaded from
BitHighlander/electrobun tag v1.16.1-keepkey.1 pointed to commit 6d36b62f
(GPU/WGPU contaminated) and its libNativeWrapper.dylib was "code object
is not signed at all" — sign-release-intel couldn't re-sign it for
notarization.

Changes:
1. build-electrobun-x64-core.sh now adhoc-signs all x64 binaries after
   cross-compile, matching what the linker auto-does on arm64. Without
   adhoc signatures, subsequent Developer-ID signing fails.
2. CI build.yml FORK_TAG bumped to v1.16.1-keepkey.2 — new release
   built from commit 4f3d422a (safe, pre-GPU) with adhoc-signed binaries.
fix: rebuild electrobun x64 core from safe commit
i18next is configured with 14 namespaces (incl. swap, staking), but
non-English locales are each missing one of them — 9 locales lack
swap.json, 5 lack staking.json. On changeLanguage('es'), Vite's dynamic
import helper throws for the missing file, rejecting the
resourcesToBackend callback, which aborts the whole language switch.
UI silently stays in English.

Catch the import rejection and return an empty resource. i18next then
falls back to English for just that namespace (via fallbackLng) and
successfully switches language for everything else.
Previous fix swallowed every dynamic-import failure, which would hide
real regressions (chunk load failures, malformed JSON, bad paths) as
silent English fallbacks — much harder to detect in production.

Match only Vite's specific "Unknown variable dynamic import" error
(thrown when the path isn't in the generated import map, i.e. the
translation file genuinely doesn't exist), warn once, and return an
empty namespace. Rethrow everything else so real failures surface.
…builds

Problem (raised during v1.2.12 retro):
- arm64 is built + signed locally via `make build-signed`
- Windows is built externally (no workflow for it here)
- Only Intel x64 + Linux genuinely need CI
- CI was uploading unsigned arm64 DMG/tar.zst + unsigned x86_64 DMG
  to the draft release. Any CI re-run (e.g. transient hdiutil failures)
  would clobber the locally-signed versions with unsigned ones.

Changes:
1. Remove "Create unsigned DMG (macOS)" step — don't make an arm64 DMG
2. Remove "Create unsigned x86_64 DMG (macOS)" step — sign-release-intel
   builds the DMG from the signed tar.zst, an unsigned one just risks
   clobbering
3. New "Remove arm64 macOS artifacts" step after x64 variant is created —
   deletes all arm64 outputs (DMG, tar.zst, update.json, patches) so
   they never reach the release page

CI still uploads:
- stable-macos-x64-keepkey-vault.app.tar.zst (sign-release-intel consumes it)
- Linux tar.zst + AppImage
- SHA256SUMS.txt

Never uploaded from CI:
- KeepKey-Vault-X.Y.Z-arm64.dmg (local via make build-signed)
- KeepKey-Vault-X.Y.Z-x86_64.dmg (local via make sign-release-intel)
- stable-macos-arm64-* anything
ci: don't upload arm64 or unsigned x64 DMG — avoid clobbering signed builds
fix: language switch silently fails when namespace file missing
Switch from BitHighlander/electrobun fork to upstream blackboardsh/electrobun.
Target macOS 13.0+ (Ventura) instead of 12.0 (Monterey).

What changed:
- .gitmodules: submodule URL → blackboardsh/electrobun, branch → main
- Submodule pinned to upstream v1.16.0
- build-electrobun-x64-core.sh: target macOS 13.0, Bun 1.3.9 (was 1.1.20)
- CI workflow: download x64 core from keepkey/keepkey-vault releases
  (tag: electrobun-x64-core-v1) instead of BitHighlander/electrobun fork

Why:
- The fork existed solely for resign-swizzle crash fix on macOS 12 Intel
- macOS 13+ doesn't have the resign-swizzle issue
- Eliminates: fork maintenance, GPU contamination risk, tag drift,
  fork-specific build.ts patches
- x64 core still cross-compiled locally and published to our own releases
Finding #1 (High): CI downloads a hardcoded x64 core tag that can drift
from the submodule. Added a CI gate that compares the submodule version
against the release notes of the downloaded x64 core tag. Emits a
::warning if they don't match. Not a hard fail (to avoid blocking
unrelated PRs) but visible in CI annotations.

Finding #2 (Medium): Makefile publish-electrobun-x64-core still pointed
at BitHighlander/electrobun. Updated to keepkey/keepkey-vault with new
tag format (electrobun-x64-core-vN). Build script "Next steps" text
also updated. All stale fork references removed.
feat: drop electrobun fork, use upstream + macOS 13+
Captures the diagnostic work from a 1.2.14 Windows install session for
the next agent to act on. Three distinct failure modes, all surfacing
to the user as the same "splash never advances" symptom but caused by
unrelated bugs in different layers.

1. `Invalid Version: vundefined.undefined.undefined` on WebUSB pair —
   defensive fix in flight at keepkey/hdwallet#37 (with regression
   tests). Bumping the submodule pointer here is a follow-up after
   that PR merges.

2. Native crash on USB unplug during in-flight WebUSB write — bun
   process disappears with no JS error after libusb's `bad write`.
   Same family as the existing macOS attach-time SIGTRAP guard in
   engine-controller.ts:251-254. Open; suggested approach in the doc
   ranges from a process-supervisor band-aid to moving USB I/O into a
   child process.

3. Splash hangs forever when port 1646 is already bound — the FATAL
   exit message is in the backend log but the BrowserWindow is created
   before the port collision check, leaving an undecorated splash on
   screen. Open; fix is to move the collision probe before window
   creation and/or surface a "Vault is already running" UI state.

Doc includes log evidence for each finding, root-cause file pointers,
reproduction recipes, and an explicit "what is NOT broken" section to
prevent the next agent from re-investigating the verified-intact
download, the 1.2.14 @noble/hashes fix, the protobuf schema, and the
frontend bundle.
docs: handoff for v1.2.14 Windows pair failures (3 findings)
…ation

THE BUG
=======

projects/keepkey-vault/src/bun/index.ts:4246 unconditionally spawned
`bash -c '<heartbeat watchdog script>'` at module load. On Win10, when
the app is launched from Explorer / Start Menu / installer Run-now,
the bun worker inherits an empty PATH from the parent process.
Bun.spawn delegates to libuv, which fails with UV_ENOENT (-4058) when
it can't locate `bash`, and the failure is delivered as an UNCAUGHT
asynchronous exception in the worker thread several seconds after the
spawn call site. The exception kills the entire app right around the
device pair flow, and the user sees a splash that hangs and then
disappears.

The watchdog is POSIX-only — its script uses bash, kill -9, date +%s,
sleep, cat — and could never have functioned on Windows even if `bash`
were on PATH. It exists to defend against an FFI freeze in
kkemu confirm_helper that only happens on macOS/Linux builds.

This was the dominant Win10 1.2.14 first-launch crash. Reproduced
twice across two different rebuilt 1.2.14 binaries (4f8ec1ba… and
2111ad61…), and verified fixed in-place by patching the installed
bundle and re-launching from the desktop icon.

THE FIX
=======

1. startHeartbeatWatchdog() returns early on process.platform === 'win32'
   with a [Vault] Heartbeat watchdog skipped on Windows log line.
2. The Bun.spawn(['bash', ...]) call is wrapped in try/catch as
   defense-in-depth on POSIX hosts where bash could conceivably be
   missing (containers, minimal NixOS, etc).
3. A long comment block above the function explains the platform
   constraint and references this incident, so the next person to
   touch the watchdog doesn't accidentally re-enable it on Windows.

OBSERVABILITY (the only reason this was diagnosable)
====================================================

The previous logger used fs.createWriteStream(LOG_FILE, {flags:'a'})
whose buffered .write() calls never reached disk on a worker-thread
crash. Every failed launch left a vault-backend.log that "ended" 2-7
seconds before the actual death point, and we spent two days chasing
wrong root causes (libusb segfault, semver throw, port collision)
because the actual exception line never made it to disk.

This commit replaces the buffered stream with fs.appendFileSync per
log call. Throughput hit is negligible at our log volume (~10-100
lines/sec at peak boot); the upside is that the log file is now a
faithful record of what executed up to a crash.

It also adds a structured boot environment dump immediately after
[Boot] Log file: …, capturing platform, arch, pid, ppid, cwd, argv,
stdio TTY status, PATH.length, LANG, LC_ALL, and Windows-only env
vars (USERNAME, SESSIONNAME, APPDATA, LOCALAPPDATA). The PATH.length
field is what surfaced this bug — Explorer launches show
PATH.length=0, terminal launches show PATH.length=882. Without that
single field, the only evidence was "splash hangs" which is
consistent with twenty different root causes.

Finally, engine-controller.ts gets boundary log lines around every
JS↔native transition in start() and fetchFirmwareManifest() —
usb.on(attach), usb.on(detach), usb.getDeviceList(), mergeManifests,
applyChannel, syncState — each wrapped in try/catch with an explicit
[Engine] FATAL: log so a future libusb segfault leaves a clear
breadcrumb instead of a silent process exit.

WHAT THIS DOES NOT FIX
======================

The three findings from docs/HANDOFF-1.2.14-WINDOWS-PAIR.md remain:

- Finding 1 (Invalid Version: vundefined.undefined.undefined) is
  addressed by keepkey/hdwallet#37 which still needs review/merge,
  followed by a submodule pointer bump here.
- Finding 2 (native crash on USB unplug during pairRawDevice) is
  separate from this PR's bug. With the sync logger landing, the
  next reproduction should leave the actual death cause in the log.
- Finding 3 (port-1646 collision splash hang) is already fixed in
  the official 1.2.14 rebuild from 2026-04-09 — verified working.

See docs/HANDOFF-1.2.14-WIN10-WATCHDOG-CRASH.md for the full
diagnosis story, including the verification recipe.
fix(win10): skip POSIX heartbeat watchdog on Windows + sync logging foundation
Bump version to 1.2.15 and pin hdwallet submodule to master (includes
keepkey/hdwallet#37 — Features version validation).

Changes since v1.2.14 pre-release:
- fix(win10): skip POSIX heartbeat watchdog on Windows (#107)
- fix: sync file logger — crash logs now survive native exceptions (#107)
- fix: boot environment dump for launch-context diagnostics (#107)
- fix: JS↔native boundary logging in engine-controller (#107)
- fix: port 1646 collision check before window creation (#106)
- fix: USB detach race guard on WebUSB pair (#106)
- fix: hdwallet Features version validation (hdwallet#37)
- docs: handoff for 1.2.14 Windows pair failures (#105)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
electrobun.config.ts had a hardcoded version (1.2.11) that drifted from
package.json. Import pkg.version so they can never diverge — the signed
artifact, installer name, and updater version all read from one source.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Release v1.2.15 — Windows 10 fixes + observability
Tab buttons (Apps, ShapeShift, Settings) were inside the
electrobun-webkit-app-region-drag zone without the no-drag
opt-out class, so clicks were swallowed by the window drag
handler on Linux and macOS. Blockchain items worked because
they're in the Dashboard, outside the drag region.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@BitHighlander BitHighlander merged commit afafa53 into master Apr 10, 2026
5 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