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