feat(branding): drive PWA manifest from brand.json
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 `?? '<existing>'` 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) <noreply@anthropic.com>
This commit is contained in:
parent
88ab432629
commit
ce5a1a6a56
11 changed files with 74 additions and 32 deletions
|
|
@ -1,6 +1,4 @@
|
|||
{
|
||||
"name": "AIO",
|
||||
"shortName": "AIO",
|
||||
"themeColor": "#ffffff",
|
||||
"backgroundColor": "#ffffff"
|
||||
"shortName": "AIO"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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/<file>` 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 || '/',
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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 || '/',
|
||||
|
|
|
|||
|
|
@ -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 || '/',
|
||||
|
|
|
|||
|
|
@ -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 || '/',
|
||||
|
|
|
|||
|
|
@ -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 || '/',
|
||||
|
|
|
|||
|
|
@ -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 || '/',
|
||||
|
|
|
|||
|
|
@ -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 || '/',
|
||||
|
|
|
|||
|
|
@ -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 || '/',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue