# Branches in this repo ## `main` — the dev workspace Every feature, every client's code, all in one place — this is where you work day to day. **`main` is never pushed to a client's GitLab directly** — it's the source a client branch gets cut from. ## `client/` — one per client, generated, not edited Produced by: ```bash scripts/export_client.sh ``` Flags are the `VITE_ALLOW_ACCESS_` suffixes already used in `.env.example`: `EFFICIENCY`, `PFI`, `RELIABILITY`, `RISK_MATRIX`, `LCCA`, `RBD`, `OH`, `IRCFA`, `AHM`. Auth, the dashboard shell, admin, and ops-dock have no flag and always ship. Each export is `main`, with: - `.env.example`'s `VITE_ALLOW_ACCESS_*` defaults set so a plain `cp .env.example .env` on the client's side turns on only what they licensed, - no `Jenkinsfile` (client has their own CI), no `memory/` (dev notes), no `ref/` (internal PRD/screenshots), no `scripts/`. **These branches are disposable — never hand-edit them.** The script does `git checkout -B client/ main`, so re-running it regenerates the branch from scratch every time. If a client needs a hotfix that shouldn't wait for the next full export, make the fix on `main` first, then re-run the export. Pushing to the client's GitLab is a separate, explicit step (the script prints the command, or pass `--push ` to do it inline): ```bash git push client/:main ``` ### What the flags do and don't do — read this before relying on them Unlike the backend, **this does not remove any code from the repo.** `src/router/index.tsx` statically imports every page under `src/pages/` regardless of flags — they're still individually code-split via `lazy()` (so a page nobody licensed isn't in anyone's initial JS payload), but the source is still sitting in the export, reachable by anyone with repo access. What a disabled flag *does* block, via `src/router/FeatureGate.tsx`: every route under that domain — both the flat `/xxx-app/*` paths and the `/unit/:unitId/xxx/*` paths, plus its `/landing/*` teaser page where one exists — renders `UnderConstructionPage` instead. `FeatureGate` wraps the layout route's `element`, not something inside it, so the real layout component (and whatever data-fetching hooks it kicks off on mount) is never mounted at all when disabled, and there's no `` in the tree for any child route to render into — direct URL navigation to a sub-page hits the same placeholder as the domain's root. So a client *can't see* a feature they weren't sold, even by guessing/typing the URL. They just aren't prevented from finding it in the repo's source if they have repo access at all — that's the part that would need the bigger job below. If you need the frontend repo itself to not contain another client's page source at all (not just block them from seeing it rendered), that's a bigger job: `router/index.tsx`'s route tree would need to be filtered per export the way `config/urls.py` is on the backend, and every page under an unlicensed feature would need to come out of `src/pages/` and `src/features/` too. Not done here — flag if you want it built. ### Known gap: hardcoded branding `public/Logo_PLN.png`, `Logo_PLN.svg`, `rcfa-app/logo-tjb.png` and similar are referenced directly (by that literal filename) from ~16 files — nav sidebars, the login page, PDF/Excel report generators. `export_client.sh` prints a warning when these are still present in an export, but does not attempt to fix it: swapping branding per client needs those references replaced with a generic path (e.g. `/branding/logo.png`) on `main` first, the same way `config/settings_local.py` only works on the backend because `main` already has a hook pointed at it. Until that refactor happens, every client export currently ships the same PLN/TJB branding and plant-specific copy (see also `src/pages/unit-3`, and `SchematicName`-style hardcoding on the backend) regardless of who it's actually for. ## `overlay/` — one per client, permanent, hand-maintained For whatever's unique to one client and genuinely not shared with any other. Created with: ```bash scripts/new_client_overlay.sh ``` This is an **orphan branch** — no shared history with `main`, no shared history with any other client's overlay. Nobody working on `overlay/client-b` ever has a reason to check out `overlay/client-a`; the two are as separate as if they were different repos. An overlay may only ever *add* files, never edit one that also exists on `main`. Three extension points exist, all the same shape — a path that doesn't exist on `main`, discovered via `import.meta.glob`, which resolves to an empty object when nothing matches, so each is always safe to be absent: - **Routes** — `src/router/overlay/.tsx`, picked up by `src/router/index.tsx` via `import.meta.glob("./overlay/*.tsx")`. Export `routes: RouteObject[]`. `scripts/new_client_overlay.sh` scaffolds this one with an empty array — safe by construction, since an empty array adds nothing until you fill it in. - **Layout/shell** — `src/shared/components/layouts/overlay/AppLayout.tsx`, picked up by `src/shared/components/layouts/AppLayout.tsx` (every domain's `*LayoutRoute` imports `AppLayout` from there, never `DefaultAppLayout` directly, so this one file swaps the whole shell — sidebar, navbar, colors — everywhere at once). Default export, same props as `DefaultAppLayout`. - **Theme** — `src/styles/overlay/*.css`, loaded by `src/main.tsx` after `index.css`, so redefining `:root`/`.dark`'s CSS custom properties (`--primary`, `--background`, etc.) there wins the cascade. The layout and theme hooks are **not** scaffolded with a placeholder file the way routes are, and that's deliberate: an empty `routes` array has no effect, but the layout/theme hooks activate the instant a matching file exists at all, regardless of content. Only add one once you have a real replacement. Also allowed, same "new path only" rule: - New components/pages that an overlay route or layout references. - New static assets under a path `main` doesn't already use (see the branding gap above for why an *existing* asset path isn't safe to override yet). That constraint isn't arbitrary: `client/` is regenerated from scratch on every export, so `.env.example` and every existing file are rewritten every single time. An overlay branch has no commit in common with whatever `client/` looks like *this* time, so if it touched a file the export also touches, every future export would hit the same merge conflict forever. Sticking to new, untouched paths sidesteps that entirely — a merge that only adds files can't conflict with content it never shared. `export_client.sh` merges `overlay/` into the freshly-built `client/` automatically, if that branch exists, right before the final guard — so overlay content gets the same leak check as everything else.