Files
paliad/frontend/src/branding.ts
m 495e519475 feat(t-paliad-065): firm-agnostic branding via single FIRM_NAME constant
Paliad ships firm-agnostic per CLAUDE.md ("survives firm renames") but
landing copy, email templates, page titles, and form placeholders still
hard-coded "Hogan Lovells" / "HL Patents". Replaces every user-facing
firm reference with a single source of truth: internal/branding.Name on
the server and frontend/src/branding.ts in the bundle, both reading
FIRM_NAME at startup/build time and defaulting to "HLC".

Server: branding package + boot log; auth, invite, admin_users error
strings; courts/offices/models comments; mail templates thread
{{.Firm}} via injected payload default. Files handler keeps the
upstream "HL Patents Style.dotm" path (must match mWorkRepo's blob
name) but renders the user-visible DownloadName from branding.Name.

Frontend: branding.ts read via Bun.build define so process.env.FIRM_NAME
is statically substituted into client bundles (no runtime process
reference); index/login/downloads/kostenrechner/Sidebar/ProjectFormFields
and every i18n.ts string templated against ${FIRM}.

ALLOWED_EMAIL_DOMAINS whitelist intentionally untouched — email
domains and display name rotate independently.

Verified: go build/vet/test clean; bun run build clean; FIRM_NAME=Acme
override produces "Acme" in HTML and JS bundles end-to-end.
2026-04-28 22:44:06 +02:00

32 lines
1.8 KiB
TypeScript

// frontend/src/branding.ts — single source of truth for the firm name
// Paliad's UI renders. Mirrors internal/branding/firm.go on the server.
//
// At build time this resolves twice:
// 1. In the server-side render path (build.ts → renderXxx() returning HTML)
// Bun is running under Node, so process.env.FIRM_NAME is the real env
// var the deployer set; this file is loaded as a regular ESM module.
// 2. In the bundled client modules (e.g. client/i18n.ts) Bun.build replaces
// `process.env.FIRM_NAME` with a string literal via the `define` option
// configured in build.ts. Browsers never see process.env — every
// reference is statically substituted before the bundle is emitted.
//
// Both paths default to "HLC" when FIRM_NAME is unset.
//
// IMPORTANT: do NOT guard the read with `typeof process !== "undefined"` or
// any check on `process` itself. The minifier rewrites that guard into a
// short-string lexical comparison (`typeof process < "u"`) which evaluates
// false in the browser and would short-circuit the value back to "HLC" even
// when define has substituted the env var. The bare `process.env.FIRM_NAME`
// reference is only safe because build.ts's `define` rewrites it away
// completely for browser bundles.
//
// Why a runtime constant rather than i18n placeholder substitution: every
// Paliad surface (HTML title, hero headline, email body, PDF footer) has the
// firm name baked in literally; threading {{firm}} placeholders + a
// formatter through every t() call would be a far larger churn for the same
// firm-agnostic outcome. Re-deploying with FIRM_NAME=Acme rebuilds every
// asset with the new name in one step.
const RAW: string = (process.env.FIRM_NAME ?? "").trim();
export const FIRM: string = RAW !== "" ? RAW : "HLC";