feat(branding): brand kit architecture (Phase 1) #96

Merged
padreug merged 10 commits from feat/brand-kit into dev 2026-06-10 08:17:56 +00:00
Owner

Closes #95 (Phase 1) — establishes the white-label brand kit in the webapp tree. Phase 2 (NixOS hosts migrate to BRAND_DIR) tracked in aiolabs/server-deploy#8.

What changed

A branding/<dep>/ directory now drives PWA icons, manifest fields, and the in-app <img> logo across the hub and all 8 standalones. The committed branding/default/ is aiolabs's brand. Downstream deployers point BRAND_DIR at their own variant and get a fully rebranded build without forking.

  • branding/default/logo.png (seeded from src/assets/logo.png) + brand.json with { name, shortName }. SVG slot reserved; defaults ship PNG-only until a vector source lands.
  • pwa-assets.config.ts + @vite-pwa/assets-generator — derive the full PWA icon set from a single source. Resolution order: branding/<dep>/icons/<app>/logo.{svg,png}branding/<dep>/logo.{svg,png} → error.
  • vite-branding.ts — shared resolver. Exports BRAND_DIR, the typed brand object, a @brand import alias, brandManifestName() helper, and brandAssetsPlugin() (auto-runs the generator on every build/dev start via Vite's buildStart).
  • scripts/generate-pwa-assets.mjs — wraps the CLI and cleans up the staged source so the 1024px brand source doesn't leak into dist/icons/.
  • 4 Vue components migrated from @/assets/logo.png to @brand/logo.png (Login.vue, LoginDemo.vue, AppSidebar.vue, MobileDrawer.vue).
  • 9 vite configs + 9 HTML files updated for /icons/<name> paths (the seven hand-crafted public/*.png|.ico|.svg binaries are deleted; public/icons/ is gitignored).
  • 8 standalone manifest configs now accept brand.themeColor/backgroundColor as optional chrome overrides with ?? fallbacks to existing per-app accents. Events takes its full manifest.name/short_name from brand.
  • branding/README.md + a Brand Kit section in CLAUDE.md document the contract.

Strict, no compat shims

Per CLAUDE.md's pre-public-launch policy, VITE_APP_NAME no longer overrides. Hosts that currently set it (cfaun → "Bouge") must declare BRAND_DIR pointing at a per-host branding/ directory before their next webapp flake input bump. That migration is aiolabs/server-deploy#8.

Between landing this PR and Phase 2:

  • aio-demo (tracks webapp-demodev) immediately ships as "AIO Hub" / "AIO" branded — acceptable for staging vet.
  • Production hosts (cfaun, four84, …) are unaffected until their next webapp flake.lock bump.

Deferred to follow-up

  • flake.nix exposing lib.mkWebapp — has real complexity (pnpm + sharp/libvips under nix sandbox). Filed as a separate issue.
  • An SVG vector source for the aiolabs default brand — non-blocking quality upgrade.

Commit layout

9 narrow commits, one concern each (scaffold → install generator → @brand alias → cleanup wrapper → manifest wiring → drop compat shim → icon path migration → auto-gen plugin → docs).

Test plan

  • pnpm install clean (sharp peer dep warning is expected — generator uses sharp via CLI, not through vite-plugin-pwa)
  • pnpm build produces dist/index.html with <title>AIO Hub</title> and 6 icons in dist/icons/
  • pnpm build:events produces a manifest with name: "AIO", theme #1f2937
  • pnpm build:wallet keeps name: "Wallet — Lightning" + theme #eab308 accent (verifying per-app fallbacks)
  • BRAND_DIR=branding/<fixture> pnpm build:events swaps logo + manifest name end-to-end
  • pnpm build:demo succeeds across all 9 builds, each emits its own dist-<app>/icons/ set
  • Smoke-test in a browser on aio-demo once merged

🤖 Generated with Claude Code

Closes #95 (Phase 1) — establishes the white-label brand kit in the webapp tree. Phase 2 (NixOS hosts migrate to `BRAND_DIR`) tracked in aiolabs/server-deploy#8. ## What changed A `branding/<dep>/` directory now drives PWA icons, manifest fields, and the in-app `<img>` logo across the hub and all 8 standalones. The committed `branding/default/` is aiolabs's brand. Downstream deployers point `BRAND_DIR` at their own variant and get a fully rebranded build without forking. - `branding/default/` — `logo.png` (seeded from `src/assets/logo.png`) + `brand.json` with `{ name, shortName }`. SVG slot reserved; defaults ship PNG-only until a vector source lands. - `pwa-assets.config.ts` + `@vite-pwa/assets-generator` — derive the full PWA icon set from a single source. Resolution order: `branding/<dep>/icons/<app>/logo.{svg,png}` → `branding/<dep>/logo.{svg,png}` → error. - `vite-branding.ts` — shared resolver. Exports `BRAND_DIR`, the typed `brand` object, a `@brand` import alias, `brandManifestName()` helper, and `brandAssetsPlugin()` (auto-runs the generator on every build/dev start via Vite's `buildStart`). - `scripts/generate-pwa-assets.mjs` — wraps the CLI and cleans up the staged source so the 1024px brand source doesn't leak into `dist/icons/`. - 4 Vue components migrated from `@/assets/logo.png` to `@brand/logo.png` (`Login.vue`, `LoginDemo.vue`, `AppSidebar.vue`, `MobileDrawer.vue`). - 9 vite configs + 9 HTML files updated for `/icons/<name>` paths (the seven hand-crafted `public/*.png|.ico|.svg` binaries are deleted; `public/icons/` is gitignored). - 8 standalone manifest configs now accept `brand.themeColor`/`backgroundColor` as optional chrome overrides with `??` fallbacks to existing per-app accents. Events takes its full `manifest.name`/`short_name` from brand. - `branding/README.md` + a Brand Kit section in `CLAUDE.md` document the contract. ## Strict, no compat shims Per CLAUDE.md's pre-public-launch policy, `VITE_APP_NAME` no longer overrides. Hosts that currently set it (cfaun → "Bouge") **must declare `BRAND_DIR` pointing at a per-host `branding/` directory before their next `webapp` flake input bump**. That migration is aiolabs/server-deploy#8. Between landing this PR and Phase 2: - `aio-demo` (tracks `webapp-demo` → `dev`) immediately ships as "AIO Hub" / "AIO" branded — acceptable for staging vet. - Production hosts (cfaun, four84, …) are unaffected until their next `webapp` flake.lock bump. ## Deferred to follow-up - `flake.nix` exposing `lib.mkWebapp` — has real complexity (pnpm + sharp/libvips under nix sandbox). Filed as a separate issue. - An SVG vector source for the aiolabs default brand — non-blocking quality upgrade. ## Commit layout 9 narrow commits, one concern each (scaffold → install generator → @brand alias → cleanup wrapper → manifest wiring → drop compat shim → icon path migration → auto-gen plugin → docs). ## Test plan - [x] `pnpm install` clean (sharp peer dep warning is expected — generator uses sharp via CLI, not through vite-plugin-pwa) - [x] `pnpm build` produces `dist/index.html` with `<title>AIO Hub</title>` and 6 icons in `dist/icons/` - [x] `pnpm build:events` produces a manifest with `name: "AIO"`, theme `#1f2937` - [x] `pnpm build:wallet` keeps `name: "Wallet — Lightning"` + theme `#eab308` accent (verifying per-app fallbacks) - [x] `BRAND_DIR=branding/<fixture> pnpm build:events` swaps logo + manifest name end-to-end - [x] `pnpm build:demo` succeeds across all 9 builds, each emits its own `dist-<app>/icons/` set - [ ] Smoke-test in a browser on aio-demo once merged 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Introduces branding/default/ as the unparameterized aiolabs brand:
- logo.png (1024x1024, seeded from src/assets/logo.png)
- brand.json with name, shortName, themeColor, backgroundColor

First step toward white-label PWA branding (aiolabs/webapp#95). No
consumer wiring yet — that's the next commits. Future deployers
(NixOS hosts in server-deploy, third-party white-labelers) point
BRAND_DIR at their own variant of this layout.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds pwa-assets.config.ts that reads $BRAND_DIR (default
./branding/default) and $BRAND_APP (optional per-standalone
override), resolves logo.svg/logo.png with documented fallback
order, and emits the existing icon set (favicon.ico,
icon-{192,512}.png, icon-maskable-{192,512}.png,
apple-touch-icon.png) into public/icons/.

Generator outputs alongside its source, so the config stages the
brand source into public/icons/.brand-source.{svg,png}; gitignoring
public/icons/ covers both staged source and generated icons in one
line.

Adds pnpm script `generate-pwa-assets`. Vite configs / HTML <link>
href updates come in follow-up commits; this commit alone produces
the icon set under public/icons/ but doesn't yet replace the
committed public/*.png binaries.

Part of aiolabs/webapp#95 (brand kit architecture).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
vite-branding.ts is the shared resolver. Exports BRAND_DIR (absolute,
defaults to ./branding/default) and brandAlias for spreading into each
vite config's resolve.alias map.

All 9 vite configs now spread brandAlias so `@brand/<file>` resolves
to the active brand dir at build time.

Migrates the four <img src="@/assets/logo.png"> consumers
(Login.vue, LoginDemo.vue, AppSidebar.vue, MobileDrawer.vue) to
@brand/logo.png. Unused Navbar.old.vue left as-is.

Build verified: dist/assets/logo-<hash>.png emits from the aliased
import. Future deployers point BRAND_DIR at their brand kit and the
in-app logo follows automatically.

Part of aiolabs/webapp#95.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
pwa-assets.config.ts stages the brand logo in public/icons/.brand-source.*
so the CLI (which emits next to its source) writes alongside it. Without
cleanup, the full-resolution 1024+ source ends up in dist/icons/ on every
build and is publicly served at /icons/.brand-source.png.

scripts/generate-pwa-assets.mjs runs the CLI, then removes the staged
source. Wire it through the `generate-pwa-assets` pnpm script.

Part of aiolabs/webapp#95.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
Per the pre-public-launch policy in CLAUDE.md, strict-from-the-start.
brand.json is the sole source for app naming; VITE_APP_NAME no longer
overrides.

Migration responsibility moves to aiolabs/server-deploy#8 (Phase 2):
hosts that currently set VITE_APP_NAME (cfaun → "Bouge") must instead
declare BRAND_DIR pointing at a per-host branding/ directory before
their next webapp flake input bump.

process.env.VITE_APP_NAME is still set internally (from brand.name) to
keep Vite's HTML %VITE_APP_NAME% substitution working — but it's
internal wiring now, not a user-facing knob.

Part of aiolabs/webapp#95.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PWA icons now ship from public/icons/ (generated by
@vite-pwa/assets-generator, gitignored). The seven hand-crafted
binaries at public/ root come out of the tree.

Changes:
- 7 deleted: public/{favicon.ico, apple-touch-icon.png, mask-icon.svg,
  icon-{192,512}.png, icon-maskable-{192,512}.png}
- 9 HTML: <link rel="icon"|"apple-touch-icon"> hrefs prefixed with
  /icons/. mask-icon link dropped (PNG source → no sharp SVG; modern
  browsers prefer favicon.svg anyway, which we can revisit when an
  SVG brand source lands).
- 8 vite configs: includeAssets[] + manifest.icons[].src prefixed
  with icons/. Vite rewrites /icons/foo → <base>/icons/foo when base
  is set (so /events/icons/favicon.ico under /events/ deploys).

Build is now dependent on `pnpm generate-pwa-assets` running first
(or whatever invokes the generator — Phase 2 NixOS builds wire this
into buildNpmPackage). Standalone dev runs the generator on first
boot or whenever BRAND_DIR changes.

Part of aiolabs/webapp#95.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
vite-branding.ts now exports brandAssetsPlugin() — a Vite plugin
whose buildStart hook runs scripts/generate-pwa-assets.mjs once per
build / dev-server start. All 9 vite configs register it first in
plugins[], so PWA icons under public/icons/ are guaranteed to exist
before VitePWA's includeAssets / manifest.icons get processed and
before the public/ → dist/ copy.

Removes the "did you remember to pnpm generate-pwa-assets?" failure
mode. Dev mode now also auto-populates icons (no more dev 404s on
/icons/favicon.ico).

Verified build from clean state (no public/icons/ pre-existing): the
plugin generates, all 6 icons land in dist-wallet/icons/.

Part of aiolabs/webapp#95.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
branding/README.md is the deployer contract:
- Directory layout, source format constraints (SVG > PNG ≥ 1024),
  brand.json schema, per-standalone override resolution order
- BRAND_DIR / BRAND_APP usage, generator pipeline walkthrough
- Pointer to issue #95 + the NixOS Phase 2 integration

webapp CLAUDE.md gains a Brand Kit section describing the moving
parts (vite-branding.ts, @brand alias, brandAssetsPlugin,
public/icons/ gitignore, per-app override path) so future sessions
on this repo know the convention without grepping.

Adds BRAND_DIR / BRAND_APP to the Environment Variables example.

Workspace ~/dev/CLAUDE.md note about "brand changes don't need
flake.lock bump" deferred to Phase 2 (server-deploy migration) —
that's when the workflow becomes reality.

Part of aiolabs/webapp#95.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the seed copy of src/assets/logo.png (8-bit colormap) with
the official AIO logo from ~/Pictures/AIO/aio.png (8-bit/color RGBA,
1024x1024, with alpha — better for maskable icons).

Generator output verified: all 6 icons regenerate cleanly from the
new source.

Part of aiolabs/webapp#95 / #96.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Improved version of the AIO mark from ~/Pictures/AIO/aio.png. Same
1024x1024 RGBA dimensions; ~57% smaller on disk.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
padreug force-pushed feat/brand-kit from 9b38c00938 to 3388b481a7 2026-06-09 23:25:51 +00:00 Compare
padreug force-pushed feat/brand-kit from 3388b481a7 to 1516320c06 2026-06-09 23:27:47 +00:00 Compare
padreug force-pushed feat/brand-kit from 1516320c06 to be427f1821 2026-06-10 07:51:33 +00:00 Compare
padreug deleted branch feat/brand-kit 2026-06-10 08:17:56 +00:00
Sign in to join this conversation.
No description provided.