From 65064e2bb0eefb90c3585bf49fe7a5c038da6454 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Sun, 16 Aug 2026 17:00:28 -0700 Subject: Staging wears its name on the logo and deals in play money: HAL_TARGET=staging stamps the brand, renames the PWA, and starts fresh wallets on Mutinynet — one master, no diverging branch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01JtaatFfAbx91Nh3xqKVqKz --- build.js | 28 +++++++++++++++++++++++----- src/api.js | 6 +++++- src/app.js | 4 +++- src/build-flags.js | 9 +++++++++ src/style.css | 10 ++++++++++ 5 files changed, 50 insertions(+), 7 deletions(-) create mode 100644 src/build-flags.js diff --git a/build.js b/build.js index ea8540b..8abf5fb 100644 --- a/build.js +++ b/build.js @@ -225,12 +225,26 @@ const featurePlugin = (enabled) => ({ }, }); -export async function buildHtml({ minify = true, pwa = minify, features = process.env.HAL_FEATURES } = {}) { +// HAL_TARGET=staging flips src/build-flags.js in the bundle: mutinynet-first +// defaults and a visible staging stamp, without a diverging branch. +export const isStaging = (target = process.env.HAL_TARGET) => target === 'staging'; + +const flagsPlugin = (staging) => ({ + name: 'coinos-build-flags', + setup(b) { + b.onLoad({ filter: /src[\/\\]build-flags\.js$/ }, () => ({ + contents: `export const STAGING = ${!!staging};\n`, + loader: 'js', + })); + }, +}); + +export async function buildHtml({ minify = true, pwa = minify, features = process.env.HAL_FEATURES, staging = isStaging() } = {}) { const result = await Bun.build({ entrypoints: ['./src/app.js'], target: 'browser', minify, - plugins: [featurePlugin(enabledFeatures(features))], + plugins: [featurePlugin(enabledFeatures(features)), flagsPlugin(staging)], }); if (!result.success) { for (const log of result.logs) console.error(log); @@ -247,7 +261,7 @@ export async function buildHtml({ minify = true, pwa = minify, features = proces -Coinos +${staging ? 'Coinos Staging' : 'Coinos'} ${pwa ? PWA_HEAD : ''} @@ -278,8 +292,12 @@ if (import.meta.main) { const locales = await readdir('src/locales'); for (const f of locales) await Bun.write('dist/locales/' + f, Bun.file('src/locales/' + f)); - // PWA sidecars. - await Bun.write('dist/manifest.webmanifest', JSON.stringify(MANIFEST, null, 2)); + // PWA sidecars. The staging install gets its own name so the two icons are + // tellable apart on a phone's home screen (identity is per-origin anyway). + const manifest = isStaging() + ? { ...MANIFEST, name: 'Coinos Staging', short_name: 'Staging' } + : MANIFEST; + await Bun.write('dist/manifest.webmanifest', JSON.stringify(manifest, null, 2)); // Full builds get the auto-answering NWC worker; feature-stripped builds // keep the notify-only handler (the responder would just dead-weight them). const feats = enabledFeatures(); diff --git a/src/api.js b/src/api.js index c081cfe..c020a0c 100644 --- a/src/api.js +++ b/src/api.js @@ -6,6 +6,8 @@ // - a 429 from ANY request immediately backs off ALL subsequent requests // (global exponential pause), easing back as requests succeed. +import { STAGING } from './build-flags.js'; + // Block explorer selection, stored per network (see the per-network presets + // nk() helper below). Each network defaults to its first esplora preset; users // can pick another or a custom Esplora/electrs REST URL (their own node). @@ -41,7 +43,9 @@ export function getNetwork() { const n = localStorage.getItem(NETWORK_KEY); if (NETWORKS.some((x) => x.id === n)) return n; } catch {} - return 'mainnet'; + // Staging builds default fresh profiles to mutinynet (play money); a + // stored choice — including mainnet — always wins over the default. + return STAGING ? 'mutinynet' : 'mainnet'; } export function setNetwork(net) { try { localStorage.setItem(NETWORK_KEY, net); } catch {} diff --git a/src/app.js b/src/app.js index ecb25bc..191b8fe 100644 --- a/src/app.js +++ b/src/app.js @@ -12,6 +12,7 @@ import { nip98Header } from './nostr-login.js'; import { NOSTR_MARK } from './features/nostrlogin.js'; import { scanQr } from './scan.js'; import { dataSources, getSource, setSource, getNetwork, setNetwork, NETWORKS } from './api.js'; +import { STAGING } from './build-flags.js'; import { buildFeatures } from './features/index.js'; import { t, LANGS, getLang, setLang, isRTL, loadLocale } from './i18n.js'; import { @@ -1848,7 +1849,8 @@ function brandHeader(withLock) { { class: 'row between' }, h( 'div', - { class: 'brand', style: 'cursor:pointer', title: t('home'), onClick: goHome }, + { class: 'brand' + (STAGING ? ' staging' : ''), style: 'cursor:pointer', title: t('home'), onClick: goHome }, + STAGING ? h('span', { class: 'staging-badge', title: 'staging build — new wallets start on Mutinynet' }, 'staging') : null, h('div', { class: 'logo-full', 'aria-label': 'coinos', role: 'img', html: '' }) ), withLock diff --git a/src/build-flags.js b/src/build-flags.js new file mode 100644 index 0000000..e466da2 --- /dev/null +++ b/src/build-flags.js @@ -0,0 +1,9 @@ +// Build-time flags. This module ships its defaults (a production build); the +// deploy hook builds the staging webroot with HAL_TARGET=staging and build.js +// swaps these values in the bundle (same mechanism as features/index.js) — so +// staging behavior lives on master instead of a perpetually-rebased branch. +// +// STAGING: new profiles default to Mutinynet (Settings can still switch to +// mainnet), and the header logo wears a staging stamp so nobody mistakes +// which site their sats are on. +export const STAGING = false; diff --git a/src/style.css b/src/style.css index 5c41a26..b5d0576 100644 --- a/src/style.css +++ b/src/style.css @@ -493,6 +493,16 @@ hr.divider { margin: 16px 0; } .hat-btn { white-space: nowrap; flex-shrink: 0; } .hat-btn.wearing { background: var(--accent); border-color: var(--accent); color: var(--accent-fg); } .hat-btn.confirm { border-color: var(--green); color: var(--green); } + +/* staging builds: an unmissable stamp across the header logo */ +.brand.staging { position: relative; } +.staging-badge { + position: absolute; left: 2px; bottom: -3px; z-index: 2; pointer-events: none; + background: #f7931a; color: #fff; font-size: 9px; font-weight: 750; + letter-spacing: .06em; text-transform: uppercase; line-height: 1; + padding: 2px 6px; border-radius: 5px; transform: rotate(-6deg); + box-shadow: 0 1px 2px rgba(0, 0, 0, .25); +} .wallet-row-btn { font-size: 19px; line-height: 1; padding: 8px 12px; } .chat-avatar.header-ava.fallback { align-items: center; justify-content: center; display: flex; color: var(--muted); } -- cgit v1.2.3