# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## System Overview

ScalesApp is a three-tier weighing scale management system with real-time data synchronization:
- **Django Backend** (REST API + Server-Sent Events) on port 8000
- **React Frontend** (SPA) on port 3000
- **Serial Bridge** (Flask SSE server) on port 5000 for COM port reading

## Development Commands

### Backend (Django)

```bash
cd backend
python -m venv venv
venv\Scripts\activate  # Windows: venv\Scripts\activate | Unix: source venv/bin/activate
pip install -r requirements.txt

# Database operations
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser

# Run server
python manage.py runserver  # Development (WSGI)
uvicorn scalesapp.asgi:application --reload  # Development (ASGI - for SSE)

# Testing (when implemented)
python manage.py test
python manage.py test api.tests.test_models  # Single test file
```

### Frontend (React)

```bash
cd frontend
npm install
npm start      # Development server
npm run build  # Production build
npm test       # Run tests
```

### Serial Bridge

```bash
cd serial_bridge
python -m venv venv
venv\Scripts\activate
pip install -r requirements.txt
python app.py  # Run Flask SSE server

# Build standalone executable
pyinstaller serial_bridge.spec
```

### Testing COM Port (requires com0com)

```bash
cd test_comport_writer
python test_writer.py  # Sends test data to virtual COM port
```

## Architecture

### Django Backend Structure

**Three Django Apps:**
- **`api/`** - Core API, user management, COM port readings, report templates
- **`vehicles/`** - Vehicle weighing management with dynamic nomenclature data
- **`nomenclatures/`** - Runtime-configurable schema system (lookup tables and custom fields)

**Custom User Model:** `api.User` (extends AbstractUser)
- Fields: `role` (employee/viewer), `is_admin` (boolean)
- All authentication requires JWT tokens

**Database:** PostgreSQL (production), SQLite (development fallback)

**Key Models:**
- `User` - Custom user with roles
- `ComPortReading` - Serial port data log
- `Report` - Report templates (JSONField stores element definitions)
- `Vehicle` - Core weighing entity (vehicle_number, tare, gross, with user/timestamp tracking)
- `VehicleExtra` - OneToOne with Vehicle, JSONField for dynamic nomenclature data
- `Nomenclature` - Configurable data types (applies_to: vehicle/container, kind: lookup/field)
- `NomenclatureField` - Field definitions (text, number, bool, choice types)
- `NomenclatureEntry` - Data entries for lookup tables

**ViewSet Pattern:** All views use DRF's ModelViewSet with custom actions via `@action` decorator
- Examples: `@action(detail=True, methods=['post']) def set_tare()`

### Real-Time Communication (SSE)

**Implementation:** `backend/scalesapp/sse.py`
- ASGI-based async SSE endpoint: `/sse-connect/`
- JWT authentication via query parameter or Authorization header
- Global `updates_queue` with 100-event buffer
- Event ID tracking for reconnection/replay of missed events
- `sse_broadcast_update(operation, model, data)` called from views after mutations
- Auto-reconnect with Last-Event-ID header support

**Usage in Views:**
```python
from scalesapp.sse import sse_broadcast_update

def create(self, request):
    # ... create logic ...
    sse_broadcast_update('insert', 'vehicle', serializer.data)
```

### Nomenclature System (Dynamic Schema)

**Purpose:** Runtime-configurable extra fields without database migrations

**Flow:**
1. Admin creates `Nomenclature` (e.g., "CARGO_TYPE", kind=lookup, applies_to=vehicle)
2. Defines `NomenclatureField`s (e.g., name: text, code: text)
3. Creates `NomenclatureEntry` records (e.g., "Coal", "Iron Ore")
4. Frontend loads nomenclatures on mount via REST API
5. User selects from dropdown → stores entry ID in `vehicle.extra.data.CARGO_TYPE` (JSONField)
6. Validation in `VehicleExtraSerializer` enforces type constraints

### React Frontend Structure

**Context Providers (State Management):**
- **`AuthContext`** - JWT token management, auto-refresh every 14 minutes, axios interceptor for 401 handling
- **`NomenclatureContext`** - Centralized data cache with SSE subscription to Django `/sse-connect/`
  - Real-time updates (insert/update operations)
  - Maintains sorted arrays for all nomenclatures
  - Auto-reconnect on connection loss
  - Parallel loading of all nomenclature endpoints on mount
- **`DataContext`** - Report editor data fetching (for preview mode)
- **`BandContext`** - Report editor band iteration state

**Key Components:**
- **`Main.jsx`** - Primary weighing interface
  - Left panel: Vehicle list with search
  - Right panel: Selected vehicle details with dynamic nomenclature fields
  - Real-time weight display from serial bridge
  - Tare/Gross weight setting with user/timestamp tracking
  - SSE-based automatic UI updates
- **`ReportEditor/`** - Custom report builder (see Report Editor section)

**API Communication:** `services/api.js`
- Axios instance with JWT interceptor
- Token refresh queue (pauses failed requests during refresh)
- Auto-logout on refresh failure

**Routing:**
- `/` - Main weighing interface (protected)
- `/report-editor` - Report designer (protected)
- Shows Login component if not authenticated

### Serial Bridge Architecture

**Components:**
- `app.py` - Flask SSE server on port 5000
- `serial_reader.py` - SerialPortReader class with threading
- `tray_icon.py` - Windows system tray integration

**Data Flow:**
1. Thread reads from COM port continuously (pyserial)
2. Data queued in global `data_queue`
3. SSE endpoint `/events` streams to frontend
4. Frontend's `useSerialData` hook receives updates
5. **Note:** Serial bridge is independent of Django (direct SSE to frontend)

**Configuration:** `.env` file with COM_PORT, BAUD_RATE, TIMEOUT, READ_INTERVAL

### Real-Time Data Flow Example

```
COM Port → Serial Bridge (Flask SSE :5000) → Frontend useSerialData hook
                                                      ↓
                                               Main.jsx displays weight
                                                      ↓
                                               User clicks "Set Tare"
                                                      ↓
                                               POST /api/vehicles/{id}/set-tare/
                                                      ↓
                                               Django sse_broadcast_update()
                                                      ↓
                                               SSE → Frontend NomenclatureContext
                                                      ↓
                                               Vehicle list auto-updates
```

## Report Editor System

**Architecture:** Custom drag-drop report designer with grid-based layout

**Grid System:**
- Page dimensions: 80 columns × 66 rows (character grid)
- All elements positioned in grid coordinates
- Pixel conversion: `x * charWidth`, `y * charHeight`

**Element Types (OOP Class Hierarchy):**
```
Element (base class)
├── TextElement (static text)
├── DBTextElement (data-bound field with objectKey/fieldPath)
├── FrameElement (borders with box-drawing characters)
├── HorizontalLineElement
├── VerticalLineElement
├── SymbolElement (single character)
└── BandElement (repeating section with children[])
```

**Band Types:**
- `header` - Page header
- `footer` - Page footer
- `detail` - Main repeating data section
- `subdetail` - Nested repeating (child of detail)
- `summary` - Summary/totals section

**Key Components:**
- `ReportEditor.jsx` - Main orchestrator with save/load to Django `/api/reports/`
- `EditorCanvas.jsx` - Canvas rendering, drag/drop, multi-select
- `Toolbar.jsx` - Tool selection (text, frame, lines, bands, etc.)
- `ObjectInspector.jsx` - Property editor for selected elements
- `ConfigPanel.jsx` - API endpoint configuration
- `CharacterPalette.jsx` - Box-drawing character picker
- `models/Element.jsx` - Element class definitions

**Data Binding:**
- `DBTextElement` has `objectKey` and `fieldPath`
- Example: objectKey="invoice", fieldPath="customer.name"
- Preview mode fetches data from configured `apiEndpoint`
- BandContext provides current item during band iteration

**Persistence:**
- Entire report serialized to JSON
- Saved to Django `Report` model (JSONField stores elements array)

**Features:**
- Multi-select with drag/drop
- Resize handles
- Keyboard shortcuts (Delete, Ctrl+S, etc.)
- Grid snapping
- Character palette for box-drawing
- Preview mode with live data

## Authentication & Security

**JWT Token Flow:**
- Access token: 15 minutes lifetime
- Refresh token: 7 days lifetime
- Stored in localStorage (XSS risk - consider httpOnly cookies for production)
- Frontend auto-refreshes tokens every 14 minutes
- All API requests require Bearer token (except `/api/token/` and `/api/token/refresh/`)
- SSE connection authenticated via query param: `/sse-connect/?token=<jwt>` or Authorization header

**CORS Configuration:**
- Allowed origins: http://localhost:3000, http://127.0.0.1:3000, http://localhost:5174
- Credentials allowed (for cookies if needed)

**User Roles:**
- `employee` - Full access to weighing operations
- `viewer` - Read-only access
- `is_admin` - Access to Django admin panel

## Key Technical Decisions

**Why SSE instead of WebSockets?**
- Simpler server implementation (one-way communication sufficient)
- Auto-reconnect built into EventSource API
- Event ID-based replay for missed events
- HTTP-based (easier to proxy/firewall)

**Why ASGI?**
- Required for async SSE endpoint
- Uses uvicorn server
- `sync_to_async` wrapper for ORM queries in SSE handler

**Why Separate Serial Bridge?**
- Independence from Django (can restart Django without losing COM connection)
- System tray integration for user control
- Portable executable deployment
- Dedicated threading for serial I/O

**Why Nomenclature System?**
- Avoid schema migrations for custom fields
- Different deployments have different requirements
- User-configurable without code changes
- Validation still enforced via serializers

## Important Files for Understanding Architecture

1. `backend/scalesapp/settings.py` - Django configuration, JWT, CORS, database
2. `backend/scalesapp/sse.py` - Real-time SSE implementation
3. `backend/api/models.py` - Core models (User, ComPortReading, Report)
4. `backend/vehicles/models.py` - Vehicle and VehicleExtra with JSONField
5. `backend/nomenclatures/models.py` - Dynamic schema system
6. `frontend/src/App.jsx` - Routing and AuthProvider setup
7. `frontend/src/contexts/NomenclatureContext.jsx` - SSE client and data cache
8. `frontend/src/contexts/AuthContext.jsx` - JWT token management
9. `frontend/src/components/Main.jsx` - Primary weighing interface
10. `frontend/src/components/ReportEditor/ReportEditor.jsx` - Report builder
11. `serial_bridge/app.py` - Flask SSE server for COM port

## Configuration Files

**Backend:** `backend/.env`
```
SECRET_KEY=your-secret-key
DEBUG=True
ALLOWED_HOSTS=localhost,127.0.0.1
DB_NAME=scalesapp
DB_USER=postgres
DB_PASSWORD=
DB_HOST=localhost
DB_PORT=5432
```

**Frontend:** `frontend/.env`
```
REACT_APP_API_URL=http://localhost:8000
```

**Serial Bridge:** `serial_bridge/.env`
```
COM_PORT=COM1
BAUD_RATE=9600
BACKEND_URL=http://localhost:8000
AUTO_CONNECT=True
DEBUG=False
```

## Running the Full Application

1. Start Django: `cd backend && python manage.py runserver`
2. Start Serial Bridge: `cd serial_bridge && python app.py`
3. Start React: `cd frontend && npm start`

For SSE to work properly in Django, use uvicorn instead: `uvicorn scalesapp.asgi:application --reload`

## Common Patterns

**Adding a new model:**
1. Define model in appropriate app (api/vehicles/nomenclatures)
2. Create serializer in `serializers.py`
3. Create ViewSet in `views.py` with `sse_broadcast_update()` calls
4. Register route in `urls.py`
5. Run `python manage.py makemigrations && python manage.py migrate`
6. Update frontend Context to subscribe to SSE updates

**Adding a new nomenclature field:**
1. Create Nomenclature via Django admin or API
2. Define NomenclatureFields
3. Frontend auto-loads on next mount
4. No code changes needed (dynamic rendering in Main.jsx)

**Adding a report element type:**
1. Create new Element subclass in `models/Element.jsx`
2. Add component file (e.g., `MyElement.jsx`)
3. Register in `EditorCanvas.jsx` render switch
4. Add tool button in `Toolbar.jsx`
5. Add properties in `ObjectInspector.jsx`
