Selecting a plant item in the Booked to dropdown silently did nothing. The menu
opened, the option highlighted, and clicking it produced no request at all — the
menu just closed. Reproduced with a real browser click: no POST to /book.
The menu is rendered with createPortal(..., document.body) because the line table
lives in an overflow-x-auto container that would clip an in-place dropdown. A
portal is not a DOM descendant of the component that made it, so the click-away
handler's `boxRef.contains(target)` reported every option as an outside click:
mousedown closed the menu, React unmounted the button, and the click that would
have booked it never landed. The menu now has its own ref and is treated as part
of the control. Verified with a real click — the booking posts, the site clears,
and the unbooked badge goes.
The site dropdown was never affected; it is a native <select>, outside the portal.
Also on this screen:
- The Booked to column needs min-w-[240px] with the two pickers stacked inside
it. At 768px the column is only ~166px, so two 200px controls could not sit
side by side; as flex children they compressed into each other by ~12px, and
once they were not allowed to shrink they spilled past the edge of the table.
- Dropped the "Booked to income / VAT to SARS / Billed to customer / Still
unbooked" money block, its component and the variables feeding it. The status
line stays: "1 line not yet booked to a machine".
Typecheck and build green. Checked at 768x1024 and 375x812: no page overflow, no
control past the table edge, no tap target under 44px.
The machine dropdown was still hard-coding one of four dark surface hexes
that globals.css already defines as a token.
Also ignore .verify/, where the Round 4 mobile checklist leaves its
screenshots. They are regenerated on every pass, so they were never meant
to be committed.
Two things this question turned up.
The upload DID store the PDF, but on the intake record, never on the invoice
— so "Open invoice PDF" 404'd for every invoice imported through the app
while working for the older ones. The file now lives on invoices.source_pdf
and the intake record keeps only the provenance (who sent it, what was
parsed), rather than a second copy of the file. The document endpoint also
falls back to the intake record, which makes the seven already-stored
documents reachable again without a migration. All 10 now open.
The machine combobox searched only the register's own words, so an invoice
line reading "CB22 2.2 TON Roller Vibration" could never be matched to the
asset filed as "CAT 2,4TON RV" — the words simply were not in the list. The
line's own invoice code and description are now searchable too, so the two
sides of the same machine can actually be found from either direction.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two different causes, both from the same root — the header was raised to
z-[1100] so the Plant Map's Leaflet controls (z-1000) could not scroll over
it, which quietly pushed my overlays underneath it.
The toast sat at z-60 and the Add Invoice dialog at z-50, both below the
header, so a confirmation appeared to vanish behind the nav bar. Rung out
against the existing ladder instead of picking a new number: header 1100,
nav drawer 1200, dialog 1250, toast 1300. The toast and the combobox list sit
at the same layer deliberately — they never open at the same time.
The machine dropdown was a separate and worse bug: it was not merely behind
anything, it was CLIPPED. It rendered inside the line table's overflow-x-auto
container, so the list was cut off by the table edge and the card around it —
it could never escape, which is why the search appeared to do nothing.
It now renders through a portal into <body>, positioned from the input's
measured viewport rect, so no ancestor can clip it. The rect is measured
before paint so it never flashes in the wrong place, kept in sync with
scroll and resize, and the list flips upward when there is not enough room
below — which is the common case on a phone with the keyboard open.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Booking a line meant scrolling a native <select> of 44 machines, where two
dozen near-identical "CASE TLB" entries sit next to each other. A native
select cannot be filtered, so finding TLB07 meant scrolling past most of the
list.
Replaced with a combobox that filters as you type, on the code, the name or
the category, and says how many of 44 machines matched. Still behaves like a
select rather than a text box: opening it, Up/Down to move, Enter to choose,
Escape to cancel, and a "Not booked" entry to clear.
The list stays grouped by category so the codes read the way the plant
register does, and every row is a 44px target with the list scrolling within
its own box, so it stays usable on a phone. It is an ARIA combobox —
aria-expanded, aria-autocomplete, aria-controls — and the highlighted row
scrolls into view as you arrow through it.
Booking behaviour is unchanged: still posts on selection, still rolls the
display back if the save fails, still refreshes on success.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>