578 lines
33 KiB
Markdown
578 lines
33 KiB
Markdown
# AGENTS.md — Dashfront Developer & Agent Guide
|
|
|
|
> **Audience**: AI Coding Agents & Pair-Programmers working on `dashfront`.
|
|
> **Workspace**: `/home/seb/src/dashfront`
|
|
> **Last Updated**: 2026-09-21
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
1. [System Overview & Ecosystem Map](#1-system-overview--ecosystem-map)
|
|
2. [Technology Stack & Key Libraries](#2-technology-stack--key-libraries)
|
|
3. [Directory Layout](#3-directory-layout)
|
|
4. [State Management Conventions](#4-state-management-conventions)
|
|
5. [Backend Communication & Authentication Rules](#5-backend-communication--authentication-rules)
|
|
6. [Route & Navigation Architecture](#6-route--navigation-architecture)
|
|
7. [Developer Workflows & Commands](#7-developer-workflows--commands)
|
|
8. [Critical Agent Rules & Common Pitfalls](#8-critical-agent-rules--common-pitfalls)
|
|
9. [Deep Dive: The Automats (`react-automat`)](#9-deep-dive-the-automats-react-automat)
|
|
10. [Deep Dive: Configuration Automats (Company, Category, Brand, Unit & POS Terminals)](#10-deep-dive-configuration-automats-company-category-brand-unit--pos-terminals)
|
|
11. [`dashgui` Component Library Integration](#11-dashgui-component-library-integration)
|
|
12. [Application Views & Route Hierarchy](#12-application-views--route-hierarchy)
|
|
13. [Media, Images & Document Pipelines](#13-media-images--document-pipelines)
|
|
14. [Real-time SSE Signals & Invalidation](#14-real-time-sse-signals--invalidation)
|
|
15. [Developer Tooling & Debugging](#15-developer-tooling--debugging)
|
|
16. [Agent Cheatsheet & Code Recipes](#16-agent-cheatsheet--code-recipes)
|
|
|
|
---
|
|
|
|
## 1. System Overview & Ecosystem Map
|
|
|
|
`dashfront` is the React web client and Progressive Web App (PWA) for the Dash ERP/POS system. It is part of a multi-repository workspace under `/home/seb/src/`:
|
|
|
|
```
|
|
/home/seb/src/
|
|
├── dashfront/ # THIS REPO: Vite + React 19 web/PWA client (port 4209 HTTP, 4308 HTTPS)
|
|
├── dashback/ # Backend Express REST/SSE API server (port 4208)
|
|
├── dashdbschema/ # LibDashStore (MSSQL data layer, migrations, schema graph, API docs)
|
|
├── dashgui/ # Reusable custom UI component library (file:../dashgui)
|
|
└── automat/ # react-automat state management library (file:../automat or npm)
|
|
```
|
|
|
|
### End-to-End Data & Execution Flow
|
|
|
|
```yaml
|
|
# Machine-readable execution flow:
|
|
flow:
|
|
- step: 1_client
|
|
component: dashfront
|
|
runtime: React 19 (PureComponent & Function Components)
|
|
state_layers:
|
|
automats: [artikelAutomat, articleSearchAutomat, articleIndexAutomat, deliveryEditorAutomat, authAutomat, sseAutomat, supplierAutomat, navigationAutomat, userSettingsAutomat, companyAutomat, categoryAutomat, brandAutomat, unitAutomat, posterminalAutomat, deliveryAutomat, quoteAutomat, orderAutomat, entityAutomat, customergroupAutomat, numberAutomat]
|
|
transport: src/api (http(), named namespaces)
|
|
egress:
|
|
protocol: HTTP (fetch with credentials: "include") & SSE (/api/events)
|
|
proxy: Vite dev server (/api -> http://localhost:4208)
|
|
target: dashback
|
|
|
|
- step: 2_api_gateway
|
|
component: dashback
|
|
runtime: Express 5 API server (src/api/routes/*)
|
|
security:
|
|
auth_middleware: Cookie auth middleware (validates JWT in HTTP-only "token" cookie)
|
|
context_injection:
|
|
user_auth_id: req.user.googleAuthId
|
|
tenant_company_id: req.selectedCompanyId
|
|
events: Dispatches real-time SSE mutation events to subscribed clients via /api/events
|
|
egress:
|
|
interface: In-process method calls (store.<namespace>.<method>())
|
|
target: dashdbschema
|
|
|
|
- step: 3_data_layer
|
|
component: dashdbschema
|
|
runtime: LibDashStore (src/store/*)
|
|
storage: MSSQL connection pool, migrations, and SQL transactions
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Technology Stack & Key Libraries
|
|
|
|
| Technology | Version / Spec | Notes |
|
|
|---|---|---|
|
|
| **Build & Dev** | Vite 8 (`type: module`) | Dev server on `4209`, HTTPS on `4308`, proxies `/api` → `http://localhost:4208` |
|
|
| **Framework** | React 19 (`react`, `react-dom`) | Uses `PureComponent` (with Automats & dashgui) and modern function components |
|
|
| **Routing** | `react-router-dom` v7 | Configured in [src/appRouter.jsx](file:///home/seb/src/dashfront/src/appRouter.jsx) with HMR router preservation |
|
|
| **UI & Theming** | MUI 9 (`@mui/material`, Emotion) | Custom theme in `src/theme/`, base background `#85b7ad` in `src/colors.js` |
|
|
| **Shared GUI** | `dashgui` (`file:../dashgui`) | `PanelTile`, `RecordList`, `Tile`, `SingleLineTextEdit`, `ElevatedPill` |
|
|
| **State Management** | `react-automat` | Single state layer for all domain & configuration data (articles, categories, company, brands, units, POS terminals, session) |
|
|
| **Notifications** | `notistack` | `useSnackbar()` / `<SnackbarProvider>` in [src/main.jsx](file:///home/seb/src/dashfront/src/main.jsx) |
|
|
| **Media & Images** | `@jsquash/avif`, `@jsquash/resize`, `thumbhash` | Client-side AVIF encoding and instant thumbnail hash generation |
|
|
| **Documents** | `pdfconvert-wasm` | In-browser PDF processing for supplier delivery invoices & receipts |
|
|
| **Drag & Drop** | `@shopify/draggable`, `sortablejs` | Category re-ordering, article assignments, nested tree nodes |
|
|
|
|
---
|
|
|
|
## 3. Directory Layout
|
|
|
|
```
|
|
src/
|
|
├── api/ # Backend communication layer
|
|
│ └── index.js # Canonical API module (article, category, delivery, entity, …)
|
|
├── automats/ # react-automat state modules (fine-grained subscriptions)
|
|
│ ├── artikelAutomat.js # Article cache, deduplicated loads, and reactive single-article state
|
|
│ ├── authAutomat.js # Authentication state, session bootstrap, company selection
|
|
│ ├── companyAutomat.js # Active company config (tax rates, root category, legal details)
|
|
│ ├── categoryAutomat.js # Hierarchical category tree, breadcrumbs, and category articles
|
|
│ ├── brandAutomat.js # Catalog brands / manufacturer list
|
|
│ ├── unitAutomat.js # Units of measure (Stück, kg, Liter, ...)
|
|
│ ├── posterminalAutomat.js # POS terminal config and enabled categories
|
|
│ ├── deliveryEditorAutomat.js # Multi-line delivery receipt / invoice editor & draft state
|
|
│ ├── navigationAutomat.js # Remembers active paths (last delivery path, last article SKU)
|
|
│ ├── supplierAutomat.js # Supplier list and supplier-article catalogs
|
|
│ ├── userSettingsAutomat.js # User preferences (viewer positions, layout settings)
|
|
│ └── index.js # Barrel exports for all automats and helper selectors
|
|
├── components/ # Shared UI components
|
|
│ ├── AppShell.jsx # Main application frame (top bar, sidebar, nav buttons)
|
|
│ ├── SectionLayout.jsx # Secondary navigation header for sections (/artikel, /lieferung...)
|
|
│ ├── AuthBootstrap.jsx # Session bootstrapper, Google One Tap, company setup gate
|
|
│ ├── LoginPage.jsx # Google One Tap / credentials login interface
|
|
│ ├── UserMenu.jsx # User avatar, company switcher dropdown, logout action
|
|
│ ├── BackendError.jsx # Graceful error banner for backend communication failures
|
|
│ ├── article/ # Article tiles, editors, dialogs, photos, price tiers
|
|
│ ├── category/ # Category navigation, dialogs, and picker components
|
|
│ ├── manifest/ # Manifest / delivery line inspection widgets
|
|
│ ├── settings/ # Terminal, unit, and brand settings dialogs
|
|
│ └── tiles/ # Tile components extending dashgui base Tile
|
|
├── devtools/ # Dev-only debug tools
|
|
│ └── AutomatInspector.jsx # Docked overlay inspecting all registered window.__AUTOMATS__
|
|
├── pages/ # Top-level view routes
|
|
│ ├── artikel/ # Article index, category browser, warehouse, SKU detail editor
|
|
│ ├── lieferung/ # Delivery list, new delivery editor, supplier manager, edit page
|
|
│ ├── auftrag/ # Quotes (Angebote) and orders (Bestellungen)
|
|
│ ├── kunde/ # Customer list, new customer, customer groups, edit page
|
|
│ └── infos/ # System and company information page
|
|
├── theme/ # MUI theme definitions (light/dark palette, typography, components)
|
|
├── utils/ # Helpers (apiShape.js, number formatters, notify.js, etc.)
|
|
├── appRouter.jsx # react-router-dom route definitions
|
|
├── nav.js # Navigation items (NAV_ITEMS, SUB_NAV, badges, active path matchers)
|
|
├── colors.js # Shared color constants (MAIN_BACKGROUND)
|
|
├── main.jsx # React root mount, providers, dev SW unregistration
|
|
└── App.jsx # Main router provider wrapper
|
|
```
|
|
|
|
---
|
|
|
|
## 4. State Management Conventions
|
|
|
|
In `dashfront`, two state management solutions coexist harmoniously:
|
|
|
|
### A. `react-automat` (Recommended for Domain Models & Frequent Updates)
|
|
- **Skill references**: Read [.agents/skills/automat/SKILL.md](file:///home/seb/src/dashfront/.agents/skills/automat/SKILL.md) and [.agents/skills/coding-style/SKILL.md](file:///home/seb/src/dashfront/.agents/skills/coding-style/SKILL.md) (names [src/automats/brandAutomat.js](file:///home/seb/src/dashfront/src/automats/brandAutomat.js) as the reference automat).
|
|
- Lives outside the React lifecycle in module scope (`src/automats/*.js`).
|
|
- Designed for React `PureComponent` to bypass unnecessary React reconciliations.
|
|
- **Pattern**:
|
|
```jsx
|
|
import { PureComponent } from 'react';
|
|
import { artikelAutomat, createArticleSelector, getArticle } from '../automats';
|
|
|
|
class ArticleView extends PureComponent {
|
|
constructor(props) {
|
|
super(props);
|
|
// 1. Synchronous read in constructor — zero blank flash!
|
|
this.state = {
|
|
article: getArticle(props.sku),
|
|
};
|
|
}
|
|
|
|
componentDidMount() {
|
|
// 2. Subscribe with fine-grained selector
|
|
this.unsub = artikelAutomat.subscribe(this, (state) => ({
|
|
article: createArticleSelector(this.props.sku)(state),
|
|
}));
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
// 3. Always unsubscribe
|
|
this.unsub?.();
|
|
}
|
|
|
|
render() {
|
|
const { article } = this.state;
|
|
if (!article) return <div>Lädt...</div>;
|
|
return <div>{article.name}</div>;
|
|
}
|
|
}
|
|
```
|
|
|
|
### B. Configuration Automats (`src/automats/`)
|
|
- Company, hierarchical `category` tree, `brand`, `posterminal`, and `unit` state live in dedicated automats (`companyAutomat`, `categoryAutomat`, `brandAutomat`, `unitAutomat`, `posterminalAutomat`).
|
|
- Company-scoped **lists** (article index, deliveries, quotes, orders, entities, customer groups, number sequences) live in list automats built by `createListAutomat`.
|
|
- Reads use the same fine-grained `subscribe`/selector pattern as the domain automats.
|
|
- Cross-automat coordination is done by subscribing to `authAutomat`: when it reaches `authed` with a `selectedCompanyId`, these automats load themselves and reset on `anon` (see [§10](#10-deep-dive-configuration-automats-company-category-brand-unit--pos-terminals)).
|
|
|
|
### C. No React Context for Data (`src/api` has no context layer)
|
|
- There is **no** store/context provider in this app. Components import the automat or `src/api` namespace they need directly. There is deliberately no `static contextType` for data access.
|
|
- Call the API through the namespace functions (`article.getBySku(...)`, `delivery.list()`), and keep shared/cached state in an automat.
|
|
- Barrel exports live in [src/automats/index.js](file:///home/seb/src/dashfront/src/automats/index.js); `src/main.jsx` imports it for its side effects so every store is registered before the session bootstraps.
|
|
|
|
---
|
|
|
|
## 5. Backend Communication & Authentication Rules
|
|
|
|
### Authentication & Cookies
|
|
1. **HTTP-only cookie**: The session token is stored in an HTTP-only cookie (`token`).
|
|
2. **`credentials: 'include'`**: Every `fetch` request MUST set `credentials: 'include'`. The `http()` helper in [src/api/index.js](file:///home/seb/src/dashfront/src/api/index.js) does this automatically.
|
|
3. **Never pass `googleAuthId`**: The frontend NEVER passes `googleAuthId` in request bodies or query parameters. The backend middleware extracts the authenticated user directly from the cookie session!
|
|
4. **Session Company**: Multi-tenancy is scoped to the user's selected company. When a user switches companies (`PUT /api/auth/selected-company`), the backend updates the cookie session. All subsequent calls operate on that company.
|
|
|
|
### API Entry Point
|
|
Import API namespaces from `src/api`:
|
|
```js
|
|
import { article, category, delivery, supplier, entity, company } from '../api';
|
|
|
|
// Examples:
|
|
const { article: data } = await article.getBySku('SKU-1001');
|
|
const { deliveries } = await delivery.list();
|
|
const { suppliers } = await supplier.list();
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Route & Navigation Architecture
|
|
|
|
- Main navigation sections are defined in [src/nav.js](file:///home/seb/src/dashfront/src/nav.js):
|
|
- `/artikel` — Articles overview, SKU editor, Warehouse stock
|
|
- `/kategorien/*` — Drag-and-drop category tree
|
|
- `/lieferung` — Inbound deliveries, New delivery editor, Suppliers
|
|
- `/auftrag` — Quotes (`/auftrag`) and Orders (`/auftrag/bestellung`)
|
|
- `/kunde` — Customers, Customer groups
|
|
- `/infos` — System settings & company info
|
|
- **Router Singleton**:
|
|
- Defined in [src/appRouter.jsx](file:///home/seb/src/dashfront/src/appRouter.jsx).
|
|
- Preserved across Vite HMR via `import.meta.hot.data.router` to prevent mid-session resets when hot-reloading code.
|
|
- **Dynamic Badges**:
|
|
- `DeliveryNavIcon` and `DeliveryNeuIcon` in `src/nav.js` subscribe to `deliveryEditorAutomat` to show live uncommitted draft line badges in real time.
|
|
|
|
---
|
|
|
|
## 7. Developer Workflows & Commands
|
|
|
|
Run commands from the repository root `/home/seb/src/dashfront`:
|
|
|
|
```bash
|
|
# Start Vite development server (proxies /api -> http://localhost:4208)
|
|
npm run dev
|
|
|
|
# Start development server with local HTTPS (uses certs/key.pem and certs/cert.pem)
|
|
npm run dev:https
|
|
|
|
# Run full PWA production build + preview (to test service worker / offline caching)
|
|
npm run dev:pwa
|
|
|
|
# Generate local self-signed TLS certificates
|
|
npm run cert
|
|
|
|
# Production build
|
|
npm run build
|
|
|
|
# Preview production build
|
|
npm run preview
|
|
|
|
# Regenerate Automat Graph visualization (automat-graph.svg & automat-graph.dot)
|
|
npm run automat-graph
|
|
```
|
|
|
|
> [!NOTE]
|
|
> When testing camera-based barcode scanning or Progressive Web App features on mobile devices or local network IP addresses, start the server using `npm run dev:https`. Web browsers require a secure context (HTTPS) to grant access to media devices and service workers.
|
|
|
|
---
|
|
|
|
## 8. Critical Agent Rules & Common Pitfalls
|
|
|
|
1. **Do not create dummy/mock backends**: `dashback` runs locally at `http://localhost:4208`. All `/api/*` routes are handled by it.
|
|
2. **Keep `dashgui` links working**: `dashgui` is imported via `file:../dashgui`. Vite has deduplication configured (`dedupe: ['react', 'react-dom', ...]` in `vite.config.js`) to avoid dual-instance React conflicts.
|
|
3. **Article Deletion Restriction**: An article CANNOT be deleted if it has child variants (`is_parent: 1` or `childCount > 0`). The backend returns HTTP 400.
|
|
4. **Prices and Multipliers**:
|
|
- Article prices are tiered by `minmultiplier` and `customergroup_id`.
|
|
- Prices sent to the backend are net prices (`net`).
|
|
5. **Binary / Image Handling**:
|
|
- Image uploads convert source images into AVIF variants client-side.
|
|
- Frontend components use `thumbhash` for instant blur placeholders while loading full images via `/api/image/{fullHash}` or `/api/thumb/{fullHash}`.
|
|
6. **No Hooks in PureComponents**: Never call React hooks (`useState`, `useEffect`) inside `PureComponent` classes. Automats provide direct reactive subscription without hooks.
|
|
7. **Always Unsubscribe**: Every `automat.subscribe(this, ...)` in `componentDidMount()` must have its cleanup callback called in `componentWillUnmount()`.
|
|
|
|
---
|
|
|
|
## 9. Deep Dive: The Automats (`react-automat`)
|
|
|
|
All domain Automats are located in [src/automats/](file:///home/seb/src/dashfront/src/automats/). Each automat exposes an observable state container with explicit action methods:
|
|
|
|
### 1. `artikelAutomat` ([src/automats/artikelAutomat.js](file:///home/seb/src/dashfront/src/automats/artikelAutomat.js))
|
|
- **Role**: Cache and reactive distributor for article detail payloads (`GET /api/article/:sku`).
|
|
- **Inflight Deduplication**: Uses an internal `Map<string, Promise<object>>` so concurrent UI tiles or navigation transitions requesting the same SKU share a single HTTP call.
|
|
- **Key Actions**:
|
|
- `loadArticle(sku)`: Fetches if missing, resolves immediately if cached.
|
|
- `reloadArticle(sku)`: Silently re-fetches to update cache (used on SSE mutation events).
|
|
- `setArticle(article)`: Updates cache after mutation responses.
|
|
- `setArticleName(sku, name)`: Optimistic immediate rename.
|
|
- `clearArticle(sku)` / `clear()`: Invalidates cache.
|
|
- **Helper Selectors**: `getArticle(sku)`, `hasArticle(sku)`, `createArticleSelector(sku)`, `selectArticle(sku)`.
|
|
|
|
### 2. `authAutomat` ([src/automats/authAutomat.js](file:///home/seb/src/dashfront/src/automats/authAutomat.js))
|
|
- **Role**: Manages user authentication lifecycle and multi-tenant company selection.
|
|
- **State**: `{ user: AuthUser | null, googleClientId: string | null, status: 'loading' | 'anon' | 'authed' }`.
|
|
- **Consumed by other automats**: The company, category, brand, unit, and posterminal automats `subscribe` to `authAutomat` and (re)load or reset on its state changes. `authAutomat` also emits lifecycle events via the registered `setAuthEventDispatcher`:
|
|
- `auth/sessionEstablished`
|
|
- `auth/companyChanged`
|
|
- `auth/loggedOut`
|
|
- **Actions**: `bootstrapSession(session)`, `switchCompany(companyId)`, `logout()`.
|
|
|
|
### 3. `deliveryEditorAutomat` ([src/automats/deliveryEditorAutomat.js](file:///home/seb/src/dashfront/src/automats/deliveryEditorAutomat.js))
|
|
- **Role**: State engine for the multi-line inbound delivery receiver and invoice reconciler ([src/pages/lieferung/neu.jsx](file:///home/seb/src/dashfront/src/pages/lieferung/neu.jsx)).
|
|
- **Features**:
|
|
- Line items editor (`lines`, `createEmptyLineitem`).
|
|
- Supplier article auto-matching via `buildSupplierArticlesMap(articles)` indexing by supplier SKU, internal SKU, and EANs.
|
|
- PDF/Receipt attachments parsing with OCR integration.
|
|
- Reactive draft indicators (provides draft counts for badges in `src/nav.js`).
|
|
|
|
### 4. `supplierAutomat` ([src/automats/supplierAutomat.js](file:///home/seb/src/dashfront/src/automats/supplierAutomat.js))
|
|
- **Role**: Caches supplier directory list and maps articles to their corresponding supplier catalogs (`GET /api/supplier`).
|
|
|
|
### 5. `navigationAutomat` ([src/automats/navigationAutomat.js](file:///home/seb/src/dashfront/src/automats/navigationAutomat.js))
|
|
- **Role**: Remembers last active subpaths and last visited article SKU. When a user navigates between the main sidebar sections (e.g. from `/lieferung` to `/artikel` and back), it restores the exact active SKU or delivery document instead of resetting to the root list.
|
|
|
|
### 6. `userSettingsAutomat` ([src/automats/userSettingsAutomat.js](file:///home/seb/src/dashfront/src/automats/userSettingsAutomat.js))
|
|
- **Role**: Stores client-side user layout preferences such as viewer panel coordinates and drawer positions.
|
|
|
|
### 7. `sseAutomat` ([src/automats/sseAutomat.js](file:///home/seb/src/dashfront/src/automats/sseAutomat.js))
|
|
- **Role**: Owns the **single** `EventSource` for `/api/events` and fans signals out into the domain automats. Reconnects whenever the selected company changes; disconnects on logout.
|
|
- **Built-in handlers**: `category` → `categoryAutomat.loadTree()` + `invalidateAllCategoryViews()`; `articles` → `articleIndexAutomat.reload()` + category-view invalidation; `warehouse` → `warehouseAutomat.reload()`; `attributename` / `propertyname` → `attributeAutomat.load()` / `propertyAutomat.load()`; `supplier` → `supplierAutomat.reload()`.
|
|
- **Extension API**:
|
|
- `sseAutomat.actions.on(event, handler)` — register a handler for a named backend event. Returns an unsubscribe callback. Handlers registered while disconnected attach on the next connect.
|
|
- `sseAutomat.actions.watchArticle(sku, handler)` — subscribe to `article-<sku>` for one SKU. Exactly one listener is bound per SKU while at least one caller is subscribed; the listener resolves its handler set at call time, so an unwatched SKU is a no-op.
|
|
- `invalidateArticle(sku)` — reloads the article plus its keyed history/supplier views. Background refreshes stay silent.
|
|
|
|
### 8. Company-Scoped List Automats
|
|
Built with the shared [src/automats/createListAutomat.js](file:///home/seb/src/dashfront/src/automats/createListAutomat.js) factory, which follows the brand/unit/warehouse pattern (`status: idle | loading | ready | failed`):
|
|
|
|
| Automat | Backs |
|
|
|---|---|
|
|
| `articleIndexAutomat` | Article search index (`GET /api/articleIndex`), mapped to the recordset shape; also patched optimistically on rename |
|
|
| `deliveryAutomat` | Delivery list |
|
|
| `quoteAutomat` / `orderAutomat` | Quote / order lists |
|
|
| `entityAutomat` / `customergroupAutomat` | Customer list and customer groups |
|
|
| `numberAutomat` | Document number sequences |
|
|
|
|
Each exposes `load()` (deduplicated), `reload()` (silent), `replace()`, `ensureLoaded()`, and `reset()`. They subscribe to `authAutomat`, so they load on `authed` with a `selectedCompanyId` and reset to `idle` on `anon`.
|
|
|
|
### 9. `articleSearchAutomat` ([src/automats/articleSearchAutomat.js](file:///home/seb/src/dashfront/src/automats/articleSearchAutomat.js))
|
|
- **Role**: Client-side search over the cached article index for the `/artikel` overview. `search(target)` returns `{ categoryFacets, totalCount, recordset, error }`; the page subscribes to `result` instead of receiving a callback.
|
|
|
|
### 10. `articleDetailAutomat` ([src/automats/articleDetailAutomat.js](file:///home/seb/src/dashfront/src/automats/articleDetailAutomat.js))
|
|
- **Role**: Two `KeyedAutomat` stores keyed by SKU — `articleHistoryAutomat` (`GET /api/article/{sku}/history`, paginated client-side) and `articleSupplierAutomat` (`GET /api/article/{sku}/supplier`). Both reload automatically when `sseAutomat` reports an `article-<sku>` signal.
|
|
|
|
---
|
|
|
|
## 10. Deep Dive: Configuration Automats (Company, Category, Brand, Unit & POS Terminals)
|
|
|
|
The remaining static/relational/configuration state is **also** managed by `react-automat`. These automats live alongside the domain automats in [src/automats/](file:///home/seb/src/dashfront/src/automats/) and follow the same subscribe/selector pattern:
|
|
|
|
1. **`companyAutomat`** ([src/automats/companyAutomat.js](file:///home/seb/src/dashfront/src/automats/companyAutomat.js)): Active company configuration, tax rates (standard, reduced), root category ID, company legal details. Selectors: `selectCompanyTaxrate()`, `selectCompanyReducedTaxrate()`, `selectCompanyRootCategoryId()`, `selectCompanyName()`, …
|
|
2. **`categoryAutomat`** ([src/automats/categoryAutomat.js](file:///home/seb/src/dashfront/src/automats/categoryAutomat.js)): Hierarchical category tree (id → node map), breadcrumbs, category navigation, article lists per category, and category/article mutations (`createCategory`, `createArticle`, `deleteCategory`, `moveCategory`).
|
|
3. **`brandAutomat`** ([src/automats/brandAutomat.js](file:///home/seb/src/dashfront/src/automats/brandAutomat.js)): Catalog brands / manufacturer list (`load`, `upsert`).
|
|
4. **`posterminalAutomat`** ([src/automats/posterminalAutomat.js](file:///home/seb/src/dashfront/src/automats/posterminalAutomat.js)): Registered point-of-sale hardware terminals and their enabled categories.
|
|
5. **`unitAutomat`** ([src/automats/unitAutomat.js](file:///home/seb/src/dashfront/src/automats/unitAutomat.js)): Allowed units of measure (Stück, kg, Liter, etc.).
|
|
|
|
### Automatic Bootstrap via `authAutomat` Subscriptions
|
|
Each of these automats subscribes directly to `authAutomat` at module load and coordinates its own loading:
|
|
```js
|
|
// e.g. src/automats/companyAutomat.js
|
|
authAutomat.subscribe((auth) => {
|
|
const companyId = auth.user?.selectedCompanyId;
|
|
if (auth.status === 'authed' && companyId) {
|
|
void companyAutomat.actions.load();
|
|
} else if (auth.status === 'anon') {
|
|
companyAutomat.actions.reset();
|
|
}
|
|
});
|
|
```
|
|
When the auth state becomes `authed` with a `selectedCompanyId` (on session bootstrap, login, or company switch), the company, brand, unit, and posterminal automats load automatically; on logout/anon they reset. `categoryAutomat.loadTree()` then reads the company's `rootCategoryId` from `companyAutomat`.
|
|
|
|
### Automatic Debugging
|
|
All automats are observable in the [AutomatInspector](file:///home/seb/src/dashfront/src/devtools/AutomatInspector.jsx), which lists every automat registered in `window.__AUTOMATS__` with live state, subscriber counts, and actions.
|
|
|
|
---
|
|
|
|
## 11. `dashgui` Component Library Integration
|
|
|
|
`dashgui` is a shared, high-performance UI library linked from `file:../dashgui`:
|
|
|
|
### Core Components
|
|
- **`PanelTile`**: Structured card container featuring standard header styling, action slots, collapsible state, and clean borders.
|
|
- **`RecordList`**: High-performance list component optimized for large dataset rendering with custom row click, hover, and selection handlers.
|
|
- **`Tile`**: Base container element for building responsive dashboard grids.
|
|
- **`SingleLineTextEdit`**: Inline editable text field with debounced autosave, blur commit, and escape rollback.
|
|
- **`ElevatedPill`**: Compact visual badges for status, tags, and category chips.
|
|
|
|
### React 19 & Deduplication Warning
|
|
Because `dashgui` is symlinked from `../dashgui`, it has its own `node_modules`. To prevent duplicate React, Emotion, or MUI instances from causing runtime context errors (`Cannot read properties of null (reading 'useContext')`), [vite.config.js](file:///home/seb/src/dashfront/vite.config.js) enforces `dedupe`:
|
|
```js
|
|
resolve: {
|
|
dedupe: [
|
|
'react', 'react-dom', 'react/jsx-runtime', 'react/jsx-dev-runtime',
|
|
'react-router', 'react-router-dom', '@emotion/react', '@emotion/styled',
|
|
'@mui/material', '@mui/icons-material'
|
|
]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 12. Application Views & Route Hierarchy
|
|
|
|
The application routing is defined in [src/appRouter.jsx](file:///home/seb/src/dashfront/src/appRouter.jsx) and styled with [src/components/AppShell.jsx](file:///home/seb/src/dashfront/src/components/AppShell.jsx):
|
|
|
|
```
|
|
/
|
|
├── /artikel
|
|
│ ├── index → ArtikelUebersichtPage (Search & filter grid)
|
|
│ ├── /kategorien/* → ArtikelKategorienPage (Interactive category tree)
|
|
│ ├── /lager → ArtikelLagerPage (Warehouse stock overview)
|
|
│ └── /:sku/:tab? → ArtikelSkuPage (SKU detail: info, photos, prices, suppliers, warehouse)
|
|
├── /lieferung
|
|
│ ├── index → LieferungListePage (Inbound delivery list)
|
|
│ ├── /neu → LieferungNeuPage (Receipt editor & OCR reconciliation)
|
|
│ ├── /lieferanten → LieferungLieferantenPage (Supplier directory)
|
|
│ └── /:deliveryId → LieferungEditPage (Delivery inspection & editing)
|
|
├── /auftrag
|
|
│ ├── index → AuftragAngebotPage (Quotes / Angebote)
|
|
│ ├── /neu → AuftragAngebotNeuPage (Create quote)
|
|
│ ├── /:quoteId → AuftragAngebotEditPage (Edit quote)
|
|
│ ├── /bestellung → AuftragBestellungPage (Orders / Bestellungen)
|
|
│ ├── /bestellung/neu → AuftragBestellungNeuPage (Create order)
|
|
│ └── /bestellung/:id → AuftragBestellungEditPage (Edit order)
|
|
├── /kunde
|
|
│ ├── index → KundeListePage (Customer directory)
|
|
│ ├── /neu → KundeNeuPage (Create customer)
|
|
│ ├── /gruppen → KundeGruppenPage (Customer groups & price tiers)
|
|
│ └── /:entityId → KundeEditPage (Customer profile & address editor)
|
|
└── /infos → InfosPage (Company information & POS setup)
|
|
```
|
|
|
|
---
|
|
|
|
## 13. Media, Images & Document Pipelines
|
|
|
|
### Client-Side AVIF Conversion
|
|
To minimize backend CPU usage and reduce bandwidth:
|
|
- When a user selects or drops an article photo, the frontend processes the image in-browser using `@jsquash/avif` and `@jsquash/resize`.
|
|
- Images are scaled to target variant dimensions and compressed into AVIF format before transmission to `/api/article/:sku/photo`.
|
|
|
|
### ThumbHash Placeholders
|
|
- For instant visual feedback without layout shift, article photos generate a 25-byte binary hash using `thumbhash`.
|
|
- [src/components/article/ThumbHashImage.jsx](file:///home/seb/src/dashfront/src/components/article/ThumbHashImage.jsx) renders the blur preview instantly while the full image loads asynchronously from `/api/thumb/{hash}` or `/api/image/{hash}`.
|
|
|
|
### PDF & Document Ingestion
|
|
- [src/pages/lieferung/neu.jsx](file:///home/seb/src/dashfront/src/pages/lieferung/neu.jsx) utilizes `pdfconvert-wasm` to process supplier PDF delivery notes and invoices directly inside the browser.
|
|
- Extracted pages are rendered to canvas, displayed for line-by-line inspection, and passed to OCR recognition to populate delivery line items.
|
|
|
|
---
|
|
|
|
## 14. Real-time SSE Signals & Invalidation
|
|
|
|
The frontend connects to the backend Server-Sent Events stream at `/api/events`:
|
|
- **Architecture**: When mutations occur on `dashback` (whether triggered by this client, another terminal, or the POS), SSE events are broadcast with payload information (e.g. `{ type: 'article-update', sku: '1001' }`).
|
|
- **Reactive Cache Refresh**:
|
|
- `artikelAutomat.actions.reloadArticle(sku)` re-fetches the updated article silently, updating subscribers without flicker.
|
|
- Category updates trigger `categoryAutomat` tree/article reloads.
|
|
- Delivery and order updates trigger automatic list reloads.
|
|
|
|
---
|
|
|
|
## 15. Developer Tooling & Debugging
|
|
|
|
### AutomatInspector ([src/devtools/AutomatInspector.jsx](file:///home/seb/src/dashfront/src/devtools/AutomatInspector.jsx))
|
|
- Docked overlay on the right side of the screen, active only in development mode (`import.meta.env.DEV`).
|
|
- Auto-discovers all running automats registered in `window.__AUTOMATS__`.
|
|
- Allows developers and agents to:
|
|
- Inspect full state JSON trees in real time.
|
|
- View subscriber counts and registered actions.
|
|
- Trigger actions and test state changes without code modifications.
|
|
|
|
### Vite API Proxy Logging
|
|
[vite.config.js](file:///home/seb/src/dashfront/vite.config.js) includes automatic proxy logging for all `/api` requests and responses. HTTP method, path, request payloads, response statuses, and timing are formatted directly in the Vite terminal.
|
|
|
|
### TLS / HTTPS Development
|
|
For testing hardware barcode scanners, camera video feeds, and PWA offline modes:
|
|
```bash
|
|
npm run cert # Generates local certs in ./certs/
|
|
npm run dev:https # Runs Vite HTTPS server on port 4308
|
|
```
|
|
|
|
### Automat Graph Visualization (`automat-graph.svg` & `automat-graph.dot`)
|
|
- Visual topology diagram generated via `npm run automat-graph` (`generate-automat-graph.js`).
|
|
- Modeled after `schema-graph.svg` in `dashdbschema` and `api-graph.svg` in `dashback`.
|
|
- Visualizes 32 Automats across 12 domain clusters (Session & Real-Time, Catalog, Search, Properties, Purchasing, Sales, Inventory, Finance, POS, AI Workspace, Virtual Grids, System UI).
|
|
- Color-coded badges for `STATE` (purple), `ACTION` (green), `SELECT` (blue), `API` (amber), `SSE` (rose), and `HOOK` (orange).
|
|
- Traces 60 relational and dataflow links with dedicated arrival ports (`in#`), exit compass directions (`e`/`w`), and arc-fixed self-loops.
|
|
|
|
---
|
|
|
|
## 16. Agent Cheatsheet & Code Recipes
|
|
|
|
### Recipe 1: Consuming an Automat in a React PureComponent
|
|
```jsx
|
|
import { PureComponent } from 'react';
|
|
import { artikelAutomat, createArticleSelector, getArticle } from '../automats';
|
|
|
|
export class ArticleTitle extends PureComponent {
|
|
constructor(props) {
|
|
super(props);
|
|
// 1. Read synchronously to avoid blank flash
|
|
this.state = {
|
|
article: getArticle(props.sku),
|
|
};
|
|
}
|
|
|
|
componentDidMount() {
|
|
// 2. Subscribe with fine-grained selector
|
|
this.unsub = artikelAutomat.subscribe(this, (state) => ({
|
|
article: createArticleSelector(this.props.sku)(state),
|
|
}));
|
|
}
|
|
|
|
componentDidUpdate(prevProps) {
|
|
// 3. Handle prop changes cleanly
|
|
if (prevProps.sku !== this.props.sku) {
|
|
this.setState({ article: getArticle(this.props.sku) });
|
|
this.unsub?.();
|
|
this.unsub = artikelAutomat.subscribe(this, (state) => ({
|
|
article: createArticleSelector(this.props.sku)(state),
|
|
}));
|
|
}
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
// 4. Always cleanup
|
|
this.unsub?.();
|
|
}
|
|
|
|
render() {
|
|
const { article } = this.state;
|
|
return <h1>{article?.name ?? 'Lädt...'}</h1>;
|
|
}
|
|
}
|
|
```
|
|
|
|
### Recipe 2: Calling the API and Notifying Errors
|
|
```jsx
|
|
import { article } from '../api';
|
|
import { enqueueSnackbar } from 'notistack';
|
|
import { notifyError } from '../utils/notify';
|
|
import { artikelAutomat } from '../automats';
|
|
|
|
async function handleSavePrice(sku, netPrice) {
|
|
try {
|
|
const res = await article.updatePrice(sku, { net: netPrice });
|
|
artikelAutomat.actions.setArticle(res.article);
|
|
enqueueSnackbar('Preis erfolgreich aktualisiert', { variant: 'success' });
|
|
} catch (err) {
|
|
notifyError(err);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Recipe 3: Calculating Gross Prices with Company Tax Rates
|
|
```jsx
|
|
// Prices in the database and API are NET prices.
|
|
// Gross prices shown to end users must apply company tax rates from companyAutomat
|
|
// (selectCompanyTaxrate() / selectCompanyReducedTaxrate()):
|
|
export function calculateGrossPrice(netPrice, taxRatePercent) {
|
|
const net = Number(netPrice) || 0;
|
|
const rate = Number(taxRatePercent) || 0;
|
|
return Math.round(net * (1 + rate / 100) * 100) / 100;
|
|
}
|
|
```
|