Sentinel Frontend — Architecture
Next.js 16 App Router application serving two financial intelligence services: Nexus (Document Intelligence) and Zen (Financial AI Chat).
Live: https://sentinel-dev.centricitywealth.tech
Branch: uat
Tech Stack
| Layer |
Technology |
Version |
| Framework |
Next.js (App Router) |
16.1.1 |
| Runtime |
React |
19.2.3 |
| Language |
TypeScript |
^5 |
| Styling |
Tailwind CSS |
^4 |
| State |
Zustand |
^5.0.10 |
| Data Fetching |
TanStack React Query |
^5.90.16 |
| Animation |
Framer Motion |
^12.27.1 |
| Validation |
Zod |
^4.3.5 |
| Toasts |
Sonner |
^2.0.7 |
| Icons |
Lucide React |
^0.562.0 |
| E2E Testing |
Playwright |
(5 browser projects) |
Route Architecture
app/
├── page.tsx # / — Landing (public)
├── (auth)/
│ ├── layout.tsx # Server: redirects authenticated → /dashboard
│ ├── login/page.tsx # /login
│ └── register/page.tsx # /register
└── (dashboard)/
├── layout.tsx # Server: verifies session, hydrates user
├── page.tsx # /dashboard — Service selector
├── nexus/
│ ├── layout.tsx # Client: NexusSidebar + processing indicator
│ ├── page.tsx # /nexus — Documents + Analytics tabs
│ ├── dashboard/page.tsx # /nexus/dashboard → redirect → /nexus?tab=analytics
│ ├── upload/page.tsx # /nexus/upload
│ ├── history/page.tsx # /nexus/history
│ └── insights/[jobId]/page.tsx # /nexus/insights/{jobId}
└── zen/
├── layout.tsx # Client: ZenHeader + ChatSidebar + ContextPanel
├── page.tsx # /zen — Landing with conversation starters
├── chat/[sessionId]/page.tsx # /zen/chat/{sessionId}
├── agents/page.tsx # /zen/agents
├── documents/page.tsx # /zen/documents
├── search/page.tsx # /zen/search
└── settings/page.tsx # /zen/settings
Route Groups
| Group |
Layout |
Auth |
Description |
(auth) |
Server component |
Redirects if authenticated |
Login, Register |
(dashboard) |
Server component + providers |
Required (session cookie) |
All protected pages |
Deleted Routes
| Route |
Reason |
/nexus/stats |
Superseded by /nexus?tab=analytics |
/nexus/dashboard |
Redirects to /nexus?tab=analytics |
Authentication
Middleware (middleware.ts)
Runs on every request. Checks for sentinel_session cookie.
| Route Pattern |
Behavior |
/, /api/*, /_next/*, /favicon.ico, /public/* |
Pass through (public) |
/login, /register |
If cookie present → redirect to /dashboard |
/nexus/*, /zen/*, /dashboard/* |
If no cookie → redirect to /login |
Defense-in-Depth
Middleware performs optimistic cookie-exists check only. Authoritative session verification happens in the (dashboard)/layout.tsx server component via DAL (Data Access Layer), following CVE-2025-29927 best practices.
Session Expiry Handling
verifySession (in lib/dal/auth.ts) deletes the sentinel_session cookie before redirecting to /login when the JWT is invalid or missing a userId. This prevents a redirect loop where the middleware sees the stale cookie, passes the request to the layout, which then redirects back to /login, repeating indefinitely. Session expiry is enforced by the JWT itself; there is no separate server-side expiresAt > Date.now() check.
Auth Store (stores/auth-store.ts)
interface AuthUser {
id: string;
email: string;
username: string;
firstName: string | null;
lastName: string | null;
avatar: string | null;
role: 'viewer' | 'analyst' | 'admin';
team: 'INTERNAL' | 'EXTERNAL';
}
interface AuthState {
user: AuthUser | null;
accessToken: string | null;
isAuthenticated: boolean;
isLoading: boolean;
isHydrated: boolean;
// Actions: setUser, setAccessToken, logout, clearClientState,
// broadcastLogout, broadcastLogin
}
- Persistence: Zustand middleware, key
sentinel-auth-v2
- Cross-tab sync: BroadcastChannel API for login/logout propagation
- Default role/team:
viewer / EXTERNAL when not provided by backend
Theme System
Service Detection
URL path starts with /zen/* → Service: 'zen'
Otherwise → Service: 'nexus'
Theme Modes
| Mode |
Nexus Service |
Zen Service |
auto |
Light |
Dark |
light |
Light |
Light |
dark |
Dark |
Dark |
terminal |
Terminal |
Terminal |
CSS Class Application
| Scheme |
HTML classes |
Body classes |
| Light |
light nexus |
light nexus |
| Dark |
dark zen |
dark zen |
| Terminal |
dark terminal |
dark terminal |
Theme Provider
// Available hooks
useTheme() // Full theme context (mode, scheme, service, setMode)
useService() // 'nexus' | 'zen'
useIsZen() // boolean
useIsNexus() // boolean
useIsTerminal() // boolean
useThemeClass() // Theme-aware className helper
useThemeVariable() // Computed CSS variable value
Persistence: localStorage key sentinel-theme-mode.
State Management
Zustand Stores
| Store |
Key State |
Persistence |
auth-store |
User, tokens, auth state |
sentinel-auth-v2 (localStorage) |
nexus-store |
Upload files, active jobs, filters, annotations, PDF linking |
None (session-only) |
zen-store |
Sessions, messages, agents, context docs, UI state |
None (session-only) |
React Query
All API data fetching uses TanStack React Query with:
- Conditional polling:
refetchInterval based on isTabVisible and processing state
- Placeholder data: Previous data shown during refetch (no loading flicker)
- Parallel queries:
useQueries for batch job enrichment
- Optimistic updates: Chat messages appear immediately
- Stale time: 5 minutes for enrichment data, 30s for dashboard Zen session data
Dashboard Activity Feed
The main dashboard (/(dashboard)/page.tsx) fetches Zen chat sessions via React Query (queryKey: ['zenChatSessions'], staleTime: 30000) and merges them with Nexus pipeline entries into a unified activity feed. Sessions with the default title “New Chat” are excluded. Entries are sorted by updated_at descending.
API Architecture
Backend Services
| Service |
Base URL |
API Client |
| Nexus |
NEXT_PUBLIC_NEXUS_API_URL |
lib/api/nexus.ts (88 functions) |
| Zen |
NEXT_PUBLIC_ZEN_API_URL |
lib/api/zen.ts (32 functions) |
| Studio Gateway |
NEXT_PUBLIC_STUDIO_API_URL |
lib/api/nexus-gateway.ts |
Gateway Routing (Nexus)
Request → Studio Gateway available?
├── Yes → POST /api/v1/nexus/{path}
│ ├── Success → Return response
│ ├── 5xx/401 → Mark gateway unavailable, fallback
│ └── 404 "No service route" → Fall through to direct
└── No → Direct Nexus Backend POST /{path}
Gateway availability is tracked per-session. A single 5xx marks it unavailable for subsequent requests.
API Client Options (RequestOptions)
RequestOptions is exported from lib/studio/api-client.ts and accepted by all gateway layers.
| Option |
Type |
Description |
keepalive |
boolean |
Passes keepalive: true to fetch. Use for cleanup requests that must survive page navigation. |
suppressAuthEvent |
boolean |
Suppresses the global auth:session-expired event on 401. Use for background polling loops to avoid disrupting the UI. |
skipAuth |
boolean |
Skip Bearer token injection |
rawResponse |
boolean |
Skip envelope unwrapping |
signal |
AbortSignal |
Cancellation signal |
apiVersion |
'v1' \| 'v2' |
Override API version prefix (default: v1) |
Environment Variables
| Variable |
Required |
Purpose |
NEXT_PUBLIC_NEXUS_API_URL |
Yes |
Nexus backend base URL |
NEXT_PUBLIC_ZEN_API_URL |
Yes |
Zen chatbot backend base URL |
NEXT_PUBLIC_STUDIO_API_URL |
No |
Studio gateway (enables routing) |
NEXT_PUBLIC_ENABLE_MOCKS |
No |
Enable MSW mock service worker |
NEXT_PUBLIC_APP_URL |
No |
Application URL for CORS |
NEXT_PUBLIC_COOKIE_DOMAIN |
No |
Cookie domain for auth |
Navigation Architecture
| Item |
Route |
Icon |
| Dashboard |
/nexus |
LayoutDashboard |
| Upload |
/nexus/upload |
Upload |
| History |
/nexus/history |
History |
| Analytics |
/nexus?tab=analytics |
BarChart3 |
Active route detection uses usePathname() + useSearchParams() for query-param-aware highlighting (Analytics tab requires ?tab=analytics match).
| Item |
Route |
Icon |
| Chat |
/zen |
MessageSquare |
| Agents |
/zen/agents |
Bot |
| Documents |
/zen/documents |
FileText |
| Search |
/zen/search |
Search |
| Profile |
/zen/settings |
User |
Main Dashboard Quick Actions
| Card |
Destination |
Service |
| Portfolio Intelligence |
/nexus |
Nexus |
| Document Pipeline |
/nexus/upload |
Nexus |
| Financial AI Chat |
/zen |
Zen |
| AI Agent Config |
/zen/agents |
Zen |
| Knowledge Search |
/zen/search |
Zen |
| Client Reporting |
/nexus/history |
Nexus |
Build & Deploy
Build Output
output: 'standalone' # Self-contained Node.js server
compress: true # Gzip compression
images.unoptimized: true # CloudFront handles image optimization
Deployment Pipeline
TypeScript Check → Next.js Build → Docker Build (linux/amd64)
→ ECR Push → ECS Force Deploy → CloudFront Invalidation
Infrastructure
| Component |
Resource |
| Container |
ECS Fargate (1 task, 256 CPU / 512 MB) |
| Registry |
ECR sentinel-frontend-dev |
| Load Balancer |
ALB sentinel-frontend-dev-alb |
| CDN |
CloudFront EQEH6ZAHOVUNL |
| DNS |
Route53 → sentinel-dev.centricitywealth.tech |
| Region |
ap-south-1 (Mumbai) |
Docker Build Args
ARG NEXT_PUBLIC_NEXUS_API_URL
ARG NEXT_PUBLIC_ZEN_API_URL
ARG NEXT_PUBLIC_AGENTS_API_URL
ARG NEXT_PUBLIC_KNOWLEDGE_API_URL
ARG NEXT_PUBLIC_STUDIO_API_URL
ARG NEXT_PUBLIC_ENABLE_MOCKS=false
All routes receive: X-Frame-Options: SAMEORIGIN, X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin, Permissions-Policy: camera=(), microphone=(), geolocation=().
Static assets (/_next/static/): Cache-Control: public, max-age=31536000, immutable.
Testing
Playwright Configuration
| Project |
Device |
Auth |
| chromium |
Desktop Chrome |
Authenticated |
| firefox |
Desktop Firefox |
Authenticated |
| webkit |
Desktop Safari |
Authenticated |
| mobile-chrome |
Pixel 5 |
Authenticated |
| mobile-safari |
iPhone 13 |
Authenticated |
Test Fixtures
static/assets/test-user.json — Test credentials
static/assets/test-zen.json — Zen chat test queries
static/assets/*.pdf — 14 financial document PDFs (PMS/AIF/MF)
Test Suites
| Suite |
Coverage |
all-routes.spec.ts |
Every route loads without errors |
page-rendering.spec.ts |
Key UI elements render per page |
user-journeys.spec.ts |
End-to-end flows (login → upload → insights) |
auth-flows.spec.ts |
Login, register, session expiry, cross-tab |
error-boundaries.spec.ts |
Error states, network failures, retry |
loading-states.spec.ts |
Skeleton loaders, spinners, transitions |
screenshot-all-pages.spec.ts |
Visual regression (21 reference screenshots) |
Last updated: 2026-03-03
Version: 6.2.0