# B-shell-chrome — prototype vs implementation (read-only audit)

Paths: FE = `moon-erp/src/app/features/clinic/`, VS = `FE/visit-shell/`, BE = `moon-erp-be/Modules/Clinic/app/`. Proto = `visit-shell-prototype.html`.

## 1. Verdict table

| # | prototype item (proto line) | implementation file:line | verdict | note |
|---|---|---|---|---|
| **Patient header** |
| 1 | Name · MRN · gender · age · blood · phone (1170) | VS/visit-header.component.ts:37-42, 92-103 | ✅ | BE `PatientReadService::summaryFor` L73-84. Numbers in `.en` (ltr). |
| 2 | Alert chips with a source tag, «+ تنبيه» (1174) | visit-header:57-60 → shared/patient-alert-strip:68-94 | ✅ | Allergy sorted first; a src tag per pill; «＋ تنبيه» gated on `clinic.alerts.manage`. |
| 3 | Allergy / anticoagulant / diabetes chips (581) | BE Services/Facts/PatientFactsReader.php:102-106, derivedConditionAlerts L240 | ✅ | On dev, `clinical_lookups` condition `anticoagulant` (danger), `diabetes` and `hypertension` (info) have is_alert=1. |
| 4 | «Rh سالب» chip, src=blood (581) | visit-header:100 (blood group as text only) | ❌ | No alert is derived from `blood_group`. Rh appears only in the Gyn computed/summary text. |
| 5 | Specialty alerts (pregnant 24w, latex) (507, 518) | alert-strip:105-110 (specItems), 296-330 | ✅ | GA/EDD/risk come in on the specialty facts line, and dental flags arrive as alert rows. |
| 6 | Insurance kv (1171) | visit-header:46, 106 | ⚠ | It always shows «نقدي». The field name does not match what the BE sends (see Bug B1). |
| 7 | Last visit / doctor kv (1171) | visit-header:43-47 | ✅ | The client toggles `hdr.*` do not exist (the elements slice covers those). |
| 8 | «⚠ ملف ناقص — أكمله» badge opens intake step 2 (1172, 1330) | visit-header:51 → intake/intake-badge.component.ts:32-38; reconfirm banner L65 | ✅ | One badge, ordered missing_shared > specialty > reconfirm. Clicking it opens the wizard on the right step. |
| 9 | Inpatient: 🛏 bed chip + **admitting** doctor (1171-1172) | visit-shell.html:22 (`stay.chip()`); header doctor = `encounter.doctor` (shell.ts:353) | ⚠ | The bed/ward/day chip is there. The admitting doctor is not shown; the header shows the encounter's doctor. |
| 10 | «ملف المريض» button (1173) | visit-header:52 | ✅ | |
| 11 | App bar pills: layout source, encounter.kind, autosave (1166-1168) | visit-shell.html:20-27 | ⚠ | Merged into the header row by decision (design review P0-1/P1-1). The layout pill shows only while customising, there is no encounter.kind pill, and the «حفظ تلقائي» pulse fires only on complaint saves (`onSaved` is bound only at shell.html:252). |
| **Flow / board** |
| 12 | Ordered stations, number, «تم ✓» (1127-1129) | visit-card.component.html:27-29, 47-51; shell.ts:303-311 | ✅ | Numbering covers `station:true` cards only. Non-station cards (timeline, alerts) show their icon. |
| 13 | Done → collapse to one-line summary, next station opens (1373-1375) | shell.ts:621-636; card.html:45 | ⚠ | Works. Differences: (a) after the last station it wraps back to the first undone one (L627); the proto stops. (b) history, documents and previous have no summary text (`summaries` L421-437), so their collapsed heads are blank. |
| 14 | First active = first undone (1162) | shell.ts:657-661 | ✅ | |
| 15 | Ctrl+Enter = finish the active station (1382) | shell.ts:929-934 | ✅ | |
| 16 | Alt+↓/↑ = previous/next station, wraps (1383) | shell.ts:935-942 | ✅ | Same as the proto, plus scrollIntoView. |
| 17 | Alt+F = finish panel, works in board mode too (1379) | shell.ts:921-925 | ✅ | Also matches `ev.code==='KeyF'` (Arabic keyboard layout). Suppressed while locked, customising, or a picker/dialog is open (L919-920). |
| 18 | Keyboard ignored outside the "returning" mode (1378) | shell.ts:919 | ✅ | Locked or editing → ignored. |
| 19 | Board mode (flow:false): icon head, open = !collapsed, no «تم», footer "لوحة · N كارت" (1121, 1186) | shell.ts:608-613; shell.html:205-207 | ✅ | |
| 20 | Flow done/active state | shell.ts:674-693 (localStorage `visit-shell:flow:<id>`) | ⚠ | Kept per browser only. A nurse's «تم» on vitals is not visible to the doctor on another PC. The proto kept it in memory; no spec. |
| 21 | Quick bar: pickers for history/exam/lab/dx/rx (1196) | shell.html:98-107 | ⚠ | Only «الفحص» opens a picker (`openExamPicker`). The other buttons only jump to their card (L101-105). |
| **Card anatomy** |
| 22 | Head icon/number + title + 🔗 shared / 🏷 spec / context tag (1124, 1128) | visit-card.component.html:25-38 | ✅ | |
| 23 | Layer tag + «مصدر الكارت» on the head (1128, §5) | card.html:40-43 | ⚠ | Shown only while customising (design review P1-2). A deliberate deviation from §5. |
| 24 | Collapsed shows `sum` (1128) | card.html:45 | ✅ | The body stays mounted (`[hidden]`, L55), so card state survives collapsing. |
| 25 | Size classes sz-S/M/L/F; F rows full width (1127, 1135) | visit-card.ts:53-65; shell.html:121-135 (`gridRows`) | ✅ | |
| 26 | Head = button with aria-expanded; private card aria-label (§10) | card.html:25 (div role=button, Enter/Space) | ⚠ | aria-expanded ✅. `CardDef.priv` is never read, so a private card has no restriction aria-label. |
| 27 | Customise toolbar (1126) | card.html:1-22 | ✅ | Required cards cannot be hidden. In RTL the column arrows are swapped. (The editor itself belongs to another slice.) |
| **Start rail** |
| 28 | Previous visits + «كل الزيارات» (1221) | visit-start-rail.component.ts:26-42 | ✅ | Last 3 (the current one excluded). The link goes to route `patients/:id/visits` (clinic.routes.ts:297). |
| 29 | Current meds with dose (1222) | start-rail:44-51; BE PatientFactsReader::meds L426-443 | ⚠ | Comes from active prescription items ✅. Only the drug name shows; the BE sends `sentence`, which is not rendered. |
| 30 | Latest results, red/green by flag, date tag (1223) | start-rail:53-65; BE PatientReadService::lastLabsList L245-262 | ⚠ | Every result is green (see Bug B2). No date or unit is shown. |
| 31 | Timeline sparkline in the rail (1224) | — (moved to a collapsed `timeline` card, card-catalog.ts:109) | ⚠ | Relocated, not missing. |
| 32 | Rail collapse / tablet drawer (1216-1220) | shell.html:39-60; scss:141-150; shell.ts:909-913 | ✅ | |
| **End rail** |
| 33 | Live summary: ✓ for a completed station, else number + title (1230) | visit-summary-rail.component.ts:24-32; shell.ts:440-452 | ⚠ | ✓ logic ✅, and clicking a line jumps to the card. The rail numbers every card while card heads number stations only, so they drift (general: followup is «11» in the rail, «10» on its card). In flow, lab/rx/vitals text refreshes only on «تم» or finish. |
| 34 | Orders & bill tag: immediate «محاسبة هنا» / intent «للاستقبال» (1231) | summary-rail:36-42 | ✅ | Hosts the real `app-clinic-service-order-rail` (the proto used a computed bill). |
| 35 | Inpatient «حساب الإقامة» (1231) | summary-rail:37 (always `ORDERS_BILL`) | ❌ | No stay-bill label and no stay charges: the rail always shows the encounter's `service_order_id`. |
| **Finish panel** |
| 36 | Soft warnings: key stations not done (1204) | shell.ts:464-468 (KEY_STATIONS L82) | ✅ | |
| 37 | «الشكوى الرئيسية فاضية» (1205) | shell.ts:469 | ✅ | |
| 38 | «لا طلبات — متأكد؟» (1206) | shell.ts:470-472 | ✅ | Also counts the prescription (a stricter wording). |
| 39 | Insured + dx not on screen → «add it from the catalog or record it from a dialog» (1209) | shell.ts:476; i18n `DX_NOT_ON_SCREEN` | ⚠ | Only «تخصيص الشاشة» is offered; there is no diagnosis dialog. For general the sign is hard-blocked while the dx card is off the screen (Bug B4). |
| 40 | Insured + dx on screen = soft «مطلوب للمطالبة (E11.9 ✓)» (1209) | shell.ts:483-489; finish-panel:21-29 | ⚠ | A hard block (⛔, sign disabled) instead of a soft line. Acceptable because BE `EncounterService` L248 returns 422 anyway. The panel title still says «لا تمنع التوقيع». |
| 41 | Extra warnings (undecided intents, vitals danger, spec cards) | shell.ts:473-480 | ✅ | Beyond the proto; useful. |
| 42 | «✍ توقيع وإقفال» (1210) | finish-panel:24-26 → shell.ts:840-864 | ✅ | Real `encounters.sign`; refreshes the header snapshot; checks permission `clinic.encounters.sign`. |
| 43 | «توقيع + طباعة الروشتة والطلبات» (1210) | finish-panel:27-29; shell.ts:857 | ⚠ | Prints the visit summary (`printSummary`), not the prescription + orders. |
| 44 | «رجوع» (1210) | finish-panel:34 | ✅ | |
| **Signed state** |
| 45 | 🔒 bar «موقّعة من … · time — BE lock» (1185, 15_signed.png) | shell.html:114-119, 188-189; header pill L26 | ✅ | The signer's name is not shown, only the time. Lock comes from the BE `is_locked`. |
| 46 | All cards collapsed to summaries, read-only (1121; screenshot) | shell.ts:611 (locked → closed); `[locked]` passed to every host | ⚠ | Works, but history, documents and previous show blank heads (proto showed a summary for each). |
| 47 | + Addendum, print summary, referral (1185) | shell.html:190-196, 137-163; shell.ts:866-884 | ✅ | Amendments log is listed; permission `clinic.encounters.amend`. Cashier also stays available. |
| 48 | Header after sign = what was seen at sign | alert-strip:69-72, 95-99 | ✅ | Snapshot with a «الحالي» toggle (beyond the proto). |
| **Footer** |
| 49 | Progress dots + "n/N محطات · حفظ تلقائي" (1186) | shell.html:200-204 | ✅ | |
| 50 | ☰ السجل · طباعة · 💵 كاشير · ✍ إنهاء Alt+F (1187) | shell.html:209-214 | ✅ | Cashier = `sendOrderToCashier` → /clinic/cashier, disabled with no order. «⛔ إيقاف التعامل» belongs to the nutrition slice. |
| **Intake** |
| 51 | First-visit mode replaces the main area with the wizard; «تخطّي» (1178, 1184) | intake/intake-launcher.service.ts (body-level dialog) | ⚠ | Wizard = dialog opened from the badge or registration form. The shell never switches into first-visit mode by itself. `clinic.intake.enabled` defaults false (📝). |
| 52 | Step 1 shared (identity + 7 Qs + allergies) → step 2 specialty pack; refs from layer 1 not re-asked (1235-1242) | intake/intake-wizard.component.ts:54-110, 237 | ✅ | Steps come from the BE payload, and the fill role locks a step (`STEP_LOCKED` L79-81). |
| 53 | "Who fills it" per step (1235, 1239) | wizard:79-81 (`fill_role`) | 📝 | Known: the packs lack in_intake/fill_role. |
| 54 | `intake` card in the catalog (866) | — | ❌ | No intake card; the header badge replaces it. Low impact. |
| **Shared card bodies** |
| 55 | vitals: full / compact / readonly modes, specialty and context packs, computed BMI (803-811) | shell.html:243-247 → encounter/vitals-panel (bmi computed L173) | ⚠ | Real panel, autosaves ✅, BMI computed ✅. No `mode` input (compact/readonly): readonly = `locked` or `patientScopeOnly`. 📝 for the nutrition readonly use. |
| 56 | complaint: catalog picker + ★ quick + free text; text-only rule (813-820) | VS/cards/visit-complaint-card.component.ts:33-69, 101-104, 121-191 | ⚠ | Saves (link rows + lookup_ids; note debounced 800 ms) ✅. Missing: duration and HPI fields. A note typed less than 800 ms before signing is dropped (Bug B5). |
| 57 | history: patientScopeOnly prop (821-828) | shell.html:254-257 → history-panel.component.ts:128 | ✅ | The real panel; the prop hides the encounter questionnaire, as in the proto. No collapsed summary (row 13). |
| 58 | exam: regions × catalog, per-region Select⟷Text, note (829-837) | cards/visit-exam-card.component.ts:49-90, 152-153, 228 | ✅ | `PUT encounters/{id}/findings`, debounced. |
| 59 | diagnosis: ICD picker, primary/secondary (838-845) | shell.html:263-266 → diagnosis-panel | ⚠ | Real panel, saves. The «Diagnosis = Text» client mode is not supported (always ICD; the picker slice owns this). |
| 60 | lab: immediate vs intent tag, catalog mode (846) | shell.html:267-271 → lab-orders (orderingMode/labMode inputs) | ✅ | Refreshes the summary and encounter on `intentsChanged`. |
| 61 | rad (847) | shell.html:272-276 → rad-orders | ✅ | |
| 62 | rx: sentence, duplicate, inpatient kind with times (848-857) | shell.html:277-287 → prescription / inpatient-rx-composer | ✅ | `(saved)` from app-clinic-prescription is not bound, so the rx summary goes stale until «تم» or finish. |
| 63 | documents: sick leave / report / certificate / referral + attachments (858) | cards/visit-documents-card.component.ts:21-26 | ✅ | Templated documents via `clinical-documents-list` «+» (`clinic.documents.create`), the Core attachments API, print and referral. No collapsed summary. |
| 64 | previous (859) | shell.html:291-294 → previous-visits (rxRepeated) | ✅ | «تكرار الروشتة» reloads the rx panel. |
| 65 | meds card (860) | `home_meds` card (shell.html:334-337) | ⚠ | Patient-facts home meds, not "from prescriptions". There is no `meds` catalog id. |
| 66 | results card (861) | — | ❌ | No shared `results` card (rail only). |
| 67 | notes (862) | shell.html:319-324 → patient-clinical-notes-card | ✅ | Real `clinical_notes`. |
| 68 | photos (863) | — | ❌ | No shared photos card. Proto MAPS L1280 expects it in the catalog; the nutrition copy is 📝 S2b. |
| 69 | followup: N days, date, referral, instructions (864-865) | cards/visit-followup-card.component.ts:18-32 | ⚠ | Revisit window + follow-up tasks + referral ✅. «احجز متابعة» leaves the visit for /clinic/booking (shell.ts:900-907). No inline date or instructions. |
| **Responsive / RTL** |
| 70 | Phone: one column + sticky mobsum "ملخص … · فاتورة X ج" (1182) | shell.html:199; shell.ts:453-455; scss:165-193 | ⚠ | Strip ✅ (phone only), but it has no bill total. On a phone the end rail, including orders and bill, is hidden entirely (`.rail.e, .rail.col {display:none}` scss:167). |
| 71 | Tablet: two panes, third column drops, start rail as drawer (§4) | scss:141-161 + ResizeObserver narrow/phone (shell.ts:90-91, 558-571) | ✅ | Measures the frame, not the viewport (better). |
| 72 | Touch targets ≥44px (§10) | scss:147, 158 (`.btn {height:32px}`), 184 (36px) | ⚠ | Only the rail toggles reach 44px. |
| 73 | RTL first, numbers/codes ltr (§10) | `.en` direction:ltr (_clinical-ui.scss:89, header scss:10); card.html:8-9 | ✅ | |
| **Classic / redirect** |
| 74 | Shell when the switch is on, `?classic=1` opts out | encounter/consultation.component.ts:447-456 | ✅ | general and gyn_ob follow the global flag; other specialties also need `features.shell`; inpatient/day-case always go to the shell. |
| 75 | Classic link from the shell; unsupported type falls back | shell.html:31-34, 230 | ✅ | |
| 76 | Shell route gated by the switch | clinic.routes.ts:350-355; shell.ts:204 (`supported` = has a default layout) | ⚠ | `/clinic/encounter/:id/shell` opens even with `clinic.visit_shell` off; `visit_shell_enabled` is never read in the shell. Minor. |

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

- **B1 — Insurance name never shows, always «نقدي».** `VS/visit-header.component.ts:106` reads `summary().insurance.company_name`. BE `PatientReadService.php:91` returns `$patient->insurance_info` as-is, which LIS writes as an array `[{contract_id, contract_name}]` (FE `features/lis/patients/lis-patients.component.ts:533-536`; `services/clinic-patient.service.ts:220-221`). An array has no `.company_name`. The FE type `ClinicPatientInsurance {company_name, coverage_percent}` (clinic-patient.service.ts:69-73) describes a payload the BE never sends. The same defect is in the classic `shared/clinic-patient-header/clinic-patient-header.component.html:97`, so fix it once in the shared layer (either the BE maps `insurance` → `{company_name: insurance_info[0].contract_name}`, or the FE reads `[0].contract_name`). Dev has 0 insured patients, so a live check won't catch it. The finish gate is not affected (it uses `encounter.is_insured`).
- **B2 — «آخر النتائج» never flags abnormal values.** `visit-start-rail.component.ts:58-59` colours by `r.flag`, which is declared optional in `ClinicPatientLabResult` (clinic-patient.service.ts:75-79). BE `lastLabsList()` (PatientReadService.php:256-260) returns only `{name, value, unit}`, so every result renders green (HbA1c 7.4 looks normal). Clinically misleading. Fix: the BE sends the result flag/abnormal status (and the unit and date).
- **B3 — FE and BE sign gates use slightly different rules.** FE `finishBlocked` (shell.ts:488) accepts any `is_primary`. BE `DiagnosisService::blockSignForInsured` (L124-131) also requires `code IS NOT NULL`. The shell's diagnosis panel always saves ICD codes (diagnosis-panel.component.ts:158), so only legacy or uncoded primary rows reach the BE 422 (English message toast). Low severity.
- **B4 — Possible dead end for insured patients.** For `general`, `finishBlocked` (shell.ts:483-489) blocks even when the client layout removed the diagnosis card (the type-specific exemption at L487 applies only to non-general types). The doctor sees ⛔ + «أضيفه من تخصيص الشاشة», but without `can_manage` / allow_doctor_override there is no customise button (shell.ts:291-294), so the doctor must go to the classic screen. The reverse case (nutrition/dental, insured, no dx card) lets the doctor press sign, and the BE returns 422 anyway (EncounterService.php:248). The FE «soft» promise there is false; it is the S2 Decision 5 territory, so it needs an owner call.
- **B5 — The complaint note can be lost on fast sign or navigation.** `visit-complaint-card.component.ts:114` debounces 800 ms. `ngOnDestroy` (L117-119) unsubscribes without flushing, and `saveNote` returns early once `locked` (L183). Text typed less than 800 ms before «توقيع» or leaving the page is never saved. The exam card has the same debounce pattern (L133); worth checking.
- **B6 — Cosmetic numbering mismatch.** The live-summary rail numbers every non-bare card (`summary-rail:28`, `i + 1` over `summaryLines`), while card heads number stations only (`stationIndex`). In the general default the collapsed `timeline` shifts the rail numbers by one for followup.

## 3. Top 5 gaps by owner impact

1. **B1:** the insurance name in the header (shell and classic) always reads «نقدي». It contradicts the insured sign gate and is visible on every visit.
2. **B2:** latest results are all green, so abnormal values are not flagged in the start rail (plus no date or unit).
3. **Inpatient:** no «حساب الإقامة» bill in the end rail (it shows the encounter's service order) and no admitting doctor in the header.
4. **Collapsed and signed read-back:** history, documents and previous show blank summary lines, and the rx/vitals/lab lines refresh only on «تم». The signed visit (15_signed.png) therefore reads thinner than the proto.
5. **Phone:** orders and bill are unreachable (end rail hidden, mobsum has no bill total); tablet touch targets are 32px, not 44px. The missing Rh-negative alert chip comes next.
