# ScalesApp React Expert Memory ## Key File Paths - Report Editor root: `frontend/src/components/ReportEditor/` - Element models: `models/Element.jsx` — OOP class hierarchy, `fromJSON` has `elementClasses` map - Element CSS: `elements.css` — one block per element type - Inspector CSS: `ObjectInspector.css` - Data resolver utils: `utils/dataResolver.js` — `resolveDBTextValue`, `getAvailableObjects`, `getAvailableFields` - Expression evaluator: `utils/expressionEvaluator.js` — recursive-descent, no eval - A4 designer (sibling tree, mm-based): `frontend/src/components/ReportEditor/a4/` ## Two editors, deliberately duplicated The matrix editor (`EditorCanvas.jsx`, `models/Element.jsx`, `ResizeHandles.jsx`, `ObjectInspector.jsx`, `ReportEditor.jsx`) is treated as frozen. The A4 designer under `a4/` copies similar logic rather than extracting shared abstractions — per the plan at `.claude/plans/a4-report-designer.md`. Do not "DRY up" the two; the duplication is the point. Key unit difference: matrix bounds are **inclusive** (`x + width - 1`); A4 bounds in `a4/geometry.js` are **exclusive** (a 40mm box at x=10 has right edge 50). Safely reusable by both: `hooks/useElementDrag.jsx` (emits raw pixel deltas, snapping left to the consumer), `hooks/useElementSelection.jsx`, `DataContext`, `BandContext`. ## Element Type Pattern (matrix editor) Adding a new element type requires touching 8 files: 1. `models/Element.jsx` — new class + register in `elementClasses` map in `fromJSON` 2. `NewElement.jsx` — component file, modeled on `DBTextField.jsx` 3. `ExpressionEditorPanel.jsx` (or equivalent inspector panel) 4. `EditorCanvas.jsx` — import + `renderElement` case + click handler in `handleCanvasClick` 5. `Toolbar.jsx` — tool button 6. `ObjectInspector.jsx` — import panel + render for `element.type === 'xxx'` 7. `elements.css` — CSS block for new class 8. `ResizeHandles.jsx` — add type to `getHandles` switch, `getHandlePosition` branch, and resize logic branch ## ObjectInspector Band-Detection Pattern When a panel needs to detect if an element is inside a band (for field resolution): ```js const parentBand = allElements.find(el => el.type === 'band' && el.children && el.children.includes(element.id)); // Nested band handling: if (parentBand.parentBandId) { const grandparentBand = allElements.find(el => el.id === parentBand.parentBandId); // grandparentData[0][parentBand.dataSource] → nestedData array } ``` ## DBTextField / ExpressionField Props Pattern Both receive: `element, isSelected, isSingleSelection, onSelect, onUpdate, onDelete, onDragStart, onDrag, charWidth, charHeight, previewMode, toolMode, parentBandId` Uses hooks: `useElementDrag`, `useElementSelection`, `useReportData`, `useBandContext` ## resolveDBTextValue signature ```js resolveDBTextValue(reportData, objectKey, fieldPath, parentBandId, { currentBandData }) ``` - objectKey = `''` for band-relative field access ## ExpressionField Field Ref Format `[objectKey.fieldPath]` for top-level fields, `[fieldPath]` for band-relative fields. Strip brackets, split on first `.` to get objectKey and fieldPath. ## Build tooling **Vite + Vitest** (no CRA/Jest). `npm run dev` / `npm start` → port 5172; `npm test` → `vitest run`; `npm run build` → `frontend/build/`. Config: `frontend/vite.config.mjs`. Env vars are `import.meta.env.VITE_*`. **Any new production file containing JSX must be named `.jsx`** — a `.js` file with JSX passes tests and the dev server but fails `vite build`. See [vite-vitest-migration](vite-vitest-migration.md). ## Detailed notes - [Vite + Vitest build tooling](vite-vitest-migration.md) — the JSX-in-.js build trap, axios mocks, jsdom `border: none` - [A4 entry points](a4-entry-points.md) — report `kind` decides the editor/print route; the two contexts A4ReportEditor now needs in tests - [A4 smart guides](a4-smart-guides.md) — px-not-mm snap threshold; why useElementDrag must stay untouched; the drag-test gotcha snapping introduces - [Testing React in this repo](testing-react.md) — no testing-library installed; jsdom/React event gotchas that cost real debugging time - [Decimal number inputs](decimal-inputs.md) — why `type="number"` breaks mm/float entry