# W1-FE-1 report: visit-shell bug fixes and small layout-engine fixes (FE)

Date: 2026-10-23. Nothing committed, nothing deployed. I did not bump I18N_VERSION.

## Files changed
VS = `moon-erp/src/app/features/clinic/visit-shell/`
- `VS/visit-shell.component.{ts,html,scss}`: tasks 4, 6, 8, 9, 10, 11 and 12 (owned).
- `VS/visit-layout.model.ts`: adds `VisitContext.remove?: string[]`.
- `VS/resolve-layout.ts`:
  - adds `insured` input, `isRequired(def, insured)`, `context.remove`, the replace dedup, `insertAtDefaultPosition` and `withContextPlaceholders()` (#46).
- `VS/layout-edit.ts`: `applyEditOp(..., {insured})`.
  - ↑↓ now only swaps with a card in the same column AND the same tab.
  - Context placeholders cannot be edited.
- `VS/contexts/inpatient.context.ts`: replaces `followup` and `gyn_followup` with `inp_discharge`, and removes `gyn_discharge`.
- `VS/specialties/dental.cards.ts`: `dent_workspace.required = true`.
- `VS/visit-shell-bus.service.ts`: `registerBeforeSign(fn) → unregister` and `flushBeforeSign()`.
- `VS/cards/visit-complaint-card.component.ts` (B5):
  - The note save is now one serialized `flushNote()` (no `distinctUntilChanged`, no double PATCH).
  - It flushes on destroy.
  - `flush()` waits for the note and for any in-flight complaint sync, and is registered on the bus.
- `VS/cards/visit-exam-card.component.ts` (B5):
  - `flush()` waits for the in-flight save and then the pending re-save; it is registered on the bus.
  - New `saved` output (#11).
  - The debounce skips the save if a flush already saved.
- `VS/visit-header.component.ts`, `VS/visit-start-rail.component.ts`, `VS/visit-summary-rail.component.ts`, `VS/visit-finish-panel.component.ts`, `VS/visit-rail.scss`.
- `VS/resolve-layout.check.ts`, `VS/contexts/inpatient.context.check.ts`: new test cases.

**Small edits to shared files** (all named in the brief):
- `features/clinic/services/clinic-patient.service.ts`:
  - New types `ClinicPatientInsurance {company_name, contract_id, contract_name, coverage_percent}` and `ClinicPatientInsuranceLegacy`.
  - `insurance` is typed as a union of the new object, the legacy array, or null.
  - Shared helpers `patientInsuranceView()`, `labFlagTone()` and `labFlagArrow()`.
  - `ClinicPatientLabResult` gains `flag`, `unit` and `date`.
- `features/clinic/services/clinic-encounter.service.ts`: `Encounter.signed_by_name?`.
- `features/clinic/shared/clinic-patient-header/clinic-patient-header.component.{ts,html}`:
  - The insurance tag reads through `patientInsuranceView()`; the `%` is hidden when coverage is unknown.
  - The last-labs flag reads through the same shared helper (HH/LL/A now also flag). This lab-flag change is a small addition beyond the brief.
- `src/assets/i18n/{ar,en}.json`: adds the keys below and retitles `FINISH.TITLE`. Each file has 8 insertions and 2 deletions: the retitled `FINISH.TITLE`, and the `ACTIONS` line, which gained a trailing comma.
  - `VISIT_SHELL.BAR.SIGNED_BY`
  - `CTX.PH_INPATIENT`, `CTX.PH_EMERGENCY`
  - `FINISH.HARD_HEAD`, `FINISH.SOFT_HEAD`, `FINISH.FLUSH_FAIL`
  - `DX_NOT_ON_SCREEN` is kept but no longer used.

## Per task
1. **B1 insurance.** Both headers use the same helper:
   - New object: `company_name` (`company · contract` when they differ), with `· n%` only when a coverage is sent.
   - Legacy array: the first row's `contract_name`.
   - Null or empty: «نقدي».
2. **B2 last results.**
   - Red for H/L/A (also high/low/abnormal); solid red for HH/LL/critical.
   - Green only for a value with no flag (or N/normal). No value shows grey.
   - An unknown non-empty flag shows red, the safe default.
   - Each result shows its unit and a small date tag.
   - Current meds now come from the structured `summary.meds`, with the `sentence` under the drug name. The flat `current_meds` is the fallback.
3. **B5 lost note.**
   - `sign()` is now async: `signing=true`, then `await bus.flushBeforeSign()`, then `encounters.sign()`.
   - The cards only lock when `encounter.is_locked` flips, which happens after the sign response, so they stay editable during the flush.
   - If a flush fails, the sign stops and a `FINISH.FLUSH_FAIL` toast shows (the card has already toasted its own error). The text is kept for the next try.
4. **D bug 1, GYN inpatient.**
   - `remove` runs before `replace`.
   - When two sources map to the same target, the target appears once, at the first source's place; the later source goes to `dropped`.
   - Admitted GYN visit: one `inp_discharge` (at `gyn_followup`'s place), no `gyn_discharge`, no follow-ups. The GYN summary card stays.
5. **B6 numbering.** `SummaryLine` gains `n` and `icon`. A line is numbered only for a flow station (the same number as the card head); every other line shows the card icon.
6. **B4 insured diagnosis.**
   - With `insured`, a `requiredWhen:'insured'` card counts as required.
   - It is re-inserted at its default-layout place (after the nearest preceding default card still on screen), or at the end when the default doesn't have it. It is tagged `from:'required'`.
   - In edit mode its ✕ is disabled (the shell passes a def with `required:true` through `cardDefs()`), and `applyEditOp` refuses to hide it.
   - `finishBlocked` now requires `is_primary && code` (the BE rule, B3) for every clinical type. The `type!=='general'` exemption and the DX_NOT_ON_SCREEN warning are gone.
   - The finish panel shows hard blocks under «لازم قبل التوقيع» and soft warnings under «ملاحظات (لا تمنع التوقيع)».
7. **A bugs 1-3.**
   - The editor count is now the whole draft (`draft().cards.length`).
   - ↑↓ only swaps within the same column and the same tab.
   - `dent_workspace` is required.
8. **#46 context cards while editing.**
   - `fullLayout` while editing is `withContextPlaceholders(draft, resolved(), context())`: the draft stays context-free, and the context cards are inserted as dashed, dimmed placeholders. Each shows the icon, the title and «سياق الإقامة — مش بيتحفظ في الشكل» (or «سياق الطوارئ…» for an emergency visit).
   - Placement: a replace target sits right after the card it replaces (that card stays editable); an added card sits after its nearest live neighbour.
   - Save is unchanged; a check confirms that persisting the view equals persisting the draft.
9. **#62 / #11 summaries and pulse.**
   - Prescription `(saved)` and the inpatient-rx `(saved)` now call `onRxSaved()`, which refreshes the rx part (summary line and live rail) and fires the pulse.
   - The «حفظ تلقائي» pulse also fires on: exam `(saved)`, lab/rad `intentsChanged`, fact-card `(changed)` and bus `refresh` (spec cards).
   - I checked that none of these fire while a card is loading, so the pill does not light up on page open:
     - lab/rad `intentsChanged` is emitted only from `recordIntents`, delete-intent and `onExternalResultSaved`;
     - every `bus.refresh(...)` caller (MAR, round, triage, discharge, GYN flowsheet) runs after a save.
10. **#13a.** No wrap to the first undone station. After the last station nothing is active and `footer button.finish-btn` gets focus. Alt+↑↓ still wraps; I left that alone because it is navigation.
11. **#76.**
    - When the layout payload says `visit_shell_enabled=false`, the shell redirects to `/clinic/encounter/:id?classic=1` with `replaceUrl`.
    - `?force=1` keeps the shell open; this is documented in a code comment.
    - A null payload (load error) fails open, i.e. stays in the shell.
12. **#45.** The locked bar shows «موقّعة من <name> · <time>» when `encounter.signed_by_name` is sent; otherwise it shows the time only, as before.

## Tests / verification
The FE has no test runner, so the pure `.check.ts` scripts are the tests. I ran each one (esbuild bundle + node):
- `resolve-layout.check.ts`: **34 passed, 0 failed**. New cases:
  - insured re-insert, its position and size, and no duplicate;
  - nutrition: the card goes to the end and joins the last tab;
  - `isRequired`;
  - hide is refused when insured and allowed on a cash visit;
  - ↑↓ within a tab, and placeholders cannot be edited.
  - §3 now reads "not re-inserted on a cash visit". The old rule (owner decision 8ب) is overridden for insured visits by the brief's B4 decision.
- `contexts/inpatient.context.check.ts`: **41 passed, 0 failed**. New cases: GYN inpatient, day-case and nurse runs, and the #46 placeholder view (position, renumbering, persisting the view equals persisting the draft, triage first).
- `specialties/dental-layout.check.ts`: **16 passed, 0 failed** (still green after `dent_workspace.required`).
- `npx ng build --base-href /app/`: **success**, with no warnings or errors in any file I touched.

## Could NOT verify
- No live/browser check. Not deployed, so the flush-before-sign timing, the redirect, the placeholders and the focus are verified only through the code paths and the build.
- The BE shapes (`summary.insurance` object, lab `flag/unit/date`, `signed_by_name`): W1-BE-A had not landed them in the tree when I checked. The FE accepts both the old and the new shapes.

## Deviations / decisions
- **Forced diagnosis applies to every clinical type**, not only general. The diagnosis card is `shared`, and the BE blocks an insured sign for every type (`EncounterService::sign`). So an insured nutrition/dental board now gets the diagnosis card (at the end, in the last tab). This removes the false «soft» promise from B4's reverse case.
- **Side effect of forcing the card:** when an admin customises the screen while an insured visit is open, the forced diagnosis is in the draft and is saved into the client layout. This follows from "cannot be hidden in edit mode".
- **Pulse not wired for vitals or diagnosis:**
  - The vitals panel has no save output (only `dangerCountChange`), and the vitals panel is out of scope.
  - The diagnosis panel's `insuredNoPrimaryChange` fires from an effect, not on save. The diagnosis summary line therefore still refreshes only on «تم» or on opening the finish panel. The sign gate is unaffected (it refreshes when the boolean flips).
  - Each would need a `saved` output on those panels in a later wave.
- **Not my edit:** `src/app/core/models/nutrition-visit.model.ts` shows as modified in the FE tree. That is another agent's change.
