Frontend Architecture
SUPERSEDED (Section 19.2 — Application Structure): The 5-product suite architecture described in this chapter was replaced by a canvas-first, route-per-level architecture on the
feat/engineering-visualisedbranch. The(application)/route group replaces the former(app)/structure. Each hierarchy level now has its own SvelteKit route with its own SvelteFlow instance. Technology stack details (19.1) remain accurate. Seedocs/specs/2026-03-18-route-canvas-architecture.mdfor the current architecture.
Chapter 19: Frontend Architecture
Section titled “Chapter 19: Frontend Architecture”The RAPID AI frontend is the surface through which plant operators, reliability engineers, maintenance managers, and plant managers interact with the diagnostic intelligence engine. It must render complex engineering data — SSI health scores, failure mode evidence chains, RUL projections, RCM decision trees — in a way that drives action, not confusion. This chapter defines the technology choices, application structure, state management patterns, and offline capabilities that make that possible.
19.1 Technology Stack
Section titled “19.1 Technology Stack”The frontend is built on SvelteKit with Svelte 5 runes (no legacy svelte/store), chosen for compiled reactivity, minimal runtime overhead, and first-class server-side rendering. The full stack:
| Layer | Technology | Purpose |
|---|---|---|
| Framework | SvelteKit + Svelte 5 (runes only) | File-based routing, SSR, compiled reactivity |
| Language | TypeScript | End-to-end type safety from API to component |
| Runtime | Bun 1.3 | Package manager, build tool, production server (svelte-adapter-bun) |
| Database Access | Drizzle ORM | Type-safe queries, migration authority (server-side only) |
| Authentication | better-auth (admin + org plugins) | Session management, RBAC, admin-only user creation |
| Styling | Tailwind CSS v4 + @rapidai/ui tokens | Utility-first CSS, product accent colors (oklch) |
| Components | bits-ui | Headless UI primitives (Dialog, Tabs, Popover) |
| Icons | @lucide/svelte | Svelte 5 native icon library |
| Charts | Chart.js + @xyflow/svelte | Data visualization + flow diagrams (asset hierarchy) |
| Toasts | svelte-sonner | Toast notification system |
| Types | @rapidai/contracts | TypeScript SSOT (generated from Python Pydantic → OpenAPI) |
| Real-Time | SSE (primary) + polling fallback | Diagnostic alerts, swarm events |
| Offline | Service Worker + IndexedDB | Field engineer support in low-connectivity environments |
Why Svelte 5 runes? The $state, $derived, and $effect primitives replace Svelte 4’s reactive declarations with explicit, predictable reactivity. For RAPID AI, this matters because:
$state.rawholds server data (diagnostic results, sensor readings) without deep proxy overhead — critical when rendering hundreds of asset health cards.$derivedcomputes SSI color bands, threshold violations, and RUL countdown values reactively without manual subscriptions.$effectis reserved strictly for side effects (WebSocket lifecycle, IndexedDB writes) — never for state synchronization.
19.2 Application Structure
Section titled “19.2 Application Structure”5-Product Suite Architecture
Section titled “5-Product Suite Architecture”The application serves five products within a single SvelteKit deployment. Each product has a dedicated route, accent color, and CSS wrapper — product identity expressed through data-product attributes and oklch CSS tokens, not separate deployments.
| Product | Route | Sidebar Item | CSS Token | Purpose |
|---|---|---|---|---|
| COMMAND | / | Command | --product-command (oklch blue) | Fleet dashboard — attention queue, health grid, FleetPulse |
| FLEET | /fleet | Fleet | --product-fleet (oklch indigo) | Hierarchy browser — canvas/table toggle, @xyflow/svelte |
| OPERATIONS | /operations | Operations | --product-operations (oklch teal) | Alarms, work orders, PM schedules, inventory |
| DIAGNOSTICIAN | /diagnose + Ctrl+K | Diagnostician | --product-diagnostician (oklch violet) | Signal analysis, AI RCA, pipeline explorer |
| EQUIPMENT HUB | /equipment/[id] | — (via Fleet) | --product-equipment (oklch amber) | Machine detail (6 tabs), reached from Fleet hierarchy |
Sidebar: Command · Fleet · Operations · Diagnostician · Admin · Settings (6 items). Equipment Hub is NOT in sidebar — it is navigated to from the Fleet hierarchy.
Route Structure (Production)
Section titled “Route Structure (Production)”src/├── routes/│ ├── (app)/ ← Auth-protected, sidebar layout│ │ ├── +layout.svelte ← Sidebar + ProductWrapper│ │ ├── +page.svelte ← COMMAND (fleet dashboard)│ │ ├── diagnose/ ← DIAGNOSTICIAN│ │ │ └── +page.svelte ← Signal analysis + Ctrl+K modal│ │ ├── fleet/ ← FLEET│ │ │ └── +page.svelte ← Hierarchy browser│ │ ├── equipment/[id]/ ← EQUIPMENT HUB│ │ │ └── +page.svelte ← Machine detail (6 tabs)│ │ ├── operations/ ← OPERATIONS│ │ │ └── +page.svelte ← Alarms, work orders, PM, inventory│ │ ├── settings/ ← Settings (General + System tabs)│ │ ├── admin/ ← Admin panel (admin-only)│ │ └── [legacy]/ ← 19 redirect +page.server.ts files (301s)│ ├── (auth)/ ← Login/register (no sidebar)│ │ ├── login/+page.svelte│ │ └── register/+page.svelte ← "Contact your administrator"│ └── api/ ← SvelteKit API routes (BFF)│ ├── stream/+server.ts ← SSE bridge (local bus + engine swarm)│ ├── swarm/+server.ts ← Intent-based dispatch proxy│ └── health/+server.ts ← Health check proxy├── lib/│ ├── server/ ← BFF layer (server-only)│ │ ├── auth.ts ← better-auth config (admin + org plugins)│ │ ├── engine.ts ← HTTP client to Python API│ │ ├── engine.remote.ts ← Typed wrappers for engine calls│ │ ├── db/ ← Drizzle connection + schema│ │ ├── repositories/ ← Server-side data access patterns│ │ ├── ai/ ← AI provider config│ │ └── sse-bus.ts ← SSE event multiplexer│ ├── features/ ← Feature-scoped modules│ │ ├── analysis/ ← Brief, charts, pipeline explorer, chapter blocks│ │ ├── assets/ ← Hierarchy tree, schematic builder, SVG trains│ │ ├── dashboard/ ← COMMAND: fleet roster, attention queue, pulse│ │ ├── diagnostics/ ← AnalysisRunner, DiagnosticianModal, RCA trace│ │ ├── maintenance/ ← Work orders, schedules, InventoryTab│ │ ├── monitoring/ ← Vibration wave, zone heatmap│ │ ├── consulting/ ← QuickConsultModal, signal input/preview│ │ ├── faults/ ← Fault graph, fault nodes│ │ └── swarm/ ← Agent dispatch, swarm activity panel│ └── shared/ ← Cross-feature primitives│ ├── ui/ ← Card, HealthBadge, RadialGauge, ProductWrapper│ ├── utils/ ← narrative.ts, colors.ts, formatters.ts│ ├── actions/ ← evaluate.ts, inView.ts│ ├── state/ ← api-health.svelte.ts, toast.svelte.ts│ └── api/ ← auth-client.ts, types/ (re-exports @rapidai/contracts)└── hooks.server.ts ← Auth middleware, route guards, RBACProductWrapper Pattern
Section titled “ProductWrapper Pattern”Each product page is wrapped in ProductWrapper.svelte, which sets the data-product attribute for CSS token scoping:
<script> let { product, children } = $props();</script>
<div data-product={product}> {@render children()}</div>CSS tokens defined in packages/ui/src/lib/styles/tokens.css apply per-product accent colors, so each product feels distinct while sharing the same layout chrome.
BFF (Backend-for-Frontend) Pattern
Section titled “BFF (Backend-for-Frontend) Pattern”The browser never calls the Python Engine directly. SvelteKit’s server-side routes proxy all requests, handling session validation, data shaping, and error transformation:
Browser → SvelteKit Server Routes → Python Engine API ↓ src/lib/server/ ├── engine.ts — HTTP client to Python API ├── engine.remote.ts — Typed wrappers for engine calls ├── auth.ts — better-auth integration ├── db/ — Direct Drizzle DB access ├── repositories/ — Server-side data access └── sse-bus.ts — SSE event multiplexerWhy BFF? Auth stays server-side (httpOnly cookies), Engine API needs no auth (trusts internal network), BFF can merge engine + DB data before sending to client, and SSE multiplexing merges local bus + engine swarm events into one browser stream.
19.3 Role-Based Access Control (RBAC)
Section titled “19.3 Role-Based Access Control (RBAC)”RAPID AI uses better-auth with admin + organization plugins. Self-registration is disabled — all accounts are admin-created. Roles are org-scoped:
| Role | Scope | Capabilities |
|---|---|---|
| owner | Organisation | Full org management, user management, all features |
| admin | Organisation | User management, analysis, maintenance, assets |
| engineer | Organisation | Run analyses, create work orders, view assets |
| manager | Organisation | View dashboards, approve work orders |
| executive | Organisation | Read-only dashboards, fleet overview |
| viewer | Organisation | Read-only access |
Route-Level Guards
Section titled “Route-Level Guards”The hooks.server.ts file enforces route access before any page loads:
// hooks.server.ts (simplified)const routePermissions: Record<string, Role[]> = { '/(auth)/dashboard': ['operator', 'engineer', 'maintenance_manager', 'plant_manager'], '/(auth)/assets': ['engineer', 'maintenance_manager', 'plant_manager'], '/(auth)/diagnostics': ['engineer', 'operator'], '/(auth)/rcm': ['engineer', 'maintenance_manager'], '/(auth)/copilot': ['engineer'], '/(auth)/maintenance': ['maintenance_manager', 'engineer'], '/(auth)/admin': ['admin'],};If a user’s role does not match the route, the hook returns a 403 redirect to the dashboard. This is defense in depth — the backend independently enforces authorization on every API call, so even if a frontend guard were bypassed, protected data would remain inaccessible.
Component-Level Visibility
Section titled “Component-Level Visibility”Within shared pages, components conditionally render based on role:
{#if user.role === 'engineer' || user.role === 'maintenance_manager'} <RCMWorkbook {assetId} editable={user.role === 'engineer'} />{/if}The editable prop distinguishes between “can view” and “can modify” — maintenance managers see RCM workbooks but cannot alter failure mode classifications, which requires engineering judgment.
19.4 Real-Time Data Flow
Section titled “19.4 Real-Time Data Flow”Industrial environments demand real-time visibility. RAPID AI uses a layered approach:
WebSocket (primary): A persistent connection streams live sensor readings from the backend, which bridges MQTT data from SCADA/historian systems (OSIsoft PI, Wonderware). The WebSocket delivers structured frames:
{ "type": "sensor_update", "asset_id": "P-101", "timestamp": "2026-03-14T12:00:05+05:30", "readings": { "vibration_rms": 8.9, "bearing_temp_c": 86.0, "flow_m3_hr": 168 }}Server-Sent Events (SSE): One-way push channel for diagnostic alerts. When the backend completes a diagnostic inference that yields a warning or alarm, it pushes an SSE event to all connected clients viewing that asset. SSE is simpler than WebSocket for unidirectional alerts and survives proxy/load-balancer configurations that may strip WebSocket headers.
Polling fallback: In environments where WebSocket is blocked by corporate firewalls, the client falls back to polling at a configurable interval (default: 30 seconds). The frontend detects WebSocket failure and automatically degrades.
Reconnection logic: Exponential backoff with jitter (1s, 2s, 4s, 8s… up to 60s max) prevents thundering-herd reconnection storms when a backend restarts. A connection status indicator in the UI shows green/amber/red so operators know if data is live or stale.
Client-side buffering: Incoming sensor data is buffered in a ring buffer (last 300 data points per sensor type) for smooth chart rendering. Charts subscribe to the buffer rather than raw WebSocket frames, preventing UI jank from burst data.
19.5 State Management
Section titled “19.5 State Management”Svelte 5’s rune system eliminates the need for external state management libraries. RAPID AI uses a clear hierarchy:
Server State (load functions)
Section titled “Server State (load functions)”SvelteKit’s +page.server.ts load functions fetch data on the server and pass it as props:
// routes/(app)/fleet/+page.server.tsexport const load = async ({ locals }) => { const equipment = await getEquipment(locals.session.orgId); return { equipment };};Feature-Scoped State Classes
Section titled “Feature-Scoped State Classes”State is co-located with features in .svelte.ts files (which enable runes in TypeScript):
export class FleetState { equipment = $state<Equipment[]>([]); filter = $state('all');
filtered = $derived( this.filter === 'all' ? this.equipment : this.equipment.filter(e => e.health === this.filter) );
criticalCount = $derived( this.equipment.filter(e => e.health === 'critical').length );}Shared State (Cross-Feature)
Section titled “Shared State (Cross-Feature)”class ApiHealthState { status = $state<'connected' | 'disconnected' | 'checking'>('checking'); lastCheck = $state<Date | null>(null);}export const apiHealth = new ApiHealthState();Component State ($state, $state.raw, $derived)
Section titled “Component State ($state, $state.raw, $derived)”let timeRange = $state<'1h' | '8h' | '24h' | '7d'>('24h'); // User selectionlet sensorData = $state.raw<SensorReading[]>(initialData); // Server data (no deep proxy)let criticalReadings = $derived(sensorData.filter(r => r.value > r.threshold));let ssiColor = $derived(computeSSIColor(props.healthScore));$state.raw is used for all server-fetched data — analysis results, fault lists, asset trees are replaced wholesale, not mutated.
URL State
Section titled “URL State”Shareable views via URL parameters for shift handover:
/?timeRange=24h&status=warning,alarm/equipment/P-101?tab=diagnostics&from=2026-03-0119.6 Component Architecture
Section titled “19.6 Component Architecture”Key components and their data contracts, derived from the dashboard JSON specifications:
AssetHealthCard
Section titled “AssetHealthCard”Renders the primary health summary for a single asset. Maps directly to the dashboard_asset_health_card.json contract.
interface AssetHealthCardProps { asset_id: string; // e.g., "P-101" title: string; // e.g., "Pump P-101" status: 'Normal' | 'Warning' | 'Alarm' | 'Critical'; health_score: number; // 0-100, maps to color band failure_mode: string; // Top-ranked failure mode confidence_score: number; // 0.0-1.0 estimated_rul_days: number | null; // Days until failure recommended_action: string; // Human-readable recommendation trend_summary: Record<string, 'Rising' | 'Falling' | 'Stable'>;}TrendChart
Section titled “TrendChart”Time-series visualization for sensor data with configurable overlays (thresholds, projections, annotations).
interface TrendChartProps { assetId: string; sensorType: 'vibration' | 'temperature' | 'current' | 'oil' | 'process'; timeRange: '1h' | '8h' | '24h' | '7d' | '30d'; showProjection: boolean; // RUL projection overlay showThresholds: boolean; // Warning/alarm threshold lines liveUpdate: boolean; // Subscribe to WebSocket feed}DiagnosticPanel
Section titled “DiagnosticPanel”Displays a complete diagnostic result with evidence chain and rule trace. Derived from the end_to_end_response_example.json contract.
interface DiagnosticPanelProps { diagnosticResult: { failure_mode: string; confidence_score: number; estimated_rul_days: number; sensor_evidence: string[]; // e.g., ["BPFO rising", "Envelope energy high"] }; showEvidence: boolean; // Expand evidence section showRuleTrace: boolean; // Show which rules fired (engineer view)}RiskMatrix
Section titled “RiskMatrix”5x5 risk matrix plotting assets by severity and probability, color-coded by state or confidence.
CopilotChat
Section titled “CopilotChat”Streaming chat interface for the AI diagnostic copilot, scoped to a specific asset context.
RCMWorkbook
Section titled “RCMWorkbook”Interactive FMEA/RCM workbook displaying failure modes, consequence categories, RPN rankings, and recommended strategies. Maps to the rcm_decision_request_motor.json contract.
19.7 Offline Support
Section titled “19.7 Offline Support”Field engineers frequently work in areas with unreliable connectivity — switchgear rooms, pump houses, remote substations. RAPID AI’s offline strategy ensures they can still access recent diagnostic data and queue new requests.
Service Worker
Section titled “Service Worker”A service worker caches the application shell (HTML, CSS, JS bundles) using a cache-first strategy. API responses use a network-first strategy with cache fallback, so the most recent data is always attempted but stale data is available when offline.
IndexedDB Storage
Section titled “IndexedDB Storage”Recent diagnostic results (last 50 per asset), the asset registry, and the failure mode reference library are persisted to IndexedDB. When offline, the dashboard renders from this local cache with a visual indicator showing data staleness.
Outbound Request Queue
Section titled “Outbound Request Queue”When a user triggers a diagnostic run or submits a copilot question while offline, the request is queued in IndexedDB with a pending status. When connectivity restores:
- The service worker detects the
onlineevent. - Queued requests are replayed in order.
- Results are written to IndexedDB and the UI is updated.
- A toast notification confirms successful sync.
Design Rationale
Section titled “Design Rationale”This is not a “nice to have.” In Indian industrial facilities — where RAPID AI is initially deployed — cellular connectivity inside plant buildings can drop to zero. A field engineer doing a bearing inspection on Pump P-101 must be able to pull up the last diagnostic result, review the evidence chain, and note their inspection findings, regardless of network state. The offline layer ensures the diagnostic copilot remains useful even when the network does not cooperate.
19.8 Performance Considerations
Section titled “19.8 Performance Considerations”Bundle Size
Section titled “Bundle Size”SvelteKit’s compiled output produces minimal JavaScript. Chart.js and D3 are the largest dependencies and are loaded lazily only on routes that use them. The dashboard shell loads in under 100KB of JavaScript (gzipped).
Rendering Strategy
Section titled “Rendering Strategy”- SSR for initial load: All protected pages server-render with data from load functions, ensuring fast first paint and SEO-irrelevant but crawlable content.
- Client-side navigation: Subsequent navigation uses SvelteKit’s client-side router with prefetching on link hover.
- Virtualized lists: Asset registries with hundreds of entries use virtual scrolling to avoid DOM bloat.
- Chart throttling: Real-time charts limit redraws to 2 frames per second maximum, regardless of WebSocket message frequency.
Caching
Section titled “Caching”- Server-side: SvelteKit load functions set
Cache-Controlheaders for reference data (failure modes, maintenance tasks) with 5-minute TTL. - Client-side: The
$state.rawpattern naturally avoids unnecessary re-renders since Svelte only triggers updates on reference identity changes, not deep equality checks.
Next: Chapter 20 defines the backend API architecture that powers these frontend experiences — the FastAPI service layer, endpoint contracts, authentication middleware, and integration patterns.
Standards Alignment
Section titled “Standards Alignment”| Standard | Relevance to This Chapter |
|---|---|
| OWASP Top 10 — Web application security | The SvelteKit frontend implements OWASP-compliant security practices including CSRF protection via better-auth session management, input validation through Pydantic schemas, and XSS prevention through Svelte’s built-in HTML escaping. |
| IEC 62443 — Industrial cybersecurity | The offline-first architecture (Service Worker + IndexedDB) supports IEC 62443’s availability requirements for industrial systems operating in environments with intermittent network connectivity. |
Changelog
Section titled “Changelog”| Version | Date | Author | Changes |
|---|---|---|---|
| 2.1.0 | 2026-03-17 | Rick D | Added standards alignment, living doc metadata, changelog |
| 2.0.0 | 2026-03-17 | Rick D | Enriched with production codebase content |
| 1.0.0 | 2026-03-17 | Rick D | Initial chapter creation |