Web UI
Stack & Technology

Web UI Stack — Technology & Tools

Last updated: Feb 20, 2026 21:30 UTC
Purpose: Complete technology breakdown for Vitruvyan Web UI
Status: ✅ Production stack (Feb 2026)


Overview

The Vitruvyan Web UI is built on a modern, production-grade stack optimized for:

  • Developer experience — TypeScript, hot reload, smart defaults
  • Performance — Server-side rendering, code splitting, edge deployment
  • Accessibility — WCAG 2.1 AA compliant components
  • Maintainability — Strong typing, contract enforcement, modular architecture

Core Stack

Framework

TechnologyVersionPurpose
Next.js15.1.7React meta-framework (App Router)
React18.3.1UI library (concurrent features)
TypeScript5.xType system (strict mode)

Why Next.js 15?

  • App Router — File-based routing with layouts, server components
  • Server Components — Zero JS for static content (faster loads)
  • Turbopack — Rust-based bundler (7x faster than Webpack)
  • Incremental Static Regeneration (ISR) — Static + dynamic hybrid
  • Edge Functions — Deploy backend logic globally (low latency)

Why TypeScript?

  • Contract enforcement at compile time
  • IntelliSense for adapters, contracts, components
  • Refactoring safety (rename, move, extract)
  • Self-documenting code (interfaces as specs)

UI Components

Component Library

TechnologyVersionPurpose
Radix UILatestUnstyled, accessible primitives
lucide-reactLatestIcon library (1000+ icons)
Tailwind CSS3.4.xUtility-first CSS framework

Radix UI Primitives Used:

  • Accordion — Collapsible evidence sections
  • Dialog — Modals for confirmation/details
  • Tooltip — VEE annotations, metric explanations
  • Tabs — Multi-view switching (e.g., chart types)
  • Select — Dropdowns for filters
  • Checkbox, RadioGroup — Form inputs

Why Radix UI?

  • Accessibility built-in — ARIA labels, keyboard navigation, focus management
  • Unstyled — Full Tailwind control (no CSS fights)
  • Headless — Compose your own UX (adapter pattern aligns perfectly)
  • Type-safe — TypeScript definitions included

Tailwind Configuration:

// tailwind.config.js
module.exports = {
  content: [
    './components/**/*.{js,ts,jsx,tsx}',
    './app/**/*.{js,ts,jsx,tsx}'
  ],
  theme: {
    extend: {
      colors: {
        vitruvyan: {
          primary: '#000000',
          accent: '#3b82f6'
        },
        metrics: {
          positive: '#10b981',
          negative: '#ef4444',
          neutral: '#6b7280'
        }
      },
      fontFamily: {
        mono: ['Inconsolata', 'monospace']
      }
    }
  }
};

Data Fetching & State

HTTP Client

TechnologyVersionPurpose
Native Fetch APIBuilt-inHTTP requests (modern browsers)
SWRLatest (optional)Client-side data fetching with cache

Why Native Fetch?

  • Next.js 15 extends fetch() with caching, revalidation
  • No external dependencies
  • TypeScript-friendly

SWR for Client-Side (optional):

import useSWR from 'swr';
 
const fetcher = (url: string) => fetch(url).then(r => r.json());
 
function MyComponent() {
  const { data, error } = useSWR('/api/graph/query', fetcher);
  
  if (error) return <div>Failed to load</div>;
  if (!data) return <div>Loading...</div>;
  
  return <div>{data.result}</div>;
}

State Management

No Redux/Zustand — React Server Components + Context API sufficient.

PatternUse Case
React Server ComponentsStatic data fetching (server-side)
React ContextGlobal state (auth, theme, domain)
useState/useReducerLocal component state (accordions, forms)
URL StateFilters, pagination (shareable links)

Auth Context Example:

// contexts/AuthContext.tsx
import { createContext, useContext, ReactNode } from 'react';
 
interface AuthContextType {
  user: User | null;
  login: (credentials: Credentials) => Promise<void>;
  logout: () => void;
}
 
const AuthContext = createContext<AuthContextType | undefined>(undefined);
 
export function AuthProvider({ children }: { children: ReactNode }) {
  // Implementation
  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}
 
export function useAuth() {
  const context = useContext(AuthContext);
  if (!context) throw new Error("useAuth must be used within AuthProvider");
  return context;
}

Styling & Design Tokens

CSS Framework

TechnologyVersionPurpose
Tailwind CSS3.4.xUtility-first CSS
PostCSSLatestCSS processing
AutoprefixerLatestBrowser compatibility

Design Tokens (components/theme/tokens.js):

export const tokens = {
  colors: {
    vitruvyan: {
      bg: 'bg-white dark:bg-gray-900',
      border: 'border-gray-200 dark:border-gray-700',
      text: 'text-gray-900 dark:text-gray-100'
    },
    metrics: {
      blue: 'bg-blue-50 border-blue-200 text-blue-900',
      green: 'bg-green-50 border-green-200 text-green-900',
      orange: 'bg-orange-50 border-orange-200 text-orange-900',
      red: 'bg-red-50 border-red-200 text-red-900'
    }
  },
  spacing: {
    card: { gap: '1rem', padding: '1.25rem' },
    section: { gap: '1.25rem' },
    metric: { gap: '0.75rem', padding: '1rem' }
  },
  radius: {
    card: '0.75rem',
    metric: '0.5rem',
    chip: '9999px'
  }
};

Dark Mode Support:

// app/layout.tsx
export default function RootLayout({ children }) {
  return (
    <html lang="en" className="dark">
      <body className="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
        {children}
      </body>
    </html>
  );
}

Markdown & Rich Text

Markdown Rendering

TechnologyVersionPurpose
react-markdownLatestMarkdown → React components
remark-gfmLatestGitHub Flavored Markdown (tables, strikethrough)
remark-mathLatestMath equations (KaTeX support)

Usage:

import Markdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
 
function NarrativeBlock({ text }: { text: string }) {
  return (
    <Markdown remarkPlugins={[remarkGfm]}>
      {text}
    </Markdown>
  );
}

Development Tools

Package Manager

TechnologyVersionPurpose
pnpmLatest (recommended)Fast, disk-efficient package manager
npm10.x (alternative)Standard Node.js package manager

Why pnpm?

  • 3x faster installs than npm
  • Shared disk cache — Single copy of dependencies across projects
  • Strict — No hoisting surprises (better reproducibility)

Installation:

npm install -g pnpm
pnpm install
pnpm dev

Linting & Formatting

TechnologyVersionPurpose
ESLintLatestTypeScript/React linting
PrettierLatestCode formatting
eslint-config-nextLatestNext.js-optimized rules

ESLint Config (.eslintrc.json):

{
  "extends": [
    "next/core-web-vitals",
    "prettier"
  ],
  "rules": {
    "no-console": "warn",
    "prefer-const": "error",
    "@typescript-eslint/no-unused-vars": "error"
  }
}

Prettier Config (.prettierrc):

{
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "es5",
  "printWidth": 100
}

Testing

TechnologyVersionPurpose
JestLatestUnit test runner
React Testing LibraryLatestComponent testing
PlaywrightLatest (optional)E2E testing

Jest Config (jest.config.js):

module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/$1'
  }
};

Example Adapter Test:

import { ConversationalAdapter } from '@/components/adapters/ConversationalAdapter';
 
describe('ConversationalAdapter', () => {
  it('should match conversational intent', () => {
    const adapter = new ConversationalAdapter();
    expect(adapter.match({ intent: 'conversational' })).toBe(true);
  });
 
  it('should produce valid UIResponsePayload', () => {
    const adapter = new ConversationalAdapter();
    const state = { summary: 'Test', intent: 'conversational' };
    const payload = adapter.map(state);
    
    expect(payload.narrative).toBeDefined();
    expect(payload.narrative.text).toBe('Test');
  });
});

Build & Deployment

Build System

TechnologyVersionPurpose
TurbopackBuilt-in Next.js 15Development bundler
SWCBuilt-in Next.js 15TypeScript/JSX compiler (Rust)

Build Commands:

pnpm dev       # Development server (http://localhost:3000)
pnpm build     # Production build (optimized)
pnpm start     # Production server
pnpm lint      # ESLint check
pnpm test      # Run tests

Build Output:

.next/
├── static/           # Static assets (images, fonts)
├── server/           # Server-side code
└── cache/            # Build cache

Deployment Targets

PlatformConfigurationNotes
VercelZero-configOfficial Next.js host (Edge Functions, ISR)
DockerDockerfile includedSelf-hosted (Kubernetes, VPS)
Static Exportoutput: 'export'CDN deployment (no SSR)

Docker Deployment:

# Dockerfile
FROM node:20-alpine AS base
WORKDIR /app
 
# Dependencies
FROM base AS deps
COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm && pnpm install --frozen-lockfile
 
# Build
FROM base AS builder
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
 
# Production
FROM base AS runner
ENV NODE_ENV production
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
 
EXPOSE 3000
CMD ["node", "server.js"]

Build Command:

docker build -t vitruvyan-ui .
docker run -p 3000:3000 vitruvyan-ui

Environment Configuration

Environment Variables

VariableExamplePurpose
NEXT_PUBLIC_API_GRAPH_URLhttp://localhost:8420LangGraph API endpoint
NEXT_PUBLIC_API_CONCLAVE_URLhttp://localhost:8200Synaptic Conclave API
NEXT_PUBLIC_ENVproductionEnvironment name

.env.local (development):

NEXT_PUBLIC_API_GRAPH_URL=http://localhost:8420
NEXT_PUBLIC_API_CONCLAVE_URL=http://localhost:8200
NEXT_PUBLIC_ENV=development

Access in code:

const apiUrl = process.env.NEXT_PUBLIC_API_GRAPH_URL;

Performance Optimizations

Code Splitting

Next.js automatically code-splits by route:

  • Each route = separate JavaScript bundle
  • Shared code extracts to _app chunk
  • Dynamic imports for heavy components

Dynamic Import Example:

import dynamic from 'next/dynamic';
 
const HeavyChart = dynamic(() => import('@/components/HeavyChart'), {
  loading: () => <p>Loading chart...</p>,
  ssr: false // Disable server-side rendering
});

Image Optimization

Next.js <Image> component:

  • Automatic WebP conversion
  • Lazy loading (off-screen images)
  • Responsive sizes
import Image from 'next/image';
 
<Image
  src="/logo.png"
  alt="Vitruvyan Logo"
  width={200}
  height={50}
  priority // Above-the-fold images
/>

Font Optimization

Next.js next/font (built-in):

import { Inter } from 'next/font/google';
 
const inter = Inter({ subsets: ['latin'] });
 
export default function RootLayout({ children }) {
  return (
    <html lang="en" className={inter.className}>
      <body>{children}</body>
    </html>
  );
}

Why next/font?

  • Zero layout shift (font metrics precomputed)
  • Self-hosted fonts (no Google CDN request)
  • Automatic subset selection

Accessibility (WCAG 2.1 AA)

Tools

TechnologyVersionPurpose
axe DevToolsBrowser extensionAccessibility auditing
eslint-plugin-jsx-a11yLatestAccessibility linting

Key Requirements:

  • Keyboard navigation (Tab, Enter, Escape, Arrow keys)
  • Screen reader labels (aria-label, aria-describedby)
  • Color contrast ≥ 4.5:1 (text), ≥ 3:1 (UI)
  • Focus indicators visible

Example:

<button
  onClick={handleClick}
  aria-label="Close dialog"
  className="focus:outline-none focus:ring-2 focus:ring-blue-500"
>
  <X size={20} aria-hidden="true" />
</button>

Monitoring & Analytics

Error Tracking

TechnologyOptionalPurpose
SentryYesError tracking, performance monitoring

Setup (optional):

pnpm add @sentry/nextjs
npx @sentry/wizard -i nextjs

Web Vitals

Built-in to Next.js:

// app/layout.tsx
export function reportWebVitals(metric) {
  console.log(metric); // { id, name, value, label }
  // Send to analytics endpoint
}

Tracked Metrics:

  • TTFB — Time to First Byte
  • FCP — First Contentful Paint
  • LCP — Largest Contentful Paint
  • CLS — Cumulative Layout Shift
  • FID — First Input Delay

Version Matrix

TechnologyVersionReleaseStatus
Next.js15.1.7Jan 2026✅ Production
React18.3.1Apr 2024✅ Stable
TypeScript5.7.2Jan 2026✅ Production
Tailwind CSS3.4.16Dec 2024✅ Production
Radix UI1.2.xOngoing✅ Stable
pnpm9.15.2Feb 2026✅ Recommended

Upgrade Strategy

Minor Version Upgrades (Safe)

pnpm update --latest --interactive

Review changelogs:


Major Version Upgrades (Breaking Changes)

  1. Read migration guide (official docs)
  2. Test in staging environment
  3. Run compatibility checks:
    pnpm build
    pnpm test
    pnpm lint
  4. Incremental rollout (feature flags, canary deployment)

References


Last updated: Feb 20, 2026 21:30 UTC