# Fix — Broken "Add New Category / Brand" in Repair Intake

**Date:** 2026-11-07 · **Branch:** `techtitans/repair-r1` · Frontend-only fix (no backend/API/schema change). Not pushed.

## Root cause (reproduced in a real browser, not assumed)
In New Repair → Step 1 → Register a new device, the Category/Brand `LookupSelect` had **two** defects that made "Add New" fail for real users even though mocked unit tests passed:

1. **"Add New" was a dead end.** Clicking "+ Add new category/brand" with an empty search only re-focused the input — it opened **no creation form** and fired **0 API calls** (reproduced: `0 create POST(s)`). The only way to create was the undiscoverable "type a name → click Create "X"" row, with no name field / Save / Cancel / loading / error.
2. **The outside-click handler closed the menu mid-click.** It used `rootRef.contains(event.target)`. When an option is clicked it unmounts, so by the time the document `mousedown` handler ran the target was detached → `contains()` returned false → the menu was treated as an outside click and closed. In the real browser this closed the dropdown even when the form should have opened (the first fix attempt still showed a closed, empty input). jsdom's timing didn't reproduce it, so unit tests were green while the browser was broken — exactly the "tests passed but UX fails" the report warned about.

## Fix (`src/components/repairs/LookupSelect.tsx` + `repairs.css`)
- **"Add New" opens a real inline creation form** rendered as a dropdown panel: a labelled **"New <noun> name"** field (prefilled with any typed text) + **Save** and **Cancel**. Always visible/clickable, including with an empty search.
- **Save** persists via the existing backend endpoint, shows **loading** ("Saving…"), surfaces **permission (403)/validation (422)/generic** errors inline (never a raw 500), **auto-selects** the returned canonical name on success (so a case-/space-different add **dedups to the existing** row), closes the form, and preserves every other intake field. On error the form stays open with the message and nothing else is lost.
- **Save/Cancel are plain `type="button"` and Enter/Escape are swallowed** (`preventDefault` + `stopPropagation`), so creating a category/brand never submits the repair wizard (verified: `0` ticket POSTs during create).
- **Robust outside-click**: a capture-phase `onMouseDownCapture` flag (`pointer started inside`) replaces the `contains(target)` check, so a click on an option/button that unmounts mid-interaction no longer mis-closes the menu. A `justClosed` guard also stops the menu re-opening when focus returns after select/create/cancel.
- No backend change — devices still store category/brand as name strings; the shared brand/category endpoints and dedup are reused.

## Tests (`NewRepairLookups.test.tsx`)
Updated + extended to the new flow: **13 lookup tests pass** (full repairs suite **38 pass**), `tsc` clean, lint clean, `build` succeeds. New coverage: empty "Add new" opens the form and Save creates+selects; the create form is prefilled from typed text; **Enter saves and does not submit the wizard**; Cancel closes and preserves fields; a failed create shows a **permission** error and keeps the form/fields; a **case/space duplicate resolves to the canonical row**.

## Real-browser verification (live `techpos`, Edge/Chromium via Playwright)
Screenshots in `docs/techtitans/screenshots/repair-lookups-fix/`:
- `01-category-create-form` / `03-brand-create-form` — "Add new" opens the inline name + Save/Cancel form.
- `02-category-created-selected` / `04-brand-created-selected` — Save → **201** POST → the new value is selected.
- `05-persist-after-steps` — Category/Brand/Model retained across Continue → Back.
- `07-persist-after-refresh` — the created category persists after a full page reload (DB-backed; direct API confirmed the synthetic categories + brands exist).
- `06-mobile-create-form` — the creation form on a 390px mobile viewport.

Observed in the run: create form present; Category value after Save = the created name; brand + category POSTs returned **201**; **ticket POSTs during create = 0**; Escape cancels; mobile form present; created entries persist after reload. Difficult interactions covered: empty add-new, typed Create, Enter/Escape, Cancel, outside-click close, case/space duplicate, permission error, mobile.

## Files changed
- `frontend/src/components/repairs/LookupSelect.tsx` — inline creation form + robust outside-click + focus guard.
- `frontend/src/pages/repairs/repairs.css` — creation-form styles.
- `frontend/src/pages/repairs/NewRepairLookups.test.tsx` — updated/extended tests.

## Commit
See the frontend commit hash on `techtitans/repair-r1` (added in the same change as this report). Nothing pushed or deployed; no backend/schema/API change; unrelated repair functionality untouched.
