28 KiB
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
- System Overview & Ecosystem Map
- Technology Stack & Key Libraries
- Directory Layout
- State Management Conventions
- Backend Communication & Authentication Rules
- Route & Navigation Architecture
- Developer Workflows & Commands
- Critical Agent Rules & Common Pitfalls
- Deep Dive: The Automats (
react-automat) - Deep Dive: Redux Toolkit Architecture
dashguiComponent Library Integration- Application Views & Route Hierarchy
- Media, Images & Document Pipelines
- Real-time SSE Signals & Invalidation
- Developer Tooling & Debugging
- 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
# Machine-readable execution flow:
flow:
- step: 1_client
component: dashfront
runtime: React 19 (PureComponent & Function Components)
state_layers:
automats: [artikelAutomat, deliveryEditorAutomat, authAutomat, supplierAutomat, navigationAutomat, userSettingsAutomat]
redux_slices: [company, category, brand, unit, posterminal]
transport: src/api (http(), ApiClient)
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 with HMR router preservation |
| UI & Theming | MUI 9 (@mui/material, Emotion) |
Custom theme in src/theme/, base background #c5ddd8 in src/colors.js |
| Shared GUI | dashgui (file:../dashgui) |
PanelTile, RecordList, Tile, SingleLineTextEdit, ElevatedPill |
| State Management | react-automat + Redux Toolkit |
Automats for reactive domain models; Redux for category tree & session bootstrap |
| Notifications | notistack |
useSnackbar() / <SnackbarProvider> in 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 exports (article, category, delivery, etc.)
│ ├── ApiClient.js # Object-oriented API client (this.article, this.delivery, etc.)
│ ├── StorageManager.js # Client-side cache / local storage management
│ └── storageManagerContext.js # React context provider for StorageManager
├── automats/ # react-automat stores with fine-grained subscriptions
│ ├── artikelAutomat.js # Article cache, deduplicated loads, and reactive single-article state
│ ├── authAutomat.js # Authentication state, company list, company selection
│ ├── 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
├── store/ # Redux Toolkit store (legacy & tree slice)
│ ├── index.js # configureStore with dev logger & listener middleware
│ ├── authSlice.js # Redux auth state (session bootstrap, company selection)
│ ├── companySlice.js # Redux company details (tax rates, rootCategoryId)
│ ├── categorySlice.js # Redux category tree, breadcrumbs, category articles
│ ├── brandSlice.js # Redux brand list and lookup
│ ├── posterminalSlice.js # Redux POS terminals configuration
│ └── unitSlice.js # Redux unit of measure definitions
├── 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 reference: Read .agents/skills/automat/SKILL.md.
- Lives outside the React lifecycle in module scope (
src/automats/*.js). - Designed for React
PureComponentto bypass unnecessary React reconciliations. - Pattern:
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. Redux Toolkit (src/store/)
- Used for static/relational trees:
company, hierarchicalcategorytree,brand,posterminal, andunit. - Uses
connect(mapStateToProps, mapDispatchToProps)to connect toPureComponentclasses or functional hooks where appropriate. - Coordinated via
createListenerMiddleware: whenauthAutomatemits an auth change (auth/sessionEstablishedorauth/companyChanged), listeners automatically dispatch loads for company info, brands, POS terminals, and units.
5. Backend Communication & Authentication Rules
Authentication & Cookies
- HTTP-only cookie: The session token is stored in an HTTP-only cookie (
token). credentials: 'include': Everyfetchrequest MUST setcredentials: 'include'. Thehttp()helper in src/api/index.js does this automatically.- Never pass
googleAuthId: The frontend NEVER passesgoogleAuthIdin request bodies or query parameters. The backend middleware extracts the authenticated user directly from the cookie session! - 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:
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:
/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.
- Preserved across Vite HMR via
import.meta.hot.data.routerto prevent mid-session resets when hot-reloading code.
- Dynamic Badges:
DeliveryNavIconandDeliveryNeuIconinsrc/nav.jssubscribe todeliveryEditorAutomatto show live uncommitted draft line badges in real time.
7. Developer Workflows & Commands
Run commands from the repository root /home/seb/src/dashfront:
# 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
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
- Do not create dummy/mock backends:
dashbackruns locally athttp://localhost:4208. All/api/*routes are handled by it. - Keep
dashguilinks working:dashguiis imported viafile:../dashgui. Vite has deduplication configured (dedupe: ['react', 'react-dom', ...]invite.config.js) to avoid dual-instance React conflicts. - Article Deletion Restriction: An article CANNOT be deleted if it has child variants (
is_father: 1orchildCount > 0). The backend returns HTTP 400. - Prices and Multipliers:
- Article prices are tiered by
minmultiplierandcustomergroup_id. - Prices sent to the backend are net prices (
net).
- Article prices are tiered by
- Binary / Image Handling:
- Image uploads convert source images into AVIF variants client-side.
- Frontend components use
thumbhashfor instant blur placeholders while loading full images via/api/image/{fullHash}or/api/thumb/{fullHash}.
- No Hooks in PureComponents: Never call React hooks (
useState,useSelector,useEffect) insidePureComponentclasses. Automats andconnect()provide direct reactive subscription without hooks. - Always Unsubscribe: Every
automat.subscribe(this, ...)incomponentDidMount()must have its cleanup callback called incomponentWillUnmount().
9. Deep Dive: The Automats (react-automat)
All domain Automats are located in src/automats/. Each automat exposes an observable state container with explicit action methods:
1. artikelAutomat (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)
- Role: Manages user authentication lifecycle and multi-tenant company selection.
- State:
{ user: AuthUser | null, googleClientId: string | null, status: 'loading' | 'anon' | 'authed' }. - Bridge to Redux: Calls
emit(action)registered viasetAuthEventDispatcher(store.dispatch). Emits:auth/sessionEstablishedauth/companyChangedauth/loggedOut
- Actions:
bootstrapSession(session),switchCompany(companyId),logout().
3. deliveryEditorAutomat (src/automats/deliveryEditorAutomat.js)
- Role: State engine for the multi-line inbound delivery receiver and invoice reconciler (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).
- Line items editor (
4. supplierAutomat (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)
- Role: Remembers last active subpaths and last visited article SKU. When a user navigates between the main sidebar sections (e.g. from
/lieferungto/artikeland back), it restores the exact active SKU or delivery document instead of resetting to the root list.
6. userSettingsAutomat (src/automats/userSettingsAutomat.js)
- Role: Stores client-side user layout preferences such as viewer panel coordinates and drawer positions.
10. Deep Dive: Redux Toolkit Architecture
Redux Toolkit manages static, relational, and configuration data in src/store/:
Slices
categorySlice: Hierarchical category tree structure, category breadcrumbs, article-category memberships, category reordering (moveCategory,reorderCategories).companySlice: Active company configuration, tax rates (standard, reduced), root category ID, company legal details.brandSlice: Catalog brands and manufacturer list.posterminalSlice: Registered point-of-sale hardware terminals.unitSlice: Allowed units of measure (Stück, kg, Liter, etc.).
Automatic Bootstrap with Listener Middleware
In src/store/index.js, createListenerMiddleware coordinates data loading automatically when auth events fire:
companyLoadListener(listenerMiddleware);
unitLoadListener(listenerMiddleware);
brandLoadListener(listenerMiddleware);
posterminalLoadListener(listenerMiddleware);
When auth/sessionEstablished or auth/companyChanged occurs, these listeners trigger thunks to reload company tax settings, brands, units, and POS terminals without boilerplate in React views.
Redux Dev Logger
In development mode (import.meta.env.DEV), reduxDevLogger intercepts actions from auth/, company/, category/, brand/, posterminal/, and unit/, logging structured before/after counts to the browser console.
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 enforces dedupe:
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 and styled with 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/avifand@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 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 utilizes
pdfconvert-wasmto 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 Redux category slice refreshes.
- Delivery and order updates trigger automatic list reloads.
15. Developer Tooling & Debugging
AutomatInspector (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 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:
npm run cert # Generates local certs in ./certs/
npm run dev:https # Runs Vite HTTPS server on port 4308
16. Agent Cheatsheet & Code Recipes
Recipe 1: Consuming an Automat in a React PureComponent
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
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
// Prices in the database and API are NET prices.
// Gross prices shown to end users must apply company tax rates from Redux companySlice:
export function calculateGrossPrice(netPrice, taxRatePercent) {
const net = Number(netPrice) || 0;
const rate = Number(taxRatePercent) || 0;
return Math.round(net * (1 + rate / 100) * 100) / 100;
}