aboutsummaryrefslogtreecommitdiffstats
path: root/src/app.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-06-24 20:13:25 -0700
committerAdam Soltys <[email protected]>2026-06-24 20:13:25 -0700
commit3db3ba8e134363a3f11081f99a23950f1e6af9e2 (patch)
tree7d8a1f5df1cd1e9c009f2358d2a0e781f4fd511a /src/app.js
parent9e2737e6736f9dd582d083c6d6fd669a28a99316 (diff)
downloadcoinosv3-3db3ba8e134363a3f11081f99a23950f1e6af9e2.tar.gz
coinosv3-3db3ba8e134363a3f11081f99a23950f1e6af9e2.zip
Fix dark-mode contrast and stop install-link jitter
- warn-box, addr-box, summary, toast and green badge/notice borders used hardcoded light colors that washed out or vanished in dark mode; route them through theme variables (new --toast-bg/-fg and --green-line pairs). - Drive the footer "Install app" link from a persisted localStorage flag instead of the live beforeinstallprompt event so it's present on first paint after a refresh, eliminating the layout shift. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Diffstat (limited to 'src/app.js')
-rw-r--r--src/app.js37
1 files changed, 31 insertions, 6 deletions
diff --git a/src/app.js b/src/app.js
index 76474f5..8ec3b71 100644
--- a/src/app.js
+++ b/src/app.js
@@ -119,8 +119,10 @@ function footer() {
{ style: 'margin-top:4px' },
h('button', { class: 'linklike', style: 'font-weight:400', onClick: openHowItWorks }, t('howItWorks')),
// Chrome no longer prompts to install on its own โ€” surface our own link
- // once it reports the app is installable (beforeinstallprompt fired).
- installPrompt
+ // once it reports the app is installable. We render from a persisted flag
+ // (not the live event) so the link is present on the first paint after a
+ // refresh, avoiding a layout shift when beforeinstallprompt fires late.
+ installable()
? h('span', {}, h('span', { class: 'faint' }, ' ยท '),
h('button', { class: 'linklike', style: 'font-weight:400', onClick: triggerInstall }, t('installApp')))
: null,
@@ -151,25 +153,48 @@ function toggleTheme() {
// PWA install. Chrome fires beforeinstallprompt when the app qualifies; we stash
// the event and reveal an "Install app" link, then replay it on a user tap (the
-// browser requires a gesture). beforeinstallprompt only fires when not already
-// installed, so the link naturally hides once installed.
+// browser requires a gesture). The link's visibility is driven by a persisted
+// flag rather than the live event so it's present on the first paint after a
+// refresh (no layout shift); the event only supplies the prompt to replay.
+const INSTALLABLE_KEY = 'btc-wallet-installable';
let installPrompt = null;
+function isStandalone() {
+ try {
+ return matchMedia('(display-mode: standalone)').matches || navigator.standalone === true;
+ } catch { return false; }
+}
+function installable() {
+ if (isStandalone()) return false;
+ try { return localStorage.getItem(INSTALLABLE_KEY) === '1'; } catch { return false; }
+}
+function setInstallable(v) {
+ try {
+ if (v) localStorage.setItem(INSTALLABLE_KEY, '1');
+ else localStorage.removeItem(INSTALLABLE_KEY);
+ } catch {}
+}
if (typeof window !== 'undefined') {
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
installPrompt = e;
- render();
+ // Only re-render if this changes what's on screen; on a refresh the link is
+ // already shown from the persisted flag, so nothing moves.
+ const wasShown = installable();
+ setInstallable(true);
+ if (!wasShown) render();
});
window.addEventListener('appinstalled', () => {
installPrompt = null;
+ setInstallable(false);
render();
});
}
async function triggerInstall() {
const e = installPrompt;
+ // The prompt event may not have fired yet this load even though the link is
+ // shown from the persisted flag; bail quietly if so.
if (!e) return;
installPrompt = null;
- render();
e.prompt();
try {
await e.userChoice;