diff --git a/wiki/_master-index.md b/wiki/_master-index.md
index bfdfb1c..7fc75e3 100644
--- a/wiki/_master-index.md
+++ b/wiki/_master-index.md
@@ -23,7 +23,7 @@ This 3-hop pattern works for hundreds of articles without vector search.
| [[wiki/tech-patterns/_index\|tech-patterns/]] | Recurring tech stacks: FastAPI, React/Vite, Next.js, Azure AD, AI, Box, One2Edit, Redis/Celery, cost-tracker | 15 |
| [[wiki/architecture/_index\|architecture/]] | Cross-cutting architectural patterns: Docker Compose, multi-agent AI, GCP timeout, RAG, hotfolder, optical-dev deploy, cost-tracker, new-project checklist, troubleshooting playbooks, ADR log | 10 |
| [[wiki/client-knowledge/_index\|client-knowledge/]] | Per-client notes for Ford, H&M, L'Oréal, Barclays, Ferrero, 3M | 6 |
-| [[wiki/concepts/_index\|concepts/]] | Atomic knowledge extracted from Claude Code sessions | 61 |
+| [[wiki/concepts/_index\|concepts/]] | Atomic knowledge extracted from Claude Code sessions | 62 |
| [[wiki/connections/_index\|connections/]] | Cross-cutting insights linking 2+ concepts: FastAPI+Azure AD+Docker trinity, AI→cost-tracker, Apache+Vite basePath, GCP→REST polling, Box+hotfolder, Docker DNS+AdGuard | 9 |
| [[wiki/qa/_index\|qa/]] | Filed answers to queries (saved with `--file-back`) | 0 |
| [[wiki/homelab/_index\|homelab/]] | Self-hosted infra: Proxmox install, IOMMU/PCI passthrough, hypervisor setup, budget builds, HP Elitedesk G3, Homarr API + Apps + Boards + Certificates + Integrations + Settings + Tasks + AdGuard + Clock + Docker Stats + Docker Integration + Download Client + Firewall + Proxmox Integration + Radarr + Readarr + Sonarr + Bookmarks + Calendar + Icons + App Widget + Weather + GitHub + Nextcloud + qBittorrent + RSS Feed + Speedtest Tracker + System Health Monitoring + System Resources + Services Map + Media Stack | 39 |
diff --git a/wiki/client-knowledge/barclays.md b/wiki/client-knowledge/barclays.md
index 4ecab09..45d6bae 100644
--- a/wiki/client-knowledge/barclays.md
+++ b/wiki/client-knowledge/barclays.md
@@ -91,6 +91,23 @@ DISABLE_AUTH=true
---
+## Banner Builder UI Rebrand (2026-04-28)
+
+The banner builder UI was rebranded from the Barclays design system to the **Oliver Modcomms design system**. Visual layer changed; core functionality unchanged.
+
+**What changed:**
+- Tailwind tokens renamed from `barclays-*` → `oliver-*`
+- Layout: horizontal nav → dark vertical sidebar (`w-[220px]`), matching the Modcomms pattern
+- Theme colour picker added to both `BannerEditor` and `VariantsGrid` views; variants have a `theme` enum: `navy | sky-blue | yellow | lime | teal`
+- Logo: placeholder `CopyGenBannerAgent_RFA.png` generated via `sips` (PIL not available on macOS); real CopyGen/Oliver asset needed
+
+**What did NOT change (by design):**
+- Internal localStorage key names (`barclays-*`) intentionally kept to avoid invalidating in-flight user sessions — see [[wiki/concepts/localstorage-key-migration-rebrand|localstorage-key-migration-rebrand]] for why and how to handle this in a future release
+- AI Refine/Improve box only mutates copy text fields — cannot change visual theme/colours (by architecture)
+- Barclays brand hex codes (`#00AEEF`, `#00395D`, etc.) remain correct in `tailwind.config.ts` as of commit `47b3f12`
+
+---
+
---
## Lessons from banner-builder (2026-04-28)
@@ -144,3 +161,4 @@ See [[wiki/concepts/pydantic-model-dict-interface]] for full pattern.
- [[wiki/concepts/export-endpoint-filter-pattern|export-endpoint-filter-pattern]] — variant_ids in exports
- [[wiki/concepts/zustand-async-hydration]] — Zustand hydration timing bug (Banner Builder)
- [[wiki/concepts/pydantic-model-dict-interface]] — Pydantic vs dict silent failure (Banner Builder tasks.py)
+- [[wiki/concepts/localstorage-key-migration-rebrand]] — localStorage key migration after rebrand (Banner Builder 2026-04-28)
diff --git a/wiki/concepts/_index.md b/wiki/concepts/_index.md
index c9c8ad3..0624eeb 100644
--- a/wiki/concepts/_index.md
+++ b/wiki/concepts/_index.md
@@ -69,5 +69,7 @@
| [[wiki/concepts/zustand-async-hydration]] | Zustand persist hydrates localStorage asynchronously — components must gate API calls behind hasHydrated or token will be null on first render | daily/2026-04-28.md | 2026-04-28 |
| [[wiki/concepts/pydantic-model-dict-interface]] | Pydantic model passed where dict expected — .get() returns None silently instead of raising; use isinstance check or model_dump() at boundary | daily/2026-04-28.md | 2026-04-28 |
+| [[wiki/concepts/localstorage-key-migration-rebrand]] | localStorage key migration after renaming storage keys in a rebrand — session loss, Zustand persist name, migration script pattern | daily/2026-04-28.md | 2026-04-28 |
+
diff --git a/wiki/concepts/localstorage-key-migration-rebrand.md b/wiki/concepts/localstorage-key-migration-rebrand.md
new file mode 100644
index 0000000..c208b89
--- /dev/null
+++ b/wiki/concepts/localstorage-key-migration-rebrand.md
@@ -0,0 +1,117 @@
+---
+title: "Frontend — localStorage Key Migration When Rebranding"
+aliases: [localstorage-key-migration, storage-key-rename, rebrand-session-loss, zustand-persist-rename]
+tags: [frontend, localstorage, zustand, react, rebrand, auth, migration]
+sources:
+ - "daily/2026-04-28.md"
+created: 2026-04-28
+updated: 2026-04-28
+---
+
+# Frontend — localStorage Key Migration When Rebranding
+
+When a frontend app is rebranded and its localStorage key names are changed (e.g., from `barclays-auth-storage` to `oliver-auth-storage`), all existing user sessions are silently invalidated. The old keys remain in the browser's localStorage but are no longer read — on next load, the app finds no data under the new key name and treats the user as unauthenticated.
+
+## Key Points
+
+- **Old localStorage keys persist forever** — renaming the key in code does not migrate existing browser data; the old key sits orphaned, the new key is empty
+- **All users are effectively logged out** after a key rename deploy — no error, no warning; the app simply starts fresh as if the user never logged in
+- **Zustand `persist` middleware uses a `name` option as the localStorage key** — changing this name causes the same session loss
+- **Fix: migration script at app entry point** — on load, check for old key, copy data to new key, delete old key; runs once per user, transparent
+- **Decision to keep internal keys**: in barclays-banner-builder, internal session keys (`barclays-*`) were intentionally NOT renamed to avoid invalidating in-flight sessions during the rebrand; only UI-visible branding strings were changed
+
+## Details
+
+### Why This Happens
+
+Browsers store localStorage data keyed by the exact string used in `localStorage.setItem(key, value)`. When a Zustand `persist` store has `name: "barclays-auth-storage"`, Zustand writes to and reads from that exact string. When the name is changed to `"oliver-auth-storage"`, Zustand looks for the new key — finds nothing — initialises with the default (unauthenticated) state. The old `barclays-auth-storage` entry remains in the browser forever.
+
+This is not a bug in Zustand or localStorage — it's the expected behavior of key-value storage. The same problem occurs with any keyed storage: sessionStorage, IndexedDB, cookie names, etc.
+
+### Symptom
+
+After a rebrand deploy:
+- Users who were logged in are suddenly redirected to the login page
+- No error in the console or network tab
+- `Application → Local Storage` in DevTools shows both old and new keys: old with data, new empty
+- Logging in again works fine (new key is populated); old key remains as clutter
+
+### The Migration Script
+
+Add to `main.tsx` (or the app's entry point), before the React tree mounts:
+
+```typescript
+// main.tsx — runs once before React mounts
+function migrateStorage() {
+ const OLD_KEY = "barclays-auth-storage";
+ const NEW_KEY = "oliver-auth-storage";
+
+ const oldData = localStorage.getItem(OLD_KEY);
+ if (oldData && !localStorage.getItem(NEW_KEY)) {
+ // Copy old data to new key
+ localStorage.setItem(NEW_KEY, oldData);
+ // Clean up old key
+ localStorage.removeItem(OLD_KEY);
+ console.log("[migration] Migrated localStorage from", OLD_KEY, "to", NEW_KEY);
+ }
+}
+
+migrateStorage();
+
+ReactDOM.createRoot(document.getElementById("root")!).render();
+```
+
+This migration is safe to ship before or alongside the key rename: if old data exists and new key is empty, migrate; otherwise no-op.
+
+### When to Keep Old Keys (No Migration)
+
+In some cases, deliberately not migrating is the right choice:
+
+- **During a deploy with in-flight user sessions**: renaming now would log everyone out immediately; decide whether a one-time logout is acceptable
+- **When the stored data format changed** during the rebrand: migrating old-format data into a new-format key would corrupt state; better to let users re-login with a clean slate
+- **Internal-only keys** (e.g., cache keys, feature flags): user impact is minimal, no migration needed
+
+barclays-banner-builder 2026-04-28 decision: `barclays-*` session keys kept unchanged because the rebrand was cosmetic (visual redesign only), and invalidating active work sessions mid-project was unacceptable.
+
+### Zustand-Specific Pattern
+
+For Zustand `persist` stores, the migration can also be done inside the store's `onRehydrateStorage` callback:
+
+```typescript
+// Alternative: migrate inside Zustand store initialisation
+export const useAuthStore = create()(
+ persist(
+ (set) => ({ /* ... */ }),
+ {
+ name: "oliver-auth-storage", // ← new key
+ onRehydrateStorage: () => {
+ // Check for old key and migrate if needed
+ const old = localStorage.getItem("barclays-auth-storage");
+ if (old) {
+ localStorage.setItem("oliver-auth-storage", old);
+ localStorage.removeItem("barclays-auth-storage");
+ }
+ },
+ }
+ )
+);
+```
+
+This is neater for Zustand stores but couples the migration to the store initialisation rather than the app entry point.
+
+### Other Storage Types
+
+The same pattern applies to:
+- `sessionStorage` — same API, same key-rename problem (though sessions are cleared on tab close anyway)
+- Cookie names — renaming a session cookie name logs all users out; same migration approach applies server-side
+- IndexedDB database names — renaming requires schema migration via `onupgradeneeded`
+
+## Related Concepts
+
+- [[wiki/concepts/zustand-async-hydration]] — Zustand persist hydration timing; keys are the configuration point where naming matters
+- [[wiki/concepts/memory-compiler-mac-migration]] — similar "config key mismatch" pattern: hooks reference paths/identifiers that break when the environment changes
+- [[wiki/client-knowledge/barclays]] — barclays-banner-builder rebrand context where this was encountered
+
+## Sources
+
+- [[daily/2026-04-28.md]] — barclays-banner-builder UI rebrand from Barclays design tokens to Oliver Modcomms design system; `barclays-*` localStorage keys intentionally kept to avoid session invalidation; decision documented as tech debt item (migrate to `oliver-*` in a future release with migration script)
diff --git a/wiki/log.md b/wiki/log.md
index 0db2b18..c72c42d 100644
--- a/wiki/log.md
+++ b/wiki/log.md
@@ -3,6 +3,11 @@
+## [2026-04-29T21:00:00+01:00] compile | 2026-04-28.md (pass 3)
+- Source: daily/2026-04-28.md
+- Articles created: [[wiki/concepts/localstorage-key-migration-rebrand]]
+- Articles updated: [[wiki/client-knowledge/barclays]] (added Banner Builder UI rebrand section: Oliver Modcomms design system, vertical sidebar, theme picker, localStorage key intentionally kept)
+
## [2026-04-28T23:15:00+01:00] compile | 2026-04-28.md (pass 2)
- Source: daily/2026-04-28.md
- Articles created: (none — all primary knowledge captured in pass 1)