Frontend
Purpose
nuxt-frontend/ is the primary product UI. It serves operator dashboards, admin workflows, floor plans, emergency tooling, analytics, and a mobile-first layout. It also hosts the published docs output and doubles as the Capacitor app shell.
Key structure
| Path | Role |
|---|---|
pages/ | Route-level screens — see pages reference below |
components/ | Shared UI and domain components |
components/mobile/views/ | Mobile-specific views layered over the same product domains |
layouts/ | Desktop/mobile shells and navigation |
middleware/ | Route guards — see middleware section below |
plugins/ | Client-side plugins — see plugins section below |
composables/ | Shared stateful behavior — see table below |
server/ | Nuxt/Nitro server layer — see Nuxt Server |
public/docs/ | Generated VitePress output |
android/, ios/ | Capacitor native projects |
Pages reference
All pages in nuxt-frontend/pages/:
| Page | Route | Purpose |
|---|---|---|
index.vue | / | Root redirect |
login.vue | /login | Email/password login form; OIDC redirects to /auth/login |
dashboard.vue | /dashboard | Operator home — building/floor summary, gateway health, personnel counts |
floorplan.vue | /floorplan | Real-time RTLS map with Leaflet — protected by lock file |
personnel.vue | /personnel | Personnel list, search, status overview |
personnel/[id].vue | /personnel/:id | Personnel detail and beacon assignment |
assets.vue | /assets | Asset list and status overview |
assets/[id].vue | /assets/:id | Asset detail and history |
emergency.vue | /emergency | Emergency scenario trigger, muster tracking, notification status |
gateways.vue | /gateways | Gateway list, health, and BLE scan visualization |
desktop-gateways.vue | /desktop-gateways | Windows gateway management and site config |
ambient.vue | /ambient | Ambient BLE device overview |
ambient/[id].vue | /ambient/:id | Ambient device detail |
buildings.vue | /buildings | Building/floor/zone management |
analytics.vue | /analytics | Occupancy and movement analytics |
activity.vue | /activity | Live activity log stream |
battery.vue | /battery | Battery health dashboard and alerts |
weather.vue | /weather | Weather feed and alert configuration |
ble-scanner.vue | /ble-scanner | Virtual and serial BLE scanner UI |
admin.vue | /admin | Admin settings root |
admin/personnel.vue | /admin/personnel | Bulk personnel and beacon management — protected by lock file |
admin/api-docs.vue | /admin/api-docs | In-app OpenAPI documentation viewer |
profile.vue | /profile | User profile and password change |
change-password.vue | /change-password | Forced password change (redirected when mustChangePassword flag is set) |
phone-badge-onboarding.vue | /phone-badge-onboarding | Operator flow for issuing phone badges |
phone-beacon-enroll.vue | /phone-beacon-enroll | Public token-driven phone enrollment (native app required) |
scan-qr.vue | /scan-qr | Employee self-service: in-app QR scanner + manual token entry |
accessibility.vue | /accessibility | Accessibility statement |
unauthorized.vue | /unauthorized | RBAC access denied landing |
Route middleware
Located in nuxt-frontend/middleware/:
| File | Type | Invocation | Purpose |
|---|---|---|---|
auth.ts | Named | middleware: ['auth'] in definePageMeta | Redirects unauthenticated users to /login or OIDC /auth/login; enforces role check; redirects to /change-password when mustChangePassword is set |
device.global.ts | Global | Automatic on every navigation | Switches between default and mobile Nuxt layouts based on viewport width at navigation time |
permissions.ts | Named | middleware: ['auth', 'permissions'] | Fine-grained RBAC; use permissions: ['resource.action'] (all required) or permissionsAny: [...] (any one sufficient) in definePageMeta |
Auth middleware behavior
- OIDC-aware: when
oidcProvideris'okta'in runtime config, the SSO button redirects to/auth/oidc/loginvianuxt-oidc-auth - Server-side: uses httpOnly
auth_tokencookie to determine auth state (authoritative) - Client-side: uses non-httpOnly
auth_usercookie (set alongsideauth_tokenon login)
Device middleware
Runs globally on every page navigation. Calls setPageLayout('mobile' | 'default') based on window.matchMedia(NARROW_BREAKPOINT).matches. Only activates on the client — server always renders with the default layout.
Client plugins
Located in nuxt-frontend/plugins/:
| File | Runs | Purpose |
|---|---|---|
driver.client.ts | Client only | Loads driver.js CSS — enables the useOnboarding composable to run Driver.js step-by-step onboarding tours (used on the desktop gateway onboarding wizard) |
errorHandler.client.ts | Client only | Registers a global Vue error handler and window.onerror / window.onunhandledrejection listener; forwards all unhandled errors to useErrorLogging |
Composables reference
Key composables in composables/. The floorplan subsystem is split across many focused files.
| Composable | Purpose |
|---|---|
useApi / useApiClient | Base HTTP fetch helpers and typed API client |
useAuth | Login state, JWT/session handling, permission checks |
useRealtime | Centrifugo client — WebSocket connection and event subscription |
useEmergency | Emergency trigger, muster status, scenario management |
useCommunications | Emergency notification channel state |
useNotificationRules | Alert rule management |
usePersonnel / usePersonnelAvatar | Personnel list, search, avatar loading |
useBuildings | Buildings/floors/zones tree |
useFloorplan | Top-level floorplan orchestrator — composes all floorplan sub-composables |
useFloorplanMap | Leaflet map lifecycle |
useFloorplanDataStore | Shared reactive position/device data store for the floorplan |
useFloorplanMarkers | Marker rendering and clustering |
useFloorplanClustering | Marker cluster plugin management |
useFloorplanAmbientLayer | Ambient device overlay |
useFloorplanAssetLayer | Asset overlay |
useFloorplanGatewayLayer | Gateway status overlay |
useFloorplanHeatmap | RSSI / position heatmap layer |
useFloorplanWebGL | WebGL-accelerated rendering for large marker counts |
useFloorplanZones | Zone polygon rendering and click handling |
useFloorplanControls | Zoom, pan, and toolbar control state |
useFloorplanAccuracyLayer | Positioning accuracy visualisation |
useFloorplanRSSI | Per-receiver RSSI display |
useFloorplanTooltip | Marker tooltip content |
useFloorplanDebug | Debug overlay toggle |
useFloorplanDragSelect | Drag-to-select multiple markers |
useFloorplanMeasure | Distance measurement tool |
useCharlotte | Port of reelyActive charlotte.js — Cytoscape.js Hyperlocal Context graph showing BLE device ↔ gateway RSSI relationships |
useVirtualGateway | Turns a browser/phone into a BLE gateway using Web Bluetooth; posts scanned observations to the same ingest pipeline as hardware gateways |
useNativeBleScanner | BLE scanning via the Capacitor @capacitor-community/bluetooth-le plugin (native app only) |
useSerialBleScanner | BLE scanning via Web Serial API (Chrome desktop) |
useRssiFilter | RSSI sliding-window averaging shared by virtual and serial scanners |
useDeviceIdentifier | Sniffypedia BLE device identification and RPA resolution |
usePhoneBeaconEnrollment | Phone badge enrollment state machine |
useOnboarding | Desktop gateway onboarding wizard state |
useActivity | Activity log streaming |
useAnalytics | Analytics dashboard data |
useBattery | Battery health data |
useWeather | Weather feed |
useAdmin | Admin settings API |
useErrorHandler / useErrorLogging | Centralised error capture and logging |
useFormatters | Date/time/unit formatting helpers |
useResponsiveView | Breakpoint detection for desktop vs mobile layout switching |
useMobileSheet / useCrudDialog | Shared sheet and dialog state patterns |
useMapMarkerScale | Scale-aware marker sizing on the floorplan |
usePwaInstall | PWA install prompt (present but intentionally disabled) |
Current UX model
- Desktop and mobile are both first-class in the same app.
layouts/mobile.vueprovides a dedicated mobile header, drawer, bottom nav, and shared page-title mapping.components/mobile/views/mirrors the major product areas with mobile-specific composition.- Documentation is linked back into the application through
/docs/.
Build and runtime model
- Default local build target is
node-server. - Azure deploys use Nitro
azurepreset. - Capacitor builds set
CAPACITOR_BUILD=1, which disables SSR and produces a static SPA-style bundle. - PWA support is intentionally disabled because stale service workers previously broke deploys.
Authentication model
- Local email/password auth is active.
- Okta SSO is active via
nuxt-oidc-auth(setNUXT_OKTA_DOMAIN+NUXT_OKTA_CLIENT_IDto enable). Auth0 has been removed. - The frontend consumes JWT/session data and permission lists returned by the backend auth routes.
Frontend coding conventions
Formatting
- Prettier: no semicolons, single quotes, width 100, trailing commas
es5, 2-space indentation .vuefiles use the Vue parser override in Prettier
ESLint rules that matter
- Component tags in templates must use
PascalCase prefer-constandno-varare enforcedconsoleanddebuggerare warnings in dev, errors in productionanyand non-null assertions are warnings in app code, relaxed in testsMobileBottomSheetis intentionally restricted in favor ofMobileDetailSheet
Practical style guidance
- Follow existing Composition API patterns and typed
script setup - Reuse composables before adding new page-local data flow
- Preserve separate desktop/mobile rendering layers when a domain already has both
- Do not re-enable PWA/service-worker behavior without a replacement cache strategy
Useful commands
cd nuxt-frontend
npm run dev
npm run build
npm run typecheck
npm run lint
npm run test:unit
npm run test:e2e
npm run storybookQC enforcement rules
The frontend QC pipeline enforces these rules on every changed file:
| Rule | Tool | Enforcement |
|---|---|---|
| TDD — test file required per source file | check-test-quality.sh | Error if missing; warns on mount-only or zero-assertion tests |
| Zod validation on all server mutation routes | check-zod-validation.sh | Blocks bare readBody() without schema validation |
| NISC design tokens only — no hardcoded hex or PrimeVue CSS vars | check-branding.sh | Blocks any hardcoded color outside the approved palette |
| SSR safety — browser APIs must be guarded | check-ssr-safety.sh | Blocks window.*/document.* outside onMounted/import.meta.client |
Heavy packages must use dynamic import() | check-imports.sh | Blocks static imports of leaflet, chart.js, xlsx, jspdf, etc. |
Env vars via useRuntimeConfig() only | check-env-vars.sh | Blocks hardcoded secrets and bare process.env in client code |
| Mobile-responsive patterns | check-mobile.sh | Blocks raw window.innerWidth; warns on DataTable without responsiveLayout |
OpenAPI defineRouteMeta() on every route | check-openapi-compliance.sh | Blocks server routes without metadata |
| No direct DB access from Nuxt routes | check-db-compliance.sh | Blocks pg imports and raw SQL in non-exempt server files |
| camelCase everywhere | ESLint | Blocks snake_case variable/property names in frontend code |
Run npm run qa (lint then test) before committing. The pipeline also runs npm run copilot:commit for Copilot-authored changes.
Frontend-specific handoff notes
nuxt.config.tsis a high-impact file: runtime config, Azure routing, auth activation, docs path, and caching behavior all live there.pages/floorplan.vueis protected by lock files because it contains fragile ambient-rendering behavior.- The app currently ships both operational docs and product UI together, so docs changes can affect deploy output size and routing.