This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/BRANDING.md
2026-02-21 21:59:38 +00:00

10 KiB
Raw Blame History

Branding — Volume Mount Contract

Version: 0.7.3 Status: Spec


Overview

Chat Switchboard supports white-label branding through a volume mount at /branding/ on the frontend container. Deployers provide a ConfigMap (or bind mount) with their identity assets. The app reads a JSON config on startup, serves static assets at runtime, and falls back gracefully when no branding is mounted.

Switchboard provides the hooks. Your brand lives in its own repo.


Mount Path

/branding/              ← volume mount root (frontend container)
├── branding.json       ← config seed (required if mount exists)
├── favicon.png         ← tab/bookmark icon
├── logo.png            ← splash page hero, optional sidebar
└── custom.css          ← style overrides (power-user escape hatch)

All files are optional individually, but branding.json is expected if the mount exists. Missing files degrade gracefully — no broken images, no JS errors.


branding.json — Config Seed

{
  "org_name": "Gobha.ai",
  "tagline": "Something clever here",
  "accent_color": "#4a9eff",
  "logo": "logo.png",
  "favicon": "favicon.png"
}

Fields:

Field Type Default Description
org_name string "Chat Switchboard" Displayed in splash, header, auth card, <title>
tagline string "Multi-Model AI Chat" Splash page subtitle, auth footer
headline string null Splash hero headline (default preserved if null)
accent_color string "#4a9eff" Primary UI accent (hex)
logo string null Filename relative to /branding/
favicon string null Filename relative to /branding/
pills array (Switchboard defaults) Feature pills on splash. [] = hide. See below.

Pills format:

"pills": [
  { "icon": "⚡", "text": "Fast inference", "style": "accent" },
  { "icon": "🔒", "text": "Zero trust", "style": "purple" },
  { "icon": "🏢", "text": "Enterprise ready" }
]

style is optional: "accent" uses the accent color, "purple" uses purple, omit for the default neutral pill. Set "pills": [] to hide the section entirely. Omit the field to keep the stock Switchboard pills.

Lifecycle:

  1. Frontend entrypoint (docker-entrypoint-fe.sh) reads /branding/branding.json on container start
  2. Values are injected into index.html as a <script> block: window.__BRANDING__
  3. The branding key is also upserted into global_settings via the backend on startup (admin panel override layer — if admins change values in the UI, those take precedence until the next cold deploy with a branding mount)
  4. Frontend initBranding() applies values from window.__BRANDING__ (fast, no API call) then overlays any DB overrides from App.serverSettings.branding (from public settings)

Resolution order: branding.json (fast init) → DB global_settings.branding (override layer)


favicon.png — Static Asset

Served at /branding/favicon.png by nginx. The frontend <link rel="icon"> is set dynamically by initBranding():

// If branding favicon exists, use it; otherwise keep built-in default
document.querySelector('link[rel="icon"]').href = '/branding/favicon.png';

Recommendations:

  • PNG format (modern browsers prefer it over ICO)
  • 32×32 minimum, 192×192 recommended (covers PWA + high-DPI)
  • Transparent background works best with dark themes

logo.png — Static Asset

Served at /branding/logo.png. Used in:

  • Splash page hero (replaces the 🔀 emoji)
  • Sidebar header (optional, depends on size)

Recommendations:

  • PNG with transparency
  • Max 512×512 (larger files are wasteful; displayed at ~80px on splash)
  • Aspect ratio: square or landscape (tall logos will be clamped)

custom.css — Style Extension

Loaded after the main stylesheet:

<link rel="stylesheet" href="/branding/custom.css" onerror="this.remove()">

The onerror handler silently removes the tag if the file doesn't exist (no 404 console noise in unbrandeded deployments).

What you can do:

  • Override --accent-color and any other CSS custom property
  • Change fonts (@import or @font-face with files in /branding/)
  • Add background textures or patterns
  • Hide elements you don't want (display: none)
  • Override specific component styles

What you shouldn't do:

  • Rely on internal class names that may change between versions
  • Override layout properties (flex, grid) unless you're testing against the current release
  • Import external resources (breaks airgapped deployments)

Example:

:root {
    --accent-color: #e74c3c;
    --bg-primary: #1a1a2e;
}

.splash-logo img {
    border-radius: 50%;
}

Frontend Touchpoints

These are the DOM elements and CSS properties that branding affects:

Element / Property Default Value Branding Source
<title> Chat Switchboard org_name
.brand-text Chat Switchboard org_name
.hero-wordmark Chat Switchboard org_name
.hero-headline One interface. Every AI model. headline
.hero-sub (default description) tagline
.splash-logo SVG switchboard icon logo.png<img>
link[rel="icon"] favicon-32.png favicon.png
--accent-color #4a9eff accent_color
.auth-card-header h2 Welcome back Welcome to {org_name}
.auth-card-header p Sign in to continue to your workspace Sign in to continue
.auth-footer p Self-hosted AI chat... tagline
.hero-features Switchboard feature pills pills array or [] to hide

Nginx Configuration

The frontend entrypoint adds a branding location block. For path-based deployments, the block is under BASE_PATH so Traefik routes requests to the correct pod:

# Root deployment (no BASE_PATH):
location /branding/ {
    alias /branding/;
    expires 1h;
    add_header Cache-Control "public";
    try_files $uri =404;
}

# Path-based deployment (e.g. /dev, /test):
location ${BASE_PATH}/branding/ {
    alias /branding/;
    expires 1h;
    add_header Cache-Control "public";
    try_files $uri =404;
}

Frontend JS resolves paths via window.__BASE__ + '/branding/' so URLs automatically include the environment prefix.

Short cache (1h) so branding updates via ConfigMap rollout are picked up without requiring users to hard-refresh.


K8s Deployment

The frontend deployment mounts the branding ConfigMap:

spec:
  containers:
  - name: frontend
    volumeMounts:
    - name: branding
      mountPath: /branding
      readOnly: true
  volumes:
  - name: branding
    configMap:
      name: switchboard-branding
      optional: true          # ← app works without it

The optional: true is critical — Switchboard deploys cleanly with zero branding config. The switchboard-gobha-ai repo (or any deployer's equivalent) creates this ConfigMap.


Deployer Repo Structure (Example: switchboard-gobha-ai)

switchboard-gobha-ai/
├── branding/
│   ├── branding.json
│   ├── favicon.png
│   ├── logo.png
│   └── custom.css
├── k8s/
│   └── configmap.yaml
├── README.md
└── .gitea/
    └── workflows/
        └── deploy.yaml

configmap.yaml:

apiVersion: v1
kind: ConfigMap
metadata:
  name: switchboard-branding
  namespace: ${NAMESPACE}
data:
  branding.json: |
    {
      "org_name": "Gobha.ai",
      "tagline": "Your tagline",
      "accent_color": "#4a9eff"
    }
binaryData:
  favicon.png: <base64-encoded>
  logo.png: <base64-encoded>
  custom.css: <base64-encoded-or-use-data>

Note: For binary files in ConfigMaps, use binaryData with base64 encoding. Alternatively, use a script that creates the ConfigMap from files:

kubectl create configmap switchboard-branding \
  --from-file=branding/branding.json \
  --from-file=branding/favicon.png \
  --from-file=branding/logo.png \
  --from-file=branding/custom.css \
  --dry-run=client -o yaml | kubectl apply -f -

Backend Integration

The backend participates in branding in two ways:

  1. Startup seed (optional, future): If the backend also mounts /branding/, it can read branding.json and upsert into global_settings alongside the admin bootstrap. This enables admin-panel overrides without redeployment.

  2. Public settings: The branding key is added to publicSettingKeys, making it available to non-admin users via GET /api/v1/settings/public.

For 0.7.3, the frontend reads branding from the static mount at init time. Backend DB seeding is a future enhancement for admin-panel editing.


Graceful Degradation

Condition Behavior
No /branding/ mount All defaults. App looks like stock Switchboard
Mount exists, no branding.json Static assets served, no text/color overrides
branding.json missing fields Each field falls back to its default
logo field set, file missing <img> gets 404, onerror shows emoji fallback
custom.css missing <link> tag self-removes via onerror
favicon.png missing Built-in favicon remains