Utility Libraries
Core utility functions, application constants, color maps, and formatting helpers used throughout the dashboard.
Table of contents
src/lib/utils.ts
Tailwind CSS class merging utility powered by clsx and tailwind-merge. This is the standard cn() helper used by shadcn/ui components.
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
/**
* Merge Tailwind CSS classes with conflict resolution.
*
* Accepts any combination of strings, arrays, objects, and conditional
* expressions supported by `clsx`, then deduplicates and resolves
* conflicting Tailwind utilities via `tailwind-merge`.
*
* @example
* cn('px-4 py-2', isActive && 'bg-primary text-primary-foreground')
* cn('text-sm', className) // allows consumer overrides
*/
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
src/lib/constants.ts
Application-wide constants including API configuration, color maps, agent names, gate definitions, and navigation items.
/* ------------------------------------------------------------------ */
/* API Configuration */
/* ------------------------------------------------------------------ */
/** Base URL for the MetaForge Gateway REST API */
export const API_BASE_URL: string =
import.meta.env.VITE_API_URL || 'http://localhost:3000';
/** WebSocket URL for real-time events */
export const WS_URL: string =
import.meta.env.VITE_WS_URL || 'ws://localhost:3000/ws';
/** Whether mock API mode is enabled */
export const MOCK_API_ENABLED: boolean =
import.meta.env.VITE_MOCK_API === 'true';
/* ------------------------------------------------------------------ */
/* Color Maps */
/* ------------------------------------------------------------------ */
import type { RiskLevel, LifecycleStatus } from '@/types/bom';
import type { SessionStatus } from '@/types/session';
import type { AgentStatus } from '@/types/agent';
import type { ComplianceMarket } from '@/types/compliance';
/**
* Tailwind color classes for risk levels.
* Each entry provides a text class and a background class.
*/
export const RISK_COLORS: Record<RiskLevel, { text: string; bg: string }> = {
low: { text: 'text-emerald-600', bg: 'bg-emerald-100 dark:bg-emerald-900/30' },
medium: { text: 'text-amber-600', bg: 'bg-amber-100 dark:bg-amber-900/30' },
high: { text: 'text-orange-600', bg: 'bg-orange-100 dark:bg-orange-900/30' },
critical: { text: 'text-red-600', bg: 'bg-red-100 dark:bg-red-900/30' },
};
/** Tailwind color classes for component lifecycle statuses */
export const LIFECYCLE_COLORS: Record<LifecycleStatus, { text: string; bg: string }> = {
active: { text: 'text-emerald-600', bg: 'bg-emerald-100 dark:bg-emerald-900/30' },
nrnd: { text: 'text-amber-600', bg: 'bg-amber-100 dark:bg-amber-900/30' },
eol: { text: 'text-orange-600', bg: 'bg-orange-100 dark:bg-orange-900/30' },
obsolete: { text: 'text-red-600', bg: 'bg-red-100 dark:bg-red-900/30' },
unknown: { text: 'text-gray-500', bg: 'bg-gray-100 dark:bg-gray-800/30' },
};
/** Tailwind color classes for session statuses */
export const SESSION_STATUS_COLORS: Record<SessionStatus, { text: string; bg: string; dot: string }> = {
created: { text: 'text-gray-600', bg: 'bg-gray-100 dark:bg-gray-800/30', dot: 'bg-gray-400' },
running: { text: 'text-blue-600', bg: 'bg-blue-100 dark:bg-blue-900/30', dot: 'bg-blue-500' },
processing: { text: 'text-blue-600', bg: 'bg-blue-100 dark:bg-blue-900/30', dot: 'bg-blue-500' },
pending_approval: { text: 'text-amber-600', bg: 'bg-amber-100 dark:bg-amber-900/30', dot: 'bg-amber-500' },
approved: { text: 'text-emerald-600', bg: 'bg-emerald-100 dark:bg-emerald-900/30', dot: 'bg-emerald-500' },
applying: { text: 'text-violet-600', bg: 'bg-violet-100 dark:bg-violet-900/30', dot: 'bg-violet-500' },
completed: { text: 'text-emerald-600', bg: 'bg-emerald-100 dark:bg-emerald-900/30', dot: 'bg-emerald-500' },
failed: { text: 'text-red-600', bg: 'bg-red-100 dark:bg-red-900/30', dot: 'bg-red-500' },
rejected: { text: 'text-red-600', bg: 'bg-red-100 dark:bg-red-900/30', dot: 'bg-red-500' },
};
/** Tailwind color classes for agent statuses */
export const AGENT_STATUS_COLORS: Record<AgentStatus, { text: string; bg: string; dot: string }> = {
idle: { text: 'text-gray-600', bg: 'bg-gray-100 dark:bg-gray-800/30', dot: 'bg-gray-400' },
running: { text: 'text-blue-600', bg: 'bg-blue-100 dark:bg-blue-900/30', dot: 'bg-blue-500' },
error: { text: 'text-red-600', bg: 'bg-red-100 dark:bg-red-900/30', dot: 'bg-red-500' },
disabled: { text: 'text-gray-400', bg: 'bg-gray-50 dark:bg-gray-900/30', dot: 'bg-gray-300' },
};
/** Gate status color mapping */
export const GATE_STATUS_COLORS: Record<string, { text: string; bg: string }> = {
'not-ready': { text: 'text-gray-600', bg: 'bg-gray-100 dark:bg-gray-800/30' },
'at-risk': { text: 'text-amber-600', bg: 'bg-amber-100 dark:bg-amber-900/30' },
ready: { text: 'text-emerald-600', bg: 'bg-emerald-100 dark:bg-emerald-900/30' },
passed: { text: 'text-blue-600', bg: 'bg-blue-100 dark:bg-blue-900/30' },
};
/** Compliance market brand colors */
export const COMPLIANCE_MARKET_COLORS: Record<ComplianceMarket, { text: string; bg: string; border: string }> = {
UKCA: {
text: 'text-indigo-700 dark:text-indigo-400',
bg: 'bg-indigo-100 dark:bg-indigo-900/30',
border: 'border-indigo-300 dark:border-indigo-700',
},
CE: {
text: 'text-blue-700 dark:text-blue-400',
bg: 'bg-blue-100 dark:bg-blue-900/30',
border: 'border-blue-300 dark:border-blue-700',
},
FCC: {
text: 'text-emerald-700 dark:text-emerald-400',
bg: 'bg-emerald-100 dark:bg-emerald-900/30',
border: 'border-emerald-300 dark:border-emerald-700',
},
};
/* ------------------------------------------------------------------ */
/* Agent Display Names */
/* ------------------------------------------------------------------ */
/**
* Mapping from agent short codes to human-readable display names.
* Covers all 18 agents across Phase 1, 2, and 3.
*/
export const AGENT_DISPLAY_NAMES: Record<string, string> = {
// Phase 1 agents (6-7)
REQ: 'Requirements Agent',
ARCH: 'Architecture Agent',
PWR: 'Power Budget Agent',
SCH: 'Schematic Agent',
BOM: 'BOM Agent',
DFM: 'DFM Agent',
COMP: 'Compliance Agent',
// Phase 2 agents (additional)
IND: 'Industrial Design Agent',
MECH: 'Mechanical Engineering Agent',
FW: 'Firmware Agent',
SIM: 'Simulation Agent',
PROTO: 'Prototyping Agent',
TEST: 'Testing Agent',
MFG: 'Manufacturing Agent',
COST: 'Cost Engineering Agent',
SYS: 'Systems Engineering Agent',
// Phase 3 agents (additional)
FIELD: 'Field Engineering Agent',
SAFE: 'Safety Engineering Agent',
};
/* ------------------------------------------------------------------ */
/* Gate Definitions */
/* ------------------------------------------------------------------ */
import type { GateType } from '@/types/gate';
/** Manufacturing gate metadata */
export const GATE_DEFINITIONS: Record<GateType, { label: string; description: string }> = {
EVT: {
label: 'Engineering Validation Test',
description:
'Validates that the design meets functional requirements. Focuses on core functionality, power performance, and basic environmental testing.',
},
DVT: {
label: 'Design Validation Test',
description:
'Validates that the product meets design specifications under expected conditions. Includes reliability, compliance, and manufacturing readiness.',
},
PVT: {
label: 'Production Validation Test',
description:
'Validates the production process and confirms that mass-manufactured units meet quality and performance targets.',
},
};
/* ------------------------------------------------------------------ */
/* Navigation */
/* ------------------------------------------------------------------ */
/**
* Sidebar navigation items.
* The `icon` field references a Lucide React icon name.
*/
export interface NavItem {
label: string;
path: string;
icon: string;
}
export const NAV_ITEMS: NavItem[] = [
{ label: 'Overview', path: '/', icon: 'LayoutDashboard' },
{ label: 'Sessions', path: '/sessions', icon: 'Play' },
{ label: 'Agents', path: '/agents', icon: 'Bot' },
{ label: 'Digital Twin', path: '/digital-twin', icon: 'Box' },
{ label: 'BOM', path: '/bom', icon: 'ClipboardList' },
{ label: 'Compliance', path: '/compliance', icon: 'ShieldCheck' },
{ label: 'Testing', path: '/testing', icon: 'FlaskConical' },
{ label: 'Supply Chain', path: '/supply-chain', icon: 'Truck' },
{ label: 'Approvals', path: '/approvals', icon: 'CheckSquare' },
{ label: 'Settings', path: '/settings', icon: 'Settings' },
];
/* ------------------------------------------------------------------ */
/* Misc Constants */
/* ------------------------------------------------------------------ */
/** Default pagination page size */
export const DEFAULT_PAGE_SIZE = 25;
/** WebSocket reconnection parameters */
export const WS_RECONNECT = {
initialDelay: 1_000,
maxDelay: 30_000,
backoffMultiplier: 2,
} as const;
/** Lead time threshold (weeks) above which a component is flagged */
export const LONG_LEAD_THRESHOLD_WEEKS = 12;
/** RPN threshold above which an FMEA entry is flagged as high priority */
export const HIGH_RPN_THRESHOLD = 200;
src/lib/format.ts
Formatting utility functions for dates, currency, file sizes, percentages, and domain-specific values.
import { formatDistanceToNow, format, parseISO } from 'date-fns';
/* ------------------------------------------------------------------ */
/* Date & Time */
/* ------------------------------------------------------------------ */
/**
* Format an ISO date string into a human-readable date.
*
* @example formatDate('2025-03-15T10:30:00Z') // "Mar 15, 2025, 10:30 AM"
*/
export function formatDate(date: string): string {
try {
return format(parseISO(date), 'MMM d, yyyy, h:mm a');
} catch {
return date;
}
}
/**
* Format an ISO date string as a relative time string.
*
* @example formatRelativeTime('2025-03-15T10:30:00Z') // "2 hours ago"
*/
export function formatRelativeTime(date: string): string {
try {
return formatDistanceToNow(parseISO(date), { addSuffix: true });
} catch {
return date;
}
}
/**
* Format an ISO date string into a short date (no time).
*
* @example formatShortDate('2025-03-15T10:30:00Z') // "Mar 15, 2025"
*/
export function formatShortDate(date: string): string {
try {
return format(parseISO(date), 'MMM d, yyyy');
} catch {
return date;
}
}
/* ------------------------------------------------------------------ */
/* Currency */
/* ------------------------------------------------------------------ */
/**
* Format a number as a USD currency string.
*
* @example formatCurrency(1234.5) // "$1,234.50"
*/
export function formatCurrency(amount: number): string {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}).format(amount);
}
/* ------------------------------------------------------------------ */
/* File Size */
/* ------------------------------------------------------------------ */
const FILE_SIZE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB'] as const;
/**
* Format a byte count into a human-readable file size string.
*
* @example formatFileSize(1_258_291) // "1.2 MB"
*/
export function formatFileSize(bytes: number): string {
if (bytes === 0) return '0 B';
const exponent = Math.min(
Math.floor(Math.log(Math.abs(bytes)) / Math.log(1024)),
FILE_SIZE_UNITS.length - 1,
);
const value = bytes / Math.pow(1024, exponent);
const unit = FILE_SIZE_UNITS[exponent];
return `${value.toFixed(exponent === 0 ? 0 : 1)} ${unit}`;
}
/* ------------------------------------------------------------------ */
/* Percentage */
/* ------------------------------------------------------------------ */
/**
* Format a decimal or whole number as a percentage string.
* Values > 1 are treated as already being percentages.
*
* @example formatPercentage(0.853) // "85.3%"
* @example formatPercentage(85.3) // "85.3%"
*/
export function formatPercentage(value: number): string {
const pct = value > 1 ? value : value * 100;
return `${pct.toFixed(1)}%`;
}
/* ------------------------------------------------------------------ */
/* Lead Time */
/* ------------------------------------------------------------------ */
/**
* Format a lead time value (in weeks) into a human-readable string.
* Uses months for values >= 8 weeks.
*
* @example formatLeadTime(4) // "4 weeks"
* @example formatLeadTime(12) // "3 months"
* @example formatLeadTime(1) // "1 week"
*/
export function formatLeadTime(weeks: number): string {
if (weeks < 1) return 'In stock';
if (weeks === 1) return '1 week';
if (weeks < 8) return `${weeks} weeks`;
const months = Math.round(weeks / 4.33);
return months === 1 ? '1 month' : `${months} months`;
}
/* ------------------------------------------------------------------ */
/* FMEA */
/* ------------------------------------------------------------------ */
/**
* Calculate the Risk Priority Number (RPN) for an FMEA entry.
* RPN = Severity x Occurrence x Detection
* Each factor ranges from 1 to 10, so RPN ranges from 1 to 1000.
*
* @example formatRPN(8, 4, 6) // 192
*/
export function formatRPN(severity: number, occurrence: number, detection: number): number {
return severity * occurrence * detection;
}
/* ------------------------------------------------------------------ */
/* String */
/* ------------------------------------------------------------------ */
/**
* Truncate a string to a maximum length, appending an ellipsis if truncated.
*
* @example truncate('Hello, world!', 8) // "Hello..."
*/
export function truncate(str: string, length: number): string {
if (str.length <= length) return str;
return `${str.slice(0, length - 3)}...`;
}
/* ------------------------------------------------------------------ */
/* Numbers */
/* ------------------------------------------------------------------ */
/**
* Format a large number with compact notation.
*
* @example formatCompactNumber(1_500_000) // "1.5M"
*/
export function formatCompactNumber(value: number): string {
return new Intl.NumberFormat('en-US', {
notation: 'compact',
maximumFractionDigits: 1,
}).format(value);
}
/**
* Format a number with thousand separators.
*
* @example formatNumber(12345) // "12,345"
*/
export function formatNumber(value: number): string {
return new Intl.NumberFormat('en-US').format(value);
}
/* ------------------------------------------------------------------ */
/* Status Labels */
/* ------------------------------------------------------------------ */
/**
* Convert a snake_case or kebab-case status string into a Title Case label.
*
* @example formatStatusLabel('pending_approval') // "Pending Approval"
* @example formatStatusLabel('not-ready') // "Not Ready"
*/
export function formatStatusLabel(status: string): string {
return status
.replace(/[_-]/g, ' ')
.replace(/\b\w/g, (char) => char.toUpperCase());
}