From ce5a1a6a56ab697a8453c227281aa23025342ddb Mon Sep 17 00:00:00 2001 From: Padreug Date: Tue, 9 Jun 2026 23:11:30 +0200 Subject: [PATCH] feat(branding): drive PWA manifest from brand.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vite-branding.ts now loads brand.json into a typed `brand` object and exports a `brandManifestName()` helper. Schema: { name (required), shortName?, themeColor?, backgroundColor? } Default brand.json drops themeColor/backgroundColor — they're optional overrides; per-app accents (wallet yellow, chat green, …) keep working via `?? ''` fallbacks in each standalone's vite config. events: manifest.name/short_name driven by brand. VITE_APP_NAME env override stays (Phase 2 server-deploy migration still in flight) and, when set, overrides both name and short_name to preserve pre-#95 behavior. cfaun's VITE_APP_NAME=Bouge keeps working unchanged. hub (vite.config.ts): brand.name flows into %VITE_APP_NAME% Hub title. 7 other standalones (wallet, chat, market, forum, tasks, restaurant, libra): only theme_color/background_color get brand overrides. Their manifest.name/short_name stay hardcoded so multi-PWA home-screen labels remain differentiated ("Wallet", "Chat", …) rather than all collapsing to the brand short_name. Verified default build: events manifest name=AIO; wallet keeps "Wallet — Lightning" + #eab308 accent. Verified VITE_APP_NAME=Sortir override: events name+short_name=Sortir. Part of aiolabs/webapp#95. Co-Authored-By: Claude Opus 4.7 (1M context) --- branding/default/brand.json | 4 +--- vite-branding.ts | 34 ++++++++++++++++++++++++++++++++++ vite.chat.config.ts | 6 +++--- vite.config.ts | 8 +++++++- vite.events.config.ts | 18 +++++++++++------- vite.forum.config.ts | 6 +++--- vite.libra.config.ts | 6 +++--- vite.market.config.ts | 6 +++--- vite.restaurant.config.ts | 6 +++--- vite.tasks.config.ts | 6 +++--- vite.wallet.config.ts | 6 +++--- 11 files changed, 74 insertions(+), 32 deletions(-) diff --git a/branding/default/brand.json b/branding/default/brand.json index 06660c3..2fc12c4 100644 --- a/branding/default/brand.json +++ b/branding/default/brand.json @@ -1,6 +1,4 @@ { "name": "AIO", - "shortName": "AIO", - "themeColor": "#ffffff", - "backgroundColor": "#ffffff" + "shortName": "AIO" } diff --git a/vite-branding.ts b/vite-branding.ts index 99e871d..abcb841 100644 --- a/vite-branding.ts +++ b/vite-branding.ts @@ -1,3 +1,4 @@ +import { readFileSync } from 'node:fs' import { resolve } from 'node:path' /** @@ -9,6 +10,28 @@ import { resolve } from 'node:path' */ export const BRAND_DIR = resolve(process.env.BRAND_DIR ?? './branding/default') +/** Fields parsed from brand.json. All but `name` are optional. */ +export interface Brand { + /** Brand label — drives PWA manifest name. */ + name: string + /** PWA install/home-screen short label. Defaults to `name`. */ + shortName?: string + /** + * Optional PWA chrome theme color (status bar / title bar when installed). + * When unset, each standalone's vite config keeps its hardcoded accent. + */ + themeColor?: string + /** + * Optional PWA splash background. When unset, each standalone's vite + * config keeps its hardcoded value. + */ + backgroundColor?: string +} + +export const brand: Brand = JSON.parse( + readFileSync(resolve(BRAND_DIR, 'brand.json'), 'utf-8'), +) + /** * Spread into a vite config's `resolve.alias` map. Lets components * import deployer-provided assets via `@brand/` instead of @@ -17,3 +40,14 @@ export const BRAND_DIR = resolve(process.env.BRAND_DIR ?? './branding/default') export const brandAlias = { '@brand': BRAND_DIR, } as const + +/** + * PWA manifest name for a standalone. Combines the brand name with the + * app's own label, or returns the bare brand when no label is given. + * + * Example: `brandManifestName('Wallet')` → "AIO Wallet" / "Cfaun Wallet". + * Example: `brandManifestName()` → "AIO" / "Sortir". + */ +export function brandManifestName(appLabel?: string): string { + return appLabel ? `${brand.name} ${appLabel}` : brand.name +} diff --git a/vite.chat.config.ts b/vite.chat.config.ts index 9d6c3c2..5bd797d 100644 --- a/vite.chat.config.ts +++ b/vite.chat.config.ts @@ -5,7 +5,7 @@ import { defineConfig, type Plugin } from 'vite' import { VitePWA } from 'vite-plugin-pwa' import { ViteImageOptimizer } from 'vite-plugin-image-optimizer' import { visualizer } from 'rollup-plugin-visualizer' -import { brandAlias } from './vite-branding' +import { brand, brandAlias } from './vite-branding' function chatHtmlPlugin(): Plugin { return { @@ -72,8 +72,8 @@ export default defineConfig(({ mode }) => ({ name: 'Chat — Encrypted', short_name: 'Chat', description: 'End-to-end encrypted Nostr chat', - theme_color: '#16a34a', - background_color: '#ffffff', + theme_color: brand.themeColor ?? '#16a34a', + background_color: brand.backgroundColor ?? '#ffffff', display: 'standalone', orientation: 'portrait-primary', start_url: process.env.VITE_BASE_PATH || '/', diff --git a/vite.config.ts b/vite.config.ts index 2bb909f..a6a25f3 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -5,7 +5,7 @@ import { defineConfig } from 'vite' import Inspect from 'vite-plugin-inspect' import { ViteImageOptimizer } from 'vite-plugin-image-optimizer' import { visualizer } from 'rollup-plugin-visualizer' -import { brandAlias } from './vite-branding' +import { brand, brandAlias } from './vite-branding' // https://vite.dev/config/ // @@ -13,6 +13,12 @@ import { brandAlias } from './vite-branding' // the entire origin and blocked Chrome from offering installs for the // path-mounted standalones at /libra/, /market/, etc. The hub is a // launcher page; users install the standalones they actually use. + +// Brand name flows into index.html's `%VITE_APP_NAME% Hub` title. +// VITE_APP_NAME env override stays during the Phase 2 server-deploy +// migration (aiolabs/webapp#95); drop once all hosts move to BRAND_DIR. +process.env.VITE_APP_NAME = process.env.VITE_APP_NAME ?? brand.name + export default defineConfig(({ mode }) => ({ // Per-app dep cache so concurrent dev servers don't race on .vite/deps cacheDir: 'node_modules/.vite-hub', diff --git a/vite.events.config.ts b/vite.events.config.ts index 967b1fe..c855806 100644 --- a/vite.events.config.ts +++ b/vite.events.config.ts @@ -5,7 +5,7 @@ import { defineConfig, type Plugin } from 'vite' import { VitePWA } from 'vite-plugin-pwa' import { ViteImageOptimizer } from 'vite-plugin-image-optimizer' import { visualizer } from 'rollup-plugin-visualizer' -import { brandAlias } from './vite-branding' +import { brand, brandAlias, brandManifestName } from './vite-branding' /** * Plugin to rewrite dev server requests to events.html @@ -42,10 +42,12 @@ function eventsHtmlPlugin(): Plugin { * VITE_BASE_PATH=/events/ → app.ariege.io/events/ (shared auth) * (default: /) → bouge.ariege.io (standalone subdomain) * - * Set VITE_APP_NAME to brand the standalone (PWA name, HTML title, console - * logs). cfaun overrides to "Bouge" via NixOS. Defaults to "Events". + * Brand name resolves from brand.json under $BRAND_DIR (see + * vite-branding.ts and aiolabs/webapp#95). VITE_APP_NAME remains an + * env override during the Phase 2 server-deploy migration; drop the + * env path once all hosts have moved to BRAND_DIR. */ -const APP_NAME = process.env.VITE_APP_NAME || 'Events' +const APP_NAME = process.env.VITE_APP_NAME ?? brandManifestName() // Surface the resolved value back into env so Vite's HTML %VITE_APP_NAME% // substitution picks up the fallback when nothing was explicitly set. process.env.VITE_APP_NAME = APP_NAME @@ -86,10 +88,12 @@ export default defineConfig(({ mode }) => ({ ], manifest: { name: APP_NAME, - short_name: APP_NAME, + // VITE_APP_NAME, when set, overrides both name and short_name to + // preserve the pre-#95 behavior. Otherwise brand.shortName drives. + short_name: process.env.VITE_APP_NAME ?? brand.shortName ?? APP_NAME, description: 'Discover events near you', - theme_color: '#1f2937', - background_color: '#ffffff', + theme_color: brand.themeColor ?? '#1f2937', + background_color: brand.backgroundColor ?? '#ffffff', display: 'standalone', orientation: 'portrait-primary', start_url: process.env.VITE_BASE_PATH || '/', diff --git a/vite.forum.config.ts b/vite.forum.config.ts index 1313a89..2693075 100644 --- a/vite.forum.config.ts +++ b/vite.forum.config.ts @@ -5,7 +5,7 @@ import { defineConfig, type Plugin } from 'vite' import { VitePWA } from 'vite-plugin-pwa' import { ViteImageOptimizer } from 'vite-plugin-image-optimizer' import { visualizer } from 'rollup-plugin-visualizer' -import { brandAlias } from './vite-branding' +import { brand, brandAlias } from './vite-branding' function forumHtmlPlugin(): Plugin { return { @@ -72,8 +72,8 @@ export default defineConfig(({ mode }) => ({ name: 'Forum — Discussions', short_name: 'Forum', description: 'Decentralized link aggregator and discussion forum on Nostr', - theme_color: '#2563eb', - background_color: '#ffffff', + theme_color: brand.themeColor ?? '#2563eb', + background_color: brand.backgroundColor ?? '#ffffff', display: 'standalone', orientation: 'portrait-primary', start_url: process.env.VITE_BASE_PATH || '/', diff --git a/vite.libra.config.ts b/vite.libra.config.ts index c792987..9736e46 100644 --- a/vite.libra.config.ts +++ b/vite.libra.config.ts @@ -5,7 +5,7 @@ import { defineConfig, type Plugin } from 'vite' import { VitePWA } from 'vite-plugin-pwa' import { ViteImageOptimizer } from 'vite-plugin-image-optimizer' import { visualizer } from 'rollup-plugin-visualizer' -import { brandAlias } from './vite-branding' +import { brand, brandAlias } from './vite-branding' /** * Plugin to rewrite dev server requests to libra.html @@ -79,8 +79,8 @@ export default defineConfig(({ mode }) => ({ name: 'Libra — Team Accounting', short_name: 'Libra', description: 'Team accounting and expense management', - theme_color: '#1f2937', - background_color: '#ffffff', + theme_color: brand.themeColor ?? '#1f2937', + background_color: brand.backgroundColor ?? '#ffffff', display: 'standalone', orientation: 'portrait-primary', start_url: process.env.VITE_BASE_PATH || '/', diff --git a/vite.market.config.ts b/vite.market.config.ts index b3fc189..d0befc9 100644 --- a/vite.market.config.ts +++ b/vite.market.config.ts @@ -5,7 +5,7 @@ import { defineConfig, type Plugin } from 'vite' import { VitePWA } from 'vite-plugin-pwa' import { ViteImageOptimizer } from 'vite-plugin-image-optimizer' import { visualizer } from 'rollup-plugin-visualizer' -import { brandAlias } from './vite-branding' +import { brand, brandAlias } from './vite-branding' function marketHtmlPlugin(): Plugin { return { @@ -72,8 +72,8 @@ export default defineConfig(({ mode }) => ({ name: 'Market — Nostr', short_name: 'Market', description: 'Decentralized marketplace on Nostr with Lightning payments', - theme_color: '#dc2626', - background_color: '#ffffff', + theme_color: brand.themeColor ?? '#dc2626', + background_color: brand.backgroundColor ?? '#ffffff', display: 'standalone', orientation: 'portrait-primary', start_url: process.env.VITE_BASE_PATH || '/', diff --git a/vite.restaurant.config.ts b/vite.restaurant.config.ts index d95ff73..f3205e9 100644 --- a/vite.restaurant.config.ts +++ b/vite.restaurant.config.ts @@ -5,7 +5,7 @@ import { defineConfig, type Plugin } from 'vite' import { VitePWA } from 'vite-plugin-pwa' import { ViteImageOptimizer } from 'vite-plugin-image-optimizer' import { visualizer } from 'rollup-plugin-visualizer' -import { brandAlias } from './vite-branding' +import { brand, brandAlias } from './vite-branding' function restaurantHtmlPlugin(): Plugin { return { @@ -79,8 +79,8 @@ export default defineConfig(({ mode }) => ({ description: 'Order from your local Nostr-native restaurant with Lightning payments', // Green to differentiate from market red. PDF tile is purple // (see ~/dev/shared/extensions/restaurant/static/image/restaurant.png). - theme_color: '#16a34a', - background_color: '#ffffff', + theme_color: brand.themeColor ?? '#16a34a', + background_color: brand.backgroundColor ?? '#ffffff', display: 'standalone', orientation: 'portrait-primary', start_url: process.env.VITE_BASE_PATH || '/', diff --git a/vite.tasks.config.ts b/vite.tasks.config.ts index cdce062..4fd8efa 100644 --- a/vite.tasks.config.ts +++ b/vite.tasks.config.ts @@ -5,7 +5,7 @@ import { defineConfig, type Plugin } from 'vite' import { VitePWA } from 'vite-plugin-pwa' import { ViteImageOptimizer } from 'vite-plugin-image-optimizer' import { visualizer } from 'rollup-plugin-visualizer' -import { brandAlias } from './vite-branding' +import { brand, brandAlias } from './vite-branding' function tasksHtmlPlugin(): Plugin { return { @@ -72,8 +72,8 @@ export default defineConfig(({ mode }) => ({ name: 'Tasks — Work Orders', short_name: 'Tasks', description: 'Decentralized task management on Nostr', - theme_color: '#4338ca', - background_color: '#ffffff', + theme_color: brand.themeColor ?? '#4338ca', + background_color: brand.backgroundColor ?? '#ffffff', display: 'standalone', orientation: 'portrait-primary', start_url: process.env.VITE_BASE_PATH || '/', diff --git a/vite.wallet.config.ts b/vite.wallet.config.ts index fb34c51..8b0677b 100644 --- a/vite.wallet.config.ts +++ b/vite.wallet.config.ts @@ -5,7 +5,7 @@ import { defineConfig, type Plugin } from 'vite' import { VitePWA } from 'vite-plugin-pwa' import { ViteImageOptimizer } from 'vite-plugin-image-optimizer' import { visualizer } from 'rollup-plugin-visualizer' -import { brandAlias } from './vite-branding' +import { brand, brandAlias } from './vite-branding' /** * Plugin to rewrite dev server requests to wallet.html @@ -78,8 +78,8 @@ export default defineConfig(({ mode }) => ({ name: 'Wallet — Lightning', short_name: 'Wallet', description: 'Lightning Network wallet — send, receive, and manage sats', - theme_color: '#eab308', - background_color: '#ffffff', + theme_color: brand.themeColor ?? '#eab308', + background_color: brand.backgroundColor ?? '#ffffff', display: 'standalone', orientation: 'portrait-primary', start_url: process.env.VITE_BASE_PATH || '/',