Live App →

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}
  • baseUrl is read from config.apiBaseUrl (defaults to studio-backend-dev.centricitywealth.tech).
  • version defaults to v1 via config.apiPrefix. Callers can override by passing apiVersion: 'v2' in the request options.
  • path is 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:

  1. On first call, it dynamically imports useAuthStore from @/stores/auth-store.
  2. It reads accessToken from the store’s in-memory state via useAuthStore.getState().accessToken.
  3. The token is injected as a Bearer header on every outbound request (unless skipAuth is 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