--- name: a4-designer-conventions description: Non-obvious invariants of the A4 report designer — line element shape, mm-vs-px flooring, and why matrix inspector panels must not be imported metadata: type: project --- Conventions in `frontend/src/components/ReportEditor/a4/` that are easy to get wrong because they contradict the matrix editor sitting next to them. **Lines carry only one dimension.** An `a4-hline` has `width` + `thickness` and NO `height`; an `a4-vline` has `height` + `thickness` and NO `width`. Anything computing a box from an element must substitute `thickness` for the missing axis — `getA4Bounds` in `geometry.js` does this, and it is the function to reach for rather than reading `el.width`/`el.height` directly. Reading the raw props for a line compares against `undefined` and passes vacuously in tests while being wrong in the product. **Screen floors hairlines, print must never.** Screen renderers clamp a line's painted thickness and a rect's border to `Math.max(1, …)` px, because 0.3mm at 50% zoom is 0.57px and browsers can round it away to nothing. `PrintDocument` deliberately does no such thing — the printed thickness is what a steel rule measures. If a test asserts a floored value on the print path, the test is wrong, not the code. **Do not import the matrix inspector panels.** `ExpressionEditorPanel.jsx` looks tempting for the A4 expression section but is coupled two ways: it finds a parent band with `el.type === 'band'` (A4 bands are `a4-band`) and its alignment select writes `element.alignment`, whereas every A4 element and renderer reads `element.align`. Reusing it silently creates a dead second alignment property. The duplication between the two inspectors is accepted deliberately — see the plan's "matrix editor is not modified" section. **`fillColor: null` means no fill,** distinct from white, which would hide whatever a rect is framing. A colour input cannot express null, so a checkbox owns that state. It round-trips through Django's JSONField as SQL null. **Float inputs use `MmInput`,** never ``: a number input returns `""` for the intermediate value `"12."`, so the decimal point appears not to type. `MmInput` (in `A4Inspector.jsx`) holds a draft string while focused. Font size in pt is the one exception — its step is 0.5 and users never type a long decimal tail there. Related: [[vite-vitest-migration]]