> **Audience:** This file is the primary briefing for AI coding agents and senior devs onboarding to the project.
---
## Project Overview
RackMapper is a two-module, dark-mode, full-stack web application for network infrastructure management:
1.**Rack Planner** — A visual, drag-and-drop rack diagram builder. Users populate standard 19" racks (42U default) with modular devices (switches, aggregate switches, modems, routers, NAS units, PDUs, patch panels, blanks, etc.), resize devices to span multiple U-spaces, and configure networking devices through interactive port modals with VLAN assignment. Multiple racks are displayed side-by-side when more than one rack exists.
2.**Service Mapper** — A node-graph canvas (React Flow) where users visually map service interactions, traffic flows, and dependencies between infrastructure components, applications, and external services. Device nodes can be auto-populated from Rack Module data and then freely repositioned on the canvas.
All data is persisted to SQLite via Prisma ORM with a migration-first workflow. The app is web-facing and protected by a single hardcoded admin account configured via Docker/Unraid environment variables.
- **Single admin account** — no registration UI, no user table in the database
- Credentials are injected at runtime via Docker environment variables: `ADMIN_USERNAME` and `ADMIN_PASSWORD_HASH`
- Password is stored as a `bcryptjs` hash (never plaintext) — generate the hash once with a seed script and store it in Unraid's Docker template as an environment variable
- JWT issued on login, stored in an `httpOnly`, `SameSite=Strict`, `Secure` cookie — never `localStorage`
- All `/api/*` routes except `/api/auth/login` are protected by `authMiddleware.ts`
- Token expiry: `8h` (configurable via `JWT_EXPIRY` env var)
### Hash Generation Utility
Provide a one-time script at `scripts/hashPassword.ts`:
npx prisma generate # regenerate client after any schema change
npx prisma studio # visual DB browser at localhost:5555
```
> ⚠️ **Never** manually edit migration files after they are applied. Create a new migration for every schema change. Never use `prisma db push` — it skips migration history.
---
## Seed
`prisma/seed.ts` starts **blank** — no default VLANs, no sample racks. It should only:
- Log a confirmation message that the DB is ready
- Be a no-op safe to run multiple times
Do not pre-seed VLANs, racks, or modules unless the user explicitly requests it.
**Devices with interactive ports:** `SWITCH`, `AGGREGATE_SWITCH`, `ROUTER`, `FIREWALL`, `PATCH_PANEL`, `AP`, `MODEM`, `NAS`, `PDU`, (SERVER optionally has ports) (Ability to add custom rack items) (Ability to assign number of ports and what type they are to network devices) (Constants are good for rapid deployment bu all devices beed to be fully configurable)
Ports are **auto-generated** when a Module is created based on these defaults. Users should not need to manually add ports.
### PNG Export
- Export button in rack toolbar: captures the entire rack view (all side-by-side racks) as a single PNG
- Use `html-to-image` (`toPng`) targeting the rack canvas container ref
- Filename: `rackmapper-rack-<timestamp>.png`
- Show a brief "Exporting..." toast during capture
---
## Module 2 — Service Mapper
### Library
Use **React Flow (`@xyflow/react` v12+)**. Do not substitute with D3, Cytoscape, or Dagre without approval.
### Device-to-Module Linking
-`DeviceNode` carries an optional `moduleId` field linking it to a physical `Module` in the rack
- When `moduleId` is set, the node displays the module's name, type badge, and IP address pulled from the linked record
- Clicking a linked `DeviceNode` opens a read-only info panel showing full module detail with a "View in Rack" button that navigates to `/rack` and highlights that module
- Nodes are **fully draggable** regardless of module linkage — position on the canvas is independent of rack position
### Auto-Populate from Rack
`POST /api/maps/:id/populate` fetches all Modules across all racks and creates a `DeviceNode` for each one that doesn't already have a node in the map. Nodes are arranged in a grid layout (column per rack, rows per module in U-order). This is a one-way import — subsequent module additions must be manually added or re-triggered.
### Custom Node Types
Each `NodeType` has a custom React Flow node component in `client/src/components/mapper/nodes/`:
| Node Type | Component | Visual Style |
|---|---|---|
| `DEVICE` | `DeviceNode.tsx` | Rack icon; accent border color by ModuleType; shows IP if linked |
Debounce all position/data changes by 500ms after drag end — PATCH to API. Never save on every pixel move. Use React Flow's `onNodesChange` and `onEdgesChange` callbacks.
### PNG Export
- "Export PNG" button in Service Mapper toolbar
- Use `html-to-image` (`toPng`) on the React Flow container
- Temporarily hide minimap and controls panel during capture, restore after
- Route handlers are thin — all business logic in `server/services/`
- All Prisma queries in service files — never inline in route handlers
- Throw typed `AppError` instances; catch in `errorHandler` middleware
- Consistent JSON response shape: `{ data, error, meta }`
---
## Commands
```bash
# Development
npm run dev # Vite + Node (concurrently)
npm run dev:client # Vite only
npm run dev:server # Nodemon server only
# Auth
npx ts-node scripts/hashPassword.ts mypassword # generate bcrypt hash for env var
# Database
npx prisma migrate dev --name <name> # create + apply dev migration
npx prisma migrate deploy # apply in production / Docker
npx prisma generate # regenerate client after schema change
npx prisma studio # visual DB browser at localhost:5555
npx prisma db seed # run seed (no-op by default)
# Build
npm run build # Vite build + tsc check
npm run typecheck # tsc --noEmit — run before every PR
# Lint / Format
npm run lint
npm run lint:fix
npm run format # Prettier --write
# Tests
npm run test
npm run test:watch
npm run test -- path/to/file.test.ts
# Docker
docker compose up --build -d
docker compose down
docker compose logs -f
```
---
## Docker / Unraid Configuration
**`docker-compose.yml` environment block:**
```yaml
environment:
- NODE_ENV=production
- PORT=3001
- DATABASE_URL=file:./data/rackmapper.db
- ADMIN_USERNAME=admin
- ADMIN_PASSWORD_HASH=$2a$12$... # bcrypt hash
- JWT_SECRET=... # min 32 chars
- JWT_EXPIRY=8h
volumes:
- ./data:/app/data # persists SQLite file across container restarts
```
The Dockerfile should run `npx prisma migrate deploy && node dist/index.js` as the startup command to auto-apply any pending migrations on container start.
---
## Agent Permissions
### Allowed without asking
- Read any file
- Run `npm run lint`, `npm run typecheck`, `npm run format`
- Run `vitest` on a single file
- Run `npx prisma generate`
- Edit files in `client/src/`, `server/`, `prisma/schema.prisma`
- Edit migration files that have already been applied
- Commit secrets or `.env` files
- Hard-code credentials, IP addresses, or VLAN IDs
- Run `prisma db push` — migrations only
- Rewrite the entire repo in one diff — prefer targeted, minimal changes
---
## Key Design Decisions
1.**SQLite over PostgreSQL** — intentional for single-container Unraid deployment. No external DB process. Do not suggest migrating unless asked.
2.**httpOnly cookie auth** — chosen over `localStorage` for XSS resistance on a web-facing deployment. Do not change to `localStorage`.
3.**Single admin account via env vars** — no user table, no registration. Admin resets password by updating the Unraid Docker template env var and restarting the container.
4.**U1 = top of rack** — all U-position logic is 1-indexed from the top. Validate and render accordingly.
5.**`@dnd-kit` over `react-beautiful-dnd`** — `react-beautiful-dnd` is unmaintained.
6.**React Flow for Service Mapper** — first-class TypeScript, custom node API, active maintenance. Do not swap.
7.**Zustand over Redux** — intentional for this app's scope. Do not introduce Redux or Context API for global state.
8.**Ports auto-generated on Module creation** — use `MODULE_PORT_DEFAULTS`. Do not require manual port addition.
9.**DeviceNode position is canvas-independent** — linking a node to a Module does not constrain its canvas position.
10.**VLAN seed is blank** — the user creates all VLANs manually. Do not pre-seed VLAN records.
---
## Accessibility & UX Standards
- All modals: keyboard navigable (Tab order, Escape to close, Enter to confirm)
- All interactive elements: `aria-label` or visible label