Skip to content

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-visualised branch. 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. See docs/specs/2026-03-18-route-canvas-architecture.md for the current 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.


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:

LayerTechnologyPurpose
FrameworkSvelteKit + Svelte 5 (runes only)File-based routing, SSR, compiled reactivity
LanguageTypeScriptEnd-to-end type safety from API to component
RuntimeBun 1.3Package manager, build tool, production server (svelte-adapter-bun)
Database AccessDrizzle ORMType-safe queries, migration authority (server-side only)
Authenticationbetter-auth (admin + org plugins)Session management, RBAC, admin-only user creation
StylingTailwind CSS v4 + @rapidai/ui tokensUtility-first CSS, product accent colors (oklch)
Componentsbits-uiHeadless UI primitives (Dialog, Tabs, Popover)
Icons@lucide/svelteSvelte 5 native icon library
ChartsChart.js + @xyflow/svelteData visualization + flow diagrams (asset hierarchy)
Toastssvelte-sonnerToast notification system
Types@rapidai/contractsTypeScript SSOT (generated from Python Pydantic → OpenAPI)
Real-TimeSSE (primary) + polling fallbackDiagnostic alerts, swarm events
OfflineService Worker + IndexedDBField 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.raw holds server data (diagnostic results, sensor readings) without deep proxy overhead — critical when rendering hundreds of asset health cards.
  • $derived computes SSI color bands, threshold violations, and RUL countdown values reactively without manual subscriptions.
  • $effect is reserved strictly for side effects (WebSocket lifecycle, IndexedDB writes) — never for state synchronization.

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.

ProductRouteSidebar ItemCSS TokenPurpose
COMMAND/Command--product-command (oklch blue)Fleet dashboard — attention queue, health grid, FleetPulse
FLEET/fleetFleet--product-fleet (oklch indigo)Hierarchy browser — canvas/table toggle, @xyflow/svelte
OPERATIONS/operationsOperations--product-operations (oklch teal)Alarms, work orders, PM schedules, inventory
DIAGNOSTICIAN/diagnose + Ctrl+KDiagnostician--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.

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, RBAC

Each product page is wrapped in ProductWrapper.svelte, which sets the data-product attribute for CSS token scoping:

ProductWrapper.svelte
<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.

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 multiplexer

Why 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.


RAPID AI uses better-auth with admin + organization plugins. Self-registration is disabled — all accounts are admin-created. Roles are org-scoped:

RoleScopeCapabilities
ownerOrganisationFull org management, user management, all features
adminOrganisationUser management, analysis, maintenance, assets
engineerOrganisationRun analyses, create work orders, view assets
managerOrganisationView dashboards, approve work orders
executiveOrganisationRead-only dashboards, fleet overview
viewerOrganisationRead-only access

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.

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.


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.


Svelte 5’s rune system eliminates the need for external state management libraries. RAPID AI uses a clear hierarchy:

SvelteKit’s +page.server.ts load functions fetch data on the server and pass it as props:

// routes/(app)/fleet/+page.server.ts
export const load = async ({ locals }) => {
const equipment = await getEquipment(locals.session.orgId);
return { equipment };
};

State is co-located with features in .svelte.ts files (which enable runes in TypeScript):

src/lib/features/dashboard/state/fleet.svelte.ts
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
);
}
src/lib/shared/state/api-health.svelte.ts
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 selection
let 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.

Shareable views via URL parameters for shift handover:

/?timeRange=24h&status=warning,alarm
/equipment/P-101?tab=diagnostics&from=2026-03-01

Key components and their data contracts, derived from the dashboard JSON specifications:

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'>;
}

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
}

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

5x5 risk matrix plotting assets by severity and probability, color-coded by state or confidence.

Streaming chat interface for the AI diagnostic copilot, scoped to a specific asset context.

Interactive FMEA/RCM workbook displaying failure modes, consequence categories, RPN rankings, and recommended strategies. Maps to the rcm_decision_request_motor.json contract.


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.

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.

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.

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:

  1. The service worker detects the online event.
  2. Queued requests are replayed in order.
  3. Results are written to IndexedDB and the UI is updated.
  4. A toast notification confirms successful sync.

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.


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

  • 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.
  • Server-side: SvelteKit load functions set Cache-Control headers for reference data (failure modes, maintenance tasks) with 5-minute TTL.
  • Client-side: The $state.raw pattern 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.


StandardRelevance to This Chapter
OWASP Top 10 — Web application securityThe 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 cybersecurityThe offline-first architecture (Service Worker + IndexedDB) supports IEC 62443’s availability requirements for industrial systems operating in environments with intermittent network connectivity.
VersionDateAuthorChanges
2.1.02026-03-17Rick DAdded standards alignment, living doc metadata, changelog
2.0.02026-03-17Rick DEnriched with production codebase content
1.0.02026-03-17Rick DInitial chapter creation