# Frontend Architecture & Developer Manual

# Frontend Architecture &amp; Developer Manual

*Plock WMS — AI-Native Warehouse Management System  
Last updated: 2026-03-04 | Tech lead: ALAI Engineering*

---

## Table of Contents

1. [Architecture Overview](#bkmrk-1.-architecture-over)
2. [Tech Stack](#bkmrk-2.-tech-stack)
3. [Shared Packages](#bkmrk-3.-shared-packages)
4. [Shell App (port 3000)](#bkmrk-4.-shell-app-%28port-3)
5. [Inventory MFE (port 3001)](#bkmrk-5.-inventory-mfe-%28po)
6. [Orders MFE (port 3002)](#bkmrk-6.-orders-mfe-%28port-)
7. [Picking MFE (port 3003)](#bkmrk-7.-picking-mfe-%28port)
8. [Settings MFE (port 3004)](#bkmrk-8.-settings-mfe-%28por)
9. [AI MFE (port 3005)](#bkmrk-9.-ai-mfe-%28port-3005)
10. [Backend API Reference](#bkmrk-10.-backend-api-refe)
11. [Brand &amp; Design Tokens](#bkmrk-11.-brand-%26-design-t)
12. [Development Commands](#bkmrk-12.-development-comm)
13. [File Counts &amp; Project Size](#bkmrk-13.-file-counts-%26-pr)

---

## 1. Architecture Overview

### Monorepo Structure

Plock uses a Turborepo-managed monorepo with npm workspaces. The repository is organized as follows:

```
plock/
├── frontend/
│   ├── shell/              # Host application — layout, routing, auth (port 3000)
│   ├── mfe-inventory/      # Inventory management MFE (port 3001)
│   ├── mfe-orders/         # Order management MFE (port 3002)
│   ├── mfe-picking/        # Picking & scanning MFE (port 3003)
│   ├── mfe-settings/       # Settings & admin MFE (port 3004)
│   └── mfe-ai/             # AI chat & analytics MFE (port 3005)
├── packages/
│   ├── types/              # @plock/types — shared TypeScript interfaces
│   ├── ui/                 # @plock/ui — shared React components
│   └── utils/              # @plock/utils — API client, hooks, auth
├── backend/                # Kotlin/Ktor backend API (port 8080)
├── package.json            # Root workspace config
└── turbo.json              # Turborepo pipeline config
```

### Micro-Frontend Architecture (Module Federation)

Plock implements a micro-frontend (MFE) architecture using **Vite Module Federation** (`@module-federation/vite`). Each MFE is an independently deployable application that exposes specific routes and components to the Shell host.

<table id="bkmrk-application-port-rol"> <thead> <tr> <th>Application</th> <th>Port</th> <th>Role</th> <th>Exposed</th> </tr> </thead> <tbody> <tr> <td>**Shell**</td> <td>3000</td> <td>Host — layout, auth, routing</td> <td>Consumes all remotes</td> </tr> <tr> <td>**mfe-inventory**</td> <td>3001</td> <td>Stock, receiving, locations</td> <td>`./App`</td> </tr> <tr> <td>**mfe-orders**</td> <td>3002</td> <td>Order lifecycle management</td> <td>`./App`</td> </tr> <tr> <td>**mfe-picking**</td> <td>3003</td> <td>Wave picking, barcode scanning</td> <td>`./App`</td> </tr> <tr> <td>**mfe-settings**</td> <td>3004</td> <td>Users, warehouses, integrations</td> <td>`./App`</td> </tr> <tr> <td>**mfe-ai**</td> <td>3005</td> <td>AI chat, analytics, alerts</td> <td>`./App`</td> </tr> <tr> <td>**backend**</td> <td>8080</td> <td>Kotlin/Ktor REST API</td> <td>—</td> </tr> </tbody></table>

### Architecture Diagram

```
┌─────────────────────────────────────────────────────────┐
│                  Shell App (:3000)                      │
│  ┌──────────────┐  ┌──────────────────────────────────┐ │
│  │   Sidebar    │  │          Main Content             │ │
│  │   (#1B4D3E)  │  │   ┌──────────────────────────┐   │ │
│  │  - Inventory │  │   │   RemoteLoader            │   │ │
│  │  - Orders    │  │   │   (Suspense + ErrorBound) │   │ │
│  │  - Picking   │  │   └──────────────────────────┘   │ │
│  │  - Settings  │  └──────────────────────────────────┘ │
│  │  - AI        │                                        │
│  └──────────────┘                                        │
└─────────────────────────────────────────────────────────┘
         │              │             │            │
    [:3001]        [:3002]       [:3003]      [:3004/3005]
  mfe-inventory  mfe-orders   mfe-picking  mfe-settings/ai
         │              │             │            │
         └──────────────┴─────────────┴────────────┘
                              │
                     Backend API [:8080]
                     (Kotlin/Ktor)
```

---

## 2. Tech Stack

<table id="bkmrk-layer-technology-ver"> <thead> <tr> <th>Layer</th> <th>Technology</th> <th>Version</th> <th>Purpose</th> </tr> </thead> <tbody> <tr> <td>UI Framework</td> <td>React</td> <td>19.x</td> <td>Component rendering</td> </tr> <tr> <td>Language</td> <td>TypeScript</td> <td>5.x</td> <td>Type safety throughout</td> </tr> <tr> <td>Build Tool</td> <td>Vite</td> <td>6.x</td> <td>Fast dev server + HMR</td> </tr> <tr> <td>Styling</td> <td>Tailwind CSS</td> <td>4.x</td> <td>Utility-first CSS</td> </tr> <tr> <td>MFE Runtime</td> <td>@module-federation/vite</td> <td>latest</td> <td>Module Federation via Vite</td> </tr> <tr> <td>Monorepo</td> <td>Turborepo + npm workspaces</td> <td>latest</td> <td>Build pipeline + caching</td> </tr> <tr> <td>Icons</td> <td>Lucide React</td> <td>latest</td> <td>Icon library</td> </tr> <tr> <td>Font</td> <td>Inter</td> <td>—</td> <td>Headings and body text</td> </tr> <tr> <td>Mono Font</td> <td>JetBrains Mono</td> <td>—</td> <td>Code blocks</td> </tr> <tr> <td>Charts</td> <td>CSS-only</td> <td>—</td> <td>No external chart libraries</td> </tr> <tr> <td>Barcode</td> <td>@zxing/library</td> <td>latest</td> <td>Picking MFE scanner</td> </tr> <tr> <td>Backend</td> <td>Kotlin / Ktor</td> <td>—</td> <td>REST API on port 8080</td> </tr> <tr> <td>DB ORM</td> <td>Flyway (migrations) + Prisma (introspect)</td> <td>—</td> <td>Flyway owns migrations; Prisma read-only</td> </tr> </tbody></table>

---

## 3. Shared Packages

### 3.1 @plock/types — TypeScript Type Contracts

Located at `packages/types/src/index.ts` (242 lines). Provides all shared TypeScript interfaces and enums aligned with Kotlin backend DTOs.

#### Enums

<table id="bkmrk-enum-values-userrole"> <thead> <tr> <th>Enum</th> <th>Values</th> </tr> </thead> <tbody> <tr> <td>`UserRole`</td> <td>ADMIN, WAREHOUSE\_MANAGER, PICKER, RECEIVER, VIEWER</td> </tr> <tr> <td>`OrderStatus`</td> <td>PENDING, ALLOCATED, PICKING, PACKED, SHIPPED, DELIVERED, CANCELLED</td> </tr> <tr> <td>`OrderType`</td> <td>OUTBOUND, INBOUND, TRANSFER, RETURN</td> </tr> <tr> <td>`LocationType`</td> <td>PICK, BULK, RECEIVING, SHIPPING, STAGING</td> </tr> <tr> <td>`WaveStatus`</td> <td>PENDING, IN\_PROGRESS, COMPLETED, CANCELLED</td> </tr> <tr> <td>`InventoryStatus`</td> <td>AVAILABLE, RESERVED, QUARANTINE, DAMAGED</td> </tr> </tbody></table>

#### Key Interfaces

<table id="bkmrk-interface-key-fields"> <thead> <tr> <th>Interface</th> <th>Key Fields</th> </tr> </thead> <tbody> <tr> <td>`User`</td> <td>id, email, name, role, warehouseId, createdAt</td> </tr> <tr> <td>`Warehouse`</td> <td>id, name, address, timezone, isActive</td> </tr> <tr> <td>`Location`</td> <td>id, code, type, zoneId, warehouseId, capacity</td> </tr> <tr> <td>`Product`</td> <td>id, sku, name, description, category, weight, dimensions, barcode</td> </tr> <tr> <td>`InventoryItem`</td> <td>id, productId, locationId, quantity, reservedQuantity, status</td> </tr> <tr> <td>`Order`</td> <td>id, type, status, warehouseId, customerId, lines, createdAt</td> </tr> <tr> <td>`OrderLine`</td> <td>id, orderId, productId, quantity, pickedQuantity, locationId</td> </tr> <tr> <td>`PickWave`</td> <td>id, status, assignedTo, orderIds, items, startedAt, completedAt</td> </tr> <tr> <td>`ReceivingOrder`</td> <td>id, supplierId, warehouseId, lines, status, expectedDate</td> </tr> <tr> <td>`DashboardStats`</td> <td>totalOrders, pendingOrders, activeWaves, lowStockCount, todayShipments</td> </tr> </tbody></table>

#### API / Request Types

<table id="bkmrk-type-description-pag"> <thead> <tr> <th>Type</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td>`PagedResponse<T>`</td> <td>Generic paginated response: data\[\], total, page, size</td> </tr> <tr> <td>`ApiError`</td> <td>status, message, details?</td> </tr> <tr> <td>`CreateWarehouseRequest`</td> <td>name, address, timezone</td> </tr> <tr> <td>`CreateOrderRequest`</td> <td>type, warehouseId, customerId, lines</td> </tr> <tr> <td>`AdjustStockRequest`</td> <td>productId, locationId, delta, reason</td> </tr> <tr> <td>`LoginRequest`</td> <td>email, password</td> </tr> </tbody></table>

### 3.2 @plock/ui — Shared React Components (14 components)

Located at `packages/ui/src/`. All components are exported from `index.ts`.

<table id="bkmrk-component-props-%2F-va"> <thead> <tr> <th>Component</th> <th>Props / Variants</th> </tr> </thead> <tbody> <tr> <td>**Button**</td> <td>variant: primary | secondary | ghost | danger; size: sm | md | lg; loading: boolean</td> </tr> <tr> <td>**Card**</td> <td>Base card container with optional title, padding</td> </tr> <tr> <td>**StatCard**</td> <td>Metric card with value, label, icon, delta</td> </tr> <tr> <td>**Badge**</td> <td>color: green | amber | red | blue | gray; children</td> </tr> <tr> <td>**Input**</td> <td>label, error, hint, leftIcon, rightIcon, all native props</td> </tr> <tr> <td>**DataTable&lt;T&gt;**</td> <td>Generic sortable table: columns\[\], data\[\], onRowClick, loading</td> </tr> <tr> <td>**Select**</td> <td>Native styled select: options\[\], label, error</td> </tr> <tr> <td>**Dialog**</td> <td>Modal overlay with escape key close, focus trap, title, children, actions</td> </tr> <tr> <td>**Tabs**</td> <td>Horizontal tabs with keyboard navigation: tabs\[\], activeTab, onChange</td> </tr> <tr> <td>**Toast / ToastProvider / useToast**</td> <td>type: success | error | warning | info; auto-dismiss; context hook</td> </tr> <tr> <td>**Dropdown**</td> <td>trigger element + items\[\]; click-outside-to-close</td> </tr> <tr> <td>**Skeleton / SkeletonText / SkeletonCard**</td> <td>Loading placeholder components</td> </tr> <tr> <td>**EmptyState**</td> <td>icon, title, description, action (CTA button)</td> </tr> <tr> <td>**StatusBadge**</td> <td>Auto-maps status string to Swedish label + semantic color</td> </tr> </tbody></table>

### 3.3 @plock/utils — API Client and Hooks

Located at `packages/utils/src/`. Five modules:

#### api.ts — HTTP Client

```
import { api } from '@plock/utils';

// All methods attach JWT from localStorage automatically
await api.get('/api/v1/inventory');
await api.post('/api/v1/orders', createOrderPayload);
await api.put('/api/v1/orders/123', updatePayload);
await api.delete('/api/v1/orders/123');

// Base URL: http://localhost:8080
```

#### auth utilities

```
import { getToken, setToken, clearToken } from '@plock/utils';

setToken('eyJhbGciOiJIUzI1NiIs...');   // Persists to localStorage
getToken();                              // Returns string | null
clearToken();                           // Removes from localStorage
```

#### useAuth hook (AuthProvider)

```
// Wrap app in AuthProvider
<AuthProvider>
  <App />
</AuthProvider>

// Use inside any component
const { user, login, logout, register, isAuthenticated, isLoading } = useAuth();

// login() calls POST /auth/login, stores JWT, fetches /api/v1/auth/me
// On mount: validates stored token via GET /api/v1/auth/me
```

#### useFetch hook

```
// Generic data fetching with loading/error states
const { data, loading, error, refetch } = useFetch<InventoryItem[]>('/api/v1/inventory');

```

#### useSSE hook

```
// Server-Sent Events with auto-reconnect
const { data, connected } = useSSE<DashboardEvent>('/api/v1/events/stream');

```

---

## 4. Shell App (port 3000)

The Shell is the host application. It owns the layout, authentication, routing, and dynamically loads the MFEs via Module Federation.

### Layout

- **Sidebar**: Collapsible left navigation, background color `#1B4D3E` (brand green). Links to all 5 MFE domains.
- **Header**: Warehouse selector dropdown, global search, notifications bell, user profile menu.

### Authentication

- **ProtectedRoute**: HOC wrapping all authenticated pages. Redirects to `/login` if no valid JWT.
- Token validation: calls `GET /api/v1/auth/me` on boot via `useAuth`.
- Login page: `/login` — email/password form, calls `POST /auth/login`.

### Pages

<table id="bkmrk-page-route-descripti"> <thead> <tr> <th>Page</th> <th>Route</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td>LoginPage</td> <td>/login</td> <td>Email + password auth form</td> </tr> <tr> <td>Dashboard</td> <td>/</td> <td>Live KPI stats, activity feed, order breakdown chart, low-stock alerts</td> </tr> </tbody></table>

### MFE Loading

```
// RemoteLoader component — error boundary + Suspense
<RemoteLoader remote="inventory" fallback={<SkeletonCard />}>
  <InventoryApp />
</RemoteLoader>
```

### Routes

<table id="bkmrk-path-handler-%2F-dashb"> <thead> <tr> <th>Path</th> <th>Handler</th> </tr> </thead> <tbody> <tr> <td>/</td> <td>Dashboard (Shell)</td> </tr> <tr> <td>/login</td> <td>LoginPage (Shell)</td> </tr> <tr> <td>/inventory/\*</td> <td>mfe-inventory (remote)</td> </tr> <tr> <td>/orders/\*</td> <td>mfe-orders (remote)</td> </tr> <tr> <td>/picking/\*</td> <td>mfe-picking (remote)</td> </tr> <tr> <td>/settings/\*</td> <td>mfe-settings (remote)</td> </tr> <tr> <td>/ai/\*</td> <td>mfe-ai (remote)</td> </tr> </tbody></table>

---

## 5. Inventory MFE (port 3001)

### Tab Navigation

- Lagersaldo (Stock Balance)
- Inleverans (Receiving)
- Platser (Locations)
- Cykelräkning (Cycle Count)

### Pages

<table id="bkmrk-page-route-descripti-1"> <thead> <tr> <th>Page</th> <th>Route</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td>InventoryList</td> <td>/inventory</td> <td>Full stock levels with product search and status filter</td> </tr> <tr> <td>StockAdjust</td> <td>/inventory/adjust</td> <td>Manual stock adjustment form (delta + reason)</td> </tr> <tr> <td>ReceivingList</td> <td>/inventory/receiving</td> <td>All inbound purchase orders</td> </tr> <tr> <td>ReceivingDetail</td> <td>/inventory/receiving/:id</td> <td>Receive goods per-line with quantity confirmation</td> </tr> <tr> <td>LocationBrowser</td> <td>/inventory/locations</td> <td>Zone hierarchy tree view of warehouse locations</td> </tr> <tr> <td>CycleCountPage</td> <td>/inventory/cycle-counts</td> <td>List of cycle count tasks</td> </tr> <tr> <td>CycleCountCreate</td> <td>/inventory/cycle-counts/new</td> <td>Create new cycle count task</td> </tr> <tr> <td>CycleCountDetail</td> <td>/inventory/cycle-counts/:id</td> <td>Execute cycle count, record variances</td> </tr> </tbody></table>

### API Endpoints Used

- `GET/POST /api/v1/inventory`
- `POST /api/v1/inventory/adjust`
- `GET/POST /api/v1/receiving`
- `POST /api/v1/receiving/:id/receive-item`
- `GET /api/v1/locations`
- `GET/POST /api/v1/cycle-counts`
- `POST /api/v1/cycle-counts/:id/complete`

---

## 6. Orders MFE (port 3002)

### Order Status Lifecycle

```
PENDING → ALLOCATED → PICKING → PACKED → SHIPPED → DELIVERED
                                                    ↓ (or)
                                                 CANCELLED
```

### Pages

<table id="bkmrk-page-route-descripti-2"> <thead> <tr> <th>Page</th> <th>Route</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td>OrderList</td> <td>/orders</td> <td>Filterable, sortable DataTable of all orders</td> </tr> <tr> <td>OrderDetail</td> <td>/orders/:id</td> <td>Order lines, status timeline, action buttons (allocate, ship, cancel)</td> </tr> <tr> <td>OrderCreate</td> <td>/orders/new</td> <td>Multi-line order form: type, customer, product lines with quantities</td> </tr> </tbody></table>

### API Endpoints Used

- `GET /api/v1/orders` — list with filters (status, type, warehouseId, page)
- `POST /api/v1/orders` — create order
- `GET /api/v1/orders/:id` — order detail
- `PUT /api/v1/orders/:id` — update order
- `POST /api/v1/orders/:id/allocate` — allocate inventory
- `POST /api/v1/orders/:id/ship` — mark as shipped

---

## 7. Picking MFE (port 3003)

### Pages

<table id="bkmrk-page-route-descripti-3"> <thead> <tr> <th>Page</th> <th>Route</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td>WaveList</td> <td>/picking</td> <td>All pick waves with status badges and progress indicators</td> </tr> <tr> <td>WaveDetail</td> <td>/picking/:waveId</td> <td>Wave items, per-item progress, start wave action</td> </tr> <tr> <td>PickingExecution</td> <td>/picking/:waveId/execute</td> <td>Mobile-optimized step-by-step picking flow with barcode scanner</td> </tr> <tr> <td>ScannerPage</td> <td>/picking/scan</td> <td>Dedicated barcode/QR code scanning view</td> </tr> <tr> <td>PickConfirmation</td> <td>/picking/:waveId/confirm</td> <td>End-of-wave confirmation and summary</td> </tr> </tbody></table>

### Barcode Scanner

Uses `@zxing/library` for in-browser barcode scanning. Supported formats:

- CODE\_128 (product barcodes)
- EAN\_13 (retail barcodes)
- QR\_CODE (location codes)

### API Endpoints Used

- `GET /api/v1/picking/waves` — list waves
- `GET /api/v1/picking/waves/:id` — wave detail
- `POST /api/v1/picking/waves/:id/start` — start wave
- `POST /api/v1/picking/waves/:id/pick-item` — confirm item picked

---

## 8. Settings MFE (port 3004)

### Tab Navigation

- Användare (Users)
- Lager (Warehouses)
- Transportörer (Carriers)
- Integrationer (Integrations)
- Profil (Profile)

### Pages

<table id="bkmrk-page-route-descripti-4"> <thead> <tr> <th>Page</th> <th>Route</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td>UserManagement</td> <td>/settings/users</td> <td>Full CRUD for users; role assignment (UserRole enum)</td> </tr> <tr> <td>WarehouseSettings</td> <td>/settings/warehouse</td> <td>Warehouse details, timezone, active/inactive toggle</td> </tr> <tr> <td>CarrierSettings</td> <td>/settings/carriers</td> <td>PostNord, DHL, Instabee — activate/deactivate, API keys</td> </tr> <tr> <td>IntegrationSettings</td> <td>/settings/integrations</td> <td>Fortnox OAuth flow, sync status, manual sync trigger</td> </tr> <tr> <td>ProfilePage</td> <td>/settings/profile</td> <td>Current user — name, email, password change</td> </tr> </tbody></table>

### API Endpoints Used

- `GET/POST/PUT/DELETE /api/v1/users`
- `GET/PUT /api/v1/warehouses`
- `GET/POST /api/v1/carriers`
- `POST /api/v1/carriers/:id/activate`
- `POST /api/v1/carriers/:id/deactivate`
- `GET /integrations/fortnox/connect` — OAuth redirect
- `GET /integrations/fortnox/callback` — OAuth callback
- `POST /integrations/fortnox/sync` — manual sync trigger

---

## 9. AI MFE (port 3005)

### Tab Navigation

- AI-chatt (AI Chat)
- Analys (Analytics)
- Varningar (Alerts)

### Features

#### AI Chat

- Streaming responses via Server-Sent Events (`useSSE` hook)
- Language toggle: Swedish / English
- Example prompt chips to guide users
- Chat history within session

#### Analytics

- CSS-only charts (no Recharts, no Chart.js) for zero bundle overhead
- Metrics: inventory levels over time, order throughput, picking performance

#### Alerts

- Severity filter: INFO, WARNING, CRITICAL
- Per-alert actions: Acknowledge / Dismiss

### API Endpoints (Planned)

- `POST /api/v1/ai/chat` — streaming chat endpoint (SSE)
- `GET /api/v1/analytics/inventory` — inventory trend data
- `GET /api/v1/analytics/orders` — order throughput data
- `GET /api/v1/analytics/picking` — picking performance metrics

---

## 10. Backend API Reference

Kotlin/Ktor backend running on **port 8080**. 24 route files, JWT authentication required on all `/api/v1/*` endpoints.

### Authentication

<table id="bkmrk-method-path-descript"> <thead> <tr> <th>Method</th> <th>Path</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td>POST</td> <td>/auth/login</td> <td>Login — returns JWT token</td> </tr> <tr> <td>POST</td> <td>/auth/register</td> <td>Register new user</td> </tr> <tr> <td>GET</td> <td>/api/v1/auth/me</td> <td>Validate token, return current user</td> </tr> </tbody></table>

### Warehouses

<table id="bkmrk-method-path-descript-1"> <thead> <tr> <th>Method</th> <th>Path</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td>GET</td> <td>/api/v1/warehouses</td> <td>List all warehouses</td> </tr> <tr> <td>POST</td> <td>/api/v1/warehouses</td> <td>Create warehouse</td> </tr> <tr> <td>GET</td> <td>/api/v1/warehouses/:id</td> <td>Get warehouse by ID</td> </tr> <tr> <td>PUT</td> <td>/api/v1/warehouses/:id</td> <td>Update warehouse</td> </tr> <tr> <td>DELETE</td> <td>/api/v1/warehouses/:id</td> <td>Delete warehouse</td> </tr> </tbody></table>

### Products

<table id="bkmrk-method-path-descript-2"> <thead> <tr> <th>Method</th> <th>Path</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td>GET</td> <td>/api/v1/products</td> <td>List products (query: search, category, warehouseId, page, size)</td> </tr> <tr> <td>POST</td> <td>/api/v1/products</td> <td>Create product</td> </tr> <tr> <td>GET</td> <td>/api/v1/products/:id</td> <td>Get product</td> </tr> <tr> <td>PUT</td> <td>/api/v1/products/:id</td> <td>Update product</td> </tr> <tr> <td>DELETE</td> <td>/api/v1/products/:id</td> <td>Delete product</td> </tr> </tbody></table>

### Inventory

<table id="bkmrk-method-path-descript-3"> <thead> <tr> <th>Method</th> <th>Path</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td>GET</td> <td>/api/v1/inventory</td> <td>List inventory items</td> </tr> <tr> <td>POST</td> <td>/api/v1/inventory</td> <td>Create inventory record</td> </tr> <tr> <td>GET</td> <td>/api/v1/inventory/:id</td> <td>Get inventory item</td> </tr> <tr> <td>PUT</td> <td>/api/v1/inventory/:id</td> <td>Update inventory item</td> </tr> <tr> <td>POST</td> <td>/api/v1/inventory/adjust</td> <td>Manual stock adjustment (AdjustStockRequest)</td> </tr> <tr> <td>GET</td> <td>/api/v1/inventory/search</td> <td>Search inventory by product, location, status</td> </tr> <tr> <td>GET</td> <td>/api/v1/inventory/low-stock</td> <td>Items below reorder threshold</td> </tr> </tbody></table>

### Orders

<table id="bkmrk-method-path-descript-4"> <thead> <tr> <th>Method</th> <th>Path</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td>GET</td> <td>/api/v1/orders</td> <td>List orders (filter: status, type, warehouseId)</td> </tr> <tr> <td>POST</td> <td>/api/v1/orders</td> <td>Create order</td> </tr> <tr> <td>GET</td> <td>/api/v1/orders/:id</td> <td>Get order with lines</td> </tr> <tr> <td>PUT</td> <td>/api/v1/orders/:id</td> <td>Update order</td> </tr> <tr> <td>DELETE</td> <td>/api/v1/orders/:id</td> <td>Delete / cancel order</td> </tr> <tr> <td>POST</td> <td>/api/v1/orders/:id/allocate</td> <td>Allocate inventory to order</td> </tr> <tr> <td>POST</td> <td>/api/v1/orders/:id/ship</td> <td>Mark order as shipped</td> </tr> </tbody></table>

### Picking

<table id="bkmrk-method-path-descript-5"> <thead> <tr> <th>Method</th> <th>Path</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td>GET</td> <td>/api/v1/picking/waves</td> <td>List pick waves</td> </tr> <tr> <td>POST</td> <td>/api/v1/picking/waves</td> <td>Create pick wave</td> </tr> <tr> <td>GET</td> <td>/api/v1/picking/waves/:id</td> <td>Get wave detail with items</td> </tr> <tr> <td>POST</td> <td>/api/v1/picking/waves/:id/start</td> <td>Start wave (assigns to current user)</td> </tr> <tr> <td>POST</td> <td>/api/v1/picking/waves/:id/pick-item</td> <td>Confirm item picked at location</td> </tr> </tbody></table>

### Receiving

<table id="bkmrk-method-path-descript-6"> <thead> <tr> <th>Method</th> <th>Path</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td>GET/POST</td> <td>/api/v1/receiving</td> <td>List / create receiving orders (POs)</td> </tr> <tr> <td>GET/PUT/DELETE</td> <td>/api/v1/receiving/:id</td> <td>Get / update / delete receiving order</td> </tr> <tr> <td>POST</td> <td>/api/v1/receiving/:id/receive-item</td> <td>Record receipt of a specific line item</td> </tr> <tr> <td>POST</td> <td>/api/v1/receiving/:id/complete</td> <td>Mark receiving order as complete</td> </tr> </tbody></table>

### Locations &amp; Zones

<table id="bkmrk-method-path-descript-7"> <thead> <tr> <th>Method</th> <th>Path</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td>GET/POST</td> <td>/api/v1/locations</td> <td>List / create locations</td> </tr> <tr> <td>GET/PUT/DELETE</td> <td>/api/v1/locations/:id</td> <td>CRUD single location</td> </tr> <tr> <td>GET</td> <td>/api/v1/locations/put-away/suggest</td> <td>AI-suggested put-away location for a product</td> </tr> <tr> <td>GET/POST</td> <td>/api/v1/zones</td> <td>List / create zones</td> </tr> <tr> <td>GET/PUT/DELETE</td> <td>/api/v1/zones/:id</td> <td>CRUD single zone</td> </tr> </tbody></table>

### Suppliers

<table id="bkmrk-method-path-descript-8"> <thead> <tr> <th>Method</th> <th>Path</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td>GET/POST</td> <td>/api/v1/suppliers</td> <td>List / create suppliers</td> </tr> <tr> <td>GET/PUT/DELETE</td> <td>/api/v1/suppliers/:id</td> <td>CRUD single supplier</td> </tr> </tbody></table>

### Carriers

<table id="bkmrk-method-path-descript-9"> <thead> <tr> <th>Method</th> <th>Path</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td>GET/POST</td> <td>/api/v1/carriers</td> <td>List / create carriers</td> </tr> <tr> <td>GET/PUT/DELETE</td> <td>/api/v1/carriers/:id</td> <td>CRUD single carrier</td> </tr> <tr> <td>POST</td> <td>/api/v1/carriers/:id/activate</td> <td>Activate carrier</td> </tr> <tr> <td>POST</td> <td>/api/v1/carriers/:id/deactivate</td> <td>Deactivate carrier</td> </tr> <tr> <td>GET/POST</td> <td>/api/v1/carriers/postnord/\*</td> <td>PostNord carrier integration endpoints</td> </tr> <tr> <td>GET/POST</td> <td>/api/v1/carriers/dhl/\*</td> <td>DHL carrier integration endpoints</td> </tr> <tr> <td>GET/POST</td> <td>/api/v1/carriers/instabee/\*</td> <td>Instabee carrier integration endpoints</td> </tr> </tbody></table>

### Shipments

<table id="bkmrk-method-path-descript-10"> <thead> <tr> <th>Method</th> <th>Path</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td>GET/POST</td> <td>/api/v1/shipments</td> <td>List / create shipments</td> </tr> <tr> <td>GET/PUT/DELETE</td> <td>/api/v1/shipments/:id</td> <td>CRUD single shipment</td> </tr> <tr> <td>POST</td> <td>/api/v1/shipments/:id/{transition}</td> <td>State machine transition (book, dispatch, deliver, etc.)</td> </tr> </tbody></table>

### Roles &amp; Users

<table id="bkmrk-method-path-descript-11"> <thead> <tr> <th>Method</th> <th>Path</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td>GET/POST</td> <td>/api/v1/roles</td> <td>List / create roles</td> </tr> <tr> <td>POST</td> <td>/api/v1/roles/:id/assign</td> <td>Assign role to user</td> </tr> <tr> <td>POST</td> <td>/api/v1/roles/:id/remove</td> <td>Remove role from user</td> </tr> <tr> <td>GET/POST</td> <td>/api/v1/users</td> <td>List / create users</td> </tr> <tr> <td>GET/PUT/DELETE</td> <td>/api/v1/users/:id</td> <td>CRUD single user</td> </tr> </tbody></table>

### Cycle Counts

<table id="bkmrk-method-path-descript-12"> <thead> <tr> <th>Method</th> <th>Path</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td>GET/POST</td> <td>/api/v1/cycle-counts</td> <td>List / create cycle count tasks</td> </tr> <tr> <td>GET/PUT/DELETE</td> <td>/api/v1/cycle-counts/:id</td> <td>CRUD single cycle count</td> </tr> <tr> <td>POST</td> <td>/api/v1/cycle-counts/:id/start</td> <td>Start cycle count</td> </tr> <tr> <td>POST</td> <td>/api/v1/cycle-counts/:id/complete</td> <td>Complete and calculate variance</td> </tr> <tr> <td>GET</td> <td>/api/v1/cycle-counts/:id/variance</td> <td>Get variance report for completed count</td> </tr> </tbody></table>

### Returns

<table id="bkmrk-method-path-descript-13"> <thead> <tr> <th>Method</th> <th>Path</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td>GET/POST</td> <td>/api/v1/returns</td> <td>List / create return orders (RMAs)</td> </tr> <tr> <td>POST</td> <td>/api/v1/returns/:id/authorize</td> <td>Authorize return</td> </tr> <tr> <td>POST</td> <td>/api/v1/returns/:id/receive</td> <td>Receive returned items</td> </tr> <tr> <td>POST</td> <td>/api/v1/returns/:id/inspect</td> <td>Inspect and grade returned items</td> </tr> <tr> <td>POST</td> <td>/api/v1/returns/:id/complete</td> <td>Complete return processing</td> </tr> </tbody></table>

### Webhooks

<table id="bkmrk-method-path-descript-14"> <thead> <tr> <th>Method</th> <th>Path</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td>GET/POST</td> <td>/api/v1/webhooks</td> <td>List / register webhooks</td> </tr> <tr> <td>GET/PUT/DELETE</td> <td>/api/v1/webhooks/:id</td> <td>CRUD single webhook</td> </tr> <tr> <td>POST</td> <td>/api/v1/webhooks/:id/test</td> <td>Send test event to webhook URL</td> </tr> <tr> <td>POST</td> <td>/api/v1/webhooks/retry-failed</td> <td>Retry all failed webhook deliveries</td> </tr> <tr> <td>GET</td> <td>/api/v1/webhooks/:id/deliveries</td> <td>Delivery history with status codes</td> </tr> </tbody></table>

### SSE Events

<table id="bkmrk-method-path-descript-15"> <thead> <tr> <th>Method</th> <th>Path</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td>GET</td> <td>/api/v1/events/stream</td> <td>Server-Sent Events stream — real-time warehouse events</td> </tr> </tbody></table>

### Barcodes

<table id="bkmrk-method-path-descript-16"> <thead> <tr> <th>Method</th> <th>Path</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td>POST</td> <td>/api/v1/barcodes/generate</td> <td>Generate barcode image for arbitrary data</td> </tr> <tr> <td>GET</td> <td>/api/v1/barcodes/product/:id</td> <td>Generate barcode for a product</td> </tr> <tr> <td>GET</td> <td>/api/v1/barcodes/location/:id</td> <td>Generate barcode for a location</td> </tr> <tr> <td>GET</td> <td>/api/v1/barcodes/lookup/:barcode</td> <td>Resolve barcode to product or location</td> </tr> </tbody></table>

### Audit &amp; Stock Movements

<table id="bkmrk-method-path-descript-17"> <thead> <tr> <th>Method</th> <th>Path</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td>GET</td> <td>/api/v1/audit</td> <td>Audit log (query: warehouseId, entityType, entityId, userId, from, to)</td> </tr> <tr> <td>GET</td> <td>/api/v1/stock-movements</td> <td>All stock movement events (IN/OUT/ADJUST/TRANSFER)</td> </tr> </tbody></table>

### Dashboard

<table id="bkmrk-method-path-descript-18"> <thead> <tr> <th>Method</th> <th>Path</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td>GET</td> <td>/api/v1/dashboard/stats</td> <td>KPI stats: orders, waves, shipments, low-stock count</td> </tr> </tbody></table>

### Fortnox Integration

<table id="bkmrk-method-path-descript-19"> <thead> <tr> <th>Method</th> <th>Path</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td>GET</td> <td>/integrations/fortnox/connect</td> <td>OAuth2 initiation — redirects to Fortnox</td> </tr> <tr> <td>GET</td> <td>/integrations/fortnox/callback</td> <td>OAuth2 callback — stores tokens</td> </tr> <tr> <td>POST</td> <td>/integrations/fortnox/sync</td> <td>Manual sync trigger (products, customers, orders)</td> </tr> <tr> <td>GET</td> <td>/integrations/fortnox/status</td> <td>Connection status and last sync time</td> </tr> </tbody></table>

---

## 11. Brand &amp; Design Tokens

All design tokens are defined as CSS custom properties in `frontend/shell/src/styles/globals.css` and imported by each MFE.

### Color Palette

<table id="bkmrk-token-value-usage---"> <thead> <tr> <th>Token</th> <th>Value</th> <th>Usage</th> </tr> </thead> <tbody> <tr> <td>`--color-primary`</td> <td>\#1B4D3E</td> <td>Sidebar, primary buttons, headings</td> </tr> <tr> <td>`--color-secondary`</td> <td>\#2E7D5B</td> <td>Hover states, secondary actions</td> </tr> <tr> <td>`--color-accent`</td> <td>\#F5A623</td> <td>Alerts, highlights, badges</td> </tr> <tr> <td>`--color-surface`</td> <td>\#F7FAF8</td> <td>Page backgrounds</td> </tr> <tr> <td>`--color-text`</td> <td>\#1A2B23</td> <td>Primary text</td> </tr> <tr> <td>`--color-border`</td> <td>\#e4ebe7</td> <td>Card borders, dividers</td> </tr> <tr> <td>`--color-muted`</td> <td>\#8aaa97</td> <td>Secondary text, placeholder</td> </tr> </tbody></table>

### Typography

<table id="bkmrk-context-font-heading"> <thead> <tr> <th>Context</th> <th>Font</th> </tr> </thead> <tbody> <tr> <td>Headings &amp; body</td> <td>Inter (Google Fonts)</td> </tr> <tr> <td>Code blocks, monospace</td> <td>JetBrains Mono</td> </tr> </tbody></table>

### Spacing &amp; Grid

- Base unit: **8px** (Tailwind's default scale)
- Icons: **Lucide React** — consistent stroke-based icon set
- Border radius: rounded-md (6px) for cards, rounded-lg (8px) for modals

---

## 12. Development Commands

### Setup

```
# Install all workspace dependencies (run from repo root)
npm install
```

### Development Servers

```
# Run the Shell app on http://localhost:3000
npm run dev:shell

# Run the Inventory MFE on http://localhost:3001
npm run dev:inventory

# Run Orders MFE on http://localhost:3002
npm run dev --workspace=frontend/mfe-orders

# Run Picking MFE on http://localhost:3003
npm run dev --workspace=frontend/mfe-picking

# Run Settings MFE on http://localhost:3004
npm run dev --workspace=frontend/mfe-settings

# Run AI MFE on http://localhost:3005
npm run dev --workspace=frontend/mfe-ai

# Run all (Shell + Inventory concurrently)
npm run dev:all
```

### Type Checking

```
# Type-check ALL workspaces simultaneously via Turborepo
npx turbo run type-check
```

### Build

```
# Build all workspaces (production)
npx turbo run build

# Or via root script
npm run build
```

### Backend

```
# Start Kotlin/Ktor backend on http://localhost:8080
cd backend && ./gradlew run

# Compile check only (no run)
cd backend && ./gradlew compileKotlin

# Run database migrations (Flyway)
cd backend && ./gradlew flywayMigrate
```

### Linting &amp; Formatting

```
# Lint all workspaces
npm run lint

# Format all files with Prettier
npm run format

# Check formatting without writing
npm run format:check
```

### Testing

```
# Run all tests
npm run test

# Run with coverage
npm run test:coverage
```

---

## 13. File Counts &amp; Project Size

<table id="bkmrk-layer-count-notes-fr"> <thead> <tr> <th>Layer</th> <th>Count</th> <th>Notes</th> </tr> </thead> <tbody> <tr> <td>Frontend (TypeScript/TSX)</td> <td>74 files</td> <td>Shell + 5 MFEs</td> </tr> <tr> <td>Backend (Kotlin)</td> <td>74 files</td> <td>24 route files, 21 services, models, plugins</td> </tr> <tr> <td>@plock/types</td> <td>1 file, 242 lines</td> <td>All shared TypeScript types</td> </tr> <tr> <td>@plock/ui</td> <td>14 components + index</td> <td>Shared React component library</td> </tr> <tr> <td>@plock/utils</td> <td>5 modules</td> <td>api.ts, auth utils, useAuth, useFetch, useSSE</td> </tr> </tbody></table>

### Backend Route File Summary

<table id="bkmrk-domain-route-file-au"> <thead> <tr> <th>Domain</th> <th>Route File</th> </tr> </thead> <tbody> <tr> <td>Auth</td> <td>authRoutes.kt</td> </tr> <tr> <td>Warehouses</td> <td>warehouseRoutes.kt</td> </tr> <tr> <td>Products</td> <td>productRoutes.kt</td> </tr> <tr> <td>Inventory</td> <td>inventoryRoutes.kt</td> </tr> <tr> <td>Orders</td> <td>orderRoutes.kt</td> </tr> <tr> <td>Picking</td> <td>pickingRoutes.kt</td> </tr> <tr> <td>Receiving</td> <td>receivingRoutes.kt</td> </tr> <tr> <td>Locations</td> <td>locationRoutes.kt</td> </tr> <tr> <td>Zones</td> <td>zoneRoutes.kt</td> </tr> <tr> <td>Suppliers</td> <td>supplierRoutes.kt</td> </tr> <tr> <td>Carriers</td> <td>carrierRoutes.kt</td> </tr> <tr> <td>Shipments</td> <td>shipmentRoutes.kt</td> </tr> <tr> <td>Roles</td> <td>roleRoutes.kt</td> </tr> <tr> <td>Users</td> <td>userRoutes.kt</td> </tr> <tr> <td>Audit</td> <td>auditRoutes.kt</td> </tr> <tr> <td>Stock Movements</td> <td>stockMovementRoutes.kt</td> </tr> <tr> <td>Cycle Counts</td> <td>cycleCountRoutes.kt</td> </tr> <tr> <td>Returns</td> <td>returnRoutes.kt</td> </tr> <tr> <td>Webhooks</td> <td>webhookRoutes.kt</td> </tr> <tr> <td>SSE</td> <td>sseRoutes.kt</td> </tr> <tr> <td>Barcodes</td> <td>barcodeRoutes.kt</td> </tr> <tr> <td>Dashboard</td> <td>dashboardRoutes.kt</td> </tr> <tr> <td>Fortnox Integration</td> <td>fortnoxRoutes.kt</td> </tr> <tr> <td>PostNord/DHL/Instabee</td> <td>carrierIntegrationRoutes.kt</td> </tr> </tbody></table>

---

*Document maintained by ALAI Engineering. Last generated: 2026-03-04.*