# W2-FE-2 report — pick-don't-type everywhere + in-place catalog management (FE)

Date: 2026-10-23 · FE `/home/moonui3/public_html/moon-erp` (hazemdev3) + 2 small BE edits · nothing committed, nothing deployed.
`npx ng build --base-href /app/` → **green** (final run 17:09, after the i18n write; only pre-existing deprecation warnings). i18n audit: every literal translate key in the touched files resolves in both ar.json and en.json.

## Shell binding the orchestrator must add (W4-FE-5 owns visit-shell.component.html)
In `features/clinic/visit-shell/visit-shell.component.html`, `@case ('lab')` / `@case ('rad')` (~lines 281 / 286), add one input each:
```html
<app-clinic-lab-orders [compact]="true" [encounterId]=… />
<app-clinic-rad-orders [compact]="true" [encounterId]=… />
```
Both inputs default to `false`, so until that line is added the shell shows the classic inline pickers (no breakage).
Optional, when W3-FE-4 has the field rules: `<app-clinic-diagnosis-panel … [inputMode]="fields()['diagnosis']?.input === 'text' ? 'text' : 'select'" />` (default `'select'`; an insured visit is always forced to ICD inside the panel).

## What was done, per task
1. **In-place catalog management (Fable §B)** — `shared/components/clinical-picker/clinical-picker-dialog.component.{ts,html,scss}`:
   «✎ إدارة» head button (only with `canManage`) toggles manage mode («خلاص», `aria-pressed`). Manage strip: «إظهار المخفي» switch, «＋ فئة جديدة», hint. Rows: CDK drag handle ⋮⋮ (same category only; disabled while searching), ↑ ↓, move-to-category `<select>`, ✎ rename, 👁 hide/show, 🗑 delete; phone (≤640px) = one 44px `⋯` opening an inline action sheet, drag handle hidden. Row label click / F2 = inline rename with AR|EN toggle (Enter saves, Esc cancels, empty = not saved; duplicate pre-check → «الاسم موجود بالفعل», hidden duplicate → «موجود كقيمة موقوفة…», server 422 → DUP). Hide/show (`PATCH is_active`), dimmed + «موقوف» tag when shown. Reorder debounced 600 ms into one `POST reorder` (hidden siblings keep their slots). Move = `PATCH {parent_id, display_order: end}`. Delete: disabled-look + tooltip «مستخدم في n زيارة…» when `usage_count>0`; else inline «حذف «X»؟ [نعم] [لا]»; 409 → shows the usage note + «إخفاء». New category = `POST {parent_id:null}`, kept as a category in this session even with no children. Category headers get their own ✎ / 👁. Undo: ONE action, 6 s, inline strip above the footer (`aria-live=polite`), runs the inverse call (reverse PATCH / previous order / restore / hide) then reloads the managed list. Keyboard: F2, Alt+↑/↓, Delete, H, Space(pick); Esc is staged rename → delete prompt/new-cat/sheet → manage → close. Manage list uses `role=list/listitem` + a real checkbox for the pick. All manage calls are optimistic with rollback + toast «ماقدرناش نحفظ…».
   Box (`clinical-picker-box.component.ts`): `canAdd` default → `clinic.lookups.add || clinic.history.manage`; new `canManage` input default → `clinic.lookups.manage || clinic.history.manage`. Inactive values: a chosen value with no active row renders `.chip.on.off` (dashed, muted, tooltip «القيمة موقوفة…»), never dropped, still removable while editable. Add now sends `specialty` + the open category's `parent_id`. Empty/error states: 403 → «مفيش صلاحية لعرض الكتالوج»; empty & can't add → «اطلب من الدكتور…»; no hits & can't add → «لا نتائج … — اطلب إضافته من الدكتور»; empty & can add → focus the add input.
2. **Cache (bugs 4, 5)** — `clinical-lookup.service.ts`: every mutation (add / ★ / rename / hide / show / move / reorder / delete / restore) patches ALL cached `key|*` lists (`patchAll` / `upsertAll`, respecting specialty fit; hidden rows leave the active lists). A failed list is cached as `[]` (so `changed` bumps never refetch it); only a 403 is flagged forbidden (`isForbidden()` → «مفيش صلاحية»), a network/5xx failure reads as an empty catalog. New: `listManaged` (`include_inactive=1&with_usage=1`, uncached), `update`, `reorder`, `remove` (204 / `LookupInUseError` on 409), `restore`, `isLoaded`. Pickers now re-read the snapshot when loaded (also when it became empty), not only when non-empty.
3. **GYN short lists (bug 6)** — `features/gyn/form/gyn-lookup-field.component.ts`: «＋» always shown when not disabled (aria-label/title «بحث · مفضّلة · إضافة»); add/manage gated inside the dialog. A chosen code with no active row is shown as a dashed `.off` chip in open mode (before it was simply invisible). `lookupCode()` moved to `clinical-picker.model.ts` and re-exported from the gyn file (all old imports keep working).
4. **Exam rule by region code (bug 2)** — `visit-shell/cards/visit-exam-card.component.ts`: `exam_finding_<code>` first, legacy `exam_finding_<id>` as fallback; `ClinicalPickerCategory` now carries `code`. The card also follows in-place edits (rename/hide/reorder of a region) via the `changed` signal. Dev DB: company 1 has the 8 seeded regions `exam_general … exam_skin` with 5–8 findings each, so the card renders 8 region boxes (first one double width).
5. **Diagnosis on the picker (C #15)** — `encounter/diagnosis-panel/`: chips of the chosen diagnoses (code + title + «رئيسي/ثانوي» toggle + ✕) + «＋ اختيار / بحث» opening the new `diagnosis-picker-dialog.component.ts` (same `.pk` look): search 2+ chars (debounced), ★ block (`GET icd10?favorites=1`, ★ toggle via `clinic_favorites` type `icd10`), chapter pills (`GET icd10/chapters`, count>0; browse `?chapter=`), default list = the existing most-used browse, inline «إضافة كود» = the existing magic-add (`clinic.diagnosis.record`), live multi-select (click a ✓ row removes it). First chosen = primary (guarded against quick double picks); the toggle demotes/promotes (`PATCH is_primary` — BE supports false). `@Input() inputMode: 'select'|'text' = 'select'`; text mode (only when NOT insured) shows a free-text row posting `{code:null, description}`. The old search-as-you-type `app-pick-multiselect` row and pending-chip buttons are removed. **This panel is shared, so the classic consultation screen gets the same chips + picker** (fix-in-shared-layer).
6. **Lab / rad (C #16 #17)** — new generic `features/clinic/shared/catalog-grid-picker/catalog-pick-dialog.component.ts` (same API as `app-catalog-grid-picker`, styles reused from the clinical picker): search across the catalog, ★ block, category tiles (open ONE category), ✓ counts, optional inline add only when the host passes `createFn`. `lab-orders` / `rad-orders` get `@Input() compact = false`; compact = basket chips (+ free-text chips) + up to 4 ★ quick chips + «🧪 اختيار من الفئات…» / «🩻 اختيار أشعة…», and one row with free text (intent mode), note, record/send (+ print for lab). Classic layout untouched when `compact` is false. Add-to-catalog stays standalone-only (lab: new `createInDialog` → `clinic_service` create; rad: existing `catalogCreateFn`).
7. **Rx ★ in formulary mode (C #18)** — FE: formulary hits get the ★ (type `clinic_drug`, kept apart from product ids), quick picks list ★ first in formulary mode too, draft rows show ★ by source. **BE (minimal, outside the FE brief — needed, there was no formulary favourite type):** `Modules/Clinic/app/Http/Controllers/FavoriteController.php` `ALLOWED_TYPES += 'clinic_drug'`; `DrugPresetController::mostUsed` flags formulary rows from `clinic_drug` favourites and appends starred formulary drugs the doctor never prescribed (count 0, named). Product behaviour unchanged.
8. **History card on the catalog (C #14)** — new `encounter/history-panel/history-lookup-picker.component.ts`; the history panel renders it for single/multi-select questions that have `lookup_key` (visit + patient banks), storing codes in the panel's existing draft format (comma-joined), so the save path is unchanged. Plain options / yes-no keep their toggles. `HistoryQuestion` FE model gained `lookup_key`, `clinical_type`.

## Files
FE changed: `shared/components/clinical-picker/{clinical-lookup.service.ts, clinical-picker.model.ts, clinical-picker-box.component.ts, clinical-picker-dialog.component.ts|html|scss}`, `features/gyn/form/gyn-lookup-field.component.ts`, `features/clinic/visit-shell/cards/visit-exam-card.component.ts`, `features/clinic/encounter/diagnosis-panel/diagnosis-panel.component.{ts,html,scss}`, `features/clinic/encounter/orders/{lab,rad}-orders.component.{ts,html,scss}`, `features/clinic/encounter/prescription/prescription.component.{ts,html}`, `features/clinic/encounter/history-panel/history-panel.component.{ts,html}`, shared services (additive): `features/clinic/services/{clinic-favorite.service.ts (types clinic_drug, icd10), clinic-diagnosis.service.ts (Icd10Chapter, browse opts, setPrimaryFlag, nullable code), clinic-history.service.ts (lookup_key)}`, `src/assets/i18n/{ar,en}.json`.
FE new: `features/clinic/encounter/diagnosis-panel/diagnosis-picker-dialog.component.ts`, `features/clinic/shared/catalog-grid-picker/catalog-pick-dialog.component.ts`, `features/clinic/encounter/history-panel/history-lookup-picker.component.ts`.
BE changed: `Modules/Clinic/app/Http/Controllers/FavoriteController.php`, `Modules/Clinic/app/Http/Controllers/DrugPresetController.php`; BE new test `Modules/Clinic/tests/Feature/W2Fe2FormularyFavoriteTest.php`. chown moonui3 done, `optimize:clear` run. No migrations.
i18n (both files, I18N_VERSION NOT bumped): `CLINICAL_PICKER.NO_HITS_ONLY, RESULTS`, all of `CLINICAL_PICKER.MANAGE.*` from Fable §3.9 + `NEW_CAT_PH, T_NEW_CAT, DRAG, UP, DOWN, ACTIONS`; `LAB_PICKER.COMPACT_EMPTY/COMPACT_PICK/DIALOG_TITLE`; `CLINIC_RADORDER.COMPACT_EMPTY/COMPACT_PICK/DIALOG_TITLE`; `CLINIC_DIAGNOSIS.PICKER_TITLE/PICKER_NOTE/MOST_USED/ADD_CODE/ADD_CODE_PH/ADD_CODE_NOTE/MAKE_SECONDARY/SECONDARY_CHIP/TEXT_PLACEHOLDER/EMPTY_PICK`.

## Tests
- BE `W2Fe2FormularyFavoriteTest`: **3 passed (16 assertions)** — run by me on SQLite.
- Regression, run by me: `FavoriteTest` + `DrugPresetTest`: **14 passed (50 assertions)**.
- FE: no test runner (skipTests); verification = `ng build` green.

## Not verified
- Nothing was exercised in a browser / live API with a logged-in user (no deploy by brief): manage mode, drag-drop, undo, 409 flow, dx picker, compact lab/rad, formulary ★ are verified by build + code reading only. The exam layout with the 8 seeded regions was checked by DB query, not visually.
- **History picker path (task 8) cannot be seen on dev with real data:** dev has 0 general/core `history_questions` with `lookup_key` (only gyn_ob ones, which mostly render through the GYN module forms).
- GYN / history label of a HIDDEN value: non-managers never receive inactive rows (BE ignores `include_inactive` without manage), so such a chip shows its raw code (dashed `.off`). Complaint/exam chips keep their stored labels. A BE option to resolve labels of hidden codes for viewers would close this.
- A HIDDEN category hides its values from the pick and manage lists (they no longer belong to a visible section) until «إظهار المخفي», but still show in the ★ block / quick-fav chips. Undo or «إظهار» brings the category back.
- `drug-presets/most-used` still returns `[]` for a user without a LabDoctor identity (pre-existing), so formulary ★ quick picks don't surface for such a user (the ★ on search hits still works).

## Suggested CHANGELOG `[Unreleased]` bullet (orchestrator's file)
«نافذة الاختيار الإكلينيكية: إدارة القيم من جوه النافذة (تعديل الاسم/إخفاء/ترتيب/نقل/حذف + تراجع) · التشخيص والتحاليل والأشعة على نفس نافذة الاختيار (★ وفصول ICD-10 وإضافة كود) · ★ لأدوية الفورمولاري» / "Clinical picker: manage catalog values in place (rename/hide/reorder/move/delete + undo) · diagnosis, lab and imaging on the same picker (★, ICD-10 chapters, add code) · ★ for formulary drugs".

## Deviations / decisions
1. Undo is an inline strip in the dialog (6 s, aria-live), not a PrimeNG toast: hosts share one generic `<p-toast>` with no action template and the dialog's `MessageService` is optional. Failures still toast.
2. Manage mode always uses the sectioned list (also for `grid` catalogs — no current caller passes grid to the lookup picker).
3. Reorder of the loose «أخرى» level sends only the loose ids with `parent_id:null`; their `display_order` 1..n may tie with category roots (name tiebreak) — harmless.
4. A new empty category is a category only for the current manage session (the API can't tell an empty category from a loose value); once a value is moved/added into it, it is a real category.
5. Lab/rad compact search matches label + code (not lab's Arabic-normalised `searchRank`).
6. BE edits for task 7 (one allow-list line + mostUsed formulary favourites) — outside the FE brief, minimal, with a test; no other agent's files touched.
7. Brief path `features/clinic/services/clinical-lookup.service.ts` does not exist; the service is `shared/components/clinical-picker/clinical-lookup.service.ts`.
