Visit-shell prototype vs implementation · 2026-10-23 · moonui3 · مراجعة للكود بس، من غير أي تعديل + صور من الشاشة الحية على /app · المرجع: knowledge-base/plans/clinical-platform/visit-shell-prototype.html
قشرة الزيارة نفسها مطابقة للنموذج: الهيدر والتنبيهات، والمحطات بالكيبورد، والملخص الحي، والطلبات والفاتورة، والتوقيع والتحذيرات، والكروت المشتركة، والنسا، والأسنان، وسياق الإقامة، ومحرك ترتيب الشكل (افتراضي ← عميل ← دكتور ← سياق). الناقص الأكبر شاشة «إعدادات الشكل» بتاعة العميل (تعميم Program Setting): مش موجودة كواجهة. وفيه 6 أخطاء حقيقية لازم تتصلح قبل أي عميل، منها اسم التأمين اللي بيظهر دايماً «نقدي».
219 بند اتراجعوا على 4 أجزاء. كل بند عليه رقم السطر في الكود (التفاصيل الكاملة في الملحق تحت). بنود الـ ⚠ أغلبها فروق صغيرة أو قرار تصميم، مش أعطال.











insurance.company_name، والبيانات متخزنة [{contract_id, contract_name}]. يتصلح مرة واحدة في الطبقة المشتركة. (visit-header.component.ts:105، clinic-patient-header.component.html:97) — اتأكدت منه بنفسي.visit-start-rail.component.ts:58 ← PatientReadService.php:256)inpatient.context.ts:65)clinic-vitals.service.ts:30) — اتأكدت منه.أصغر من كده: ترقيم الملخص الحي مش زي ترقيم المحطات، كارت فك الأسنان مش «إلزامي»، عداد الكروت في وضع التخصيص بيعد التبويب المفتوح بس، مفتاح Select⟷Text في الفحص متربط بـ id من قاعدة البيانات بدل الكود (مش هينفع يتنقل من عميل لعميل)، وإضافة قيمة جديدة مش بتتحقق من parent_id ولا بتسجل التخصص.
/clinic/settings (مفتاح القشرة + سماح الدكتور + مزايا الأسنان).clinic.history.manage، ودي مش عند دور «طبيب» العادي، فأغلب الدكاترة مش هيشوفوا «+ إضافة».| WP | المحتوى | الحجم |
|---|---|---|
| R1 أخطاء | اسم التأمين (مشترك للهيدرين)، وعلامات النتائج + التاريخ والوحدة، وحفظ الملاحظات قبل التوقيع والخروج، وكروت الخروج المكررة في النسا والإقامة، والترقيم، وكارت الفك إلزامي، وعداد التخصيص | صغير · FE+BE |
| R2 الكتالوج | seed للشكاوى العامة + بنود الفحص بالمناطق، ومفتاح Select⟷Text بالكود بدل id، والتحقق من parent_id وتسجيل التخصص عند الإضافة، وتوحيد الكاش | صغير–متوسط · BE |
| R3 إعدادات الشكل | شاشة العميل «إعدادات الشكل»: قواعد الحقول (إظهار/إلزام/Select⟷Text)، والعناصر الـ 8 (وتوصيل الـ 6 الناقصين)، ومزايا النسا، ومفتاح القشرة لكل تخصص، وسماح الدكتور | متوسط–كبير · FE (+BE بسيط) |
| R4 العلامات الحيوية | كارت العلامات يعرض باقة التخصص والسياق (الخصر/الورك/WHR، وقياسات الحمل، والتنفس/الوعي/الألم/O₂) + خانة سوائل داخل/خارج في الجولة | متوسط · FE+BE |
| R5 الإقامة | الطبيب المعالج ومدة الإقامة في الهيدر، و«حساب الإقامة» مكان الفاتورة، وتنبيه Rh سالب | متوسط |
| R6 الاختيار | التشخيص بمفضلة ★ ومجموعات، وشاشة إدارة الكتالوج، وملخص سطر للكروت المطوية، وحالة المحطات على السيرفر، والفاتورة على الموبايل، وأزرار 44px | متوسط–كبير |
Full per-item verdict tables (English, with file:line evidence), one per review slice.
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:
/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:
{visible, required, input};gyn.feature.*;features.shell switch.| # | 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. |
No hard defects in the resolve/persist path. Minor misbehaviours:
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).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.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.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.features.shell switch can only be set by API/DB. A real centre cannot lock Urogyn/Sexual or turn on the specialty shells itself.Paths: FE = moon-erp/src/app/features/clinic/, VS = FE/visit-shell/, BE = moon-erp-be/Modules/Clinic/app/. Proto = visit-shell-prototype.html.
| # | 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. |
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).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).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.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.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.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.Paths: BE = /home/moonui3/moon-erp-be/Modules, FE = /home/moonui3/public_html/moon-erp/src/app. Picker = FE shared/components/clinical-picker/.
| # | prototype item (proto line) | implementation file:line | verdict | note |
|---|---|---|---|---|
| 1 | ONE catalog clinical_lookups {lookup_key,label,group,display_order,specialty,is_active} (587-592) | BE Clinic/database/migrations/2026_11_03_100006_create_clinical_lookups_tables.php:21-39 | ✅ as spec | Group = self-ref parent_id (category row → children); value_ar/value_en; company-scoped; soft-deletes. |
| 2 | Field definitions reference {lookup_key, group} (591, 1273) | BE …/2026_11_03_100005_add_engine_v2_columns_to_history_questions.php:38-39; HistoryQuestionResource.php:44 | ⚠ partial | history_questions.lookup_key only, no group. Real users: 279 gyn multi + 27 gyn single (DB). General shell fields have no definitions: complaint/exam hard-code the key in the card. |
| 3 | Multi-select = link rows, never CSV (588-590) | complaint: encounter_complaints + lookup_id (…280002:17-20); exam: encounter_findings (…280000:20-33); dx: encounter_diagnoses | ⚠ partial | Complaint/exam/dx are real link rows. Every multi_select history field (all 279 GYN chip fields, incl. categories gyn_cat_<g>) is a JSON code array in answer_value/value_json (HistoryService.php:411-418, 657-664). Not CSV, but not link rows either. |
| 4 | Per-doctor favourites FAVS[doctor][key] (644-651) | BE clinical_lookup_favorites(company,user,lookup_key,lookup_id) migration :41-55; LookupCatalogService.php:28-59,112-133; route api.php:476 | ✅ as spec | Real and persisted per user. The list endpoint sorts ★ first. Un-favourite does forceDelete. Dev DB has 0 favourites so far. |
| 5 | Favourites for lab/rad/drug | BE clinic_favorites (2026_07_08_000010), FavoriteController.php:27-33; FE services/clinic-favorite.service.ts | ✅ (2nd store) | A separate per-doctor store for lis_investigation / lis_package / rad_procedure / clinic_service / drug. There is no ICD/diagnosis type. |
| 6 | Inline «+ إضافة جديد» writes to catalog with lookups.manage (778, 796-797) | BE api.php:475 permission:clinic.history.manage; StoreLookupRequest.php:84; LookupCatalogService.php:67-105; FE picker-box.ts:160 | 📝 deferred (known) | Real: it persists, is idempotent on value_ar and is permission-gated, both FE and BE. But the gate is the broad clinic.history.manage. On dev, roles doctor and gyn_doctor do NOT have it (only owner / طبيب), so most doctors never see the add row. |
| 7 | On card: ONLY selected chips + up to 4 ★ quick + «+ اختيار / بحث» (780-784) | FE clinical-picker-box.component.ts:77-99,162-171 | ✅ as spec | Chip click removes it; quick-favs exclude categories and already-selected values; capped at 4. |
| 8 | Focused picker: search, ★ block on top, grouped list, category pills, multi/single, footer «المختار (n)», Done (772-779, 759-771) | FE clinical-picker-dialog.component.html:251-394; .ts:86-121 | ✅ as spec | Search matches ar + en. A ★ row also appears again in its group, as in the prototype. |
| 9 | Investigations = category grid by display_order, open ONE category (763-766) | dialog.html:307-327 (grid input) | ⚠ partial | Grid mode exists in the shared picker, but NO caller passes [grid]=true. Lab/rad use their own legacy pickers (rows 16-17). |
| 10 | Keyboard: Enter picks first hit, Esc closes (776) | dialog.ts:221-241; rows tabindex + Enter/Space (html:397-398) | ✅ as spec | No arrow-key navigation (the prototype had none either). |
| 11 | Mobile full-screen sheet (screenshot 14) | dialog.scss:87-89 (max-width:640px → 100vh, no radius) | ✅ as spec | Gyn option picker: p-dialog breakpoint 640px → 100vw (gyn-option-picker.component.ts:41). |
| 12 | Complaint uses picker, specialty-scoped, multi → link rows (741, 814-818) | FE visit-shell/cards/visit-complaint-card.component.ts:41-50,121-139; shell html:249-250 | ⚠ partial | The mechanics are real (lookup → clinic_complaints by name + lookup_id). But dev company 1 has NO general complaint rows: only 20 complaint/gyn_ob (DB). In a general visit the picker is empty and there is no seeder (see Bug 1). |
| 13 | Exam: ONE catalog exam.finding, regions = categories, a box per region + full picker behind the head button (743, 830-832) | FE visit-exam-card.component.ts:48-86,147-158; BE EncounterFindingService LOOKUP_KEY | ⚠ partial | The code is right. Dev has 0 exam.finding rows and nothing seeds them, so the card falls back to one flat empty box with no regions (see Bug 1). |
| 14 | History questions picker history.question (742) | FE encounter/history-panel (legacy chips from history_answer_options); HistoryQuestionResource.php:48-54 | ❌ missing | The general «التاريخ» card is the old panel: open chips per question, no catalog, no ★, no inline add. |
| 15 | Diagnosis: picker with ★, group pills, inline add, first = primary (744, 839-844; screenshot 06) | FE encounter/diagnosis-panel/diagnosis-panel.component.html:42-52 (app-pick-multiselect, minChars 2); ts:64-68 | ⚠ partial | Search-as-you-type ICD autocomplete with magic-add (POST icd10, perm clinic.diagnosis.record). No favourites, no group browse, no picker behind a button. Primary/secondary is set with buttons. |
| 16 | Lab orders: grid picker behind a button, ★, add-new (745, 846; screenshots 02/03) | FE encounter/orders/lab-orders.component.html:3-200; .ts:181-215,501-530 | ⚠ partial | ★ is real (clinic_favorites) and there is a search ⟷ grid toggle. But the whole picker + basket is spread on the card, not behind a button. Add-to-catalog only in standalone mode, with no FE perm check. |
| 17 | Rad orders picker (746) | FE encounter/orders/rad-orders.component.html:13-19; .ts:164-206 | ⚠ partial | Same as lab: inline catalog-grid-picker with real ★; createFn only in standalone mode. |
| 18 | Rx: drug + default sentence auto-filled and editable, Duplicate (747; screenshot 04) | FE encounter/prescription/prescription.component.ts:383-410,462-468,495-499,531-549,242 | ✅ as spec | Search behind a toggle; presets/sentences; duplicate; save typed sentence as preset; quick-add drug gated by perms. ★ favourites only in products pharmacy mode (:223-231): standalone_formulary has only most-used. |
| 19 | Gyn categories picker, noAdd, ★ (748-750; screenshot 10) | FE gyn/cards/gyn-category-picker.component.ts:52-56,95-160 + gyn/form/gyn-option-picker.component.ts | ✅ as spec | Feature-filtered groups; sexual group needs gyn.sexual.view; ★ persisted through clinical_lookups; no add (as in the prototype). Stored as answer gyn_cat_<group> (JSON). |
| 20 | Gyn module chips (PK_DYN/gchips) e.g. VTE (751; screenshot 11) | FE gyn/form/gyn-lookup-field.component.ts:29-45,74-84; gyn-module-form.component.html:42,74 | ✅ as spec | Uses the shared box/dialog with specialty=gyn_ob. Lists of ≤6 (multi) or ≤7 (single) render open; gyn_vte_pre etc. have lookup_key (DB). |
| 21 | Nutrition dialog investigations picker (screenshot 22) | FE nutrition/visit/nutrition-consult-board.component.html:654-693 (p-dialog → app-clinic-lab-orders) | 📝 deferred (S2b) | The classic board hosts the same lab panel in a dialog: ★ is real, but no category-grid-first view as in the screenshot. |
| 22 | Per-client per-field input Select⟷Text (INPUT_FIELDS 654, rule/inputType 734-735) | BE VisitLayoutController.php:42-46,60-61 stores fields.*.input; FE complaint-card.ts:101-104, exam-card.ts:152-158 | ⚠ partial | The renderer honours it for chief_complaint and exam_finding_<regionDbId> only. Diagnosis rule is ignored. No UI ever writes fields: the only saveClient call (visit-shell.component.ts:782) sends layout only. The prototype's settings rows (proto 1248) are not built. |
| 23 | Doctor may override field rules/elements when the client allows it (734, 736) | visit-layout.service.ts:42-44 (doctor level = layout only) | ❌ missing | Doctor override covers the layout only, never fields or elements. |
| 24 | ELEMENTS show/hide (653, elem 736) | complaint-card.ts:102,104 (chief_complaint_note, complaint_quick) | ⚠ partial | Only 2 of the 8 are honoured. hdr.insurance / hdr.lastVisit / hdr.doctor / complaint.hpi / lab.prev / rx.interact / dx.secondary: no code reads them, and there is no settings UI. |
| 25 | Required marker per client rule (711) | complaint-card.ts:103 | ⚠ partial | Only on the complaint text-only mode. There is no generic required-field rendering from fields. |
| 26 | Computed BMI read-only (714) | BE VitalsService.php:70,102,135 (client value unset, server computes); FE vitals-panel.component.html:67-78 | ✅ as spec | Shown read-only with an abnormal flag. |
| 27 | Computed WHR (715) | BE Nutrition/app/Services/Visit/ConsultBoardReader.php:94-95; FE nutrition-consult-board.html:157-160 | ✅ as spec | Read-only, with a risk pill. |
| 28 | Computed GA from LMP (716) | BE Clinic/app/Support/ComputedRegistry.php:150-195 (edd/gaDays/ga); Gyn/app/Support/GynComputed.php:58,135-152; FE gyn/engine/computed.ts:333 | ✅ as spec | Saved with source=derived (HistoryService recomputeDerived). |
| 29 | Computed NEWS2 (717) | ComputedRegistry.php:215-253 (per-parameter bands); FE hospital/nursing/vital-round.component.html:4 | ✅ (better) | Real RCP bands instead of the prototype toy. Shown on hospital rounds and as a computed history field. Per-parameter display is 📝 deferred (known). |
| 30 | Managed catalog admin (legacy investcats SHOW/EDIT/DELETE; «كتالوج مُدار») | BE api.php:474-476 (index/store/favorite only) | ❌ missing | No update / rename / deactivate / reorder / delete endpoint or screen, so a mistyped inline-add stays forever. |
| 31 | Favourites shared across sibling pickers live | clinical-lookup.service.ts:45-46,88-98 (changed signal + in-place cache patch) | ✅ as spec | Cache is per key+specialty (see Bug 4). |
complaint with specialty NULL/general and 0 rows of exam.finding (SELECT on clinical_lookups). Only complaint/gyn_ob (20) comes from gyn:install-pack. The complaint card asks for specialty='general' (visit-shell.component.html:250 → LookupCatalogService.php:41-44), so it gets []. The exam card then drops to the "no regions" fallback (exam-card.ts:79-86). No seeder or migration in Modules/* inserts either key (grep). So on general visits the two flagship pick-don't-type cards show nothing to pick, and roles without clinic.history.manage (dev: doctor, gyn_doctor) cannot even add values.exam_finding_<parent row id>, exam-card.ts:153,157). A rule cannot be shipped in a default or copied between installs, and it breaks if a region is re-created. The prototype used stable keys (exam_general, exam_chest …). clinical_lookups.code exists (D2a) and should be the key.parent_id (StoreLookupRequest.php:95: nullable|integer). The id is not checked to belong to the same company or lookup_key, so a value can be filed under another key's or tenant's category row. Its specialty is also not checked against known types, and the FE never sends it (clinical-lookup.service.ts:26-32). So every inline-added GYN complaint becomes core (visible to all specialties).key|specialty cache entry they were called with (clinical-lookup.service.ts:79-81,92-94). A value starred or added under gyn_ob does not show as starred or present in the same key's general cache until reload. Minor.changed bump. On error, catchError deletes the cache and never sets rows (service :63-66). Each box's effect (picker-box.ts:174-183) then calls list() again on every add or ★ anywhere. This is a perf leak for users without clinic.history.view.canAdd is true (gyn-lookup-field.component.ts:37,84), so a doctor without clinic.history.manage never reaches the ★ for lists of ≤6/≤7.complaint and exam.finding (with regions), so the general visit's pick-don't-type cards are empty out of the box (Bug 1).clinic.history.manage, which the stock doctor / gyn_doctor roles lack, and there is no catalog admin screen to fix or reorder or deactivate values. "Managed catalog" is half-done.Decision: favourites and add-new are REAL, not cosmetic. Favourites are persisted per doctor in clinical_lookup_favorites (lookups) and clinic_favorites (lab / rad / drug / service); the server sorts them first and they are scoped by company and user. Add-new is persisted, idempotent, and permission-checked on BE (clinic.history.manage) and FE. Their usefulness on general visits is limited by the empty seed catalog and by the permission most doctor roles lack.
Paths: FE = /home/moonui3/public_html/moon-erp/src/app/features/, VS = FE/clinic/visit-shell/, BE = /home/moonui3/moon-erp-be/Modules/. Proto = visit-shell-prototype.html.
| # | prototype item (proto line) | implementation file:line | verdict | note |
|---|---|---|---|---|
| NUTRITION | ||||
| N1 | 14 board cards as separate catalog cards, 3-col default (448-461, 494-502) | VS/specialties/nutrition.cards.ts:28-72 (nut_measure + nut_board, 2 tabs, hideRail) | 📝 deferred (S2b) | One bare card hosts the unchanged app-nutrition-consult-board. You can't move, hide or resize single cards, so the proto's «عيادة د. منى تشيل 3 كروت» example can't be done. |
| N2 | nut_patient (+ readonly vitals, ideal/required/water/fromFirst/outstanding/points/WHR/BMI) (868-871) | FE/nutrition/visit/nutrition-consult-board.component.html:91-170 (finance 143-150, loyalty 151, WHR+risk 157-160, vitals 165) | ✅ as spec | Reachable inside the board. |
| N3 | nut_quest yes/no + ✎ (872) | board html:218-223 (+ app-nv-questionnaire-dialog 614) | ✅ as spec | |
| N4 | nut_lifestyle (873) | board html:244 app-nv-lifestyle-card | ✅ as spec | |
| N5 | nut_appts + contact log (874) | board html:247-300 (📞 log contact 252, + book 255, last contact 300) | ✅ as spec | |
| N6 | nut_weights (+ وزن, InBody-due bar, sparkline, adherence ✎) (875-879) | board html:326-430 (InBody overdue 336-344, spark 347-361, adherence 408-429) | ✅ as spec | |
| N7 | nut_meas last-5 table incl. waist/hip (880) | board html:444-494 (waist 479, hip 480, WHR 481) | ✅ as spec | |
| N8 | nut_sessions (881) | board html:503 app-nv-sessions-card | ✅ as spec | |
| N9 | nut_plan (✎ / shopping / choose) (882) | board html:505-533 | ✅ as spec | |
| N10 | nut_notes / nut_meds / nut_labs (pending + external result) / nut_photos (883-887) | board html:543 / 550 / 558 (labs-card.component.ts:37-41 pending, 117 external dialog) / 598 | ✅ as spec | |
| N11 | nut_packages incl. «المنتجات المباعة» (886) | board html:569-597 | ⚠ partial | Packages and remaining count are there. The products-sold sub-table isn't, anywhere on the board or in its model. |
| N12 | nut_progress (888) | board html:324 app-nv-progress-card | ✅ as spec | Conditional, as S2a's comment says. |
| N13 | Header actions rx/lab/rad/nut_plan_dialog/print_plan/note/next_appt/stop (497) | board html:3-35 (rx 5, lab+rad dialog 8 & 668-669, plan 11, print 14, note 17, next 20, stop 32) | ✅ as spec | Rad is a tab of the lab dialog, not a separate button. |
| N14 | Measurements tab + service-order rail | VS/specialties/nutrition-measure-card.component.ts:161-175 | ✅ as spec | Hosts the classic tab with the order rail at 358px. |
| N15 | vitalsPack waist/hip/whr in the vitals card (495, 809) | BE Clinic/database/seeders/ClinicObservationDefinitionSeeder.php:88-89 (nut_waist/nut_hip defs) vs FE/clinic/services/clinic-vitals.service.ts:30-39 (VITALS_WHITELIST, 8 core) + vitals-panel.component.ts:215-217 | ⚠ partial | The specialty definitions are seeded but the vitals card only renders the 8 core fields, and there's no nut_whr. The nutrition layout has no vitals card anyway. Waist/hip/WHR can be reached through body comp (N7) and WHR (N2). |
| N16 | intakePack nut_* (496) | BE Clinic/app/Clinical/IntakeRegistry.php:59-67 | ⚠ partial | nut_goal, nut_goal_weight and nut_activity_level aren't intake codes; the goal lives in the nutrition patient profile. Other codes are renamed (naming drift). |
| DENTAL | ||||
| D1 | dent_chart workspace F, required:true (463, 511) | VS/specialties/dental.cards.ts:23-32 (dent_workspace, tab «مخطط الأسنان») | ⚠ partial | Hosted unchanged, but it isn't required, so a client layout could drop it. That can't happen today because Decision 7 locks saves. |
| D2 | Odontogram FDI/Universal, permanent/primary, filter, legend (950-953, 919-923) | FE/dental/workspace/dental-workspace.component.html:36-50 (filter, dentition, notation) + 107-119 (odontogram, legend incl. note/xr) | ✅ as spec | Notation default comes from dental.notation (ts:229). |
| D3 | Side panel (surfaces, proc/cond palettes, status, sections, lab, notes, imaging) (924-944) | workspace ts:93-109 (surfaces/palette/procStatus/mobility/note/image) + html 107ff | ✅ as spec | Surfaces are gated by features.surfaces (ts:408), note templates by note_templates (ts:570). |
| D4 | Sub-views: chart/plan/perio/fmx/ortho/lab/recall tabs + quick mode (981-984, 947) | workspace html:12-27 (chart/perio/ortho/xray tabs), 71 simple mode, 96-101 views, 300 plan panel, 315 recall, 355 lab-case dialog | ⚠ partial | Matches the classic workspace. Plan sits under the chart, not as a tab. Lab board and recall are separate addable cards (dental.cards.ts:101-122), not workspace tabs. |
| D5 | dent_today (986) | dental.cards.ts:51-59 → consult/dental-today | ✅ as spec | |
| D6 | dent_exam: 12 items = 4 pain + 8 mouth (987-988) | FE/dental/consult/dental-exam.component.ts:10,41-44 | ✅ as spec | Driven by the dent_ pack (PAIN + swelling, mouth, notes). |
| D7 | historyPack dent_pain_* (504) | dental-exam.component.ts:10 | ✅ as spec | Lives in the exam card, not the complaint card (classic). |
| D8 | dent_plan / dent_fmx as cards (466, 468) | not in DENTAL_CARDS | 📝 deferred (S2c) | Both are still reachable inside the workspace (D4). |
| D9 | dent_perio / dent_ortho / dent_lab / dent_recall with feature (467-471) | dental.cards.ts:79-122 (feature perio/ortho/lab/recall + permission) | ✅ as spec | Catalog-only, not in the default. |
| D10 | settings mode/notation/features.* gate cards and tabs (508, 981) | BE Dental/database/seeders/DentalSettingDefinitionSeeder.php:25-34; VS/visit-shell.component.ts:176-183, 540-546; workspace html:12-27, 96-101 | ✅ as spec | Set in the clinic settings screen (group «الأسنان»). While meta loads, or if it fails, no lock is applied (documented). |
| D11 | Latex alert from dent_allergy_latex (507) | BE Clinic/app/Support/ClinicProfilePacks.php:199 (storage=alert, alert_code latex); PatientFactsReader.php:60 | ✅ as spec | Shows as a shared header alert pill. |
| D12 | intakePack + intakeRefs (shared refs not re-asked) (505-506) | BE IntakeRegistry.php:69-75 | ⚠ partial | The pack also has dent_anticoagulant and dent_allergy_penicillin, which overlap the shared anticoagulant/allergies. They route into the same alert/condition store, so the data is one source, but the question is asked twice. |
| D13 | Default: complaint(shared)/today/exam/diagnosis/rx + collapsed vitals, history(patientScopeOnly), previous; actions lab/rad (510-514) | dental.cards.ts:126-151; dental-more-card.component.ts:26-41 (vitals / history [patientScopeOnly]="true" / previous toggles + lab/rad dialog) | ✅ as spec | Same behaviour delivered as «المزيد». Kept the dental complaint card, dent_alerts and hidden rails (📝 S2c). |
| GYN | ||||
| G1 | gyn_cats required, 4 group lines, chips + «+ اختيار» picker (473, 1087-1090) | FE/gyn/gyn-shell-cards.ts:39-43 (required, station); FE/gyn/cards/gyn-category-picker.component.ts:95-117 | ✅ as spec | Answers stored as gyn_cat_<group>. With no category, a soft warning shows at sign (:131-139). |
| G2 | Locked group struck through «مقفول عند العميل (gyn.feature.x)» (1088; 25_mona) | picker :48-51, 102; BE Gyn/app/Http/Controllers/GynSchemaController.php:130-142 | ✅ as spec | Without gyn.sexual.view the sex line is hidden entirely (stricter than proto). Fertility only hides infertility (GynFeatures.php:25-37, documented clinical deviation). |
| G3 | gyn_modules required host, show_if engine, first module open, «اتفتح من الاختيار» (474, 1062-1072) | gyn-shell-cards.ts:44-48; FE/gyn/state/gyn-visit.store.ts:117-129, 142-150, 168-170; FE/gyn/engine/show-if.ts | ✅ as spec | Feature-off modules are filtered server-side (GynModuleRegistry::visibleFor, SchemaController:30). Core modules are excluded (gyn.models.ts:363). |
| G4 | Any non-core module addable as gyn_<key> card carrying its feature (1099-1101; §3) | gyn-shell-cards.ts:155-174; VS/card-catalog.ts:59-68 (registerSpecCards) | ✅ as spec | Labour/vd/cs are folded into one gyn_delivery card. Private modules need gyn.sexual.view. |
| G5 | gyn_timeline (475) | VS/card-catalog.ts:36 shared timeline card in GYN default (gyn-shell-cards.ts:128); BE Clinic TimelineService | ✅ as spec | Renamed: a shared timeline of visits, episodes (cycles and pregnancies, G3a), procedures and documents. |
| G6 | gyn_summary, 10 types (476, 1001, 1094) | FE/gyn/cards/gyn-summary-card.component.ts:9-15, 41-46; BE Clinic/app/Clinical/ClinicalTypeRegistry.php:121-155 | ✅ as spec | Types are filtered by feature. Saves as a numbered PDF (C1c). |
| G7 | Private (Sexual) excluded from every summary type (1083; §3) | BE Clinic/app/Services/Clinical/ClinicalSummaryService.php:55-70; FE/clinic/clinical/summary-card.component.ts:45-50 | ⚠ partial | Excluded by default and never in Couple, but there's an opt-in «تضمين المحتوى الخاص» for users holding clinic.fields.private. The card note says so. |
| G8 | Default flow order (521-523) | gyn-shell-cards.ts:102-134 | ✅ as spec | Superset: adds first_visit, obhx, exam, imaging, operation, discharge, team, gyn_followup. Order kept. |
| G9 | vitalsPack lmp/ga/fundal/fhr/urine_alb (516) | BE ClinicObservationDefinitionSeeder.php:95-96 (fundal, fhr defs); FE/gyn/cards/gyn-antenatal-flowsheet.component.ts:33-55 (GA, BP, weight, urine, FHR, FH…) | ⚠ partial | Not in the vitals card (whitelist, see N15). It lives in the gyn_antenatal card and the pregnancy module (LMP), which are catalog cards or host-opened, not in the default. |
| G10 | historyPack gyn_bleeding / gyn_discharge (516) | gyn-shell-cards.ts:177-189 (complaint slot); schema complaint fields gyn_complaint_hmb_*, _irreg_pattern, _disch_char/_assoc | ✅ as spec | Each complaint opens its own questions. |
| G11 | intakePack marital/spouse/marriage yrs/children/GPA/menarche/contraception (517) | FE/gyn/cards/gyn-first-visit-card.component.ts:20-25, 104-118 (profile, spouse panel, menstrual); BE Gyn routes intake-status; GynExtensions.php:193-206 (GPAL, spouse) | ✅ as spec | Uses the profile fields' in_intake flags. IntakeRegistry gyn_ob is empty (IntakeRegistry.php:75). |
| G12 | Pregnancy alert pill «حامل — 24 أسبوع» rose (518) | FE/clinic/shared/patient-alert-strip/patient-alert-strip.component.ts:251-252, 313-320 | ⚠ partial | Shown as a GA/EDD fact chip at the head of the facts line, not a rose alert pill (Fable decision). High-risk/VTE become alert pills. |
| G13 | gyn.feature.* fertility/obstetrics/urogyn/sexual/oncology (519) | BE Gyn/app/Support/GynFeatures.php:18-61 (+operations) | ⚠ partial | Enforcement is complete (schema, categories, cards, summaries, write guard 422). But the switches live only in clinic.visit_layouts['gyn_ob'].features. There's no setting definition and no FE screen, and saveClient doesn't send features (visit-shell.component.ts:782). Only the API can lock Urogyn/Sexual. |
| G14 | Layout saving for gyn | VS/visit-shell.component.ts:269-272 | ✅ as spec | Open for gyn_ob (flow, no tabs), locked for nutrition/dental (📝 Decision 7). |
| INPATIENT CONTEXT | ||||
| I1 | addCards mar / nursing / discharge, F (529) | VS/contexts/inpatient.context.ts:42-68; inpatient.cards.ts | ✅ as spec | Split into inp_mar (L), inp_round (M), inp_nursing_notes (M, not for day case), inp_discharge. Each is permission-gated, and resolve-layout.ts:114-118 drops what the user can't use. |
| I2 | replace followup → discharge (529) | inpatient.context.ts:65; resolve-layout.ts:120-131 | ✅ as spec | A nurse without the discharge permission loses follow-up too (intended). |
| I3 | rx kind=inpatient (530, 849) | inpatient.context.ts:66; VS/visit-shell.component.html:277-287 (app-inpatient-rx-composer when MAR-on-Rx, else app-clinic-prescription [kind]) | ✅ as spec | When hospital.mar_on_prescriptions is off, the Rx card keeps its legacy behaviour. |
| I4 | MAR grid on prescription_items (890-891) | contexts/inpatient/inpatient-mar-card.component.ts (hosts app-mar-board, [marOnRx], hideOrder) | ✅ as spec | |
| I5 | Nursing: notes + rounds/NEWS2 (892) | inpatient-round-card / inpatient-notes-card; FE/hospital/nursing/vital-round.component.html:3-10 | ✅ as spec | NEWS2 badge, ACVPU and O2 are in. Per-parameter NEWS2 scores are 📝 deferred. |
| I6 | ctx vitalsPack rr/avpu/pain/o2/io/news2 (530) | vital-round.component.html:7-10 (temp, pulse, BP, rr, spo2, pain, glucose, consciousness, on_oxygen) vs BE Hospital/app/Http/Controllers/NursingController.php:132-133 (accepts intake_ml/output_ml) | ⚠ partial | Lives in the round card, not the vitals card. There's no intake/output (I/O) input on the FE even though the BE accepts it. |
| I7 | Discharge: type/date/summary + «روشتة الخروج kind=discharge» + PDF (893) | contexts/inpatient/inpatient-discharge-card.component.html:44-52, 67-70 ([kind]="'discharge'"), 81-90 print Rx, 100 discharge_summary document | ✅ as spec | |
| I8 | Header bed · ward · day 2/4 + admitting doctor (530, 1172) | VS/visit-shell.component.html:22; contexts/inpatient-stay.service.ts:70-80 | ⚠ partial | Shows ward, bed and day N. There's no expected length of stay («/4») and no admitting doctor. Users without hospital.beds.view… get a bare «إقامة» chip (stay service :17-19). |
| I9 | orderingMode intent for inpatient (528) | VS/visit-shell.component.ts:208 (encounter.ordering_mode) | ⚠ partial | The context doesn't force intent; it follows the company setting (default intent). A company set to immediate gets immediate orders on inpatient visits too. |
| CLINIC vs CENTER | ||||
| C1 | CONTEXTS clinic/center: orderingMode immediate/intent, lab standalone/integrated, pharmacy formulary/products (526-527) | BE Clinic/app/Support/OrderingMode.php:21-33; ClinicCatalogMode.php:26-36 (lab_mode + rad_mode); PharmacySource.php:23-27; EncounterResource.php:142-148; ClinicSettingDefinitionSeeder.php:210/226/258 | ⚠ partial | There's no single "context" switch. It's 4 independent company settings (clinic.ordering_mode, clinic.lab_mode, clinic.rad_mode, clinic.pharmacy_source) set in the settings screen and sent per encounter. The lab/rad/rx cards read them. Same outcome, but a clinic must set all four. |
| GENERAL | ||||
| GN1 | Default flow vitals → complaint → history → exam → diagnosis → lab → rad → rx → documents → followup (489-493) | VS/card-catalog.ts:92-112 | ✅ as spec | Same order and sizes, plus a collapsed timeline before followup (C1c). |
followup with inp_discharge (inpatient.context.ts:65). The GYN default also has gyn_discharge (gyn-shell-cards.ts:125) and gyn_followup (:131), and those stay. An admitted GYN patient sees «الخروج» twice (GYN discharge module + admission discharge order) plus the GYN follow-up schedule. It's not data-corrupting, but it is confusing.VITALS_WHITELIST (clinic-vitals.service.ts:30-39) caps the vitals card at the 8 core fields. A client who enables or edits these definitions expecting them in «العلامات الحيوية» will see nothing. That's dead config, not a crash.NursingController accepts intake_ml/output_ml (:132-133), but the round form (vital-round.component.html:7-10) has no inputs for them, so fluid balance can't be recorded from the shell.GynFeatures reads clinic.visit_layouts['gyn_ob'].features (GynFeatures.php:9-11, 52-60). The FE save sends {clinical_type, layout} only (visit-shell.component.ts:782), and no setting definitions exist. The approved «عيادة د. منى» lock (screenshot 25) can only be made with a raw API call.(Lesser: an inpatient nutrition visit prescribes through the board's own app-clinic-prescription dialog (board html:650) with no kind=inpatient, because the nutrition layout has no shared rx card for cardProps to reach.)
gyn.feature.* (fertility/obstetrics/urogyn/sexual/oncology/operations). Enforcement works, but an admin can't turn a group off.hospital.beds.view, and a GYN admission shows duplicate discharge/follow-up cards.حدود المراجعة: مقارنة الكود بالنموذج والمواصفة (design-visit-shell.md) + صور من الشاشة الحية لـ 7 زيارات. متعملش ضغط فعلي على كل زرار، ولا اتشغلت اختبارات، ولا اتعدل أي كود.