diff options
| author | Adam Soltys <[email protected]> | 2026-06-27 19:12:29 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-06-27 19:12:29 -0700 |
| commit | 0a17635c93b1e7ec1b2937ece25000c518881f64 (patch) | |
| tree | e4a598bc28f8cb02e8b9ea23fd03887e3f0b04ac /src | |
| parent | 803ccd12cded3f09086c715fffbc918a01575e09 (diff) | |
| download | coinosv3-0a17635c93b1e7ec1b2937ece25000c518881f64.tar.gz coinosv3-0a17635c93b1e7ec1b2937ece25000c518881f64.zip | |
Browser history / back-button navigation
Mirror the app's screen position (screen, tab, txDetail, bump, giftsAll, giftMode,
claimStep) into the browser history, so Back/Forward (and Android system back)
move between screens actually viewed. render() snapshots just these nav fields and
pushState()s on change — incidental re-renders (typing, polling) don't add
entries. popstate restores the snapshot and re-renders. History-API calls are
guarded so they can never break a render.
Verified via CDP: tab nav and tx-detail push entries; Back/Forward (incl. A→B→A)
restore the right screen and the DOM follows.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Diffstat (limited to 'src')
| -rw-r--r-- | src/app.js | 50 |
1 files changed, 50 insertions, 0 deletions
@@ -249,9 +249,59 @@ function render() { // Remember where we are so a refresh restores it (only meaningful on the wallet). try { sessionStorage.setItem(NAV_KEY, JSON.stringify({ tab: ui.tab, txDetail: ui.txDetail })); } catch {} } + syncHistory(); // mirror the current screen into browser history (Back/Forward) } wallet.subscribe(render); +// ---- browser-history navigation ------------------------------------------ +// Mirror the app's screen position into the browser history so the Back/Forward +// buttons (and Android/system back) move between screens we've actually viewed. +// We snapshot only the navigation-relevant `ui` fields, so incidental re-renders +// (typing, polling, balance updates) don't create history entries. +const NAV_FIELDS = ['screen', 'tab', 'txDetail', 'bump', 'giftsAll', 'giftMode', 'claimStep']; +function navSnapshot() { + const s = {}; + for (const f of NAV_FIELDS) s[f] = ui[f] ?? null; + return s; +} +const navSig = (s) => JSON.stringify(s); +let navStack = []; // in-memory mirror of the history entries (to detect an in-app Back) +let navIndex = -1; +let restoringHistory = false; // true while applying a popstate (suppresses pushing) + +function syncHistory() { + if (restoringHistory) return; + try { + const snap = navSnapshot(); + const sig = navSig(snap); + if (navIndex >= 0 && sig === navSig(navStack[navIndex])) return; // no navigation change + // Every screen change is a new history entry. (We deliberately don't try to + // detect in-app "back" navigations — an A→B→A pattern is indistinguishable + // from a genuine back, so guessing corrupts the stack. An in-app back just + // adds an entry; Back/Forward still walk the screens correctly.) + navStack = navStack.slice(0, navIndex + 1); // drop any forward entries + navStack.push(snap); + navIndex++; + const entry = { nav: snap, i: navIndex }; + if (navIndex === 0) history.replaceState(entry, ''); + else history.pushState(entry, ''); + } catch {} // history API failures must never break a render +} + +window.addEventListener('popstate', (e) => { + const st = e.state; + const snap = (st && st.nav) || navSnapshot(); + restoringHistory = true; + try { + for (const f of NAV_FIELDS) ui[f] = f in snap ? snap[f] : null; + if (st && typeof st.i === 'number') navIndex = st.i; + else { const i = navStack.findIndex((s) => navSig(s) === navSig(snap)); if (i >= 0) navIndex = i; } + render(); + } finally { + restoringHistory = false; + } +}); + // ---------------------------------------------------------------- utilities let toastTimer; function toast(msg) { |
