---
title: "Client Knowledge: Barclays"
description: "Barclays-specific context: projects, tech constraints, deployment quirks, and lessons learned"
tags: [client-knowledge, barclays]
created: 2026-04-27
updated: 2026-04-28
---

# Client Knowledge: Barclays

## Key Takeaways

- Two active projects: Mod Comms (GCP, multi-agent AI) and Banner Builder (optical-dev, React+FastAPI)
- Barclays requires strict brand compliance — logo versions matter, Barclays design tokens used in UI
- GCP deployment = no WebSockets — REST polling is mandatory for Mod Comms
- Banner Builder uses Zustand for workflow state management (journey store pattern)

---

## Projects

| Project | Server | Stack | Status | Purpose |
|---------|--------|-------|--------|---------|
| [[01 Projects/modcomms/Mod Comms\|Mod Comms]] | GCP | FastAPI + React + Gemini + PostgreSQL | active | AI proof review — compliance/brand/tone/channel checks |
| [[01 Projects/Barclays-banner-builder/Barclays Banner Builder\|Banner Builder]] | optical-dev | FastAPI + React + PostgreSQL + Docker | active | AI banner generation tool — Brief → Variants → Edit → Export |

---

## Mod Comms — Key Facts

**What it does:** Upload proof (image/PDF) → 4 AI agents analyze in parallel → lead agent synthesizes verdict

**4 agents:** Legal compliance, Brand adherence, Tone of Voice, Channel suitability

**AI:** Google Gemini Pro (primary) + Flash (fallback) — chosen for GCP co-location

**Critical incident (2026-03-18):** WebSocket connections dropped at 30s on GCP LB → switched to REST polling. See [[wiki/architecture/gcp-deployment-lb-timeout|gcp-deployment-lb-timeout]].

**Auth:** Azure AD (MSAL) — uses `DISABLE_AUTH=true` locally

**Dev start:**
```bash
# Backend
cd backend && uvicorn app.main:app --reload --host 0.0.0.0 --port 8000

# Frontend
cd frontend && npm install && npm run dev

# DB migrations
cd backend && alembic upgrade head
```

**Env vars (backend):**
```
GEMINI_API_KEY=
DATABASE_URL=postgresql+asyncpg://user:pass@localhost:5432/modcomms
AZURE_TENANT_ID=
AZURE_CLIENT_ID=
DISABLE_AUTH=true
```

---

## Banner Builder — Key Facts

**What it does:** AI-assisted banner creation. Workflow: Brief → Edit Variants → Banner Editor → Export CSV/PDF

**Workflow state:** Managed with Zustand `journey store` — backward navigation allowed, forward steps grayed out until completed. See [[wiki/concepts/export-endpoint-filter-pattern|export-endpoint-filter-pattern]].

**Export quirk:** PDF/CSV exports must receive `variant_ids` from frontend — backend cannot infer selection. Always pass explicitly.

**Deploy:** optical-dev at `/barclays-banner-builder/` subpath. Deploy via `bash deploy.sh` on server.

**Apache config:** Barclays Include fragment at `/opt/barclays-banner-builder/deploy/apache-barclays.conf`. Port: 8010.

**Critical incident (2026-04-17):** Apache Include directive ordering — Banner Builder's conf was loading after hp-prod-tracker's catch-all `ProxyPass / http://...`, which intercepted all requests. Fixed by reordering Include lines in vhost config.

**Stack:**
- Frontend: React + TypeScript + Vite + Zustand
- Backend: FastAPI + Python + Alembic + PostgreSQL
- Auth: Azure AD (MSAL)
- Deploy: Docker Compose + Apache subpath

---

## Brand Requirements

- Logo versions matter — track which version is active (`v4`, `v5`, `v6`)
- Barclays design tokens used in UI (Zustand journey stepper used Barclays color tokens)
- Export outputs go to OMG media booking system — format must be exact

---

## 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)

QA session on barclays-banner-builder surfaced three non-obvious bugs. All three share a common trait: they fail silently rather than throwing a clear error.

### DB Seed Silent Skip

Seed scripts using `INSERT IF NOT EXISTS` or `get_or_create` silently skip existing rows. If a test/demo user was created before the seed ran (e.g., with a different password), the seed never updates it. Auth fails at login with no error in logs — the user row simply has the wrong password.

**Fix:** Always verify actual DB state when auth fails unexpectedly after seeding:
```sql
SELECT email, created_at FROM users WHERE email = 'seed@example.com';
-- If it exists with old data, DELETE and re-run seed, or UPDATE manually
```

### Zustand Async Hydration Bug

`ConversationLanding` fired an API call on mount before the Zustand auth store had hydrated from localStorage. On first render, `token` was `null` (initial state) → API call sent without auth header → 401 → redirect to login, even though the user was logged in.

**Fix:** Gate all auth-dependent API calls behind `hasHydrated`:
```typescript
const { token, hasHydrated } = useAuthStore()
useEffect(() => {
  if (!hasHydrated) return
  fetchData()
}, [hasHydrated, token])
```

See [[wiki/concepts/zustand-async-hydration]] for full pattern.

### Pydantic Model/Dict Interface Bug in tasks.py

`refine_variant_copy` Celery task was written to accept a `dict` but was called with a `BannerCopy` Pydantic model. Every `.get("field")` call on the Pydantic object returned `None` silently — the task continued running with all-null inputs, hanging the AI refinement pipeline without raising an exception.

**Fix:** Convert at call site: `task.delay(banner_copy.model_dump(), ...)` or update the function to accept the Pydantic model directly.

See [[wiki/concepts/pydantic-model-dict-interface]] for full pattern.

### LoginPage Hardcoded Redirect

`LoginPage` had a hardcoded redirect to `/brief` instead of `/` in the auth success handler. This caused the entire post-login flow to break for users who should land on the home route. Always use a configurable redirect target (e.g., `location.state?.from` or a constant) rather than a hardcoded path.

---

## Related

- [[wiki/architecture/gcp-deployment-lb-timeout|gcp-deployment-lb-timeout]] — WebSocket → REST polling
- [[wiki/architecture/optical-dev-server-deploy|optical-dev-server-deploy]] — Banner Builder deployment
- [[wiki/tech-patterns/python-ai-agents|python-ai-agents]] — multi-agent pattern used in Mod Comms
- [[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)
