Files
dashfront/AGENTS.md
2026-09-21 20:21:57 +02:00

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

  1. System Overview & Ecosystem Map
  2. Technology Stack & Key Libraries
  3. Directory Layout
  4. State Management Conventions
  5. Backend Communication & Authentication Rules
  6. Route & Navigation Architecture
  7. Developer Workflows & Commands
  8. Critical Agent Rules & Common Pitfalls
  9. Deep Dive: The Automats (react-automat)
  10. Deep Dive: Redux Toolkit Architecture
  11. dashgui Component Library Integration
  12. Application Views & Route Hierarchy
  13. Media, Images & Document Pipelines
  14. Real-time SSE Signals & Invalidation
  15. Developer Tooling & Debugging
  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

# 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:

  • Skill reference: Read .agents/skills/automat/SKILL.md.
  • Lives outside the React lifecycle in module scope (src/automats/*.js).
  • Designed for React PureComponent to 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, hierarchical category tree, brand, posterminal, and unit.
  • Uses connect(mapStateToProps, mapDispatchToProps) to connect to PureComponent classes or functional hooks where appropriate.
  • Coordinated via createListenerMiddleware: when authAutomat emits an auth change (auth/sessionEstablished or auth/companyChanged), listeners automatically dispatch loads for company info, brands, POS terminals, and units.

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 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:

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.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:

# 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

  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_father: 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, useSelector, useEffect) inside PureComponent classes. Automats and connect() 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/. 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 via setAuthEventDispatcher(store.dispatch). Emits:
    • auth/sessionEstablished
    • auth/companyChanged
    • auth/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).

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 /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)

  • 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

  1. categorySlice: Hierarchical category tree structure, category breadcrumbs, article-category memberships, category reordering (moveCategory, reorderCategories).
  2. companySlice: Active company configuration, tax rates (standard, reduced), root category ID, company legal details.
  3. brandSlice: Catalog brands and manufacturer list.
  4. posterminalSlice: Registered point-of-sale hardware terminals.
  5. 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/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 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-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 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;
}