# Slice A — layout engine, catalog, registry, persistence, edit mode, client settings

FE root = `/home/moonui3/public_html/moon-erp/src/app/features/clinic/visit-shell/` (abbrev `vs/`). BE root = `/home/moonui3/moon-erp-be/Modules/`.

**Verdict on the client settings screen: ❌ MISSING.** The prototype's «⚙ إعدادات الشكل / Program Setting» panel (proto 1245-1265, screenshot 16_settings.png) has no FE screen. Here is what exists in its place:
- (a) the in-shell «✎ تخصيص الشاشة» editor, which only edits the card layout;
- (b) the generic `/clinic/settings` page, which shows only `clinic.visit_shell`, `clinic.layout.allow_doctor_override` (group clinic_modes) and `dental.feature.*` (group «الأسنان»).

Everything else exists in the API only, with no UI:
- field rules `{visible, required, input}`;
- `gyn.feature.*`;
- the per-type `features.shell` switch.

## 1. Verdict table

| # | prototype item (proto line) | implementation file:line | verdict | note |
|---|---|---|---|---|
| **Card catalog** |||||
| 1 | Shared cards vitals/complaint/history/exam/diagnosis/lab/rad/rx/documents/previous/notes/followup, sizes (431-445) | vs/card-catalog.ts:17-32 | ✅ | Ids, kinds and sizes match exactly. Layers match too (history L1, exam L4, rest L3). |
| 2 | `diagnosis.requiredWhen:'insured'` (435) | card-catalog.ts:21; resolve-layout.ts:97-100; visit-shell.component.ts:476, 483-489 | ✅ | Never re-inserted. Soft «DX_NOT_ON_SCREEN» warning when the card is absent. If the card is present on general, there is a hard block until a primary dx exists (classic rule). |
| 3 | `meds` current-meds card (441) | vs/visit-start-rail.component.ts:45 (rail only); card-catalog.ts:31 `home_meds` | ⚠ | Not a catalog card. `home_meds` is the D2c patient-facts list, not «reads prescriptions». Current meds show only in the start rail. |
| 4 | `results` card (app-lab-history-groups) (442) | visit-start-rail.component.ts:54 (rail only) | ❌ | Not addable as a card. The prototype MAP (1280) says rail cards are also catalog cards. |
| 5 | `photos` card (444) | — | ❌ | No card. Only `documents` exists. |
| 6 | `intake` status card (446) | visit-header.component.ts:51,65 (intake badge/banner) | ⚠ | Shown as a header badge, not as a catalog card. |
| 7 | Extra FE shared cards: header(required, chrome), alerts, conditions, home_meds, summary, orders, timeline, procedures, team, summary_gen | card-catalog.ts:16, 29-39 | ✅ | Later-phase additions, permission-gated (`permission` field, resolve-layout.ts:75). |
| 8 | Nutrition 14 spec cards nut_* (448-461) | vs/specialties/nutrition.cards.ts:28-55 (`nut_measure`, `nut_board`) | 📝 | S2b. The board is hosted as one bare F card plus a measurements card. None of the 14 ids exist. |
| 9 | `dent_chart` F **required:true** (463) | specialties/dental.cards.ts:23-32 `dent_workspace` | ⚠ | Mapped to dent_workspace, but **not `required`**. Once dental saves unlock, a client could hide the jaw chart. |
| 10 | dent_today / dent_exam (464-465) | dental.cards.ts:51-68 | ✅ | Sizes M/F (proto S/M). Bare cards. |
| 11 | dent_plan, dent_fmx(imaging) (466, 468) | — | 📝 | S2c: kept inside the workspace view switch. The `imaging` feature therefore gates no card. |
| 12 | dent_perio/ortho/lab/recall with `feature` (467-471) | dental.cards.ts:79-122 | ✅ | `feature` perio/ortho/lab/recall plus permissions. Sizes are F only (proto L/F, M/F, S/M), so resize does nothing. |
| 13 | gyn_cats / gyn_modules required (473-474) | features/gyn/gyn-shell-cards.ts:39-48 | ✅ | `required:true`, station. gyn_cats is layer 4 (proto L3). |
| 14 | gyn_timeline (475) | card-catalog.ts:36 shared `timeline` | ✅ | Renamed to the shared C1c timeline (naming drift). |
| 15 | gyn_summary (476) | gyn-shell-cards.ts:93-94 | ✅ | Plus the runtime `gyn_<module>` addable cards via registerSpecCards (card-catalog.ts:59-68), as spec §3 asks. |
| 16 | ctx cards mar / nursing / discharge (478-480) | vs/contexts/inpatient.cards.ts:20-42 | ✅ | `inp_mar`, `inp_round` + `inp_nursing_notes` (split), `inp_discharge`, plus an emergency `triage`. kind 'ctx', permission-gated. |
| **Registry defaults** |||||
| 17 | general = flow, 1 col, 10 stations (489-493) | card-catalog.ts:93-112 | ✅ | Same order and sizes. Adds `timeline` S collapsed before followup (Fable consult). |
| 18 | nutrition = 3-col board, 13 cards (499-502) | nutrition.cards.ts:58-72 | 📝 | 1 col with 2 tabs (measure / الكشف) and hideRail. Known S2b. |
| 19 | dental = 2-col board (510-513) | dental.cards.ts:126-151 | 📝 | 2 cols ✅. Cards differ: dent_alerts kept, dent_quick_complaint instead of shared complaint, dent_more instead of the collapsed vitals/history/previous; tabs; hideRail. All known S2c. |
| 20 | gyn = flow, 13 stations (521-523) | gyn-shell-cards.ts:102-133 | ⚠ | Superset of 21 cards: first_visit, obhx, exam, imaging, operation, discharge, team, gyn_followup added. Prototype relative order kept. history `patientScopeOnly:true` (proto had none). |
| **resolveLayout** |||||
| 21 | default → client → doctor only if allowDoctorOverride, full replace (560-562) | vs/resolve-layout.ts:51-61 | ✅ | `isUsable` guard. Never merges. |
| 22 | Drop unknown / other-spec / ctx / feature-off (564) | resolve-layout.ts:69-86 | ✅ | Also drops duplicates, chrome ids and cards whose permission fails. |
| 23 | Size fallback to sizes[0] (565) | resolve-layout.ts:90 | ✅ | |
| 24 | Re-insert missing `required` at top, tagged 'required' (566) | resolve-layout.ts:98-107 | ✅ | Skips feature-locked and permission-denied cards (consistent with the drop rule). |
| 25 | Col clamp (570) | resolve-layout.ts:63-64, 91 | ✅ | Also caps columns at 4. |
| 26 | `version` lower → migration (spec §2.3) | resolve-layout.ts:44-46 | ✅ | Stub (v1 only). |
| 27 | Context replace followup→discharge (529, 567) | contexts/inpatient.context.ts:65; resolve-layout.ts:120-131 | ✅ | If the user lacks the discharge permission, the followup card is dropped (no stray follow-up). Tagged 'context'. |
| 28 | Context addCards mar/nursing/discharge (529, 568) | inpatient.context.ts:56-61; resolve-layout.ts:132-138 | ✅ | Appended at the end. day_case has no nursing notes. Emergency triage goes `at:'start'` (extra). |
| 29 | cardProps rx kind=inpatient (530, 569) | inpatient.context.ts:66; visit-shell.component.html:277-285 | ✅ | Only when `hospital.mar_on_prescriptions` ≠ false; otherwise the legacy order form. |
| 30 | Proto contexts clinic/center (orderingMode/labMode/pharmacy) (526-527) | visit-shell.component.ts:208, 281 (encounter.ordering_mode / lab_mode / rad_mode) | ✅ | Derived from the encounter (BE), not from a FE context. Correct mapping. |
| 31 | Vitals pack = base 8 + specialty pack + context pack (572) | encounter/vitals-panel/vitals-panel.component.ts:214-217 (`VITALS_WHITELIST`) | ❌ | The vitals card is always the same 8 fields. Waist/hip/WHR, LMP/GA/fundal/FHR and RR/AVPU/NEWS2 exist only in the spec/ctx cards (nut_measure, gyn_antenatal, inp_round), not as a composed pack. |
| 32 | Vitals `props.mode full\|compact\|readonly` (431) | visit-shell.component.html:244-246 | ❌ | Only `patientScopeOnly` is passed. There is no compact or readonly mode prop. |
| 33 | effectiveFeatures = specialty defaults + client override (556) | visit-shell.component.ts:182-184, 540-548; card-catalog.ts:74-85; Gyn/app/Support/GynFeatures.php:40-61 | ✅ | Dental reads `DentalService.meta().settings.features` (Decision 6). Others read normalized `client.features`. Missing = ON. |
| 34 | Each card tagged with source level; editor shows "الحالي من" (571, 1141) | visit-layout.model.ts:13,107-110; visit-layout-editor.component.ts:20 | ✅ | |
| **Persistence (BE)** |||||
| 35 | Client level setting `clinic.layout.{type}` + fields + features (532-547) | Clinic/database/seeders/ClinicSettingDefinitionSeeder.php:994-1009 (`clinic.visit_layouts`, company, is_visible=false) | ✅ | One JSON keyed by type: `{layout, fields, features}`. |
| 36 | Doctor level = user preference (548-553) | Seeder:1010-1025 `clinic.visit_layout_doctor` scope=user | ✅ | Stores only the layout. |
| 37 | allow_doctor_override (default off) (536, 540) | Seeder:978-993; VisitLayoutController.php:77-81, 130-134 | ✅ | PUT doctor returns 403 when off. GET hides the doctor layout when off. |
| 38 | GET / PUT client / PUT doctor + clinic.layout.manage | Clinic/routes/api.php:482-484; VisitLayoutController.php:32-88 | ✅ | PUT client sent with the layout only keeps fields/features (55-65). `layout:null` resets that level. The permission is seeded and granted to 3 roles on dev. |
| 39 | Validation of layout / tabs / props / fields.*.input | VisitLayoutController.php:41-49, 91-115 | ✅ | Card ids are not checked against a catalog (the FE drops unknown ids at resolve). Raw `layout` is stored so props/tabs survive. |
| 40 | stripLayout: context cards and `from` not persisted (1115) | resolve-layout.ts:190-210; visit-shell.component.ts:225, 751-757, 779 | ✅ | The editor edits `resolvedBase` (context-free), so followup is not lost (S3 Decision 10). |
| **Edit mode «✎ تخصيص الشاشة»** |||||
| 41 | Who: client admin; doctor only if allowed (1139-1142) | visit-shell.component.ts:291-294; visit-shell.component.html:225-228 | ✅ | Shown when `can_manage \|\| allow_doctor_override`, and hidden on a signed visit. The prototype let anyone edit. |
| 42 | ↑↓ reorder, ◀▶ columns, size cycle, collapsed, hide except required (1126, 1147-1155) | vs/visit-card.component.html:1-22; vs/layout-edit.ts:40-89 | ✅ | Same algorithm, immutable. RTL arrow glyphs flipped. The size button is hidden when the card has one size. |
| 43 | Add from catalog, filtered by specialty + features, ctx excluded (1140, 1145, 1319) | card-catalog.ts:122-135; visit-shell.component.ts:295-300; layout-edit.ts:91-95 | ✅ | Also permission-filtered. Default size M (proto: sizes[0]). Shared cards get 🔗 like the prototype. |
| 44 | Save for client (primary) / save for me if allowed / reset per level / cancel (1142-1143, 1334-1338) | visit-layout-editor.component.ts:27-39; visit-shell.component.ts:776-801 | ✅ | After a save, the draft is rebuilt from the new payload and a message is shown. |
| 45 | Saving non-general specialty layouts | visit-shell.component.ts:272; visit-layout-editor.component.ts:42 | 📝 | Decision 7: saves are open only for general and gyn_ob. The BE already keeps tabs/props (LEDGER ef1d0f968), so this lock is now policy, not a technical need. |
| 46 | Context cards stay visible while editing (proto `layout()` concat ctx, 1118) | visit-shell.component.ts:254, 225 | ⚠ | Editing an inpatient visit shows the base layout only. MAR/round/discharge disappear until cancel. Persistence is correct either way. |
| **Client settings screen (⚙ Program Setting) — decides the verdict** |||||
| 47 | A dedicated settings panel with per-client / per-doctor scope (1245-1265) | — (no route; `/clinic/settings` = generic setting_definitions page, clinic.routes.ts:625-636) | ❌ | There is no screen anywhere that lists the type's cards, the field rules and the feature groups together (spec §2.7). |
| 48 | Per-field visible / required (544; spec §2.3) | API: VisitLayoutController.php:42-46. Consumers: cards/visit-complaint-card.component.ts:101-104, cards/visit-exam-card.component.ts:153-157, Clinic/app/Services/Intake/IntakeCompleteness.php:33-34 | ❌ UI (API ✅) | No FE caller ever sends `fields` (the only saveClient call is visit-shell.component.ts:782, layout only). Setting rules needs a hand-made PUT or a DB edit. |
| 49 | Per-field input Select⟷Text, legacy Examination Controller (545, 1248, 1261) | Consumed for exam regions (`exam_finding_<region>`) and chief_complaint only (exam-card:153, complaint-card:101) | ❌ UI / ⚠ consumers | No UI. The diagnosis Select/Text rule (proto INPUT_FIELDS 'diagnosis') is not consumed at all. |
| 50 | Element toggles hdr.insurance / complaint.quick / dx.secondary (+ hdr.lastVisit, hdr.doctor, complaint.hpi, lab.prev, rx.interact) (540, 653, 1262) | visit-header.component.ts:46 (insurance always shown); complaint-card:104 (`fields.complaint_quick.visible`) | ❌ | Only complaint.quick has a consumer (via field rules, API only). There is no `clinic.elements` store. Header insurance and secondary dx cannot be hidden. |
| 51 | Specialty features dental.feature.* (508, 546) | Dental/database/seeders/DentalSettingDefinitionSeeder.php (8 keys, group clinic_dental, visible); /clinic/settings «الأسنان» group (settings/clinic-settings.registry.ts:139-146) | ✅ | Real UI, enforced by the BE controllers (Lab/Ortho/Plan/Chart). |
| 52 | Specialty features gyn.feature.* (519, 546) | Gyn/app/Support/GynFeatures.php:19-61 (stored in `clinic.visit_layouts['gyn_ob'].features`) | ❌ UI (API ✅) | The setting row is is_visible=false and no FE sends `features`. Fertility/obstetrics/urogyn/sexual/oncology/operations can only be switched off via the API. |
| 53 | allow_doctor_override toggle | /clinic/settings group clinic_modes (Seeder:978-993, is_visible=1) | ✅ | Generic boolean switch. |
| 54 | Per-client layout | in-shell editor (#41-45) | ⚠ | Works for general and gyn_ob. Nutrition and dental are locked (#45). There is no off-visit place to manage layouts: you must open a live encounter. |
| 55 | Doctor-scope fields / elements override (proto cfgScope='doctor', 1246, 1360) | VisitLayoutController.php:84 (doctor stores layout only) | ❌ | The doctor level is layout-only. |
| 56 | Per-type shell switch `features.shell` (nutrition/dental) | encounter/consultation.component.ts:453; dev DB `{"nutrition":{"features":{"shell":true}},"dental":{...}}` | ❌ UI | API/DB only. An install cannot turn on the nutrition/dental shell without a hand PUT. |
| **Layer colouring / source tags** |||||
| 57 | «تلوين الحقول بالطبقة» toggle L1-L4 (163-164, 1165, 1310) | — | ❌ | No toggle and no `data-l` field marking. Low impact: it was a prototype-harness control. |
| 58 | Card head shows layer + source (default/client/doctor/context/required/draft) always (1128; spec §5) | visit-card.component.html:39-43; visit-card.component.ts:46-47 | ⚠ | Shown only while customising (design review P1-2). The kind tag (🔗/🏷/سياق) is always shown. |

## 2. Real bugs found (implemented but wrong)

No hard defects in the resolve/persist path. Minor misbehaviours:

1. **Editor card count is per tab.** `visit-shell.component.html:70` passes `layout()?.cards?.length`, but `layout()` is the open tab only (visit-shell.component.ts:260-263). On tabbed layouts (nutrition/dental) the bar says e.g. «1 كارت» instead of the draft total. The prototype counts `state.draft.cards` (1141).
2. **↑↓ can be a silent no-op on tabbed layouts.** `layout-edit.ts:74-84` swaps with the neighbour in the same column across the whole draft, including cards on another tab. The card moves "under" an invisible card. Latent only while dental/nutrition saves stay locked.
3. **`dent_workspace` is not `required`** (dental.cards.ts:23-32), unlike proto `dent_chart.required:true` (463). Once Decision 7 is lifted, «✕ إخفاء» will be enabled on the jaw chart, and `resolveLayout` will not re-insert it if a saved layout omits it.
4. **The Decision 7 lock is now stale.** The comment at visit-shell.component.ts:268 says the lock waits for the BE to keep `layout.tabs/props`. The BE does keep them (VisitLayoutController.php:57-58, 107-114). If the lock is still wanted, its stated reason should be updated.

## 3. Top 5 gaps by owner impact

1. No client settings screen (⚙ Program Setting): field visible/required/input, elements and gyn features have no UI. This is the headline difference from what the owner approved (16_settings.png).
2. gyn.feature.* and the nutrition/dental `features.shell` switch can only be set by API/DB. A real centre cannot lock Urogyn/Sexual or turn on the specialty shells itself.
3. Element toggles are not implemented: hdr.insurance, dx.secondary, hdr.lastVisit, hdr.doctor, complaint.hpi, lab.prev and rx.interact have no consumer. Only complaint.quick has one, via field rules.
4. The vitals card does not compose specialty or context packs. It is always the fixed 8 vitals, and there is no compact/readonly mode prop.
5. Several shared cards are rail-only or missing from the catalog: results, current meds, photos, intake status. Layout editing is also limited to general and gyn_ob, and only from inside a live encounter.
