Architecture
BFF gateway pattern, microservice routing, and frontend data flow
Architecture
Solutions Architecture
Sentinel is an enterprise AI platform for wealth management. The system processes financial documents through AI pipelines and provides conversational intelligence — all delivered through a single Next.js frontend that communicates exclusively with one BFF (Backend-for-Frontend) gateway.
graph TB
subgraph Clients["Client Layer"]
WM["Wealth Managers"]
RMs["Relationship Managers"]
PO["Private Offices"]
end
subgraph Frontend["Frontend — sentinel.centricitywealth.tech"]
NextJS["Next.js 16 App<br/>11 Routes · 3 Stores"]
end
subgraph BFF["BFF Gateway — studio-backend-dev"]
Studio["Studio Backend<br/>Auth · RBAC · Routing<br/>Envelope · Discovery"]
end
subgraph Services["Microservice Layer"]
Nexus["Nexus<br/>Document Intelligence<br/>10-Stage AI Pipeline"]
Zen["Zen<br/>Financial Chat<br/>RAG · Web · Guards"]
Agents["Agents<br/>Agentic Workflows<br/>Autonomous Tasks"]
end
subgraph Infra["Cloud Infrastructure"]
S3["S3<br/>Document Storage"]
LLM["LLM APIs<br/>GPT-4o · Claude"]
Search["Web Search<br/>Market Data"]
end
Clients --> Frontend
Frontend --> BFF
BFF --> Services
Nexus --> S3
Nexus --> LLM
Zen --> LLM
Zen --> Search
Agents --> Zen
class WM,RMs,PO neutral
class NextJS primary
class Studio dark
class Nexus,Zen,Agents highlight
class S3,LLM,Search neutral
classDef primary fill:#dbeafe,stroke:#3b82f6,color:#1e293b
classDef secondary fill:#e0e7ff,stroke:#6366f1,color:#1e293b
classDef success fill:#d1fae5,stroke:#10b981,color:#1e293b
classDef warning fill:#fef3c7,stroke:#f59e0b,color:#1e293b
classDef danger fill:#fee2e2,stroke:#ef4444,color:#1e293b
classDef neutral fill:#f1f5f9,stroke:#64748b,color:#1e293b
classDef highlight fill:#fae8ff,stroke:#a855f7,color:#1e293b
classDef dark fill:#1e293b,stroke:#334155,color:#f8fafc
System Overview
Sentinel follows a strict BFF (Backend-for-Frontend) pattern. Every API call originating from the browser is routed through the Studio Backend middleware layer at studio-backend-dev.centricitywealth.tech. The frontend never calls microservice backends (Nexus, Zen, Agents) directly.
This design provides several guarantees:
- Single origin – avoids CORS complexity; the browser only talks to one host.
- Auth centralisation – Studio validates JWT tokens and can refresh/reject them in one place.
- Response normalisation – Studio wraps all responses in a
ResponseEnvelope<T>so the frontend receives a consistent shape. - Service discovery – the frontend is decoupled from backend deployment topology; Studio resolves service URLs internally.
Request Flow
graph TB
Browser["Browser<br/>Next.js 16"] --> ApiClient["api-client.ts<br/>URL Builder + JWT"]
ApiClient --> StudioBFF["Studio Backend BFF"]
StudioBFF --> NexusGW["nexus-gateway.ts<br/>32 Explicit Routes"]
StudioBFF --> ZenGW["zen-gateway.ts<br/>Wildcard Proxy"]
StudioBFF --> DashGW["dashboard.ts<br/>Aggregated Endpoint"]
NexusGW --> NexusBE["Nexus Backend"]
ZenGW --> ZenBE["Zen Backend"]
ZenGW --> AgentsBE["Agents Backend"]
ServerActions["Server Actions<br/>auth.ts"] --> StudioBFF
ServerActions -.->|HttpOnly Cookies| Browser
class Browser primary
class ApiClient secondary
class StudioBFF dark
class NexusGW,ZenGW,DashGW highlight
class NexusBE,ZenBE,AgentsBE neutral
class ServerActions secondary
classDef primary fill:#dbeafe,stroke:#3b82f6,color:#1e293b
classDef secondary fill:#e0e7ff,stroke:#6366f1,color:#1e293b
classDef success fill:#d1fae5,stroke:#10b981,color:#1e293b
classDef warning fill:#fef3c7,stroke:#f59e0b,color:#1e293b
classDef danger fill:#fee2e2,stroke:#ef4444,color:#1e293b
classDef neutral fill:#f1f5f9,stroke:#64748b,color:#1e293b
classDef highlight fill:#fae8ff,stroke:#a855f7,color:#1e293b
classDef dark fill:#1e293b,stroke:#334155,color:#f8fafc
Document Processing Data Flow
This diagram traces a financial document from upload to structured export — the core value proposition of Sentinel.
graph LR
subgraph Upload["1. Upload"]
PDF["PDF File"]
Upload_API["POST /nexus/upload"]
end
subgraph Pipeline["2. AI Pipeline — 10 Stages"]
Classify["Classify"]
Layout["Layout<br/>Extract"]
Structure["Document<br/>Structure"]
Entity["Entity<br/>Detect"]
Metadata["Metadata<br/>Extract"]
Info["Info<br/>Extract"]
Validate["Validate"]
Store["Store"]
end
subgraph Output["3. Structured Output"]
Securities["Securities<br/>Holdings"]
AssetAlloc["Asset<br/>Allocation"]
AcctMeta["Account<br/>Metadata"]
Export["Excel/JSON<br/>Export"]
end
PDF --> Upload_API
Upload_API --> Classify
Classify --> Layout
Layout --> Structure
Structure --> Entity
Entity --> Metadata
Metadata --> Info
Info --> Validate
Validate --> Store
Store --> Securities
Store --> AssetAlloc
Store --> AcctMeta
Securities --> Export
AssetAlloc --> Export
AcctMeta --> Export
class PDF,Upload_API primary
class Classify,Layout,Structure,Entity,Metadata,Info,Validate,Store secondary
class Securities,AssetAlloc,AcctMeta success
class Export success
classDef primary fill:#dbeafe,stroke:#3b82f6,color:#1e293b
classDef secondary fill:#e0e7ff,stroke:#6366f1,color:#1e293b
classDef success fill:#d1fae5,stroke:#10b981,color:#1e293b
classDef warning fill:#fef3c7,stroke:#f59e0b,color:#1e293b
classDef danger fill:#fee2e2,stroke:#ef4444,color:#1e293b
classDef neutral fill:#f1f5f9,stroke:#64748b,color:#1e293b
classDef highlight fill:#fae8ff,stroke:#a855f7,color:#1e293b
classDef dark fill:#1e293b,stroke:#334155,color:#f8fafc
Frontend Module Architecture
The frontend is organised into clean layers. Each layer has a single responsibility and communicates only with the layer directly below it.
graph TB
subgraph Pages["Pages — App Router"]
Dashboard["/dashboard"]
NexusPages["/nexus/*"]
ZenPages["/zen/*"]
AuthPages["/login · /register"]
end
subgraph Hooks["Hooks — React Query"]
UseDash["useDashboard"]
UseProc["useProcessing"]
UseResults["useResults"]
UseChat["useChat"]
end
subgraph API["API — Service Modules"]
NexusAPI["nexus.ts"]
ZenAPI["zen.ts"]
DashAPI["dashboard.ts"]
end
subgraph Gateway["Gateway — Path Prefixing"]
NexusGW["nexus-gateway.ts<br/>/nexus"]
ZenGW["zen-gateway.ts<br/>/zen"]
end
subgraph Client["Client — Studio API"]
ApiClient["api-client.ts<br/>URL · Auth · Envelope"]
end
subgraph Store["State — Zustand"]
AuthStore["auth-store"]
NexusStore["nexus-store"]
ZenStore["zen-store"]
end
Pages --> Hooks
Hooks --> API
API --> Gateway
Gateway --> Client
Client -.-> Store
Hooks -.-> Store
class Dashboard,NexusPages,ZenPages,AuthPages primary
class UseDash,UseProc,UseResults,UseChat secondary
class NexusAPI,ZenAPI,DashAPI highlight
class NexusGW,ZenGW dark
class ApiClient dark
class AuthStore,NexusStore,ZenStore success
classDef primary fill:#dbeafe,stroke:#3b82f6,color:#1e293b
classDef secondary fill:#e0e7ff,stroke:#6366f1,color:#1e293b
classDef success fill:#d1fae5,stroke:#10b981,color:#1e293b
classDef warning fill:#fef3c7,stroke:#f59e0b,color:#1e293b
classDef danger fill:#fee2e2,stroke:#ef4444,color:#1e293b
classDef neutral fill:#f1f5f9,stroke:#64748b,color:#1e293b
classDef highlight fill:#fae8ff,stroke:#a855f7,color:#1e293b
classDef dark fill:#1e293b,stroke:#334155,color:#f8fafc
Gateway Layer
The gateway layer sits between the consumer API modules (nexus.ts, zen.ts, dashboard.ts) and the Studio API client. Each gateway prepends a service prefix to the path so the BFF can route the request to the correct backend.
Gateway Routing Comparison
graph LR
subgraph Nexus["Nexus Gateway"]
N_Call["nexus.ts calls<br/>nexusRequest()"] --> N_GW["nexus-gateway.ts<br/>prepends /nexus"]
N_GW --> N_Route["32 Explicit Routes<br/>Must register each"]
end
subgraph Zen["Zen Gateway"]
Z_Call["zen.ts calls<br/>zenRequest()"] --> Z_GW["zen-gateway.ts<br/>prepends /zen"]
Z_GW --> Z_Route["Wildcard Proxy<br/>Any /zen/* works"]
end
class N_Call,N_GW secondary
class N_Route dark
class Z_Call,Z_GW highlight
class Z_Route success
classDef primary fill:#dbeafe,stroke:#3b82f6,color:#1e293b
classDef secondary fill:#e0e7ff,stroke:#6366f1,color:#1e293b
classDef success fill:#d1fae5,stroke:#10b981,color:#1e293b
classDef warning fill:#fef3c7,stroke:#f59e0b,color:#1e293b
classDef danger fill:#fee2e2,stroke:#ef4444,color:#1e293b
classDef neutral fill:#f1f5f9,stroke:#64748b,color:#1e293b
classDef highlight fill:#fae8ff,stroke:#a855f7,color:#1e293b
classDef dark fill:#1e293b,stroke:#334155,color:#f8fafc
nexus-gateway.ts
| Property | Value |
|---|---|
| File | app/src/lib/api/nexus-gateway.ts |
| Path prefix | /nexus |
| Routing strategy | 32 explicitly-defined routes – no wildcard catch-all |
| API versions | V1 (default) and V2 (pipeline process, progress) |
| Exports | nexusRequest<T>(), nexusUpload<T>() |
Because Nexus uses explicit route definitions in the middleware, any new backend endpoint must be registered in Studio before it becomes accessible. Unregistered paths return 404.
Key V2 endpoints:
POST /api/v2/nexus/pipeline/process– start async document processing (Route 9)GET /api/v2/nexus/status/process/{process_id}/progress– poll processing progress (Route 11)
zen-gateway.ts
| Property | Value |
|---|---|
| File | app/src/lib/api/zen-gateway.ts |
| Path prefix | /zen |
| Routing strategy | Wildcard proxy – all /zen/* paths are forwarded |
| Exports | zenRequest<T>(), zenUpload<T>(), zenStream(), agentsRequest<T>() |
The wildcard proxy means new Zen or Agents backend endpoints are automatically available without middleware changes. The Agents service is also routed through agentsRequest(), which prepends /agents and falls back gracefully if the service is not deployed.
dashboard.ts
| Property | Value |
|---|---|
| File | app/src/lib/api/dashboard.ts |
| Path prefix | none (top-level /dashboard) |
| Key endpoint | GET /api/v1/dashboard?period=all_time |
The dashboard endpoint is an aggregated BFF route – Studio assembles data from Nexus stats, Zen stats, platform metrics, document pipeline status, recent activity, and service health into a single response payload. This replaced an earlier pattern of four separate queries that frequently failed independently.
API Client
File: app/src/lib/studio/api-client.ts
The API client is the lowest-level HTTP module in the frontend. Every gateway and API function ultimately calls through it.
URL Construction
{baseUrl}/api/{version}/{path}
baseUrlis read fromconfig.apiBaseUrl(defaults tostudio-backend-dev.centricitywealth.tech).versiondefaults tov1viaconfig.apiPrefix. Callers can override by passingapiVersion: 'v2'in the request options.pathis the service-relative path (e.g.,/nexus/documents/,/zen/chat/).
ResponseEnvelope Unwrapping
Studio Backend wraps most responses in a standard envelope:
interface ResponseEnvelope<T> {
success: boolean;
result: T;
error?: string;
message: string;
}
The client automatically unwraps this envelope so callers receive T directly, not ResponseEnvelope<T>. This is the primary value-add of the Studio client over raw fetch.
Smart Envelope Detection
graph LR
Response["API Response"] --> Check{"Has boolean<br/>success field?"}
Check -->|Yes| Unwrap["Unwrap from<br/>ResponseEnvelope"]
Check -->|No| PassThrough["Return raw JSON<br/>as-is"]
Unwrap --> Caller["Caller gets T"]
PassThrough --> Caller
class Response primary
class Check warning
class Unwrap success
class PassThrough neutral
class Caller success
classDef primary fill:#dbeafe,stroke:#3b82f6,color:#1e293b
classDef secondary fill:#e0e7ff,stroke:#6366f1,color:#1e293b
classDef success fill:#d1fae5,stroke:#10b981,color:#1e293b
classDef warning fill:#fef3c7,stroke:#f59e0b,color:#1e293b
classDef danger fill:#fee2e2,stroke:#ef4444,color:#1e293b
classDef neutral fill:#f1f5f9,stroke:#64748b,color:#1e293b
classDef highlight fill:#fae8ff,stroke:#a855f7,color:#1e293b
classDef dark fill:#1e293b,stroke:#334155,color:#f8fafc
Not all responses are wrapped. The V2 progress endpoint and wildcard-proxied Zen paths may return raw JSON from the backend. The client detects whether a response is an envelope by checking for the presence of a boolean success field.
Additional Capabilities
| Capability | Method | Notes |
|---|---|---|
| File upload | uploadFile<T>() |
Multipart/form-data; does not set Content-Type so the browser can add the boundary |
| SSE streaming | streamRequest() |
Used by Zen chat; returns AbortController for cancellation |
| Auth injection | automatic | Reads JWT from Zustand in-memory state via getAccessToken() |
| 401 handling | throws AuthenticationError |
Caller (gateway) decides whether to retry or redirect to /login?expired=true |
Auth Model
Authentication uses a server-action + in-memory token pattern. The access token is never persisted to localStorage – it lives only in Zustand memory and is hydrated on each page load from an HttpOnly cookie set by the server action.
sequenceDiagram
participant B as Browser
participant SA as Server Actions
participant SB as Studio Backend
B->>SA: Login (email, password)
SA->>SB: POST /api/v1/auth/login
SB-->>SA: access_token + refresh_token + user
SA-->>B: Set HttpOnly Cookie
SA-->>B: Hydrate Zustand auth-store
Note over B: Token lives in memory only
B->>B: getAccessToken() from Zustand
B->>SB: GET /api/v1/nexus/... + Bearer token
SB-->>B: ResponseEnvelope T
Note over B,SB: On 401 — redirect to /login?expired=true
Security Architecture
graph TB
subgraph Browser["Browser — Client Side"]
Memory["Zustand Memory<br/>accessToken (ephemeral)"]
LS["localStorage<br/>user + isAuth ONLY"]
Cookie["HttpOnly Cookie<br/>Encrypted refresh"]
end
subgraph Server["Server Side"]
SA["Server Actions<br/>Cookie read/write"]
BFF["Studio BFF<br/>JWT · Token refresh"]
end
subgraph Protection["Security Measures"]
CORS["Single Origin<br/>No CORS needed"]
XSS["XSS Protection<br/>No token in storage"]
CSRF["HttpOnly Cookies<br/>Not readable by JS"]
Headers["Security Headers<br/>CSP · Referrer"]
end
Memory -->|Bearer header| BFF
Cookie -->|Auto-sent| SA
SA -->|Refresh flow| BFF
class Memory,LS danger
class Cookie primary
class SA,BFF dark
class CORS,XSS,CSRF,Headers success
classDef primary fill:#dbeafe,stroke:#3b82f6,color:#1e293b
classDef secondary fill:#e0e7ff,stroke:#6366f1,color:#1e293b
classDef success fill:#d1fae5,stroke:#10b981,color:#1e293b
classDef warning fill:#fef3c7,stroke:#f59e0b,color:#1e293b
classDef danger fill:#fee2e2,stroke:#ef4444,color:#1e293b
classDef neutral fill:#f1f5f9,stroke:#64748b,color:#1e293b
classDef highlight fill:#fae8ff,stroke:#a855f7,color:#1e293b
classDef dark fill:#1e293b,stroke:#334155,color:#f8fafc
Token Reading
The getAccessToken() function uses a lazy-import pattern to avoid circular dependencies:
- On first call, it dynamically imports
useAuthStorefrom@/stores/auth-store. - It reads
accessTokenfrom the store’s in-memory state viauseAuthStore.getState().accessToken. - The token is injected as a
Bearerheader on every outbound request (unlessskipAuthis set).
Zustand Persistence
The auth store uses Zustand’s persist middleware with skipHydration: true. Only user and isAuthenticated are persisted to localStorage['toolit-auth-v2']. The access token is explicitly excluded from persistence – it is hydrated by the UserHydration component on page load from the server-set cookie.
State Management
Sentinel uses three Zustand stores, each scoped to a specific domain:
graph LR
subgraph AuthStore["auth-store"]
A1["user"]
A2["accessToken"]
A3["isAuthenticated"]
end
subgraph NexusStore["nexus-store"]
N1["uploadFiles"]
N2["processIdMap"]
N3["activeJobIds"]
end
subgraph ZenStore["zen-store"]
Z1["sessions"]
Z2["messages"]
Z3["webSearchEnabled"]
end
AuthStore -->|"Persisted<br/>(no token)"| LS["localStorage"]
NexusStore -->|Ephemeral| Mem1["Memory only"]
ZenStore -->|Ephemeral| Mem2["Memory only"]
class A1,A2,A3 primary
class N1,N2,N3 secondary
class Z1,Z2,Z3 highlight
class LS,Mem1,Mem2 neutral
classDef primary fill:#dbeafe,stroke:#3b82f6,color:#1e293b
classDef secondary fill:#e0e7ff,stroke:#6366f1,color:#1e293b
classDef success fill:#d1fae5,stroke:#10b981,color:#1e293b
classDef warning fill:#fef3c7,stroke:#f59e0b,color:#1e293b
classDef danger fill:#fee2e2,stroke:#ef4444,color:#1e293b
classDef neutral fill:#f1f5f9,stroke:#64748b,color:#1e293b
classDef highlight fill:#fae8ff,stroke:#a855f7,color:#1e293b
classDef dark fill:#1e293b,stroke:#334155,color:#f8fafc
auth-store
| Property | Purpose |
|---|---|
| File | app/src/stores/auth-store.ts |
| Key state | user, accessToken, isAuthenticated, isHydrated |
| Persistence | localStorage['toolit-auth-v2'] – persists user and isAuthenticated only |
| Token storage | In-memory only (not persisted) |
nexus-store
| Property | Purpose |
|---|---|
| File | app/src/stores/nexus-store.ts |
| Key state | uploadFiles, activeJobIds, processIdMap, currentJob, jobs, filters, pagination |
| Persistence | None (ephemeral) |
| Notable | processIdMap maps job_id to process_id for V2 pipeline progress tracking |
zen-store
| Property | Purpose |
|---|---|
| File | app/src/stores/zen-store.ts |
| Key state | sessions, messages, activeSessionId, agents, webSearchEnabled, pendingPrompt |
| Persistence | None (ephemeral) |
| Notable | webSearchEnabled toggles web search for Zen chat queries (defaults to true) |
Frontend Route Architecture
All routes use the Next.js App Router with route groups for layout segmentation.
graph TB
subgraph Root["Root Layout"]
Root_Page["/ — Home"]
end
subgraph Auth["(auth) — No Nav Shell"]
Login["/login"]
Register["/register"]
end
subgraph Dashboard["(dashboard) — Full App Shell"]
Dash["/dashboard"]
Profile["/profile"]
subgraph NexusRoutes["Nexus Sub-Layout"]
NexusOverview["/nexus"]
NexusUpload["/nexus/upload"]
NexusDocs["/nexus/documents"]
NexusDetail["/nexus/docs/docId"]
end
subgraph ZenRoutes["Zen Sub-Layout"]
ZenLanding["/zen"]
ZenChat["/zen/chat/sessionId"]
end
end
Root_Page -->|Unauthenticated| Login
Root_Page -->|Authenticated| Dash
Dash --> NexusOverview
Dash --> ZenLanding
class Root_Page neutral
class Login,Register danger
class Dash,Profile primary
class NexusOverview,NexusUpload,NexusDocs,NexusDetail secondary
class ZenLanding,ZenChat highlight
classDef primary fill:#dbeafe,stroke:#3b82f6,color:#1e293b
classDef secondary fill:#e0e7ff,stroke:#6366f1,color:#1e293b
classDef success fill:#d1fae5,stroke:#10b981,color:#1e293b
classDef warning fill:#fef3c7,stroke:#f59e0b,color:#1e293b
classDef danger fill:#fee2e2,stroke:#ef4444,color:#1e293b
classDef neutral fill:#f1f5f9,stroke:#64748b,color:#1e293b
classDef highlight fill:#fae8ff,stroke:#a855f7,color:#1e293b
classDef dark fill:#1e293b,stroke:#334155,color:#f8fafc
| Route | Service | Auth | Layout Group | File |
|---|---|---|---|---|
/ |
– | Yes (redirect) | root | app/src/app/page.tsx |
/login |
Auth | No | (auth) |
app/src/app/(auth)/login/page.tsx |
/register |
Auth | No | (auth) |
app/src/app/(auth)/register/page.tsx |
/dashboard |
Dashboard | Yes | (dashboard) |
app/src/app/(dashboard)/page.tsx |
/nexus |
Nexus | Yes | (dashboard) |
app/src/app/(dashboard)/nexus/page.tsx |
/nexus/upload |
Nexus | Yes | (dashboard) |
app/src/app/(dashboard)/nexus/upload/page.tsx |
/nexus/documents |
Nexus | Yes | (dashboard) |
app/src/app/(dashboard)/nexus/documents/page.tsx |
/nexus/documents/[docId] |
Nexus | Yes | (dashboard) |
app/src/app/(dashboard)/nexus/documents/[docId]/page.tsx |
/zen |
Zen | Yes | (dashboard) |
app/src/app/(dashboard)/zen/page.tsx |
/zen/chat/[sessionId] |
Zen | Yes | (dashboard) |
app/src/app/(dashboard)/zen/chat/[sessionId]/page.tsx |
/profile |
Zen (users) | Yes | (dashboard) |
app/src/app/(dashboard)/profile/page.tsx |
The (auth) layout group provides a centred card layout with no navigation shell. The (dashboard) layout group provides the full app shell with sidebar navigation, header, and theme support.
Middleware Route Mapping
The Studio Backend exposes the following key routes used by the frontend:
| # | Method | Path | API Version | Description |
|---|---|---|---|---|
| 4 | GET | /api/v1/nexus/documents/ |
V1 | List all documents |
| 5 | GET | /api/v1/nexus/documents/{doc_id} |
V1 | Get document details |
| 8 | GET | /api/v1/nexus/documents/{doc_id}/extraction |
V1 | Get extraction results |
| 9 | POST | /api/v2/nexus/pipeline/process |
V2 | Start async processing |
| 11 | GET | /api/v2/nexus/status/process/{process_id}/progress |
V2 | Poll processing progress |
| 17 | GET | /api/v1/nexus/stats |
V1 | System statistics (ADMIN) |
| 22 | GET | /api/v1/nexus/doc-fetch/{job_id}?type=ORIGINAL |
V1 | Pre-signed S3 URL for PDF |
| – | GET | /api/v1/dashboard?period=all_time |
V1 | Aggregated dashboard data |
| – | GET | /api/v1/discovery/services/nexus/health |
V1 | Nexus health check |
| – | * | /api/v1/zen/* |
V1 | Wildcard proxy to Zen |
| – | * | /api/v1/agents/* |
V1 | Routed through Zen wildcard |
Tech Stack
| Technology | Version | Purpose |
|---|---|---|
| Next.js | 16.1 | React framework with App Router, Server Actions |
| TypeScript | 5 | Type safety across the codebase |
| Tailwind CSS | 4 | Utility-first CSS with design tokens |
| Zustand | 5.0 | Lightweight state management (3 stores) |
| TanStack React Query | 5.90 | Server state, caching, polling |
| Motion (Framer Motion) | 12.27 | Page transitions, micro-interactions |
| Three.js | 0.182 | 3D hologram on dashboard (via @react-three/fiber) |
| Recharts | 3.6 | Dashboard charts and data visualisation |
| React Markdown | 10.1 | Zen chat message rendering with GFM |
| Playwright | 1.57 | End-to-end testing framework |
| shadcn/ui | – | 15 Radix primitives (Dialog, Select, Tabs, Tooltip, etc.) |
| Zod | 4.3 | Schema validation for forms and API responses |
| React Hook Form | 7.71 | Form state management with Zod resolver |
| Sonner | 2.0 | Toast notifications |