/** * @file main.tsx * @description Vite client entry point. Bootstraps React 18, loads global styles * and i18n, registers the service worker for offline asset caching, and mounts * {@link App} into `#root`. * * ## Fonts * Only Latin subsets of Inter and JetBrains Mono are imported so Vite bundles * lean WOFF2 files instead of every glyph subset Google Fonts would serve. * * ## Service worker reload policy * On first visit there is no controlling SW yet — we must not reload when the * initial SW activates (the page is already fresh). On subsequent deploys the * page is controlled; when a new SW takes over we reload once so hashed bundle * URLs update without a manual hard refresh. * * @author Nguyễn Ngọc Trí Vĩ */ /* ============================================================================= * MODULE_GUIDE — extended in-file reference (comments only; safe to read, never executed) * ============================================================================= * **Purpose:** Dashboard module consumed by the React client, MCP tools, or desktop shell depending on deployment mode. * * ## Design constraints * - Local-first: no telemetry leaves the machine unless the user configures webhooks. * - Fail-safe hooks path on the server must never block Claude Code; UI mirrors that * philosophy by degrading gracefully (empty states, stale badges, reconnect loops). * - Destructive flows stay behind explicit confirmation modals and server-side gates. * - Internationalization: user-visible strings belong in i18n JSON, not literals here. * * ## Remote data & SSH * Remote Data Sources let operators aggregate multiple machines. SSH entries describe * how to reach a peer dashboard; the global data scope (`dataScope.ts`) narrows every * scoped GET via `?sources=`. Health checks and import history surface in Settings. * * ## Observability * Prometheus scrapes `GET /api/metrics` (see `monitoring/`). Grafana ships four * provisioned boards (overview, sessions, tools, alerts). Native npm scripts and * Docker Compose profiles are documented in `monitoring/README.md`. * * ## Internal dependencies * - `./App` * * ## Testing pointers * - Prefer colocated `__tests__` with Vitest + Testing Library for UI. * - Server contract changes require `npm run test:server` and OpenAPI sync. * - MCP edits: `npm run mcp:typecheck` and `npm run mcp:build`. * * ## Related docs * - `ARCHITECTURE.md` — hooks → API → SQLite → WebSocket → UI pipeline. * - `docs/API.md` — REST reference. * - `.claude/skills/file-headers/` — mandatory `@author` header policy. * ============================================================================= */ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; // Latin-only subset (matches the app's previous Google Fonts request) so Vite // bundles just the latin WOFF2 per weight instead of every subset. import "@fontsource/inter/latin-400.css"; import "@fontsource/inter/latin-500.css"; import "@fontsource/inter/latin-600.css"; import "@fontsource/inter/latin-700.css"; import "@fontsource/jetbrains-mono/latin-400.css"; import "@fontsource/jetbrains-mono/latin-500.css"; import App from "./App"; import "./i18n"; import "./index.css"; if ("serviceWorker" in navigator) { // Detect whether the page is already controlled by an SW *before* we // register. On a truly fresh install there is no controller yet, so the // first `controllerchange` should NOT reload (the user just opened the page // - nothing is stale). On every subsequent rebuild the page is controlled, // a new SW takes over, and a one-shot reload picks up the new bundle URLs // automatically - no hard refresh needed. const wasControlled = !!navigator.serviceWorker.controller; let reloaded = false; navigator.serviceWorker.addEventListener("controllerchange", () => { if (!wasControlled || reloaded) return; reloaded = true; window.location.reload(); }); navigator.serviceWorker .register("/sw.js") .then((reg) => { // Poke the registration so a freshly-built SW activates promptly // instead of waiting for the browser's lazy update check. reg.update().catch(() => {}); }) .catch(() => {}); } const root = document.getElementById("root"); if (!root) throw new Error("Root element not found"); createRoot(root).render( );