brands/<name>/ and src/ contains no client names, colors, or assets.
Brand Inputs
Each brand directory underbrands/ holds its config and assets. Each deployment gets its own brand directory.
| File | Required | Purpose |
|---|---|---|
.env | Yes | VITE_* config (API URL, brand name, token key, theme colors) |
favicon.png | Yes | Browser favicon |
favicon.ico | Optional | Legacy favicon |
logo.png | Optional | Header/app logo (absent → no logo mark) |
theme.css | Optional | Extra CSS injected as a stylesheet link |
favicon-512.png | Optional | High-res favicon variant |
favicon.png + logo.png, while another ships only favicons (no logo).
The Build Script
scripts/build.sh <brand> is the single entry point.
Steps performed:
- Validates the brand exists under
brands/. - Copies
brands/<brand>/.envto the repo root.env. - Copies
logo.png,favicon.png,favicon.ico, andtheme.cssintopublic/via a helper thatchmod 644s each file. Iflogo.pngortheme.cssis absent, the script removes any stale copy frompublic/. - Runs
npm run build(Vite). - Injects a
<style>:root{...}</style>block intodist/index.htmlcontaining--primary,--primary-soft, and--bgread from the brand.env. - If
theme.cssexists, injects a<link rel="stylesheet" href="/theme.css">intodist/index.html.
Brand Config Values
src/brand.ts reads the VITE_* values with safe defaults:
| Export | Env var | Default |
|---|---|---|
BRAND_NAME | VITE_BRAND_NAME | CXB Console |
BRAND_POWERED_BY | VITE_BRAND_POWERED_BY | Powered by CX Bridge |
TOKEN_KEY | VITE_TOKEN_KEY | cxb_token |
BRAND_LOGO | VITE_BRAND_LOGO | shown unless set to false |
AURORA_COLOR1 / AURORA_COLOR2 | VITE_AURORA_COLOR1 / 2 | teal defaults |
BRAND_THEME | VITE_BRAND_THEME | empty |
--primary, --primary-soft, --bg) come from VITE_PRIMARY_COLOR, VITE_PRIMARY_SOFT, and VITE_BG_COLOR, injected into the HTML by the build script rather than read at runtime.
CSS Variables, Not a Plugin
CSS variable injection happens in the build script withsed against dist/index.html, not through a Vite plugin. This is deliberate — direct HTML injection is more reliable than relying on plugin ordering. The layouts and primitives reference these variables (var(--primary), etc.) so a single brand .env retheme propagates everywhere.
The ADK Gate
src/tier.ts computes the ADK flag by hashing the lowercased BRAND_NAME against an allow-list. Agent Desk routes, nav items, and the bot Agent Desk section are gated on ADK. A brand without Agent Desk licensing simply never renders those surfaces — no source edits required.
No-Leakage Rules
The whole point of the brand split is that one brand’s identity never reaches another’s build.- Do not hard-code client names, colors, favicons, or logos in
src/— that defeats the build-time split. - When deploying, rsync the built
dist/with--delete. Without it, stale chunks from a previous brand linger in the web root and can surface cross-brand assets or trigger CORS errors on old chunk filenames. - After a build, verify zero leakage across HTML, JS, CSS, favicons,
.env, and API responses.
Related Docs
API Client & Auth
VITE_API_URL and the brand-scoped token key.
App Shell & Routing
How the ADK flag gates routes.