# FABLE — In-place settings & in-place catalog management (visit shell)

**Status:** design spec, 2026-10-23 · for W3-FE-4 (settings), W2-FE-2 (picker), W2-BE-C / W1-BE-B (contracts) · read-only, no code.
**Owner ask:** «إضافة للعناصر عايزها بشكل سهل ميكونش من شاشات تانية» + the design must look like the approved prototype.
**Ledger decisions applied:** 2 (doctor add+manage, nurse add), 3 (facility type = one selector), 6 (no separate Program-Setting screen).

Sources read: prototype `visit-shell-prototype.html` 535-654, 727-801, 1113-1157, 1245-1286 + screenshots 16/21/02/06/20/25; `design-visit-shell.md` §2.3-2.7, §5; `review-A-layout-engine.md`, `review-C-picker-lookups.md`; live `his-analysis/visit-shell-review/{general,general-edit,general-finish,gyn-preg}.png`; FE `visit-layout-editor.component.ts`, `visit-card.component.html`, `visit-layout.model.ts`, `card-catalog.ts`, `shared/components/clinical-picker/*`, `gyn/form/gyn-lookup-field.component.ts`; BE `Clinic/routes/api.php` 473-476.

---

## 0. The one-paragraph version

Everything the prototype's «⚙ إعدادات الشكل / Program Setting» panel did moves **into the shell's existing «✎ تخصيص الشاشة» mode** as two surfaces: a **per-card ⚙ popover** (that card's field rules + element switches) and an **«إعدادات التخصص» side sheet** on the purple edit bar (feature groups, shell switch, and a «عام للعيادة» block that is a second door to two company-wide keys). A **scope switch** on the bar («للعيادة كلها» ⟷ «لي أنا») makes the whole edit mode — layout AND rules — act on one level at a time, with one draft, one save, one reset. In the picker dialog, a **«✎ إدارة» mode** turns the same list into an inline catalog editor (rename / hide / reorder / move / delete-if-unused), gated by the two new permissions, with a single undo toast. The visual language is unchanged: dark-green header, card heads with 🔗/🏷 tags, chips, the purple edit bar, the teal picker.

---

## 1. Data contract (read this before building either surface)

The prototype encodes two different merge rules, and the implementer must not copy the layout rule onto the field rules.

| What | Resolution | Prototype line | Note |
|---|---|---|---|
| `layout` (cards/cols/flow) | each level **replaces** the previous whole | 557-562 | already in `resolve-layout.ts` — unchanged |
| `fields.{key}` rules | **merge per key**: `{...client[key], ...doctor[key]}` | 734 `rule()` | a doctor override touches only the keys he changed |
| `elements.{key}` | doctor value if defined, else client, else `true` | 736 `elem()` | missing = ON |
| `features.{group}` | specialty default → client only | 556 | never doctor |

### 1.1 Which rule types each level may set

| Rule | Client (`clinic.layout.manage`) | Doctor (only if `clinic.layout.allow_doctor_override`) |
|---|---|---|
| `fields.*.visible` | ✓ | ✗ (read-only row) |
| `fields.*.required` | ✓ | ✗ (read-only row) |
| `fields.*.input` Select⟷Text | ✓ | ✓ unless client `locked` |
| `elements.*` on/off | ✓ | ✓ unless client `locked` |
| `features.*` | ✓ | ✗ |
| `shell` switch (nutrition/dental) | ✓ | ✗ |
| `allow_doctor_override`, `facility_type` | ✓ + `clinic.settings.manage` | ✗ |

Rationale (prototype 1262): visible/required change what the clinic *collects* (intake + sign-off enforcement) — that is a clinic policy. Input type and element visibility change how one person *works* — that is personal.

### 1.2 `locked` — a flag that does not exist yet (W2-BE-C contract addition)

The brief asks for «the doctor sees locked (client-forced) rules as locked». The prototype has no per-rule lock; the only lock is the global `allow_doctor_override`. Add it:

```ts
// visit-layout.model.ts
export interface FieldRule { visible?: boolean; required?: boolean; input?: 'select' | 'text' | 'chips'; locked?: boolean; }
export interface ElementRule { on: boolean; locked?: boolean; }           // client level
export interface ClientProfile { layout: VisitLayout | null; fields: Record<string, FieldRule>; elements: Record<string, ElementRule>; features: Record<string, boolean>; }
export interface DoctorProfile { layout: VisitLayout | null; fields: Record<string, Pick<FieldRule, 'input'>>; elements: Record<string, boolean>; }
```

Stored exactly where today's JSON lives: client `clinic.visit_layouts[type] = {layout, fields, elements, features}`; doctor `clinic.visit_layout_doctor[type] = {layout, fields, elements}` (today the doctor key stores layout only — review A #55). The BE must ignore `visible/required/features` on the doctor PUT and drop doctor values for any key the client marked `locked`.

**Decision — elements are per type**, inside the same per-type JSON (matches the API that exists; the prototype's flat `CLIENTS.mona.elements` is a harness shortcut). `hdr.*` is shared chrome, so a clinic that hides insurance on the general screen will not see it hidden on gyn until it sets it there too. Mitigation in UI: when saving `hdr.*` on one type the popover shows a one-line hint «ده للشاشة دي بس — التخصصات التانية ليها إعدادها». Listed in §6 as an open item if the owner prefers company-wide `hdr.*`.

### 1.3 Stable field keys (fixes review C bug 2)

Rule keys are **codes**, never DB ids. Exam regions use `clinical_lookups.code` (`exam_general`, `exam_chest`, …); when a region row has no `code`, the exam card must not offer a rule for it (and W1-BE-B seeds codes for the standard 8 regions). The full key table is §2.3.

### 1.4 One draft, one save, live preview

- Entering edit mode builds **one draft per scope** `{layout, fields, elements, features}` from the resolved profile of that scope.
- While editing, the shell renders **from the draft** (cards, field visibility, input type, element switches) — the prototype's «أي تغيير هنا بيتعكس فوراً في الشاشة فوق» (1259). Toggling `complaint.quick` off hides the ★ quick chips on the complaint card immediately.
- «حفظ» PUTs the whole draft for the current scope (one request to the existing endpoint, body now includes `fields/elements/features`). Cancel discards all. Reset per level nulls layout + rules of that level.
- Per-card ⚙ gets a **dirty dot** (small amber dot on the icon) when that card's rules differ from the saved profile of the scope.

---

## 2. A — In-place screen settings inside «✎ تخصيص الشاشة»

### 2.1 Entry points and the edit bar (replaces today's `visit-layout-editor.component.ts` bar)

Keep the purple bar (`#1f1033` / `#e9d5ff`, `.btn.pri` lilac) exactly as in general-edit.png. New row structure, two rows on desktop, wraps naturally on tablet:

```
Row 1  ✎ تخصيص شاشة عيادة عامة · الحالي من: [افتراضي] · 11 كارت          [＋ إضافة كارت (8)] [⚙ إعدادات التخصص]
Row 2  الإعداد لمين؟  (للعيادة كلها | لي أنا)   افتراضي ─→ العيادة ✓ ─→ أنا     [حفظ للعيادة] [↺ رجّع افتراضي العيادة] [إلغاء]
```

- **Scope switch** `seg2` «للعيادة كلها» / «لي أنا» — shown only when both are possible (`canManage && allowDoctor`). Manager without doctor override: no switch, scope = client. Doctor without manage: no switch, scope = doctor (label «بتعدّل نسختك أنت»). The whole mode (card toolbar + ⚙ + sheet) acts on the chosen scope.
- **Level strip** `افتراضي → العيادة → أنا`: three small pills; the active scope is filled; a level that has saved data gets «✓»; the strip is the visual answer to "what overrides what" and has `title` «كل مستوى بيستبدل اللي قبله — والدكتور فوق العيادة».
- **Single primary save**, label follows scope: «حفظ للعيادة» / «حفظ لي». Reset follows scope: «↺ رجّع افتراضي العيادة» / «↺ رجّع افتراضيي». This replaces the two parallel save buttons (less ambiguity about which level the rules go to).
- Switching scope with a dirty draft → inline confirm in the bar (not a modal): «في تعديلات مش محفوظة لـ{scope} — [احفظ] [سيبها] [ارجع]».
- Cross-level hints (one `.note.msg` line under the bar, violet border like today's message):
  - client scope, doctor override exists for me: «ليك نسخة شخصية مفعّلة — اللي بتحفظه هنا للكل، ونسختك هتفضل فوقه. [شيل نسختي]».
  - doctor scope: «نسختك فوق شكل العيادة — القواعد اللي عليها 🔒 العيادة مثبّتاها».
- **⚙ إعدادات التخصص** button opens the side sheet (§2.4). In doctor scope the button still shows (read-only sheet) so the doctor understands why cards are missing.
- Keep «＋ إضافة كارت (n)» and its `.addp` chip catalog unchanged. Fix the per-tab count bug (review A bug 1) — count the draft.

### 2.2 Per-card ⚙ popover

**Where:** a new last button in the card's edit toolbar `.etb` (`visit-card.component.html` line 2-21), after «✕ إخفاء»: `⚙ إعدادات` (icon + text on desktop, icon only ≤ 900px). Shown **only for cards that declare rules** (§2.3) — cards with nothing to set do not get a ⚙ (keeps the toolbar as clean as the prototype's).

**Container:** PrimeNG 21 `Popover` (not OverlayPanel) anchored to ⚙, width 380px, max-height 70vh scrollable, same white card style as `.addp` (`border:1px solid #c4b5fd; border-radius:12px`). Under 640px it becomes a bottom sheet (full width, max-height 85vh, drag handle) — same breakpoint the picker dialog already uses.

**Anatomy (RTL):**

```
┌──────────────────────────────────────────────────────┐
│ ⚙ إعدادات «الشكوى»            [للعيادة كلها ▾]   ✕  │  ← title + read-only scope chip (click = jumps to bar switch)
│ المصدر: افتراضي · العيادة ✓ · أنا –                   │  ← mini level strip for THIS card
├─ الحقول ─────────────────────────────────────────────┤
│ الشكوى الرئيسية        ظاهر [●]  إلزامي [●]  (Select|Text)  🔒  [العيادة] │
│ مدة الشكوى              ظاهر [●]  إلزامي [○]        —        ○   [افتراضي] │
├─ العناصر ────────────────────────────────────────────┤
│ المفضّلة السريعة ★       [●]   ○   [افتراضي]                            │
│ حقل HPI                  [○]   🔒  [العيادة]                             │
│ تفصيل حر (ملاحظة)        [●]   ○   [افتراضي]                            │
├──────────────────────────────────────────────────────┤
│ ↺ رجّع الكارت للافتراضي                          [تم]  │
└──────────────────────────────────────────────────────┘
```

Row grammar:
- Label (field/element name, the same i18n label the card uses).
- `ظاهر` switch (`role=switch`) — client scope only; in doctor scope the cell is a plain «ظاهر»/«مخفي» text in `--muted`.
- `إلزامي` switch — client scope only; auto-off + disabled when `ظاهر` is off (`title`: «الحقل مخفي»). Shows as text in doctor scope.
- `الإدخال` `seg2` **Select | Text** (`role=radiogroup`, two `role=radio`). Only for fields whose consumer supports it (§2.3 column «input»). Select = «من الكتالوج (chips)», Text = «نص حر» — show as `title` on hover.
- 🔒 **lock toggle** (client scope only): a small outlined lock; on = filled lock + `aria-pressed`. `title`: «ثبّت — الدكتور ميقدرش يغيّرها». In doctor scope a locked row renders grey (`opacity:.55`), lock filled, and the controls disabled with `title` «مقفول من العيادة».
- **Source tag** at the end: reuse the card-head `.from` classes — `افتراضي` (grey), `العيادة` (violet, `.from.client`), `لي` (`.from.doctor`). Computed per row from where the effective value came.
- A `*` after the label when the effective rule is required (so it reads like the card will).

Footer: «↺ رجّع الكارت للافتراضي» (removes this card's keys from the draft of the current scope — not a save) and «تم» (closes; nothing to save here — the bar saves). No «حفظ» in the popover, by design.

**Empty states inside the popover:**
- Card has fields but none are rule-able for this scope (doctor on a card with only visible/required) → rows render read-only + a note «العيادة بتحدد الحقول دي — انت تقدر تغيّر نوع الإدخال والعناصر بس».
- History card: the questions are entities with danger ranges, managed in `/clinic/settings → الحقول السريرية`. Popover shows elements only + a note with a link «أسئلة التاريخ المرضي بتتدار من الحقول السريرية ↗» — the one justified jump to another screen.

**Forced rows** (not just locked — the system forces them): e.g. `diagnosis.required` when the visit is insured (Decision 1). Row shows the switch on, disabled, with an amber tag «مفروض: مريض مؤمّن» instead of a source tag. Same for `gyn_cats` (required card) and the dental chart.

### 2.3 Field / element keys per card (data-driven from `CardDef`)

Add to `CardDef`: `rules?: { fields?: readonly string[]; elements?: readonly string[] }`. The popover renders from this; a card without `rules` gets no ⚙. Labels come from `VISIT_SHELL.RULE.<KEY>`.

| Card | Key | Kind | visible | required | input | Consumer today (review A/C) | Status |
|---|---|---|---|---|---|---|---|
| header (chrome — **no card toolbar**; its ⚙ sits on the dark header strip itself in edit mode, next to the «تسلسل محطات · عمود واحد» pills, opening the same popover titled «إعدادات الهيدر») | `hdr.insurance` | element | ✓ | – | – | none (always shown) | build consumer |
| | `hdr.lastVisit` | element | ✓ | – | – | none | build |
| | `hdr.doctor` | element | ✓ | – | – | none | build |
| vitals | `vital.<code>` ×8 (`weight,height,bmi,bp,pulse,temp,spo2,rbg`) + pack codes | field | ✓ | ✓ | – | `VITALS_WHITELIST` (W2-FS-3) | after FS-3 |
| complaint | `chief_complaint` | field | – (always) | ✓ | ✓ | complaint-card:101-104 | ✓ exists |
| | `complaint_duration` | field | ✓ | ✓ | – | W2-BE-C storage | build |
| | `complaint.quick` | element | ✓ | – | – | complaint-card:104 (today via `fields.complaint_quick.visible` — migrate to `elements`) | migrate |
| | `complaint.hpi` | element | ✓ | – | – | none | build |
| | `complaint.note` | element | ✓ | – | – | complaint-card:102 (`chief_complaint_note`) | migrate |
| history | `history.patientOnly` | element | ✓ | – | – | today a layout `props.patientScopeOnly` on the gyn card, not an element — keep it as a card prop; the popover only *shows* it as a read-only line | note only |
| exam | `exam_<regioncode>` ×8 | field | ✓ | ✓ | ✓ | exam-card:153-157 keyed by DB id → **re-key by `code`** | fix |
| | `exam.normal` («سليم» quick button) | element | ✓ | – | – | exam-card | wire |
| | `exam.notes` | element | ✓ | – | – | exam-card | wire |
| diagnosis | `diagnosis` | field | – (card rule) | ✓ (forced when insured) | ✓ Select(ICD)/Text | none for `input` (review C #22) → W2-BE-C «diagnosis text mode storage» | build |
| | `dx.secondary` | element | ✓ | – | – | none | build |
| lab | `lab.prev` («نتائج سابقة») | element | ✓ | – | – | none | build |
| rad | `rad.prev` | element | ✓ | – | – | none | build (optional, mirrors lab) |
| rx | `rx.interact` | element | ✓ | – | – | none | build |
| followup | `followup_date` | field | – | ✓ | – | followup card | wire |
| spec: nutrition intake card | `nut_how_heard, nut_job, nut_surgery, …` | field | ✓ | ✓ | – | `IntakeCompleteness.php:33` + spec forms via `SpecCardCtx.fields` | ✓ exists |
| spec: dental today/exam | `dent_pain_level, dent_radiotherapy, dent_bisphosphonate` | field | ✓ | ✓ | – | same | ✓ exists |
| spec: gyn first-visit | `spouse_name, menarche, …` | field | ✓ | ✓ | – | same | ✓ exists |
| gyn_cats | — | — | | | | features only → popover shows note + «إعدادات التخصص» link | |

Cards with no row (documents, previous, notes, timeline, summary, orders, team, procedures, ctx cards): **no ⚙**.

### 2.4 «إعدادات التخصص» side sheet

**Container:** right-anchored in LTR / **inline-end** in RTL (slides in from the left edge on Arabic, over the end rail), width 420px, full screen under 640px. Header in the purple bar colour; body white. Esc closes; focus returns to the bar button.

**Sections (client scope; doctor scope = same sheet, all controls disabled, banner «إعدادات العيادة — للاطلاع بس»):**

1. **«مجموعات {type}»** — `{type}.feature.*` switches, one row each, with a count line «بيقفل: 3 كروت · 2 تصنيف».
   - gyn_ob: الخصوبة · الحمل والولادة · المسالك النسائية (Urogyn) · الصحة الجنسية 🔒 · الأورام · العمليات.
   - dental — the 8 seeded `dental.feature.*` keys (`DentalSettingDefinitionSeeder`): `perio` اللثة · `ortho` التقويم · `lab` معمل الأسنان · `recall` الاستدعاء · `imaging` الأشعة (FMX) · `surfaces` أسطح السن · `note_templates` قوالب الملاحظات · `plan_signature` توقيع خطة العلاج. These are already real switches in `/clinic/settings → الأسنان`; here they are the same keys (second door), not a copy.
   - general / nutrition: the section renders one note «مفيش مجموعات مزايا للتخصص ده».
   - Turning a group off while the draft contains its cards: those cards leave the draft **immediately** (preview) and the bar message says «اتشالت 3 كروت لأن «المسالك» اتقفلت — هترجع لو فتحتها تاني».
   - Why here: per-type, affects *this screen's* cards and the categories picker — the prototype put it on the same panel (1254, screenshot 25).
2. **«الشاشة»** — `features.shell` «الشاشة الجديدة لهذا التخصص» (nutrition, dental only; general/gyn show «الشاشة الجديدة هي الأساسية للتخصص ده»). Helper text: «لما تقفلها، الدكاترة يفتحوا الشاشة الكلاسيكية — والتخصيص هنا يفضل محفوظ». Why here: per-type, the clinic admin is already looking at this type's screen.
   - **Decision 7 `saveLocked` interaction (must be settled in W3-FE-4):** today the nutrition/dental save button is disabled for the whole draft. Recommendation: **scope the lock to `layout` only** — rules, elements, features and the shell switch save normally for every type (the BE already keeps `tabs/props`, review A bug 4 says the lock's reason is stale; if the lock is kept at all it is policy). Concretely: for a save-locked type the bar's «حفظ للعيادة» stays enabled, the PUT sends `layout: <unchanged saved value>` + the new `fields/elements/features`, and the `SPEC.SAVE_PENDING` note changes to «ترتيب الكروت للتخصص ده لسه معاينة — القواعد والمجموعات بتتحفظ». If the orchestrator lifts Decision 7 instead, delete that note.
3. **«عام للعيادة»** (collapsed `details`, grey header, badge «عام — مش للتخصص ده بس»). A **second door, not a second copy** (repo precedent: ISS-2026-0177 payment methods). Writes the same setting keys `/clinic/settings` writes; needs **`clinic.settings.manage`** in addition to `clinic.layout.manage`, otherwise rows render read-only with «من إعدادات العيادة ↗».
   - «السماح للطبيب بتخصيص شاشته» `clinic.layout.allow_doctor_override` switch + helper «لو مقفول، كل الدكاترة يشوفوا شكل العيادة زي ما هو».
   - «نوع المنشأة» `clinic.facility_type` radio-cards (same `radio-cards` variant the settings registry already has): **عيادة** · **مركز** · **مستشفى** · **مخصص**. It is a preset over the four existing keys the shell already reads — `clinic.ordering_mode`, `clinic.lab_mode`, `clinic.rad_mode` (surfaced on the encounter as `ordering_mode / lab_mode / rad_mode`, `visit-shell.component.ts:208,304`) and `clinic.pharmacy_source` (design §2.6). **The per-preset values and the one-line description under each card come from W2-BE-C's `clinic.facility_type` definition (Decision 3) — do not invent behaviour copy on the FE**; the card shows the definition's `desc` and, under it, the four resulting values as small `code` chips (e.g. `ordering: reception · lab: integrated · rad: integrated · pharmacy: products`). «مخصص» shows the four current values and a link «عدّلها من إعدادات العيادة ↗». Changing it shows an **inline confirm** under the cards: «ده بيغيّر مكان تنفيذ الطلبات/التحاليل/الأشعة/الصيدلية للزيارات الجاية — [أكّد] [رجوع]». It is applied with the bar's «حفظ للعيادة» (FE sends the settings PUT first, then the layout PUT; a settings failure keeps the draft and shows the error line).
   - Why here at all: the owner's rule («مش من شاشات تانية») and Decision 3; why *not* primary here: these two keys are not per-screen, so their canonical home and audit stay in `/clinic/settings`.

**What stays only in `/clinic/settings`:** revenue model, hospital night rules, payment methods, the history-question entity editor («الحقول السريرية»), dental numbering/mode — none of them is "the look of the screen".

### 2.5 Scope, overrides, resets — the rules in one table

| Situation | Bar | ⚙ popover | Sheet |
|---|---|---|---|
| Manager, override off | no switch; «حفظ للعيادة» | full | full |
| Manager, override on, no doctor data | switch (default client) | full | full |
| Manager + has own doctor layout | switch; hint «ليك نسخة شخصية…» + «شيل نسختي» | full | full |
| Doctor, override on | no switch (doctor scope), «حفظ لي» | visible/required read-only; input/elements editable unless 🔒 | read-only |
| Doctor, override off | edit button hidden (today's rule) | — | — |
| Signed visit | edit button hidden (today's rule) | — | — |

Reset semantics: «رجّع افتراضي العيادة» = `layout:null, fields:{}, elements:{}, features:{}` for the type at client level (features back to specialty defaults — say so in the confirm line: «هيرجّع كمان مجموعات التخصص لافتراضيها»). «رجّع افتراضيي» = the doctor's JSON for the type removed. Per-card reset = this card's keys only (§2.2 footer).

### 2.6 Required / locked cards — how they look

| Card | Why | Toolbar | Head (edit mode) | Popover |
|---|---|---|---|---|
| diagnosis, insured visit | Decision 1 (`from:'required'`, re-inserted) | «✕ إخفاء» disabled, `title` «إلزامي للمؤمّن — ميتشالش» | `.from.required` tag «إلزامي» + amber dot | `required` row forced on, tag «مفروض: مريض مؤمّن» |
| gyn_cats, gyn_modules | `required:true` | disabled ✕, «كارت إلزامي — يتحرك ولا يتشال» (exists) | «إلزامي» tag | note «التصنيفات والـ modules أساس شاشة النسا — تتحكم فيها من إعدادات التخصص» |
| dental chart `dent_workspace` | **must become `required:true`** (review A bug 3) | same | same | note only |
| any card whose rules are all 🔒 (doctor view) | client locked | normal | ⚙ shows a tiny lock badge | all rows grey + lock |

Also: a card that is on screen only because a feature group is on gets a 2nd tag «مجموعة: الخصوبة» in edit mode so the admin knows what will remove it.

### 2.7 Mobile / tablet / keyboard / a11y

- Tablet (≤ 1024): bar wraps to 3 rows; scope switch stays on its own row; ⚙ keeps text. Touch targets ≥ 44px for every switch/seg (the review's P1 — the shell's `.narrow` rule today gives 32px; raise it for the edit surfaces).
- Phone (≤ 640): ⚙ popover and the sheet are bottom sheets; the level strip collapses to «المستوى: العيادة»; the bar's buttons go into the existing phone footer row (P1-4 rule) with «حفظ» primary.
- Keyboard: `Alt+S` = save for current scope; `Esc` closes popover → sheet → (in edit mode) asks to cancel; inside the popover `Tab` moves by row, `Space` toggles switch, `←/→` moves Select⟷Text, `L` toggles the lock on the focused row (client scope).
- a11y: popover `role=dialog aria-labelledby` the card title; switches `role=switch aria-checked`; Select/Text `role=radiogroup`; source tags have `aria-label` «المصدر: العيادة»; the dirty dot has `sr-only` «فيه تعديلات غير محفوظة»; the sheet traps focus; all colours already pass 4.5:1 with `--muted #5f726e`.

### 2.8 Errors

- Save 403 (doctor override turned off meanwhile): line «العيادة قفلت تخصيص الدكتور — تعديلاتك ماتحفظتش» + keep draft read-only.
- Save 422 (unknown key): line with the key names; never silently drop.
- Settings PUT fails in the «عام» block: layout not sent; draft stays; «ماقدرناش نحفظ إعدادات العيادة العامة — جرّب تاني أو من إعدادات العيادة ↗».

### 2.9 i18n keys (bump `I18N_VERSION`)

`VISIT_SHELL.ED.SCOPE_Q` «الإعداد لمين؟» · `SCOPE_CLIENT` «للعيادة كلها» · `SCOPE_ME` «لي أنا» · `LEVEL_DEFAULT/CLIENT/ME` «افتراضي/العيادة/أنا» · `LEVEL_HINT` «كل مستوى بيستبدل اللي قبله — والدكتور فوق العيادة» · `SAVE_SCOPE_CLIENT` «حفظ للعيادة» · `SAVE_SCOPE_ME` «حفظ لي» · `DIRTY_SWITCH` «في تعديلات مش محفوظة لـ{{scope}}» · `KEEP/DISCARD/BACK` «احفظ/سيبها/ارجع» · `HAS_MY_COPY` «ليك نسخة شخصية مفعّلة — اللي بتحفظه هنا للكل، ونسختك هتفضل فوقه.» · `DROP_MY_COPY` «شيل نسختي» · `ME_OVER_CLIENT` «نسختك فوق شكل العيادة — القواعد اللي عليها 🔒 العيادة مثبّتاها» · `TYPE_SETTINGS` «إعدادات التخصص» · `CARD_SETTINGS` «إعدادات» · `UNSAVED_DOT` «فيه تعديلات غير محفوظة».
`VISIT_SHELL.SET.TITLE` «إعدادات «{{card}}»» · `FIELDS` «الحقول» · `ELEMENTS` «العناصر» · `VISIBLE` «ظاهر» · `HIDDEN` «مخفي» · `REQUIRED` «إلزامي» · `INPUT` «الإدخال» · `SELECT` «Select» · `TEXT` «Text» · `SELECT_HINT` «من الكتالوج (chips)» · `TEXT_HINT` «نص حر» · `LOCK` «ثبّت — الدكتور ميقدرش يغيّرها» · `LOCKED_BY_CLINIC` «مقفول من العيادة» · `FORCED_INSURED` «مفروض: مريض مؤمّن» · `RESET_CARD` «رجّع الكارت للافتراضي» · `HIDDEN_NO_REQ` «الحقل مخفي» · `DOCTOR_RO` «العيادة بتحدد الحقول دي — انت تقدر تغيّر نوع الإدخال والعناصر بس» · `HISTORY_LINK` «أسئلة التاريخ المرضي بتتدار من الحقول السريرية» · `HDR_PER_TYPE` «ده للشاشة دي بس — التخصصات التانية ليها إعدادها».
`VISIT_SHELL.RULE.*` one label per key in §2.3 (e.g. `HDR_INSURANCE` «التأمين في الهيدر», `COMPLAINT_QUICK` «المفضّلة السريعة ★», `COMPLAINT_HPI` «حقل HPI», `COMPLAINT_DURATION` «مدة الشكوى», `DX_SECONDARY` «التشخيص الثانوي», `LAB_PREV` «زر «نتائج سابقة»», `RX_INTERACT` «تنبيه التفاعلات الدوائية»).
`VISIT_SHELL.TYPE.GROUPS` «مجموعات {{type}}» · `NO_GROUPS` «مفيش مجموعات مزايا للتخصص ده» · `GROUP_CLOSES` «بيقفل: {{cards}} كارت · {{cats}} تصنيف» · `GROUP_DROPPED` «اتشالت {{n}} كروت لأن «{{group}}» اتقفلت — هترجع لو فتحتها تاني» · `SHELL` «الشاشة» · `SHELL_SWITCH` «الشاشة الجديدة لهذا التخصص» · `SHELL_HINT` «لما تقفلها، الدكاترة يفتحوا الشاشة الكلاسيكية — والتخصيص هنا يفضل محفوظ» · `SHELL_PRIMARY` «الشاشة الجديدة هي الأساسية للتخصص ده» · `GLOBAL` «عام للعيادة» · `GLOBAL_BADGE` «عام — مش للتخصص ده بس» · `ALLOW_DOCTOR` «السماح للطبيب بتخصيص شاشته» · `ALLOW_DOCTOR_HINT` «لو مقفول، كل الدكاترة يشوفوا شكل العيادة زي ما هو» · `FACILITY` «نوع المنشأة» · `FACILITY_CLINIC/CENTER/HOSPITAL/CUSTOM` «عيادة/مركز/مستشفى/مخصص» · `FACILITY_CONFIRM` «ده بيغيّر مكان تنفيذ الطلبات/التحاليل/الأشعة/الصيدلية للزيارات الجاية» · `RO_BANNER` «إعدادات العيادة — للاطلاع بس» · `FROM_SETTINGS` «من إعدادات العيادة».
English keys: plain equivalents («For the whole clinic» / «Just me» / «Card settings» / «Specialty settings» / «Lock — doctors can't change it» …).

---

## 3. B — In-place catalog management inside the shared picker dialog

### 3.1 Permissions (Decision 2; migrates off `clinic.history.manage`)

| Action | `clinic.lookups.add` | `clinic.lookups.manage` | neither (has `clinic.history.view`) |
|---|---|---|---|
| see list, search, pick | ✓ | ✓ | ✓ |
| ★ per doctor | ✓ | ✓ | ✓ |
| «＋ إضافة جديد» row | ✓ | ✓ | hidden; the no-hits note says «اطلب إضافته من الدكتور» |
| «✎ إدارة» mode (rename / hide / reorder / move / delete / new category / show hidden) | ✗ | ✓ | ✗ |

Roles on dev: doctor roles → add + manage; nurse → add; reception/others → none. FE: the box's `canAdd` default changes to `clinic.lookups.add`; a new `canManage` input defaults to `clinic.lookups.manage`. BE routes: `POST` → `clinic.lookups.add`; `PATCH/reorder/DELETE/restore` → `clinic.lookups.manage`.

### 3.2 «✎ إدارة» — a mode, not always-on actions

The picker is a clinical tool first; management must not clutter the pick path. In the dialog head (`.pk-h`), after the multi/single tag: a `btn xs` **«✎ إدارة»** (only with manage). Clicking it toggles `manage` and the button becomes `pri` «خلاص» (`aria-pressed`). Manage mode changes:

- A **manage strip** appears under the search: `[إظهار المخفي ○] [＋ فئة جديدة]  اسحب ⋮⋮ أو ↑↓ للترتيب · F2 تعديل الاسم · Esc خلاص` (hint in `--muted`).
- Row (`.pk-i`) gains, at the start edge: a drag handle `⋮⋮` (CDK DragDrop, within the same category only), and at the end edge an action cluster: `↑ ↓ · ✎ · 👁 · 🗑` (desktop) or one `⋯` menu (phone). The checkbox and label stay; **row click = start rename** (inline), checkbox click still toggles the pick — so «hide this and pick that» works without leaving the mode.
- The ★ block stays at the top but is **not reorderable** (it is the server's favourites sort); reordering happens inside the category sections / «باقي الكتالوج». In grid mode (investigations) management is per open category, same row cluster.
- Keyboard: `F2` rename focused row, `Alt+↑/↓` move, `Delete` → delete flow, `H` hide/show, `Esc` cancels rename → exits manage → closes dialog.

### 3.3 Actions

| Action | Control | Behaviour | API (W1-BE-B — confirm names) | Undo |
|---|---|---|---|---|
| Rename | ✎ / row click / F2 → inline input replaces the label; a tiny `AR|EN` toggle inside edits `value_ar` / `value_en`; `Enter` saves, `Esc` cancels | 422 duplicate → inline red «الاسم موجود بالفعل»; empty → not saved | `PATCH /clinical-lookups/{id} {value_ar, value_en}` | reverse PATCH |
| Hide / show | 👁 | `is_active=false`; the row leaves the list unless «إظهار المخفي» is on, where it shows dimmed with tag «موقوف» and 👁 becomes «إظهار» | `PATCH {is_active}` | reverse PATCH |
| Reorder | drag ⋮⋮ or ↑↓ / `Alt+↑↓` | within category; `display_order` renumbered for the category; debounced 600ms | `POST /clinical-lookups/reorder {parent_id, ids[]}` | previous order resent |
| Move to another group | the category `select` that appears on the row in manage mode (or the ⋯ menu «نقل إلى…») | row moves to the end of the target category | `PATCH {parent_id}` | reverse PATCH |
| Delete if unused | 🗑; disabled with `title` «مستخدم في {{n}} زيارة — اخفيه بدل ما تحذفه» when `usage_count > 0` | enabled → the row turns `--red-bg` with inline «حذف «X»؟ [نعم] [لا]» (no modal) | `DELETE /clinical-lookups/{id}` → 204, or `409 {usage_count}` (race) | `POST /{id}/restore` |
| New category | «＋ فئة جديدة» in the strip | inline input at the top of the list; creates a parent row | `POST {lookup_key, value_ar, parent_id:null}` (existing store) | hide |
| ★ | unchanged (both modes) | per doctor; `title` «أضف لمفضّلتي (لي أنا — مش للعيادة)» exists | existing | — |
| Add | unchanged row at the bottom; also «إضافة واختيار» stays the pick-path add | validated `parent_id` same key + company (review C bug 3); sends `specialty` | existing store | hide |

Data the list needs in manage mode: `GET ?lookup_key&specialty&include_inactive=1&with_usage=1` → rows carry `is_active`, `usage_count`. Outside manage mode the request is unchanged (active only, no counts).

**Cache rule (review C bugs 4/5):** every manage mutation patches **all** `key|*` cache entries of that key, not only `key|specialty`; a 403 list must cache `[]` with an `error` flag and never refetch on `changed`.

### 3.4 Undo

One toast per action (PrimeNG toast, bottom, 6 s, `aria-live=polite`): «اتخفى «ألم بطن» — [تراجع]» / «اتعدّل الاسم» / «اتنقل لـ«هضمي»» / «اتحذف «X»». Undo stack depth = 1 (last action); «تراجع» runs the inverse call and re-patches the cache. After 6 s the toast goes; nothing else to do (soft delete keeps restore possible from BE anyway).

### 3.5 Inactive values and old visits

- `ClinicalPick` already carries its own `label` and matches by `lookupId` — a hidden value **still renders** on every visit that selected it. Add a chip state `.chip.off` (dashed border, `--muted` text, `title` «القيمة موقوفة من الكتالوج — بتفضل ظاهرة في الزيارات القديمة»); it stays removable on an unsigned visit; on a signed visit it is read-only like everything else. Never drop it, never re-label it.
- In the dialog, if the visit's selection includes an inactive row and «إظهار المخفي» is off, the footer «المختار (n)» still lists it (from the pick's label) and the ★ block ignores it.
- Quick ★ chips on the card exclude inactive rows.

### 3.6 Short GYN chip lists rendered open (review C bug 6)

`gyn-lookup-field` renders ≤6 (multi) / ≤7 (single) values open. Change: the trailing **«＋» is always shown** when not disabled — it is the only door to ★, search and (if allowed) add/manage. Inside the dialog the add row and the «✎ إدارة» button are gated as in §3.1; ★ works for everyone. `aria-label` of the «＋» becomes «بحث · مفضّلة · إضافة». Open chips keep the `★` prefix for favourites so a starred value is visibly first.

### 3.7 Empty / error states

- Empty catalog, can add: «الكتالوج فاضي — أضف أول عنصر من تحت» (exists) + focus the add input.
- Empty, cannot add: «الكتالوج فاضي — اطلب من الدكتور أو الأدمن يضيف القيم».
- 403 on list: «مفيش صلاحية لعرض الكتالوج» inside the list area (not the empty-catalog text).
- Manage call fails (network/5xx): toast «ماقدرناش نحفظ التعديل — جرّب تاني»; the row reverts (optimistic UI with rollback).
- Rename to a name that exists but is hidden: 422 message «موجود كقيمة موقوفة — اعرض المخفي وفعّله».

### 3.8 Mobile / a11y

- Phone: the dialog is already full-screen; in manage mode each row gets one `⋯` button (44px) opening a small action sheet (تعديل الاسم / نقل إلى… / إخفاء / حذف / ↑ / ↓); the drag handle is hidden (touch-drag competes with scroll).
- In manage mode the list switches from `role=listbox` to `role=list` + `role=listitem` (inline inputs are not valid inside options); the pick checkbox becomes a real `<input type=checkbox>` with a label. The undo toast is `aria-live`. Every icon button has an `aria-label`.

### 3.9 i18n keys (`CLINICAL_PICKER.MANAGE.*`, bump `I18N_VERSION`)

`BTN` «✎ إدارة» · `DONE` «خلاص» · `SHOW_HIDDEN` «إظهار المخفي» · `NEW_CAT` «＋ فئة جديدة» · `HINT` «اسحب أو ↑↓ للترتيب · F2 تعديل الاسم · Esc خلاص» · `RENAME` «تعديل الاسم» · `MOVE` «نقل إلى…» · `HIDE` «إخفاء» · `SHOW` «إظهار» · `DELETE` «حذف» · `DELETE_Q` «حذف «{{v}}»؟» · `YES/NO` «نعم/لا» · `USED` «مستخدم في {{n}} زيارة — اخفيه بدل ما تحذفه» · `INACTIVE` «موقوف» · `INACTIVE_CHIP` «القيمة موقوفة من الكتالوج — بتفضل ظاهرة في الزيارات القديمة» · `DUP` «الاسم موجود بالفعل» · `DUP_HIDDEN` «موجود كقيمة موقوفة — اعرض المخفي وفعّله» · `UNDO` «تراجع» · `T_HIDDEN` «اتخفى «{{v}}»» · `T_SHOWN` «اتظهر «{{v}}»» · `T_RENAMED` «اتعدّل الاسم» · `T_MOVED` «اتنقل لـ«{{cat}}»» · `T_DELETED` «اتحذف «{{v}}»» · `T_REORDERED` «اتغيّر الترتيب» · `FAIL` «ماقدرناش نحفظ التعديل — جرّب تاني» · `NO_PERM_LIST` «مفيش صلاحية لعرض الكتالوج» · `EMPTY_NO_ADD` «الكتالوج فاضي — اطلب من الدكتور أو الأدمن يضيف القيم» · `ASK_ADD` «اطلب إضافته من الدكتور» · `PLUS_LABEL` «بحث · مفضّلة · إضافة».

---

## 4. C — Visual deviations in today's /app vs the prototype (for the FIN pass)

Judged from `general.png`, `general-edit.png`, `general-finish.png`, `gyn-preg.png` against 16/21/02/06/20/25. Non-visual review rows are not repeated.

**P0 — the owner would call it "not the prototype"**
1. Dental jaw chart is hideable once the save lock lifts (`dent_workspace` not `required`) — the prototype's `dent_chart.required:true`. Make it required + show the «إلزامي» tag.
2. Insured visit: the diagnosis card must show as forced (`إلزامي` tag, disabled ✕, forced row in ⚙) — verify visually after W1-FE-1; today a hidden dx card blocks signing with no visible reason on the card.
3. No ⚙ / no settings surface anywhere in edit mode (`general-edit.png`): the headline gap vs `16_settings.png`. This spec.
4. Picker catalog management absent and «＋ إضافة جديد» invisible to stock doctor roles (perm) — vs `06_general_dx_inline_add.png`. This spec §3.

**P1 — visible mismatch**
5. Header carries «الأدوية الحالية» chips (general.png, gyn-preg.png); the prototype's header is **alerts only** (red/amber/violet chips) and current meds live in the start rail. Move meds out of the header; keep the rail card.
6. Vitals station renders the legacy tile panel (large bordered tiles, serif/monospace digits «122/78», «38.4») instead of the prototype's compact labelled inputs with unit suffixes (21_edit_mode.png). Needs the `props.mode compact|full|readonly` (review A #32) and the shell font for digits.
7. Collapsed station summaries are empty for التاريخ المرضي / الفحص / التحاليل / الأشعة / المستندات (general.png); the prototype always shows a one-line summary («سكري، ضغط…», «HbA1c, INR, TSH»).
8. The edit bar wraps into two rows with «إلغاء» alone on the second (general-edit.png); with the scope switch and «إعدادات التخصص» added, use the explicit two-row grammar of §2.1 so it is intentional, not overflow.
9. Tablet targets are 32px (`.narrow`) vs the 44px spec; phone hides orders/bill (review). Raise to 44px for chips/switches/toolbar buttons on touch widths.
10. gyn-preg.png: «بيانات أول زيارة (النسا)» sits above the vitals as an unnumbered bare card while the summary rail numbers it «1» — either number it on the head too or move it out of the station sequence. Also 21 rail rows vs the prototype's 13: collapse the extras (`team`, `discharge`, `operation`, `gyn_followup`) by default on general GYN visits.
11. Lab/rad cards still spread the legacy picker + basket on the card (review C #16-17) vs `02_general_lab_picker.png` (chips + «＋ اختيار / بحث» + grid dialog). W2-FE-2.

**P2 — polish**
12. App-bar context pills («تسلسل محطات · عمود واحد», `encounter.kind`) show only in edit mode; the prototype shows them always in the app bar. Low cost to show always as `hpill`s.
13. The «ملف ناقص» amber pill and «الشاشة الكلاسيكية» button are not in the prototype — keep both (useful), but align the pill style with the header chips (`.tag.amber`).
14. Card-head layer/source tags appear only while editing (design review P1-2 decision) — fine; but in edit mode the source tag reads «افتراضي» in grey while the prototype uses `.from` colours; ensure `.from.client/.doctor/.required` colour classes are applied.
15. Numbers inside vitals tiles and the orders rail («300.00») use a monospace face; the shell uses Inter/Cairo everywhere else — one font.
16. Finish banner (general-finish.png) matches the prototype's amber note; add the prototype's per-line «افتح المحطة» links so the warnings are actionable.

---

## 5. Build notes for the implementer

- Components: `VisitCardSettingsPopoverComponent` (per-card ⚙, fed by `CardDef.rules` + draft + scope), `VisitTypeSettingsSheetComponent` (sheet), extend `VisitLayoutEditorComponent` (scope switch, level strip, single save), extend `ClinicalPickerDialogComponent` (manage mode) and `ClinicalLookupService` (patch/reorder/delete/restore, include_inactive, cache-all-keys), `GynLookupFieldComponent` («＋» ungated).
- Draft shape per scope: `{layout, fields, elements, features}`; selectors: `effectiveRule(key)`, `effectiveElement(key)`, `ruleSource(key) → 'default'|'client'|'doctor'`, `isLocked(key)`. Rules resolve by **per-key merge** (§1).
- Consumers to wire (from §2.3) read through those selectors, never the raw JSON.
- `CardDef.rules` for every card in §2.3; `dent_workspace.required = true`.
- i18n: all keys above in `ar.json` + `en.json`; bump `I18N_VERSION` in `app.config.ts`.
- Deploy `/app` after the FE wave; the owner tests on live.

## 6. Open items for the owner / orchestrator

1. `hdr.*` elements per type (this spec) or company-wide? Per type matches the API; company-wide matches the mental model «الهيدر واحد».
2. Should nurses see «✎ إدارة» read-only (to spot typos and tell the doctor)? Spec says no.
3. Facility type second door in the sheet — keep, or /clinic/settings only? Spec keeps it (Decision 3 + owner's rule), with inline confirm.
4. Delete-if-unused counts visits only, or also favourites/intake answers? Spec: any referencing row (`encounter_*`, history answers JSON mirror) counts.
