demo / SPEC-DEMO-GUI.md
AIVLAD's picture
cleanup: remove obsolete code and unused fields
6754f1c
# Wall Construction API - GUI Technical Specification
## Philosophy: Minimal Dependencies, Maximum Standards
This specification defines a **production-ready React GUI** with an absolute minimal dependency footprint while adhering to 2025 industry best practices.
**Core Principle**: Every dependency must justify its existence. No bloat, no convenience libraries that add marginal value.
---
## Technology Stack
### Framework & Build Tools
- **React 19.2.0** (October 2025 release)
- Latest stable with React Compiler
- Actions API for async operations
- Enhanced form handling
- Improved hydration and error reporting
- **Vite 7.0** (2025 release)
- Requires Node.js 20.19+ or 22.12+
- ESM-only distribution
- Native `require(esm)` support
- 5x faster builds than Vite 6
- Instant HMR (Hot Module Replacement)
### Styling & UI
- **Tailwind CSS v4.0** (January 2025)
- Zero configuration setup
- Single CSS import: `@import "tailwindcss"`
- Built-in Vite plugin
- 5x faster full builds, 100x faster incremental builds
- Modern CSS features (cascade layers, @property, color-mix)
- P3 color palette for vibrant displays
- Container queries support
### Data Visualization
- **Recharts 2.x** (latest)
- 24.8k GitHub stars
- React-native component API
- SVG-based rendering
- Responsive by default
- Composable chart primitives
- Built on D3.js submodules
### HTTP & State
- **Native Fetch API** (no axios, no external HTTP libs)
- **React useState/useReducer** (no Redux, no Zustand, no external state libs)
---
## Dependencies
### Production Dependencies (3 total)
```json
{
"react": "^19.2.0",
"react-dom": "^19.2.0",
"recharts": "^2.15.0"
}
```
### Development Dependencies (2 total)
```json
{
"vite": "^7.0.0",
"@tailwindcss/vite": "^4.0.0"
}
```
**Total: 5 dependencies**
---
## Project Structure
```
wall-construction-gui/
β”œβ”€β”€ public/
β”‚ └── favicon.ico
β”œβ”€β”€ src/
β”‚ β”œβ”€β”€ components/ # Reusable UI components
β”‚ β”‚ β”œβ”€β”€ Button.jsx
β”‚ β”‚ β”œβ”€β”€ Card.jsx
β”‚ β”‚ β”œβ”€β”€ Input.jsx
β”‚ β”‚ β”œβ”€β”€ Select.jsx
β”‚ β”‚ β”œβ”€β”€ DatePicker.jsx
β”‚ β”‚ β”œβ”€β”€ Spinner.jsx
β”‚ β”‚ β”œβ”€β”€ ErrorBoundary.jsx
β”‚ β”‚ └── charts/
β”‚ β”‚ β”œβ”€β”€ LineChart.jsx
β”‚ β”‚ β”œβ”€β”€ BarChart.jsx
β”‚ β”‚ └── AreaChart.jsx
β”‚ β”œβ”€β”€ pages/ # Page-level components
β”‚ β”‚ β”œβ”€β”€ Dashboard.jsx
β”‚ β”‚ β”œβ”€β”€ ProfileDetail.jsx
β”‚ β”‚ β”œβ”€β”€ SimulationForm.jsx
β”‚ β”‚ β”œβ”€β”€ SimulationResults.jsx
β”‚ β”‚ β”œβ”€β”€ DailyIceUsage.jsx
β”‚ β”‚ └── CostAnalytics.jsx
β”‚ β”œβ”€β”€ hooks/ # Custom React hooks
β”‚ β”‚ β”œβ”€β”€ useApi.js
β”‚ β”‚ β”œβ”€β”€ useFetch.js
β”‚ β”‚ └── useDebounce.js
β”‚ β”œβ”€β”€ utils/ # Helper functions
β”‚ β”‚ β”œβ”€β”€ api.js # Fetch wrapper
β”‚ β”‚ β”œβ”€β”€ formatters.js # Number/date formatting
β”‚ β”‚ └── constants.js # App constants
β”‚ β”œβ”€β”€ App.jsx # Root component
β”‚ β”œβ”€β”€ main.jsx # Entry point
β”‚ └── index.css # Global styles
β”œβ”€β”€ index.html
β”œβ”€β”€ vite.config.js
└── package.json
```
---
## Setup Instructions
### 1. Initialize Project
```bash
# Create Vite project
npm create vite@latest wall-construction-gui -- --template react
cd wall-construction-gui
```
### 2. Install Dependencies
```bash
# Install production dependencies
npm install react@19.2.0 react-dom@19.2.0 recharts
# Install dev dependencies
npm install -D vite@7 @tailwindcss/vite@4
```
### 3. Configure Vite
**vite.config.js**
```javascript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [
react(),
tailwindcss()
],
server: {
port: 5173,
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true
}
}
}
})
```
### 4. Setup Tailwind CSS
**src/index.css**
```css
@import "tailwindcss";
/* CSS Custom Properties for Theme */
:root {
--color-primary: #3b82f6;
--color-secondary: #64748b;
--color-success: #10b981;
--color-danger: #ef4444;
--color-warning: #f59e0b;
--color-ice: #93c5fd;
--color-gold: #fbbf24;
}
/* Global Styles */
body {
@apply bg-gray-50 text-gray-900;
}
```
### 5. Run Development Server
```bash
npm run dev
```
Server starts at `http://localhost:5173`
---
## Component Architecture
### Base Components
#### Button Component
```jsx
// src/components/Button.jsx
export default function Button({
children,
variant = 'primary',
onClick,
disabled = false,
type = 'button'
}) {
const variants = {
primary: 'bg-blue-600 hover:bg-blue-700 text-white',
secondary: 'bg-gray-200 hover:bg-gray-300 text-gray-900',
danger: 'bg-red-600 hover:bg-red-700 text-white'
}
return (
<button
type={type}
onClick={onClick}
disabled={disabled}
className={`
px-4 py-2 rounded-lg font-medium
transition-colors duration-200
disabled:opacity-50 disabled:cursor-not-allowed
${variants[variant]}
`}
>
{children}
</button>
)
}
```
#### Card Component
```jsx
// src/components/Card.jsx
export default function Card({ children, className = '' }) {
return (
<div className={`bg-white rounded-lg shadow-md p-6 ${className}`}>
{children}
</div>
)
}
```
#### Input Component
```jsx
// src/components/Input.jsx
export default function Input({
label,
type = 'text',
value,
onChange,
placeholder,
required = false,
error
}) {
return (
<div className="mb-4">
{label && (
<label className="block text-sm font-medium text-gray-700 mb-2">
{label}
{required && <span className="text-red-500 ml-1">*</span>}
</label>
)}
<input
type={type}
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder={placeholder}
required={required}
className={`
w-full px-4 py-2 border rounded-lg
focus:outline-none focus:ring-2 focus:ring-blue-500
${error ? 'border-red-500' : 'border-gray-300'}
`}
/>
{error && (
<p className="text-red-500 text-sm mt-1">{error}</p>
)}
</div>
)
}
```
### Chart Components
#### LineChart Wrapper
```jsx
// src/components/charts/LineChart.jsx
import {
LineChart as RechartsLine,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer
} from 'recharts'
export default function LineChart({ data, dataKey, xKey, color = '#3b82f6' }) {
return (
<ResponsiveContainer width="100%" height={300}>
<RechartsLine data={data}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey={xKey} />
<YAxis />
<Tooltip />
<Line
type="monotone"
dataKey={dataKey}
stroke={color}
strokeWidth={2}
/>
</RechartsLine>
</ResponsiveContainer>
)
}
```
#### BarChart Wrapper
```jsx
// src/components/charts/BarChart.jsx
import {
BarChart as RechartsBar,
Bar,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer
} from 'recharts'
export default function BarChart({ data, dataKey, xKey, color = '#10b981' }) {
return (
<ResponsiveContainer width="100%" height={300}>
<RechartsBar data={data}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey={xKey} />
<YAxis />
<Tooltip />
<Bar dataKey={dataKey} fill={color} />
</RechartsBar>
</ResponsiveContainer>
)
}
```
---
## Pages
### 1. Dashboard
**File**: `src/pages/Dashboard.jsx`
**Purpose**: Display simulation results overview with total statistics
**Data Source**: `GET /api/simulation/overview/total/`
**Components**:
- Grid of profile cards
- Summary statistics (total ice, total cost, total feet)
- "View Details" button per profile
- "+ New Profile" button
**Key Features**:
- Responsive grid (1 col mobile, 2 col tablet, 3 col desktop)
- Loading states with skeleton cards
- Empty state when no profiles exist
- Filter by active/inactive status
### 2. ProfileDetail
**File**: `src/pages/ProfileDetail.jsx`
**Purpose**: Detailed view of simulation results by day
**Data Source**: `GET /api/simulation/overview/{day}/` or `GET /api/simulation/overview/all-by-day/`
**Components**:
- Profile header (name, team lead)
- Summary cards (total cost, total ice, avg/day)
- Line chart: Daily cost trend
- Line chart: Daily feet built
- Area chart: Cumulative cost
- Data table: Daily breakdown
**Key Features**:
- Date range picker (default: last 30 days)
- Responsive charts
- Download CSV button (client-side generation)
- Print-friendly layout
### 3. SimulationForm
**File**: `src/pages/SimulationForm.jsx`
**Purpose**: Run wall construction simulation with multi-profile configuration
**Data Source**: `POST /api/simulation/simulate/`
**Components**:
- Configuration textarea (multi-line input for wall heights)
- Number of teams input (default: 10)
- Run Simulation button
- Example config link/tooltip
- Results display area (summary statistics)
- View Logs button (navigates to SimulationResults)
**Key Features**:
- Config format validation (numbers only, range 0-30)
- Example: "5, 10, 15" creates 3 profiles with heights 5, 10, 15
- Real-time simulation execution
- Summary display: total ice used, total cost, days to completion
- Success/error notifications
- Link to detailed logs and daily breakdown
### 4. SimulationResults
**File**: `src/pages/SimulationResults.jsx`
**Purpose**: Display detailed simulation logs and daily progress
**Data Source**: Read from `/logs/team_*.log` files or use `GET /api/simulation/overview/all-by-day/`
**Components**:
- Log viewer (scrollable text area with team logs)
- Day-by-day summary table
- Team status indicators (working/relieved)
- Simulation summary statistics
**Key Features**:
- Syntax-highlighted log display
- Filter logs by team number
- Download logs button
- Refresh simulation button
### 5. DailyIceUsage
**File**: `src/pages/DailyIceUsage.jsx`
**Purpose**: Breakdown of ice usage by wall section for a specific date
**Data Source**: `GET /api/profiles/{id}/daily-ice-usage/?date=YYYY-MM-DD`
**Components**:
- Profile selector
- Date picker
- Summary card (total feet, total ice)
- Horizontal bar chart (ice by section)
- Data table (section breakdown with percentages)
**Key Features**:
- Client-side percentage calculations
- Color-coded bars
- Sortable table columns
- Export to CSV
### 6. CostAnalytics
**File**: `src/pages/CostAnalytics.jsx`
**Purpose**: Multi-chart cost analytics dashboard
**Data Source**: `GET /api/simulation/overview/all-by-day/`
**Components**:
- Date range selector
- 4 summary cards (total cost, total feet, avg/day, days)
- Line chart: Daily cost
- Line chart: Daily feet built
- Area chart: Cumulative cost
- Compare profiles (optional enhancement)
**Key Features**:
- Multiple chart views in grid layout
- Responsive breakpoints
- Print view
- Share URL with date filters in query params
---
## API Integration
### API Client
**src/utils/api.js**
```javascript
const API_BASE = import.meta.env.DEV ? '/api' : 'https://api.example.com/api'
class ApiError extends Error {
constructor(message, status, data) {
super(message)
this.status = status
this.data = data
}
}
async function request(endpoint, options = {}) {
const url = `${API_BASE}${endpoint}`
const config = {
headers: {
'Content-Type': 'application/json',
...options.headers
},
...options
}
try {
const response = await fetch(url, config)
const data = await response.json()
if (!response.ok) {
throw new ApiError(
data.message || 'Request failed',
response.status,
data
)
}
return data
} catch (error) {
if (error instanceof ApiError) {
throw error
}
throw new ApiError('Network error', 0, { originalError: error })
}
}
export const api = {
// Profiles (CRUD endpoints for manual management)
getProfiles: () => request('/profiles/'),
getProfile: (id) => request(`/profiles/${id}/`),
createProfile: (data) => request('/profiles/', {
method: 'POST',
body: JSON.stringify(data)
}),
// Simulation
runSimulation: (config, numTeams = 10) => request('/simulation/simulate/', {
method: 'POST',
body: JSON.stringify({ config, num_teams: numTeams })
}),
// Analytics
getOverviewTotal: () => request('/simulation/overview/total/'),
getOverviewByDay: (day) => request(`/simulation/overview/${day}/`),
getOverviewAllByDay: () => request('/simulation/overview/all-by-day/')
}
```
### Custom Hooks
**src/hooks/useFetch.js**
```javascript
import { useState, useEffect } from 'react'
export function useFetch(fetchFn, dependencies = []) {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
let cancelled = false
async function fetchData() {
try {
setLoading(true)
setError(null)
const result = await fetchFn()
if (!cancelled) {
setData(result)
}
} catch (err) {
if (!cancelled) {
setError(err)
}
} finally {
if (!cancelled) {
setLoading(false)
}
}
}
fetchData()
return () => {
cancelled = true
}
}, dependencies)
return { data, loading, error }
}
```
**Usage Example**:
```javascript
import { useFetch } from '../hooks/useFetch'
import { api } from '../utils/api'
function Dashboard() {
const { data: profiles, loading, error } = useFetch(() => api.getProfiles())
if (loading) return <Spinner />
if (error) return <ErrorMessage error={error} />
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{profiles.results.map(profile => (
<ProfileCard key={profile.id} profile={profile} />
))}
</div>
)
}
```
---
## State Management Strategy
### Component-Level State
Use `useState` for:
- Form inputs
- UI toggles (modals, dropdowns)
- Local loading/error states
### Lifted State
Use props drilling for:
- Shared data between sibling components
- Parent-child communication
**Example**:
```javascript
function App() {
const [currentView, setCurrentView] = useState('dashboard')
const [selectedProfile, setSelectedProfile] = useState(null)
return (
<div>
<Navigation view={currentView} onNavigate={setCurrentView} />
{currentView === 'dashboard' && (
<Dashboard onSelectProfile={setSelectedProfile} />
)}
{currentView === 'profile' && (
<ProfileDetail profile={selectedProfile} />
)}
</div>
)
}
```
### When to Add Context
Only add React Context if:
- Props drilling exceeds 3 levels
- Data is truly global (theme, auth, language)
- Performance profiling shows re-render issues
**Not needed for this app initially**.
---
## Routing Strategy
### Hash-Based Routing (Minimal Approach)
**src/App.jsx**
```javascript
import { useState, useEffect } from 'react'
import Dashboard from './pages/Dashboard'
import ProfileDetail from './pages/ProfileDetail'
import SimulationForm from './pages/SimulationForm'
import SimulationResults from './pages/SimulationResults'
function App() {
const [route, setRoute] = useState(window.location.hash.slice(1) || 'dashboard')
const [params, setParams] = useState({})
useEffect(() => {
const handleHashChange = () => {
const hash = window.location.hash.slice(1)
const [path, query] = hash.split('?')
setRoute(path || 'dashboard')
// Parse query params
const searchParams = new URLSearchParams(query)
const paramsObj = {}
searchParams.forEach((value, key) => {
paramsObj[key] = value
})
setParams(paramsObj)
}
window.addEventListener('hashchange', handleHashChange)
return () => window.removeEventListener('hashchange', handleHashChange)
}, [])
const navigate = (path, queryParams = {}) => {
const query = new URLSearchParams(queryParams).toString()
window.location.hash = query ? `${path}?${query}` : path
}
return (
<div className="min-h-screen bg-gray-50">
<nav className="bg-white shadow-sm mb-6">
<div className="max-w-7xl mx-auto px-4 py-4">
<div className="flex gap-4">
<button onClick={() => navigate('dashboard')}
className={route === 'dashboard' ? 'font-bold' : ''}>
Dashboard
</button>
<button onClick={() => navigate('simulation')}
className={route === 'simulation' ? 'font-bold' : ''}>
Run Simulation
</button>
<button onClick={() => navigate('results')}
className={route === 'results' ? 'font-bold' : ''}>
View Results
</button>
</div>
</div>
</nav>
<main className="max-w-7xl mx-auto px-4">
{route === 'dashboard' && <Dashboard navigate={navigate} />}
{route === 'profile' && <ProfileDetail profileId={params.id} navigate={navigate} />}
{route === 'simulation' && <SimulationForm navigate={navigate} />}
{route === 'results' && <SimulationResults navigate={navigate} />}
</main>
</div>
)
}
export default App
```
### URL Structure
```
/#dashboard
/#profile?id=1
/#simulation
/#results
/#ice-usage?id=1&date=2025-10-15
```
---
## Styling Guidelines
### Tailwind Utility Classes
- Use composition for common patterns
- Avoid inline style objects
- Keep classes readable (multi-line for complex components)
**Good**:
```jsx
<div className="
flex items-center justify-between
bg-white rounded-lg shadow-md
p-6 mb-4
hover:shadow-lg transition-shadow
">
```
**Bad**:
```jsx
<div style={{ display: 'flex', padding: '24px', background: 'white' }}>
```
### Responsive Design
Use Tailwind breakpoints:
- `sm:` - 640px
- `md:` - 768px
- `lg:` - 1024px
- `xl:` - 1280px
**Example**:
```jsx
<div className="
grid
grid-cols-1
md:grid-cols-2
lg:grid-cols-3
gap-6
">
```
### Color Palette
Use Tailwind's default colors + custom properties:
```css
:root {
--color-ice: #93c5fd; /* Light blue for ice theme */
--color-gold: #fbbf24; /* Gold for currency */
}
```
Apply in Tailwind:
```jsx
<div className="bg-[var(--color-ice)]">
```
---
## Error Handling
### Error Boundary Component
**src/components/ErrorBoundary.jsx**
```javascript
import { Component } from 'react'
class ErrorBoundary extends Component {
constructor(props) {
super(props)
this.state = { hasError: false, error: null }
}
static getDerivedStateFromError(error) {
return { hasError: true, error }
}
componentDidCatch(error, errorInfo) {
console.error('Error caught by boundary:', error, errorInfo)
}
render() {
if (this.state.hasError) {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<div className="bg-white p-8 rounded-lg shadow-lg max-w-md">
<h2 className="text-2xl font-bold text-red-600 mb-4">
Something went wrong
</h2>
<p className="text-gray-600 mb-4">
{this.state.error?.message || 'An unexpected error occurred'}
</p>
<button
onClick={() => window.location.reload()}
className="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700"
>
Reload Page
</button>
</div>
</div>
)
}
return this.props.children
}
}
export default ErrorBoundary
```
### API Error Handling
Display user-friendly error messages:
```javascript
function ErrorMessage({ error }) {
const getMessage = () => {
if (error.status === 404) return 'Resource not found'
if (error.status === 500) return 'Server error. Please try again later.'
if (error.status === 0) return 'Network error. Check your connection.'
return error.message || 'An error occurred'
}
return (
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
<p className="text-red-800">{getMessage()}</p>
</div>
)
}
```
---
## Performance Optimization
### Code Splitting (Future Enhancement)
When bundle size grows, use React.lazy:
```javascript
import { lazy, Suspense } from 'react'
const ProfileDetail = lazy(() => import('./pages/ProfileDetail'))
function App() {
return (
<Suspense fallback={<Spinner />}>
<ProfileDetail />
</Suspense>
)
}
```
### Memoization
Use React.memo for expensive list items:
```javascript
import { memo } from 'react'
const ProfileCard = memo(function ProfileCard({ profile }) {
return (
<Card>
<h3>{profile.name}</h3>
<p>{profile.team_lead}</p>
</Card>
)
})
```
### Debouncing
For search/filter inputs:
```javascript
// src/hooks/useDebounce.js
import { useState, useEffect } from 'react'
export function useDebounce(value, delay = 300) {
const [debouncedValue, setDebouncedValue] = useState(value)
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value)
}, delay)
return () => clearTimeout(timer)
}, [value, delay])
return debouncedValue
}
```
---
## Build & Deployment
### Development Build
```bash
npm run dev
```
### Production Build
```bash
npm run build
```
Output: `dist/` directory with optimized static files
### Preview Production Build
```bash
npm run preview
```
### Build Optimizations (Vite 7)
- Automatic code splitting
- CSS minification
- Tree shaking
- Asset optimization (images, fonts)
- Source maps (optional)
**vite.config.js** (production settings):
```javascript
export default defineConfig({
plugins: [react(), tailwindcss()],
build: {
sourcemap: false,
minify: 'esbuild',
target: 'es2020',
rollupOptions: {
output: {
manualChunks: {
'react-vendor': ['react', 'react-dom'],
'charts': ['recharts']
}
}
}
}
})
```
---
## HuggingFace Space Deployment
### Static Site Setup
**Dockerfile**
```dockerfile
FROM nginx:alpine
# Copy built files
COPY dist /usr/share/nginx/html
# Copy nginx config
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 7860
CMD ["nginx", "-g", "daemon off;"]
```
**nginx.conf**
```nginx
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
server {
listen 7860;
server_name _;
root /usr/share/nginx/html;
index index.html;
# SPA fallback
location / {
try_files $uri $uri/ /index.html;
}
# API proxy (if Django backend in same Space)
location /api {
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
}
```
### Space Configuration
**README.md** (HuggingFace header):
```yaml
---
title: Wall Construction Tracker
emoji: 🏰
colorFrom: blue
colorTo: gray
sdk: docker
app_port: 7860
---
```
### Environment Variables
**src/utils/api.js**:
```javascript
const API_BASE = import.meta.env.VITE_API_BASE || '/api'
```
**.env.production**:
```
VITE_API_BASE=https://your-api-domain.com/api
```
---
## Testing Strategy (Future Enhancement)
When tests become necessary:
### Unit Tests
- Vitest (Vite-native test runner)
- React Testing Library
- Test utilities, formatters, API client
### Integration Tests
- Test page-level components
- Mock API responses
- Test user workflows
### E2E Tests
- Playwright or Cypress
- Test critical paths (record progress, view analytics)
**Not included in minimal spec** - add when project matures.
---
## Accessibility (a11y)
### Semantic HTML
Use proper elements:
```jsx
<button> instead of <div onClick>
<nav> for navigation
<main> for main content
<header>, <footer> for sections
```
### ARIA Labels
```jsx
<button aria-label="Close modal">Γ—</button>
<input aria-describedby="error-message" />
```
### Keyboard Navigation
- All interactive elements focusable
- Visible focus states
- Logical tab order
### Color Contrast
- WCAG AA minimum (4.5:1 for text)
- Use Tailwind's accessible color combinations
---
## Development Workflow
### 1. Start Backend (Django)
```bash
cd /path/to/django/backend
python manage.py runserver
```
### 2. Start Frontend (Vite)
```bash
cd /path/to/react/frontend
npm run dev
```
### 3. Access Application
- Frontend: http://localhost:5173
- Backend API: http://localhost:8000/api
- Vite proxies `/api` requests to backend
### 4. Make Changes
- Edit React components
- Save file
- Vite HMR updates browser instantly (no refresh needed)
---
## Code Quality Standards
### Formatting
- Consistent indentation (2 spaces)
- Trailing commas in multiline arrays/objects
- Single quotes for strings
- Semicolons optional (be consistent)
### Naming Conventions
- Components: PascalCase (`ProfileCard.jsx`)
- Hooks: camelCase with `use` prefix (`useFetch.js`)
- Utilities: camelCase (`formatNumber.js`)
- Constants: UPPER_SNAKE_CASE (`API_BASE`)
### File Organization
- One component per file
- Group related components in folders
- Keep files under 200 lines
- Extract complex logic to hooks/utils
### Comments
- Use JSDoc for functions
- Explain "why", not "what"
- Remove commented-out code
**Example**:
```javascript
/**
* Formats a number as currency (Gold Dragons)
* @param {number} value - The value to format
* @returns {string} Formatted string like "1,234,567 GD"
*/
function formatCurrency(value) {
return `${value.toLocaleString()} GD`
}
```
---
## Browser Support
### Target Browsers (Vite 7 defaults)
- Chrome 107+
- Edge 107+
- Firefox 104+
- Safari 16.0+
These align with Vite 7's "baseline-widely-available" target.
### Polyfills
None needed - modern browsers support:
- ES2020 syntax
- Fetch API
- Async/await
- CSS Grid/Flexbox
- CSS custom properties
---
## Future Enhancements (Not in Minimal Spec)
### When to Add
1. **React Router** - When hash-based routing becomes limiting
2. **React Context** - When props drilling exceeds 3 levels
3. **React Query** - When caching/invalidation becomes complex
4. **TypeScript** - When team grows or errors increase
5. **Testing** - When regression bugs appear frequently
6. **Storybook** - When design system emerges
7. **i18n** - When internationalization is required
8. **PWA** - When offline support is needed
### Don't Add Unless Needed
- Redux (useState is sufficient)
- CSS-in-JS (Tailwind is enough)
- Component libraries (build your own)
- Lodash (native JS is powerful enough)
---
## Summary
This specification defines a **minimal, production-ready React GUI** with:
βœ… **5 total dependencies** (React, ReactDOM, Recharts, Vite, Tailwind)
βœ… **Modern 2025 stack** (React 19.2, Vite 7, Tailwind v4)
βœ… **Zero configuration** (Tailwind v4, Vite auto-discovery)
βœ… **Fast builds** (5x faster with Vite 7 + Tailwind v4)
βœ… **Component-based architecture** (reusable, composable)
βœ… **Hash-based routing** (no external router)
βœ… **Native fetch** (no HTTP libraries)
βœ… **Simple state management** (useState/props)
βœ… **Recharts integration** (SVG-based, responsive charts)
βœ… **Tailwind styling** (utility-first, no CSS frameworks)
βœ… **HuggingFace Space ready** (Docker, nginx, static build)
**Philosophy**: Start minimal, add dependencies only when complexity demands it.