Project Setup

Complete project configuration files including package manifests, TypeScript config, Vite build config, HTML entry point, CSS theme, and application entry points.

Table of contents

  1. dashboard/package.json
  2. dashboard/tsconfig.json
  3. dashboard/tsconfig.node.json
  4. dashboard/vite.config.ts
  5. dashboard/index.html
  6. dashboard/src/index.css
  7. dashboard/src/main.tsx
  8. dashboard/src/App.tsx
  9. dashboard/src/vite-env.d.ts
  10. dashboard/public/favicon.svg

dashboard/package.json

{
  "name": "@metaforge/dashboard",
  "private": true,
  "version": "0.1.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc -b && vite build",
    "preview": "vite preview",
    "lint": "eslint ."
  },
  "dependencies": {
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "react-router-dom": "^7.1.0",
    "@tanstack/react-query": "^5.62.0",
    "@tanstack/react-table": "^8.20.0",
    "zustand": "^5.0.0",
    "axios": "^1.7.9",
    "@react-three/fiber": "^8.17.0",
    "@react-three/drei": "^9.114.0",
    "three": "^0.170.0",
    "recharts": "^2.15.0",
    "lucide-react": "^0.468.0",
    "clsx": "^2.1.1",
    "tailwind-merge": "^2.6.0",
    "class-variance-authority": "^0.7.1",
    "@radix-ui/react-dialog": "^1.1.4",
    "@radix-ui/react-dropdown-menu": "^2.1.4",
    "@radix-ui/react-select": "^2.1.4",
    "@radix-ui/react-tabs": "^1.1.3",
    "@radix-ui/react-tooltip": "^1.1.6",
    "@radix-ui/react-scroll-area": "^1.2.2",
    "@radix-ui/react-separator": "^1.1.1",
    "@radix-ui/react-label": "^2.1.1",
    "@radix-ui/react-progress": "^1.1.1",
    "@radix-ui/react-slot": "^1.1.1",
    "cmdk": "^1.0.4",
    "sonner": "^1.7.1",
    "date-fns": "^4.1.0"
  },
  "devDependencies": {
    "@types/react": "^18.3.14",
    "@types/react-dom": "^18.3.3",
    "@types/three": "^0.170.0",
    "@vitejs/plugin-react": "^4.3.4",
    "typescript": "^5.6.3",
    "vite": "^6.0.5",
    "@tailwindcss/vite": "^4.0.0",
    "tailwindcss": "^4.0.0",
    "msw": "^2.7.0"
  }
}

dashboard/tsconfig.json

{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,

    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "isolatedModules": true,
    "moduleDetection": "force",
    "noEmit": true,
    "jsx": "react-jsx",

    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedIndexedAccess": true,
    "forceConsistentCasingInFileNames": true,

    /* Path aliases */
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["src"],
  "references": [{ "path": "./tsconfig.node.json" }]
}

dashboard/tsconfig.node.json

{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["ES2023"],
    "module": "ESNext",
    "skipLibCheck": true,

    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "isolatedModules": true,
    "moduleDetection": "force",
    "noEmit": true,

    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true
  },
  "include": ["vite.config.ts"]
}

dashboard/vite.config.ts

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tailwindcss from '@tailwindcss/vite';
import path from 'path';

export default defineConfig({
  plugins: [react(), tailwindcss()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
  server: {
    port: 5173,
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
      },
      '/ws': {
        target: 'ws://localhost:3000',
        ws: true,
      },
    },
  },
  build: {
    target: 'ES2020',
    sourcemap: true,
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom', 'react-router-dom'],
          three: ['three', '@react-three/fiber', '@react-three/drei'],
          query: ['@tanstack/react-query'],
          charts: ['recharts'],
        },
      },
    },
  },
});

dashboard/index.html

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="description" content="MetaForge Digital Twin Dashboard" />
    <title>MetaForge Dashboard</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

dashboard/src/index.css

@import "tailwindcss";

/* ------------------------------------------------------------------ */
/* Theme tokens — shadcn/ui compatible                                */
/* ------------------------------------------------------------------ */

@theme {
  --color-background: oklch(1 0 0);
  --color-foreground: oklch(0.145 0 0);
  --color-card: oklch(1 0 0);
  --color-card-foreground: oklch(0.145 0 0);
  --color-popover: oklch(1 0 0);
  --color-popover-foreground: oklch(0.145 0 0);
  --color-primary: oklch(0.205 0.064 270.94);
  --color-primary-foreground: oklch(0.985 0 0);
  --color-secondary: oklch(0.97 0 0);
  --color-secondary-foreground: oklch(0.205 0.064 270.94);
  --color-muted: oklch(0.97 0 0);
  --color-muted-foreground: oklch(0.556 0 0);
  --color-accent: oklch(0.97 0 0);
  --color-accent-foreground: oklch(0.205 0.064 270.94);
  --color-destructive: oklch(0.577 0.245 27.33);
  --color-destructive-foreground: oklch(0.985 0 0);
  --color-border: oklch(0.922 0 0);
  --color-input: oklch(0.922 0 0);
  --color-ring: oklch(0.708 0.165 254.62);
  --color-chart-1: oklch(0.646 0.222 41.12);
  --color-chart-2: oklch(0.6 0.118 184.71);
  --color-chart-3: oklch(0.398 0.07 227.39);
  --color-chart-4: oklch(0.828 0.189 84.43);
  --color-chart-5: oklch(0.769 0.188 70.08);
  --color-sidebar-background: oklch(0.985 0 0);
  --color-sidebar-foreground: oklch(0.145 0 0);
  --color-sidebar-primary: oklch(0.205 0.064 270.94);
  --color-sidebar-primary-foreground: oklch(0.985 0 0);
  --color-sidebar-accent: oklch(0.97 0 0);
  --color-sidebar-accent-foreground: oklch(0.205 0.064 270.94);
  --color-sidebar-border: oklch(0.922 0 0);
  --color-sidebar-ring: oklch(0.708 0.165 254.62);
  --radius: 0.625rem;
}

/* ------------------------------------------------------------------ */
/* Dark mode                                                          */
/* ------------------------------------------------------------------ */

.dark {
  --color-background: oklch(0.145 0 0);
  --color-foreground: oklch(0.985 0 0);
  --color-card: oklch(0.205 0 0);
  --color-card-foreground: oklch(0.985 0 0);
  --color-popover: oklch(0.205 0 0);
  --color-popover-foreground: oklch(0.985 0 0);
  --color-primary: oklch(0.922 0 0);
  --color-primary-foreground: oklch(0.205 0.064 270.94);
  --color-secondary: oklch(0.269 0 0);
  --color-secondary-foreground: oklch(0.985 0 0);
  --color-muted: oklch(0.269 0 0);
  --color-muted-foreground: oklch(0.708 0 0);
  --color-accent: oklch(0.269 0 0);
  --color-accent-foreground: oklch(0.985 0 0);
  --color-destructive: oklch(0.704 0.191 22.22);
  --color-destructive-foreground: oklch(0.985 0 0);
  --color-border: oklch(0.352 0 0);
  --color-input: oklch(0.352 0 0);
  --color-ring: oklch(0.556 0.165 254.62);
  --color-chart-1: oklch(0.488 0.243 264.05);
  --color-chart-2: oklch(0.696 0.17 162.48);
  --color-chart-3: oklch(0.769 0.188 70.08);
  --color-chart-4: oklch(0.627 0.265 303.9);
  --color-chart-5: oklch(0.645 0.246 16.44);
  --color-sidebar-background: oklch(0.205 0 0);
  --color-sidebar-foreground: oklch(0.985 0 0);
  --color-sidebar-primary: oklch(0.488 0.243 264.05);
  --color-sidebar-primary-foreground: oklch(0.985 0 0);
  --color-sidebar-accent: oklch(0.269 0 0);
  --color-sidebar-accent-foreground: oklch(0.985 0 0);
  --color-sidebar-border: oklch(0.352 0 0);
  --color-sidebar-ring: oklch(0.556 0.165 254.62);
}

/* ------------------------------------------------------------------ */
/* Base styles                                                        */
/* ------------------------------------------------------------------ */

*,
*::before,
*::after {
  border-color: var(--color-border);
}

body {
  background-color: var(--color-background);
  color: var(--color-foreground);
  font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",
    "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* ------------------------------------------------------------------ */
/* Scrollbar                                                          */
/* ------------------------------------------------------------------ */

::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}

::-webkit-scrollbar-track {
  background: transparent;
}

::-webkit-scrollbar-thumb {
  background-color: var(--color-border);
  border-radius: 4px;
}

::-webkit-scrollbar-thumb:hover {
  background-color: var(--color-muted-foreground);
}

/* Firefox */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--color-border) transparent;
}

/* ------------------------------------------------------------------ */
/* Utility classes                                                    */
/* ------------------------------------------------------------------ */

/* Hide scrollbar but allow scrolling */
.scrollbar-hidden {
  -ms-overflow-style: none;
  scrollbar-width: none;
}

.scrollbar-hidden::-webkit-scrollbar {
  display: none;
}

/* Fade-in animation */
@keyframes fade-in {
  from {
    opacity: 0;
    transform: translateY(4px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.animate-fade-in {
  animation: fade-in 0.2s ease-out;
}

/* Pulse dot for live indicators */
@keyframes pulse-dot {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.4;
  }
}

.animate-pulse-dot {
  animation: pulse-dot 2s ease-in-out infinite;
}

dashboard/src/main.tsx

import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { BrowserRouter } from 'react-router-dom';
import { App } from '@/App';
import '@/index.css';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 30_000,
      retry: 1,
      refetchOnWindowFocus: false,
    },
    mutations: {
      retry: 0,
    },
  },
});

async function enableMocking(): Promise<void> {
  if (import.meta.env.VITE_MOCK_API !== 'true') {
    return;
  }

  const { worker } = await import('@/mocks/mock-server');
  await worker.start({
    onUnhandledRequest: 'bypass',
    quiet: false,
  });

  console.info('[MSW] Mock service worker started');
}

enableMocking().then(() => {
  const root = document.getElementById('root');

  if (!root) {
    throw new Error('Root element not found. Ensure index.html has a <div id="root">.');
  }

  createRoot(root).render(
    <StrictMode>
      <QueryClientProvider client={queryClient}>
        <BrowserRouter>
          <App />
        </BrowserRouter>
      </QueryClientProvider>
    </StrictMode>,
  );
});

dashboard/src/App.tsx

import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
import { Toaster } from 'sonner';
import { RootLayout } from '@/components/layout/root-layout';
import { LoadingSpinner } from '@/components/shared/loading-spinner';

/* ----- Lazy-loaded pages ----- */

const OverviewPage = lazy(() =>
  import('@/pages/overview/overview-page').then((m) => ({ default: m.OverviewPage })),
);
const SessionsPage = lazy(() =>
  import('@/pages/sessions/sessions-page').then((m) => ({ default: m.SessionsPage })),
);
const SessionDetailPage = lazy(() =>
  import('@/pages/sessions/session-detail-page').then((m) => ({ default: m.SessionDetailPage })),
);
const AgentsPage = lazy(() =>
  import('@/pages/agents/agents-page').then((m) => ({ default: m.AgentsPage })),
);
const DigitalTwinPage = lazy(() =>
  import('@/pages/digital-twin/digital-twin-page').then((m) => ({ default: m.DigitalTwinPage })),
);
const BomPage = lazy(() =>
  import('@/pages/bom/bom-page').then((m) => ({ default: m.BomPage })),
);
const CompliancePage = lazy(() =>
  import('@/pages/compliance/compliance-page').then((m) => ({ default: m.CompliancePage })),
);
const TestingPage = lazy(() =>
  import('@/pages/testing/testing-page').then((m) => ({ default: m.TestingPage })),
);
const SupplyChainPage = lazy(() =>
  import('@/pages/supply-chain/supply-chain-page').then((m) => ({ default: m.SupplyChainPage })),
);
const ApprovalsPage = lazy(() =>
  import('@/pages/approvals/approvals-page').then((m) => ({ default: m.ApprovalsPage })),
);
const SettingsPage = lazy(() =>
  import('@/pages/settings/settings-page').then((m) => ({ default: m.SettingsPage })),
);

/* ----- Page-level loading fallback ----- */

function PageLoader() {
  return (
    <div className="flex h-[calc(100vh-4rem)] items-center justify-center">
      <LoadingSpinner size="lg" label="Loading page..." />
    </div>
  );
}

/* ----- Application component ----- */

export function App() {
  return (
    <>
      <Routes>
        <Route element={<RootLayout />}>
          <Route
            index
            element={
              <Suspense fallback={<PageLoader />}>
                <OverviewPage />
              </Suspense>
            }
          />
          <Route
            path="sessions"
            element={
              <Suspense fallback={<PageLoader />}>
                <SessionsPage />
              </Suspense>
            }
          />
          <Route
            path="sessions/:id"
            element={
              <Suspense fallback={<PageLoader />}>
                <SessionDetailPage />
              </Suspense>
            }
          />
          <Route
            path="agents"
            element={
              <Suspense fallback={<PageLoader />}>
                <AgentsPage />
              </Suspense>
            }
          />
          <Route
            path="digital-twin"
            element={
              <Suspense fallback={<PageLoader />}>
                <DigitalTwinPage />
              </Suspense>
            }
          />
          <Route
            path="bom"
            element={
              <Suspense fallback={<PageLoader />}>
                <BomPage />
              </Suspense>
            }
          />
          <Route
            path="compliance"
            element={
              <Suspense fallback={<PageLoader />}>
                <CompliancePage />
              </Suspense>
            }
          />
          <Route
            path="testing"
            element={
              <Suspense fallback={<PageLoader />}>
                <TestingPage />
              </Suspense>
            }
          />
          <Route
            path="supply-chain"
            element={
              <Suspense fallback={<PageLoader />}>
                <SupplyChainPage />
              </Suspense>
            }
          />
          <Route
            path="approvals"
            element={
              <Suspense fallback={<PageLoader />}>
                <ApprovalsPage />
              </Suspense>
            }
          />
          <Route
            path="settings"
            element={
              <Suspense fallback={<PageLoader />}>
                <SettingsPage />
              </Suspense>
            }
          />
        </Route>
      </Routes>
      <Toaster
        position="bottom-right"
        toastOptions={{
          classNames: {
            toast: 'bg-card text-card-foreground border-border',
            title: 'text-foreground',
            description: 'text-muted-foreground',
          },
        }}
      />
    </>
  );
}

dashboard/src/vite-env.d.ts

/// <reference types="vite/client" />

interface ImportMetaEnv {
  /** Enable MSW mock API ('true' to enable) */
  readonly VITE_MOCK_API: string;

  /** Gateway API base URL (default: http://localhost:3000) */
  readonly VITE_API_URL: string;

  /** WebSocket URL (default: ws://localhost:3000/ws) */
  readonly VITE_WS_URL: string;
}

interface ImportMeta {
  readonly env: ImportMetaEnv;
}

dashboard/public/favicon.svg

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none">
  <!-- Hexagonal anvil / forge icon -->
  <path
    d="M32 4L56.785 18.5V47.5L32 62L7.215 47.5V18.5L32 4Z"
    fill="#1a1a4e"
    stroke="#4a5cd6"
    stroke-width="2"
  />
  <!-- Anvil body -->
  <path
    d="M20 38H44V42C44 43.1 43.1 44 42 44H22C20.9 44 20 43.1 20 42V38Z"
    fill="#a0aec0"
  />
  <!-- Anvil top surface -->
  <path
    d="M18 34H46V38H18V34Z"
    fill="#cbd5e0"
  />
  <!-- Anvil horn -->
  <path
    d="M14 34L18 34V38L14 36V34Z"
    fill="#a0aec0"
  />
  <!-- Spark 1 -->
  <circle cx="28" cy="26" r="1.5" fill="#f6ad55" opacity="0.9" />
  <!-- Spark 2 -->
  <circle cx="36" cy="24" r="1" fill="#fc8181" opacity="0.8" />
  <!-- Spark 3 -->
  <circle cx="32" cy="22" r="1.2" fill="#fbd38d" opacity="0.85" />
  <!-- Hammer -->
  <rect x="30" y="18" width="4" height="14" rx="1" fill="#718096" transform="rotate(-15 32 25)" />
  <rect x="27" y="15" width="10" height="5" rx="1" fill="#4a5568" transform="rotate(-15 32 17.5)" />
</svg>