What's new
Version history
What changed in each release
v7.08
RUNTIME
The app was quietly redrawing itself sixty times a second, even while sitting still — draining battery and warming the phone. It now redraws only when something actually changes.
Current
- Battery drain and device heating fixed. A helper that watches the dose page for content below the fold was re-triggering itself in a loop, forcing the phone to re-measure the page at 60–120 frames per second on every screen — even the Home screen, even with nothing moving. Reported from the field as a hot phone with fast battery drain while the app was open.
- Nothing visible changes. The “content below” cue on dose pages behaves exactly as before; the app simply stops doing invisible work when idle. Verified by measurement: idle activity went from 60 redraws a second to zero.
v7.07
SAFETY
Lidocaine 1% IO was showing a dose far below the protocol. It is corrected, and the full give – dwell – flush – give sequence is now on the page.
- Lidocaine 1% IO showed too small a dose. For a 22 kg child the page read 0.5 mg / 0.05 mL. The protocol gives 11 mg (1.1 mL), then a further 5.5 mg (0.55 mL). The figure shown was around a thirtieth of the correct amount. Corrected, and checked at every weight.
- Why it happened. This protocol is not one dose — it is two doses with a dwell and a flush between them. There was nowhere in the drug table to record a second dose, so the whole instruction had been typed into the box that holds the maximum. The app read the first number it found there, “0.5 mg/kg”, as a hard ceiling of 0.5 mg — a limit lower than any real dose, so every patient was shown the limit instead of their dose.
- The page now shows the whole sequence. Give 11 mg over 2 minutes → let it dwell in the IO space for 1 minute → rapid 5 mL sodium chloride 0.9% flush → give 5.5 mg over 1 minute. Each dose carries its own draw-up volume and its own maximum, and the total for the patient sits at the foot of the card. It is deliberately not shown as a “repeat” — a repeat is optional, and this second dose is not.
- No other drug was affected. Every row in the app was re-checked for the same pattern. Lidocaine IO was the only one.
- Two new checks now block a release. A maximum can no longer be written per kilogram, or hold two different limits in one box. And any dose ceiling that would already bind on a 2 kg newborn is refused — a ceiling that catches the smallest patient is not a ceiling, it is a mistake. Both were confirmed to fail on the old lidocaine row before being trusted.
v7.04
SAFETY
The six drugs flagged for extended-scope review have been checked route by route against the protocols. Five were correct as they stood; one salbutamol infusion row was wrong and is withdrawn.
- Amiodarone, atropine, calcium gluconate, fentanyl and ketamine are unchanged and were correct. Each protocol does carry extended-scope badges, but on rows this app never included — an adult infusion, a post-transfusion indication, an RSI or anaesthesia induction. The paediatric rows the app actually shows are badged plain CCP.
- Salbutamol IV infusion is withdrawn. The protocol badges that route extended-scope CCP, and carries no paediatric infusion dose at all — the paediatric section lists inhaler and nebuliser only. The row had been built from the adult entry. It showed no dose, only an instruction to consult, and it was already hidden from view, so nothing incorrect ever reached a screen.
- Why the earlier review over-flagged. These protocols print the adult table on the left of the page and the paediatric table on the right. A badge was being matched to a row by how far down the page it sat, so adult badges were landing on paediatric rows. Matching now requires the badge to sit within its own table, and was checked against a row already known to be correct before being trusted.
- No dose, volume or maximum changed anywhere in this release. Only which rows are shown.
v7.03
SAFETY
Lidocaine subcutaneous infiltration is withdrawn from the app. Yesterday’s restriction to CCP was itself wrong — the protocol authorises that route for extended scope of practice only, which this app cannot yet represent.
- The row is now hidden from everyone. Lidocaine subcut was released at ACP2 in v7.01, restricted to CCP in v7.02, and is withdrawn here. Both earlier states were wrong: the protocol badges that route extended scope ACP2 and extended scope CCP, and not plain CCP. An extended-scope ACP2 may give it; an ordinary CCP may not.
- Why restricting it to CCP did not fix it. v7.02 assumed extended scope sits inside the level above it, so storing an extended-CCP row at CCP would only narrow who sees it. That is backwards — it exposed the row to every ordinary CCP, a larger group than the one authorised. The clinical levels are not a ladder, and there is no level setting in this app that means “extended scope only”.
- Nothing is being guessed in the meantime. Rather than pick the least-wrong level, the row is inactive until the app can express extended scope properly. The dose itself was verified correct against both the protocol and the QAS app — only the authorisation was misread.
- Lidocaine IO is unaffected and remains available to CCP. It is badged plain CCP in the protocol and was correct throughout.
- Under review. Six further drugs already in the app — amiodarone, atropine, calcium gluconate, salbutamol, fentanyl and ketamine — carry extended-scope badges on some routes and are being checked against the protocols route by route. No change to any of them in this release.
v7.02
SAFETY
Scope correction — lidocaine subcutaneous infiltration is restricted to CCP. It was released yesterday visible to all ACP2 clinicians; the protocol authorises it for extended practice only.
- What was wrong. The lidocaine subcut row shipped in v7.01 at general ACP2 scope. The DTP authorises that row for extended practice ACP2 and CCP — marked by an “E” against each authorisation badge — not for ACP2 generally. The badge marker was misread during import.
- What changed. The row is now CCP only. This matches how every other extended-practice row in the app is already stored. The lidocaine IO row is unaffected: it was always plain CCP and was correct.
- Known limitation. The app does not yet model extended practice as its own authorisation level, so an extended-practice ACP2 clinician who is authorised will not see this row. That is deliberate — showing a dose to someone outside its authorisation is the worse failure, so the correction under-exposes rather than over-exposes.
- No dose changed. Values, ceilings, preparation and contraindications are all unchanged from v7.01. This release only changes who can see the subcut row.
v7.01
CLINICAL
Lidocaine 1% added — IO analgesia after EZ-IO insertion (CCP), and subcutaneous local anaesthetic infiltration (ACP2 and CCP).
- IO analgesia — CCP. Pain associated with IO drug and fluid administration following EZ-IO® needle insertion. 0.75 mg/kg, given in two parts: 0.5 mg/kg (max 40 mg) over 2 minutes, dwell in the IO space for 1 minute, a rapid 5 mL sodium chloride 0.9% flush, then a further half the initial dose (max 20 mg) over 1 minute. Total max dose 60 mg. Source: DTP_LID_0924, paediatric dosages page.
- Subcutaneous infiltration — ACP2 and CCP. Local anaesthesia for radial artery line placement, regional anaesthesia (digital block), suturing, fishhook removal and thoracostomy. Up to 3 mg/kg (0.3 mL/kg), total maximum infiltration 150 mg (15 mL). This is the first row to use the dose-intent field added in v7.00: the protocol states a limit, not a target.
- Drawn undiluted. Ampoule 50 mg/5 mL lidocaine 1% — a final concentration of 10 mg/mL, no preparation step. Administer slowly with frequent aspiration to avoid inadvertent intravascular injection, and flush the line afterwards.
- No IM dose row. The DTP lists IM only as “as authorised on DTP: Ceftriaxone” — lidocaine is the reconstitution diluent there and carries no dose of its own, so no calculator entry was created.
- Transcribed by hand and cross-checked. The watermark on this DTP corrupts the machine-readable text of the dose cells, so the usual two-pass automated agreement was not available. Every value was read from the page image and independently confirmed against the QAS app’s own rendering of the same protocol — all doses and authorisation levels agree.
- Tentative — pending sign-off. Data verified against the DTP; clinical sign-off outstanding, in line with every other drug currently in the app. Not for clinical use without consultation.
v7.00
GOVERNANCE
The dataset now records whether a calculated dose is an instruction or a limit. No dose, card or calculation changes in this release — the groundwork lands first, the display change follows under separate clinical review.
- “3 mg/kg” and “up to 3 mg/kg” are not the same instruction. One says administer this; the other says do not exceed this. The arithmetic is identical, so until now both rendered the same way. Every dose row now carries a dose intent of either target or ceiling, recorded from the DTP’s own wording.
- One live drug was already affected. Midazolam IV/IO for sedation reads “Up to 100 microg/kg” in the DTP and has been carrying it as an ordinary target dose. It is now correctly classified as a ceiling. Its dose card is unchanged in this release — changing how a live drug displays is a clinical change and gets its own review.
- The classification must be evidence-based, both ways. A new release-blocking lint fails if source wording states a ceiling but the row is marked as a target, and equally if a row is marked as a ceiling without source wording to support it. The classification can never drift into being an opinion someone typed.
- An unrecognised value fails the build. Dose intent parses through a strict vocabulary that raises rather than defaulting — the same discipline applied to boolean fields in v6.96, and for the same reason: a silent fallback here would display a limit as an instruction.
- Verified as no visible change. The dataset was built before and after and compared field by field; every clinical value is byte-identical, with the only differences being the new field and the build timestamp.
v6.99
CLINICAL
Levetiracetam added for CCP — 60 mg/kg IV/IO infusion for status epilepticus continuing past 20 minutes, contraindicated under 1 year. Ships with a rebuilt drug importer that reads administration duration as a clinical concept rather than part of the dose.
- Levetiracetam (CCP only). Convulsive Status Epilepticus continuing > 20 minutes post first midazolam administration. 60 mg/kg IV/IO INF, weight rounded up to the nearest 5 kg, over 5–10 minutes, single dose only, total maximum 4500 mg. Drawn from a 500 mg/5 mL vial (100 mg/mL) and diluted to 50 mL in sodium chloride 0.9%; doses over 2500 mg are split across two 50 mL syringes. Administered via the Perfusor Space Medication Library (Levetiracetam-1). Source: DTP_LEV_0924, paediatric dosages page.
- Absolute age gate under 1 year. The DTP contraindicates levetiracetam below 1 year of age, so the row is banded “1 year or older” and carries a structured contraindication rule (age_min_months 12) — the same mechanism that gates Benzatropine under 3 years. A patient under 1 year gets the contraindication surface, never a dose. Toxicology-related seizures are a relative contraindication requiring consult approval.
- Importer now extracts clinical concepts, not just numbers. The drug importer previously refused any dose cell containing the word “minutes”, which silently dropped 68 dose rows across the DTP library — levetiracetam among them. Administration duration, maximum dose and the dose itself are now extracted as separate concepts, each reserving the text it owns, so a duration or a ceiling can never be read as the dose. Unclassifiable wording blocks the row for review instead of being dropped.
- Reconciliation defects found and fixed. The two extraction passes used different unit vocabularies: per-kg doses could never agree (so no per-kg drug could import), “20,000 units” parsed as zero, and an infusion rate could confirm a bolus. One canonical unit parser now serves both passes across every unit family the dataset uses.
- New release-blocking guards. Parser invariants (I1–I5) assert concept spans never overlap and every number is accounted for; release-integrity guards assert the built dataset faithfully preserves clinical_level, age gate, dose, maximum, rounding, concentration and route, and that no CCP row is ever exposed at ACP scope. Boolean spreadsheet fields now parse through one strict parser — a cell typed “False” can no longer evaluate true.
- Tentative — pending sign-off. Data verified against the DTP; clinical sign-off outstanding, in line with every other drug currently in the app. Not for clinical use without consultation.
v6.98
UI
The CCP Resus deck now fits the screen — the drug chip strip follows the active page with an edge-fade scroll hint, and the IV/IO route pill no longer hides under the gold CCP badge.
- Active chip always on screen. With the two CCP arrest drugs the Resus chip strip holds four chips and scrolls sideways; swiping to Amiodarone or Calcium previously left the active chip clipped at the screen edge with no hint the strip scrolls. The strip now auto-centres the active chip on every tap or swipe, and a subtle fade at either edge shows when more chips sit off-screen.
- Route pill clear of the CCP badge. In CCP mode the fixed gold ⚕ CCP pill sat directly on top of the page's IV/IO route pill in the Resus header. The route pill now shifts left of the badge, so both are fully readable.
- ACP2 unchanged. The two-chip ACP2 strip never overflows (no fades ever show) and the route-pill shift only applies in CCP mode.
- Guarded. test_resus_ccp_drugs now also asserts the geometry: on a CCP boot the active Calcium chip must sit fully inside the viewport with the strip auto-scrolled and the edge fade engaged, and the route pill must clear the CCP badge; an ACP2 boot must show no shift.
v6.97
SAFETY
Vital Signs now updates instantly when the patient is changed on the Vitals screen — it previously kept the previous patient's ranges beside the new patient's age. The cross-view refresh guard was rewritten to drive the current patient picker so this class of bug blocks release again.
- Stale Vitals fixed. Changing the patient while already on the Vital Signs screen (tapping the patient strip opens the age/weight picker in place) updated the strip but not the reference ranges — the tiles could show newborn ranges (HR 100–170) beside a strip reading "7 years" (fresh render: HR 70–115). The Vitals view now re-renders off the same canonical patient-change signal the Resus deck has used since v5.50, so every way of changing the patient refreshes it.
- Why the guard missed it. The release-blocking cross-view refresh guard (test_patient_refresh_matrix) changed the patient through the legacy edit sheet, which happens to re-render Vitals directly on Apply. The app moved to the unified band-grid picker in v4.99 — which didn't — so the guard was passing against a retired code path while the real path went stale.
- Guard rewritten, coverage widened. The matrix now drives the CURRENT affordance — a real tap on the visible patient strip, then the band-grid picker's Set patient — asserts the picker actually committed (non-vacuous), and sweeps the drug list and drug overview pages as well as Resus, dose and Vitals. Proven to fail on the unfixed build (stale Vitals reproduced), green after the fix.
- Contract updated. The patient-state contract's recipe no longer blesses "the edit sheet re-renders it for you": every patient-dependent view must own a canonical change-signal listener, because commit-path direct calls die silently when an affordance is replaced.
v6.96
SAFETY
Magnesium sulphate added (tentative) — paediatric Irukandji, box jellyfish and Torsades de Pointes, delivered as a SPRINGFUSOR infusion. Envenomation shows for ACP2 and CCP; Torsades is CCP-only.
- Magnesium sulphate added. Weight-based 0.1 mmol/kg, single dose capped at 5 mmol (total maximum 10 mmol), repeat once. Irukandji syndrome and box jellyfish envenomation (IV, over 15 min) show in normal ACP2 mode and in CCP mode; Torsades de Pointes (IV/IO, over 10 min) shows in CCP mode only. Delivered as a SPRINGFUSOR infusion at 60 mL/hour — the syringe recipe, infusion rate and the DTP's "round up to the nearest 0.5 mmol" instruction ride on the dose page as an administration note; the dose value is computed from the dataset, none typed in the renderer.
- Marine-envenomation caveat carried through. The DTP restriction — magnesium for marine envenomation is only administered by trained QAS paramedics at coastal stations from Rainbow Beach and north — is shown on the envenomation pages. The paediatric IO consult-only route for envenomation is not surfaced (it is a consultation referral with no numeric dose).
- Tentative — pending sign-off. Magnesium is added under the not-for-clinical-use umbrella, marked pending clinical sign-off like the other recent additions.
v6.95
SAFETY
CCP mode gains two arrest drugs on the Resus deck (tentative) — Amiodarone and Calcium gluconate get their own pages to the right of Adrenaline — and Sodium bicarbonate 8.4% joins the CCP formulary (tentative). ACP2 sees none of it.
- Amiodarone on the Resus deck (CCP only). A new chip to the right of Adrenaline opens an Amiodarone page for cardiac arrest refractory to 3 shocks: 5 mg/kg IV/IO, single dose only, with the draw-up volume computed from the 10 mg/mL dilution (in glucose 5%) and the DTP's administration caution on the page. Same dark dose-card layout as the Adrenaline page; every value reads from the dataset row, none are typed in the renderer.
- Calcium gluconate on the Resus deck (CCP only). A second new chip opens the suspected hyperkalaemic cardiac arrest page: 0.1 mmol/kg IV/IO capped at 2.2 mmol (the cap is applied to the computed dose and always shown on the INITIAL DOSE band), repeat once at 10 minutes, drawn directly from the 2.2 mmol/10 mL (10%) vial. The extravasation / slow-push / flush caution rides along from the dataset. The drug's other three CCP indications stay on the Drugs page.
- Scope-gated twice, like the ETT row. On an ACP2 boot the chips and pages are physically removed from the DOM (so deck indexing and swipe never see them) and a deny-by-default CSS rule hides them independently — the same double gate the v6.94 ETT row uses. The Resus band header follows the live chip strip, so it can no longer drift from the pages.
- Guarded release-blocking. New test_resus_ccp_drugs asserts an ACP2 boot shows neither drug anywhere on the Resus deck across the age range, and that a CCP boot renders both pages with dose and volume matching the dataset — including the 2.2 mmol cap at heavier weights — and live-refreshes on a patient change.
- Sodium bicarbonate 8.4% added (CCP mode). Weight-based 1 mL/kg IV/IO, single dose only, not to exceed 100 mL, drawn directly from the 100 mL (8.4 g/100 mL) vial. Covers cardiac arrest from hyperkalaemia, propranolol or TCA overdose, crush syndrome risk, suspected hyperkalaemia with QRS widening and/or AV dissociation, and non-TCA sodium-channel blockade. Not indicated in the newly born — the age gate blocks it, mirroring Naloxone’s rule. Lives on the Drugs page (CCP mode only).
- Tentative — pending sign-off. Both drugs remain under the CCP-beta not-for-clinical-use umbrella (added v6.83/v6.85, pending clinical sign-off); this release adds no new clinical values — it surfaces the existing rows on the Resus deck.
v6.94
SAFETY
Ketamine's paediatric dilution is now spelled out as two numbered steps in the slide-up — including the easy-to-miss "discard 18 mL" — and CCP mode gains ETT sizes on the Resus page (tentative): tube size and depth-at-lips on a gold CCP row, full QAS sizing chart one tap deep. ACP2 never sees the ETT row.
- Ketamine two-step dilution, spelled out. The Dilution slide-up for paediatric IV Ketamine now walks the recipe as two numbered step cards, each ending in a "You now have" line: First dilution — 200 mg (2 mL ampoule) + 18 mL NaCl 0.9% → 20 mL at 10 mg/mL; Second dilution — keep 2 mL (discard the other 18 mL) + 18 mL NaCl 0.9% → 20 mL at 1 mg/mL, shown as the mint use-for-dosing strip. The old single row compressed both dilutions into "+18 mL then +18 mL" and never surfaced the discard step. The dose-page dilution strip now carries a "· 2 steps" cue, recipe values are authored in the dataset (new multi-step prep columns), and the layout is guarded release-blocking by test_prep_steps (proven-to-fail on the single-row build).
- ETT on the Resus airway card (CCP only). A gold ⚕ CCP row under the OPA/i-gel sizes shows the recommended tube — e.g. Size 4.0 for 2 to <4 years — from the QAS ETT table (pre-term 2.5 cuffless; term ≥3 kg 3.0; then 3.5/4.0/4.5/5.0/5.5 by age to <10 years; 10 years and up shows 6.0 "Large child"). The "i" opens the full sizing chart with brand and stylet/bougie per row (6 Fr stylet for 2.5–4.0, 10 Fr for 4.5–5.5, Frova 14 Fr from 6.0) and the adult rows (7.0–9.0) for reference — adult sizes are never the pick. Source: CPP Direct laryngoscopy and intubation (CPP_AM_DLAI_0626, Jun 2026), transcribed to a new dataset sheet — no value is typed in the renderer.
- Depth at lips, only where the CPP states it. Under the size: newly born/neonate = 6 + weight (kg) cm; 6 months = 11 cm; 1 year = 12 cm; over 1 year = age ÷ 2 + 12 cm. Age bands the CPP doesn't anchor (e.g. 3 months) show no depth rather than an interpolated guess. Two interpretations are recorded in the data sheet for sign-off: a term newborn under 3 kg falls back to the cuffless 2.5 (the 3.0 row is ≥3 kg by the CPP), and the newborn depth uses the app's canonical newborn weight (3 kg → 9 cm) with the CPP's own formula, where the CPP's worked anchor (9.5 cm) assumes 3.5 kg.
- Scope-gated twice, guarded release-blocking. The row and chart render only in CCP mode — a JS emit gate plus a deny-by-default CSS gate — and a new CLINICAL guard (test_ett_scope, proven-to-fail with the gates stripped: 15 blocks) asserts an ACP2 boot is ETT-free across five patients including a pre-term newborn, that a CCP boot renders the row, and that it live-refreshes on a patient change. test_resus_content gains RC4/RC5 (CCP ETT row + chart parity with the dataset across 4 ages).
- Tentative — pending sign-off. Same umbrella as the other CCP-beta additions: transcribed and cross-checked from the CPP but not yet clinically verified for release; not for clinical use without consultation. CCP feedback wanted on what belongs on the face (size only vs depth/stylet).
- Resus deck scroll clearance. The Resus pages now reserve the same floating-nav clearance as the dose pages (single-count formula), so the last card scrolls fully clear of the pill — the new ETT row had exposed a pre-existing ~6 px shortfall.
v6.93
SAFETY
Estimated weights now use the QAS operational formula (age × 3) + 7 for 1–12 years — matching how crews estimate on road — instead of the paracetamol-table values. Doses for most bands from 2 years up increase accordingly.
- (age × 3) + 7 estimates. The age-band weight estimates for 1–12 years now follow the QAS operational formula general operators use in the field: 2 y 13 kg · 3 y 16 · 4 y 19 · 5 y 22 · 6 y 25 · 7 y 28 · 8 y 31 · 9 y 34 · 10 y 37 · 11 y 40 · 12 y 43. Infant bands (newborn to 9–12 months) stay on the months-based estimates. A measured weight still overrides the estimate everywhere.
- What follows from heavier estimates. Weight-based doses rise for every band from 2 years up, and single-dose caps engage one to two bands earlier (e.g. the Fentanyl/Morphine 25 kg cap weight is now reached at 6–7 years). Glucagon at 7–8 years now sits in the >25 kg band (1 mg). Defibrillation pad selection crosses the 25 kg rule at 6 years, and i-gel sizes step up at 6–7 and 10 years. Every cell of the dose surface was diffed before release — no band lost coverage and no dose became invalid.
- Verification. The full clinical-output export was regenerated under the new weights (zero anomalies) and the safety regime guards were updated to drive rounding-caption cases with manual weights so they are independent of the estimates.
v6.91
SAFETY
Fixed Ondansetron PO — it was showing an Ampoule/mL draw-up copied from the injectable prep; it now shows the correct ODT (wafer) presentation with no draw-up volume.
- Ondansetron PO now reads as a wafer, not a liquid. The oral route had been wired to the same prep as IM/IV — Ampoule, 4 mg/2 mL — so the dose card told you to "Draw up 2 mL" for an oral dose that is actually an Orally Disintegrating Tablet (ODT/wafer), never drawn up. Presentation now reads Orally Disintegrating Tablet (ODT/wafer), 4 mg and the dose card shows a plain "no draw-up" note instead. Dose values are unchanged (2 mg, 2–4 years; 4 mg, ≥5 years; single dose only) per DTP_OND_0722.
v6.90
SAFETY
Fail-visible error boundary: if a screen ever fails to build, the app now shows a clear "App error — reload required" instead of a half-broken clinical page.
- App-error boundary. If the app ever hits an unexpected error while building a screen (dose card, plan sheet, drug list, navigation), it now replaces the whole screen with a plain App error — reload required message rather than leaving a partially-drawn or blank clinical page. A partial dose card is more dangerous than an honest "reload", so the app fails loud and safe. It's scoped so a normal working screen is never affected.
v6.89
SAFETY
Clinical sign-off is now a governed, honest status: every drug is marked "pending clinical sign-off" until a clinician reviews it — nothing is silently treated as verified.
- Governed clinical sign-off. A new structured field records whether each drug has had an independent clinician sign-off — replacing free-text notes as the source of truth. Because no drug has yet been independently signed off, the whole formulary is honestly marked tentative — pending sign-off. This is a pilot build and remains not for clinical use; the marking makes that explicit and machine-checkable.
- No conflicting statuses. An automated release check makes it impossible for the data to say "verified" while a note says "pending", and the source spreadsheet now uses a fixed dropdown so an invalid status can't be typed in. Governance reports are generated deterministically from this field.
v6.88
SAFETY
Deploy verification — every release now proves the live site is serving the new build; plus the CCP badge is redrawn to sit centred.
- Deploy verification. After each release an automated check fetches the live site and confirms it is actually serving the promoted version — both the app shell and its offline worker — using a cache-busted request so a stale cached copy can't hide a failed deployment. If the live site does not match, the release check goes red. Closes the gap between "pushed" and "live".
- CCP badge redrawn. The ⚕ caduceus in the gold CCP badge is now drawn precisely (not a text symbol) so it sits vertically centred in the pill on every device.
v6.87
UI
Alphabetical drug list, a proper CCP scope switch with App status at the top of More, two restored repeat instructions, and a centred CCP badge.
- Alphabetical formulary. The By-drug list now shows every drug A→Z, and any newly-added drug always inserts in alphabetical order instead of landing at the bottom.
- CCP scope switch + tidier More. The CCP mode control in More is now a proper sliding switch, and the App status panel sits at the top of the More screen with the menu items below it.
- Restored repeat instructions. Two repeat directions that were being dropped now show correctly: Sucrose 24% ("may be repeated once at 5 minutes") and Ketamine ("repeated every 2–3 minutes"). No dose values changed.
v6.86
GOVERNANCE
Formalised the unit-family safety rules (mass / volume / millimole) into a governing contract with an automatic parity check.
- Unit-family contract. The rules keeping mass, volume, and millimole doses strictly separate — and refusing any cross-family conversion — are now a formal release-governing document with a build-time check that the app and its independent test mirror agree exactly.
- Parser hardening. Four dose-text patterns that did not yet recognise millimole values (range doses, inline maximums, one concentration form) were extended so a future mmol-dosed protocol can never be silently dropped. No current dose values change.
v6.85
SAFETY
Added Calcium gluconate (CCP, tentative) — the first millimole-dosed drug — for paediatric hyperkalaemia and related emergencies.
- Calcium gluconate added (CCP mode). Weight-based 0.1 mmol/kg IV/IO, single dose capped at 2.2 mmol, may repeat once at 10 minutes. Covers suspected hyperkalaemic cardiac arrest, severe hyperkalaemia, verapamil/diltiazem toxicity, and hypotension from a magnesium infusion. Drawn directly from the 2.2 mmol/10 mL (10%) vial.
- First millimole-dosed drug. The dose engine gained a new amount-of-substance (mmol) unit family, kept strictly separate from mass and volume — a cap in the wrong unit can never be applied (it fails safe rather than dropping the ceiling).
- Tentative — pending sign-off. Transcribed from the QAS DTP and cross-checked, but not yet clinically verified for release. Visible only in CCP mode.
v6.84
SAFETY
Updated the paediatric hypoglycaemia rescue threshold to match the June-2026 protocols, and added Sucrose 24% for infant procedural pain.
- Glucose threshold updated (Glucose 10% + Glucose gel). The June-2026 QAS protocols lowered the paediatric rescue endpoint: keep giving glucose until BGL ≥ 3.0 mmol/L (previously 4.0). The app now matches — Glucose 10% repeat and the Glucose gel under-2-years dose both use 3.0 (adult stays 4.0). Glucose gel under-2yr amount also corrected to ~0.2 g.
- Sucrose 24% added (ACP2). Oral analgesia for short-term procedural pain in infants: 0–1 month → 0.1 mL (2 drops); 2–12 months → 0.5 mL (10 drops), may repeat once at 5 minutes. Shown as contraindicated over 12 months of age. Appears under Pain / Fever.
- Tentative — pending sign-off. The Sucrose entry was transcribed from the QAS DTP and cross-checked, but is not yet clinically verified for release. The glucose changes align existing doses to the current protocol.
v6.83
SAFETY
Two more CCP drugs added (tentative) — Amiodarone for paediatric cardiac arrest and Benzatropine for acute dystonic reaction.
- Amiodarone (CCP). Paediatric cardiac arrest in a shockable rhythm refractory to three shocks — 5 mg/kg IV/IO, single dose, diluted to 10 mg/mL in glucose 5%. Appears under Cardiac arrest in CCP mode.
- Benzatropine (CCP). Acute dystonic reaction, 3 years and older — 20 microg/kg IM/IV, single dose (2 mg/2 mL vial, drawn directly). Shown as contraindicated under 3 years.
- Tentative — pending sign-off. Both were transcribed from the QAS Drug Therapy Protocols and cross-checked three ways, but have not yet been clinically verified for release; they sit under the CCP beta's "not for clinical use without consultation" umbrella. ACP2 dosing is unchanged.
v6.82
UI
Removed the gold line across the top of the screen in CCP mode — the gold ⚕ CCP pill stays as the scope indicator.
- CCP top bar removed. The thin gold strip that ran across the top of every screen in CCP mode is gone. The gold ⚕ CCP pill in the top-right corner remains, so it's still clear when CCP scope is active.
v6.81
UI
Tidied the grouped indication list — section headings now line up with the indications, and the redundant count line is gone.
- Headings aligned. The body-system section labels (Allergy, Respiratory, …) now sit in line with the indication names beneath them, instead of flush to the card edge.
- Removed the redundant count. The "Indications · N" line under the Indications/Drugs toggle has been dropped — it only repeated the toggle. The Drugs list keeps its "N of M" count, which is useful when filtered.
v6.80
SAFETY
The indication list is now grouped by body system, and several presentations that couldn't be found by symptom before now have a category.
- Grouped by body system. In the Indications view, presentations are now organised under headed sections — Allergy, Respiratory, Cardiac & circulation, Neurological, Pain & trauma, Metabolic & endocrine, Gastrointestinal, Toxicology, Infection, Behavioural — listed life-threatening systems first, instead of one flat list sorted by drug count.
- Newly findable by symptom. Indications that previously had no category — so could only be reached through the drug — now appear in the list: suspected meningococcal septicaemia and open fracture / amputation (both ACP2), plus the CCP set (bradycardia, envenomation / organophosphate, post-ketamine hypersalivation, sedation, suspected hyperkalaemia, refractory shock, severe traumatic pain).
- CCP-only categories stay scoped. Categories that only exist at CCP level appear only when CCP mode is on, tagged gold ⚕ CCP; ACP2 sees just its own indications. No dose, volume or protocol values changed.
v6.78
SAFETY
CCP mode now shows the CCP protocol for pain, and adds the IO fluid route — so a CCP medic sees titrate-to-effect, not the ACP cap.
- CCP protocol replaces the ACP cap. In CCP mode, Fentanyl IV and Morphine IM for significant pain now show the CCP titrate-to-effect protocol (no maximum dose) instead of also showing the capped ACP version alongside it. Where a CCP row exists for the same drug, indication and route, the ACP capped row is hidden in CCP mode. ACP2 mode is unchanged — the caps still apply.
- Sodium chloride gains its IO route. The shock / hypovolaemia fluid bolus now offers IO (intraosseous) as well as IV when in CCP mode, matching the protocol. IV stays the only route in ACP2.
v6.77
SAFETY
CCP beta gains its first dedicated drugs — Ketamine and Atropine (CCP) and Cefazolin (ACP2) — added tentatively from the protocols.
- Three new drugs. Ketamine (severe traumatic pain, CCP) and Atropine (bradycardia, envenomation/organophosphate, post-ketamine hypersalivation, CCP) appear in CCP mode; Cefazolin (open fracture / amputation antibiotic) is added to the ACP2 formulary. Each dose, route, ceiling and preparation was transcribed from the QAS Drug Therapy Protocols and cross-checked three independent ways.
- Tentative — pending sign-off. These are a prototype addition and have not yet been clinically verified for release; they sit under the CCP beta's "not for clinical use without consultation" umbrella. Provenance (source PDF + verbatim quote) is recorded for every value.
- No change to ACP2 dosing. Apart from the new Cefazolin, the existing ACP2 formulary is untouched; Ketamine and Atropine only appear when CCP mode is on.
v6.76
SAFETY
New CCP mode (beta) — an opt-in scope for Critical Care Paramedics, with every page clearly marked gold so you always know your level.
- CCP mode, opt-in via More. A new "CCP mode · BETA" toggle adds the Critical Care Paramedic indications and IV/IO routes that are already in the protocols (Sedation, Shock, hyperkalaemia, and IV/IO/IM routes of existing drugs). Default off; ACP2 is unchanged unless you turn it on.
- Every page is marked while it's on. A gold bar across the top and a gold "⚕ CCP" pill appear on every screen, so it is never ambiguous which scope you are in — the key safety control for a dual-level tool. ACP2 stays unmarked.
- Honest beta. It carries the CCP routes of the existing drugs only — not a full CCP formulary. Two infusion items are hidden until proper infusion-rate support exists. Not for clinical use without consultation.
- "Repeat, no maximum" doses read correctly. A titrate-to-effect repeat with no total cap (e.g. Fentanyl IV) now shows the actual reduced repeat dose and "No max," instead of mislabelling it "same dose."
v6.75
UI
More breathing room on the Feedback page — the intro no longer sits cramped against the heading.
- Feedback spacing. The Feedback heading has no subtitle, so the intro text sat directly under the title and read cramped. Added space above the intro so the page breathes like the others. Presentation only.
v6.74
UI
Version history no longer scrolls sideways, and the Feedback page text is left-aligned to match the rest of the app.
- No more sideways scroll on Version history. Long technical terms in older entries could push this page wider than the screen, letting it drift left and right. Those terms now wrap, and the page is locked to the screen width.
- Feedback text left-aligned. The intro and the disclaimer on the Feedback page were centred, which read awkwardly under the left-aligned heading. They are now left-aligned and consistent. Presentation only.
v6.73
UI
The More pages (About, Save to home screen, Feedback, Version history) now match the rest of the app — a consistent back button and the page title leading the content.
- Consistent back button. The four More sub-pages used an outlined pill back button that looked different from every other screen. They now use the same solid back control as the dose, drug, Resus and Vitals pages.
- Title leads the content. The page title and subtitle used to be squeezed into the top bar beside the back button. They now sit at the top of the page as a heading — the same pattern as Home — so each page has room to breathe and reads consistently. Presentation only.
v6.72
UI
Repeat-once drugs now read as a finite "give once, may repeat once" regime — with the repeat dose, its rule, and a 2-dose cap stated plainly.
- Repeat-once strip. Drugs that may be repeated only once (Fentanyl intranasal, Methoxyflurane, Droperidol) previously showed the same open-ended "Repeat doses · Next dose · Total max" block as titratable drugs, which could read as "keep going to the total max". They now show a dedicated Repeat once strip: the repeat dose + volume, a Final flag, and a "Max 2 doses · ceiling X" line. The total max stays as a quiet ceiling, not a target.
- The repeat rule is shown when it differs. Where the repeat is calculated differently from the first dose (Fentanyl intranasal: 1.5 microg/kg initial, 1 microg/kg repeat), that per-kg rule is shown in amber on the strip — so the smaller repeat reads as intentional, not an error. Same-dose repeats simply read "Same dose".
- Repeat-dose working in Show calculation. The calculation sheet now walks the repeat-dose math (e.g. 1 microg/kg × 18 kg = 18 microg ÷ 50 microg/mL = 0.36 mL) alongside the initial dose, so the lower repeat is never an unexplained number.
- Full plan says "repeat once". Opening the full plan leads with a "Repeat once only · maximum 2 doses" banner. Glucose gel keeps its conditional "repeat once if BGL ≤4.0" wording. No dose values changed — presentation only.
v6.71
SAFETY
Indication-scoped CI filter now fails open on an unrecognised prefix, and a build guard catches malformed prefixes before they can ship.
- Fail-open prefix matching. Previously, a CI line with a typo'd indication prefix would be silently hidden. Now, if the prefix does not match any known indication for the drug, the full line (with prefix visible) is shown rather than omitted — a paramedic seeing "BadPrefix: CI text" is safe; silently missing a CI is not.
- Build-time prefix integrity guard. A new static test (
test_ci_prefix_integrity) validates every indication-prefixed CI against the drug's indication titles at build time. A mismatched prefix fails the build before it can reach a phone.
v6.70
UI
Contraindications sheet only shows CIs relevant to the current indication, and labels each severity group once instead of on every card.
v6.69
UI
The contraindications sheet now shows only what applies to the selected indication, and groups items under a single label instead of repeating it on every card.
v6.68
UI
Single-dose drugs read more clearly — "SINGLE DOSE" header, one tidy regime row — plus a concentration-rounding fix.
- Single-dose cards are clearer. For drugs given as one dose (Ceftriaxone, Hydrocortisone, Glucagon, Ondansetron…), the dose header now reads SINGLE DOSE instead of "INITIAL DOSE" (which implied more were coming). The old separate "SINGLE DOSE ONLY — DO NOT REPEAT" box is folded into one tidy row — Single dose | Total max — the same column format the repeatable drugs use ("single dose" already implies do-not-repeat, and the header reinforces it). Same information, less clutter. Repeatable drugs are unchanged.
- Concentration rounding. On the route screen, Ceftriaxone IM showed its strength as "1 g in 3 mL (0.3333333333333333 g/mL)". The per-mL value is now shown in the familiar unit and rounded — "1 g in 3 mL (333 mg/mL)". Display only; doses and volumes were never affected, and the same rounding now guards every drug's concentration label.
v6.67
DATA
Refractory anaphylaxis is now its own hydrocortisone indication, and the second-line anaphylaxis drugs carry a clear "after adrenaline" reminder.
- Hydrocortisone — refractory anaphylaxis split out. The 4 mg/kg dose (single, max 100 mg, IM and IV) was previously bundled under a combined "Asthma / refractory anaphylaxis wheeze" label. It now has its own Refractory anaphylaxis with persistent wheeze indication, so it shows when you browse by Anaphylaxis. The asthma rows keep the same dose under an "Asthma (unable to tolerate oral medication)" label. Doses are unchanged.
- Adjunct reminders across the anaphylaxis cluster. The second-line anaphylaxis drugs now carry a quiet sub-line (small amber dot) under the indication, reminding you they follow adrenaline rather than replace it: Hydrocortisone "only if unresponsive to 3 × IM adrenaline", Glucagon "only if unresponsive to 3 × IM adrenaline + adequate fluid", and nebulised Adrenaline "with upper airway obstruction, only after 3 × IM adrenaline". Glucagon's long indication title is trimmed (the qualifier moves into the sub-line), and the nebulised-adrenaline gate — previously buried in internal notes — is now visible. Doses are unchanged.
- Nebulised adrenaline (anaphylaxis) verified. The 5 mg NEB row was checked against the Drug Therapy Protocol and confirmed; scope tagging tidied to match the rest of the dataset (no change to who can give it — ACP2 scope).
v6.66
SAFETY
Fixed a preterm gestation tag that was incorrectly showing on the patient bar for every patient.
- Gestation tag now only shows when it should. The amber "27–37 wk" preterm tag could appear on the patient bar for any patient — including children, and on the Indications, Drugs, dose and Resus screens — even when no preterm gestation had been selected. A display rule was overriding the tag's hidden state. It now appears only for a newborn that has actually been set to a preterm gestation. Dose calculations were never affected by this — it was a display-only tag.
- Home label. The header kicker now reads "FIELD REFERENCE" instead of "ACP2 FIELD REFERENCE".
v6.65
UI
Home title moved further down the page for a more balanced top.
- Title spacing. The title block now sits lower again, giving more breathing room below the status bar and clear of the version chip.
v6.64
UI
Home title nudged down so the "ACP2 field reference" line sits clear of the version chip.
- Title spacing. The title block sits a little lower so it no longer crowds the version indicator in the top-right corner.
v6.63
UI
Tidier Home that fits a standard iPhone — feedback no longer hides under the bottom bar, and offline status moves to a clear ✓/✗ up top.
- Fits the screen. The Home was trimmed (the subtitle and the small version line under the title are gone) so everything — including the Send feedback card — sits above the bottom menu on a normal iPhone instead of slipping underneath it.
- Clearer offline status. The version chip top-right now shows a green ✓ when the app is ready to work offline and a red ✗ when it is not, instead of a small line of text below the title.
v6.62
SAFETY
The app now reports crashes automatically so problems can be caught and fixed.
- Automatic problem reporting. If the app ever hits an unexpected error, it now sends a short diagnostic report (what screen you were on and the technical error) so the issue can be found and fixed quickly. No patient details are ever included.
v6.61
RUNTIME
Access revocation now takes effect within 30 minutes instead of 24 hours.
- Faster revocation. The background check that detects a revoked access code now runs every 30 minutes while the app is in use, down from once per day. If access is revoked, the app will re-gate within 30 minutes of the next open.
v6.60
SAFETY
Fixed a preterm gestation tag that could briefly show on a non-newborn patient.
- Gestation tag now always matches the age shown. The amber preterm tag (e.g. "27–37 wk") could momentarily appear on the patient bar next to an older age such as "7 years" when switching from a newborn. The tag is now tied to the same age shown beside it, so it only ever appears for a newborn and can never sit next to an older age.
v6.59
UI
The access-code keypad now shows a "Checking…" pulse while your code is being verified.
- Clear feedback after the last digit. Once you enter the sixth digit of your access code, the dots now pulse with a "Checking…" message while it is verified online, instead of sitting still — so it no longer looks frozen during the brief check.
v6.58
UI
The pilot disclaimer now states the app is for your use only and must not be shared or passed on.
- Personal-use reminder on the disclaimer. The "not for clinical use" splash you tick on each open now adds a line that your access is for your use only — please don't share it or pass the app on to anyone else during the pilot.
v6.57
UI
A preterm newborn is now flagged on the patient bar with an amber gestation pill, so the lower arrest dose is obvious at a glance.
- Preterm flag on the patient bar. When a newborn is set to a preterm gestation (27–37 wk or <27 wk), an amber pill showing that band now appears on the patient bar — next to the age and weight — so it's clear at a glance that this is a preterm baby (whose Adrenaline cardiac-arrest dose is lower: 25 or 10 microg instead of 50).
- Fits the bar. The pill is compact and sits on one line on every current iPhone; term newborns and older patients show nothing new.
v6.56
UI
The "contraindicated — do not give" banner has a cleaner look — a dark card with a bold red header band instead of the solid red block.
- Restyled, not weakened. When a drug can't be given to the patient, the red plate that replaces the dose is now a dark card with a bold red header band (warning icon, CONTRAINDICATED, and an ABSOLUTE/RELATIVE tag), with the reason and "do not administer" beneath. It sits in the same visual language as the rest of the dose page.
- Same safety behaviour. The do-not-give signal is just as unmissable, the plate is still the only thing shown (no dose underneath), and the wording is unchanged — this is a look-and-feel change only.
v6.55
SAFETY
Offline support is back — the app now works with no signal again, using a safer network-first cache.
- Works offline again. The service worker (the bit that caches the app for no-signal use) is back, now that the app is on stable hosting. Open the app online once and it stays available with no signal.
- Network-first, so it can't get stuck. The old cache could wedge on a stale or redirected page. The new one always tries the network first and only falls back to the saved copy when you're genuinely offline — so when you have signal you always get the latest, and you can never be stuck on an old screen.
- Always the current version online. Each release cleanly replaces the previous cached copy, so an online launch is always the freshest build.
- Heavily tested. Reinstated the full offline test suite (install, offline boot, freshness, interrupted-load recovery, version cleanup, self-heal) — this is the safety-critical part, so it ships only with those green.
v6.54
SAFETY
Newborn Adrenaline cardiac-arrest dose now set by gestational age (term/preterm), and the Resus deck's infant arrest dose corrected.
- Gestational newborn dosing. When the patient is a Newborn, the age picker now shows a Gestation at birth choice — Term (≥38 wk), 27–37 wk, or <27 wk. The Adrenaline cardiac-arrest dose follows it: Term 50 microg, 27–37 wk 25 microg, <27 wk 10 microg (per the QAS Adrenaline DTP). Term is pre-selected, so a term newborn is unchanged and needs no extra tap.
- Resus deck infant fix. The Resus Adrenaline page previously applied the 10 microg/kg per-kilogram rule to every patient, which under-dosed infants under 1 year (a newborn showed 30 microg instead of 50; a 3-month-old 60 instead of 70). It now uses the same age-band logic as the drug dose page, so both screens always agree.
- Preterm weight note. Choosing a preterm band shows a reminder that the ~3 kg estimate is a term figure — enter a measured weight for any weight-based drug.
- Everything else unchanged. Gestation only affects the newborn Adrenaline arrest dose; every other drug, age and dose is exactly as before.
v6.53
SAFETY
Clinical data corrections from a protocol re-check — Naloxone and Glucagon routes tightened to match the QAS DTPs, and an Adrenaline entry relabelled to its correct indication.
- Naloxone (paediatric). Route corrected to IM only. The QAS Naloxone DTP lists intramuscular as the only paediatric route, so the previous "IM/IV" listing has been narrowed to IM. Dose (20 microg/kg, max 800 microg) unchanged.
- Glucagon (paediatric). Removed an intravenous entry for symptomatic hypoglycaemia. The QAS Glucagon DTP gives paediatric hypoglycaemia by IM only (paediatric IV glucagon appears only under refractory anaphylaxis). The IM dosing is unchanged.
- Adrenaline. A paediatric IV/IO entry was relabelled from "Bradycardia with poor perfusion" to its correct indication, "Shock unresponsive to adequate fluid resuscitation" — the dose values were always those of the shock entry. Paediatric bradycardia remains consultation-only.
- No other doses changed. These are fidelity-to-protocol corrections from a line-by-line re-check against the source DTP PDFs.
v6.52
SAFETY
Pilot access now uses a personal code for each tester instead of one shared passcode — entered once per device, then the app opens straight away (and still works offline).
- Your own code. Each tester gets their own 6-digit code. Enter it once on a device and that device is approved — every launch after goes straight into the app, no code prompt, online or offline.
- One device per code. A code activates on the first device it's used on; entering it on a second device is refused. This keeps codes from being passed around.
- Stays current. The app quietly checks (when online) that your access is still valid, so a code can be switched off or expired centrally if needed. None of this sends any patient or dosing information — only your code, a random device ID, and the app version.
- Re-entry. You'll only be asked for your code again if the app's data is cleared, it's reinstalled, or your code is turned off.
v6.51
UI
Tapping Back from the Feedback page now returns you to where you opened it — the home screen if you used the home "Send feedback" card — instead of always jumping to the More menu.
- What changed. Feedback can be opened two ways: the "Send feedback" card on the home screen, or More → Feedback. Back used to always go to the More menu, which felt wrong when you'd come from home. Now Back (and the left-edge back-swipe) returns to wherever you started.
- Why it matters. Less surprise — you land back on the screen you came from rather than somewhere you never visited.
v6.50
RUNTIME
Removed the background offline worker that was causing the "can't open the page" errors. The app now loads straight from the web every time — reliably.
- What this fixes. The "Response served by service worker has redirections" dead-ends were caused by the offline background worker choking on the host's hiccups. That worker is now removed, so the app loads directly — exactly like opening it in a private tab, which always worked.
- Self-cleaning. If your phone still has the old worker, opening this version removes it and clears its old cache automatically, so it heals itself.
- Offline is paused for now. The app needs a connection to open. Offline support will return later, rebuilt in a way that can't cause this — once the app is on its new, stable home.
v6.49
RUNTIME
Reworked how the app updates itself so every release installs cleanly and predictably — ending the class of "stuck on an old version / won't update" problems.
- One predictable update model. The background worker that powers offline is now tied to the release version, so every update ships a fresh worker that installs promptly and wipes the previous offline copy. No more mismatch between the version you see and the worker actually running underneath.
- No stale state. The offline cache is now versioned per release and old versions are purged automatically on update, so a bad cached copy can't linger across releases.
- Why it matters. This is the root-cause fix behind the recent "can't open the page" / won't-update episodes — the worker and the app could drift apart and an old worker could keep control. They now move together every release. Guarded by new release-blocking tests (version coupling + a simulated release that proves the old cache is purged).
v6.48
SAFETY
Choosing a new patient's age band now resets the weight to that band's estimate — a manually entered weight can no longer stick across to a different patient.
- The bug. The patient editor pre-fills the current measured weight so you can tweak it. But if you switched the age band (say to Newborn) without clearing it, the old weight rode across — e.g. 80 kg shown for a newborn. That's a serious dosing hazard.
- The fix. Picking a different age band now drops any carried-over manual weight and shows that band's estimated weight instead. If the new patient has a measured weight, just enter it. Applies to both the age-band picker and the older edit sheet.
- Guarded. A release-blocking safety test now sets a manual weight, switches the band, and proves the weight reverts to the new band's estimate.
v6.47
SAFETY
Phones stuck on the old "service worker has redirections" error now repair themselves: a bad saved copy is detected and replaced with a clean one automatically.
- Self-repairing offline copy. A phone left over from the old sign-in setup could have a broken page saved offline, which caused the error even with no signal — and an iPhone's per-site data clear often didn't remove it. The app now checks the saved copy really is the calculator before showing it; if it's the old broken page, it throws it away and fetches a clean one.
- Fixes itself on update. Because this updates the background worker, an affected phone heals the next time it checks for an update, instead of needing a manual wipe.
- If a phone is still stuck: open pedcalc.app in a Safari Private tab to get straight in, or do a one-time Settings → Safari → Clear History and Website Data, then re-add the home-screen icon.
v6.46
SAFETY
Access is now controlled by a passcode inside the app instead of a sign-in page — which fixes the offline failures and the "service worker has redirections" errors for good.
- Why this changes things. The old sign-in page worked by redirecting before the app loaded, which is what broke the offline cache and threw the "service worker has redirections" error. The app now loads directly and asks for a passcode inside the app, so there's no redirect to break anything — online or offline.
- Passcode unlock. Enter the pilot passcode once per device; it's remembered after that and works with no signal. You'll still see the "not for clinical use" agreement each time you open the app.
- One-time reset per phone. To pick this up, each phone needs a single cleanup — clear the pedcalc.app website data and delete + re-add the home-screen icon — then it's stable, and offline works.
v6.45
UI
The home screen now fits a standard iPhone without scrolling — the "Help improve this app" heading is gone and the spacing is tightened.
- Fits on screen. Removed the "Help improve this app" heading above the feedback card and tightened the spacing of the patient card and the menu cards, so the whole home — including Send feedback — sits above the bottom bar on a standard iPhone.
v6.44
UI
The build version now shows on the pilot agreement screen, so feedback can be tied to the exact build you were using.
- Build stamp on the consent screen. The "not for clinical use" screen now shows the build number underneath, making it easy to note which version a problem happened on when you send feedback.
v6.43
SAFETY
Update now repairs itself: if your sign-in has expired when you tap Update, the app sends you to sign in and reinstalls cleanly instead of getting stuck.
- Self-healing update. When you tap Update, the app checks for a clean copy first. If your secure sign-in has lapsed, it now clears the stale offline copy, resets the background worker, and takes you to the sign-in page so it reinstalls fresh — rather than reloading into a dead-end error.
- Always gets the latest worker. The update also pulls the newest background worker before reloading, so a fix like this one can never leave you stranded on an old, broken version.
- Offline is respected. If you're simply offline (not signed out), it keeps serving the saved copy as before — the reset only happens when it actually detects an expired sign-in.
- Guarded. A release-blocking test simulates an expired sign-in during Update and proves the app self-heals instead of wedging.
v6.42
SAFETY
Fixes the "Safari can't open the page — response served by service worker has redirections" error some testers hit when tapping Update.
- What went wrong. The app is served behind a secure sign-in (Cloudflare Access). When a sign-in session quietly expired, tapping Update reloaded the page straight from the network, hit the sign-in redirect, and Safari refused to show a redirected page served by the offline worker — a dead-end error screen.
- The fix. Page reloads now always come from the saved offline copy first, and the app will never store a sign-in redirect in place of the real app. So an expired session can no longer break the page or corrupt the offline copy.
- Guarded. A new release-blocking test simulates the sign-in redirect and proves the app keeps loading and the offline copy stays intact.
v6.41
SAFETY
Pilot build: a "not for clinical use" agreement now appears on every open, the app no longer brands itself as QAS, and there's a feedback shortcut on the home screen.
- Consent on open. Each time you open the app you must confirm you understand it's an unapproved test build that may contain dosing errors and must not be used for clinical decisions — always confirm against the current official Drug Therapy Protocols.
- De-branded for the pilot. The home title now reads "Paediatric Drug Calculator" (no "QAS"), so the app doesn't present as an officially approved product while it's in testing. The protocols the data is drawn from are still credited.
- Feedback on the home screen. A "Send feedback" card sits under the menu so it's quick to report anything wrong or missing. It opens the same form as More → Feedback.
v6.40
SAFETY
Changing the age band now clears a manually entered weight, so a measured weight can never silently carry over to a different patient.
- The fix. If you typed a measured weight (say 20 kg) and then changed the age, the manual weight used to stay put — so a switch to Newborn kept dosing on 20 kg. Now changing the age band drops the manual weight and falls back to the band estimate, exactly like starting that patient fresh.
- Measured weights you mean to keep are safe. When you set age and weight together in the patient editor, your measured weight is re-applied in the same step and preserved.
- Guarded. A release-blocking safety test now proves a stale manual weight can't survive an age change.
v6.39
DATA-ONLY
Maintenance: the drug dataset was rebuilt from the cleaned source workbook so its internal fingerprint and build date line up. No drug, dose, route, maximum or protocol value changed.
- Why. The source spreadsheet was tidied earlier (guide sheet, column order, formatting), which changed its file fingerprint but none of its clinical content. The built dataset is now regenerated from that workbook so the two match.
- No clinical change. All 72 drug rows are byte-identical to the previous release — verified automatically. Nothing a paramedic sees or calculates changes.
- Housekeeping. Also prunes the snapshot archive to its retention window and adds an internal clinical-output export tool.
v6.38
UI
Resus airway card: the i-gel now shows its standard size colour as a square (like the OPA) and stacks the OG tube size underneath the size, so both devices read the same way at a glance.
- i-gel colour square. The i-gel size now carries its standard colour-coded square (size 2 = Grey, 2.5 = White, 3 = Yellow, and so on), matching the OPA's colour cue. The colour comes from the resus dataset, not hand-typed in the screen.
- OG tube stacked. The OG tube size now sits on its own line under the i-gel size rather than running on beside it.
- Presentation/data. Adds the i-gel size colour to the dataset; no size, OG-tube or dose values changed.
v6.37
UI
Fix: the OPA colour square on the Resus airway card was invisible for dark colours (e.g. Black) because it blended into the dark card. The swatch now has a hairline outline and is a touch larger, so every colour reads clearly.
- OPA swatch always visible. A light hairline border (plus a faint dark ring) is added to the OPA colour square and it is bumped from 13 px to 16 px, so dark colours like Black no longer disappear into the card background.
- Presentation only. No size, colour, OG-tube or dose values changed.
v6.36
UI
Resus page airway card is tidier: the OPA now shows a small colour square instead of spelling out the colour name, and the i-gel line drops the weight bracket and shows just the OG tube size.
- OPA colour as a swatch. The OPA size now sits beside a small coloured square (the QAS colour code) instead of the colour name in words — the square is the sizing cue at a glance. The full colour names are still in the OPA size chart behind the "i".
- i-gel line trimmed. The i-gel now shows the size and its OG tube size only; the weight bracket is dropped from the card (it is still in the i-gel size chart behind the "i").
- Presentation only. No size, colour, OG-tube or dose values changed.
v6.35
UI
A few small tidy-ups: the long Source protocols list on the About page is now collapsed by default (tap to expand), the More menu shows Version history above Feedback, and the Next dose draw-up volume on dose pages is a touch larger.
- About page is shorter. The Source protocols list (every QAS DTP the dataset draws from) was always expanded and pushed the rest of the page down. It now sits behind a single "Source protocols" row — tap to expand it when you need it.
- More menu order. Version history now appears above Feedback.
- Easier-to-read repeat volume. On the dose pages, the draw-up volume in the green slab under "Next dose" is bumped up a little so the millilitres are quicker to read. The value is unchanged.
v6.34
UI
The Resus deck is simplified to two tabs — Resus and Adrenaline. The separate CPR and Airway tabs are removed now that their key details sit on the Resus page, and the CPR Technique and Ratio now stack in a clearer column.
- Two tabs instead of four. The CPR and Airway chips are gone. The Resus page already shows the CPR Technique and Ratio and the OPA + i-gel airway sizes (tap an "i" for the full size charts), so the standalone pages were redundant. The deck is now just Resus and Adrenaline.
- CPR column stacks. On the Resus page the CPR labels (Technique, Ratio) now sit above their values instead of running into them, so the column reads cleanly beside the energy.
- Note. The old CPR page also listed a compression rate (100–120/min) and depth (⅓ chest); with that page removed, those two figures are no longer shown in the app. Pad placement, energy, CPR ratio, airway sizes and all drug doses are unchanged.
v6.33
UI
The Resus page is reworked: the manual energy now sits beside the CPR essentials, the airway sizes (OPA and i-gel) fold in below, and each device has an "i" that opens the full size chart in a slide-up sheet.
- Energy and CPR side by side. The manual defib energy moves to the left of the card with the CPR Technique and Ratio beside it, so the shock energy and the compression basics read in one glance. The compression rate stays on the CPR page. No energy, pad or CPR values changed.
- Airway sizes on the Resus page. A new airway card shows the patient's OPA size and colour and the i-gel size with its OG tube — both age/weight-derived from the same data as the Airway tab. Tap the "i" on either device to open the full OPA or i-gel size chart in a slide-up sheet, with the patient's row highlighted.
- Tidier header. The first chip is renamed "Resus" and the page title is dropped (the active chip already names the page); the source line is removed from the page. The Airway tab is unchanged.
v6.32
UI
App status and "Check for updates" now live in one place. Tapping the version in the top-right corner opens the More tab and highlights its App status card, instead of a second, separate popover.
- One place for app status. The version, last-checked time, offline-ready state and "Check for updates" used to appear both in a popover behind the corner version chip and in the App status card on the More tab. The popover is retired — tapping the version chip now switches to the More tab and rings that card, so there is a single status surface with one entry point. No version numbers, update logic or offline behaviour changed.
v6.31
UI
The Defib page now also shows the CPR essentials — Technique, Ratio and Rate in one bar — and is renamed "Defib & CPR" to match.
- CPR at a glance on the Defib page. Below the energy card, a CPR · compressions bar shows the age-appropriate Technique (2 hands · sternum lower half, or 2 thumbs · encircling for infants), the Ratio (15:2, or 3:1 for newborns) and the Rate (100–120/min) — the same values as the CPR tab, so they can't drift. The page is renamed "Defib & CPR". No defib energy or pad values changed.
v6.30
UI
The "More" page heading now uses the same font weight and letter-spacing as the other page titles, so it no longer reads heavier than the rest of the app.
- More heading brought in line. The "More" title was the only heading set in the heaviest weight (900) with tighter letter-spacing, making it stand out from every other page. It is now weight 800 at −0.4px tracking — matching the Resus, Vitals and drug page titles — while keeping its larger large-title size. Presentation only; nothing else on the page changed.
v6.29
UI
The Resus Adrenaline page now matches the Drugs dose page — the same INITIAL DOSE → DRAW UP action-pair card and the three-column Repeat doses bar (Next dose · Interval · Total max).
- Adrenaline reads the same everywhere. The Resus Adrenaline card was still using the older stacked layout. It now renders the identical action-pair design as the Drugs dose page — dose alongside the mint DRAW UP volume, the per-kg rule in the INITIAL DOSE band, and a three-column Repeat doses bar (Next dose · Interval · Total max). Every value comes from the same dataset — no dose figures changed.
v6.28
UI
The adult Defib figure now labels Anterior under the front view and Lateral under the side view (spread apart like the paediatric labels), and a stray fragment at the bottom of the illustration has been cleaned up.
- Clearer adult pad labels. The single "Anterior – Lateral" caption is split into two labels — "Anterior" under the front view and "Lateral" under the side view — matching the paediatric "Anterior / Posterior" layout. The illustration is also tidied (a stray fragment from the source page at the bottom-right is removed). No energy or pad values changed.
v6.27
UI
The adult Defib pad figure now spans the card (lined up with the "Adult pads" title) and carries an "Anterior – Lateral" label below it, matching the paediatric "Anterior / Posterior" labels.
- Clearer adult pad figure. The adult anterior–lateral illustration is larger and edge-aligned with the title, and now has a centred "Anterior – Lateral" caption underneath in the same style as the paediatric labels. No energy or pad values changed.
v6.26
UI
The Defib page is redesigned: the pad-placement figures sit side by side (anterior + posterior), the energy reads as a clean "Manual energy · 4 J/kg · 75 J" card, and the calculation tucks behind an "i". The "Cardiac arrest" banner is removed from the resus pages.
- Tidier Defib page. The paediatric figures are now side by side — anterior (chest) and posterior (back) — instead of stacked, so the card is shorter. The energy card leads with "MANUAL ENERGY", the per-kg rule, and the joules; tap the "i" to see the full working. The "Alternatives & AED mode" list is removed.
- Cleaner resus pages. The "Cardiac arrest" indication bar is dropped from the Defib, CPR and Airway pages — the whole Resus section is cardiac arrest, so it was redundant. The Adrenaline page is unchanged. No energy, pad or dose values changed.
v6.25
UI
The Defib page now shows the official QAS pad-placement illustration instead of the simple diagram — the actual figure from the Defibrillation protocol, swapping between the paediatric (front + back) and adult (front + side) placement with patient age.
- The real placement diagram. The Resus → Defib page replaces the small symbolic torso with the official QAS illustration from the Defibrillation CPP: paediatric anterior–posterior (pad on the chest + pad between the shoulder blades) under 6 years, adult anterior–lateral (upper-right chest + left flank) at 6 years and over. It's the same picture clinicians train on, and it swaps automatically with the patient's age. Energy, pad type and placement wording are unchanged.
v6.24
UI
You can now swipe back from the More sub-pages — About, Add to home screen, Feedback and Version history — to return to the More menu, the same left-edge back-swipe used elsewhere in the app.
- Swipe back from More pages. The About, Install, Feedback and Version history pages now respond to the left-edge back-swipe, taking you back to the More menu — matching the Back button on each page and the swipe-back you already use on drug and dose pages.
v6.23
UI
On the Repeat doses card, the volume under "Next dose" now sits in a small green slab, matching the main DRAW UP block — so the amount to draw for the repeat reads in the same familiar format.
- Repeat draw-up in the familiar format. The "draw this" volume under the Next dose (e.g. 0.44 mL) is now a compact green slab like the main DRAW UP block, instead of plain mint text — quicker to spot as the volume to draw. The figure is unchanged, and the card is the same height.
v6.22
UI
In-app feedback now goes to both the team's chat channel and email, so nothing gets missed. The form is unchanged — type and send.
- Two ways through. Feedback you send from the Feedback page now posts to the team's chat channel (with your app version and device) and emails a backup copy. If one path is ever down, the other still gets through.
v6.21
UI
In-app feedback now goes straight to the team's chat channel, so problems and suggestions are seen and actioned faster. The form works exactly the same — type and send.
- Faster to the team. When you send feedback from the Feedback page, it now lands in the team's chat channel along with your app version and device, so reports don't sit in an inbox. Nothing changes for you — write what happened and tap Send.
v6.20
UI
In the "Show calculation" sheet, Initial dose, Repeat dose and Dose ceilings are now separate cards instead of one continuous list, so the three parts of the working are much easier to follow.
- Three clear blocks. The calculation used to run together as one ledger — initial dose, repeat dose and the ceilings blended into each other. Each section now sits in its own card (the same card style as the dose page), so it's obvious where one part ends and the next begins.
- Same numbers, barely taller. Only the grouping changed — every figure is identical. The cards are trimmed so the sheet grows only slightly; the busiest drugs already scrolled and the rest still fit.
v6.19
UI
On dose pages with a Low/High or per-kg selector (e.g. Fentanyl IM, Morphine IM), the selected option is now clearly marked with a mint outline ring, so it's obvious at a glance which dose you've picked.
- Clearer selection. The selected segment of the dose toggle used to be only a slightly lighter knob — hard to read at a glance. It now carries a thin mint outline ring, making the active choice unmistakable. The dose figure and DRAW UP value are unchanged.
- Mint stays for the numbers. The ring is a hairline accent only, not a fill, so it doesn't compete with the mint dose and volume values.
v6.18
UI
The "Reference only" line at the bottom of the dose page is now a single short line instead of two, reclaiming vertical space in the field. The full disclaimer still lives in About.
- Shorter disclaimer. The two-line "Reference only · Always confirm against current QAS guidelines and use clinical judgement." footer is now a single quiet line — "Reference only · confirm & use clinical judgement" — so it costs less vertical space at the dose. The complete disclaimer remains on the About page.
v6.17
UI
Dose and drug pages that fit on screen no longer scroll. The empty space reserved below the content for the floating nav was over-sized (it double-counted the phone's safe area), so short pages scrolled even when everything was visible. They now sit still and only scroll when the content genuinely runs long.
- No scroll when it fits. The clearance reserved at the bottom for the floating navigation pill was ~96px plus the full home-indicator safe area — but the pill already sits inside the safe area, so that was double-counted, leaving ~130px of empty tail. Short pages like Fentanyl IM scrolled despite fitting. The clearance now tracks the pill's actual height plus a small gap, so a page that fits stays put.
- Long pages unchanged. Pages with more content still scroll, and the last row still scrolls fully clear of the floating nav.
v6.16
UI
The pinned top of the dose page now reads as one continuous frosted pane. The small gap between the Back row and the patient bar is no longer see-through — it blurs the dose content like the rest of the pinned top, so there's no transparent seam.
- No transparent seam. The thin band between the Back row and the patient bar used to be see-through, showing dose content sharply through it. It now carries the same frosted blur as the rest of the pinned top, so the Back row and patient bar feel like one cohesive frosted surface — the standard for pinned navigation chrome.
- Patient bar still defined. The age/weight card keeps a slightly stronger frost so it stays clearly readable within the band.
v6.15
UI
The pinned top of the dose page — the Back row and the patient age/weight bar — is now frosted glass like the floating bottom nav: translucent with a soft blur, so dose content reads softly through it as you scroll. The Back row also sits 2px higher.
- Frosted pinned top. The Back row and the patient bar now use the same translucent, blurred treatment as the floating bottom navigation, so the pinned top feels like one cohesive layer and dose content frosts softly beneath it instead of sliding under a solid block.
- Patient stays legible. The fill is kept dark enough that the age and weight stay clearly readable even when the bright DRAW UP volume scrolls behind the bar.
- Back row 2px higher. The Back button moves up a touch under the status bar / Dynamic Island (this is the change the previous release intended; it now takes effect).
v6.14
UI
The patient age and weight now stay pinned at the top of the dose page while you scroll, so the weight every dose is calculated from is always in view. The Back row also sits a touch higher under the status bar.
- Patient always in view. On the dose page the age/weight strip used to scroll away with the content. It now stays pinned just under the Back bar at any scroll depth — the weight is the basis of every dose on the page, so it should never leave the screen. Reverses the earlier "everything scrolls" dose layout for the patient strip only.
- No layout jump. The strip pins exactly where it already sits, so engaging the pin is seamless, and dose content tucks cleanly underneath it as you scroll.
- Tighter Back row. The Back button sits 2px higher under the status bar / Dynamic Island, reclaiming a little vertical space while still clearing the safe area.
v6.13
UI
Horizontal swipes that changed clinical context are removed: a flick can no longer switch you to a neighbouring drug or change the route under your thumb. You change drug and route only by a deliberate tap. The left-edge swipe-back is unchanged. Also fixes a stray vertical line in the Show calculation sheet.
- No accidental drug change. Swiping sideways on a drug page used to cycle to the next drug in the list — an easy accidental bump while scrolling that silently swapped which drug you were reading. That gesture is gone; change drug from the list or the bottom tabs.
- No accidental route change. A sideways flick on the dose page used to switch the route, which also changes the dose and volume. Removed for the same reason; change route with the route pill or picker.
- Back is untouched. The left-edge swipe-back still steps up one level exactly as before — only the context-changing horizontal swipes were removed.
- Calculation sheet tidy. A thin vertical line that could appear down the right of the Show calculation sheet (a horizontal-overflow artefact) is gone. No figures changed.
v6.12
UI
The "Show calculation" sheet is redesigned: a clean worked-equation layout with an operator rail (×, ÷, =), one tidy step for the concentration, an amber line when a dose is capped, and a Single max / Total max footer. Band-dosed drugs now read as a proper table with your patient's band ticked.
- A worked equation, not a ledger. Per-kg drugs (Fentanyl, Ceftriaxone, Morphine, Dexamethasone…) show the dose rule, × weight, any rounding, the capped final dose, then ÷ concentration → volume — each step on its own row with an operator badge. The repeated concentration lines are gone.
- Bands as a real table. Age/weight-band drugs (Adrenaline, Glucagon, Salbutamol, Ipratropium) show every band in an outlined table with a mint header and your patient's band ticked, so you can sanity-check the match at a glance.
- Limits in one place. A Single max / Total max footer shows the ceilings — amber when a cap was applied — and every figure comes from the same engine as the dose, so the working can't drift from the number.
v6.11
UI
Two tidy-ups on the drug pages: the cards under the patient bar now line up to the same width, and the repeated "not a replacement for your training" disclaimer is removed from the drug list and the drug picker.
- Cards line up. On the drug indication picker, the "Select indication" card and the indication rows now sit at exactly the same width as the patient bar above them, so every edge lines up cleanly.
- Less repetition. The long "this tool is not a replacement for your training" disclaimer that repeated at the bottom of the drug list and the picker is removed. The full version still lives in About, and the dose page still carries its "Reference only · always confirm" line right at the dose.
v6.10
UI
The bright mint header band is gone across the app: Home, the drug list, Resus, Vitals and the drug indication picker now all use the same dark forest header as the dose page, and the redundant centred page titles are dropped — the highlighted bottom tab, the active sub-tab and the body already name each page.
- Home loses the mint strip. The thin mint band at the top of Home is removed; the forest background runs straight to the top edge with the title sitting just below the status bar.
- One header everywhere. The Pharmacology/Drugs list, Resus, Vitals and the drug indication picker all drop their bright mint band for the same dark forest header as the dose page — light title, and the same white Back/Home pill. Consistent top-to-bottom.
- Page titles dropped, no jump. The centred header titles on the list, Vitals, Resus and the drug picker are gone — the highlighted bottom tab, the active sub-tab and the body already name each page. Headers are sized to match the dose page so the patient strip holds its position and nothing jumps as you move in and out. (More-tab pages keep their titles.)
v6.09
UI
The More tab is now one consistent design: About, Install, Feedback and Version history have moved off the old pale theme onto the app's dark forest look, with the same header, mint accents and cards as the rest of the app.
- No more theme jump. Tapping a More row used to drop you onto a pale-cream page that looked like a different app. Every More page now uses the same dark background, header and Back control as Home, Pharma and Resus.
- Cards match the app. The About safety box, data panels, install steps, the feedback form and the version-history timeline are all rebuilt with the app's dark cards, mint section labels and accent pills. Wording and information are unchanged — this is purely the look.
v6.08
UI
For drugs dosed by age or weight band (Adrenaline, Glucagon, Salbutamol, Ipratropium…), the "Show calculation" sheet now lays every band out as a clear table with your patient's band highlighted — so you can see the whole ladder and check the match at a glance.
- The whole band ladder, at a glance. Instead of "Band selected" plus a separate "other bands" list, the calculation sheet now shows one outlined table — every age (or weight) band and the dose it gives — with your patient's band highlighted in mint. Easy to sanity-check that, say, your 10-year-old really is in the 6 yr+ band.
- Total max where it differs. When the bands carry different total-dose ceilings (e.g. Ipratropium 750 microg vs 1.5 mg), the table adds a Total max column so each band shows its own limit.
- Read-only by design. The band is set by the patient's age/weight — you can't tap to pick a different one here. If a band looks wrong, fix the patient and every screen updates together.
v6.06
SAFETY
Glucose 10% now calculates its reduced repeat dose like every other drug — a computed Next dose with the "until BGL >4.0" stop condition, instead of a per-kg rate you had to multiply yourself.
- The repeat is now worked out for you. Glucose 10% repeats at a deliberately smaller 0.1 g/kg until BGL >4.0. It now shows the calculated Next dose (e.g. 1.8 g / 18 mL at 18 kg) · Interval 5 min · Repeat until BGL >4.0, exactly like the other repeat-dose drugs. Values come straight from the same calculator.
- Stop condition kept. The "until BGL >4.0 mmol/L" target stays on the card, now in the same slot the other drugs use for their cap. Guarded so the computed repeat and the condition can't be dropped again.
v6.05
UI
The Set-patient sheet got clearer: age and weight as two big aligned tiles, a bright "Tap to edit" weight button, and bigger age buttons + weight keypad.
- Weight is unmistakable. The weight sits in its own mint-outlined tile beside the age, with a full-width "✎ Tap to edit" bar. The label reads WEIGHT · ESTIMATED, switching to MEASURED once you enter a real weight.
- Bigger, easier to hit. Age buttons and weight keypad keys are taller, numbers larger, more breathing room above the action buttons. The two age sections keep their titles and divider. Fits an iPhone SE with no scrolling.
v6.04
UI
The bottom-nav tabs behave like the Home cards: tapping Pharma, Resus or Vitals with no patient names the age/weight prompt and continues to that tab once set.
- Bottom nav remembers too. Tapping a clinical tab (Pharma / Resus / Vitals) without a patient used to drop you on a bare "Set patient" screen. Now the same "Continue to …" sheet appears and continues straight to the tab when you set age/weight. Home and More open instantly as before.
v6.03
UI
No more bouncing back to Home: tapping a Home section card with no patient names the age/weight prompt and takes you straight there once you've set the patient.
- It remembers what you tapped. Tapping Resus, Vitals, By indication or By drug with no patient set used to ask for age/weight and then drop you back on Home. Now the patient sheet shows "Continue to …" and continues straight to that section once set.
- Cancel is safe. Dismiss the patient sheet and nothing jumps.
v6.02
UI
Fixed the floating nav going missing for anyone who'd switched it off during the beta, and restyled the dose card: the DRAW UP volume is a solid mint chip with the dose dimmed beside it, and the green glow is gone.
- Floating nav fixed. If you'd turned the floating nav off while it was in beta, v6.01 left you stuck on the old docked bar with no way back. Floating is now the one and only nav for everyone — that stale setting is ignored and cleared.
- Volume leads by colour, not glow. The DRAW UP box is filled solid mint (dark text); the dose number beside it is dimmed to a cross-check. The previous mint glow/halo is removed. Values unchanged — presentation only.
v6.01
UI
The floating bottom nav is now simply the navigation — the "Floating nav" toggle under More was removed, retiring the classic docked bar from the menu.
- One nav, no toggle. Floating became the default in v6.00; this removed the More-menu switch so there is just the one nav. Presentation only — navigation behaviour and every dose page are unchanged.
v6.00
UI
The floating bottom nav was promoted to the default look — a frosted pill floating above the bottom of the screen, spanning the card width with the active tab labelled.
- Now the default. After several rounds of tuning in beta, the floating nav was promoted to production — on for everyone by default. Presentation only.
v5.99
UI
Floating nav (beta): the frost behind the pill became a proper Instagram-style 12px blur instead of the barely-there 2.4px.
- Frosted blur. The blur behind the floating pill was raised to 12px — a real frosted-glass effect like Instagram's bar. The page content behind is still visible, just softly blurred.
v5.98
UI
Floating nav (beta): the pill sits lower, hugging the bottom edge like Instagram's bar.
- Sits lower. The floating pill dropped ~20px toward the bottom of the screen so it hugs the home-indicator zone like Instagram's bar, instead of floating noticeably above it.
- Beta tuning only. Still OFF by default; the full-width labelled bar is unchanged. Toggle under More → "Floating nav (beta)".
v5.97
UI
Floating nav (beta): the pill spans the same width as the cards (18px side margins) instead of a narrow centred pill, and dose pages get extra bottom room so the last rows scroll fully clear of the floating nav.
- Full card width. The floating pill stretches to the same left/right margins as the cards above it, rather than hugging its tabs in the centre. Tabs spread evenly across; the active tab keeps its mint label-pill.
- Bottom content no longer trapped. The floating pill sits higher off the screen than the old docked bar, so on longer dose pages the last rows (total max, Full plan) could sit behind it with no room to scroll. Dose pages get extra bottom padding in this mode so everything scrolls fully clear.
v5.96
UI
Floating nav (beta): the v5.95 green glow behind the pill was removed (it read as an unfinished half-screen wash). The transparent pill lets the page show through, and the "Repeat dosing below" tag stays as the explicit scroll cue.
- Glow removed. The v5.95 mint→forest glow only rose part-way up the screen, so it looked cut off. Gone — the mostly-transparent pill lets page content bleed through, which is the natural "there's more to scroll" cue.
- Scroll cue kept where it matters. The "Repeat dosing below ⌄" tag remains (lifted clear above the floating pill) — it names the cut-off dosing / total-max content. The standard full-width bar keeps its soft fade.
v5.95
UI
Floating nav (beta) made genuinely see-through (lighter background, blur cut right down) and the over-fold dark fade dropped under it. A green glow tried in this release was removed in v5.96.
- See-through, not frosted. The floating-nav beta pill had its background dropped right down (now mostly transparent) and its blur cut to almost nothing, so you can actually see the page content behind it instead of a frosted panel.
- No dark fade under the floating nav. With the transparent floating nav the over-fold dark fade was redundant, so it is dropped in this mode — the "Repeat dosing below" tag stays (lifted clear above the pill) so cut-off dosing/total-max content is still flagged. The standard full-width bar keeps its fade unchanged.
v5.92
UI
New optional look for the bottom navigation: a floating, frosted, centred pill where the tab you are on expands to show its label and the rest are icons. Off by default — turn it on under More → "Floating nav (beta)" to try it.
- What it is. Instead of the full-width bar, the nav becomes a rounded translucent pill that floats above the bottom of the screen, grouped to the centre. The active tab (e.g. Pharma) shows as a filled mint pill with its label; the other tabs show just their icon. A modern, lighter look.
- Opt-in only. It is OFF by default — the standard full-width labelled bar is unchanged. Toggle it on under More → "Floating nav (beta)" to trial it on your phone; toggle off to revert. Presentation only — navigation behaviour and every dose page are unchanged.
v5.91
SAFETY
Dose pages that run past the bottom of the screen now show a soft fade at the foot, and — when the dosing/total-max block is the part cut off — a small "Repeat dosing below" tag, so it is clear there is more to scroll to (the pinned bottom bar could otherwise make a page look complete).
- Why. A few dense pages (the consult fluids like Sodium chloride, Droperidol; the taller Morphine/Fentanyl IM cards) extend below the fold on a phone. With the nav bar pinned at the bottom, the page could read as finished, risking a missed total max or reassess instruction below the fold.
- What shows. Whenever a dose page can still scroll down, a gentle fade appears above the bottom bar. If a priority block (the dosing / repeat / total-max panel) is the content being cut, a labelled tag ("Repeat dosing below ⌄" / "Total max below ⌄") names what is down there. Both disappear once you reach the bottom, and never appear on pages that already fit. Presentation only — no dose values change. Guarded release-blocking by build/test_fold_cue.py.
v5.90
SAFETY
Fentanyl IM and Morphine IM at the higher weights, High dose: the repeat panel no longer shows an empty "next dose". When the first dose hits its 50 microg / 5 mg single-dose limit but the total maximum still leaves a little room, the remaining amount now appears as a clearly-marked ceiling-limited TOP-UP, with a note that it is NOT the standard repeat. Affected only these two drugs on the High selection.
- The defect. On Fentanyl IM and Morphine IM, selecting the High dose for a heavier child (about 9–12 years) capped the first dose at its single-dose maximum while the per-kg total maximum sat higher — so the repeat panel showed an interval and a total max above the dose, but no actual next-dose amount. It implied another dose was due without saying how much.
- The fix. That remaining amount is the ceiling-limited top-up the calculator already knew about (e.g. Fentanyl High at 41 kg: 50 microg given, 82 microg ceiling, so a 32 microg top-up). It now shows as its own amber TOP-UP dose + draw-up volume, with an explicit "ceiling-limited — not the standard repeat dose" note, and the Full plan opens the complete sequence.
- Unchanged. The default Low selection, lighter weights (which correctly read "Total max reached"), and every non-variable drug are untouched. Guarded release-blocking by a new top-up surface check (test_bucket_surfaces P11), proven to fail on the previous build for both drugs.
v5.89
UI
Setting the patient is cleaner. The age picker now splits clearly into "Age in months" (under 1) and "Age in years", the duplicate weight estimate is gone from every age button (it shows in the readout once you pick), and a measured weight is entered by tapping the weight chip. The separate weight button and the New patient reset are removed from the sheet.
- Months and years, labelled. The band grid now carries two unit-led headers — "Age in months · ≤ 11 mo" over the infant row and "Age in years · 1–12 yr" over the rest — so the month/year boundary is unmistakable in the field.
- No repeated weight. Each year button used to print its own "~kg" estimate; that number already appears in the readout the moment a band is selected, so it is dropped from the buttons to cut clutter.
- Tap the weight to type one. The estimated-weight chip in the readout (dashed, with a ✎) is now the way in to the measured-weight keypad; the separate dashed "Weight known?" button is retired.
- No New patient button here. Re-entering an age or weight already replaces the patient, so the sheet's New patient reset is removed. (The underlying reset is unchanged and still available to the app.)
v5.88
UI
Single-dose drugs no longer say "single dose" twice — the repeat row that read "Regime: Single dose" is gone, leaving the clear "SINGLE DOSE ONLY — DO NOT REPEAT" banner as the one signal. Drugs with a dose ceiling still show it.
- One single-dose message, not two. Single-dose drugs (Adrenaline nebuliser, Glucagon, Ceftriaxone, Dexamethasone, Hydrocortisone, Naloxone, Ondansetron…) used to show a "Regime: Single dose" row AND the "SINGLE DOSE ONLY — DO NOT REPEAT" banner. The redundant row is removed; the banner is the single source.
- Dose ceilings kept. Where a single-dose drug has a maximum (Ceftriaxone 1 g, Ondansetron 4 mg, Naloxone 800 microg…) it still shows as a lone TOTAL MAX; drugs with no ceiling simply show the banner. No dose values changed.
v5.87
UI
DRAW UP volume stands out more — the volume to draw into the syringe now sits in a gently highlighted box that lifts off the card with a soft mint glow, so the number you act on catches the eye first. The dose in mg is unchanged beside it as the cross-check.
- The number you draw leads. On the dose card the DRAW UP box (the volume you actually draw up) now has a mint outline and a soft glow and floats slightly forward. Nothing is bigger — both numbers stay the same size — the volume simply reads as the primary, do-this figure, with the milligram dose right beside it as the governed cross-check.
- Presentation only. No dose, volume or eligibility value changed — this is purely how the draw-up box is styled.
v5.86
UI
Consistent "time between doses" — the dosing INTERVAL now sits in the same labelled column on every repeatable drug, whether it repeats the same dose with no maximum (Adrenaline, Salbutamol) or has a dose plan (Morphine, Fentanyl). PRN shows as INTERVAL = PRN, not a blank.
- One place to look for the interval. Repeatable drugs now share one REPEAT block with the same three labelled columns — NEXT DOSE · INTERVAL · TOTAL MAX — so the time between doses is always in the same spot. Previously a no-max drug showed the interval woven into a one-line strip ("Repeat every 5 min …"), while dose-plan drugs showed it as a labelled column; now both read identically. Same-dose drugs show "Same dose" as the next dose and "No max"; dose-plan drugs add the dose-count dots and the Full plan button.
- PRN reads as a value. Drugs repeated as needed (Salbutamol nebuliser) now show INTERVAL = PRN in that column, so it's clearly "no fixed interval", not a missing field. No dose values change.
v5.85
DATA
Salbutamol nebule presentation now matches the age — 1–5 years shows the 2.5 mg/2.5 mL nebule, 6 years and over shows the 5 mg/2.5 mL nebule, instead of listing both nebules on every age.
- Age-specific nebule. The Salbutamol bronchospasm nebule dose is already age-banded (2.5 mg for 1–5 years, 5 mg for 6 years and over), but the Presentation line showed "2.5 mg/2.5 mL or 5 mg/2.5 mL nebule" for both ages. It now shows only the nebule that matches the dose — 2.5 mg/2.5 mL for 1–5 years, 5 mg/2.5 mL for 6 years and over — so the presentation reflects what to reach for at that age. Dose values unchanged.
v5.84
UI
New dose page is now the default — the redesigned dose card (dose → DRAW UP pair, grouped repeat block, quiet selectors) that has been in beta is now what every drug shows. The old layout and its beta toggles are gone.
- Out of beta. The new dose page — the linked dose → draw-up pair on every card, the grouped Repeat doses block, the compact safety row and the quiet Low/High selectors — is now the production dose page for all drugs. No setting to turn on; it is simply the app. The old layout and its beta toggles are gone.
- Repeat instructions verified preserved. A full check of the new page against the old confirmed every dose, volume and repeat instruction carries across. Three were restored where the new page had been too terse: Glucose gel ("repeat if BGL ≤4.0"), Glucose 10% ("repeat until BGL >4.0") and the Sodium chloride bolus plan ("may be repeated twice — reassess between boluses · 60 mL/kg"). No dose value changed.
- Guarded. A new release-blocking check makes sure conditional repeat instructions and fluid reassessment wording can never silently drop again.
v5.83
UI
Range-dose cards join the new pair — Low/High drugs (Morphine IM, Fentanyl IM) now show the selected end as the same "dose → DRAW UP" pair, and their Low/High switch is a quiet raised toggle so it no longer competes with the dose number. Dose-design-v3 beta only.
- Range drugs match the rest. Drugs with a Low/High dose switch used to keep the old stacked layout (dose number above a detached volume slab). The selected end now reads as the same linked pair — dose → volume-in-a-box — as every other dose card, so the whole calculator is consistent and the card is shorter.
- Quieter switch. The selected Low/High button was a loud mint fill that pulled the eye away from the dose. It is now a quiet raised slate toggle; mint is reserved for the dose and the volume to draw. Switching ends still updates the dose + volume exactly as before — values are unchanged. Dose-design-v3 beta only — out for assessment, not yet the default.
v5.82
UI
New dose card (beta, for assessment) — the dose and the volume to draw now sit side by side as a linked pair, "dose → DRAW UP", with the volume framed in a soft mint box. The clinical dose still leads; the volume is the clearly-marked "draw this" step. Dose-design-v3 beta only.
- Dose and volume as one statement. The big dose number and the DRAW UP volume used to be two separate blocks (number, then a detached slab). They now read as a single linked pair — dose → volume-in-a-box — so the relationship "this dose = this volume to draw" is explicit and both are visible at a glance, in less vertical space. The dose mass stays the governed lead; the volume is framed as the action.
- Handles every case. Fixed-dose (Adrenaline), per-kg (Ceftriaxone), measured oral (Dexamethasone) all show the pair; no-draw-up drugs (Salbutamol puffs) show a single dose; the weight/volume rounding captions are unchanged. Dose-design-v3 beta only — out for assessment, not yet the default.
v5.81
UI
Consistent INITIAL DOSE label — on the dose-layout beta, fixed-dose drugs (Adrenaline, Salbutamol) had their INITIAL DOSE header centred while per-kg drugs had it left-aligned; every dose card now left-aligns the label so the header reads the same on every drug.
- The INITIAL DOSE label is left-aligned on every drug. Per-kg drugs carry the label in a left-aligned header band (with the per-kg rate); fixed-dose drugs (Adrenaline anaphylaxis/arrest, Salbutamol) rendered the same band but inherited the card's centring, so the label sat centred and looked inconsistent. The fixed-dose header is now left-aligned too. The big dose number and DRAW UP block stay centred, exactly as on the per-kg cards. Presentation only.
v5.80
UI
Rounding is now shown on the dose card, not just in the Calculation sheet — when the protocol rounds the patient's weight (Ceftriaxone, Ondansetron) or the drawn volume (Dexamethasone), the card now spells out what was rounded and from what. Dose-layout beta.
- Weight rounding on the card. Drugs whose protocol rounds the weight to the nearest 5 kg (Ceftriaxone IM/IV, Ondansetron IM) now show a line in the INITIAL DOSE header — e.g. "on 25 kg · rounded up from 22 kg" — so the weight the dose was calculated on is visible without opening the Calculation sheet. Shows only when rounding actually changed the weight.
- Volume rounding on the card. Dexamethasone PO (Croup / Asthma) rounds the drawn volume to the nearest 0.5 mL, which also nudges the dose. A line under the DRAW UP block now reads "↻ 1.5 mL · rounded from 1.35 mL · nearest 0.5", so the rounding is explicit at the point you draw up. Shows only when rounding changed the volume.
- No dose values change. Both captions are presentation only — the same rounded values the calculator already produced, now surfaced on the card. Guarded by build/test_round_caption.py.
v5.79
UI
Single-dose drugs now always show their ceiling — on the dose-layout beta a single-dose drug whose maximum is a single-dose limit (Ondansetron 4 mg, Naloxone 800 microg) now displays it in the TOTAL MAX strip instead of "—", so the cap is visible, not just enforced.
- The ceiling is visible on every single-dose drug. The dose-bar's TOTAL MAX read only the cumulative "total max" field, so single-dose drugs whose limit is a single-dose maximum (Ondansetron 4 mg, Naloxone 800 microg) showed "—" even while the dose was being held at that cap. They now show the ceiling, consistent with drugs like Ceftriaxone. Drugs with genuinely no maximum still show "—". No dose value changes (the caps were already applied in v5.77).
- Repeating drugs are unaffected. The single-dose ceiling is only surfaced on genuine single-dose rows; a repeating drug still shows its cumulative total max, never its per-dose limit. Guarded by build/test_single_dose_ceiling.py.
v5.78
INFRA
No visible change — closes the test gap that let the v5.77 dose-cap bug ship: parse_max (the dose-ceiling parser) now has direct unit tests, plus a dataset-wide invariant that blocks any release where a stated numeric ceiling parses to "no maximum".
- The dose-ceiling parser is now guarded. The v5.77 fix (Ondansetron / Naloxone caps) revealed that parse_max had no direct test, so a silently-dropped cap was invisible to the whole safety regime. test_parser now pins the exact "single dose only, not to exceed X" strings and asserts a release-blocking invariant: if a max field's raw text states a number, the parsed cap must not be null. A dropped numeric ceiling can never ship again.
v5.77
SAFETY
Single-dose ceilings now enforced for Ondansetron and Naloxone — a "single dose only, not to exceed X" maximum was being dropped, so the per-kg dose could read above the stated cap for heavier patients. The cap is now parsed, shown, and applied.
- Ondansetron and Naloxone now cap correctly. Both state their maximum as "Single dose only, not to exceed X" (Ondansetron 4 mg, Naloxone 800 microg). That wording was being read as "no maximum", so the number was lost and the weight-based dose ran past it — e.g. at 50 kg Ondansetron showed 5 mg (now 4 mg) and Naloxone showed 1000 microg (now 800 microg). The dose is now held at the ceiling for patients at or above the cap weight (~40 kg); lighter patients are unchanged.
- Why it matters. The maximum is part of the dose and must never be exceeded on screen. This was a parser gap affecting only these two rows (the only ones combining "single dose only" with a numeric limit); every other single-dose drug already enforced its ceiling. No other doses change.
v5.76
SAFETY
Out-of-age routes no longer appear as dead-ends — Glucose gel now offers only the route for the patient's age (buccal under 2 years, oral 2 years and over) instead of listing both with the wrong one leading to a consult dead-end.
- Right route for the age, only. Glucose gel is given buccally (1 cm strip) under 2 years and orally (15 g) from 2 years up — two routes that split by age. The route picker, the dose-page route switcher and route swipe now show only the route the patient is in age-band for, instead of also offering the other one as an "outside approved age range — consult required" dead-end. A 5-year-old no longer sees the infant buccal route at all.
- Same safety behaviour everywhere else. Drugs with a single route, or where the patient is genuinely outside every approved band, still show the consult / out-of-band message as before — nothing is hidden silently. Contraindicated routes still show in red so the do-not-give signal is never suppressed. No dose value changes.
v5.75
UI
Tighter drug detail card (beta) — the indication folds up into the drug header band with a mint marker so the card is shorter and the dose lands sooner, and the presentation row flags RECONSTITUTION / DILUTION in amber when a prep step is needed before draw-up.
- Indication folded into the header band. On the dose-layout beta the indication now sits directly under the drug name inside the mint band, marked by a small mint tick and brighter text, instead of taking its own row below — the card is shorter so the dose number and DRAW UP slab sit higher on screen. Presentation only.
- Prep step flagged in amber. The presentation row's label now goes amber for RECONSTITUTION / DILUTION (a step is required before you draw up) and stays mint for a ready-to-use PRESENTATION — so you can see at a glance whether the drug needs preparing. The recipe is unchanged, still one tap away on the prep line.
v5.74
UI
Route reads louder on the dose page, and off-indication routes tuck behind one doorway — the route pill gets a bolder mint edge and a bigger label, and the route sheet now shows only the routes for the current indication, folding the rest behind a single "also … under other indications" line you can expand.
- Louder route pill. The route on the dose page now has a bold mint outline, a faint halo and a slightly bigger label, so which route you're giving reads at a glance against the dose card below. Presentation only.
- Off-indication routes collapse to a doorway. The route sheet lists only the routes valid for the current indication. Routes that belong to a different indication fold behind one quiet line that names them (e.g. "Also IV · IM under other indications"); tap it to expand them in place — still tagged DOSE / CONSULT, and crossing into one still raises the off-context warning. Stops an off-context route being chosen by accident while keeping it discoverable.
v5.73
UI
Contraindications & precautions sheet rebuilt, and fixed-dose cards squared away — the safety sheet is now a colour-coded list (red = absolute, amber = relative, mint = precaution) with a left rail and severity word on each item; and fixed-dose drugs (e.g. Adrenaline anaphylaxis) get the same gradient header as every other dose card.
- Safety sheet is colour-coded by severity. Tapping "Contraindications / precautions" now opens a single list sorted by severity, each item a card with a coloured left rail and a small severity word — red Absolute, amber Relative, mint Precaution. Replaces the previous flat list of repeated red tags; the wording of every contraindication and precaution is unchanged.
- Fixed-dose cards match the rest. Drugs given as a fixed age-band dose (no per-kg rate) — Adrenaline anaphylaxis/arrest and similar — previously had no header band, so they looked flat and the big dose number floated to the top of the card. They now get the same mint gradient "INITIAL DOSE" header as every per-kg drug, so the number sits in its normal place. Presentation only.
v5.72
UI
No-max repeat drugs get a clean single strip (beta) — drugs that repeat the same dose with no ceiling (Adrenaline anaphylaxis & cardiac arrest, Salbutamol) now show one full-width line, "Repeat every N min · No max dose", instead of an awkward two-column bar announcing there is no ceiling.
- Right shape for "keep repeating". The compact repeat columns (v5.71) fit drugs with a finite plan, but the no-ceiling repeat-same drugs have no next-dose-that-differs, no count and no max — so two of the three columns were empty or negative ("Ceiling: No max"). Those rows now render a single full-width strip the width of the dose card: "↻ Repeat every 3–5 min · No max dose".
- Which drugs. Adrenaline IM anaphylaxis (every 5 min), Adrenaline IV cardiac arrest (every 3–5 min), Salbutamol bronchospasm (every 10 min / PRN) — anything with a repeat cadence and no total maximum. The dose to repeat is the DRAW UP figure shown above; this strip just states the cadence and that there is no ceiling.
- Data-driven. Triggered generically off "no total max + a repeat cadence", so any future no-max repeat row gets the same treatment. Finite-plan drugs keep the v5.71 columns; nothing about the doses themselves changes.
v5.71
UI
Repeat-doses card rebuilt to be far more compact (beta) — the next dose you draw, the interval and the total max now sit as three tidy columns with the dose stacked over its volume, the dose-count dots ride up in the "Repeat doses" title, and the whole card is roughly 40% shorter on the multi-dose drugs.
- Much less vertical space. The repeat card drops from ~180–275px to ~150px and no longer grows with complexity. The separate "Second dose" panel is gone — the next dose is now the first of three aligned columns (Next/Final dose · Interval · Total max), and the dose-count dots move into the "Repeat doses" header band.
- Next dose you draw, on the page. The first column shows the next (or final) repeat dose with the amount on top and the draw-up volume beneath — e.g. Fentanyl intranasal "18 microg / 0.36 mL", Midazolam "1.8 mg / 0.36 mL". Read verbatim from the same dosing plan the Full plan sheet shows; nothing is recalculated.
- Tidier labels. Dose-count dots are all filled (a capacity, not a running count); the card is titled "Repeat doses"; Sodium chloride's bolus timing reads "PRN"; and the methoxyflurane total max drops the "in 24 h" window from the bar (it stays in the Full plan). Columns wrap to a second row on small (SE-class) screens rather than crowding.
- Applies to both dose betas. The compact columns ship in dose-design v3 (full card) and the v2 layout.
v5.69
UI
Dose page (v3 beta) cards now separate cleanly: every card gets a defined rim plus a single mint hairline along its top edge, the gaps between cards are a touch wider, and the route pill sits up against the top of the drug name instead of drifting low.
- Cards have borders. The header, INITIAL DOSE and Repeat dosing cards each get a 1.5px rim so their edges read clearly against the dark background, with one uniform mint top-accent across all of them (no per-card colour).
- More breathing room. The gap between cards is opened up slightly so the stack no longer runs together.
- Route pill lifted. The route badge (e.g. NAS/IM) is pulled up to align with the top of the drug title rather than sitting centred and low. Same pill size, just higher.
v5.67
GOVERNANCE
Quality guard — a new release-blocking check makes sure a screen can never render unstyled again: every design class the app draws must have matching styling, and the layout classes removed in past redesigns can never be reused. This is the safety net for the v5.66 plan-sheet fix. No change to what you see.
- Stops a whole bug class. The v5.66 plan-sheet bug happened because a screen drew classes whose styling had been deleted. The new build check (run on every release) fails the build if any design class is drawn without styling, or if a removed layout class is reused — so that class of "raw/unstyled screen" bug is caught before it ships. Runtime behaviour is unchanged.
v5.66
UI
Fix — the Full/dosing plan sheet for Low/High and fluid-bolus drugs (e.g. Morphine, Fentanyl Low/High, sodium chloride) was rendering with no styling (raw text, chips inline). It now uses the same tidy layout as every other plan sheet. Dose values are unchanged.
- Plan sheet fixed. The variable-select / fluid-select plan sheet still emitted an old timeline layout whose styles were removed in the v5.54 plan-sheet redesign, so it looked broken. It now renders the count, the limits grid (repeat · single max · total max) and the Dose-N rows like the others. The figures are read from the same calculation as before — only the presentation changed.
v5.65
UI
Dose design v3 beta — the Contraindications / Precautions row is marked by a small quiet red dot before a mint label (back from the muted-red text). Beta only.
- Contra marker. A small quiet red dot sits before the mint "Contraindications / Precautions" label — the safety cue without colouring the whole label.
v5.64
UI
Dose design v3 beta — small polish: the repeat panel reads "Interval · 10 min" (terser), the interval/total-max panel is now properly aligned (Full plan button no longer clipped), and the Contraindications / Precautions row is marked by a muted-red label instead of a side line. Beta only.
- Tidier repeat panel. "Dosing interval · Every 10 min" is now "Interval · 10 min" to save room; the interval/total-max panel is width-corrected so it lines up with the panels above and the Full plan button sits flush right.
- Contra marker. The Contraindications / Precautions row is now flagged by a muted-red label (the side line and dot are gone).
v5.63
UI
Dose design v3 beta — the Repeat dosing card now holds three tidy panels (Second dose first, then the dose dots, then interval/total max), and the dots are all-green with "Up to N doses" — a capacity at a glance, not a progress counter. Beta only.
- Three clear panels. Inside the one Repeat dosing card: the Second dose figure leads (it is what you draw up next), then the dose dots, then the dosing interval + total max — each in its own panel like the rest of the page.
- Dots = how many, not where you are. All dots are solid green and the line reads "Up to N doses". The app does not track which dose you have given, so the old "1 of …" with a single filled dot was misleading; this states the capacity honestly. The amber cap dot is dropped (the total max already states the ceiling).
v5.62
UI
Dose design v3 beta — the repeat card is now titled "Repeat dosing" and reads as ONE card (the doses count, second-dose figure and interval/max no longer float as separate areas). The repeat figure is labelled "Second dose". Beta only.
- One card. The pips, the second-dose box and the interval/total-max row now sit inside a single bordered card instead of separate floating panels, so the repeat-dosing block reads as one unit.
- Clearer labels. Card title is "Repeat dosing" (covers one-or-many repeats); the repeat figure is labelled "Second dose".
v5.61
UI
Dose design v3 beta — the "Repeat dose" card is renamed "Dose plan" (it covers the whole regime — usually several doses, not one), and its button is renamed "Full plan" so it no longer collides with the title. Beta only.
- Clearer card name. "Repeat dose" read as a single repeat, but the card covers the whole regime — count, next dose, interval and total max, usually across several doses. It is now titled "Dose plan", and the link to the detailed timeline is "Full plan" to avoid two similarly-named items.
v5.60
UI
Dose design v3 beta — the NEXT dose block on the Repeat dose card now sits in its own outlined, tinted box (matching the mock) so it reads as a distinct figure, set apart from the pips above and the interval/max row below. Beta only.
- Next dose stands out. The repeat next-dose amount is now framed in a mint-outlined box instead of sitting flush in the card, so it reads as its own value at a glance.
v5.59
UI
Dose design v3 beta — the Repeat dose card now shows the NEXT dose amount when it differs from the initial (titrated drugs: e.g. Midazolam seizure repeats at half, max 2.5 mg), so you do not re-give the larger first dose. Plus the Contraindications / Precautions row marker is now a red side line. Beta only.
- Next dose, when it is different. Some drugs repeat at a smaller dose than the first (Midazolam seizure = half, max 2.5 mg; Morphine IV = half; Fentanyl NAS). The Repeat dose card now shows that next-dose amount + volume with a short "half of initial · max 2.5 mg" note, so the smaller repeat is visible without opening the dosing plan. The figure is read straight from the dosing-plan timeline — no new calculation.
- Where it shows. Only when the repeat amount actually differs from the initial; drugs that repeat at the same dose are unchanged. Range-dose, fluid-infusion and Low/High rows fall back to the dosing plan for now.
- Contra marker. The Contraindications / Precautions row is now flagged by a red vertical side line instead of a dot.
v5.58
UI
Dose design v3 beta — the small dose-card labels are larger for easier reading in the field: "DRAW UP" steps up to 11px, and "INITIAL DOSE", "DOSING INTERVAL" and "TOTAL MAX" from 9px to 10px. Values are unchanged. Beta only (More → Dose design v3).
- Labels easier to read. Under the v3 beta, the uppercase descriptors above the dose figures were small (9px) for field conditions. "DRAW UP" is now 11px and "INITIAL DOSE", "DOSING INTERVAL" and "TOTAL MAX" are 10px. The dose, volume and rate values themselves are unchanged.
- Beta-scoped. Applies only with "Dose design v3 (beta)" on; the default layout and every other screen are untouched.
v5.57
UI
New "Dose design v3" beta — a separate opt-in dose-page look: elevation cards with a shared mint header band on every block (drug · initial dose · repeat dose), a new grouped Repeat dose card, a label-only Contraindications / Precautions row under the indication, and a frame where only the top Back bar and the bottom tabs are pinned. Opt-in (More → Dose design v3).
- Its own beta, off by default. Toggle it at More → "Dose design v3 (beta)". It layers a new dose-page design over the existing one; with it off the page is byte-for-byte unchanged, and it's independent of the other betas.
- Three clear blocks. Drug info, Initial dose and Repeat dose each sit on their own elevated card under a shared mint header, so the page reads as three distinct groups. The grouped Repeat dose card is new. Patient bar and header scroll; only Back (top) and the tabs (bottom) stay pinned.
- Safety surfaces kept. Contraindications / Precautions is one tappable row under the indication, marked with a quiet red dot. Consult-required drugs still lead with the amber consult row and hold the dose — and now the repeat plan — behind "Show dose" until approval.
v5.56
UI
Dose layout beta — the route (IM / IV / NAS …) is now a squarer, tinted block so it stands out more on the dose page; and the in-place "CHANGE" indication switcher is removed (the indication still shows; change it by stepping Back to the indication picker). Opt-in (qas_dose_v2).
- Route reads louder. The route control gets a squarer corner radius and a slightly stronger tint so which route you're giving is more prominent next to the drug name. Still tappable to switch route when a drug has more than one. Resus and legacy layout unchanged.
- Indication is fixed for the drill. The indication is the anchor that sets the dose, routes and regime, so changing it should be deliberate — the one-tap in-place switcher is removed under the beta. The indication still shows as a label; to change it, step Back to the indication picker.
v5.55
UI
Dose layout beta — in the Dosing plan sheet each dose's dose/kg now sits on its own line under "Dose N" (stacked, like the initial dose card) so the basis of every dose is clear; and a trailing note on the total max (e.g. "(or MME)") is trimmed off. Opt-in (qas_dose_v2).
- Dose/kg stacked on every dose. The per-kg rate moves off the "Dose N" line onto its own line beneath it, across all drugs — clearer basis for each dose. The only dose without a rate is a ceiling-limited final dose, whose amount is the leftover to the total cap (not a per-kg dose); it keeps its "To max" tag instead of a misleading per-kg figure.
- Cleaner total max. A trailing parenthetical qualifier on the total max is trimmed in the summary cell ("100 microg (or MME)" → "100 microg").
v5.54
UI
Dose layout beta — the Dosing plan sheet is rebuilt: the things that are the same for every dose (repeat interval, the max limits) sit once in a summary at the top, and each dose just shows its dose/kg and the amount/volume stacked like the initial dose card. Opt-in (qas_dose_v2).
- Constants once, not per dose. A "N doses available" line, then a summary grid — Repeat every · Total max, with Single max joining only when the protocol sets a distinct single-dose cap. Each dose row is "Dose N · rate/kg" + the amount over the draw-up volume.
- Redundancy removed. Gone: the repeated preparation string on every row (prep is already shown on the dose page), the running cumulative total, the "Give now" tag on dose 1, and the sheet title (you opened it from a button that said it). The last dose carries "To max" (ceiling) or "Final" (count-limited).
v5.53
UI
Dose layout beta — the mint DRAW UP volume block is now a softer tint (low-opacity fill + mint hairline + light text) instead of a loud solid mint slab, so it sits calmer next to the dose number. Wired for assessment. Opt-in (qas_dose_v2).
- Quieter draw-up slab. The solid mint slab was the loudest block on the card; the tinted treatment keeps the volume as the mint figure but less shouty, matching the card's other tinted strips. Beta-only and dose-page only — the Resus draw-up block is unchanged. Volume value and "DRAW UP" label are unchanged in size/position.
v5.52
UI
Dose layout beta — the per-kg rate now sits stacked on its own line under the INITIAL DOSE heading (left-aligned) instead of inline beside it; the inline version made the heading row wide and crowded the dose number. Opt-in (qas_dose_v2).
- Rate stacked under the heading. "INITIAL DOSE" on one line, the rate (e.g. "100 microg/kg") on its own line below it — a narrow left-hand column clear of the centred dose figure. Replaces the v5.49 inline overline on both the standard and Low/High cards. Route still omitted (header pill); DATASET ERROR rows still print the route on the bottom line; legacy unchanged.
v5.51
SAFETY
Hardening so the v5.50 class of bug (a screen not updating when the patient changes) cannot come back: a single release-blocking guard now checks every patient-driven screen, and the rule is written into the patient-state contract.
- One guard over every screen. New
test_patient_refresh_matrix sweeps Resus, the drug dose card and Vitals: it changes the patient in place through the real edit-sheet and asserts each screen re-renders to exactly what a fresh visit would show (and that it actually reacts to the patient). Proven to fail on v5.49's stale Resus; supersedes the v5.50 one-off resus guard.
- Written into the contract.
PATIENT_STATE_CONTRACT.md §4 now makes "re-render on a live patient change + add the screen to the guard's registry" a hard rule for any new patient-driven surface. No change to the app itself.
v5.50
SAFETY
Fixed: changing the patient's age or weight while already on the Resus deck did not update the pages — defib pad selection + manual energy, CPR, airway and adrenaline all stayed on the previous patient (e.g. an 11 yr / 35 kg patient still showing PAEDIATRIC pads · "Children <6 years" · 30 J).
- All four resus pages now refresh live.
_populateResus() only ran on entry to the Resus view, so a patient change made from the patient strip while on the deck left every page stale. A patient change now re-renders all four pages off the canonical age/weight change signal, while preserving the page you're on. Drug dose pages and Vitals were already refreshing — this gap was Resus-only.
v5.49
UI
Dose layout beta — the per-kg rate (e.g. "0.1 mg/kg") now rides the INITIAL DOSE heading instead of a muted line under the DRAW UP slab, so the rate you're giving is visible at a glance and the card is shorter. Opt-in (qas_dose_v2).
- Rate on the heading. The dose card's overline reads "INITIAL DOSE · 0.1 mg/kg"; the bottom
.rule line is removed. The route is omitted (it's already in the header route pill). Applies to the standard and variable-dose (Low/High) cards. The big figure and mint DRAW UP slab are untouched.
- Safety preserved. On a DATASET ERROR row the route still prints on the bottom line (the fail-visible signal), so the rate is not lifted there. Legacy (flag OFF) is unchanged — overline stays "INITIAL DOSE", rate stays on the bottom line.
v5.48
UI
Dose layout beta — fixes the large empty gap between the leading CONSULT REQUIRED banner and the drug title on consult-required rows. Opt-in (qas_dose_v2).
- Consult-banner gap closed. When the consult banner leads, it takes the fixed-strip clearance and the hero drops its own 89px top margin. The header-v2 hero rule set that margin at higher specificity, so the hero kept the clearance and a second 89px gap opened. Added a header-v2-scoped override so the hero zeroes its top margin when the consult banner precedes it. Presentation only; legacy + non-consult rows unchanged.
v5.47
UI
Dose layout beta — the dilution / reconstitution strip now shows just the target ("Prepare to 10 microg/mL", "Prepare to 50 mg/mL") instead of the full recipe; the REQUIRED overline, "· N steps" tag and ⓘ stay, and the recipe steps are one tap away in the prep sheet. Opt-in (qas_dose_v2).
- Prepare to X.
_mzPrepLineHTML's action branch (Dilution / Reconstitution) now renders "Prepare to " + the prep's final_concentration as the main line instead of the recipe summary. The tap target (dd-dilution-trigger), steps tag and ⓘ are unchanged, so the full recipe (prep sheet tiles + then-note) stays one tap away. Consistent with the v5.46 presentation simplification; legacy keeps the full recipe when the beta is OFF.
v5.46
UI
Dose layout beta — on a consult-required row the CONSULT REQUIRED banner now LEADS (above the drug details, the gate first); the presentation strip drops its left square icon and shows just the prep amount (no concentration). Opt-in (qas_dose_v2).
- Consult leads. The consult banner is relocated into
#mzConsultTop ABOVE the hero (was inline above the dose). The dose-hold safety gate (is-consult-hold / #ddConsultReveal) now detects the banner in its new location too, so the dose stays held behind "Show dose · for after approval" until approval. Cleared every render (renderer invariant); non-consult and legacy rows unchanged.
- Cleaner presentation. The left square icon is dropped from the presentation / dilution / reconstitution strip, and the presentation shows just the prep AMOUNT (the appended final concentration and a trailing "(… /mL)" are stripped). Dilution / reconstitution recipes keep their detail. Spelled-out unit + per-kg range tiles unchanged.
v5.45
SAFETY
Fail-visible fix — the v5.42 dose-rule route removal also dropped the "— DATASET ERROR" suffix from the pilot dose card, so a corrupted-dataset row rendered "—" with no error plate. The route is now re-appended to the rule line ONLY when it carries the DATASET ERROR suffix; normal rows keep the v5.42 cleanup.
- Root cause. The integrity gate surfaces a data error by suffixing the route ("IM — DATASET ERROR"); the pilot card showed it via the rule line. v5.42 removed route from that line for cleanliness, silently removing the safety signal — undetected because the promote used the deferred-headless path that skips the
test_fail_visible guard.
- Fix. The pilot dose-card rule line re-includes the route only when
/DATASET ERROR/i matches it; otherwise it shows just the rule (v5.42 intent preserved). All 7 boot-time corruption cases plate again.
v5.44
UI
Dose layout beta — the range-dose (Low/High) selector now labels each tile with the actual per-kg rate (Fentanyl IM "1 / 2 microg/kg", Morphine IM "100 / 200 microg/kg", saline "10 / 20 mL/kg"), shows selection as a mint border + inset ring on a squarer tile (not a solid fill), and spells out the dose unit under the number (micrograms / milligrams / grams; volume stays mL). Opt-in (qas_dose_v2). (Bundles the pending v5.42/v5.43 resus Adrenaline rule-line route removal.)
- Per-kg rate on the toggle.
_mzVsCardHTML labels each .seg-b with the option's own per-kg rate (opt.dose_value + opt.dose_unit) instead of "Low dose / High dose"; the resulting dose + draw-up still populate in the card below. Label swap only — no dose value changed.
- Selection by border, not fill. Under
html.dose-v2 the segmented control becomes a squarer tile whose selected state is a mint border + inset ring on a soft tint, replacing the solid mint fill. Legacy pill toggle byte-for-byte unchanged when OFF.
- Spelled-out dose unit.
_mzUnitLong expands the mass unit under the dose number (microg→micrograms, mg→milligrams, g→grams) on both the standard and vs cards. Volume (mL) untouched. NOTE: Droperidol is not on the pilot card, so it keeps the production range layout (no Low/High toggle) until promoted.
v5.43
UI
Resus Adrenaline rule line now matches the Drugs page — route no longer appended (was "10 microg/kg · IV", now "10 microg/kg"). Completes the v5.42 route-removal across all three render sites.
- Third render site.
_pgAdrenaline was appending routeText to the rule line independently of the two sites fixed in v5.42. Removed the append; route is shown in the header pill.
- Parity guard.
test_resus_parity catches this — it proved the mismatch (drugs="10 microg/kg" vs resus="10 microg/kg · IV") and now passes.
v5.42
UI
The small muted text under each dose slab no longer repeats the route (e.g. "0.2 mg/kg · IM" → "0.2 mg/kg"). The route is already shown in the route pill in the header — showing it again under every slab was redundant.
- Both render sites updated. Main mz-pilot dose card (rule line) and variable_select dose card (ruleStr) now omit the route suffix. Rows with no per-kg rule and no rate show nothing (previously would have shown just the route).
v5.41
UI
Dose layout beta — the total-max bar value no longer overruns the Dosing-plan button for multi-clause / time-window ceilings: a ";"-separated ceiling shows just its primary clause (Methoxyflurane "6 mL in 24 hours; 15 mL in 7 days" → "6 mL · in 24 h"), and an "in/per/over X" window stacks to the muted second line. The full ceiling text stays in the plan sheet. Opt-in (qas_dose_v2).
- Bar fit completed.
_mzV2MaxHTML now drops a ";"-separated tail (keeping the primary clause on the bar) and stacks an "in/per/over <window>" qualifier — abbreviating hours→h, minutes→min, days→d — in addition to the v5.40 "(…)" stacking. Methoxyflurane no longer collides with the Dosing-plan button; Fentanyl NAS / IV, Morphine IV, Midazolam and saline all clear it. The complete ceiling (e.g. both Methoxyflurane clauses) is preserved verbatim in the plan-sheet footer.
- Presentation only. No dose, cap or ceiling value changed — only how the ceiling string is laid out on the compact bar. Gated on
html.dose-v2; legacy unchanged when OFF.
v5.40
UI
Dose layout beta — three fixes: repeat-once-different-amount rows (Fentanyl NAS) now surface their repeat + plan instead of looking single-dose; the final dose pip is a solid amber fill (was a faint dashed outline); and a "(…)" total-max qualifier (e.g. "or MME") stacks to a muted second line so the bar value no longer overruns. Opt-in (qas_dose_v2); legacy unchanged when OFF.
- Repeat-once repeat restored. A repeat_once with a different per-kg amount (Fentanyl NAS: 1.5 microg/kg initial, 1 microg/kg repeat — engine kind "two") was rendering with no pips and no plan under the beta, because
_renderRegime only publishes window._currentRegimePlan for the multi kind. _mzDecorate now falls back to regimePlanFor for the 2-dose plan, publishes it + arms the plan trigger, and the chip sheet handles the count-limited last row (Final, "Maximum doses given · ceiling X") rather than "to total max".
- Solid final pip. The capped/final dose pip is now a solid amber fill instead of a dashed outline, so a 2-dose regime reads clearly as two doses.
- Total-max stacking.
_mzV2MaxHTML splits a trailing "(…)" qualifier onto a small muted second line ("100 microg" / "or MME"; "44 microg" / "2 microg/kg"). Multi-clause ceilings (Methoxyflurane) and the long-left-segment squeeze (saline) are a separate follow-up.
v5.39
UI
Dose layout beta — the variable/fluid-select dosing-plan sheet (Fentanyl Low/High, Sodium chloride bolus) now uses the same operational timeline as the fixed-dose plan, so every plan sheet is consistent under the beta. Opt-in (qas_dose_v2); legacy chip layout unchanged when OFF.
- Consistent plan sheet.
_mzOpenVsPlanSheet gains a qas_dose_v2 branch (_mzVsV2PlanBody) that renders the shared timeline — numbered boluses/doses with the per-kg rate, interval, a running cumulative total, Give-now / Final tags and an at-max footer — from the same _mzVsPlan walk the legacy chip body used. No dose value recomputed.
- Count- vs ceiling-limited footer. The footer now distinguishes a regime that walks to the total max ("At total max · X") from one that stops on a hard dose count below the ceiling (saline "may be repeated twice" → 3 boluses under the 60 mL/kg ceiling → "Maximum doses given · ceiling X"), so the saline plan no longer implies it reaches the ceiling. The ceiling-limited top-up row keeps its explicit "NOT the standard repeat dose" wording.
- Scoped + reversible. Gated on
html.dose-v2; the legacy .mz-pl chip plan renders unchanged when OFF. Single-dose-at-ceiling and no-max rows are untouched (no plan button; Adrenaline unaffected).
v5.38
UI
Dose layout beta (Phase 2) — the "Dosing plan" sheet becomes a simple operational timeline: each dose shows its per-kg rule, interval and a running total toward the ceiling, with Give-now / Final tags and an at-max footer. Opt-in (qas_dose_v2); legacy sheet byte-for-byte unchanged when OFF.
- Operational timeline. When the beta is ON, the chip-strip dosing-plan sheet renders numbered dose rows — amount · volume · the per-dose per-kg rule (initial e.g. 200 microg/kg; repeat derived from the parsed fraction, e.g. ½ → 100 microg/kg) · interval · a running cumulative total toward the total max. Dose 1 carries a "Give now" tag, the capped final dose a "Final" tag, and an at-max footer closes the sheet.
- Presentation only. The running total is a sum of the already-computed per-dose values (no new dose math, v3.75 rule); the per-kg repeat rule is a derivation of the existing rate. Single-dose and no-max rows have no plan button and are untouched (Adrenaline unaffected). Reassessment checkpoints are intentionally omitted in v1.
- Scoped + reversible. New markup is gated on
html.dose-v2; the legacy .dd-regime-timeline sheet renders unchanged when the flag is OFF, so the default safety regime is unaffected. Variable/fluid-select plan sheets keep their existing layout for now (fast follow).
v5.37
UI
Dose layout beta (opt-in) — the inline repeat/regime/max block collapses to a dose-count pip strip plus a one-line dosing bar (interval · total max · Dosing plan); the full timeline stays one tap away. Default OFF; legacy layout byte-for-byte unchanged.
- Opt-in flag
qas_dose_v2. A new "Dose layout (beta)" toggle in More (default OFF) sets html.dose-v2, mirroring the dose-header beta mechanism. When OFF the existing repeat/regime/max region renders unchanged, so the safety regime is unaffected.
- Compact regime summary. When ON,
_mzDecorate() replaces the inline repeat block with a dose-count pip strip (shown only for a finite multi-dose repeat — current dose filled, capped final dose amber) and a one-line dosing bar: dosing interval · total max · a "Dosing plan" button that opens the existing dosing-plan sheet unchanged.
- Presentation only — no recompute. The pip count, interval and ceiling are read from the already-computed engine plan/row fields the legacy branches read; no dose value is invented (v3.75 rule). Edge buckets map cleanly: no-max (Adrenaline) and single-dose (Ceftriaxone) show the bar with no pips; repeat-once (Methoxyflurane) shows two pips. Range/variable-select rows keep their existing chip regime for now.
v5.36
SAFETY
Regression guards for the v5.35 stale-indication fix — so a leftover indication label can never silently come back. No app behaviour changes; this release adds tests only.
- Proven-to-fail guard added.
test_header_beta now drives a multi-indication drug then a single-indication one with the beta header on, and fails if the indication switcher stays visible or keeps a stale label. Confirmed to fail on the pre-v5.35 build and pass on the fix.
- Stale-state matrix extended. The renderer-invariant matrix (
test_stale_state) now watches the switcher (#mzKicker) and re-runs a focused row set with the beta header on — the mode field users actually run — so the whole class of "a beta override re-shows an element the base layout hides" is covered, not just this one symptom.
- Rule recorded. Any beta style rule that force-shows an element must re-assert the base stylesheet's visibility condition; captured in the renderer contract so it's caught in review.
v5.35
UI
Dose header (beta) stale-indication fix. A single-indication drug opened straight after a multi-indication one (e.g. Glucose 10% after Glucagon) could show the previous drug's indication — a leftover "…anaphylaxis…" line — overlapping the real one. The header now hides the indication switcher whenever it isn't in use. Beta still off by default and presentation-only.
- Stale indication label cleared. Under the beta header the multi-indication switcher (
#mzKicker) was force-shown with display:flex !important regardless of whether the current drug actually has a switcher, so on a single-indication drug it kept the prior drug's label and drew it on top of the real indication. Re-asserted the legacy gate: when the view is not in switch mode the switcher stays hidden, and its label is cleared on every leave-switch transition.
- No clinical change. Dose, volume, route, repeat, contraindications and every dataset value are untouched — this only stops a leftover indication caption from rendering. Multi-indication drugs still show the switcher exactly as before.
- Verified. Headless multi→single→multi transition under beta-on and beta-off: the switcher shows only for 2+ indication drugs and never carries a stale label into a single-indication drug.
v5.34
UI
Dose header (beta) layout fixes. On multi-indication drugs (e.g. Midazolam) the "Contras" tag was dropping onto its own line below the indication instead of sitting to its right, and the presentation line was centre-aligned instead of left-justified. Both are fixed so the live header matches the design. Beta still off by default and presentation-only.
- Contras back on the indication line. The multi-indication switcher (
#mzKicker) carried a more-specific width:100% rule, so with flex-wrap it claimed the whole line and bumped the Contras chip below. Fixed by giving the indication chunk flex-basis:0 (line-break is decided from the basis size before shrink applies) so the indication and the Contras chip share the line on every drug.
- Presentation left-justified. The folded presentation block inherited
text-align:center; it now left-aligns with the label + value inline next to the marker, matching the rest of the card.
- Guarded.
test_header_beta tightened: the same-line check is now x-based (Contras must sit to the RIGHT of the indication, not just vertically near it — the old centre-Y check false-passed on multi-line kickers), plus a presentation-not-centred assertion. Midazolam (multi-indication) is in the drive set.
v5.33
UI
Dose header (beta) refined into one pinned card. The drug name + route, the indication with a compact "Contras" tag on the same line, and the presentation now sit together in a single card that stays fixed while only the dose info scrolls beneath it. The old top-bar Vitals pill is gone (Vitals lives in the bottom menu now). Still off by default and presentation-only — every dose, volume, route and warning is calculated exactly as before.
- What changed. The beta header (
qas_header_v2) is now one consolidated card: indication and the contraindications doorway share a line (the doorway shortens to "Contras" so it fits), the presentation strip folds in as the card's last row, and the whole header — patient strip through presentation — is pinned (position:sticky) so only the dose card and below scroll. The top-bar Vitals pill is dropped (it duplicated the bottom-nav Vitals tab). Long drug names and long indications wrap; the contra/presentation rows are the SAME elements + sheets, just re-placed. No dose, route, indication, regime or dataset logic is touched.
- How to try it. More → Dose header (beta) → On. The legacy header is byte-for-byte unchanged when the toggle is Off (the shipped default).
- Guarded.
test_header_beta extended: on = title in the card, exactly one contra row folded in with the muted dot and short label, indication+Contras share a line, presentation folded in (one strip), header pinned, names never clipped, Vitals pill gone; off = legacy unchanged (no folded contra/prep). Stale-state and viewport-geometry guards still run against the Off default.
v5.32
UI
New opt-in "Dose header (beta)" in More. When on, the drug page shows the drug name and route as a bold card right under the patient bar (instead of the small line up top), with the indication and a quiet contraindications row folded into the same card. Off by default — flip it on to try it; nothing changes until you do. Presentation only: every dose, volume, route and warning is calculated exactly as before.
- What changed. A flag-gated header redesign (
qas_header_v2 → html.header-v2). The same drug-name/route element and the same contraindications row/sheet are re-placed into a prominent card under the patient strip; the indication folds in beneath, and the contraindications marker is a small muted dot. Long drug names wrap rather than truncate. No dose, route, indication, regime or dataset logic is touched.
- How to try it. More → Dose header (beta) → On. The legacy header stays exactly as-is when the toggle is Off (the shipped default).
- Guarded. New release-blocking
test_header_beta: off = legacy unchanged; on = title in the card, exactly one contraindications row folded in with the muted dot, names never clipped; toggling off leaves no stale row. The stale-state and viewport-geometry guards continue to run against the Off default.
v5.31
UI
Switching Low/High on a scrolled dose page no longer jumps the page to the top. The selector always changed the dose correctly, but the re-render reset the scroll position — so you lost your place and had to scroll back down to the figure. The dose now updates in place, with the page exactly where you left it. Distinct from the v5.30 fix (which stopped a tap on the toggle being read as a scroll/swipe gesture); this is the re-render scroll reset.
- What changed. The Low/High toggle (
_vsSelectOption) now captures the document scroll position before re-rendering the dose and restores it afterwards (and again once layout settles). Behaviour only — no dose, volume, route, indication, contraindication or interaction logic changed.
- Guarded.
test_bucket_surfaces P4 now scrolls to the bottom, toggles Low/High, and asserts the scroll position holds (proven to fail on v5.30, where it reset to the top).
v5.30
UI
Tapping or flicking the Low/High selector and other buttons on a scrolled dose page no longer scrolls the page instead of activating the button.
- What changed. Two guards: (1)
touch-action: manipulation added to the Low/High segmented buttons (.seg-b) and the older tile selector (.vs-tile) — tells iOS to treat touches here as taps, not scroll gestures; (2) the dose-page horizontal swipe handler (doseView touchstart) now disables itself when the touch originates on any button or a element, so a flick on a toggle can no longer accidentally fire route navigation.
- No dose, volume, route, indication, contraindication or interaction logic changed.
v5.29
UI
Consult required now sits directly above the dose, not up near the top of the page. On a consult drug (e.g. Droperidol for acute behavioural disturbance) the amber CONSULT REQUIRED row with the Call consult button used to float just beneath the drug title, with presentation and contraindications between it and the figure. It now renders immediately above the dose card — or above the “Show dose” reveal while the dose is held for approval — with the contraindications row kept above it as the harder stop. One consult surface, read in the same glance as the number it gates.
- What changed. Layout only. The consult message is re-emitted inline inside the dose block, below the contraindications bar and immediately above the dose card; the old top-of-page banner is suppressed so there is never a duplicate. Order is now presentation → contraindications → consult → dose.
- Scope. Only rows that already surface a consult message move (Droperidol IV/IM, Sodium chloride). Plain conditional-consult rows that showed no message are unchanged. The dose-hold (consult drugs hold the figure behind “Show dose” until tapped) still engages, deriving from the inline consult row.
- No dose, volume, route, indication, contraindication or interaction logic changed.
v5.28
UI
The dosing-plan sheet now says which path it plans for, and the fluid plan sheet speaks bolus language. On Low/High drugs the sheet title reads “Dosing plan · Low dose” (or High) and follows a switch — previously it was silent about which path the numbers belonged to. Sodium chloride’s plan sheet previously leaked broken formatting (“+0 min” timing on every bolus, “Repeat 10 mL/kg every min, titrating to effect”, and a dangling “draw” with no volume — you do not draw up a bag); it now reads “after reassess” per bolus and closes with the protocol’s own words: “May be repeated twice — reassess between boluses. Total max 1,080 mL · 60 mL/kg.”
- What changed. Presentation-only, inside the slide-up dosing-plan sheet (S3/S4 from the repeat-regime review). The path label is the dataset option’s own label; the fluid cap line is the row’s repeat wording verbatim plus the established reassess note and per-kg ceiling. No dose value is recomputed.
- Also from that review: S1 (condition line in the sheet) and S2 (daily/weekly limits in the sheet) were checked against the live dataset and have no members — Glucose gel/Glucose 10% conditions and Methoxyflurane’s time-windowed limits already render on-page, and none of those rows opens a sheet. Dropped rather than built.
- No dose, volume, route, indication, contraindication or interaction logic changed.
v5.27
UI
The repeat block under the dose card now leads with the NEXT dose. Previously it opened with an INITIAL chip restating the number already on the dose card directly above, then enumerated every dose. The first chip is now the next dose to give — the decision you actually face mid-case — alongside an explicit count (“then up to 3 more · 5 doses total”), with the amber total max and “Full plan ›” unchanged. When the next dose is the last, it carries the amber MAX/FINAL treatment. The full numbered sequence is one tap away in the dosing plan sheet. Saline’s bolus plan keeps all its chips (the bolus count is the clinical content), and ceiling-limited TOP-UP plans keep the full sequence too. Diluted-prep chips (Fentanyl/Morphine IV) now show the volume only, with a “diluted prep” cue in the header — the recipe stays in the DILUTION strip and the plan sheet. The block keeps its height (chips sit side by side); this release changes what leads the block and what no longer repeats, not how much page it uses.
- What changed. Presentation-only, both regime builders (fixed rows and Low/High variable rows). The NEXT chip value is the same engine timeline entry that previously rendered as chip #2 — nothing is recomputed (regime-engine-is-a-presentation-layer rule). Dose counts are stated explicitly so “repeat” is never ambiguous.
- Deliberately exempt. Sodium chloride’s BOLUS PLAN (count chips are the dose path) and any plan carrying a ceiling-limited TOP-UP (the top-up must be read against the full visible sequence).
- No dose, volume, route, indication, contraindication or interaction logic changed. From the repeat-regime review (2026-06-11). Measured honestly: the block keeps its height (chips sit side by side, so fewer chips does not shorten the page) — the change is what leads it, and what no longer repeats.
v5.25
SAFETY
Glucose gel’s repeat condition now appears on the dose page. The protocol says “Repeated once at 15 minutes if BGL ≤4.0 mmol/L” — previously the page showed only “REPEAT once · Total max 30 g”, with the 15-minute interval and the BGL threshold missing entirely. Repeat instructions the pipeline cannot fully structure now render their protocol wording verbatim on the repeat strip, with the if/until condition emphasised and the total max on a calm line beneath. (The buccal route is consult-gated and shows no dose surface, so nothing changes there.)
- What changed. Presentation-only. When
repeat_protocol.kind === "unstructured" and the protocol text leads with “Repeat/Repeated”, the strip renders that text verbatim (leading word trimmed — the strip already carries the REPEAT label) instead of a parsed-count summary. The condition tail (“if …” / “until …”) is bolded. Affects exactly the two Glucose gel rows.
- Nothing invented, nothing recomputed. The strip text is the DTP’s own repeat wording from the dataset; no dose value, route, or condition is generated by the renderer. Restriction notes that are not repeat instructions (e.g. Paracetamol’s 4-hour spacing rule) and CCP infusion titration text are deliberately untouched.
- No dose, volume, route, indication, contraindication or interaction logic changed. All other repeat shapes render exactly as before.
v5.23
UI
When a protocol repeats the dose exactly once at the same amount (Methoxyflurane: 3 mL, repeated once after 20 min), the page no longer draws a second full-size dose card restating the same number. The repeat is now one calm strip — "Repeat once after 20 min · same dose, 3 mL" with a FINAL badge — and the total max sits on a line beneath. Protocols whose repeat is a different dose (e.g. Fentanyl intranasal: 1 microg/kg after a 1.5 microg/kg start) are unchanged: they keep the full second dose card, because the second number genuinely differs.
- What changed. Presentation-only. A new overlay branch (_isRepeatOnceCompactRow → enriched .mz-repeat-once strip) fires when repeat_protocol.kind === "repeat_once" AND there is no explicit different repeat amount AND the row is not a range (variable_select) row. The redundant legacy second card (#ddDose2Wrap) is suppressed under the overlay for these rows only.
- Same number, never recomputed. The "same dose, X" value is the already-validated initial dose read off the production card — the repeat equals the initial here by definition, so this is a restatement, not a second calculation (regime-engine-is-a-presentation-layer rule). The interval and total max are formatted from the row's own parsed fields.
- No dose, volume, route, indication, contraindication or interaction logic changed. Fentanyl NAS and all other repeat shapes render exactly as before.
v5.22
UI
The contraindications/precautions bar on the dose page is now one quiet row: red square marker + CONTRAINDICATIONS in the same mint tag-caps as DILUTION and INITIAL DOSE, with the counts removed from the page face (they added no decision value and the red text competed with the dose). The tap-through safety sheet is unchanged and still lists all contraindications and precautions in full. Drugs with zero contraindications keep the row as a muted doorway to the sheet.
- What changed. Visual-only restyle of the v4.58 .mz-safety-bar builder + CSS (mock series design/mockups/contra-presentation-mocks.html + contra-row-typography.html, variant H10 picked by Adam over 18 alternatives). Same element id, same tap target, same sheet.
- Clinical-information note for the pilot reviewer. The precaution COUNT no longer appears on the dose page face; precaution content is unchanged, one tap away in the safety sheet. Flagged as a presentation change for clinical review.
- No dose, volume, route, indication, contraindication or interaction logic changed.
v5.21
SAFETY
Closed the remaining fail-visible gaps: an unrecognised dose_type or repeat-protocol kind now surfaces the DATASET ERROR plate (previously an unknown dose type rendered a silently transformed value, and a junk repeat kind silently dropped the repeat strip); fixed_dose_or_age_band rows now get numeric/unit validation too. The full runtime error-class vocabulary is now a release-governing contract (docs/contracts/ERROR_SURFACE_CONTRACT.md); test_fail_visible extended to 7 corruption cases.
- Integrity gate (v5.21). KNOWN_DOSE_TYPES and KNOWN_REPEAT_KINDS vocabularies added to _validateRowIntegrity, mirroring the build pipeline whitelists; fixed_dose_or_age_band rows validate fixed_dose when present. Violations route to the existing DATASET ERROR plate.
- Contract. ERROR_SURFACE_CONTRACT.md defines the complete user-facing error vocabulary (DATASET ERROR / out-of-range consult / route unavailable / check weight / update / offline / app error) and the fail-visible invariant; the APP ERROR global boundary is recorded as the next hardening item.
- No dose, volume, route, indication, contraindication or interaction logic changed for valid rows.
v5.20
SAFETY
Fixed the airway page showing the OG tube unit twice (12 FG FG — the dataset value already carries FG and the template appended another). Added release-blocking test_resus_content: defib joules, CPR ratio/depth, OPA size/colour and i-gel size/OG tube must match the dataset across four ages — the first content guards for the non-Adrenaline resus pages.
- Display fix. Both og_tube_fg call sites now strip a trailing FG from the dataset value before appending the unit, so 12 FG renders once. No clinical value changed.
- New guard. test_resus_content (G7, last item of the 2026-06-11 blind-spot hunt) asserts dataset-derived values appear on the defib, CPR and airway pages for newborn/2y/5y/12y — content-only matching, so the planned Resus visual unification can restyle freely. Proven-to-fail against the previous build (FG FG + 12y sweep gaps). Compression RATE deliberately not asserted yet: the current CPR page does not display it (known gap, fixed by Resus ship R2).
- No dose, volume, route, indication, contraindication or interaction logic changed.
v5.19
SAFETY
Hardened the dose-row integrity gate: a row whose dose value is non-numeric, non-positive, or carries an unrecognised unit now surfaces the red DATASET ERROR plate instead of a silent blank dose page or a verbatim garbage unit. New release-blocking guard test_fail_visible proves every malformed shape fails loud.
- What changed. The pre-flight row check (_validateRowIntegrity) only tested that dose fields were non-null, so a corrupted value like a text string or a negative number slipped past the gate and died later in dose math - the dose surface went blank with no signal - and an unknown unit string was rendered verbatim as if it were a dose. The gate now requires a finite positive numeric value (and value_min when present) plus a unit from the recognised vocabulary (exact match, mirroring the build pipeline whitelist). Any failure routes to the existing DATASET ERROR safety plate.
- Why it matters. Today these states are unreachable - the build pipeline regex-parses values and whitelist-validates units before anything ships. This is defence in depth for the import era: when the import skill becomes the main writer of dataset rows, the renderer itself now refuses to show a wrong or empty dose, deterministically.
- New guard. test_fail_visible corrupts a live row in-page (string, negative, null, huge-but-capped, unknown unit, trailing-space unit) and asserts the surface shows either the DATASET ERROR plate or a correctly rendered dose - never hidden-blank, never NaN/undefined, never the garbage unit token. Proven-to-fail against the v5.18 renderer.
- No dose, volume, route, indication, contraindication or interaction logic changed for valid rows.
v5.18
SAFETY
Fixed the dose-page title band overflowing on iPhone SE-class screens for long drug names (Sodium chloride 0.9 pct pushed the route pill past the screen edge at 375px and narrower); the name now ellipsizes inside the band. Added two release-blocking guards from the blind-spot hunt: viewport geometry (320/375px overflow + clipping sweep) and Vitals view coverage.
- What changed. The relocated dose title row (.dd-title-row inside the mint band) is a flex item that kept its default min-width, so a long drug name could not shrink and pushed the row past the viewport edge on SE-class widths (62px over at 320, 7px over at 375). One CSS rule change: min-width 0 + hidden overflow on the row; the existing name ellipsis now engages. No markup, JS, or clinical content touched.
- New guards. test_viewport_geometry (V1 no horizontal overflow, V2 no past-edge elements, V3 no clipped key values, at 320x568 and 375x667 across six worst-case drugs; proven-to-fail on the unfixed build) and test_vitals (VT1 parameter ranges render for newborn/2y/9y, VT2 patient-reactive, VT3 zero page errors). Both carry a drive self-check that fails the run if the target surface never opened, closing the vacuous-pass class found in the hunt.
- No dose, volume, route, indication, contraindication or interaction logic changed.
v5.17
GOVERNANCE
No visible change — deleted ~337 lines of dead aw-wheel JS/CSS (Stages 1–4): awBuild factory, ageWheelDefs/wireAgeWheel, weightWheelDefs/wireWeightWheel, indicationWheelDefs/wireIndicationWheel, and all matching CSS; retargeted test_patient_state_contract
- What changed. The v2.43 age/weight/indication scroll-wheel system (awBuild factory + four Stage blocks) was already inert: all three wire*Wheel() functions bail immediately because their host elements (#awAge, #awWt, #awWtPill, etc.) were removed from the HTML in v2.73. The v2.73 section (v273WireHome) already replaced them and re-exports window._awSyncAge/_awSyncWeight/_awSyncIndication. Removed: JS Stages 1-4 (awBuild + AW_ROW, ageWheelDefs, wireAgeWheel, AW_CHEVR, weightWheelDefs, wireWeightWheel, indicationWheelDefs, wireIndicationWheel, all calls and block comments) and their matching CSS (Stage 1 .aw-wr/.aw-wl family, Stage 3 dead .aw-wt-sub/row/val/cap/warn/.aw-chev, Stage 4 .aw-ind-* family, Stage 5 empty block). Updated the v2.73 comment to remove the stale reference to the retired wire functions.
- How it was verified safe. Confirmed host elements absent from HTML (getElementById returns null; guards return immediately). window._awSyncAge/_awSyncWeight/_awSyncIndication remain defined by v273WireHome. test_bundle_rendering _awSyncIndication probe unaffected (v273WireHome defines it). test_patient_state_contract retargeted: removed the awBuild age-wheel visibility-guard check (the code is gone; the contract is satisfied trivially). The .s2-pp-checkwt and .pp-wt-warn CSS rules (live, used by the weight-check chip) are preserved inside the Stage 3 CSS block.
- No dose, volume, route, indication, contraindication or interaction logic changed.
v5.16
GOVERNANCE
No visible change — stripped 14 dead id= attributes from HTML elements that had zero JS/CSS references: s2NavHome, s2NavResus, s2NavMore, s2BottomNav, resusChips, ddpgSetBtn, ddCalcCardLabel, ddTitleSep, v273IndChev, v286AuditDot, v286StatusIndicators, v286StatusVersion, v286VersionDot, v314RowCurrent
- What changed. Fourteen id= attributes removed from HTML elements. Each confirmed to have exactly one occurrence in src/app.html (its own definition) and zero references in CSS selectors, JS getElementById/byId calls, or build test harnesses. The two nav IDs used by test harnesses (s2NavDrugs, s2NavVitals) were explicitly preserved.
- How it was verified safe. Grep confirmed each removed ID appeared exactly once in the source (the definition itself). Build test harnesses scanned — only s2NavDrugs and s2NavVitals referenced; both retained. No CSS selectors use any of the removed IDs.
- No dose, volume, route, indication, contraindication or interaction logic changed.
v5.15
GOVERNANCE
No visible change — deleted 8 dead JS functions/exports (90 lines): formatDose, _opaSwatch, patientChip, routeClass, _capByMax, _doseFormulaText, _concentrationText, window._s2UpdatePatientStrips export; also removed orphaned .opa-swatch CSS rule
- What changed. Eight dead code items removed: formatDose (v1-era dose display helper, superseded by doseCardInnerHTML), _opaSwatch (OPA colour swatch generator, never called), patientChip (returns weight label string, zero callers since v3), routeClass (returns route colour class string, retired v3.28 per inline comment), _capByMax (dose cap helper, never wired into the engine), _doseFormulaText and _concentrationText (debug display helpers for the calculation sheet prototype, not called by _buildCalculationBlock), and the window._s2UpdatePatientStrips export (function itself is live internally; only the window export was dead). Also removed the orphaned .opa-swatch CSS rule and its comment (only reachable via the now-deleted _opaSwatch function).
- How it was verified safe. Each function confirmed zero callers via grep over src/app.html (each name appeared exactly once — its own definition). The window._s2UpdatePatientStrips export confirmed zero external references in build/ and test harnesses. The .opa-swatch CSS token appears exclusively inside the deleted _opaSwatch body and its own rule.
- No dose, volume, route, indication, contraindication or interaction logic changed.
v5.14
GOVERNANCE
No visible change — deleted the permanently-hidden v2.81 inline audit cards: 56-line .v281-audit-* CSS family, 2-line hide rule, and both HTML panels (drug view + dose view), 91 lines total
- What changed. The v2.81 inline audit/transparency cards (.v281-audit-card on the drug and dose pages) were superseded by the slide-up dd-audit-sheet and permanently hidden via #v281DrugAudit, #v281DoseAudit { display: none !important; }. The v281AuditCard() JS hook mentioned in the HTML comment was never implemented. Removed: all .v281-audit-* CSS rules, the hide rule, and both HTML panels with their comments.
- How it was verified safe. All v281-audit-* class tokens appear exclusively inside the two removed panels — no other HTML uses them. Both panel IDs and all child IDs have zero external JS or CSS references. The hide rule confirms the panels were never shown in production.
- No dose, volume, route, indication, contraindication or interaction logic changed.
v5.13
GOVERNANCE
No visible change — orphaned-CSS cleanup batch 6: deleted ~370 lines across 87 dead rules from header-patient/start-legacy, changelog/feedback-link, cats/cat, v1 indi/dose display family, s2-home-headline/logo/weight-source, mz-safety-tile, aw-wheel old CSS, bhome-foot, v281-version-corner
- What changed. Pure stylesheet housekeeping — 87 fully-dead CSS rules removed. Families: .header-patient/.header-tag/.header-stack-row/.header-start-deck, .start-select/.start-calc/.start-resus/.start-foot/.start-links, .changelog-link/.feedback-link, .cats/.cat, .indi-block/.indi-title/.dose-line/.route-tag/.pres-tag/.dose-amount-big/.dose-note/.dose-max, .s2-home-headline/.s2-logo/.s2-weight-source, .mz-safety-tile, .aw-step/.aw-well/.aw-band/.aw-whl and 4 sibling old-wheel rules, .bhome-foot, .v281-version-corner.
- How it was verified safe. Each class token confirmed absent from all HTML class= attributes and JS string/template literals outside the style block. View-state tokens (v-resus, v-drug, etc.) seeded as always-live in the checker to prevent false positives from the showView("name") concatenation pattern.
- No dose, volume, route, indication, contraindication or interaction logic changed.
v5.12
GOVERNANCE
No visible change — deleted the retired Tools placeholder view: #toolsView HTML section, all .view-tools CSS (9 rules), 4 dead JS button bindings, and the orphaned /* === TOOLS PLACEHOLDER VIEW === */ block
- What changed. The v1.84 Tools placeholder view (a stub menu with "Save to home screen / Version history / Build provenance / Feedback" buttons and a "Coming in a future release" block) was superseded by the More view and never reachable from any current navigation path. Removed: the 15-line HTML section, all 9 .view-tools CSS rules, the body.v-tools .s2-bottom-nav comma-part, and the 4 s2ToolsInstall/Version/About/Feedback bind() calls.
- How it was verified safe. No current navigation path sets body class v-tools. The four JS bindings target DOM elements that are removed in the same commit, so the bind() calls are unreachable dead code. The More view (live) already wires identical Install/Changelog/About/Feedback actions on its own button IDs.
- No dose, volume, route, indication, contraindication or interaction logic changed.
v5.11
GOVERNANCE
No visible change — orphaned-CSS cleanup batch 5: deleted ~644 lines of dead per-rule orphans across mixed CSS families (.bar/.bar-route/.bar-dose/.bar-route-only and 7 siblings, .dose-detail-route/.dots/.swipe-hint/.body, .start-field/.card/.body/.summary and label/hint sub-classes, .header-start family, .ddc-title/.ddc-sub, .drug-hero-icon/.kicker/.meta)
- What changed. Pure stylesheet housekeeping — nothing a paramedic sees or taps changes. Dead CSS rules were removed rule-by-rule from mixed families where live siblings exist, using a class-token aware parser to avoid touching live rules. Two comma-separated rules were trimmed (dead comma-parts only).
- How it was verified safe. Every deleted class confirmed by static analysis to have zero class= attribute, JS string/template literal, or dynamic-construction usage outside the style block. Remaining live siblings (.bar-vol, .bar-max-single, .bar-max-total, .bar-route-tag, .start-select, etc.) were verified live before each deletion. querySelector calls for .start-card / .header-start are no-op dead code paths (DOM elements removed at v2.73, guarded by if (!el) return).
- No dose, volume, route, indication, contraindication or interaction logic changed.
v5.10
GOVERNANCE
No visible change — orphaned-CSS cleanup batch 4: deleted ~230 lines of dead v1.71/v1.73-era resus primitives (.fact-row/.fact-cell, .check-list/.check-cell, .rec-block family, .kv-row, .scenario, .sub-block, .shock-cell, .rule-list, .tips-list, .adr-kicker/.adr-name/.adr-dose-card/.adr-banner, .warn-box/.consult-box/.info-box) — all confirmed never emitted by any current renderer, across base and dark-mode overrides
- What changed. Pure stylesheet housekeeping — nothing a paramedic sees or taps changes. Seventeen dead CSS families introduced in the v1.71/v1.73 resus redesign were removed from the base panel and all dark-mode override blocks. The v1.71 comment block (which itself noted these classes were "no longer emitted by the renderers") was also removed.
- How it was verified safe. Each class was confirmed by static analysis to have zero usage in any markup, JS string, or template literal. The
.adr-page max-width rule was preserved (it is live); its .adr-dose-card and .adr-banner comma-parts were trimmed.
- No dose, volume, route, indication, contraindication or interaction logic changed.
v5.09
GOVERNANCE
No visible change — orphaned-CSS cleanup batch 3: deleted the dead .headline-* resus-page stylesheet family (38 rules), verified safe by the headless resus-parity guard
- What changed. Pure stylesheet housekeeping — nothing a paramedic sees or taps changes. The
.headline / .headline-key / .headline-val / .headline-sub CSS family (38 rules across the base panel and the dark-mode override block) was removed from the resuscitation-page stylesheet.
- How it was verified safe. No markup or script constructs these class names — the changelog entry at v3.02 notes "Old .headline... no longer emitted". The headless resus-parity guard confirmed the rendered resuscitation screen is pixel-identical before and after removal.
- No dose, volume, route, indication, contraindication or interaction logic changed.
v5.08
GOVERNANCE
No visible change — orphaned-CSS cleanup batch 2: deleted two more fully-dead stylesheet families (an old drug-name header and the retired home-screen install pill), neither of which has any element left to style
- What changed. Pure stylesheet housekeeping — nothing a paramedic sees or taps changes. Two retired CSS families were removed:
.drug-head* (an old drug-name banner replaced by the current header) and .install-pill* (the old “Add to home screen” pill, which lived in the long-removed Tools menu).
- How it was verified safe. A static check confirmed every one of those class names is 100% dead — it appears in no markup or script (including dynamically-built names), and the family has no live sibling classes sharing a rule — so no element can carry them and removal is provably pixel-identical.
- No dose, volume, route, indication, contraindication or interaction logic changed.
v5.07
GOVERNANCE
No visible change — removed 107 lines of dead stylesheet for the retired v2.75 hamburger-menu drawer, whose markup was deleted back in v2.78 (the styles had nothing left to style)
- What changed. Pure stylesheet housekeeping — nothing a paramedic sees or taps is affected. The CSS for the old pull-down hamburger menu (
.v273-menu-*: the button, drawer, panel, items and footer) was deleted. That menu’s markup was removed back in v2.78, so these rules had no element to apply to.
- How it was verified safe. A static check confirmed not one of those class names appears anywhere in the app’s markup or scripts (including dynamically-built class names), so no element can carry them — removal cannot change a single pixel. The full safety regime was re-run to confirm.
- Why. The 2026-06-10 audit identified ~1,374 lines of provably-orphaned CSS; this is the first stratum. Trimming it shrinks the file and removes decoy styles that waste time during future styling work.
- No dose, volume, route, indication, contraindication or interaction logic changed.
v5.06
GOVERNANCE
No visible change — internal cleanup from the code audit: removed provably-dead code (an unreachable legacy volume-label block, an orphaned window.state stub, a retired toggle’s leftover style) and corrected stale developer notes
- What changed. Housekeeping only — nothing a paramedic sees or taps is affected. Three pieces of dead code were removed from the app source: a legacy “Draw up X in Y” volume-label block that was permanently switched off (
if (false)), a stray window.state object that nothing read, and the leftover style for a More-menu toggle that was retired earlier.
- Notes corrected. An internal comment still claimed the patient gate waited on a retired flag (it actually reads the canonical patient model), and the navigation notes still described the retired breadcrumb bar as live. Both were brought up to date.
- Why. The 2026-06-10 audit flagged these as low-risk dead weight. Removing them shrinks the source and keeps the documentation honest without touching any clinical or display behaviour.
- No dose, volume, route, indication, contraindication or interaction logic changed. The full safety regime passes unchanged.
v5.05
SAFETY
Range-dose repeat plans now show the ceiling-limited final top-up — when only a partial dose remains under the total maximum, it appears as a clearly-marked amber TOP-UP chip instead of being silently hidden
- What changed. On range-dose drugs (Fentanyl IM, Morphine IM), the initial dose can hit its single maximum while the total ceiling still leaves room for less than a full standard repeat — e.g. Fentanyl High at 30 kg: initial capped at 50 microg, ceiling 60 microg, so 10 microg remains. The calculator always knew this; the repeat chips silently showed nothing. The remaining amount now appears as a hollow amber TOP-UP chip with its draw-up volume.
- Unmistakably not a standard repeat. The top-up chip is visually distinct (hollow, amber) and carries an explicit note: “Top-up is ceiling-limited — it is NOT the standard repeat dose”. The full dosing plan sheet shows the same row with the same wording. Standard full repeats render exactly as before.
- Why. The 2026-06-10 audit’s new parity guard caught the calculator and the visible plan disagreeing about whether this dose exists. Hiding a clinically available, protocol-compliant amount is the kind of silent divergence this app no longer tolerates. Reviewed and approved as a deliberate clinical-display decision (variant a3 of the reviewed mockups).
- Guarded. The release-blocking parity test now requires the top-up whenever the calculator offers a partial: engine walk, visible chips and total ceiling must all agree exactly, on every range-dose row, option and weight.
- No dose mathematics changed — the calculator computed this amount before and computes it now; only its visibility changed.
v5.04
GOVERNANCE
No visible change — internal consolidation from the full code audit: one canonical context-reset, one update path that exists on every device, one “last checked” record, and the Oxygen target bands now build-checked against the clinical dataset
- One reset, not three copies. The “clear the drug/indication/dose context” logic existed as three hand-copied blocks (Home routes, New patient, tab re-tap). They now share one canonical helper, so a future field can never be cleared by two resets and leaked by the third.
- Update path always available. The shared one-tap update routine (prime the offline cache, activate the new build, reload) was only wired up if a particular More-tab panel existed; without it the update banner silently fell back to a plain reload. It is now created unconditionally at startup.
- One “last checked” record. The More-tab panel and the version-chip panel kept separate timestamps for the same fact and could show different times. All update checks now write one shared record.
- Oxygen bands locked to the dataset. The SpO₂ target bands on the Oxygen page are now compared against the clinical dataset rule at build time — if either side is edited without the other, the release is blocked. Two new release-blocking guards from the same audit batch (pilot-coverage import gate, repeat-plan parity) also joined the regime.
- No dose values, volumes, rules or clinical data changed; no screen looks different.
v5.03
SAFETY
Returning from Vitals now always redraws the page for the current patient — changing the patient on Vitals can no longer leave the previous patient’s dose on screen
- The bug. If you opened Vitals from a dose page, changed the patient there, and tapped Back, the dose page was shown again without being redrawn — it still displayed the previous patient’s weight and dose (e.g. 12 kg / 2.4 mg for a patient now set to 30 kg). Found in the 2026-06-10 audit and confirmed in a real browser. Back from Vitals now redraws the target page from the current patient first, and steps back a level if the saved dose row no longer applies to the new patient.
- One honest offline answer everywhere. The version-chip status panel used to say “Offline-ready” as soon as a service worker existed, without checking the offline cache — the Home line and About diagnostics were stricter. All three surfaces now use the same strict rule: ready only if an offline relaunch would actually boot.
- Hidden legacy widgets fully fenced. Two leftover tap handlers on the old hidden Home (the weight chip and indication list) could still write the canonical patient or indication if triggered programmatically. They now carry the same visibility fence as the old wheels, and the build fails if any of these fences is removed.
- Drug page joins the patient gate. The drug overview now has the same deny-by-default “Set patient” gate as the dose, list, Vitals and Resus screens — no clinical surface renders without a patient, on any path.
- No dose values, volumes, rules or clinical data changed.
v5.02
SAFETY
On a drug that needs a consult call before you give it, the dose is now held behind a tap so the call comes first — and the manual weight is now whole-kilogram and deterministic: what you set is what doses, with no silent fallback
- Consult drugs hold the dose until you tap. When a drug requires consult approval in every situation (e.g. Droperidol for acute behavioural disturbance), the page now leads with CONSULT REQUIRED and a Call consult button, and the dose number, draw-up and repeat plan sit behind a “Show dose — for after approval” tap. The presentation and the contraindications row stay visible the whole time. One tap reveals the figure once you have approval — nothing is removed, just deliberately gated so the call comes first. Switching the Low/High bolus after revealing keeps the dose shown.
- Manual weight is whole-kilogram and deterministic. The manual override now accepts whole kilograms only, across the full 2–160 kg range — 17 means 17, 120 means 120. The half-kilo keypad key is gone, so a decimal can’t be entered or committed, and an out-of-range or invalid value is refused outright rather than silently falling back to the age-band estimate while the bar still reads “manual”. The weight you set is always the weight the dose uses. (The engine stays decimal-capable internally, so half-kg steps can be enabled later if they’re ever clinically needed.)
v5.01
SAFETY
Render hardening from the full code audit — a mid-render fault now shows a blank dose instead of the previous one, and a failed contraindication check now blocks the dose instead of skipping the check
- Fail blank, never stale. The dose page now clears the previous dose figures and overlay before it starts drawing the new ones. If anything goes wrong mid-render, you see an empty dose card — never the last patient’s or last drug’s number under the new header.
- Contraindication check now fails closed. If the contraindication rules for a drug cannot be evaluated, the app withholds the dose behind the red plate (“Safety check failed — refer to the DTP”) instead of silently skipping the check and showing the dose as if the drug were safe.
- Latent crash removed from the repeat-dose calculator. A stray line inside the pure dose calculator referenced a screen element it had no access to. Unreachable with today’s dataset, but one future drug row could have triggered it and frozen the page mid-render. Found and removed in the 2026-06-10 full code audit.
- No dose values, volumes, rules or clinical data changed — every number renders exactly as in v5.00. This release only changes what happens when something goes wrong.
v5.00
SAFETY
The app no longer shows any dose, vital range or resus value for a patient you have not entered — every screen now waits for you to set the patient first
- No more default patient. The app used to silently open with a hidden default (a newborn) and would calculate a confident-looking dose against it. It now starts with no patient: until you set age and weight, the dose, Vitals and Resus screens show a clear “Set patient” prompt instead of a number — on every way in, not just the Home cards.
- The shown patient always matches the dosed patient. The patient strip now reads age and weight from the same place the dose is calculated from, so the weight on screen can never disagree with the weight the dose used.
- One source of truth. Behind the scenes the patient is now a single canonical value rather than a mirror kept in sync across hidden legacy screens — removing a class of stale-context bugs.
- Clinical doses, volumes and rules are unchanged — this is about when a number is allowed to appear, not what it is.
v4.99
UI
Setting age and weight now uses the same age-band grid + weight keypad everywhere, matching the Home screen — one consistent patient picker
- One patient picker. Tapping the patient strip on any page (drug, dose, Vitals, Resus) now opens the same age-band grid and measured-weight keypad the Home screen uses, instead of the older dropdown sheet. Same selection everywhere.
- New patient is built in. The picker has a two-step "New patient" (tap, then confirm) that clears the patient and current selection — the same reset as before, just in the new picker.
- Presentation only — the canonical age/weight and all dose maths are unchanged.
v4.98
UI
The Back button is now a solid dark pill instead of quiet text — easier to see and tap on every page header
- Back reads as a button now. On the mint header band the Back control was flat dark text that blended in. It's now a solid dark pill with a white chevron and "Back" label — higher contrast, an obvious button, and a bigger tap target for gloved hands.
- Consistent everywhere. The same loud Back appears on the drug, dose, Vitals and Resus headers.
- Presentation only — navigation behaviour is unchanged.
v4.97
UI
The redesigned dashboard Home and the Indications lens are now the standard experience for everyone — no more beta toggles in the More menu
- Dashboard Home is now the Home. The patient card + Resus / Vitals / By indication / By drug layout you'd been trying as a beta is now the default landing screen for everyone — no toggle to turn on.
- Indications lens is standard. The "Pharma" indication-first flow is on for all devices (it had been the default with an opt-out; the opt-out is now gone too).
- More menu tidied. The "Home (beta)" and "Indications lens" rows are removed — both features are simply always on now. About, Install, Feedback and Version history are unchanged.
- Presentation/navigation only — no dose, volume or clinical value changed.
v4.96
UI
The context breadcrumb is gone — the drug page and dose page each get 26px of vertical back, and the route-change cue now lives on the route pill itself
- Breadcrumb removed. The slim "IND › Croup › Dexamethasone › PO" trail under the patient strip is gone from both the drug and dose pages. Now that Back always steps up predictably, the trail's wayfinding job was already done — and on the dose page the drug, indication and route are all still on screen (header, the labelled indication section, and the route pill). The dose number and DRAW UP slab now sit 26px higher.
- Route-change cue moved to the route pill. When you switch route from the route sheet, the route pill briefly pulses amber instead of flagging a "CHANGED" tag in the old trail — the one signal the breadcrumb uniquely carried is preserved where you're already looking.
- Navigation/presentation only — no dose, volume or clinical value changed.
v4.95
UI
Oral liquids you measure now get the loud mint DRAW UP volume slab too — Dexamethasone, Ondansetron and Paracetamol PO read like the rest of the app instead of a small inline volume
- Measured oral volumes get the mint slab. When a PO dose is a real measured volume drawn from an ampoule or liquid (Dexamethasone croup/asthma 1.5 mL, Ondansetron PO, Paracetamol liquid), the volume now shows on the bold mint DRAW UP block under the dose — the same loud treatment injectables use — instead of the small inline "in 1.5 mL".
- Puffs, gel and tablets unchanged. The slab only fires on a real millilitre volume, so inhalers, Glucose gel and tablets keep their plain line, and fluid bags still never show a draw-up.
- Presentation only — no dose, volume or contraindication maths changed.
v4.94
UI
Beta Home gets a Vitals card — a quick tap straight to the age-based observations, sitting right under Resus in the find-a-dose list
- Vitals on the home screen. A new card under Resus jumps straight to the Vitals deck (HR, RR, BP and weight for the patient's age). It uses a cyan accent so it reads as a reference lookup, not a dose.
- Same patient gate. Like Resus, tapping it before you've set a patient opens the age picker first, then lands on the obs.
- Navigation only — it routes into the existing Vitals deck; no values are calculated on the home screen.
v4.93
UI
Home beta version line now shows the full status — dataset version, how recently the data was updated, and the truthful offline-ready indicator — matching the classic Home
- Full status line. Under the title, the version line now reads e.g. "v4.93 · dataset v3.0-ccp · updated today · Offline ready ✓" instead of just the app version. It mirrors the classic Home's line, so the dataset age and the genuine offline-ready check are surfaced on the beta Home too.
- Presentation only — it reflects the existing, validated status (no new check, no duplicated logic).
v4.92
UI
Home beta gets its finished look: the app title moves into the page under a clean version-only mint band, the patient becomes a big, glanceable hero up top, and the three route cards sit balanced below
- Title in the page, not the bar. The mint band at the top is now just the version chip; "QAS Paediatric Calculator" (with the "Field dosing reference" line) sits below it, so the header stays clean and consistent with the rest of the app.
- Patient is the hero. The patient card is bigger and bolder — the age reads at a glance ("5 yr"), weight beneath — and says "Set patient" until you confirm one. Tapping it opens the age-band + keypad picker.
- Balanced layout. A deliberate gap separates the patient (set-up) from the Resus / By indication / By drug cards (find-a-dose), which sit lower in thumb reach. Presentation only — routing and doses are unchanged.
v4.91
UI
Home beta gets the proper age/weight picker — a big age-band grid and a measured-weight keypad — and a clearer "Set patient" prompt; plus the same category fix applied to Hypovolaemia/Shock so Glucagon no longer appears there either
- The age/weight picker matches the design. In the beta Home, tapping the patient card now opens a large age-band grid (Newborn → 12 years, each showing its estimated weight) with a one-tap measured-weight keypad — the same big-target layout from the mockups, instead of the older dropdown sheet. It writes the patient exactly the same way underneath, so every dose is unchanged.
- Clearer "Set patient". The card now says "Set patient" until you actually confirm one, rather than showing the default Newborn — so it's obvious a patient still needs setting. (The app's underlying default is unchanged.)
- Second category fix. "Hypovolaemia / Shock" no longer lists Glucagon (its text mentions "hypotension/shock" in an anaphylaxis context). Only true fluid/perfusion indications — Sodium chloride — appear there now.
v4.90
UI
A redesigned Home is available as an opt-in beta (More → "Home (beta)"): a calm dashboard with the consistent mint header and three clear ways in — Resus, By indication, By drug — plus a tighter category fix so Glucagon no longer shows under Adrenal crisis
- New Home, opt-in. Turn it on under More → "Home (beta)". You get a dashboard landing: a patient card up top, then three large cards — Resus, By indication, By drug — under the same mint header the rest of the app uses. Off by default; the classic Home is untouched.
- It uses the real app underneath. The cards just route into the existing, validated Resus deck, Pharmacology lists and patient editor — no new dose maths or drug logic was added. Every dose you reach is the same trusted calculation as before.
- Category fix. "Adrenal crisis" no longer lists Glucagon — its protocol text mentions "adrenaline", which was matching the category by accident. Now only true adrenal-crisis / adrenal-insufficiency drugs appear there.
v4.89
UI
Two finishing touches on the drug list: the scroll track on the right is now actually gone, and the Indications/Drugs toggle sits flush under the patient bar so list rows no longer peek through above it
- Scrollbar gone for real. The previous fix hid the wrong element — the page actually scrolls on the document root, so the track still showed. It is now hidden on the right element.
- No more peek-through. The toggle had a small gap above it where a scrolling row could show through between it and the patient bar. The toggle now sits flush against the bar — clean, and still no jitter when you start scrolling.
- Presentation only — no change to any dose, drug, or navigation behaviour. Guarded by a new scroll-pinning test so it can't quietly come back.
v4.88
UI
There is now just one "update available" notification — the app previously had two overlapping update checkers that could each pop their own banner, sometimes with different wording
- One update banner, not two. An older auto-checker has been retired. The app now has a single update path — the boot/resume version check — so a new version raises exactly one banner ("Update available · reload before clinical use"), never two competing ones.
- The More-tab "Check for updates" matches. The manual check in the version-chip panel now shows that same single banner, and the panel's "up to date · last checked" line keeps working from the one remaining checker.
- No change to what triggers a check (boot, foreground resume, 30-min poll) or to the offline-silent, fail-closed behaviour — just one notification instead of two.
v4.87
UI
Two small scrolling fixes on the drug list: the Indications/Drugs toggle no longer shifts as you start scrolling, and the scrollbar that flashed on the right is gone
- Toggle stays put. The Indications/Drugs switch used to slide up a few pixels (and tuck behind the patient strip) the moment you started scrolling. It now pins exactly where it sits at rest — no movement.
- No scrollbar. The list no longer shows a scroll track on the right while you scroll, matching the rest of the app.
- Presentation only — no change to any dose, drug, or navigation behaviour.
v4.86
UI
The app now checks for a new version the moment you bring it back to the foreground — not only on a fresh launch — so a build left open in the background can't quietly sit on an old version for days
- Checks on resume, not just on launch. When you switch back to the app from the app-switcher, it silently re-runs the same version check it does at boot. If a newer build is live, the update banner appears; one tap applies it. Previously a warm resume (the app never fully closing) could miss updates indefinitely.
- Also checks every 30 minutes while open, as a backstop — and only while the app is actually on screen.
- Honest and quiet. Offline, the check fails silently and never falsely claims you're up to date; checks are throttled so switching apps can't spam the network. No clinical data is fetched — only the app shell version. (Note: like every iOS web app, it cannot update while fully backgrounded — it checks the instant it is brought forward.)
v4.85
UI
When you're on a filtered drug list (e.g. only the Anaphylaxis drugs), one more back-swipe now clears the filter and returns you to the full indication list — a quick one-thumb way out without reaching for the Clear button
- Back-swipe clears an active filter. After you drill into an indication and then step back to its drug list, swiping back once more now clears the filter and lands you on the full Indications list — completing the natural ladder (indication list → filtered drugs → drug → dose) in both directions.
- The Clear button stays. Nothing is removed — the "Clear" chip is still there for when you'd rather tap. The back-swipe is just a faster path under your thumb.
- No surprise jumps. Once you're on the unfiltered list (the section root), back-swipe does nothing, exactly as before — you leave via the bottom tabs. Navigation only; no clinical value is affected.
v4.84
UI
Back now knows how you got to a drug: if you reached it from an indication category (e.g. Anaphylaxis), Back returns you to that category's drug list instead of asking you to pick the indication all over again
- Back respects the indication you came from. Drilling Anaphylaxis → Glucagon → dose, then hitting Back, used to drop you on Glucagon's "Select indication" screen — listing conditions (like hypoglycaemia) that have nothing to do with where you were. Back now steps you back up to the Anaphylaxis list, keeping your category.
- Two paths, two correct destinations. If you instead opened Glucagon from the full drug list and chose the indication yourself, Back still returns you to that indication picker — unchanged. Where Back goes now matches how you got there.
- Breadcrumb and screen agree. The trail at the top and the page you land on after Back always match. Presentation/navigation only — no dose, volume, or clinical value is affected.
v4.83
UI
The Defib, CPR and Airway pages get a visual signature to match Adrenaline: a pad-placement body diagram that redraws front/back vs left/right by age, a hand or two-thumbs icon for compressions, and device silhouettes on the airway sizes
- Defib pad placement, drawn. A torso diagram shows where the pads go — right chest + left flank for anterior-lateral (≥6 yr), or front + back (dashed) for anterior-posterior in small children. The picture is generated from the dataset's placement field, so it can never disagree with the words beneath it, and it swaps automatically with patient age.
- Compressions at a glance. A single large icon shows the technique for this patient — an open hand for "2 hands", or encircling thumbs for "2 thumbs" on infants — above the existing sternum/depth text.
- Airway devices, recognisable. The OPA and i-gel cards now carry a device silhouette, with the OPA colour swatch sitting right beside the size (the colour is the sizing cue).
- Consistent icons. The deck tab row drops its mismatched glyph set (including a stray full-colour syringe emoji) for one clean set of line icons. Every clinical value stays dataset-derived — this is presentation only.
v4.82
UI
Swiping back now does exactly what the Back button does — one predictable step up out of where you drilled (dose → route → indication → drug list) — instead of retracing your exact taps, which could jump you to Home or quietly swap drug context
- One back, one meaning. The left-edge swipe is now identical to the header Back button: a single deterministic step up the hierarchy, the same way iOS itself treats the swipe and the nav-bar back as one gesture. No more guessing which "back" you're using.
- Why it changed. The old swipe retraced your exact visit history. In a clean drill that matched the hierarchy, but after jumping levels or switching indication in place it did surprising things — swiping back from a dose page could land you on Home, or swap the indication underneath you without moving up a level. That ambiguity is gone.
- At the drug list, back rests. The list is a section root; like an iOS tab root, the back-swipe there does nothing — you switch sections with the bottom tabs. The right-edge "forward" swipe is retired (a predictable hierarchy has no forward).
- Guarded. The navigation test now asserts the swipe and the Back button land on exactly the same screen at every level, the back gesture is a no-op at the root, forward is gone, and an in-place indication switch followed by back steps up rather than swapping context.
v4.81
UI
When you filter the drug list by indication, the active filter is now a loud mint bar that stays on screen as you scroll and tells you "Showing 3 of 21 drugs" — so a short filtered list never leaves you wondering where the rest of the drugs went
- The filter you can't lose. The quiet grey filter row is now a mint-edged bar matching the "you are here" language used on drug and dose pages. It pins below the Indications/Drugs toggle, so on a long filtered list it can never scroll out of sight — the answer to "why only N drugs?" is always visible.
- The count is the trust signal. "Showing X of Y drugs · filtered" makes it unmistakable that you're looking at a filtered subset, not the whole formulary. The number is read from the same list the page renders, so it can never drift.
- Long names handled. The longest indication category ("Acute behavioural disturbance") wraps to a second line; the count and one-tap Clear never move.
- Guarded. A new release-blocking test asserts that a filtered list always shows a visible, pinned, truthfully-counted bar — and that Clear restores the full list. Search stays out of scope deliberately (it has been hidden since v2.80); if it returns it will reuse this same bar.
v4.80
UI
Sodium chloride stops hiding its ceiling: the 10–20 mL/kg range becomes a Low/High bolus selector showing the chosen volume as the hero number, a three-bolus plan with reassessment between, and the total max — 60 mL/kg computed for your patient — in the same loud style every capped drug uses
- Pick the bolus, see the number. LOW 10 mL/kg / HIGH 20 mL/kg tiles replace the 200–400 range hero — at 20 kg you see 400 mL, not a decision deferred. Same selector paramedics already use on Fentanyl IM; the rates sit right in the tiles.
- The plan and the ceiling, spelled out. "May be repeated twice" now reads as a BOLUS PLAN — INITIAL · #2 · #3 — with "reassess between boluses" and the final chip marked FINAL (count limit) or MAX (ceiling hit). The footer carries "Total max 1,200 mL · 60 mL/kg" instead of one cramped line.
- No draw-up slab. You don't draw up a bag — the chosen volume IS the hero, per the locked f1a design.
- One calculator, two unit families. The dose engine gained a volume family (mL base) kept strictly apart from mass — a cap in the wrong family can never be applied (it refuses, and the row falls back to the old display). "May be repeated N times" is now a structured repeat the engine can walk, mirrored identically in the Python parity tests.
- Guarded. FLUID-SELECT is a first-class renderer bucket with its own contract section; the bucket probe exercises BOTH endpoints through the tiles (preserving the BUG-001 lower-bound guard), checks the chip count against the repeat limit, and proves the plan total can never exceed the computed cap.
v4.79
RUNTIME
The "Offline ready ✓" tick now appears the moment the offline cache finishes installing instead of waiting for a fixed timer, and the release safety regime runs its browser drills in parallel — same 33 checks, a third less wall time
- Faster truthful tick. The Home offline-ready indicator re-checks when the service worker actually activates (with the old timer kept as a fallback) — on first install the ✓ lands seconds sooner, and it is still never shown before the cache genuinely holds the app.
- Tooling. The 15-second update-banner drill joined the parallel pool of headless release checks; no blocking semantics changed.
v4.78
UI
Two field-trust additions: Home now states plainly whether the app would survive losing signal ("Offline ready ✓"), and a manual weight that looks like a typo — 8 years · 80 kg — raises a quiet CHECK WEIGHT chip on the patient bar without blocking anything
- Offline ready, truthfully. The Home version line ends with "Offline ready ✓" only when an offline relaunch would actually boot — the same strict rule the About diagnostics and the release chaos drills use (active service worker AND the app shell in cache). Otherwise it quietly reads "Offline not ready". Never a false reassurance in either direction; checked on the phone, works offline.
- Check weight, quietly. A manual weight below half or above double the age estimate — the order-of-magnitude typo wheel pickers can't catch — shows a small amber CHECK WEIGHT chip on the patient bar (tap the bar to fix it) and a one-line advisory in the patient sheet. Plausible heavy or light children never trigger it. Doses still calculate; nothing is blocked; the manual override stays yours.
- One threshold, one place, pending sign-off. Both tiers (the long-standing mild-deviation advisory and the new typo bounds) now read from a single configuration with a recorded note: thresholds require clinical governance sign-off before the pilot. This is a conservative engineering nudge, not a validated growth-chart model.
- Guarded. The patient-reset test gains seven cases (typo shows · plausible doesn't · infant typo · sheet advisory · D6 keeps the chip · New patient clears it · the offline chip never claims readiness it doesn't have) and the chaos matrix verifies the chip reports ready on a genuinely installed build.
v4.77
GOVERNANCE
No visible change — the offline guarantee is now tested under failure, not just structure: a new release-blocking chaos matrix interrupts installs mid-download, cuts the network mid-refresh, deploys updates while offline and replays the whole update flow, proving the cached app can never half-install, corrupt itself, or invent a phantom "update available" with no signal
- Six failure drills, every release. Clean install · offline relaunch · interrupted install (must fail whole — never a partial cache, the blank-screen class) · interrupted background refresh (the old build must keep serving byte-identical) · update deployed while offline then reconnect · the full update-banner flow end to end, including booting the updated build offline afterwards.
- No phantom update states. With no signal, the app must never claim an update exists — asserted at every offline phase. Field trust: no false reassurance, no false alarm.
- Real parts only. The drills run the shipped service worker against two real release snapshots on a controlled local server — no mocks. The matrix is proven to bite: a deliberately broken (non-atomic) service worker fails it immediately.
- Contract written down. New SW_CONTRACT.md governs the offline layer the way the renderer constitution governs dose surfaces; the last open engineering risk from the pre-pilot hazard analysis (R-5) is closed.
v4.76
UI
Two field-safety affordances: Home now shows how old the clinical dataset is ("updated N days ago", turning amber past 30 days), and the patient sheet gains an explicit New patient action — confirmed with a second tap — that clears age, weight and all dose context in one deliberate step
- Dataset age, always visible. The version line on Home now reads "… · updated today / N days ago", computed on the phone with no network. Quiet mint normally; amber once the data is 30+ days old. Information only — the existing update banner remains the way to fetch a new build when online.
- New patient, deliberately. Tap the patient bar → "New patient" sits at the top of the edit sheet. One tap shows a confirm step (so it can't fire by accident); confirming clears age, weight, any manual weight override, the indication/route/dose context and the page history, then lands on Home ready for the next job.
- Two resets, two meanings. Re-tapping a bottom tab still resets only the section and never touches the patient; New patient is the explicit path that does. A new release-blocking test (test_patient_reset) pins both behaviours and the age line's fresh/stale states.
v4.75
GOVERNANCE
No visible change — this release closes the renderer contract's entire test-coverage debt: new release-blocking probes for the single-dose banner, ampoule strip, titrate card, fluid volumes, oxygen panel and dose-selection reset, plus a dataset text lint that makes the recent wording-cleanup class of defect impossible to ship again
- Six new probes, one test. Every renderer bucket the contract lists now has a behavioural guard: the SINGLE DOSE ONLY banner must render (and no repeat cadence with it), the Adrenaline ✓/✕ ampoule strip must appear on high-emphasis rows and never on normal ones, fluid rows must show the per-kg volumes computed from their own rule — including the range lower bound — and the Oxygen page must stay a pure reference panel.
- Dataset text lint. The wording defects cleaned up in the previous release (authoring notes, deprecated consult phrasing, reconstitution variants, stray µ symbols) are now rejected at build time across 1,060 clinical text cells — and documented as mandatory checks in the drug-import workflow.
- Canonical screenshots. One reference render per bucket is now stored with the contract, so future visual changes are compared against a known-good image, not memory.
- Heap audited. 500 consecutive renders grow the JS heap by 0.2 MB (+6%) — no leak over a long shift.
v4.74
DATA-ONLY
Dataset text cleanup from the consistency audit: an authoring note removed from the Fentanyl IV dilution line, a Salbutamol grammar fix, one consult sentence pattern, and one reconstitution phrasing across Glucagon, Hydrocortisone and Ceftriaxone
- Fentanyl IV dilution line (A1). The presentation row read "…User supplied prep: 100 microg with 10 mL saline; maths = 10 microg/mL" — import-review language on a clinical surface. It now reads "Add 8 mL sodium chloride 0.9% to the 100 microg / 2 mL ampoule (10 mL total)", matching its own preparation sheet. Same preparation, same concentration — wording only.
- Salbutamol nebuliser (A2). "1 mg/mL or 2 mg/mL depending nebule" → "…depending on nebule".
- One consult sentence (A3). The two remaining wording variants normalise to "Consult approval required before administration in all situations." — the "all situations" scope is kept and still shows on the consult row.
- One reconstitution pattern (A4). "Reconstitute N mg vial with N mL diluent" everywhere — no "each"/"the" variants, no stray full stops. Glucagon, Hydrocortisone and Ceftriaxone IM; all quantities unchanged.
- No dose mathematics changed. 27 text cells across the source spreadsheet; every dose, volume, cap and repeat value is untouched and re-verified by the full regime.
v4.73
UI
The patient bar gets its person icon: a mint head-and-shoulders-in-a-circle now sits beside the PATIENT label on every view, as designed
- Restored from the agreed design. The persistent patient strip was designed with a circled person icon leading the PATIENT label; it had never made it into the build. It now renders on the Pharmacology list, drug, dose, Resus and Vitals views — 15px, mint, matching the label.
- Cosmetic only. No layout shift beyond the label column widening to fit the icon; no behaviour, dose or data change.
v4.72
SAFETY
A contraindicated page now shows the red plate and nothing else: the residual dose-card body that rendered beneath it — on two drugs even showing dose text — is gone, and a new release-blocking test keeps it gone
- What you saw. On a contraindicated row (e.g. Droperidol under 8 years) the red ABSOLUTE · Contraindicated plate rendered correctly, but underneath it a leftover dose-card body still said "DOSE · Contraindicated" — two signals for one state. On Salbutamol MDI and Ondansetron the leftover even included dose text ("6 inhalations (600 microg)") under a do-not-administer plate.
- Why it happened. The rules that collapse the card body on contraindicated rows date from v2.x; the v3.52 stacked-card redesign restyled the same elements and silently won the CSS cascade — the same regression class as the v4.56 repeat-strip fall-through. The fix is a single kill-switch on the card body that no future restyle can out-rank.
- The plate is the page. Contraindication, reason, and "Do not administer to this patient." — plus the alternative-drug suggestion where one exists. No dose number, volume, prep strip, rule pill or calculation icon can render beneath it.
- Guarded. New release-blocking test (test_contra_no_dose) sweeps every rule-carrying row across ages and fails if anything but the plate is visible on a contraindicated page. Verified to fail on the previous build (15 violations) and pass on this one.
v4.71
UI
The Call consult button is now gold: the consult row reads as one amber warning unit, and the call action no longer borrows the mint used for routine actions like DRAW UP
- Warning vocabulary, end to end. The compact consult row from the previous release kept a mint Call button — mint is the app's routine-action colour. The button is now solid gold with dark text, matching the row it sits in, so a consult page reads as a single amber signal rather than a warning with a routine button attached.
- Clearer next to the dose. On consult-with-dose pages the gold button no longer competes with the mint DRAW UP slab below it — one gold action (call first), one mint figure (what you'll draw once approved).
- Colour only. No layout, wording, dose or behaviour change; the consult dedupe guard from the previous release continues to cover these surfaces.
v4.70
UI
Consult pages say it once: every consult surface is now a single compact amber row — CONSULT REQUIRED, a short "Before administration" qualifier, and the Call consult button — replacing the tall panel and the sentence that repeated the title
- One banner, one message. An audit of all 13 consult-required rows found each pairing its CONSULT REQUIRED heading with a sentence restating it ("Consultation and approval required before administration."). The restatement is gone; the timing meaning survives as a short qualifier under the title. Row-specific detail — age thresholds, "in all situations" — still shows in full.
- ~75% less screen. The consult-only panel drops from ~218px to ~63px; the consult-above-dose banner from ~118px to ~63px. On consult-with-dose pages (Droperidol, Sodium chloride infusion) the calculated dose keeps the screen.
- Call consult, one tap. The mint Call button stays on every consult surface, dialling the consult line directly; the number shows on the dial confirmation.
- Guarded. New release-blocking test (test_consult_dedupe): every consult page renders exactly one consult banner, no duplicated consult wording anywhere, the Call action present, and absolute contraindications still replace the consult surface entirely. Verified to fail on the previous build.
v4.69
UI
Three refinements: the drug list header reads "Pharmacology" in full, the Indications/Drugs toggle stays on screen while the list scrolls, and the Resus Adrenaline page now renders the same dose surfaces as the drug detail page — ampoule confirm strip, dark draw-up card and the one-line repeat signal
- Pharmacology, spelt out. The mint list header now reads "Pharmacology"; the bottom-nav tab keeps the short "Pharma" so the label fits.
- The lens toggle stays put. Scrolling a long drug or indication list used to carry the Indications/Drugs toggle off screen; it now pins below the patient strip, so switching lenses never requires scrolling back to the top.
- Resus Adrenaline matches the drug page. The Resus card previously kept an older compact layout. It now renders the same surfaces the Adrenaline cardiac-arrest dose page shows: the split ✓/✕ ampoule confirmation strip (correct dilution vs the look-alike to leave in the pack), the dark stacked dose card with the mint DRAW UP volume slab and per-kg rule line, and the single "Repeat every 3–5 min · No max dose" strip. Every value is still derived from the same dataset row — presentation only, no dose-calculation change.
v4.68
UI
Tapping the tab you're already in now takes you to the top of that section: re-tap Drugs anywhere in a drug flow and you're back at a clean drug list, with the old indication, route and dose context fully cleared — your patient stays exactly as set
- Two taps, two meanings. Tapping a tab from another section still returns you to where you left off in it (unchanged since v3.38). Tapping the tab of the section you're already in resets it to its root — the standard "take me back to the top" gesture.
- Drugs reset is a clean slate. Indication filter, route picker stage, dose page state and any Low/High dose selection are all cleared; any open slide-up sheet is dismissed; the edge-swipe history is reseeded so a back-swipe can't retrace into the discarded drill-down. Header Back is unaffected — it remains the state-derived step up the ladder from v4.67.
- Patient is never touched. Age and weight survive every reset — one patient per job. Home, Resus, Vitals and More re-taps simply repaint their default surface.
- Guarded. New release-blocking test (test_tab_reset): deep drug flow → re-tap → clean root, no stale clinical context, patient unchanged, sheets dismissed, history reseeded. Verified to fail on the previous build and pass on this one.
v4.67
UI
The Back button is now predictable: it always steps one level up — dose page to its route picker, route picker to the indication picker, picker to the drug list — no matter how you arrived; the edge-swipe gesture remains the way to retrace your actual steps
- One hierarchy, one rule. Drug list → choose indication → choose route → dose. Back walks that ladder upwards, computed from what's on screen now — never from a remembered entry path. Previously a dose page reached straight from the list took Back to the list, while the same page reached through the pickers went to a picker: two destinations for one button.
- Two gestures, two meanings. Header Back = up the ladder (predictable under load). Edge-swipe = back through the pages you actually visited (kept exactly as it was, including the patient-aware row restore from v4.64).
- Skipped levels stay skipped. A drug with one indication and one route goes straight back to its drug page; a single-indication drug's dose page returns to its route picker. Nothing asks you to re-answer a question that has only one answer.
- Guarded. New release-blocking test (test_nav_up): seven scenarios chosen from the dataset by shape, including proof that the old entry-path flags can no longer change the destination. Verified to fail on the previous build and pass on this one.
v4.66
SAFETY
Slide-up sheets can no longer outlive the page they were opened from — switching drug, changing the patient, or moving to another view now always dismisses any open sheet first, so a route list for one drug can never float over a different drug's dose page
- The gap. v4.62 stopped edge-swipes from navigating under an open sheet, but other paths could still repaint the page beneath one: switching drugs from history, changing age/weight, or jumping to Vitals left the old sheet floating — and its buttons acted on the NEW page's context. A systematic audit pass found and reproduced this in-browser.
- The rule, not a patch. Every view change and every dose/drug repaint now dismisses all open sheets first, through the same single Escape path each sheet already listens on — one dismissal vocabulary, no per-sheet special cases. Normal flows (opening sheets, picking a route, the patient editor's Apply) are unchanged.
- Guarded. The release-blocking stale-state matrix now fingerprints body-level sheets and runs an extra pass that opens a sheet on every row before moving to the next; it was verified to fail on the previous build and pass on this one.
- Also in this build (documentation, no behaviour change). A full 39-row clinical-surface consistency audit (findings need clinical sign-off before any dataset wording changes), a formal navigation architecture map, a per-bucket renderer contract, and a pre-pilot remaining-risks register.
v4.65
UI
A slim breadcrumb now shows the path you're inside on every drug and dose page — Pain › Fentanyl › NAS — tinted mint for Indications-flow and neutral for Drugs-flow, so where you are and how you got there is always visible at a glance
- Context bar under the patient strip. Root (your entry list) › drug › indication › route. Every segment is tappable and lands on a surface that already exists: root → the list you came from, drug or indication → the drug page picker, route → the route sheet. Separators aren't tappable; hit zones are glove-sized despite the slim bar.
- Mode identity (IND / DRUG tag + tint). Indications-flow pages get the mint left bar and IND tag; Drugs-flow pages stay neutral with a DRUG tag — the two modes no longer look identical.
- Route changes are loud. Switching route on a dose page pulses the bar's route segment once (~1.6 s) and shows a small amber CHANGED tag until you navigate — an in-place route switch can no longer go unnoticed.
- The bar shows the question, not just the state. On pickers the leaf reads "Choose route…" / "Choose indication…"; short paths are kept short — when the category already implies the indication (Pain › Fentanyl › NAS) the redundant middle segment is dropped.
- Derived, never stored. The bar is computed fresh from the current lens mode, indication, drug, dose row and picker state on every render — it keeps no navigation strings of its own, so it can never go stale (the bug family v4.49–v4.64 eliminated). Vertical cost is capped at 26 px; every page that fit without scrolling before still fits.
v4.64
SAFETY
Navigation history is now patient-aware — swiping back to a dose page after changing the patient's age or weight re-finds the exact same protocol row, and if that row no longer applies to the new patient it returns you to the drug page instead of guessing
- Back-swipe restores the same clinical row, not the same position. History snapshots previously remembered dose pages by list position; dose lists re-band when age/weight changes, so a back-swipe after a patient change could in principle land on a neighbouring line. Snapshots now carry the dataset row identity and restore by it.
- Fail-safe when the row no longer applies. If the remembered row re-banded away (e.g. the patient crossed a weight threshold while you were on Vitals), back lands on the drug page so you re-select against the current patient — never a silently substituted dose line.
- One write path for the indication filter. The active-indication state previously had 18 separate write sites in three formats; all now go through a single validating setter that rejects malformed values. History restores validate through it too.
- Low/High selection double-locked. The dose-level selector's stored choice is now also verified against the row it was made on (drug + route + indication), so a future refactor can't carry a "High" across drugs.
- Guarded. New release-blocking nav-restore matrix (test_nav_restore): 7 drug scenarios × back + forward swipes across age/weight band crossings, asserting same-row restore or drug-page fail-safe. Expected outcomes are computed from the dataset at runtime, so the guard survives protocol updates. No dose mathematics changed.
v4.63
SAFETY
The app now tells you when a newer build exists — a quiet check on every launch shows an "Update available · tap to update" banner above the bottom nav, and the running version is visible on the Home screen, so a stale clinical build can no longer hide silently
- Boot-time version check. ~2.5 s after launch (online only, never delays startup, silent on any failure) the app compares its own version against the live one. If they differ, a mint banner appears above the bottom nav: "Update available · vX.YY — tap to update". Tapping runs the exact same one-tap update path as the More panel (cache primed before reload — no double-tap). ✕ dismisses for this session only; a stale build re-banners on every launch until updated, deliberately.
- Running version on Home. Small muted version label under the Home title — "which build am I on" no longer requires opening More/About.
- Why. The 2026-06-07 hardening audit identified silent stale builds as the highest remaining operational risk: the offline-first cache serves instantly (by design), but nothing told you a newer clinical build existed unless you manually checked. Elevated from recommendation to requirement.
- Guarded. A new release-blocking build test (test_boot_version_check) locks the contract: non-blocking check, single shared update path, session-only dismissal, visible Home version. The deployment cache policy is now documented as a safety requirement in the README.
v4.62
UI
Three fixes from a systematic safety audit: single-dose drugs no longer caption their ceiling "↻ REPEAT" next to a SINGLE DOSE ONLY banner; an edge-swipe with a sheet open now dismisses the sheet instead of navigating underneath it; the route sheet closes on Escape like every other sheet
- Single-dose ceiling strip no longer says "REPEAT". Ceftriaxone and Dexamethasone pages carried a "↻ REPEAT · Total max 1 g" strip directly above the "SINGLE DOSE ONLY — DO NOT REPEAT" banner — two surfaces contradicting each other. Rows with a total max but no repeat now show a calm unlabelled centred "Total max 1 g" strip; rows with a real repeat count are unchanged.
- Edge-swipe can no longer navigate under an open sheet. Swiping back with a preparation, calculation, dosing-plan or route sheet open used to change the page beneath while the sheet stayed up — the sheet's recipe could end up floating over a different drug's page. The first swipe now dismisses the sheet (same close path as ✕/backdrop/Escape); the next swipe navigates.
- Route sheet closes on Escape. It was the only slide-up sheet without the Escape handler the others share.
- Audited, not just fixed. Full renderer-bucket sweep (all 72 active rows): repeat/max invariants hold everywhere, no clipped or overflowing UI, no double prep surfaces, weight-banded rows (Ceftriaxone IV, Glucagon) select correctly each side of their thresholds, structured rounding verified (Ceftriaxone weight-rounding, Dexamethasone volume-rounding), 10-drug clinical spot check matched the dataset 10/10, extended stale-state matrix (8 cross-surface transitions) clean. No dose mathematics changed.
v4.61
UI
The Indications lens is now the production default for everyone — "Pharma" list, indication-first flow, no route pills — with the More toggle kept as an opt-out; only users who explicitly turned the beta off stay on the legacy flow
- Lens on by default. The Indications lens — the "Pharma" section name, the indication-first drug flow (drug → indication → dose, route chosen after the indication), the indication picker and the consolidated dose-first surfaces — previously required turning on a beta toggle. It is now the default experience on every device, including fresh installs.
- Opt-out preserved. The More-menu toggle stays (now "Indications lens", no beta tag) as an explicit kill switch. Anyone who had turned the beta off stays off; flipping the toggle still switches between the lens and the legacy route-pill flow at any time.
- Presentation/navigation only. No dataset, dose, regime or governance change — the same gate, flipped from opt-in to opt-out. Verified in a headless browser: fresh profile (no stored setting) gets the lens; stored "off" gets the legacy flow; the toggle round-trips both ways; full regime, stale-state matrix and repeat-no-max guard green.
v4.60
SAFETY
The Adrenaline cardiac-arrest ampoule check moved above the dose card as a split green/red strip — "✓ 1:10,000" beside "✕ NOT 1:1,000" — so the grab-the-right-amp decision is one glance, before the dose, not a banner below it
- Split ampoule confirm strip (high emphasis). On dose rows flagged for ampoule-confusion risk, a two-cell strip now renders directly above the dose card: green ✓ with the correct preparation (ratio + concentration, derived from the row's prep data) beside red ✕ with the clinician-authored look-alike to avoid, verbatim. Mirrors the two look-alike ampoules sitting next to each other in the pack — green = grab, red = leave. Replaces the taller green banner that sat below, with the dose now immediately under the check.
- Graded, clinician-authored emphasis model. The old TRUE/FALSE flag becomes a graded per-row setting in the clinical source sheet: normal (quiet presentation line, unchanged — every drug today except the arrest rows), check (a mint "Confirm ampoule" presentation row, for future mild-ambiguity rows — none authored yet), high (the split strip). Currently flagged high: the four Adrenaline cardiac-arrest IV rows — exactly the rows that showed the old banner. No other row's page changes.
- Fail-safe parsing. Legacy or unrecognised truthy values in the source column map to high, never silently down to normal — an authored safety pause cannot be lost to a typo.
- Data + presentation only. No dose maths or cap changes; the strip derives from the same structured prep fields the old banner used. Verified headless on the arrest rows (strip above card, no double banner), sibling Adrenaline rows (unchanged), and A→B stale-state transitions; full regime green.
v4.59
UI
Every injectable now shows the big stacked dose with the mint DRAW UP volume slab — Ceftriaxone, Cefazolin, Droperidol and the other capped/single-dose injectables no longer fall back to the small one-line dose; "Total max" no longer prints its label twice
- Stacked dose + DRAW UP slab is now the universal injectable layout. It was previously limited to the Midazolam/Fentanyl pilots and repeat-no-max injectables (Adrenaline) — every other injectable (Ceftriaxone IM "1 g in 3 mL", Cefazolin, Glucagon, Hydrocortisone, Ondansetron, Morphine, Naloxone…) silently fell through to the old inline one-line dose with the volume as plain text. Same fall-through class as the v4.56 repeat-no-max bug. All injectables with a draw-up volume now render the centred stacked number with the mint DRAW UP slab.
- Range-dose (Low/High) injectables included. The selector cards (Droperidol IM, Morphine IM) showed inline "in 0.5 mL" unless the drug happened to be a pilot; they now carry the same stacked layout + slab.
- "Total max" label de-duplicated. The repeat strip on Ceftriaxone read "Total max Total maximum dose 1 g" — the source cap text already carried its own label. It now reads "Total max 1 g".
- Presentation only. No dose maths, dataset or cap-value changes. Verified headless across all four layout buckets (capped injectables, repeat-no-max, pilots, non-injectables, fluids) plus A→B stale-state transitions; full regime green.
v4.58
UI
Contraindications & precautions now show the same way on every drug — one clear tappable line ("● 1 contraindication · ● 4 precautions ›") near the top, replacing the faint bar and the bottom two-tile layout that made some drugs scroll
- One consistent contraindications/precautions bar. The same data was previously shown three different ways — a faint near-invisible bar on most drugs, and two separate tiles at the bottom of the page on Midazolam / Fentanyl IM. It's now a single, clearly tappable line on every drug: a red-dot contraindication count · an amber-dot precaution count · a mint chevron, sitting just below the presentation. Tap it for the full contraindications & precautions sheet (unchanged).
- Fits one screen. Moving it off the bottom reclaims vertical space — the bottom two-tile layout had pushed Fentanyl IM past one iPhone screen. Counts are worded ("1 contraindication", "4 precautions") with explicit "No contraindications" when there are none.
- Presentation only. No dose maths, dataset or contraindication-content changes — same data, same sheet. Verified in a headless browser across drugs (including the former two-tile drugs); full regime, stale-state matrix and repeat-no-max guard green.
v4.57
UI
Every dose card now uses the same centred, stacked layout — inhaled and oral drugs (Salbutamol, Glucose gel, oral liquids) match the injectables instead of sitting in a wide left-weighted line
- Consistent centred dose card across the app. Non-injectable drugs (Salbutamol MDI/NEB, oral liquids, gel) previously used an inline one-line dose that, for long labels like "6 inhalations (600 microg)", nearly filled the card and read as left-aligned. They now use the same centred stacked layout as the injectables — the number on its own line with the unit and any volume below it.
- No DRAW UP slab on non-syringe routes. The mint "Draw up" volume slab stays injectable-only; inhaled / oral cards show their volume (where there is one) as a plain centred line, never as a syringe draw-up.
- Presentation only. No dose maths, dataset or contraindication changes. Verified in a headless browser across every non-injectable drug; injectables unchanged; full regime, stale-state matrix and repeat-no-max guard green.
v4.56
SAFETY
Fixed: Adrenaline IM (and every "repeat · no max" drug) lost its "Repeat every 5 min" line and "No max dose" signal under the new dose card — both are restored, the dose is centred with a DRAW UP slab, and a permanent guard stops the whole bucket dropping again
- Fixed: a whole class of drugs silently lost two safety signals. When the new dose card became the default, it re-rendered its own dose / repeat / max surfaces — but it only handled capped repeats. Drugs that are fixed dose · repeat · no max (Adrenaline IM anaphylaxis & severe bronchospasm, Adrenaline IV arrest, Salbutamol MDI/NEB) fell through entirely, so their "Repeat every 5 min" line and their "No max dose" signal both disappeared. Both are now restored on one calm line: ↻ Repeat every 5 min · No max dose.
- This is now a first-class layout, not a fall-through. Repeat-no-max injectables get the centred stacked dose with the mint DRAW UP volume slab (e.g. 150 microg → DRAW UP 0.15 mL) — no chip strip, no dosing plan. The presentation line is concise and keeps the ampoule strength: 1:1,000 (1 mg/mL).
- New permanent guard. A release-blocking test now drives every repeat-no-max row in a headless browser and fails the build if any one of them is missing its repeat line or its no-max signal — so this bucket can never silently drop again.
- Presentation only. No dose maths, dataset or contraindication changes — every value is the same number the calculator already produced. The full regime, the stale-state matrix and the new bucket test are green.
v4.55
SAFETY
A new release-blocking test checks every drug page renders the same regardless of what you viewed before it — and it caught two real carry-overs (a stale "in 0.72 mL" and a stale "DOSE 2" card) now fixed
- Fixed: a stale draw-up volume could appear on a no-syringe drug. After viewing a drug with a draw-up volume (e.g. Midazolam "0.72 mL"), an oral / inhaled drug with no syringe volume (Glucose gel "15 g", Salbutamol puffs) could show a leftover "in 0.72 mL". The volume field is now cleared on every render, not just hidden.
- Fixed: a previous drug's "DOSE 2" card could linger. A repeat-once drug (e.g. Fentanyl NAS) draws a two-step "DOSE 2 · after N min" card; viewing a Low/High range drug next (e.g. Fentanyl IM) could keep that second card on screen, because the range layout skipped the teardown. The two-step card is now always cleared up front.
- New permanent guard. A release-blocking test now drives the app in a headless browser and asserts the renderer invariant — every page renders identically regardless of what was viewed before it — across every shared surface (regime, consult, alarm, dose card, prep, range selector). This locks in the whole "no stale state between drug pages" series (v4.52–v4.55) so the class can't come back.
- Presentation only. No dose maths, dataset or contraindication changes. The full regime — including the new matrix test — is green.
v4.54
UI
Full-app bug sweep — no visible defects found; two latent (hidden) stale-DOM carry-overs cleared defensively so they can never surface
- Whole-app sweep. Every drug × indication × route was rendered in a headless browser (no console errors, no NaN/undefined/empty doses), every consult / contra / reference / prep / range / repeat state was exercised, and a drug-to-drug "stale-state" matrix checked that each page renders identically regardless of what was viewed before it. No user-visible defects were found.
- Two latent carry-overs cleared. The sweep surfaced two pieces of hidden residue — a previous drug's dose card left in the (hidden) pilot block on a contraindicated row, and a previous "CONSULT REQUIRED" title left in the (hidden) alarm. Neither was visible on screen, but both are now cleared on every render so a future style change can never expose them. This completes the "no stale state between drug pages" work started with the rule-pill (v4.52) and regime-plan (v4.53) fixes.
- Presentation only. No dose maths, dataset or contraindication changes. Verified in a headless browser; the regime is green.
v4.53
SAFETY
Sodium chloride now shows its ceiling — "↻ Repeat ×2 · Total max 60 mL/kg" — and a stale repeat regime no longer carries between drug pages (a chip strip could linger from the previous drug)
- Sodium chloride shows its repeat + ceiling. The fluid bolus page now carries a protocol-summary line — ↻ Repeat ×2 · Total max 60 mL/kg (1,080 mL at 18 kg) — so the maximum and the "may repeat twice" guidance aren't dropped. It's a single calm line: no capped chip strip, no "draw up", no syringe-style volume language (the dose is a bag volume, not a syringe draw-up).
- Fixed: a previous drug's repeat regime could linger. The shared regime plan the pilot card reads was only ever set, never reset — so navigating a multi-dose drug (e.g. Midazolam, with its chip strip + "Total max 10 mg") then a no-regime drug (e.g. Adrenaline IM) could show the PRIOR drug's regime + total max. The plan is now reset on every render, so a stale regime can never leak between pages. A new build guard locks this in.
- Presentation only. No dose maths or dataset changes. Verified in a headless browser: fresh Sodium chloride shows the repeat/max line; Midazolam → Adrenaline IM no longer inherits the chip strip; capped drugs keep their chip strip; the regime-state-leak regression test passes; no console errors.
v4.52
SAFETY
The consult warning now always sits above the dose, and a stale per-kg rule no longer carries over between drugs (Sodium chloride was showing Droperidol's "0.1–0.2 mg/kg")
- Consult banner above the dose. On a consult-required drug that still has a calculable dose (e.g. Droperidol, Sodium chloride), the amber "Consultation required" banner + Call consult button now render ABOVE the dose card, so the consult requirement is read before the number. (Pure consult rows and the production layout already ordered it this way.)
- Fixed: a previous drug's per-kg rule could linger. The dose-rule pill (e.g. "10 microg/kg") was hidden but not cleared when moving to a drug with no per-kg rule, so Sodium chloride (a mL/kg fluid with no per-kg pill) displayed the PRIOR drug's rule — Droperidol's "0.1–0.2 mg/kg" — after navigating between them. The pill text is now reset on every render, so a stale rule can never carry over.
- Presentation only. No dose maths, dataset or contraindication changes. Verified in a headless browser: consult banner sits above the dose for Droperidol and Sodium chloride; the rule pill resets between drugs (Droperidol → Sodium chloride shows "IV INF", not the stale rule); absolute contraindications still supersede; no console errors.
v4.51
SAFETY
Consult warning + Call consult button now fire on every consult-required drug — including Sodium chloride, which was silently missing its consult warning because of a wording mismatch
- Fixed: a consult-required drug could show no consult warning. Sodium chloride 0.9% (fluid bolus, 10–20 mL/kg) carries the scope note "QAS clinical consultation and approval required in all situations" — but the old detector only matched the exact words "consult required", so it slipped through and rendered a dose with no consult banner. The detector now recognises every QAS consult phrasing (consult required, consultation required, consultation and approval required, …).
- One detector everywhere. A single shared consult test (
_isConsultScope) now drives both the drug-list "Consult required" tag and the dose-page consult banner, so the two can never diverge again. The duplicated regexes are gone.
- Call consult on every consult surface. Both the pure consult panel (no dose) and the consult banner that sits alongside a calculable dose (e.g. Droperidol, Sodium chloride) now carry the mint Call consult · 1300 315 280 button — dialled on a phone, read from
QAS_DATASET.consult. Consult is treated as an overlay layer, not a per-drug renderer.
- Safety priority preserved. An absolute contraindication still supersedes the consult UI — a contraindicated row (e.g. Droperidol < 8 yr) shows the red do-not-administer overlay and the consult banner/Call button are hidden. Verified in a headless browser across Sodium chloride, Droperidol (10 yr consult + 5 yr contraindicated), and Midazolam ABD; new regression tests (T22) cover the saline phrasing and non-consult notes.
v4.50
UI
Call consult — every "Consultation required" screen now has a one-tap Call consult button (1300 315 280) that dials straight from the phone; the number lives once in the dataset
- One-tap consult call. On any consult-required drug page (e.g. Midazolam acute behavioural disturbance), the amber "Consultation required" panel now carries a mint Call consult button that dials the QAS consult line directly on a phone (a no-op on desktop). Wording stays neutral — the consultation line is never named on screen.
- Number lives in the dataset. The phone number is authored once in the build (
QAS_DATASET.consult) and read by every consult surface — never hardcoded in the renderer — so if it ever changes it's a one-line update.
- Presentation only. No dose maths, regime, contraindication or dataset-row changes. Verified in a headless browser: the consult panel renders the Call button with the correct
tel: link and neutral wording, absolute contraindications stay loud, no console errors.
v4.49
UI
Select route picker (beta) + clinical-literal guard — single-indication, multi-route drugs (e.g. Fentanyl) now ask you to pick the route before any dose; prep recipes moved into the dataset and a release-blocking test keeps clinical values out of the renderer
- Select route, never a silent default. In the beta, a drug with one indication but several routes (Fentanyl: NAS / IV / IM) now opens a full-screen "Select route" card picker — same card style as Select indication, each card showing the route and its presentation from the dataset — before any dose is shown. Choosing an indication that itself has more than one route does the same. One indication + one route still goes straight to the dose. Route is always chosen after the indication, on the list it's never pre-picked for you.
- Toggle + header tidy. The Indications / Pharma toggle now reads Indications / Drugs (the bottom-nav tab is unchanged); the mint band top label shows the full pharmacological name (e.g. "Adrenaline (epinephrine)").
- Prep recipes now come from the dataset. The drug-preparation recipe tiles (source + diluent → result, plus any "then" step) were hardcoded in the renderer in two places (PREP_STEPS and PREP_RECIPES). They're now authored once in the source spreadsheet and carried into
QAS_DATASET.preps[].tiles; both prep surfaces read from there. Displayed values are unchanged — this removes a duplicate copy, not a number.
- New release-blocking guard. A build test (
test_clinical_literals.py) now scans the renderer for hardcoded clinical values (a number bound to a clinical unit, e.g. "10 microg/mL", "200 microg/kg") and fails the release if any appear outside the dataset pipeline — enforcing "PDF → spreadsheet → dataset → renderer, never PDF → UI code".
- Verification. Dataset/build change (prep tiles) plus UI. Same dose maths and prep displays as before; the full 17-step safety regime is green and the prep sheet + dose-card prep flow were re-checked in a headless browser (Fentanyl dilution, Ceftriaxone reconstitution + then-note, Glucagon) with no console errors.
v4.48
UI
Pharma flow stage 3 (beta) — the drug lists now read "Adrenaline" instead of "Adrenaline (epinephrine)", matching the picker and dose pages; the consolidated flow is feature-complete behind the beta
- Consistent drug naming. The Pharma and Indications drug lists dropped the "(epinephrine)" parenthetical so the name matches the picker band and the dose page (the dose/drug pages already stripped it per the ACP2 convention). Beta-gated — with the lens off, the list keeps the full "Adrenaline (epinephrine)".
- Flow feature-complete behind the beta. Stages 1–3 are in: route pills off the list + "Pharma" label (1), single-indication → dose and multi-indication → the mint-header "Select indication" card picker (2), and this naming tidy (3). The legacy drug overview is fully bypassed in the beta (it remains only as the beta-off fallback). Ready to promote out of beta when validated on-device.
- Presentation only. No dataset, dose, regime or governance change. Verified in a headless browser: full beta flow (Indications and Pharma modes, single/multi-indication, back navigation) is green; beta-off unchanged; no console errors.
v4.47
UI
Patient bar no longer jumps or overlaps the mint band — it sits flush under the header and stays put while the list scrolls; the Pharma picker band now matches the dose page height exactly
- Patient bar stays put. The strip's sticky offset was sized for the old ~52px header, so against the 58–60px mint band it sat under the band and jumped up ~8px when the Pharma / Indications list scrolled. It now pins flush to the band's bottom edge and holds still while content scrolls beneath it.
- Picker band = dose band. The Select-indication header was 68px; it's now 58px, exactly matching the Drug-dose mint band, so moving picker → dose has no header jump.
- Beta-safe. The v-drug 58px pin is scoped to the lens beta; with the beta off the drug overview keeps its forest header and original offset. Presentation only — no dataset, dose, regime or governance change. Verified in a headless browser: picker band 58, strip flush on the picker, and zero strip movement on the Pharma and Indications lists while scrolling; resus / vitals strips unaffected; no console errors.
v4.46
UI
Select-indication picker now wears the mint band header (beta) — back · drug name centred — matching the dose / resus / vitals surfaces, with the redundant hero box removed
- Mint band header on the picker. The Pharma-beta "Select indication" screen used the legacy forest header with a separate drug-name box. It now uses the shared bright mint band — back left, the drug name centred — exactly like the dose, resus and vitals pages, and the separate hero box is dropped (the name lives in the band).
- Beta-gated. Scoped to the lens beta + the drug view; with the beta off, the drug overview keeps its forest header and hero unchanged.
- Presentation only. No dataset, dose, regime or governance change. Verified in a headless browser: beta-on picker header is the mint band with the drug name centred and the hero hidden; beta-off header stays forest with the hero shown; no console errors.
v4.45
UI
Select-indication picker now matches the mockup (beta) — a note plus one card per indication, each with its concentration and route tag, instead of the old plain pills
- The picker is now the designed card screen. v4.44 reused the overview's plain indication pills; this renders the approved layout — a "Select indication" note, then a card per indication showing the indication, its concentration (e.g. 1 mg in 10 mL · 100 microg/mL) and a route tag (IM / IV / NEB). Tapping a card opens that indication's dose; Back returns to the picker.
- Same behaviour, better surface. Single-indication drugs still go straight to the dose; multi-indication drugs show this picker. All beta-gated — with the lens off, the full legacy overview and route pills are unchanged.
- Presentation only. No dataset, dose, regime or governance change. Verified in a headless browser: Adrenaline shows four cards (IM/IV/NEB/IM) → Cardiac arrest card → dose (CARDIAC ARREST) → Back → picker; single-indication Fentanyl → dose direct; beta-off legacy overview intact; no console errors.
v4.44
UI
Pharma flow — stage 2 (beta): the redundant drug overview becomes a lean "Select indication" picker, and single-indication drugs go straight to the dose
- Single-indication drugs skip the overview. In the Pharma drug-first browse (beta), tapping a drug that has only one indication now opens its dose directly — the route is shown and switchable on the dose page. Back returns to the Pharma list.
- Multi-indication drugs get a clean picker. A drug with several indications (e.g. Glucagon, Adrenaline) now shows a streamlined "Select indication" screen — just the drug name and the indication choices. The contraindications, precautions and administration-route sections are hidden here because they're all on the dose page (contra/precautions chip + the do-not-administer overlay). Tapping an indication goes straight to its dose; Back returns to the picker.
- Beta-gated, presentation only. With the lens beta off, the full drug overview and route pills behave exactly as before. No dataset, dose, regime or governance change. Verified in a headless browser: single-indication → dose (Back → list); multi-indication → picker with the redundant sections hidden → indication → dose (Back → picker); beta-off unchanged; no console errors.
v4.43
UI
Pharma flow stage-1 fix (beta) — route pills now stay in the Indications drilldown (where the indication is known) and are only dropped in the drug-first Pharma browse
- Fix. v4.42 dropped the route pills whenever the beta was on — including after you'd drilled into an indication (e.g. Indications → Anaphylaxis), which added an unnecessary drug-page hop before the dose. The pills are now dropped only in the drug-first Pharma browse (no indication active); once an indication is chosen, the pills stay and tap straight to the dose, indication preset.
- Verified. Headless browser, beta on: Indications → Anaphylaxis → Adrenaline IM lands on the dose with ANAPHYLAXIS preset; the Pharma drug-first list still shows no route pills. Beta off is unchanged. No console errors.
- Presentation only. No dataset, dose, regime or governance change.
v4.42
UI
Pharma flow — stage 1 (beta): the drug list drops its route pills and the section is renamed "Pharma" when the Indications lens beta is on; nothing changes with the beta off
- Route pills removed from the list (beta). With the Indications lens beta on, drug rows no longer show inline route pills — tapping a row goes to the drug page, where the route is chosen after the indication. This removes the "pick a route before the indication is known" inversion and the silent indication-default that came with it. With the beta off, the route pills behave exactly as before.
- Renamed to "Pharma" (beta). The bottom-nav tab, the list header and the lens toggle read "Pharma" instead of "Drugs" when the beta is on; they revert to "Drugs" when it's off.
- Staged behind one flag. This is the first of three stages toward the consolidated dose-first flow, all gated behind the existing
qas_lens_pilot beta. Presentation/navigation only — no dataset, dose, regime or governance change. Verified in a headless browser: beta-off keeps route pills and "Drugs"; beta-on shows "Pharma", no route pills, and rows route to the drug page.
v4.41
UI
Consistent back labels — every drill-down back button now reads "Back" instead of the mismatched "Drugs" / "Drug" / "Back" mix
- One back label. The Drug overview back said "Drugs", the Dose page said "Drug", and the More sub-pages said "Back" — three conventions for the same control. They now all read ‹ Back. The dose page already shows the drug name as its centred title, so a named back would only have duplicated it.
- Behaviour unchanged. Each back still steps up one level: Dose → Drug overview → Drugs list, and each More sub-page → More. Only the label text changed.
- Presentation only. No dataset, dose, regime or governance change. Verified in a headless browser: both drug-flow back buttons now read "Back" and still navigate to the correct parent screen.
v4.40
UI
Navigation tidy-up — the Drugs list, Resus and Vitals tab screens no longer show a header back button (they're primary tabs), which also fixes Vitals "Back" landing on the wrong screen
- Tab roots have no back button. Drugs list, Resus and Vitals are primary bottom-nav destinations, so their header back controls ("Home" / "Home" / "Back") are removed — they only duplicated the Home tab. Back buttons remain on genuine drill-downs (Drug overview, Dose page, and the More sub-pages).
- Fixes the Vitals "Back" bug. The Vitals tab never recorded where you came from, so its "Back" jumped to a stale screen (e.g. from Home or Resus it landed on the Drugs list). With the button gone, navigation between sections is the bottom nav — consistent and predictable.
- Layout held. The centred title stays centred (grid columns pinned so removing the back slot can't shift it) and the mint band keeps its shared height. Presentation only — no dataset, dose, regime or governance change. Verified in a headless browser across all five tabs plus the drill-downs (back buttons present only where expected, titles centred, no console errors).
v4.39
UI
Resus swipe rebuilt as a proper slider — the deck no longer uses native scroll-snap at all, so it physically can't get stuck between two pages; every swipe or tap resolves to exactly one page
- New approach — transform slider. The four resus pages now live on a track that is moved with a CSS
translateX, inside a clipping viewport. There is no native horizontal scroll or scroll-snap anywhere, so the iOS bug that parked the deck halfway between two pages is gone by construction — the track is always at an exact page offset.
- Swipe gesture. A finger drag is axis-locked: a mostly-horizontal drag moves the track and commits to the next/previous page on release (by distance or a quick flick), or springs back; a mostly-vertical drag is left to the page's own scroll. The first and last pages resist over-drag. Chip taps animate to the page. Vertical scrolling of tall pages is unchanged.
- Presentation only. No dataset, dose, regime or governance change. Verified in a headless browser: chip taps and simulated drags land on exactly the right page (transform at precise offsets), end-clamping and small-drag spring-back behave, body scroll still reveals tall content, and there are no console errors.
v4.38
UI
Resus swipe reverted to native snapping — the v4.37 free-scroll experiment felt loose and janky, so native CSS snap is restored and chip taps now jump instantly so they can't collide with it
- Reverted the v4.37 approach. Removing CSS mandatory snap and snapping in JS on scroll-rest made the swipe feel worse (loose drag, a lagging catch-up snap). That change is backed out.
- Native snap restored. The deck is back to
scroll-snap-type: x mandatory with scroll-snap-stop: always for the manual swipe. The only remaining custom behaviour is that a chip tap now sets the scroll position instantly rather than animating — which sidesteps the one reproducible iOS bug (a programmatic smooth scroll colliding with mandatory snap).
- Presentation only. No dataset, dose, regime or governance change. Verified in a headless browser: chip taps land exactly on each page, no console errors.
v4.37
UI
Resus swipe fixed properly — the deck now snaps to the nearest page in JS when a swipe comes to rest, so it can never stay parked between two pages on iOS
- Root cause. The v4.36 fix only covered chip taps; a manual swipe still relied on CSS
scroll-snap-type: x mandatory, which on iOS WebKit interrupts momentum scrolling and leaves the deck parked between two pages (e.g. Defib bleeding into CPR).
- New approach. CSS mandatory snap is removed entirely. The deck scrolls freely during the gesture; a debounced "snap-on-rest" handler then animates to the nearest page once scrolling stops — for both swipes and chip taps. A short animating-guard stops the snap from fighting an in-flight programmatic scroll.
- Presentation only. No dataset, dose, regime or governance change. Verified in a headless browser: chip taps land exactly on each page, and partial swipes resting mid-page (1.40 / 1.62 / 2.55 / 0.30 of a page) all snap to the correct nearest page with no console errors.
v4.36
UI
Mint headers made consistent app-wide + resus swipe fix — every mint band is now the same height with the same back-button style, and the resus deck no longer gets stuck between two pages
- One mint band, everywhere. The Drugs-list, Resus and Vitals mint headers now match the Drug-dose page exactly — same top inset and padding (so the band is the same height and the patient strip sits the same distance below it), and the same back control (transparent dark-ink text, weight 600, not a heavier/larger or pill-style button). The Drugs-list title is bumped to the shared 21 px size.
- Resus deck swipe fix. On iOS the resus deck could halt mid-swipe and show half of two pages at once (e.g. Defib bleeding into CPR), because
scroll-snap: mandatory interrupts a programmatic smooth scroll. The chip handler now disables snap during the animation, re-asserts the exact page offset when it settles, and restores snap for manual swipes; scroll-snap-stop: always stops a gesture parking between pages.
- Presentation only. No dataset, dose, regime or governance change. Verified in a headless browser — band metrics equal across the mint headers, back-button styles matched, and the deck lands exactly on each page on every chip tap.
v4.35
UI
Vitals redesigned — the reference-ranges page now wears the mint band header ("Vital Signs") and mint-accent dark-glass cards, matching the drug, dose and resus surfaces
- Vitals joins the family. The Vitals view gets the bright mint band header titled "Vital Signs" (back left, title centred), with the patient strip beneath — the same chrome as the drug pages and the resus deck. The in-body "Vital signs" heading and the overlapping version chip are removed from this view.
- Mint-accent reference cards. The Heart rate / Resp rate / Systolic BP tiles keep their dark-glass surface but now carry a uniform mint accent stripe and mint kicker (replacing the per-vital green/teal bars), with a "Normal range" sublabel under the big range. Critical low/high keep an amber/red warning tint so the danger signal stays clear.
- Presentation only. No reference values, dataset, calculator or governance change — the ranges still come from the same age-keyed table. Verified in a headless browser (mint band, mint cards, legible criticals, no console errors).
v4.34
UI
Resus answer cards flipped to dark glass — Defib, CPR and Airway cards now match the redesigned dose card and the mint-band header instead of standing out as white panels
- Defib / CPR / Airway cards now dark glass. The three reference pages still carried the v2.27 white answer cards, which clashed with the v4.32 resus mint-band header and dark surfaces. They now use the same mint-rim glass treatment as the redesigned dose card — light text, mint accents — so the whole resus deck reads as one surface.
- Clinical colour signal preserved. The warm/cool semantic accent (amber = energy, blue = AED, purple/blue = airway device, red = defib) still rides the card kicker and the numeric unit, now in brighter dark-theme variants so it reads on glass. No content, dose, or layout change — purely the card surface and text colours.
- Presentation only. No dataset, calculator, regime or governance change. Verified in a headless browser across the Defib, CPR and Airway pages (dark cards, legible text, accents intact, no console errors).
v4.33
UI
Adrenaline joins the redesigned dose card — the last drug onto the new mint design, so every drug in the app now shares one dose-card surface
- Adrenaline promoted — the final drug. Adrenaline was the one drug still on the older production dose page, held back pending its resus-surface parity pass. That shipped in v4.32 (the Resus Adrenaline page now uses the shared dose card), so Adrenaline now joins the pilot allow-list. Its drug-detail page gets the mint band header and the redesigned dose card, identical to every other drug.
- Every in-scope route, unchanged dose. The treatment is data-driven — it reads the production-computed dose, regime plan, drug_info and indication list — so all of Adrenaline's ACP2 indications render on the new card: Anaphylaxis (IM), Cardiac arrest (IV), Croup (nebulised) and severe bronchospasm. IO routes are out of ACP2 scope and already stripped. No dose mathematics, dataset, regime or governance change.
- Presentation only. A single name added to the dose-card allow-list — no drug-specific code. Verified in a headless browser across Adrenaline's indications (each new-card dose matches the production calculator) with no console errors.
v4.32
UI
Resus deck redesigned — every page now wears the mint band header (back · page title · route pill), and Adrenaline adopts the full drug-detail layout the other drugs use
- Mint band header across the resus deck. Defib, CPR, Airway and Adrenaline now lead with the same bright mint band the drug-detail pages use — back control left, the active page title centred, and a route pill (IV) shown on Adrenaline only. The title stays in step with the swipe deck and chip strip; the compact chip strip and patient strip sit below the band exactly as before.
- Adrenaline matches the other drugs. The resus Adrenaline page drops its bespoke hero / prep-warn and adopts the drug-detail layout: a Presentation row (100 microg/mL), a centred dose card carrying the dose-per-kg rule pill (180 microg in 1.8 mL for an 18 kg child), then the Repeat every 3–5 minutes and NO MAX DOSE banners. Every value is still read from the same dataset row (QAS_ACPII_006) the page already used — presentation-only, no new dose mathematics.
- Presentation only. No dataset, calculator, regime, or governance change. Defib / CPR / Airway card bodies are untouched. Verified in a headless browser across all four resus pages (band title + pill sync, Adrenaline 180 microg / 1.8 mL computed from the dataset, no console errors).
v4.31
UI
Fentanyl and Morphine fully on the new dose card — every route now uses the redesigned dose card, not just the IM range-dose rows
- Remaining routes joined the new card. Fentanyl and Morphine previously reached the redesigned dose card only through the per-row range-dose rule, which covers their IM routes. Fentanyl NAS (weight-banded) + IV and Morphine IV (calculated + dilution) stayed on the older production dose page. Adding both drugs to the dose-card allow-list brings every route onto the new card — the weight-banded / calculated dose comes straight from the production calculator; the diluted IV routes get the v4.29 tappable dilution prep line.
- Presentation only. No dose mathematics, dataset, regime, or governance change — the new card is a presentation layer over the same calculator result. Verified by automated browser parity check on every Fentanyl and Morphine route (each new-card dose matches the production calculator exactly) plus prep-sheet click-through on the diluted IV routes.
- Card design is standard, not lens-gated. The redesigned dose card is the live surface for every allow-listed drug regardless of the Indications-lens (beta) toggle — Fentanyl and Morphine now join that set, consistent with the existing 15 drugs. Adrenaline stays on the production page for now (deferred pending its resus-surface / IO-route parity pass). The Indications-lens browse and route sheet remain the separate, off-by-default beta.
v4.29
UI
Fix (beta lens) — the prep line on the pilot dose page is tappable again for drugs that need dilution or reconstitution, reopening the slide-up recipe sheet
- Regression fixed. The v4.12+ pilot dose design hid the production prep strip (
#ddPrepStrip) — the element that carried the Dilution/Reconstitution tap target — and replaced it with a non-interactive "Presentation" line. So on the beta lens, drugs needing preparation (Midazolam IV, Morphine IV, Fentanyl IV, Ceftriaxone IV) had no way to open the recipe sheet. The pilot prep line now classifies the prep with the same presentationLabelForPrep() helper as production and becomes a quiet tap target when it's a Dilution or Reconstitution.
- Quiet by design. No loud mint "DILUTION REQUIRED" slab — just a mint overline (Dilution / Reconstitution) plus a small italic "i", in keeping with the pilot's calm card. Tapping opens the existing v3.62 slide-up recipe sheet (recipe tiles + then-note + caution) via the unchanged document-level handler. Presentation-only rows stay calm and non-tappable as before.
- Presentation only. No dose mathematics, dataset, regime, or governance change — the prep sheet content and the calculator are untouched. Verified by automated browser click-through (Midazolam IV dilution, Ceftriaxone IV reconstitution).
v4.28
SAFETY
Context-preserving route sheet (beta) — the dose-page route chip opens a two-section sheet, and crossing to a different protocol context shows a collapsible off-context warning instead of silently switching
- Two-section route sheet. On the dose page (beta lens enabled) the route chip opens a slide-up sheet: "Routes for <this indication>" first — switching among them stays in the same clinical context, no warning — then "Other protocol contexts", each tagged DOSE or CONSULT. Out-of-scope contexts are shown and flagged, not hidden.
- Off-context, never silent. Entering through one indication (e.g. Anaphylaxis → Adrenaline → IM, or Seizure → Midazolam) records that context. Switching to a different protocol context shows an amber banner — "Now showing X, a different context to <your entry>" — with a one-tap "‹ Back to <entry>". The banner is collapsible to a slim marker to save space, but is never fully disabled or removed while off-context. No route is ever silently chosen across contexts.
- Beta, off by default; built on verified pieces. Gated behind the same More → Indications lens (beta) flag; with it off, the dose page behaves exactly as before (the original route popover). The sheet reuses the shipped v3.99 indication-switch + v3.32 route-line logic; no dose mathematics changed. Verified by automated browser click-through (Midazolam, Ceftriaxone).
v4.27
UI
Fix — the "Indications lens (beta)" toggle in More is now clickable (its handler was attached before the More menu existed in the DOM)
- More toggle now works. v4.26's pilot toggle in More wouldn't respond — its click handler ran during page parse, before the More-menu markup existed, so it bound to nothing. The wiring now runs on
DOMContentLoaded, so tapping More → Indications lens (beta) flips it On/Off and the Drugs tab updates immediately.
- No other change. The lens itself, the Drug → Route → Dose rule, and everything else are unchanged; still off by default.
v4.26
UI
Indications lens (beta, off by default) — browse drugs by presentation; enable it under More → "Indications lens"
- Symptom-first browsing. A pilot lens adds an Indications / Drugs toggle to the Drugs tab. In the Indications lens you pick a presentation (Anaphylaxis, Seizure, Pain / Fever…) and see exactly the drugs for it, each with its indication-specific route(s) — Adrenaline shows IM under Anaphylaxis, NEB under Croup. The same Drug → Route → Dose safety rule applies: one route collapses to a tap, multiple routes show "Choose route".
- Off by default — gated. The lens is a behind-a-flag pilot: live users see no change. Enable it under More → Indications lens (beta); the toggle then appears below the patient strip and the Drugs tab opens on Indications. Turn it off the same way.
- Under the hood. Indication categories come from the existing indication matchers; counts and the filtered drug list use the same path, so the count you see is the list you land on. The route step now reads the indication-filtered routes when a presentation is active. No dose mathematics changed.
v4.25
SAFETY
Drug list never silently picks a route — multi-route drugs now show "Choose route" and require an explicit route tap; only single-route drugs collapse to a row tap
- No silent route default. Until now, tapping a multi-route drug row (Fentanyl, Ceftriaxone, Morphine, Droperidol…) opened its first route's dose page — silently choosing IM vs IV vs NAS. Route is a clinical decision (access, onset, sedation depth, monitoring, scope), so the list no longer guesses: multi-route drugs show a quiet "Choose route" label and you must tap the route pill explicitly.
- Fast paths stay fast. Drugs with exactly one valid route still collapse the route step — tap the row or the single pill and you go straight to the dose. Multi-indication drugs keep the chevron + detail page.
- One consistent model. Navigation is now Drug → Route → Dose everywhere, with the Route step collapsing only when there's a single valid route. No dose mathematics changed.
v4.24
UI
Drugs list alignment actually fixed (the list container's own 16 px padding was the culprit), and the search placeholder is now just "Search drugs…"
- Real cause of the indent. v4.23 set the drug rows to a 14 px margin, but the rows live inside the
.list container which adds its own 16 px side padding — so the cards still sat ~30 px in while the patient strip, search field and section label sat at 14 px. The list container's side padding is now zeroed on this view, so every element shares the same 14 px left edge.
- Search placeholder. The field now reads simply "Search drugs…" instead of "Search drugs or indications…".
- List view only. Scoped to the Drugs list; mint header, route pills and the rest are unchanged. No clinical data or calculations changed.
v4.23
UI
Drugs list alignment — the drug rows, section label and search field now share the same left edge as the patient strip (14 px)
- Edges line up. The drug rows and the "All drugs · N" label were inset 18 px and the search field 16 px, while the patient strip sits at 14 px — so the rows looked pushed to the right. All three were set to the patient strip's 14 px side margin. (The rows still had the list container's padding on top; fixed in v4.24.)
- List view only. Scoped to the Drugs list; the mint header, route pills, square search field and "All drugs · N" label are otherwise unchanged. No clinical data or calculations changed.
v4.22
UI
Drugs list polish — square search field and a compact "All drugs · N" section label, finishing the match to the list mockup
- Square search field. The drug search bar changes from a full pill to a rounded-rectangle (12 px radius), matching the mockup.
- "All drugs · N" label. The big "Drugs available / Tap a drug to view full doses" heading is replaced by a compact uppercase section label showing the live count — All drugs · 19 — and "Drugs · N" when an indication filter is active.
- List view only. Scoped to the Drugs list; the mint header, route pills and search behaviour are otherwise unchanged. No clinical data or calculations changed.
v4.21
UI
Drugs list mint header now actually applies, and the 3-letter drug icon tiles are removed for cleaner rows
- Mint header fixed. v4.20's mint header didn't show because a v2.90 rule set the list header to forest with higher specificity. The mint band now wins (scoped via the
#menuView id), so the Drugs list shows the bright mint header with the centred Drugs title and dark-ink Home / Vitals controls.
- Icon tiles removed. The 3-letter drug abbreviation tiles (ADR, CEF, DEX …) on the left of each row are gone — the rows now read as drug name + route pills, cleaner and giving the names more room.
- List view only. Scoped to the Drugs list; the search bar and route pills are unchanged. No clinical data or calculations changed.
v4.20
UI
Drugs list gets the mint header band with a centred "Drugs" title and a visible search bar, matching the approved list mockup
- Mint header. The Drugs list now carries the same bright mint header band used elsewhere in the app, with a centred Drugs title and the Home / Vitals controls restyled as dark ink on the mint. The global version chip is hidden on this view to keep the band clean. (The header rule was overridden until the v4.21 fix.)
- Search restored. The drug search field (hidden since v2.80) is shown again, sitting below the patient strip, so you can filter the 19-drug list by name. The route pills from v4.18–v4.19 are unchanged.
- Scoped to the list. All of this is scoped to the Drugs list view; the drug-detail page and every other screen keep their existing headers. No clinical data or calculations changed.
v4.19
UI
Fix — the drug-list route pills now actually render (v4.18 read the routes from the indication-filtered list, which is empty on the list, so none showed)
- Route pills now appear. v4.18 added route pills to the drug list but built them from
getFilteredBlocks(), which returns nothing while no indication filter is active — which is the normal state on the list — so the rows looked unchanged. They now read the drug's full indication list (d.doses()), so each single-indication drug shows its routes (e.g. Fentanyl → NAS / IM / IV) and tapping one jumps straight to that route's dose page.
- Behaviour as designed. Tapping a route opens that route's dose page; tapping the rest of the row opens the default (first) route. The five multi-indication drugs (Adrenaline, Dexamethasone, Glucagon, Hydrocortisone, Midazolam) keep the chevron + detail page until the indication picker is built; drugs fall back to the chevron when no patient weight is set.
- Navigation only. Same dose engine, routes and data — nothing about the calculations changed.
v4.18
UI
Drug list — tap a route pill to jump straight to the dose page, skipping the per-drug detail page for single-indication drugs
- Route pills on the list. Each single-indication drug shows its routes as a joined pill segment on the right of its row (e.g. Fentanyl → NAS / IM / IV). Tapping a route goes straight to that route's dose page; tapping the rest of the row opens the default (first) route. This skips the intermediate detail page — its indications, contraindications and precautions already live on the dose page. (Note: the pills did not render until the v4.19 fix.)
- Multi-indication drugs unchanged for now. The five drugs whose routes map to more than one indication (Adrenaline, Dexamethasone, Glucagon, Hydrocortisone, Midazolam) keep the chevron and the existing detail page until a dedicated indication picker is designed. Drugs also fall back to the detail page when no patient weight is set.
- Navigation only. Same dose engine, same routes, same data — the pills reuse the existing dose-page navigation; nothing about the calculations changed.
v4.17
UI
Pilot design extends to Fentanyl IM — stacked centred dose, mint "Draw up" slab and bottom safety tiles, keeping the Low / High dose toggle; Midazolam keeps the same treatment
- Fentanyl IM joins the pilot. The Fentanyl IM (Significant pain) dose page now uses the same redesign as Midazolam: the stacked, centred dose number, the solid mint "Draw up" volume slab beneath it, and the two contraindications / precautions tiles pinned at the bottom of the page. The Low / High dose toggle is kept — switching between 1 microg/kg and 2 microg/kg updates the number, the draw-up slab and the repeat regime live.
- Scoped precisely. Only Fentanyl's IM route (its single range-dose row) is affected; Fentanyl IN and IV stay on the standard pilot card. Midazolam keeps the treatment too. Other drugs are unchanged.
- Presentation only. No dose mathematics, regime, contraindications or clinical data changed — the figures all still come from the existing calculator path.
v4.16
UI
Midazolam pilot — the contraindications / precautions tiles now render at the bottom of the page, below the dose card and repeat regime, instead of above the dose card
- Safety moved to the bottom. On the Midazolam dose page the contraindications / precautions tiles now sit at the foot of the page, below the dose card and repeat regime. Previously the safety row rendered directly below the presentation (above the dose card); it now follows all of the dosing content, where contraindications and precautions are expected.
- Reverts the v4.15 flex experiment. v4.15 tried to push the tiles down with a flex column, which mispositioned the dose card. That is undone — the tiles are simply built last in normal document flow.
- Piloted on Midazolam only. Scoped via the
.mz-stack class; every other pilot drug keeps the safety row below the presentation. Presentation only — no dose mathematics, regime or clinical data changed.
v4.15
UI
Midazolam pilot — the contraindications / precautions tiles are now anchored to the bottom of the screen, with the spare space sitting between the regime and the tiles
- Safety tiles pinned to the bottom (superseded by v4.16). The dose view became a flex column so the dosing block filled the height and the safety tiles sat at the foot of the screen. This mispositioned the dose card in some cases and was replaced in v4.16 by simply building the tiles last in normal flow.
v4.14
UI
Midazolam pilot — the bottom contraindications / precautions row becomes two side-by-side tiles, kept compact so the whole dose page still fits one normal iPhone screen
- Two-tile safety layout. At the bottom of the Midazolam dose page the single quiet "Contraindications & precautions" bar is replaced by two side-by-side tiles — Contraindications (count in soft red with a dot) and Precautions (count with a chevron). Either tile opens the same slide-up sheet with the full detail. Counts are read live from the dataset (Midazolam: 1 absolute · 4 precautions).
- Fits one screen. The tiles are kept compact so the page — presentation row, stacked dose card with the mint draw-up slab, repeat regime, and the safety tiles — still sits on a single normal-iPhone screen without scrolling.
- Piloted on Midazolam only. Scoped via the
.mz-stack class; every other pilot drug keeps the v4.05 single quiet bar until the tiles are approved for wider rollout. Presentation only — no dose mathematics, regime or clinical data changed.
v4.13
UI
Midazolam pilot — the draw-up volume moves onto a solid mint "Draw up" slab below the dose number, making the figure you actually draw the loudest thing on the card
- "Draw up" volume slab. On the Midazolam dose page the draw-up volume comes out of the inline "in 0.72 mL" clause and onto its own solid mint block beneath the dose number, labelled "Draw up". The high-contrast slab makes the volume — the figure you act on at the syringe — read first, with the milligram dose still stacked above and the rule line below.
- Piloted on Midazolam only. Scoped via the
.mz-stack class so every other pilot drug keeps the inline one-line dose for now. If the slab reads well in use it will be promoted to the rest of the drugs in a later release.
- Layout unchanged otherwise. The wide presentation bar above the card and the contraindications / precautions block pinned at the bottom of the page are as before. Presentation only — no dose mathematics, regime or clinical data changed.
v4.12
UI
Midazolam pilot — the dose card switches to a stacked, centred layout (big number over unit over volume), making the initial dose louder and easier to read at a glance
- Centred dose card. On the Midazolam dose page the initial-dose line moves from the v4.00 inline one-line format ("3.6 mg in 0.72 mL") to a stacked, centred layout: the dose number sits large on its own line, the unit beneath it, and the volume ("in 0.72 mL") in mint below that, with the rule line under. The bigger number and dedicated volume line make the answer read louder in the field.
- Piloted on Midazolam only. Scoped via a new
.mz-stack class so every other pilot drug keeps the inline one-line dose for now. If the stacked layout reads well in use it will be promoted to the rest of the drugs in a later release.
- Presentation only. No dose mathematics, regime, contraindications or clinical data changed — purely a layout change to the dose card on one drug.
v4.11
DATA
Data fixes for the last two reference drugs: Glucose 10% now carries its real paediatric infusion dose, and Oxygen shows proper SpO₂ targets instead of a misleading "Consult Required"
- Glucose 10% — real dose restored. The row previously held a "dosing table not extracted" placeholder and rendered as Consult Required. It now carries the actual QAS paediatric IV-infusion dose: 0.25 g/kg (2.5 mL/kg), repeated at 0.1 g/kg (1 mL/kg) every 5 min until BGL > 4.0 mmol/L. So a 5-year-old (18 kg) reads "4.5 g in 45 mL" with the titration repeat shown beneath. Source: DTP_GL10_0223.
- Oxygen — reference, not consult. Oxygen has no calculable dose — it's titrated to a clinical target — but it was being lumped in with phone-for-approval protocols and shown as an amber "Consult Required". It now renders a calm Target oxygen saturation card: 100% O₂ (arrest, CO/cyanide poisoning, pre-RSI); SpO₂ 88–92% (COPD, CF, obesity, neuromuscular disease, paraquat/bleomycin); SpO₂ 92–96% (all else). Source: DTP_OXYG_1024.
- New "reference" state. Under the hood, genuine reference rows are now classified separately from consults, so they no longer borrow the consult banner or route-chip tag. Real consults (Midazolam acute behavioural disturbance, Salbutamol IV infusion) are unchanged. With this, all 19 in-scope ACP2 drugs except Adrenaline are on the redesigned page; Adrenaline remains its own dedicated resus pass.
v4.10
UI
Salbutamol joins the redesign, including the MDI "12 inhalations (1.2 mg)" puff-count form — every calculable ACP2 drug except Adrenaline is now on the new page
- Salbutamol on the new page. Covers the nebulised mg doses (2.5 / 5 mg by age band, plus hyperkalaemia) and the MDI inhalation-count form, which renders cleanly as a hero "12" with "inhalations (1.2 mg)" — no special card needed. The IV-infusion form remains a consult/reference row.
- Held back on purpose. Oxygen and Glucose 10% are NOT promoted yet: they're reference rows with a data gap (Oxygen is mis-classified as "Consult Required"; Glucose 10% still shows a "dosing table not extracted" placeholder). Promoting them would only put a mint header on a broken string — they need a dataset fix first. Adrenaline also stays on its dedicated resus surface (and has IO routes outside ACP2 scope), so it gets its own pass.
- Coverage. 18 of 19 in-scope ACP2 drugs now use the redesigned page. The redesign rollout that began with the Midazolam pilot is effectively complete bar Adrenaline and the two data-blocked reference entries.
v4.09
UI
The fixed-dose / age-band family joins the redesign — Glucagon, Glucose gel, Methoxyflurane and Ipratropium — including descriptive doses like the buccal glucose-gel strip
- Fixed-dose / age-band family. Glucagon (IM/IV, 0.5/1 mg by weight band, both hypoglycaemia and refractory-anaphylaxis indications), Glucose gel (PO 15 g / buccal strip), Methoxyflurane (3 mL Penthrox, repeatable once) and Ipratropium (nebulised 250/500 microg by age band) now use the redesigned page.
- Descriptive doses read properly. Some doses aren't a bare number — the buccal glucose gel is "1 cm strip (approximately 0.5 mL) on gloved finger". The card now surfaces that full descriptor under the dose when a fixed-dose row carries more than a plain "<number> <unit>", so the hero number is never left cryptic. Normal mg/microg/mL doses are unaffected.
- Coverage. 17 of the in-scope ACP2 drugs are now on the new design. Remaining: the inhalation "puffs" count type (Salbutamol) and the reference-only entries (Oxygen, Glucose 10%). Adrenaline stays on its dedicated resus surface.
v4.08
UI
Four more drugs join the redesigned page — Ceftriaxone, Hydrocortisone, Ondansetron and Sodium chloride 0.9% — completing the weight-based family
- Weight-based family rolled out. Ceftriaxone (IM/IV, with reconstitution prep), Hydrocortisone (both the weight-based asthma dose and the age-band adrenal-crisis doses, with the indication switcher), Ondansetron (PO/IM/IV) and Sodium chloride 0.9% (fluid bolus) now use the redesigned page. The contra/precautions Q3 bar applies to all of them.
- Fluid + fixed-dose polish. For fluids whose dose is itself a volume (Sodium chloride "180–360 mL"), the card no longer appends a dangling "in <volume>". For age-band / fixed-dose rows that have no per-kg rule (e.g. Hydrocortisone adrenal crisis), the rule line now shows just the route instead of a placeholder dash.
- Coverage. 13 of the in-scope ACP2 drugs are now on the new design. Remaining families: pure fixed-dose / age-band drugs, the inhalation "puffs" count type, and the reference-only entries — each to follow in its own batch. Adrenaline stays on its dedicated resus surface.
v4.07
UI
Morphine IM and Droperidol IV/IM join the range-dose design, completing the variable-select drug family. The pilot now enables automatically for any Low/High range row
- Range-dose family complete. The Low/High range treatment built for Fentanyl IM now also covers Morphine IM (100–200 microg/kg) and Droperidol IV/IM (0.1–0.2 mg/kg). The redesign is enabled per-row for any
variable_select line, so these came on without hardcoding each drug+route; the other routes of those drugs (Fentanyl IN/IV, Morphine IV) stay on the production page.
- Repeat-once handled. Droperidol may be repeated only once, so its regime now shows exactly two doses with the second flagged FINAL (rather than walking on to the mg ceiling). Fentanyl/Morphine keep walking their multi-dose repeats to the total max. The "Full plan" caption reads "Single repeat only" vs "Repeat … every N min" accordingly.
- Units follow the dose card. Chip and plan amounts are now formatted in the same unit the dose card shows (Morphine's options are microg-based but display as mg), so every number on the page agrees. All values still come from the production calculator path — the overlay only sequences and presents (v3.75 rule). Contraindicated/consult states (e.g. Droperidol outside 8–12 yr) correctly show the production safety overlay under the mint header.
v4.06
UI
Fentanyl IM joins the redesigned pilot with a range-dose treatment — a Low/High toggle in the dose card feeding a chip-strip repeat regime, plus a "total max reached" state when the dose is the ceiling
- Low / High toggle. Fentanyl IM is a 1–2 microg/kg range. The dose card now carries a segmented Low dose / High dose control; the big number, volume and per-kg rule update to the selected level. Only the chosen dose is shown — no second number to read past.
- Repeat regime, driven by the engine. The chip strip walks the actual repeat sequence (1 microg/kg every 10 min, capped at the single-dose 50 microg ceiling) up to the 2 microg/kg total max, with the final dose flagged MAX. "Full plan" opens a timeline sheet. When the selected dose is itself the ceiling (e.g. High at lighter weights), the regime collapses to a calm "Total max reached" panel instead of repeat chips.
- Values come from the calculator, not the overlay. The dose number/volume are read straight from the production variable-select tile, and the sequence is walked through the same
vsRepeatState primitives — the overlay only sequences and presents, never invents a dose (v3.75 rule). Scope: Fentanyl IM only; IN and IV stay on the production page. Contra/precautions Q3 bar carried over from v4.05.
v4.05
UI
Contraindications & precautions bar moved below the presentation (above the dose card) and quietened to a faint, low-contrast row — the dose card stays the hero. Applies to all four pilot drugs
- Position. The contra/precautions bar now sits directly under the Presentation row and above the dose card, instead of pinned to the bottom of the page. Quick to glance before reading the dose, without pushing the dose down the screen.
- Quieter style (Q3). Faint card, muted uppercase label, plain text counts (contraindication count in soft red, precautions in plain) and a faint chevron — deliberately de-emphasised so it never competes with the dose for attention.
- Scope. Midazolam, Dexamethasone, Naloxone and Paracetamol. Tapping still opens the full slide-up sheet of contraindications and precautions. No dose maths changed.
v4.04
DATA
Paracetamol dose now shows in mg, not grams — "270 mg" instead of "0.27 g" (18 kg). Single-max cap expressed as 1000 mg (clinically identical to 1 g)
- Why it showed grams. The dose display logic (v88) promotes the dose to the single-max cap's unit for consistent units across weight bands. Paracetamol's cap was "1 g", so a 270 mg dose was promoted up to "0.27 g" — harder to read than mg for a sub-gram dose.
- Fix. The source single-max is now expressed as 1000 mg (identical value to 1 g), so the cap's unit matches the dose unit (mg) and no gram-promotion happens. Result: "270 mg in 11.25 mL" at 18 kg; "615 mg" at 12 yr. The cap value and all dose calculations are unchanged — a heavy patient still caps at 1000 mg. Backed up to
data/backups/; full safety regime PASS.
- Scope. Paracetamol only; no other drug or calculation affected.
v4.03
UI
Contra/precautions bar pinned to the bottom (below all dosing info), + Naloxone and Paracetamol added to the pilot
- Safety bar always at the bottom. The contraindications/precautions bar now lives in its own block (
#mzSafetyBlock) pinned at the very bottom of the page, just above the disclaimer — below every dosing element, including the production "Single dose only" badge (on Dexamethasone it previously rendered above it). Contraindications/precautions are reference safety info, so they never sit above the dose.
- Two more pilot drugs. Naloxone (20 microg/kg IM/IV, 2 contraindications) and Paracetamol (15 mg/kg PO, 2 contraindications) added to the allow-list — both clean weight-based single-dose drugs, again with no drug-specific code.
- Note (pre-existing, not changed here): Paracetamol's dose displays in grams ("0.27 g in 11.25 mL" for 18 kg) — that's the existing production rendering, which the overlay mirrors faithfully. Flagged for a possible later display tweak (mg may read clearer); no calculation change.
v4.02
UI
Second pilot drug — Dexamethasone now gets the redesigned dose page, added via a one-line allow-list (the treatment is fully data-driven)
- Dexamethasone joins the pilot. The redesign (mint header, inline dark dose card, whisper presentation, indication switcher, contra/precautions bar + sheet, swipe-dismiss) now applies to Dexamethasone as well as Midazolam. Added by a single entry in the pilot allow-list — no drug-specific code, because the treatment reads the computed dose, regime plan,
drug_info and indication list from the data.
- Validates generalisation. Dexamethasone exercises the switcher (Croup ⇄ Asthma, both PO), the contra/precautions sheet (6 contraindications + 1 precaution), and a weight-based single dose with a rounding rule ("6 mg in 1.5 mL" — rounded to nearest 0.5 mL, applied by the calculator; the production rounding-warning banner correctly stays suppressed). The production "Single dose only" badge surfaces appropriately. No regime (single-dose) and no consult panel for this drug.
- Verified. Headless render of the built candidate — Dexamethasone both indications + contra sheet, and Midazolam confirmed unchanged (3.6 mg, 5-dose regime, safety bar). No clinical-data or calculation changes.
v4.01
RUNTIME
Swipe-down to dismiss every bottom sheet — drag the sheet down with a finger to close it, across all slide-up sheets (prep, calculation, dosing plan, audit, indication, contra/precautions)
- Swipe to dismiss. A reusable drag handler (
_attachSheetDrag) lets you drag any bottom sheet down with your finger; past an ~80px threshold it slides off and closes, otherwise it snaps back. Only engages when the sheet's content is scrolled to the top, so it never fights content scrolling. Attached to the four static app sheets (prep / calculation / dosing-plan / audit) and to the pilot indication + contra/precautions sheets; closing reuses each sheet's existing close path.
- Touch-first. Implemented with touch events (the on-road target is iPhone). Verified: dragging a sheet down dismisses it; a short drag snaps back; the ✕ / handle / backdrop / Esc paths are unchanged.
v4.00
UI
Midazolam pilot — contraindications/precautions bar + slide-up sheet, inline one-line dose, and a fix so the slide-up sheets no longer sit behind the bottom nav
- Contraindications & precautions. A compact bar below the regime shows the counts ("1 abs · 4 prec") and opens a slide-up sheet with the full detail, sourced live from
QAS_DATASET.drug_info (Midazolam: 1 absolute contra — allergy/ADR — and 4 precautions). Shown on both the seizure dose page and the behavioural consult page, since contraindications are drug-level.
- Inline one-line dose. The dose card now reads "3.6 mg in 0.72 mL" on a single baseline (route moves to the rule line: "200 microg/kg · NAS/IM"), saving vertical space so the safety bar fits without scrolling on a normal iPhone.
- Sheet z-index fix. The v3.99 indication sheet shipped at z-index 90/91, below the bottom nav (z-index 200), so the nav hid the sheet's lower content. The indication sheet and the new safety sheet now use z-index 240 (matching the app's other sheets) and correctly cover the nav.
- Scope. Pilot-only (Midazolam); other drugs untouched (verified: Fentanyl unchanged). Sheets dismiss via ✕ / handle / backdrop / Esc.
v3.99
UI
Midazolam pilot Ship 2 — option-B indication switcher: a tappable kicker (CHANGE) opens a sheet to switch indication right on the dose page, without going back to the drug list
- Indication switcher. The pilot kicker becomes a tappable "CHANGE" control when the drug has >1 indication. It opens a bottom sheet listing every indication with its route(s) and a DOSE / CONSULT badge; tapping one switches indication in place — no trip back to the drug page. New capability: cross-indication navigation did not previously exist on the dose page (only route-within-indication).
- How. Reads the full indication list from
state.currentDrugObj.doses() (since state.currentIndications is filtered to the tapped indication). Selecting an item sets state.indication + recomputes the filtered list exactly as the drug-page pills do, then re-renders. Verified: Seizure (NAS/IM, DOSE) ⇄ Acute behavioural disturbance (IM/IV, CONSULT) switches the route pill, dose overlay, and consult panel correctly; switching back restores 3.6 mg + regime.
- Scope. Pilot-only (Midazolam); other drugs unchanged. Dismiss via ✕ / backdrop / Esc.
v3.98
DATA
Dataset cleanup — the banned advice-line name / acronym removed from the source spreadsheet (9 cells), plus a build-blocking guard so it can never come back
- Source cleaned. The v3.96 render-time sanitiser masked the banned phrasing in the UI; this pass removes it from the source of truth. 9 cells in the source spreadsheet (8 in
app_table — Midazolam dose_rule_original, Sodium chloride scope_note, and Droperidol / Morphine / Fentanyl notes — plus 1 in the Drug-by-Drug Audit sheet) were rewritten to neutral wording (e.g. "QAS clinical consultation and approval required in all situations"). Backed up to data/backups/ first; rebuilt dataset scans clean (0 occurrences).
- Permanent guard. New
test_no_ccal_phrasing in the dataset safety test fails the build if any field contains "Clinical Consultation and Advice Line" or a standalone "CCAL" — so a future spreadsheet import can never silently reintroduce it. (Word-bounded, so "meningococcal" is unaffected.) The render-time sanitiser stays as belt-and-suspenders.
- No clinical-value change. Only the consult-instruction wording was touched; no dose, route, cap, or contraindication value changed. Verified by the full safety regime.
v3.97
UI
Midazolam pilot — consult-only rows show just the warning panel (no pseudo-dose card)
- Consult-only state. Previously a consult-only row (e.g. Midazolam Acute behavioural disturbance) rendered a dose card with "Consult Required" as a pseudo-dose value. On the pilot it now shows only an amber warning panel — phone icon, "CONSULT REQUIRED", and the (sanitised) approval message — with no dose card at all, because there is no calculable dose to present.
- How.
_mzDecorate() detects the consult _safety_fail and calls _mzRenderConsult(), which builds the panel into #mzBlock and reuses .mz-deco so the production prep strip + dose card stay hidden. Contraindicated rows are untouched (they keep the production red overlay). Verified: the ABD page shows panel-only (dose card hidden); the seizure page is unaffected (still 3.6 + regime).
v3.96
SAFETY
Consult-phrasing guard — the banned advice-line name / acronym is stripped from any dose / notes / scope text at render time, so it never reaches user-facing UI
- Why. Convention: the advice-line name and its acronym must never appear in user-facing UI. A handful of dataset rows carry the line name verbatim in their
dose_rule_original / notes / scope_note text (imported from the source protocol) — most visibly the Midazolam Acute behavioural disturbance consult card, which rendered the full sentence under "Consult Required". Pre-existing; surfaced during the pilot work.
- Guard. New
_sanitizeCCAL() rewrites the banned phrasing to neutral wording ("… clinical consultation and approval required …"). Applied at the source where the consult subtitle is written (#ddDoseDesc, on every render incl. the consult early-return path), plus a defensive _scrubCCAL() text-node sweep of each view as it is shown by showView() — catching notes / scope_note text on any surface, current and future.
- Verified. Headless scan of the built candidate across Midazolam, Droperidol, Sodium chloride and Morphine drug + dose pages — the banned phrase no longer appears in any rendered view; the Midazolam consult card now reads "QAS clinical consultation and approval required in all situations". A dataset-level cleanup of the source rows remains a separate follow-up.
v3.95
UI
Midazolam pilot — presentation overlay brings the dose page to full mock parity: vial-icon presentation, stacked INITIAL DOSE card, REPEAT REGIME chips with per-dose mL + Full plan
- Presentation overlay (
#mzBlock). A scoped overlay re-renders the prep strip, dose card and regime in the approved mock's markup — vial icon + "Ampoule 5 mg/1 mL · 5 mg/mL" presentation, a dark stacked card ("INITIAL DOSE" tag, big white number, "mg NAS/IM", "in 0.72 mL", rule), and a "REPEAT REGIME" chip strip where each chip shows its dose + draw-up mL, with a total-max chip and "Full plan ›".
- Presentation only — no recompute.
_mzDecorate() runs last in renderDoseDetail and reads the already-computed values — it re-displays validated numbers in mock markup, it never re-derives a dose. Verified: overlay values always equal the production card; 10 yr → 5 mg / [5, 2.5, 2.5]. Buttons reuse the existing calc + dosing-plan sheets; consult rows + other drugs untouched.
v3.94
UI
Midazolam pilot — dose card goes dark to match the approved mock (white number, mint volume), scoped to the Midazolam dose page
- Dark dose card (Midazolam only). The production white answer card is replaced by a dark translucent surface on the pilot — number white, volume mint, DOSE label + rule pill + info "i" recoloured. Scoped to
.mz-pilot so the shared doseCardInnerHTML and Resus are untouched (Fentanyl card stays white). Superseded the same day by the v3.95 overlay, which renders the full mock card.
v3.93
UI
Midazolam pilot — header parity fix: flatten the legacy drug-title tile, clean full-width mint band, restore kicker/presentation below the fixed patient strip, hide the global version chip
- Flattened the legacy title tile. v3.92 shipped the mint band but the v3.87 "drug name → solid mint bar" styling survived, so the title rendered as a floating rounded pill that overlapped the patient strip. The pilot now decisively flattens the drug name (no background/border/radius/padding/shadow) so it sits as plain dark text centred in the band, with the route pill (dark fill, mint text) right and the back control left.
- Fixed the patient-strip overlap. The patient strip is
position:fixed (.v285-strip-wrap, pinned by the v3.27 lock) and the hero carries a deliberate 89px clearance. v3.92 wrongly forced the hero margin-top to 16px, sliding the indication kicker + presentation strip UNDER the fixed strip. The pilot now pushes the fixed strip below the taller band and keeps the 89px clearance.
- Global version chip. The top-right
.v286-version-chip is global chrome and showView() rewrites body.className after render, so the pilot flag is set on <html> (untouched by showView) and gated on body.v-dose — hiding the chip on the Midazolam dose page only.
v3.92
UI
Midazolam pilot ship 1 — mint header redesign, whisper presentation strip, version chip off the dose page (header parity corrected in v3.93)
- Mint header (Midazolam only). The dose-detail page paints a full-bleed mint band when the drug is Midazolam, scoped via
.mz-pilot. The renderer re-parents #ddTitleRow (drug name + route pill) into the topbar so the header reads back · drug · route; every other drug is untouched.
- Whisper presentation strip. The calm Presentation state of the prep strip drops its card on the pilot (transparent, no border) — the Q6 "whisper" treatment chosen in the redesign mockups.
- Version off the dose page. Version now surfaces on the start screen only for the pilot. Follow-up: v3.92 left the legacy title tile + a fixed-strip overlap visible on-device; corrected in v3.93.
- No clinical-data or calculation changes. Dose card, regime chip-strip / dosing-plan, and the ABD consult state are unchanged and still driven by the existing single calculator path.
v3.90
UI
Patient strip now opens an in-place edit sheet — age + weight without navigating away from the current screen
- In-place patient editor. Tapping the PATIENT strip on any screen (drug list, drug page, dose detail, vitals, resus) now slides up a bottom sheet instead of navigating to the home screen. Age dropdown + EST / Measured weight toggle tiles. Dismisses with ×, handle tap, backdrop tap, or Esc.
- Weight model. Two tiles — Estimated (locked, derived from age) and Measured (tap to reveal a number input). Tapping back to Estimated clears the override. The strip badge switches between EST and MANUAL accordingly.
- Resus round-trip removed. Weight can now be corrected inline without leaving the resus deck or losing your card position.
- No state model change. Apply writes to the existing hidden
#ageSelect / #weightSelect and fires their change events — all existing consumers (dose calculator, patient strips, vitals ranges) recompute automatically.
v3.89
UI
Tabular numerals applied to vitals thresholds, max chips, regime chip strip, and route bar max values
- Tabular numerals.
font-variant-numeric: tabular-nums added to four selectors that were missing it: vitals critical-low/high threshold numbers, max chip values (Single Max / Total Max), regime chip strip labels, and route bar max values. Numbers now take equal-width slots so they align cleanly when scanning.
- No layout change. Pure CSS — no HTML, JS, or clinical data touched.
v3.88
UI
Version chip now opens a live status panel: app version, dataset date, offline-ready + up-to-date indicators, auto update check re-enabled
- Status panel. Tapping the version chip (top-right) now opens a lightweight status panel instead of the static audit card. Shows app version, dataset scope + build date, offline-ready indicator (green when SW is active), and up-to-date state (confirmed by check or "Not checked").
- Auto update check re-enabled. On launch the app silently checks for a newer deployed version (1.5 s delay, non-blocking, fails quietly). Result is stored in localStorage so the status panel can show "Up to date" or "Update available" with a last-checked timestamp.
- Actions inside the panel. "Check for updates" runs an inline version fetch and refreshes the indicators in place; "Changelog" closes the panel and opens the changelog view.
- Chip stays compact. The top-right chip continues to show just the version label — no date or status text appended to the header.
v3.87
UI
Dose-detail header restyled: drug name in a solid mint bar, route flipped to an outlined box — auto-fitting so long names stay on one line
- Solid drug bar. On the dose-detail page the drug name moves from the v3.35 dark hairline tile to a solid mint gradient bar (white name) — it's now the dominant element. Approved across drugs in the mockups.
- Route → outlined box. The route flips from the mint-glow filled pill to a calm outlined box (dark fill, mint rim + text) beside the bar, stretched to match its height. The multi-route chevron + popover are unchanged.
- Auto-fit name.
_fitDrugBar() shrinks the name from 30 px until it fits on one line (floor 17 px); only a name still too wide then wraps (on spaces, never mid-word) — so Ondansetron / Hydrocortisone / Glyceryl Trinitrate stay tidy.
- Header only. Indication accent, prep strip, dose cards and max strip are unchanged.
v3.86
UI
Administration Route redesigned as an app-native mint segmented selector; drug-landing hero shows the name only; Contraindications open by default
- Segmented route selector. The drug-landing Administration Route section drops the stacked, route-coloured rows (purple NAS / blue IV / rose IM) for a horizontal mint segmented selector in the app's own colour language — equal-width chips that fill the row and wrap to a second row at 4+ routes (max per indication today is 3, so it's normally one clean row; no horizontal scroll). Lighter, faster to scan, visually integrated with the patient strip and bottom nav.
- Safety states preserved. Tap still navigates to that route's dose page. Contraindicated and consult-required routes are NOT hidden — the chip is tinted red / amber and prints Contraindicated / Consult inside it, so the warning rides with the route. Out-of-age routes stay filtered as before.
- Hero trimmed. The "QAS DTP · <version>" source kicker above the drug name on the landing page is removed — just the drug name now. The DTP source still surfaces on the dose-detail page and in the audit/details section.
- Contraindications open by default. On the drug-landing page the Contraindications section now starts expanded (read-first), matching its clinical priority.
v3.85
UI
repeat_once protocols now show the repeat as a SECOND full-size dose card joined by an "after X min" pill connector — Fentanyl NAS et al.
- What changed. For protocols that hard-cap at exactly 2 doses (
repeat_protocol.kind === "repeat_once", e.g. Fentanyl NAS), the repeat is no longer a compact strip — it renders as a second full-size dose card identical in size to the initial, built by the same doseCardInnerHTML() source. The cards are labelled Dose 1 / Dose 2 and joined by a mint pill connector carrying the interval ("after 10 min"). Dose 2 carries the amber Final badge.
- Each card shows its own rule. Both cards surface the per-kg rule the dose was calculated from as a rule pill on the label row (Dose 1
1.5 microg/kg, Dose 2 1 microg/kg). The repeat rule reads from repeat_protocol.amount, falling back to the row's per-kg rule when the repeat is same-as-initial.
- Matched-set alignment. Both cards carry
.dd-pair — left-aligned with tabular figures — so the two answers line up as a set rather than drifting on the centred-inline-block axis. The weight-aware max strip (v3.79) is unchanged: it surfaces below Dose 2 only when a cap actually fires.
- Scope. repeat_once only. 3+ dose regimes keep the chip strip + dosing-plan sheet; other 2-dose shapes (sequence-driven) keep the existing repeat strip. The engine remains a presentation layer — Dose 2's value is the calculator's capped repeat, not a re-derivation.
v3.84
SAFETY
Per-kg repeat doses fixed — Fentanyl NAS repeat was showing the initial dose (over-repeat); Fentanyl IV / Morphine IV now show the full dosing plan to total max
- The bug (BUG-004, SAFETY). The regime engine's per-kg resolver only scaled amounts flagged
is_per_kg, but repeat amounts are stored as "microg/kg" units. So a per-kg repeat couldn't be resolved: Fentanyl NAS showed the repeat as the initial dose (27 microg @18 kg instead of the 1 microg/kg = 18 microg repeat — a silent 1.5× over-repeat), and Fentanyl IV / Morphine IV showed an open-ended repeat with no ceiling and no plan.
- The fix. The resolver now recognises a per-kg amount from the
"…/kg" unit too, scales by weight, and resolves the unit. Fentanyl NAS repeat is now 18 microg; Fentanyl IV shows a 3-dose plan 18 → 9 → 9 to 36 microg (5 doses → 120 microg @60 kg); Morphine IV shows its plan to 3.6 mg. Dosing-plan total max now displays in the dose's unit (mg, not microg).
- Why it shipped + the guard. The existing parity test checked the Python mirror of the engine, which resolved per-kg correctly, so it never exercised the buggy JS path. New release-blocking assertions now run the live JS engine (Fentanyl NAS repeat = 18, Fentanyl IV 3-dose plan, Morphine IV total in mg).
v3.83
DATA
Sodium Chloride 0.9% shock bolus fixed — full 10–20 mL/kg range, plus the 60 mL/kg total-max ceiling and the "may be repeated twice" note now surface
- The bug (BUG-001). The fluid bolus rule "10–20 mL/kg" was parsed as a flat 20 mL/kg — the range parser only recognised mass units (microg/mg/g), so the
mL/kg lower bound was silently dropped. At 18 kg the app showed "360 mL" instead of "180–360 mL".
- The fix.
mL/L added to the range-unit parser; the row now carries value_min so the dose renders the full 10–20 mL/kg range (180–360 mL @18 kg). It stays a plain range — fluids are deliberately NOT given the Low/High tile pattern (mL is a volume unit, excluded from the mass-only tile derivation; for a fluid the dose IS the volume, so there's nothing to pair).
- Fuller fluid context. The cumulative ceiling now shows as TOTAL MAX 60 mL/kg (1080 mL @18 kg) — a fluid bolus's stop signal — and the DTP's "May be repeated twice" instruction now surfaces as a repeat note (previously hidden because the repeat is unstructured). Volume-unit casing normalised to "mL".
- Guarded. A BLOCK regression (clinical-sanity) fails the build if any per-kg range row drops its lower bound again; R41 pins the saline 10–20 mL/kg row (and that it stays a plain range, not tiles); and the renderer smoke harness asserts the 180–360 mL range, the 1080 mL total-max chip, and the repeat note all render.
v3.81
SAFETY
Variable-dose tiles no longer render for out-of-band / contraindicated patients — they now obey the same eligibility gate as every other dose
- The bug (BUG-003, SAFETY). The v3.80 variable_select tile renderer keyed only on the row's
dose_mode and ignored _safety_fail, so a patient who FAILED the eligibility pipeline still saw live Low/High dose tiles. Reported case: Droperidol IV at 5 years (band 8–12 yr, drug-level contraindication <8 yr) displayed 1.8 mg / 3.6 mg tiles over the contraindication state.
- Scope — all four variable_select rows. Same root cause hit Droperidol IV, Droperidol IM, Fentanyl IM (<1 yr → consult) and Morphine IM (<1 yr → consult): any out-of-band / consult / contraindicated patient saw calculated tiles. Fixed-dose rows were never affected (they render through the gated
renderValidatedBuild path).
- The fix. The tile path now runs ONLY when the line is eligible (
!ln._safety_fail) — the identical gate fixed-dose rows use — with a defence-in-depth re-check inside the renderer that also clears any tile / repeat markup. Ineligible patients render only the existing Contraindicated / Consult Required state: no tiles, no repeat strip, no total max, no calculated values.
- Blocking regression tests added. The renderer smoke harness now asserts, for Droperidol IV/IM @5 yr and Fentanyl/Morphine IM @6 mo, that the tile card + repeat strip are suppressed and only the safety card shows. Release-blocking.
v3.80
UI
Variable-dose rows (Fentanyl IM, Morphine IM, Droperidol IV/IM) now use selectable Low/High dose tiles with weight-aware repeat availability
- The problem. Per-kg range doses (e.g. Fentanyl IM 1–2 microg/kg) rendered as an inline range like "20–40 microg in 0.4–0.8 mL" — four numbers and two dashes competing on one line, and the range's upper bound silently equalled the total max, so the ceiling showed twice.
- What's shipped. A new
dose_mode: "variable_select" renders two selectable tiles — Low / High — each pairing its own dose with its own volume. "Selected dose" label, a subtle "Dose range · X–Y" guideline line for source transparency, and a clean TOTAL MAX chip below.
- Weight-aware repeat — the safety core. Repeat availability is computed at the patient's weight from total-max headroom, never a static flag. Selecting High on Fentanyl/Morphine suppresses the repeat at low weight (initial = total max) but correctly reopens it above the 25 kg single-dose-cap weight (e.g. Fentanyl High @30 kg → 10 microg remaining). Droperidol keeps its repeat-once across the band (flat 20 mg cap never approached).
- Selection state. The Low/High choice is keyed to the dose row: it persists when only the weight changes (numbers recompute) and resets to the dataset default when the drug / route / indication changes — no stale state leaks between rows.
- Tests. New variable_select schema + scope guard, weight-aware clinical-sanity invariants, regime parity (T5), the R40 regression (locking the 20/25/30/40 kg repeat examples) and a renderer smoke + stale-state guard. All values flow from the one calculator path; the engine invents no dose maths.
v3.79
UI
Single/Total max chips now surface only when a cap actually fires for the patient; PRN "NO MAX DOSE" signal preserved
- Display-only change. No dose, dataset or calculation logic changed — only the visibility rule on the max strip below the dose card.
- Why. Previously both chips were ALWAYS visible, taking equal visual weight as the dose card. At common weights, neither fires (e.g. Fentanyl NAS @ 18 kg: dose 27, single max 50, cumulative 45, total max 100 — both well below). Paramedic gets reference info competing with the actual dose answer. Plus the chip width caused "50 microg" to wrap to two lines on common iPhone widths.
- New visibility rules.
- Single chip shows only when
calc.cap_applied is true — the single max literally fired.
- Total chip shows when the regime engine walks to / hits the total cap (Midazolam seizure 3-dose, where total cap drives the dose count; or any 2-dose row whose initial + repeat sum equals/exceeds the cap).
- "NO MAX DOSE" chip preserved unchanged — this is a PRN safety signal (Adrenaline anaphylaxis et al.) and must stay visible: it tells the paramedic they CAN keep going.
- Where the ceiling reference went. Still in the calc sheet behind the i icon (Dose ceilings section). One tap away when needed; off the main card when not.
v3.78
SAFETY
Repeat strip now carries a small amber "Final" badge for protocols that hard-cap at 2 doses (Fentanyl NAS et al.); previously the strip read like a recurring PRN
- Why. The v3.63 V3 repeat strip displays "after 10 min" for any 2-dose case, with no visual difference between a protocol-hard-capped repeat_once (Fentanyl NAS: 1 initial + 1 repeat, no more) and a recurring-but-only-shows-two scenario. A paramedic could reasonably read the strip as "give the repeat, then keep going at 10-min intervals" — which would be wrong for Fentanyl NAS.
- What changed. A small amber "Final" badge now appears at the right end of the repeat strip when
row.repeat_protocol.kind === "repeat_once". Same amber colour family as the v3.73 cap-applied sub-line — reads consistently as "this dose is the last one by protocol".
- Scope. Drives off
repeat_protocol.kind alone — no dataset change, no allowlist. Hidden for same_as_initial + fraction_of_initial + different_amount 2-dose cases, which aren't protocol-hard-capped (those rows could continue per PRN rules).
- Display-only. No dose, dataset, or calculation logic changed. Engine still returns the same values; UI just labels them more clearly.
v3.77
GOVERNANCE
Hot-fix: v3.76 added build/test_regime_engine.py but the file was never committed; CI ran the orchestrator step against a missing file and failed. Now shipped + git_release.sh hardened
- Root cause.
git_release.sh only ran git add -u + git add snapshots/. New untracked files under build/ (test scripts, generators) and scripts/ were silently excluded. The v3.76 release wired test_regime_engine.py into run_all_tests.py but never shipped the file itself — CI saw the step, tried to run the missing file, and aborted with exit 1.
- What's shipped. The actual
build/test_regime_engine.py file (four parity tests catching the v3.75 Fentanyl NAS bug shape — see v3.76 entry for the rationale).
- Script fix.
git_release.sh now adds new untracked files under build/, scripts/, and CLAUDE.md automatically. Other untracked locations (e.g. node_modules/, scratch files) are still ignored so the safety net doesn't become a leak.
v3.76
GOVERNANCE
Post-mortem from v3.75 Fentanyl NAS bug: new test_regime_engine.py blocks the build if a future change makes the engine drift from the calculator
- Architecture rule locked. The regime engine (
_doseEngine) is now formally a presentation layer: it may determine dose count, sequencing, timeline, progression — it must NOT invent dose mathematics. All dose values shown in the repeat strip, chip strip, regime sheet, and timeline must match the calculator's output (_computeRepeatDose). Whenever two calculation paths exist, drift becomes inevitable — v3.75 was the canonical example.
- Four new tests (build/test_regime_engine.py, wired into run_all_tests step 5a, BLOCK severity):
- T1 · Fentanyl NAS @ 18 kg. Initial = 27 microg, repeat = 18 microg. Fails if repeat equals initial when the dataset specifies a different rp.amount. Validates the v3.75 fix stays fixed.
- T2 · Variable-repeat-amount sweep. For every row where
repeat_protocol.amount exists with a different value than the initial rule, the calculator mirror must compute the repeat from rp.amount × weight. Discovery surface — future Fentanyl-shaped rows surface automatically.
- T3 · Midazolam seizure 3-dose. 25 kg patient: 5 mg, 2.5 mg, 2.5 mg. Fails if all doses become identical (the canonical "engine duplicated initial" bug pattern).
- T4 · Calculator parity sweep. For every structured multi-dose row, the Python calculator mirror must resolve a positive, finite repeat dose. If the mirror can't resolve it, the engine likely can't either — a structural guardrail before runtime.
- Negative test verified. Removing
repeat_protocol.amount from the Fentanyl NAS row (simulating the v3.75 bug shape) now produces a BLOCK failure: "Fentanyl NAS repeat_protocol.amount is missing — engine has no way to know the repeat dose differs from the initial."
- Recorded in CLAUDE.md under the project memory's "Remember" section so future changes inherit the architecture rule.
v3.75
SAFETY
Fix: repeat_once protocols with an explicit repeat amount (Fentanyl NAS 1 microg/kg) now show the correct repeat dose, not a copy of the initial
- Engine bug. The v3.65 multi-dose engine's
repeat_once branch was hard-coding the second dose to equal the initial, regardless of whether the protocol specified a different repeat amount. Fentanyl NAS: initial 1.5 microg/kg, repeat 1 microg/kg (per QAS DTP). The engine was reporting the repeat as 1.5 microg/kg too.
- For a typical patient. 18 kg child: initial 27 microg, repeat used to render as 27 microg (1.5 × 18). Now correctly renders as 18 microg (1 × 18). Volume scales accordingly via the constant concentration ratio.
- Calculator path was already right.
_computeRepeatDose at line ~14601 handles "repeat_once with rp.amount" as different_amount, computing the second dose from rp.amount. The engine just wasn't asking. Fix mirrors that handling.
- Cap behavior. Repeat dose still gets capped by
rp.single_max || row.single_max if it would exceed the single-dose ceiling.
- Total max ≠ target. While we're here: paramedics seeing "Total max 100 microg" with two doses summing to less may wonder why the engine stops. Answer:
repeat_once caps at 2 doses by protocol design. The total max is a ceiling for larger patients (50 kg child: 50 + 50 = 100 hits exactly), not a target to walk to.
v3.74
SAFETY
Rounded-volume drugs: displayed volume now matches what's drawn up. New ≈ glyph + outline "Rounded" pill replace the two redundant captions
- Clinical display fix. For drugs with
volume_rounding_enabled (Dexamethasone PO Croup, Ondansetron IM, etc.), the dose card now shows the ROUNDED volume as the primary number. Previously it showed the raw calc value (e.g. 1.35 mL) while a caption below said "draw up 1.5 mL" — two different numbers on the same card. Root cause: volumes[0].volume_ml wasn't being updated when the rounding block fired; it kept the raw value while out.volume_ml already held the rounded one.
- Two new markers (AE-4). A small "≈" glyph before the volume number signals "approximate" without needing words. An outline-only "Rounded" text pill in the label row (beside the v3.73 rule pill) carries the label. Together they read at a glance without competing visually.
- Two captions retired. The "0.3 mg/kg (rounded to nearest 0.5 mL)" parenthetical (from
dose_rule_original) and the "Volume X mL · rounded from Y mL" working line are both suppressed when volume was rounded — the new markers + the rule pill + the calc sheet cover everything those captions said. Weight-rounding caption (Ceftriaxone meningococcal, Morphine IM) is preserved because there's no equivalent marker for it yet.
- Math on demand. Raw value (1.35 mL → 1.5 mL working) still lives in the calc sheet behind the i icon, alongside the rest of the arithmetic. Pattern stays consistent with v3.72/v3.73: rule and rounding always visible; math one tap away.
- Resus untouched. Compact mode skips both the ≈ glyph and the rounded pill — Resus card renders bare.
v3.73
UI
v3.72 "Based on X" caption moves to a small mint pill beside the DOSE label kicker; cap sub-line stays below the answer
- Display-only change. Same render helper as v3.72 — only the markup placement and CSS changed.
- Why. v3.72 put the rule below the dose+vol line. It read as a footnote rather than as a property of the dose. Variant B from the locked mockup promotes the rule to a small mint chip beside the "DOSE" kicker — same line as the label that names what's being calculated.
- New markup. The label is now wrapped in
.dd-label-row (flex container). Inside: the existing .dd-label + a new .dd-label-rule pill carrying the rule text. The "Based on" prefix is gone — the pill colour does the framing.
- Cap sub-line stays put. "Max single dose applied" sits below the dose+vol line in soft amber, where the eye already lands on the answer.
- Resus unchanged. Compact mode skips both the pill and the cap line — Resus card still renders bare.
v3.72
UI
Dose card now shows the rule it was calculated from ("Based on 10 microg/kg"), with a cap-applied sub-line when single_max fires
- Display-only change. Reads existing structured
row.dose_per_kg and calc.cap_applied — no dose, dataset, or arithmetic change.
- What's new. A quiet caption inside the dose card, below the dose+vol line: "Based on 10 microg/kg". When the single-dose cap fired during calculation, a second sub-line appears in soft amber: "Max single dose applied". Paramedics now see why the displayed dose is what it is, without having to open the calc sheet.
- V1b spec. Rule always visible; math stays on demand behind the i-icon (v3.71 calc sheet). Cap sub-line is the only safety signal — full arithmetic is still in the calc sheet.
- Range doses. Display as "X–Y unit/kg" (e.g. Droperidol 0.1–0.2 mg/kg). Single-value rules show as "X unit/kg".
- Hidden when not applicable. Non-weight-based rows (fixed dose, age band, reference, fixed inhalation count) — nothing to show, line stays hidden. Also hidden under contra state, safety_fail, ambiguous/non-volumetric/missing-volume branches.
- Drugs-only. Resus Adrenaline card uses
doseCardInnerHTML({compact: true}) and skips the rule markup — Resus surface stays unchanged.
v3.71
UI
Show calculation now opens a slide-up sheet (same gesture as the prep + dosing-plan sheets) instead of expanding inline
- Display-only change. Calculation content is identical — only the surface it appears on has changed.
- Why. v3.70 made the i-icon a tap target but the body still expanded inline far below the icon, requiring a scroll to find. v3.71 makes the body slide up from the bottom — adjacent to the thumb, focused, dismissable with one tap.
- Single gesture vocabulary. Three sheets across the app now all behave the same way: tap a small affordance, sheet slides up with backdrop, dismiss via ✕ / handle / backdrop / Esc. Prep sheet (v3.62), dosing-plan sheet (v3.65), calculation sheet (v3.71).
- Standalone calc card retired on Drugs view. The old
.dose-detail-calc chunky chip is now display:none permanently on the Drugs detail page. Its body container #ddCalcCardBody stays populated in the DOM — the sheet reads innerHTML from it on open, so the existing _buildCalculationBlock renderer needs no changes.
- i-icon unchanged. Same 22 px outlined-mint italic-i from v3.70 — same hidden states (contra, safety_fail). Only the click target shifted from inline-toggle to sheet-open.
v3.70
UI
"Show calculation" moves into a small 22 px italic-i icon in the top-right of the dose card; chunky full-width chip retires
- Display-only change. No dose, dataset or calculation logic change — only the trigger placement and the standalone card's default visibility.
- Why. The old full-width "SHOW CALCULATION ›" chip carried the same visual weight as the dose card and max chip — three competing primary surfaces, none of which is the dose answer.
- What's new. A 22 px outlined-mint italic-i button (variant A4 from the approved mockup) sits in the top-right of the white dose card. Tapping it expands the calc card below AND scrolls it into view. Tapping it again — or the in-card header — collapses back.
- What's hidden. The standalone
.dose-detail-calc chip is display:none until expanded. When expanded, the card surface + body appear with the existing chevron header (now serving as the in-card collapse target).
- Hidden when not relevant. Icon is hidden under contra state (red overlay owns the card) and on safety failures (consult required, ambiguous, etc.) where the math card is also suppressed.
- Resus untouched. The Resus Adrenaline dose card uses
doseCardInnerHTML({compact: true}) and skips the icon — Resus surface has no calc card to point at.
v3.69
UI
Patch: max-strip drops the redundant "TOTAL MAX" label when the value is just "No max dose"
- Display-only patch. When the cumulative cap is "No max dose" (e.g. Adrenaline IM anaphylaxis, Salbutamol NEB PRN), the strip now routes to the unlabeled
.dd-max-chip-none chip — reads just "NO MAX DOSE" instead of "TOTAL MAX · No max dose", which was doubly redundant (the label implied a cap, the value said there wasn't one).
- Existing single-max behaviour preserved. Rows with a Single Max but no Total Max still show the Single Max chip — alongside the unlabeled "NO MAX DOSE" chip when applicable.
v3.68
UI
Patch: more breathing room between the indication line and the v3.67 preparation strip
- Display-only patch. Bumps
.dd-prep-strip top margin from 0 to 14 px so the strip stops crowding against the indication line above it. Bottom margin unchanged at 10 px.
v3.67
UI
Preparation strip moves ABOVE the dose card; dose card becomes a pure dose-answer with no footer
- Display-only change. No dose, dataset or clinical-logic change — only the placement of the Presentation / Dilution / Reconstitution row, plus the dose-card footer being dropped on the happy path.
- Why the move. Previously the prep label sat in the dose-card footer ("Dilution · 10 microg/mL"), competing with the dose number for the eye. The card now answers only "what dose do I give?"; the strip above answers "what am I drawing from?".
- Two visual states.
.is-calm for Presentation (compact, non-clickable, lower priority — physically smaller than the action strip per the locked mockup). .is-action for Dilution / Reconstitution (mint border, two-line: type-specific overline "DILUTION REQUIRED" / "RECONSTITUTION REQUIRED" + kind label · value · info icon below). Whole strip is the tap target.
- Sheet wiring unchanged. The action strip reuses the existing v3.62
.dd-dilution-trigger delegated click handler — tapping opens the same prep sheet (recipe tiles, then-note, caution) with no new code path.
- Ceftriaxone IV/IO. Action strip carries a
· 2 STEPS tag on the overline so the user knows the sheet contains a recipe and a draw-up/expel-excess then-note before tapping. Auto-derived from PREP_RECIPES[prepId].note.
- Dose card footer dropped on happy path.
.dd-divider + .dd-drawup stay in the DOM (used by error/ambiguous/non-volumetric branches and by the Resus Adrenaline card) but the happy volume_status === "ok" branch now leaves them empty; the existing :empty rule keeps the card clean.
- Error branches preserved. Ambiguous-volume, non-volumetric, and "volume not available" branches still write warnings into the dose card so the alert lands where the eye is — strip stays hidden in those states.
- Resus Adrenaline parity deferred. The Resus card keeps its current footer for now (out of scope for this change — Resus surface has its own layout constraints worth their own mockup pass).
v3.66
UI
Patch: "View dosing plan" trigger spans the full dose-card width (was a narrow pill in v3.63–v3.65)
- Display-only patch. No data or logic change — only the CSS for the chip-strip trigger button.
- Root cause.
.dd-regime-trigger was display: flex without width: 100%, so the <button> element shrank to fit content instead of stretching across the dose-card edges. Approved mockup showed a full-width bar; live v3.65 rendered a narrow left-aligned pill.
- Fix. Added
width: 100%; box-sizing: border-box; and bumped padding from 10/14 to 12/16 px so the bar reads as a proper tappable strip aligned with the dose card and chip strip.
v3.65
UI
Multi-dose regime engine: chip strip + repeat strip now drive off the dataset, allowlist retires
- Display-only change. No dose, dataset or clinical-logic change — engine reads existing structured fields (repeat_protocol, single_max, total_max, dose_per_kg) the same way the calculator does.
- Part-2 split landed. v3.63 shipped the UI components behind a hand-curated row allowlist (Midazolam NAS/IM seizure + Droperidol IV/IM behavioural only). v3.65 replaces that with
_doseEngine(ln, weightKg) — a dynamic walker that handles every multi-dose row with a structured cap.
- How it counts. Reads the initial dose from the calculator (already weight-adjusted and single-max-capped), resolves the repeat dose per
repeat_protocol.kind (same_as_initial, fraction_of_initial, different_amount, repeat_once), walks initial + repeat × N against the total cap (per-kg or fixed), and emits a final partial dose if there's a meaningful chunk left to land on the cap exactly.
- Coverage expanded. Now lights up for: Fentanyl IV (2 doses @25 kg, 5 doses @60 kg — varies by weight), Morphine IV, Morphine IM, Fentanyl IM, Fentanyl NAS, Methoxyflurane INH, Ipratropium NEB, Midazolam IV/IO (seizure + sedation), plus the two already shipping under v3.63's allowlist. PRN rows (Adrenaline, Salbutamol, etc.) and unstructured rows still pass through to the existing v86 repeat bar — no change there.
- Unit-safe. Internal walk runs in micrograms; display strings use the dose card's unit (mg / microg / g). Mixed-unit rows (e.g. dose in mg, total_max in microg/kg) convert correctly.
- Range doses. Walking uses the upper-range value (worst case = fewest doses to reach cap) so the chip count is never overstated.
- Bail-outs. No total cap → falls through to v86 (PRN).
repeat_protocol.kind === "unstructured" → falls through. Initial dose unreadable → falls through. Safe default — v86 always available as the backstop.
v3.64
UI
Patch: Droperidol regime strip no longer inherits the "using X" preparation suffix from the calculator
- Display-only fix. No dose, dataset or clinical-logic change.
- What broke. v3.63's regime strip read
ddVolUnit.textContent verbatim. For range doses (Droperidol IV 4.1–8.2 mg → 0.82–1.64 mL) the legacy ln.vol fallback leaves "mL using 10 mg in 2 mL (5 mg/mL)" on the unit span. The strip inherited that and rendered "4.1–8.2 mg in 0.82–1.64 mL using 10 mg in 2 mL (5 mg/mL) after 15 min if required" — duplicating the Presentation footer above and crowding the strip.
- The fix.
regimePlanFor() now strips any " using …" suffix from the dose unit and volume unit before composing the calc descriptor. The dose-card display path was already clean for most rows via the V11 overwrite; this patch closes the range-dose leak path that v3.63 surfaced.
v3.63
UI
Multi-dose regime UI (Part 1): V3 repeat strip, chip strip, View dosing plan sheet — allowlist-driven
- Display-only change. No dose, dataset or clinical-logic change. Sourced from the row's calculator output + a per-row plan rule.
- Part-1 split. Per the v3.63/v3.64 release plan, this ship is the UI components only. The dynamic dose-count engine (
doseCountForRow) that will broaden coverage to every multi-dose row ships separately in v3.64 so the clinical logic can be validated independently.
- What's new. Three components: V3 repeat strip for the 2-dose case (compact ↻ + dose + timing, matches the existing PRN repeat-bar visual language), chip strip for the 3+ dose case (numbers-only chips with position pip, unit on the meta line above), and a "View dosing plan" sheet that slides up with a timeline of every dose + interval + clinical note (same gesture vocabulary as the v3.62 prep sheet).
- Allowlist scope (v3.63). Renders for Midazolam NAS/IM seizure (3-dose titrated chip strip) and Droperidol IV/IM behavioural (2-dose repeat strip). Every other multi-dose row stays on the existing v86 repeat card. The allowlist closes once v3.64's count engine takes over.
- Repeat card retires for allowlisted rows. When the new component renders, the legacy
ddRepeatCard + repeat divider + simple repeat line are explicitly hidden so the regime UI doesn't double up. Non-allowlisted rows still see the v86 card.
v3.62
UI
Tappable Dilution / Reconstitution opens a slide-up prep sheet; orange prep card retires
- Display-only change. No dose, dataset or clinical-logic change — sheet content is sourced from existing prep records.
- What changed. The "Dilution" / "Reconstitution" word under the dose-card divider becomes a tap trigger (dotted underline + ⓘ). Tapping opens a slide-up sheet from the bottom of the screen containing a visual recipe (ingredient + diluent → result), any Caution on the prep, and any extra admin step. Dismiss via ✕, the drag handle, the dimmed backdrop, or Esc.
- Orange "Drug Preparation Required" card retires. The same data lives in the prep sheet now, gated by an explicit tap so the page stays clean for drugs that don't need prep info on every viewing. The legacy markup + populating logic remain in place (hidden via CSS) so nothing downstream breaks.
- Presentation rows stay plain. No trigger, no ⓘ, no sheet — they're stock drugs drawn direct (Adrenaline IM 1:1,000 ampoule, Naloxone IM/IV ampoule, etc.). Nothing to prep, nothing to tap.
- Ceftriaxone IV/IO multi-step preserved. Both sentences of the prep render: the reconstitution recipe as tiles ("1 g vial" + "9.4 mL water for injection" → "100 mg/mL"), and the draw-up-and-expel-excess instruction as a "Then" note below — the second sentence isn't lost.
- Resus parity preserved. Trigger renders on the Resus Adrenaline page too (via the shared
doseCardInnerHTML + drawupTwoLine), with the sheet opening for the Resus Adrenaline preps the same way.
v3.61
UI
TOTAL MAX strip hides on single-dose-only drugs (Hydrocortisone, Ceftriaxone, Glucagon, etc.)
- Display-only change. No dose, dataset or clinical-logic change. Stop-signal information for repeat / titrated drugs is preserved.
- The change. The "TOTAL MAX · {value}" strip below the dose card now suppresses for single-dose-only rows. For drugs that are administered once per encounter (Hydrocortisone, Ceftriaxone, Glucagon, Dexamethasone, Ondansetron, single-dose Salbutamol NEB, etc.) the strip was either showing "No max dose" (redundant with the "Single dose only" repeat pill above) or showing a value labelled "Total Max" that's actually the single-dose cap — confusing.
- The rule. A row is "single-dose-only" if
repeat_interval matches /single dose only|auto-injector/ or repeat_protocol.kind === "none". Belt-and-braces — the existing v131 text check is preserved and joined with a structured-field check so a row that has one but not the other still suppresses.
- Where TOTAL MAX still shows. Adrenaline IM anaphylaxis (repeat every 5 min — cumulative cap is the stop signal), Fentanyl titrated dosing, Adrenaline IV/IO cardiac arrest, Salbutamol repeated nebs — anywhere the cumulative max is what tells the paramedic when to stop.
- Scope. Applies to both the Drugs detail page (
_renderMaxStrip) and the Resus Adrenaline page (_pgAdrenaline Total max banner) — same rule, single source of truth.
v3.60
INFRA
Update Now actually updates on the first tap (no more "tap twice" to see the new version)
- What broke. Tapping Update Now used to require two taps to see the new version. The service worker's stale-while-revalidate handler served the cached (old)
index.html on the reload navigation, while it background-fetched the new one into cache for next time. Tap 1 = old. Tap 2 = new. Confusing for paramedics in the field.
- Why two taps.
sw.js is byte-static across releases (intentional — see comment block at the top), so the browser never sees a new SW to install. The existing SW keeps running with the cached index.html. The cache only refreshes after the navigation completes via the background re-fetch.
- The fix. Both
applyUpdate() functions (the v3.14 update panel + the v2.86 update banner) now explicitly fetch the new index.html (cache-busted, cache: 'no-cache' bypasses the HTTP cache) and write it into the SW cache before reloading. The reload then serves the fresh version from cache on the very first navigation request.
- Future-proofing. Added a
SKIP_WAITING message listener to sw.js. Today's stable-sw.js flow doesn't use it (the cache-prime above is what does the work), but if a future release ever changes the service worker bytes the page-side postMessage will correctly tell the waiting worker to activate immediately.
- Offline-safe. If the cache-busted fetch fails (no signal), the code falls through to the existing reload — the SW serves whatever it has cached. No regression to the offline behaviour.
v3.59
UI
Dose number bumped 42 → 48 px so it reads as the loudest element on the page
- Display-only change. No dose, dataset or clinical-logic change.
- Dose number font 42 → 48 px. Everything else (unit 16, volume 34, vol unit 15, "in" 14, concentration 13) held from v3.56.
v3.58
UI
Under-line now names the prep type: Presentation / Dilution / Reconstitution · value
- Display-only change. No dose, dataset or clinical-logic change — every value rendered comes from the existing prep records.
- The change. The under-the-divider concentration line gains a muted leading label: Presentation · 1 mg/mL for stock ampoules, Dilution · 10 microg/mL for saline-diluted preps (Fentanyl IV, Morphine IV, Midazolam IV/IO, Adrenaline infusion), Reconstitution · 50 mg/mL for powder + diluent preps (Hydrocortisone, Ceftriaxone, Glucagon).
- Classification rule.
presentationLabelForPrep() reads the prep's requires_preparation flag plus the leading word of its preparation text: false → Presentation; true + starts with "Reconstitute" → Reconstitution; else → Dilution. Single source of truth for both Drugs and Resus surfaces.
- Type spec. Label slate-400 weight 500 (muted), mid-dot slate-300, value slate-500 weight 700 (slightly stronger). Same 13 px size as the existing under-line.
- Suppression preserved. When a row has no concentration / prep context (tablets, fixed sachets, single-product inhalers — Aspirin PO, Glucose Gel, Methoxyflurane),
drawupTwoLine() returns "" and the existing :empty rule hides the under-line block + divider.
v3.57
UI
Hero polish + dose card centring: brings the live build in line with the approved iPhone mockup
- Display-only change. No dose, dataset or clinical-logic change.
- Indication bumped 10 → 12 px (letter-spacing eased 1.8 → 1.6 so it doesn't pack), with a 10 px gap above instead of 4 — breathes properly under the drug+route row.
- Drug name in the hero tile up 24 → 28 px so it visually leads the hero.
- Route pill now stretches to match the drug tile's height (align-items: stretch on the title row) and gets wider padding (6/10 → 6/20, min-width 44 → 72) — reads as the drug's matched companion rather than a small tag.
- Dose card content centred horizontally inside the card via a new
.dd-inner inline-block wrapper. Each line inside the block stays left-formatted (DOSE label / dose row / divider / concentration); the whole block centres so left and right gutters inside the card are equal. Fixes the "left-heavy" feel without narrowing the card or breaking prep-card alignment.
- Resus parity preserved — the dose card centring applies to both Drugs and Resus surfaces via the shared
doseCardInnerHTML() builder.
v3.56
UI
Dose card polish: inline dose + volume, route dropped from label, tighter padding + divider
- Display-only change. No dose, dataset or clinical-logic change. Scope is the dose card only — header, drug tile, route tile, indication, repeat / max / calc strips all untouched.
- Inline dose + volume. v3.52's stacked dose-over-volume layout switches to a single row: 150 microg in 0.15 mL. Dose is the 42 px headline; volume rides beside it at 34 px in mint. Reads as one sentence rather than two stacked numbers.
- "DOSE" only — route dropped from in-card label. Route is already shown in the M-4 mint pill in the hero above. The "· IM" half of the in-card label was duplicate noise. Now the label is just "DOSE" so the eye lands on the dose number immediately.
- Tighter card padding. 18 / 18 / 16 → 14 / 16 / 12. ~10 px of vertical whitespace reclaimed.
- Tighter divider. 12 auto → 10 / 0 / 8. Concentration line sits visually connected to the volume above it.
- Typography frozen. Dose 42 / vol 34 / dose-unit 16 / vol-unit 15 / "in" 14 / concentration 16. Dose card is now considered locked — focus shifts to repeat / max card redesign next.
v3.55
UI
Roll back v3.54: restore full-width dose card so prep cards line up beneath it
- Display-only change. No dose, dataset or clinical-logic change.
- What went wrong. v3.54 narrowed the dose card (max-width 300 px) and tightened its padding to address an empty right gutter. On real device the white card sat noticeably narrower than the drug-prep cards rendered below it — the rhythm down the page broke.
- The roll back. v3.54's max-width cap, padding tighten and divider margin trim are all reverted. The dose card returns to its v3.53 dimensions so the dose card and prep cards share the same right edge. The right-gutter ergonomics issue is acknowledged but the prep-card alignment is the load-bearing constraint.
v3.54
UI
Dose card polish: tighter padding, narrower card (rolled back in v3.55)
- Rolled back. See v3.55. Narrow card broke alignment with the drug-prep cards below.
v3.53
UI
Dose card patch: fix volume stacking, amber concentration, centred label, dashed divider
- Display-only fix. No dose, dataset or clinical-logic change.
- What broke. v3.52 shipped the V11 stacked dose card but four legacy v3.49-era rules were winning on properties the override block didn't explicitly reset. On live the volume number + unit stacked vertically inside the row (0.54 on one line, mL below), the concentration rendered amber instead of slate-500, the DOSE label stayed centred, and the divider was a 1.5 px dashed line instead of a 1 px solid hairline.
- The fix. Four explicit resets in a new v3.53 patch block — flex-direction:row + min-height:0 on the vol-grp; color:inherit on the dilution value; text-align:left on the card itself; solid 1 px border-top on the divider. No structural change; the V11 architecture is unchanged.
v3.52
UI
Dose card simplified: stacked dose / volume, concentration moves below the divider
- Note: v3.52 rendered with four legacy-bleed bugs — patched in v3.53.
- Display-only change. No dose, dataset or clinical-logic change.
- The change. The v3.49 two-mint-tile + "IN →" arrow row retires. Dose card now reads top-to-bottom as a clean three-step instruction: 250 microg (dose, F-2 30 px) → in 0.25 mL (volume, mint with muted "in" prefix) → ──── → 1:1,000 (1 mg/mL) (concentration, slate-500 / weight 700, parenthetical qualifier in slate-400).
- "CONCENTRATION" label removed. The divider already signals supporting info — the label was visual weight without clinical value (per the dose-card-v10/v11 review pass).
- Concentration weight = 700. Locked from the dose-card-v11 typography sweep (Option C). Renders consistently across variable-font and non-variable platforms.
- Suppression rule. When a row has no concentration / preparation context (single-stock tablets, fixed sachets, single-product inhalers — e.g. Aspirin PO, Glucose Gel, Methoxyflurane, Loratadine), both the divider and the under-line block hide. Card ends cleanly at the volume row.
- Resus parity preserved. Same shared
doseCardInnerHTML() helper renders both surfaces — the Drugs detail page and the Resus Adrenaline page stay visually identical, as they have since v3.23.
v3.51
UI
Drug + route row now lines up with the patient bar and dose card edges
- Display-only change. No dose, dataset or clinical-logic change.
- The gap. v3.50 shipped the D-2 header but the drug tile + route pill sat ~16 px more inset than the dose card and patient bar above/below — the .dd-title-row's own 16 px horizontal padding was stacking on top of .view-dose's existing 18 px gutter.
- The fix. Zero the .dd-title-row's self-padding on v-dose so the row sits flush with the view's gutter. Indication margin also drops to 0 horizontal so its mint accent bar lines up directly under the drug tile's left edge. All three surfaces (patient bar, drug+route row, dose card) now share the same vertical alignment line.
- Scope. v-dose only. Resus pages still use the v3.34 hero spacing and are untouched.
v3.50
UI
Header redesign: drug tile + glow route, indication moves beneath the hero
- Display-only change. No dose, dataset or clinical-logic change.
- The change. Drug name now sits in a dark forest tile (24 px, 800) with hairline border; the route pill switches to the M-4 mint variant with a soft mint halo and drop shadow (16 px, 900, 10 px radius). Indication moves below the title row with a 2 px mint accent bar on the left, at 10 px / 800 / 1.8 px tracking. Reading order is now drug → route → indication.
- Why. Header exploration in mockups/hero-split-v1 → v17 froze on D-2: the drug name dominates, the route is impossible to miss, and the indication reads as a caption for the dose card directly below — not as an eyebrow competing for the hero spot.
- Long-name safe. Drug name allows multi-line wrap inside the tile (no ellipsis), and the indication wraps cleanly to 2–3 lines with the mint bar tracking the full block — verified against "Hydrocortisone" and the full QAS "Refractory anaphylaxis with persistent wheeze (and unresponsive to 3 × IM adrenaline)" indication.
- Scope. v-dose only this ship. Resus pages still use the v3.34 hero — parity work is the next design priority alongside the V4 dose card and repeat-dose redesign.
v3.49
UI
Dose card redesigned as two mint tiles with an arrow between them
- Display-only change. No dose, dataset or clinical-logic change.
- The change. The v3.48 inline "50 microg in 1 mL" row gets pulled apart into two distinct mint-filled tiles: the dose tile on the left, the volume tile on the right, with a small "IN →" arrow column between them. Tiles share the same numeric scale (28 px / 800), unit beneath each, hairline mint rim.
- Why. Two physically separated stat-cards make the dose-to-volume relationship visually unmissable — "this much dose maps to that much volume" — without losing the side-by-side glanceability the v3.48 inline row had.
- Resus Adrenaline matches. Same shared builder, same tile layout — Drugs and Resus dose cards stay visually identical.
v3.48
UI
Dose card now reads "50 microg in 1 mL" on one line
- Display-only change. No dose, dataset or clinical-logic change.
- The gap. The dose card showed the dose twice: a 76 px headline "50 microg" then "Draw up 50 microg in 1 mL" right below. The number you actually draw up — the volume — was buried in the smaller second line.
- The fix. One inline row in the dose card: "50 microg in 1 mL". Dose number stays the visual anchor (36 px / 800 weight); the volume sits beside it at the same weight, with "in" muted between them. The redundant "Draw up …" line is gone; the concentration / dilution line stays underneath.
- Edge-case-validated. Worst-case strings (1200 mg in 24 mL, 7.5 mg in 0.75 mL, 0.15 mg in 1.5 mL, 500 microg in 5 mL) all sit on one line at 320 px viewport (iPhone SE) without wrap, truncation, or unit collision.
- Resus Adrenaline matches. Same shared builder, same inline layout — Drugs and Resus dose cards stay visually identical, as they have since v3.23.
v3.47
UI
Tap outside the indication picker to close it
- Display + interaction change. No dose, dataset or clinical-logic change.
- The gap. v3.46 made the indication list a viewport-anchored overlay sheet so it could fit any number of items, but the only way to close it without selecting an item was tapping the row's chevron again. Standard modal pattern is "tap outside to dismiss", which the picker didn't support.
- The fix. A document-level capture-phase click listener now closes the picker whenever it's open and the tap lands outside the wrap (above on the wheels, below on the bottom nav area, anywhere). The row's tap-to-toggle and the list's tap-to-select still work normally because both targets are inside the wrap. Escape on a hardware keyboard also closes for accessibility.
- Zero idle overhead. The listener no-ops when the picker isn't expanded, so there's nothing for it to do on the calm-state home screen.
v3.46
UI
Home-page indication list now works for any number of indications
- Display-only change. No dose, dataset or clinical-logic change.
- The fix. When the indication picker is expanded, the list detaches from the locked home-page flex flow and becomes a viewport-anchored overlay sheet (top below the title area, bottom above the bottom nav), with internal scrolling. Works for ACP2 today and CCP later — viewport, not item count, is the binding constraint.
v3.45
GOVERNANCE
CI verification via gh CLI, not web_fetch — permanent drift fix
- Tooling change. No dose, dataset, clinical-logic or visible UI change.
- The root cause. v3.40–v3.43 leaked into "ask the user to verify CI" because the skill depended on
mcp__workspace__web_fetch; the tool's URL-provenance window rolled off in long conversations and the fetch started failing. v3.44 made the failure graceful but didn't fix the underlying dependency.
- The permanent fix. New
build/check_ci.py queries GitHub Actions through the gh CLI. Exits 0 only on completed + success; exits 1/2/3/4 for gh unavailable, run not found, run red, still-in-progress. Authenticated API access — no provenance window to drift.
- Sandbox note. Until
gh is installed + authenticated in the sandbox the script returns exit 1 and the run falls back to local-only verification.
v3.44
GOVERNANCE
Skill no longer asks you to confirm CI when web_fetch is blocked
- Tooling change. No dose, dataset, clinical-logic or visible UI change.
- Bridge fix. Stopped the v3.40–v3.43 "Claude asks the user to verify CI" pattern by reframing CI verification as best-effort. Made graceful, but the underlying web_fetch dependency was still in place — v3.45 ships the actual permanent fix.
v3.43
UI
Home-page indication list now scrolls on small screens
- Display-only change. No dose, dataset or clinical-logic change.
- The bug. On the home screen, tapping INDICATION expands a list of options. On smaller phones the list extended past the bottom of the viewport and the home page is locked (the page itself cannot scroll), so the last items were unreachable.
- The fix. The list now caps its height to fit the viewport:
clamp(140px, calc(100dvh − 540px), 220px), with iOS momentum scrolling inside the box.
v3.42
SAFETY
Build-time guard on the offline diagnostics panel
- Tooling change. No dose, dataset, clinical-logic or visible UI change.
- Why. v3.41 added the offline diagnostics panel on the About page as load-bearing safety observability. Without a build-time test, the surface could silently drift over a long enough timeline (same pattern as the v3.36 changelog freshness gate).
- The guard.
test_offline_diag.py in the safety regime verifies eight things every release: panel markup exists, four field IDs present, _renderOfflineDiag defined, reads the app-version meta tag, probes navigator.serviceWorker and caches.keys(), keeps the strict offline-ready calculation (SW Installed AND cache has ./index.html), stays read-only.
- Governance. Skill SAFETY auto-class rule now lists four surfaces — sw.js, SW registration, diagnostics panel, readiness calculation.
v3.41
SAFETY
Offline status diagnostics on the About page
- Display + diagnostics change. No dose, dataset or clinical-logic change. Adds read-only visibility into the v3.40 offline cache state.
- The problem. If a paramedic reports "the app won't open offline" or "I added it to my home screen but it still needs internet", we previously had no way to confirm whether the service worker installed, whether the cache populated, or which version the device is actually running — without remote debugging.
- The fix. A new "Offline status" panel on the About page shows four fields: App version, Service worker state, Cache version, and Offline ready (Yes when both SW is Installed and the cache holds the app shell; No otherwise; mint when Yes, amber when No).
- Behaviour. Read-only. Fire-and-forget — every probe is independently try/catch'd so the About page renders even if every API throws.
v3.40
SAFETY
Offline app shell cache added. App works without signal after first online load.
- Infrastructure change. No dose, dataset or clinical-logic change. The drug dataset is unchanged; what changes is how reliably the app survives a no-signal situation.
- The problem. Until this release, the app had no installed service worker. Offline support relied entirely on the browser's HTTP cache, which iOS Safari will evict at any time (memory pressure, OS update, force-quit, PWA storage purge). A paramedic in an ambulance with no LTE could open the app and see a blank screen.
- The fix. A new
sw.js at the repo root pre-caches the entire app shell (index.html, manifest.json, both icons) on first online load. On every subsequent open the app launches from cache instantly, signal or no signal.
- Updates still work. The existing "Check for updates" panel on the More tab still does an explicit online fetch and prompts you to reload when a new version ships.
- Build-time enforcement. A new
test_service_worker.py step in the safety regime blocks ship if sw.js is missing, syntactically broken, doesn't pre-cache the required shell, doesn't define the install/activate/fetch handlers, or isn't registered from the app.
v3.39
UI
One CONSULT REQUIRED warning per row, not two
- Display-only change. No dose, dataset or clinical-logic change. No change to which rows are flagged consult — only to how the flag is shown.
- The bug. On consult-required rows (e.g. Glucose 10% IV INF at 5y/18kg) the dose-detail page showed two stacked "CONSULT REQUIRED" warnings: one as an amber banner above the dose card, and one inside the dose card where the dose value would normally sit. Identical titles, slightly different sub-text — read as duplicate.
- The fix. The top amber banner is now suppressed when the dose card itself shows "Consult Required" as the dose value. The dose card is the more contextual surface (carries DOSE · ROUTE plus the row-specific sub-text — "Check PDF dosing table…", "Outside approved age/weight range…"). The route-list page on the previous screen still flags CONSULT REQUIRED so the user is forewarned before they enter. Mirrors the v2.18 dedup for contraindicated rows.
v3.38
UI
Drugs tab returns you to the drug page you were last on
- Display + navigation change. No dose, dataset or clinical-logic change.
- The bug. If you were on a drug detail page or a dose detail page and tapped Vitals / Resus / More on the bottom nav, then tapped Drugs again, the app dropped you back at the drug list instead of returning to where you were.
- The fix. The Drugs tab now restores the last drug page you were on. Drug list → drug list. Drug detail page → same drug detail page. Dose detail page → same dose detail page. State persists across tab switches; the surface is freshly painted on return so the displayed dose reflects the current patient age and weight.
- Defensive fallback. If for any reason the state needed to restore the detail page is missing (shouldn't happen in normal flow), the tab falls back to the drug list — never a blank screen.
v3.37
GOVERNANCE
Changelog freshness gate now skips CI verification runs
- Tooling-only change. No dose, dataset or app behaviour change. No user-visible UI change.
- The bug. The v3.36 freshness gate (which blocks ship if the top changelog tagline matches the previous release's) fired on CI for the v3.36 commit itself because CI's
last_passing_snapshot.txt was the just-pushed v3.36, so the candidate built at v3.37 had the same cl-tagline as the v3.36 snapshot it was comparing against. False positive: CI was verifying v3.36, not preparing v3.37.
- The fix. Skip
test_changelog_freshness when run_all_tests.py is invoked with --no-promote (CI's verification mode). The gate still runs every time an actual release is prepared locally — which is the only context where it's meaningful.
v3.36
UI
Route pill stays heavy mint for single-route drugs; changelog guard added
- Display + tooling change. No dose, dataset or behaviour change.
- Route pill. v3.34 dropped the pill to a muted forest-rim style when only one route was applicable for the patient (the Midazolam seizure case at 5y/18kg). That read as "transparent / white text" rather than deliberate context. Pill is now filled mint at all times — the chevron's presence/absence is the only visual cue for whether the route can be switched.
- Changelog freshness guard. A new build-time test (
test_changelog_freshness.py) blocks shipping if the top Version-history tagline matches the previous release's. This stops the "What's new" drift that produced the v3.25 → v3.34 string of releases all carrying the same stale note. (Note: v3.36 had a CI false-positive — fixed in v3.37.)
v3.35
UI
Version history caught up — earlier release notes added
- Display-only change. No dose, dataset or behaviour change.
- Adds release notes for v3.26 through v3.34 to the version history.
- Fixes the "What's new" surface on the More tab, which had been repeating "Resus page titles centred" (the v3.25 note) on every release because the top template entry's wording wasn't being updated.
v3.34
UI
Route now a pill beside the drug name; Resus headers re-aligned
- Display-only change. No dose, dataset or behaviour change.
- Route selector. Drug name flush left, route as a filled mint pill flush right on the same row. Tap the pill to switch routes on multi-route drugs. Single-route drugs render the pill in a muted style so it doesn't read as tappable.
- Resus headers re-aligned. The Adrenaline page kicker was sitting 14 px below the Defib / CPR / Airway kickers; fixed. All four Resus deck pages now share the same kicker height.
v3.33
UI
Route inline with the drug name; "(epinephrine)" removed from display
- Display-only change. No dose, dataset or behaviour change.
- Title row. Drug name and route share one centred line. Multi-route drugs open a popover on tap; single-route drugs show the route as static context.
- Display name simplified. "Adrenaline (epinephrine)" now renders as just "Adrenaline" on the drug detail and dose detail pages. The dataset, lookups, audit, and contraindication rules all still use the canonical name — display only.
- Resus Adrenaline parity. The Resus Adrenaline page hero now uses the same markup as the drug-detail hero so the two pages line up structurally.
v3.31
UI
Resus page headers now line up
- Display-only change. No dose, dataset or behaviour change.
- What changed. The Adrenaline page's "CARDIAC ARREST" kicker now sits at the same Y as the Defib / CPR / Airway kickers. Previously the Adrenaline kicker was ~24 px lower because the page's wrapper element was double-padding for the Resus deck's edge spacing.
v3.30
UI
Removed duplicate route badge on the Resus Adrenaline page
- Display-only change. No dose, dataset or behaviour change.
- What changed. The standalone blue "IV" pill above the Resus Adrenaline prep-warning card is gone. The route now appears in one place only — inside the dose card label as "DOSE · IV".
v3.29
UI
Resus Adrenaline card matches the drug detail card
- Display-only change. No dose, dataset or behaviour change.
- What changed. The Resus Adrenaline cardiac-arrest dose card now shows the same "DOSE · IV" label as the drug-detail page. Identical typography across both surfaces.
v3.28
UI
Route picker redesigned as compact tabs under the drug name
- Display-only change. No dose, dataset or behaviour change.
- What changed. The big standalone route pill above the dose card (and the swipe hint below it) is replaced by a compact segmented-tabs row directly under the drug name. The dose card label now reads "DOSE · NAS/IM" (or whichever route is active) so the dose is always paired with its route.
- Single-route drugs don't show tabs — only the route label inside the dose card.
- Swipe gesture is preserved as a secondary input.
v3.27
UI
Patient age/weight bar locked in place on the dose detail page
- Display-only change. No dose, dataset or behaviour change.
- What changed. The patient bar and the back-button row on the dose detail page no longer jump ~50 px upward when scrolling. Both stay pinned at the top of the screen.
- Why. The view container was double-padding for the iPhone safe area; removing the redundant padding made the sticky and natural-flow positions agree.
v3.26
UI
Patient bar pinned on the drug detail page
- Display-only change. No dose, dataset or behaviour change.
- What changed. The patient age/weight bar on the drug detail intermediate page is now pinned in place when you scroll. Superseded by v3.27, which fixed the equivalent bug on the dose detail page where the jump was actually being seen.
v3.25
UI
Resus page titles centred
- Display-only change. No dose, joule, device-size or dataset changes.
- What changed. The Defibrillation, CPR and Airway page titles are now centred, matching the Adrenaline page and the drug detail pages.
v3.24
UI
Resus pages now have titles, like the drug pages
- Display-only change. No dose, joule, device-size or dataset changes.
- What changed. The Defibrillation, CPR and Airway pages now show a page title at the top — a teal "Cardiac arrest" kicker over the page name — the same header the Adrenaline page already had.
- Why. Every Resus page now identifies itself at a glance, consistent with the drug detail pages. All four pages share one header definition.
v3.23
RUNTIME
Adrenaline dose card — one shared definition, Drugs and Resus
- Internal refactor — no visible change. No dose, volume, concentration or dataset changes; the cards look exactly as before.
- The problem. The dose card was built by two separate pieces of code — one for the Drugs page, one for the Resus Adrenaline page. They could drift apart, and a shared-class accident between them caused the v3.20 contraindication display fault.
- The fix. Both pages now build the card from one shared definition, so they cannot diverge. A new automated test fails the build if a card is ever hand-built outside that shared definition.
v3.22
UI
"Consult required" route tags no longer run off the page
- Display-only fix. No dose, volume, concentration or dataset changes.
- The problem. On routes needing consultation, the warning tag repeated "Consult required" twice — and on some drugs the second copy ran off the right edge of the screen.
- The fix. The tag now reads "Consult required" once, followed only by the genuine qualifier (e.g. "in all situations") when there is one. Redundant wording is removed and the tag can no longer overflow the page.
v3.21
SAFETY
Contraindication panel — regression fix + redesign
- Fixes a v3.20 regression in the contraindication display. No dose-calculation changes. No dataset changes.
- The bug. v3.20 gave the Resus Adrenaline card the same CSS class as the drug dose card. The dose page found its card by that class, so once the Resus Adrenaline page had been opened, it could grab the wrong one — and a contraindicated drug's dose page then showed the warning twice (a "DOSE / Contraindicated" line and the red panel) with the panel's text hard to read. The contraindication was always shown — the warning never disappeared — but the presentation was degraded for the 7 drugs with an age-based absolute contraindication (Droperidol, Ipratropium bromide, Methoxyflurane, Naloxone, Ondansetron, Paracetamol, Salbutamol).
- The fix. The dose card now has a unique id, so the page selects it unambiguously — this class of bug is structurally prevented, not just patched.
- Redesigned panel. The contraindication panel is now a single solid deep-red card — severity, "Contraindicated", the specific reason, and "Do not administer to this patient" — with no doubled message and fixed high-contrast text in any context.
- What didn't change. Every dose, volume and concentration; the dataset and its schema; the calculation engine.
v3.20
UI
Resus Adrenaline dose card now matches the Drugs page
- UI-class release · the Resus Adrenaline page only. No schema changes. No dose-calculation changes. No dataset changes.
- Consistent dose card. The Adrenaline page in the Resus section now draws its dose card with the same card components and wording as the standard Drugs dose page — the "Dose" heading and the two-line "Draw up … in …" / "Concentration —" format. Previously the Resus version used a separate, slightly smaller card style, so the same drug looked different in the two places.
- The dose itself was never different — both screens compute it from the same dataset row. This change aligns only the presentation.
- What didn't change. Every dose, volume and concentration; the dataset and its schema; the calculation engine.
v3.19
UI
Resus pages — trimmed low-value collapsible sections
- UI-class release · Resus reference pages only. No schema changes. No dose-calculation changes. No dataset changes.
- Collapsible sections that did not add much have been removed to keep each Resus page focused: Defib — "Stacked shocks" and "Quick notes"; CPR — "Compression mechanics", "Rhythm workflow" and "ETCO₂ / quality"; Airway — "Airway tips"; Adrenaline — "Timing", "Administration" and "After ROSC".
- What stays. Defib keeps "Show calculation" and "Alternatives & AED mode"; Airway keeps the OPA and i-gel size charts. The patient-specific dose, joule and device-size figures, and the adrenaline prep-warning, are unchanged.
- What didn't change. Every dose, volume and concentration; the dataset and its schema; the calculation engine.
v3.18
UI
Reverted the v3.17 More-section dark pinning
- UI-class release · theming only. No schema changes. No dose-calculation changes. No dataset changes.
- v3.17 is reverted. Pinning the More sub-pages (About, Version history, Feedback, Install instructions) to a forced dark palette did not look right, so it has been removed. Those pages once again follow the phone's light/dark appearance setting.
- What didn't change. Every dose, volume and concentration; the dataset and its schema; the calculation engine.
v3.17
UI
More section pinned to dark mode
- UI-class release · theming only. No schema changes. No dose-calculation changes. No dataset changes.
- The More section is now consistently dark. The More menu has always been dark, but its sub-pages — About, Version history, Feedback, Install instructions — followed the phone's light/dark setting. In light mode that meant opening one flipped the app from a dark menu to a bright page. All four are now pinned to the dark palette, so the whole More section is one consistent dark experience.
- Drug calculation screens are unchanged — they still follow the phone's appearance setting. A full light-mode pass for the app is noted as separate, later work.
- What didn't change. Every dose, volume and concentration; the dataset and its schema; the calculation engine.
v3.16
UI
More menu tidied + About page given its own design
- UI-class release · the More menu and About page only. No schema changes. No dose-calculation changes. No dataset changes.
- More menu reordered — top to bottom: About, Install instructions, Feedback, Version history. The back button on each sub-page now reads "Back".
- About page redesigned. It had come to look identical to Version history (same cards and layout). It now has its own design — flowing sections with a distinct safety callout — so the two pages are no longer mistaken for each other.
- "App status" panel moved to the foot of the More page.
- Feedback and Install fixed. Their per-page setup — the Feedback form reset and the Install version label — now runs whichever way the page is opened.
- What didn't change. Every dose, volume and concentration; the dataset and its schema; the calculation engine.
v3.15
UI
More-menu back buttons fixed + About page rewritten for paramedics
- UI-class release · the More menu only. No schema changes. No dose-calculation changes. No dataset changes.
- Back buttons fixed. The back button on Version history, Feedback, About and Install instructions now returns to the More menu — previously it jumped all the way out to the home screen, which was disruptive.
- About page rewritten. It previously showed build-engineering detail (git commit, file hashes, build timestamps) that means nothing at the bedside. It now states what the app is, that it is an ACP2-scope reference drawn from the QAS Drug Therapy Protocols, carries a safety disclaimer, and shows a plain "data current as of" line.
- What didn't change. Every dose, volume and concentration; the dataset and its schema; the calculation engine.
v3.14
UI
Check-for-updates redesigned — a clear, status-first panel
- UI-class release · the More-tab update panel only. No schema changes. No dose-calculation changes. No dataset changes.
- Status-first design. The panel now leads with the answer to "what version am I on, and is it current?" rather than a button. At rest it shows your installed version and when it last checked; a check resolves to one of three clear states — up to date, update available, or couldn't check — each with its own icon.
- "What's new" on an available update. When a newer version is found, the panel shows that version's release summary pulled from this Version history, with a link through to the full notes.
- What didn't change. Every dose, volume and concentration; the dataset and its schema; the calculation engine; governance baselines. The update mechanism itself (a cache-busting reload) is unchanged.
v3.13
UI
Check-for-updates reliability fix + one unified version number
- Update check could falsely report an update. The checker was reading a placeholder version string out of a code comment, so it could announce a "new version" that did not exist. It now reads an explicit version tag, so "up to date" is reported correctly.
- One version number. The version shown in the app and the version recorded in the release history are now the same single number, and cannot drift apart.
- What didn't change. Every dose, volume and concentration; the dataset and its schema; the calculation engine.
v3.05
DATA-ONLY
Sodium chloride 0.9% — contraindications and precautions now shown
- Sodium chloride 0.9% drug information completed. Its contraindications and precautions, from the QAS Drug Therapy Protocol, now display on the drug page — previously those sections were blank.
- Build safeguard added. A new check blocks any release where a drug is missing its contraindications/precautions record, so this gap cannot recur.
v3.04
DATA-ONLY
Sodium chloride 0.9% — paediatric IV infusion added
- Sodium chloride 0.9% paediatric IV infusion. The fluid-resuscitation rows for sodium chloride 0.9% were imported from the QAS Drug Therapy Protocol and verified.
- What didn't change. Existing drugs, doses and the calculation engine.
v3.03
DATA-ONLY
Glucagon — refractory anaphylaxis dosing added
- Glucagon refractory anaphylaxis. IM/IV dosing rows for glucagon in refractory anaphylaxis were imported from the QAS Drug Therapy Protocol and verified.
- What didn't change. Existing drugs, doses and the calculation engine.
v3.02
SAFETY
Adrenaline cardiac arrest — corrected to age-banded paediatric dosing
- Safety correction. Adrenaline for cardiac arrest (IV/IO) previously applied 10 microg/kg across all paediatric ages — which under-/mis-dosed the youngest patients (a 3 kg newborn calculated to 30 microg).
- Now age-banded per the QAS Drug Therapy Protocol: newborn 50 microg fixed; 3–5 months 70 microg; 6–12 months 100 microg; 1 year and over 10 microg/kg. Preterm bands surface a consult advisory.
- Check your dose. If you used this app for paediatric cardiac-arrest adrenaline before v3.02, confirm against the current protocol.
v3.01
UI
Dose-detail header kept visible while scrolling
- Sticky dose-detail header. The top bar on a drug's dose page now stays pinned with a solid background as you scroll, so the back button is always reachable.
- What didn't change. Every dose, volume and concentration; the dataset; the calculation engine.
v3.00
UI
Portrait orientation lock
- Portrait lock. The app is now locked to portrait — a web app manifest requests portrait on installed devices, and a landscape overlay blocks interaction if the phone is turned, so the layout can't break mid-use.
- What didn't change. Every dose, volume and concentration; the dataset; the calculation engine.
v2.46
UI
Home screen locked + age wheel enlarged — real-device iPhone polish
- UI-class release · home-screen behaviour only. No schema changes. No dose-calculation changes. No dataset changes. Real-device iPhone feedback on the v2.43 scroll-wheel home screen.
- Home screen locked. The home view no longer scrolls or rubber-band bounces — it now behaves like a piece of equipment, not a webpage. Only the wheel components themselves scroll; the page stays fixed however the user drags it. Scoped to the home view, so the genuinely-scrollable views (drug list, dose detail, resus, version history) are untouched.
- Age wheel enlarged ~20%. The age wheel height is raised from 112 px to 134 px and the step padding increased, for a larger, more comfortable thumb target with more visible age values. No behaviour change — the centre lock, snap physics and selected-row styling are all driven off the height and were preserved; the wheel is not animated or dynamically resized.
- What didn't change. Every dose, volume and concentration; the dataset and its schema; the calculation engine; the wheels' wiring and the hidden state <select> elements; governance baselines.
v2.45
UI
Redundant "QAS Paed Drug" header label removed
- UI-class release · home-screen chrome only. No schema changes. No dose-calculation changes. No dataset changes.
- Removed the small "QAS Paed Drug" label in the top-left of the home header. It duplicated the large "QAS Paediatric Drug Calculator" headline directly below it. The version chip remains as the only top-left header element.
- What didn't change. Every dose, volume and concentration; the dataset and its schema; the calculation engine; the wheels and their wiring; governance baselines.
v2.44
UI
OPEN RESUS stays put — action button no longer jumps on the Drugs/Resus toggle
- UI-class release · home-screen layout fix only. No schema changes. No dose-calculation changes. No dataset changes. Follow-up to the v2.43 scroll-wheel home screen.
- Bug — the action button jumped position on the mode toggle. In Resus mode the Indication step is hidden (resus is age/weight driven). The generic
.start-field.is-hidden rule collapsed its space with display:none, so CALCULATE and OPEN RESUS sat at different heights — flicking the Drugs/Resus toggle made the button visibly jump.
- Fix — reserve the step's space instead of collapsing it. A
body.v-start #startIndicationField.is-hidden rule now hides the Indication step with visibility:hidden, so it keeps its box in Resus mode and OPEN RESUS lands exactly where CALCULATE does. The indication wheel is collapsed on every mode switch so the reserved height is always the compact pill height.
- Renderer probe extended in
build/test_bundle_rendering.py: the wheel structural guard now also asserts the #startIndicationField.is-hidden alignment rule is present, so the fix can't be silently removed.
- What didn't change. Every dose, volume and concentration; the dataset and its schema; the calculation engine; the wheels and their wiring; governance baselines. This release only stops the action button moving when the home toggle is used.
v2.43
UI
Scroll-wheel home screen — age, weight and indication as iPhone-style pickers
- UI-class release · home-screen input controls only. No schema changes. No dose-calculation changes. No dataset changes. The Age, Weight and Indication dropdowns on the home screen are replaced with iPhone-style scroll wheels; everything downstream is untouched.
- Wheels are a presentation skin over the existing state. The three
<select> elements (#ageSelect, #weightSelect, #indicationSelect) remain in the DOM, hidden, as the single source of truth for patient state. Each wheel writes its value into the matching <select> and dispatches a native change event, so every existing consumer — state.ageKey / state.weightOverride / state.indication, patientWeight(), updateStartSummary() and the patient strips — keeps working unchanged.
- Weight options unchanged. The weight wheel offers exactly the existing 21 presets (3–50 kg) read straight off
#weightSelect, plus a "use age estimate" anchor. No new weight values are selectable, so the dose-calculation surface is identical to v2.42.
- v2.38 indication reconcile preserved.
#indicationSelect remains the target of the showView start-view reconcile. After the reconcile settles, showView calls a re-sync hook so the visible indication wheel always reflects the reconciled value — the home picker and state.indication still cannot desync.
- Chrome. The home card flattens into three divider-separated steps (Age / Weight / Indication). The bottom-nav Resus tab's heart glyph is replaced with a defibrillation bolt — a heart read as "favourites".
- Renderer probe added to
build/test_bundle_rendering.py: a build-blocking guard asserting the three hidden state <select> elements and the wheel wiring are intact, and that each wheel's pickable rows mirror exactly its <select>'s option set — a wheel can never surface a value it cannot write back.
- What didn't change. Every dose, volume and concentration; the dataset and its schema; contraindications, precautions and indications; the calculation engine; governance baselines. This release only changes how age, weight and indication are entered on the home screen.
v2.42
SAFETY
IO removed from two ACP2 routes — intraosseous is a CCP skill
- SAFETY-class release · out-of-scope administration route corrected. No schema changes. No dose-calculation changes. No runtime logic changes. Two route labels in the source spreadsheet corrected so the ACP2 app never presents intraosseous (IO) — a CCP-level skill — as an administration option.
- Bug class — out-of-scope route shown to an ACP2 paramedic. Two rows visible in the ACP2 runtime listed their route as IV/IO, surfacing IO administration to a clinician who isn't credentialled for it. Intraosseous access is consistently scoped to CCP across the QAS DTPs (the Ceftriaxone rows already carry the scope note "IO administration is CCP scope"); these two rows were inconsistent with that.
- Adrenaline · Cardiac arrest — verified against DTP_ADR_0825. The DTP's Routes of administration table badges Intraosseous injection (IO) CCP only. The Paediatric Cardiac arrest section splits the route explicitly: the IV entry is badged ACP2 + CCP, the IO entry is badged CCP only — both carry the identical weight-banded dose table. Row
QAS_ACPII_006 route corrected IV/IO → IV. No dose change — the figure is identical for IV and IO; only the out-of-scope route label is removed.
- Glucose 10% · Symptomatic hypoglycaemia — verified against DTP_GL10_0223. The Paediatric Symptomatic hypoglycaemia dosages badge IV INF as ACP2 + CCP and IO INF as CCP only (the only ACP2-adjacent IO use is the separate "Unconscious hypoglycaemia / AMP 001/23" indication, which requires consultation and is not this row). Row
QAS_ACPII_025 route corrected IV/IO INF → IV INF. No dose change.
- Regression test R38 added to
build/test_regressions.py: a build-blocking guard asserting no active ACP2-visible row (level_rank ≤ 1) carries IO as a route token, plus spot-assertions that the two corrected rows read IV and IV INF. The CCP-level IO rows (Adrenaline refractory anaphylaxis / bradycardia, Fentanyl, Midazolam, Morphine, Ceftriaxone) are untouched — IO is correctly within CCP scope and those rows are filtered out of the ACP2 runtime anyway.
- What didn't change. Every dose, volume and concentration; contraindications and precautions; the indications; the renderer;
build/dataset.json structure; governance baselines. This is purely a route-label scope correction on two rows.
v2.41
UI
Consistency pass (pilot) — on-dark text colour routed through its token
- UI-class release · zero visual change. No clinical-data changes. No schema changes. No runtime logic changes. This is the first, deliberately tiny slice of a CSS consistency pass — a code-quality tidy, not a visible change.
- On-dark foreground colour tokenised. The colour
#F4FAF8 — the app's standard text/icon colour on dark surfaces — was hard-coded as a raw literal in 14 places (12 color:, 2 SVG fill:) while the rest of the codebase already routed it through the --colour-on-dark token. Those 14 raw uses now read var(--colour-on-dark, #F4FAF8), matching the convention already used in ~38 other places. Because --colour-on-dark is defined as exactly #F4FAF8, the rendered output is byte-identical — this is purely a single-source-of-truth tidy.
- Why this is safe. The change is a pure literal→token substitution where the token's value equals the literal, so every affected rule resolves to the same colour it did before. The diff is 14 lines, colour-only; the full safety pipeline passes 12/12.
- Context — the consistency audit. A sweep of the stylesheet found roughly 690 colour values still hard-coded as raw literals (about 69%), across 124 distinct hex colours, plus an unused spacing-token scale and 79
!important rules. Most of that is maintainability debt, not user-facing — the app already looks coherent. Tackling it well is per-occurrence judgement (which token does each use mean?), not a mechanical sweep, so it's being done in small, individually-verifiable slices like this one rather than as a risky big-bang refactor.
- What didn't change. Every rendered pixel; the xlsx source data; every drug row; the renderer logic;
build/dataset.json; governance baselines.
v2.40
GOVERNANCE
Build gate — age-gap dead-ends can't be reintroduced
- GOVERNANCE-class release · build-gate hardening. No clinical-data changes. No schema changes. No runtime changes. One new blocking test that turns the age-band audit into a permanent guard.
- The bug class being closed. v2.35 (Dexamethasone Croup) and v2.39 (Fentanyl Significant pain) both fixed the same shape of bug: an age-gated indication where some patient ages fell outside every dose band, with no consult threshold or age contraindication rule to catch them — so the paramedic saw the generic, non-tappable "outside the QAS-approved age band" fallback banner, a dead end. Each fix was per-drug; nothing stopped a future spreadsheet edit from reintroducing the gap on another drug.
- New blocking test:
test_no_age_gap_deadend in build/test_dataset.py. For every ACP2-visible indication group, it sweeps every selectable patient age (0–144 months) and asserts each age is covered by one of three things: a dose band, a consult threshold (consult_required_below/above_months), or an age-based contraindication_rule. Any age covered by none of the three is a genuine dead-end and fails the build. It is indication-level — matching what the renderer's empty-route fallback actually keys on — and credits consult / contraindication coverage, so it only fires on real dead-ends, not on intentional age gating.
- Audit result that prompted this. A full pass over every age-gated indication (run after v2.39) found no remaining genuine gaps — every age-gated indication is now either fully band-covered, uses the full "Paediatric" range, or has its young-age gap caught by a consult threshold (Dexamethasone, Fentanyl, Morphine) or an age contraindication rule (Droperidol, Ipratropium, Methoxyflurane, Naloxone, Ondansetron, Paracetamol, Salbutamol). The dataset ships clean against the new gate; from here the gate keeps it that way.
- Relationship to the existing check.
test_no_paediatric_gaps stays as the informational, route-level, band-only note it always was. The new test is the stricter, indication-level, consult/contra-aware, blocking guard — they are complementary, not duplicates.
- What didn't change. The xlsx source data; every drug row; the renderer; dose-calculation paths;
build/dataset.json structure; governance baselines; every other pipeline step.
v2.39
SAFETY
Fentanyl Significant pain — NAS age band corrected, under-age consult wired
- SAFETY-class release · Fentanyl Significant pain age bands corrected against DTP_FEN_0722. No schema changes. No dose-calculation changes. No runtime decision-path changes. Three Fentanyl dose rows in the source spreadsheet corrected; the safety pipeline and a new negative-assertion regression test (R37) verify the result.
- Bug class — autonomous dose shown where the DTP mandates consultation. The Fentanyl NAS Significant pain row carried an age band of
≥ 6 months. Per DTP_FEN_0722, the ACP2 NAS paediatric Significant pain band is ≥ 1 year — the ≥ 6 months figure belongs to the CCP NAS row. The app's NAS row is the ACP2 per-kg form (1.5 microg/kg, single max 50 microg, total max 100 microg), so its band must be ≥ 1 year. With the wrong band, a 6–11-month-old was shown an autonomous NAS fentanyl dose when the DTP requires QAS consultation. NAS band corrected from ≥ 6 months to ≥ 1 year.
- Under-age consult now wired on all three routes. DTP_FEN_0722, ACP2 paediatric Significant pain, states verbatim for each route: NAS — "< 1 year – QAS Clinical Consultation and Advice Line approval required in all situations"; IM — "< 1 year – QAS Clinical Consultation and Advice Line approval required in all situations"; IV — "< 1 year – QAS Clinical Consultation and Advice Line approval required in all situations". All three rows (
QAS_ACPII_016 NAS, QAS_ACPII_017 IV, QAS_ACPII_069 IM) now carry age_weight_criteria = "≥1 year; <1 year consult", which the parser resolves to age_min_months: 12 + consult_required_below_months: 12. The IV and IM bands were already ≥ 1 year and unchanged — only the consult threshold was added there.
- What the paramedic now sees. An under-1-year patient on Fentanyl Significant pain previously hit the generic, non-tappable "outside the QAS-approved age band" fallback banner — a dead end. Now each route renders a proper amber Consult required bar that opens the dose-detail page with the CONSULT REQUIRED alarm, via the existing v1.58 OUT_OF_BAND → CONSULT_ONLY path. A 9-month-old on NAS, who previously got an autonomous dose, now correctly gets the consult bar.
- Regression test R37 added to
build/test_regressions.py: asserts every ACP2-visible Fentanyl Significant pain row (NAS, IV, IM) carries age_min_months = 12, consult_required_below_months = 12 and consult_required_flag = true — a drift guard against the band reverting to 6 months or the consult threshold being stripped. Behaviour verified pre/post: a 3-month-old now sees three tappable consult bars instead of the dead-end banner.
- What didn't change. Fentanyl contraindications and precautions; the Sedation and other Fentanyl indications; every other drug; the renderer; dose-calculation paths;
build/dataset.json structure; governance baselines. A follow-up release will audit the remaining age-gated drugs for any similar band gaps.
v2.38
RUNTIME
Edge-swipe back/forward navigation + home-dropdown indication fix
- RUNTIME-class release · navigation. No clinical-data changes. No schema changes. No dose-calculation changes. Two renderer changes: a new edge-swipe page navigation gesture, and a fix for the home-screen indication filter not clearing.
- Edge-swipe back / forward. Swipe in from the left screen edge to go back a page, from the right edge to go forward — iOS-style. "Back" retraces the exact pages you actually visited (browser-style history), not a fixed hierarchy: e.g. Home → drug list → Droperidol → dose detail, then swipe back down the same trail and forward again. A visit-history stack records each page with the state needed to rebuild it (current drug, indication, dose route), and restoring a page re-renders it through the same code paths normal navigation uses.
- No conflict with the existing content swipes. The drug-detail page still uses a full-width swipe to change drug, the dose-detail page to change route, and the Resus deck to change page. Only touches that begin within 28 px of a screen edge drive page back/forward; the change-drug and change-route handlers now ignore that edge zone, and edge swipes that start inside the Resus deck are left to the deck. (Desktop: Alt+Left / Alt+Right mirror the gesture.)
- Fix — home-screen indication filter not clearing. Tapping an indication pill on a drug page stores the selection as
literal:<exact text>, which has no matching option in the home-screen indication dropdown. The dropdown could end up showing "All indications" while the stored filter still held the literal value — and because the dropdown was already visually on "All indications", re-selecting it fired no change event, so the stale indication never cleared and could carry into the next drug opened. showView() now reconciles the dropdown with the stored indication every time you land on the home screen: a literal indication collapses to a matching dropdown option, or to "All indications" if none matches, and the <select> is synced — the two can no longer desync.
- Tests added to
build/test_bundle_rendering.py: a DOM-stub probe of the start-view indication reconcile (literal → matching id / "all", with a hard invariant that no literal: value survives), and a state-machine probe of the navigation-history module (push on navigate, back/forward stack moves, view re-render on restore, forward-stack cleared by a fresh navigation). Both confirmed to fail against the v2.37 snapshot.
- What didn't change. The xlsx source data; every drug row;
contraindication_rules; the consult / contraindication banners; dose-calculation paths; build/dataset.json structure; governance baselines; the on-screen back buttons (still work exactly as before — the gesture is additive).
v2.37
RUNTIME
Consult-warning hardening — route-list and dose-detail can no longer diverge
- RUNTIME-class release · preventive hardening. No clinical-data changes. No schema changes. No dose-calculation changes. No drug is currently mis-displaying — v2.36 fixed the only live case (Droperidol). This release closes the underlying bug class so it cannot reopen, and adds a build gate that proves it.
- Root of the v2.36 bug: two pages, two triggers. The route-list page flags a route bar CONSULT REQUIRED when the row
scope_note matches /consult\s*required/i. The dose-detail #ddAlarm banner used a different, narrower test ("in all situations" substring after v2.36; _safety_fail-only before it). The two could silently diverge — a row could warn on the route list but not on the dose-detail page.
- Single shared trigger.
_renderAlarm() now uses the exact same /consult\s*required/i test against scope_note that the route-list renderer uses. Any row whose route bar shows CONSULT REQUIRED now shows the same banner on its dose-detail page — by construction, not by coincidence of the current data. The banner sub-text still distinguishes "in all situations" rows (e.g. Droperidol ABD SAT ≥2) from ordinary consult rows. The dose card still renders below the banner.
- Forward-compatible with CCP scope. Eight CCP-scope rows carry a "Consult required"
scope_note (Adrenaline, Fentanyl, Midazolam, Morphine, Salbutamol) and are currently filtered out of the ACP2 runtime. With the shared trigger, the day CCP scope is switched on those rows will surface the consult banner on both pages automatically — no per-drug wiring needed.
- Build gate added in
build/test_bundle_rendering.py: (1) a source-consistency check that the consult scope_note regex appears in exactly the two expected sites — route-list renderer and _renderAlarm() — so the build fails if they ever drift apart; (2) a data-driven sweep that runs every distinct scope_note in the shipped dataset — CCP rows included — through _renderAlarm() under a stubbed DOM and asserts the banner shows if and only if the route-list pattern matches; (3) expanded fixed behavioural cases pinning the _safety_fail paths. Confirmed the gate fails against the v2.36 snapshot and passes here.
- What didn't change. The xlsx source data; every drug row;
contraindication_rules; the route-list page; dose-calculation paths; build/dataset.json structure; governance baselines. The dormant legacy #ddConsultBanner element is untouched — #ddAlarm remains the single consult UI.
v2.36
SAFETY
Droperidol ABD — dose-detail page now shows the mandatory consult warning
- SAFETY-class release · dose-detail consult warning restored for Droperidol. No clinical-data changes. No schema changes. No dose-calculation changes. One renderer fix to the dose-detail alarm banner.
- The bug. For Droperidol · Acute behavioural disturbance SAT ≥2, the route-list page correctly flagged both IV and IM route bars CONSULT REQUIRED, but tapping through to the dose-detail page showed the calculated dose (e.g. 2.8–5.6 mg at 28 kg) with no consult warning at all. A paramedic landing on the detail page saw what looked like an autonomous dose. Per
DTP_DRO_0326, both routes for this indication state: "QAS Clinical Consultation and Advice Line consultation and approval required in all situations." — consult and approval are mandatory before administration in every scenario.
- Root cause. Droperidol ABD SAT ≥2 produces a real calculated dose line — not a safety-fail — that carries
scope_note: "Consult required in all situations". The legacy #ddConsultBanner element used to surface exactly this state. v2.17 (Stage 3 Ship 5) introduced the unified #ddAlarm banner and hid #ddConsultBanner with display:none on the assumption the alarm covered "both states" — but _renderAlarm() bailed early on any line without _safety_fail. The scope-note-on-a-real-dose-row case fell through the gap: route-list flagged it, dose-detail did not.
- The fix.
_renderAlarm() now also fires the amber CONSULT REQUIRED banner for a normal dose line whose row scope_note contains "consult required in all situations". The dose card still renders below the banner — the paramedic needs the figure ready for once approval is granted. The existing _safety_fail behaviour (consult_only / out_of_band / contraindicated) is unchanged. Scoped precisely: in the ACP2 runtime the only rows carrying this scope_note are Droperidol ABD SAT ≥2 IV + IM; every other "Consult required" row is either CCP-filtered or already a safety-fail line the alarm covered.
- Regression tests added to
build/test_bundle_rendering.py: two probe scenarios assert the Droperidol ABD IV + IM lines are non-safety-fail dose lines that retain the consult-in-all-situations scope_note; a DOM-stub harness directly exercises _renderAlarm() across four line shapes (consult-always normal dose → banner shows; plain dose → hidden; consult_only safety-fail → banner shows; contraindicated → suppressed). Confirmed the harness fails against the v2.35 snapshot and passes against this build.
- Verification. Full safety pipeline PASS. Behaviour verified pre/post: v2.35 dose-detail page for Droperidol ABD SAT ≥2 showed the bare dose; v2.36 shows the CONSULT REQUIRED alarm above the dose card on both IV and IM.
- What didn't change. The xlsx source data; every drug row;
contraindication_rules; the route-list page (which already flagged this correctly); dose-calculation paths; build/dataset.json structure; governance baselines. The legacy #ddConsultBanner element and its old _renderConsultBanner function remain in place but dormant — #ddAlarm is the single consult UI.
v2.35
DATA
Dexamethasone Croup — age-band consult thresholds wired to DTP_CAG_0326; generic consult banner reworded
- DATA-class release · one Verified-drug row corrected, one banner reworded. No schema changes. No dose-calculation changes. No runtime decision-path changes. Every dose, every contraindication rule, and every governance gate (G1–G5) is unchanged from v2.34. What ships is a clinical-data correction on the Dexamethasone Croup row plus a wording fix on a fallback banner.
- Dexamethasone Croup PO — age-band consult thresholds now carried on the row. DTP_CAG_0326 requires consultation for Croup patients less than 6 months OR greater than 8 years of age. The Croup PO row previously held only the bare autonomous-dosing band (
6 months to 8 years) with no consult metadata, so an out-of-band Croup patient fell through to the generic "outside the QAS-approved age band" fallback banner instead of a DTP-anchored consult message. age_weight_criteria is now 6 months to 8 years; <6 months consult; ≥9 years consult, which the parser resolves to consult_required_below_months: 6 and consult_required_above_months: 108 (108 months = the end of the 8-year-old year, the upper edge of the autonomous band). A under-6-month or 9-years-plus Croup patient now sees the specific amber Consult required route tag — "Consult required for patients under 6 months" / "Consult required for patients 9 years or older" — handled by the existing v1.58 OUT_OF_BAND → CONSULT_ONLY path. The autonomous-dosing band (6 months .. 8 years) is unchanged.
- Fallback consult banner reworded. The empty-state banner that fires when every route for an indication is out-of-age-band no longer appends "Contact the QAS Clinical Consultation and Advice Line." — it now reads "Consult required — patient is outside the QAS-approved age band for every route on this indication." and stops there, consistent with the consult-messaging convention adopted at the Ship 5 review.
- Regression test R36 added to
build/test_regressions.py: a negative-assertion drift guard asserting the Dexamethasone Croup PO row carries consult_required_below_months=6, consult_required_above_months=108, consult_required_flag=true, and that the autonomous-dosing band stays [6, 108] months. Protects against a future xlsx edit silently reverting the row to a bare band.
- Verification. Full safety pipeline PASS. All regression tests PASS including the new R36. Parser confirmed: the updated criteria string yields the band
6 .. 108 months unchanged plus the two consult thresholds. The stale build_status: 🚨 FIX NEEDED annotation on the Croup row (a parser-pattern note that predated the "X to Y" range pattern landing) was cleared to OK in the same edit — annotation-only, not read by the build.
- What didn't change.
app_drug_info; contraindication_rules; the Dexamethasone Asthma row; every other drug row; calculation paths; build/dataset.json structure; governance baselines; the renderer's layout, view-switching, or screen flow.
v2.34
UI
Stage 1 design-system foundations — Inter inlined, token system, .icon infrastructure
- UI-class release · foundations only. No clinical-data changes. No schema changes. No runtime calculation changes. No contraindication / consult-required logic changes. The 18-drug Verified set, every dose, every contraindication rule, every regression test, and every governance gate (G1–G5) is byte-equivalent to v1.75. What ships is the design-system foundation that Stage 2+ will build on.
- Inter SemiBold inlined as base64 WOFF2. The Latin subset (22.8 KB binary · 30 KB base64) is now embedded directly in
src/app.html via @font-face with a data: URI. Offline-first: paramedic devices render the chosen typeface even without network connectivity. SHA-256 of the font binary is recorded in the marker comment for drift detection. Source: @fontsource/inter@5.0.16 weight 600 latin.
- Approved design-system tokens landed in
:root. 50+ CSS variables: --colour-forest-*, --colour-mint-*, --colour-on-dark-*, --colour-status-contra-* (dark red alarm), --colour-status-consult-* (dark orange alarm), --colour-status-info-*, --colour-status-verified-*, --colour-light-* bridges (cream theme mapped into the token namespace), --type-display/h1/h2/body/caption/kicker, --space-xs/sm/md/lg/xl/2xl/3xl, --radius-sm/md/lg/pill, --shadow-1/2/3, and --font-stack. Additive only — every legacy alias above the new block is preserved so the existing cream/teal renderer paints identically.
body font-family rewired to var(--font-stack). The body declaration now reads font-family: var(--font-stack) which resolves to "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif. Inter renders when the inlined WOFF2 has loaded; before that, the fallback chain delivers a near-identical visual (system sans on iOS/macOS, Roboto on Android).
- Primitive rewires (token consumption). 56 literal CSS values have been swapped to token references where the mapping is byte-equivalent:
border-radius: 999px → var(--radius-pill) (16×), border-radius: 16px → var(--radius-lg) (9×), border-radius: 12px → var(--radius-md) (20×), border-radius: 8px → var(--radius-sm) (9×), box-shadow: 0 4px 14px rgba(0,0,0,0.18) → var(--shadow-2) (2×). Every visual primitive (header pills, nav buttons, route bars, Details card, Resus card stack, vitals pill, prep card, dose card) now consumes radius/shadow tokens. Stage 2+ will progressively swap colour tokens once the theme migration begins.
.icon base class added. Outline-first inline-SVG primitive per the approved iconography contract: viewBox 0 0 24 24, stroke 1.75, round caps/joins, fill: none, colour inherits via currentColor. Variants: .icon.small (18 px), default (24 px), .icon.large (28 px), .icon.filled-glyph (filled active state), .icon .dot (filled inner dot). No icons are rendered by the runtime yet — the class is infrastructure ready for Stage 2+ to consume.
- JS
_icon(name, opts) helper added. A global window._icon function that takes one of 18 approved icon names and returns the inline <svg> markup. The 18 paths are stored on window._ICON_PATHS with viewBox 0 0 24 24 path data exactly as authored in mockups/redesign_iconography.html: home, drugs, resus, airway (BVM silhouette), defib (lightning bolt), vitals, search, bookmark, clock, settings (slider panel), help, warning, contra (slashed circle), consult (person + speech bubble), drop, minus, plus, timer, tools (open-end spanner). Helper is unused by the renderer; Stage 2+ will progressively replace Unicode glyphs (⚠ ⛔ 🗣) with _icon() calls.
- What you'll see on the live deployment. Virtually nothing. The cream-teal field-teal theme paints exactly as it did in v1.75. The most discriminating eyes will notice subtly different letter forms on text under bold weights (Inter SemiBold rendering instead of
-apple-system 600) — particularly on small all-caps labels (route pills, kickers). Layout, spacing, colours, contraindication banners, dose maths, route bars, header bars all unchanged. Pinch-to-zoom and Vitals overlays unchanged.
- Architecture. Implementation: one idempotent Python installer (
build/_stage1_install.py) performs five surgical inserts into src/app.html using marker-comment anchors. A second script (build/_stage1_rewire.py) does the 56 literal→token CSS rewires inside the <style> region only. Both scripts re-run safely as no-ops once their markers exist. No patcher chain; the markers are stable anchors for future Stage 2 inserts.
- Verification. 12/12 safety pipeline steps PASS. G4 PASS (all historical snapshots untouched, recorded SHAs match). G5 PASS (index.html matches the Stage 1 snapshot after promote). 34 regression tests PASS. Determinism check: rebuilding the Stage 1 candidate produces a byte-identical SHA on re-invocation (
build_snapshot.py --target N twice → same SHA).
- What didn't change.
xlsx source data; app_drug_info; contraindication_rules; app_resus_* sheets; calculation paths; build/dataset.json structure; audit_manifest.json fields; governance baselines; the renderer's layout, view-switching, or screen flow. No new screens, no rearranged sections, no new nav, no new buttons.
- What Stage 2 will touch. Home + bottom-nav reskin to the forest theme using the now-available tokens. Top-of-app icon swaps to the inline SVG set. Drug-detail screen will retain its current information architecture; only chrome migrates onto the new tokens. Approval gate required before any Stage 2 commits.
v1.75
UI
Compact route-badge labels — fixes overflow on long age contraindications
- UI-class release. No clinical-data changes. No calculation changes. No contraindication / consult-required behaviour changes. The age thresholds, rule types, and full-form Contraindications wording are all preserved exactly. The change is purely visual: a long age-based contraindication label no longer overflows the red route badge on the drug-detail page.
- The fix. Added an optional
label_short field to contraindication_rules in app_drug_info. When the renderer paints a route badge that the patient triggers (red Contraindicated · … tag), it now prefers label_short over the long label. The full Contraindications card on the drug page still shows the descriptive medical wording verbatim.
- Seven rules updated with compact short forms: Paracetamol
<1 mo, Salbutamol <1 yr, Methoxyflurane <1 yr, Ipratropium bromide <1 yr, Ondansetron <2 yr, Droperidol <8 yr, Naloxone Newly born (QAS-specific wording preserved instead of an auto-derived <1 mo).
- Renderer change (single line).
_contraReason = _rule.label_short || _rule.label || _rule.reason || "". Applied to both age_min_months and age_max_months branches. Drugs without a label_short fall back to the existing label — fully backwards compatible.
- R35 regression test added. Three assertions per drug: (1)
label_short is present and matches the expected compact form; (2) the long label still contains the descriptive medical wording (guards against someone "fixing" overflow by shortening the wrong field); (3) the full Contraindications bullet list still contains the long-form sentence. Negative-assertion guards baked in.
- Architecture used. Direct edit to the xlsx (7 cell JSON updates), the renderer (1 line in
src/app.html), and test_regressions.py (R35). No patcher scripts; no schema migration (the new field is additive and optional).
- Verification. 12/12 safety pipeline steps PASS. G4 + G5 PASS. 34 regression tests now PASS (R1–R32 + R34 + R35). All other Verified drugs unaffected.
v1.74
UI
Resus pages redesigned — decision-first card stack, quiet collapsibles
- UI-class release. No clinical-data changes. No schema changes. No runtime calculation changes. No contraindication / consult-required logic changes. Every shock energy, pad threshold, OPA size, i-gel band, CPR ratio, and Adrenaline dose still comes from the existing
QAS_DATASET.resus.* sheets unchanged.
- Decision-first card stack. Every Resus page now leads with one or two big white answer cards — the single decision the paramedic needs to make at that moment in the arrest. Reference content (calculation working, charts, tips, workflow) tucks into quiet collapsibles that recede into the forest background. The big answer cards keep the visual hierarchy.
- Defib & Pads. First card: PAEDIATRIC PADS · Anterior – Posterior (or ADULT PADS · Anterior – Lateral for ≥25 kg). Second card: the J number, huge (e.g. 65 J for 16 kg manual mode). UNSYNC/SYNC mode pills removed (cardioversion is not a resus-mode decision). Collapsed below: Show calculation, Stacked shocks · witnessed monitored arrest, Alternatives & AED mode, Quick notes. Stacked-shocks is ACP2 scope and explains when 3 stacked shocks before CPR is appropriate (witnessed + monitored only).
- CPR. First card: 2 HANDS · Sternum, lower half for child ≥12 mo (2 THUMBS · Encircling for infant / newborn). Second card: 15 : 2 ratio with 30 : 2 single paramedic as the secondary. “rescuer” terminology replaced with “paramedic” throughout. Compression mechanics, rhythm workflow, and ETCO₂ / quality are now quiet collapsibles.
- Airway. OPA above i-gel (matches the actual workflow — OPA is the initial adjunct during BVM; i-gel is the escalation step). First card: OPA 6 cm · Black with the colour swatch beside the size and the measurement instruction inline. Second card: I-GEL SIZE 2 · OG tube 12 FG with the device name on the same line as the size. Size charts and airway tips collapsed.
- Adrenaline. Mirrors the live drug-detail page layout exactly. Forest background. Mint kicker Cardiac arrest, drug name big white, blue IV/IO pill, white dose card with Initial dose kicker / big black microg value / dashed divider / draw-up line. Forest-outline banners for Repeat every 3 – 5 minutes and Total max · No max dose. Below that, Resus-specific quiet collapsibles: Timing (shockable after 2nd shock; non-shockable as soon as IV/IO access), Administration (IV/IO only, flush, run-in, track count), Ampoule trap · 1:10,000 vs 1:1,000, After ROSC.
- Home button label. The CALCULATE button now reads OPEN RESUS → when the segmented mode toggle is on the Resus side. Same destination as before; clearer affordance.
- Architecture used. First non-trivial UI change to ship using the v1.72 renderer-source architecture. Implementation was a direct edit of the four resus renderer functions in
src/app.html plus an additive CSS block. No patcher scripts. Diff was reviewable; the build pipeline ran a candidate through build_snapshot.py as normal.
- What didn't change. Drug-detail pages, home page (other than the button label), dose-calculator math, contraindication rules, consult-required runtime logic, governance gates (G1–G5), CI workflow, audit baseline.
app_resus_* sheets in the xlsx unchanged; app_drug_info unchanged.
v1.72
GOVERNANCE
Renderer source separated from snapshot — src/ as editable truth
- GOVERNANCE-class architecture refactor. No clinical-data changes. No schema changes. No runtime calculation changes. No contraindication/consult-required logic changes. The build pipeline that produces snapshots is rearranged; what the user sees on a paediatric arrest is byte-equivalent to v1.71 (modulo this changelog entry, the v1.71 demotion from "current", the version label, and build-metadata fields that already vary per build).
- src/ is now the editable renderer source. Pre-v1.72 build pattern was "swap the dataset into the highest existing snapshot, then run one-off patcher scripts to add UI changes". Post-v1.72:
src/app.html holds the complete renderer (markup + inline CSS + inline JS) with two HTML-comment placeholders for the dataset and the version label. Future UI changes are direct edits to src/app.html reviewed via normal diff.
- New build script
build/build_snapshot.py replaces swap_dataset_into_snapshot.py in the pipeline. Reads src/app.html, substitutes the two placeholders, writes the candidate. Deterministic at the substitution layer; non-determinism (timestamp, git commit) lives inside dataset.js and is handled by canonical-content hashing in the audit manifest (the fix landed in ci: hash canonical dataset.json content in audit manifest at v1.69).
- Two one-off patchers retired.
build/apply_v1_70_changes.py and build/apply_v1_71_changes.py deleted. Their full content (the pill CSS, retro classification tags on v1.55–v1.69 entries, the v1.70 + v1.71 changelog entries, the Resus card-stack CSS, the 4-chip strip, the 4 renderer functions) is now permanently baked into src/app.html.
- Migration equivalence proven. At extraction time, running
build_snapshot.py --target 171 with the dataset.js that produced V1.71 yielded byte-identical output to snapshots/QAS_Ped_DrugV1.71.html (SHA 182cc23a24f3…). The src/ extraction preserves every rendered byte of v1.71. Subsequent runs against a re-built dataset.js produce semantically equivalent output: same drugs, same doses, same contraindication rules, same calc paths — only the dataset.js build-metadata fields differ (timestamp, git_commit), which is by design.
- Pipeline impact.
run_all_tests.py step count returns to 12 (was 14 with the two patchers). Step 7 renamed from swap_dataset to build_snapshot. The two patcher steps (7b, 7c) removed entirely. All other steps unchanged. G4/G5 snapshot-immutability gates honoured — every prior snapshot remains sealed with its original SHA.
- Snapshot immutability honoured. No historical snapshot was modified.
build/snapshot_hashes.json remains intact for all 170 prior entries; V1.72 is appended on promote like any other release.
- Governance artefacts auto-regenerate. Per-drug audit data, contraindication rules, regression-test inventory, and source-PDF SHA-256s all unchanged.
generated_for_snapshot updates to "v1.72" as expected.
- What this enables. Future UI redesigns (CPR/resus iteration, dose-detail tweaks, home-screen layouts) become regular edits to
src/app.html. No more per-version Python scripts threading together CSS additions, string replacements, and HTML insertions.
- Out of scope for this release: splitting
src/app.html into separate src/app.css / src/app.js / per-release changelog files; xlsx→YAML data migration (proposals/03 stage 1); signed release tags (proposals/01); provenance chain (proposals/02). v1.72 is the minimum-viable source separation — one source file, two placeholders, byte-stable substitution layer.
v1.71
UI
Resus page redesign — 4 chips, card-stack layout
- UI-class release. No clinical-data changes. No schema changes. No runtime calculation changes. Every shock energy, pad threshold, OPA size, i-gel band, CPR ratio, and Adrenaline dose still comes from the existing
QAS_DATASET.resus.* sheets unchanged.
- Resus chip strip consolidated from 7 chips to 4. "Pads" folded into "Defib" (now Defibrillation & Pads — energy + pad selection + placement on one page). "i-gel" and "OPA" merged into "Airway" (OPA sub-card + i-gel sub-card). "4 H / 4 T" page removed from the deck. CPR and Adrenaline retain their own chips. Result: no horizontal scroll on the chip strip and a tighter logical grouping.
- New unified visual vocabulary across all 4 pages, built from these primitives:
info-card (white card with teal all-caps header), rec-block (label · big-number · sub-text — colour-coded amber/purple/blue/red by page), kv-row (label/value pairs with dashed dividers), scenario (named conditional block with optional CURRENT-PATIENT highlight), sub-block (teal-tinted instruction callout), reminder-pill (amber inline reminder), step-btn (secondary action), shock-row (3-cell defib energy grid), mode-bar (UNSYNC/SYNC dual-pill), tips-card (amber-themed quality-notes card at the bottom of every page). Old .headline / .ref-table / .check-list CSS retained but no longer emitted.
- Defib + Pads page. Amber
rec-block shows the J-for-this-patient up top (manual mode preferred when patient <6 yr; AED auto fallback noted explicitly). Three-cell shock-row makes the no-escalation pattern (4 J/kg × 3 shocks) visually obvious. mode-bar shows UNSYNC active (default for VF/pVT) with SYNC muted (cardioversion only) to prevent accidental SYNC selection on a non-shockable rhythm. Pad-selection rules below: <25 kg → paediatric pads, ≥25 kg → adult. Placement sub-block defaults to anterior-posterior for paeds <6 yr / <25 kg, with A-L fallback and the post-3-refractory vector-change rule called out.
- CPR page. Compressions card uses
kv-row stack (rate / depth / recoil / pauses / hands) with an amber reminder-pill for the 2-min compressor swap. Ventilation card swaps between newborn (3:1 regardless of rescuers) and ≥1 mo (15:2 / 30:2) scenarios, with the patient's current scenario marked. Rhythm Workflow card lists shockable vs non-shockable cycles + the pulse-check rule. ETCO₂ / Quality tips-card at the bottom.
- Airway page. OPA
rec-block (purple) and i-gel rec-block (blue) as separate cards, each with its own kv-row stack and source citation. OPA card shows the colour swatch beside the cm size; i-gel card has a "Cuff pressure: N/A · non-inflatable gel cuff" line because juniors occasionally try to inflate an i-gel cuff thinking it's an LMA. sub-block on the OPA card carries the verbatim measurement instruction.
- Adrenaline page. Red
rec-block shows the patient-specific microg dose (10 microg/kg × weight) with the draw-up volume embedded in the rec-sub line. kv-rows spell out Rule / Concentration / Volume / Interval / Single max for independent verification. sub-block "Administration" carries route / flush / timing-relative-to-shocks. step-btn deep-links to the full Adrenaline drug page (existing behaviour preserved). Safety Notes tips-card flags the 1:10,000-vs-1:1,000 ampoule trap.
- What didn't change. Every clinical value is still pulled from
QAS_DATASET.resus.{defib,cpr,igel,opa} at render time — no hard-coded ratios or J values in the renderer. _pickDefibManual / _pickDefibAuto / _pickCpr / _pickIgel / _pickOpa / _calcManualJ / _wRange / _esc helpers all unchanged. app_resus_causes sheet retained in the xlsx (4 H / 4 T data is still available for future reuse outside the Resus page).
- Two one-off patchers now stacked. Implementation via
build/apply_v1_71_changes.py (this release) layered on top of build/apply_v1_70_changes.py (immutability + pill-CSS retro-tag). Each is idempotent and deterministic. The patcher chain pattern was always meant to be temporary; with v1.71 it's clearly time to land the renderer-source-file refactor (proposals/03) before v1.72 adds a third one.
v1.70
GOVERNANCE
Snapshot immutability + release classification
- GOVERNANCE-class release. Builds on v1.69's CI+drift gate. No clinical-data changes. No schema changes. No runtime calculation changes. Two additions: snapshot-immutability protection (G4/G5) and a release-classification labelling scheme on the changelog.
- Snapshot immutability gate via
build/test_snapshot_integrity.py, wired into run_all_tests.py as step 5c. Two checks: G4 BLOCK every snapshot listed in build/snapshot_hashes.json must hash to its recorded SHA-256 — modifying or deleting a historical snapshot fails the build. G5 BLOCK index.html must be byte-identical to the snapshot pointed at by build/last_passing_snapshot.txt — catches hand-edits to index.html and any half-promoted state.
- Auto-append to the immutability baseline. After a successful promote,
run_all_tests.py hashes the just-promoted snapshot and appends the entry to snapshot_hashes.json. The floor only ever rises — existing entries are never edited. A re-promote of the same version surfaces a stderr warning rather than silently rewriting.
- Release classification pills. Every changelog entry now carries one of five labels: SAFETY clinical-safety bug fixes or contraindication-rule changes; GOVERNANCE docs / audit-baseline / CI work; DATA-ONLY dataset verifications or metadata flips; RUNTIME renderer / JS-logic changes; UI visual / cosmetic only. v1.55–v1.69 entries have been retro-tagged below to anchor the convention.
- GOVERNANCE.md extended with a "Snapshot immutability policy" section (G4/G5 semantics, roll-back via
promote_to_index.py --version <N>, what is forbidden, when the rare historical-snapshot removal exception applies). CI-policy section updated to list the 12-step safety pipeline and the new G4/G5 failure modes.
- CHANGE_CLASSIFICATION.md extended with the release-class taxonomy and worked examples for v1.57 (SAFETY), v1.61 (DATA-ONLY), v1.68 (GOVERNANCE), v1.70 (GOVERNANCE).
- What v1.70 catches: hand-edits to
snapshots/QAS_Ped_DrugV<N>.html after promotion; accidental overwrites of historical snapshots; index.html drifting out of sync with its declared source snapshot; promote runs left in a half-finished state.
- Out of scope (deferred to proposal review): signed releases / signed git tags; provenance / build-hash chains; spreadsheet→YAML normalised source-model migration; renderer-source file separation. v1.70 is the last build before that proposal phase.
v1.69
GOVERNANCE
CI + automated drift detection — clinical-safety guard rails
- GOVERNANCE-class release. Builds on v1.68's audit-hardening baseline. No clinical-data changes. No schema changes. No runtime-behaviour changes. Adds CI and drift-detection guards so the v1.67-audit-baseline state stays trustworthy as the project evolves.
- GitHub Actions safety pipeline at
.github/workflows/safety-pipeline.yml. Runs on every push to main and every pull request: full safety pipeline (10 steps) + governance drift gate (3 checks) + governance-artefact regeneration + a git diff --exit-code that fails CI if any committed artefact is out of sync with the current dataset. CI never auto-promotes a new snapshot — promotion remains a local maintainer action.
- Automated drift detection via the new
build/test_governance.py. Compares the current dataset against the frozen build/audit_baseline.json (created from the v1.68 manifest as the floor). Three gates: G1 BLOCK a Verified drug must not regress to Draft; G2 BLOCK a contraindication_rule (matched by type+value) once present at baseline must remain; G3 WARN a source-PDF SHA-256 differs from baseline AND last_reviewed hasn't been bumped — flag for paramedic re-review without blocking the build.
- Pipeline integration.
build/run_all_tests.py now runs test_governance.py as step 5b (between regressions and simulate_patients), giving 11 safety steps total. Local builds catch governance violations before promote; CI catches them before merge.
- GOVERNANCE.md extended with three new sections: CI policy (what the workflow does, what it catches, no-skip rules); governance drift policy (G1/G2/G3 semantics + when and how to regenerate
audit_baseline.json); expected workflow for future clinical-data edits (the 12-step sequence from classify → approve → backup → apply → test → behavioural-verify → promote → atomic-commit → push → CI-verify).
- What CI catches: edits to the xlsx that didn't regenerate
VERIFIED_DRUGS.md / manifests; pyc or whitespace drift in committed artefacts; broken safety-pipeline steps; silent Verified→Draft regressions; silent contraindication_rule removals; PDF SHA changes without last_reviewed updates.
- Out of scope (deferred to future proposals): release signing / signed git tags, provenance / build-hash chains, spreadsheet→YAML normalised-model migration, runtime features. v1.69 is strictly belt-and-braces guard rails for the audit baseline.
v1.68
GOVERNANCE
Governance baseline — docs + auto-generated audit artefacts
- Governance baseline release. v1.67 closed the drug_info audit (all 18 in-scope drugs Verified). v1.68 layers on documentation and tooling to preserve that work against future drift — no clinical-data, schema, or runtime-behaviour changes. The v1.67 commit will be tagged
v1.67-audit-baseline as the immutable reference point.
- Two policy docs at the repo root:
GOVERNANCE.md (source-of-truth hierarchy, re-audit cadence, drift detection, sensitive-area MAJOR-classification rules) and CHANGE_CLASSIFICATION.md (PATCH / MINOR / MAJOR / SAFETY / GOVERNANCE taxonomy with worked examples from v1.58, v1.60, v1.66, and this v1.68 release).
- Four auto-generated artefacts regenerated by every
run_all_tests.py pass:
VERIFIED_DRUGS.md — per-drug summary, contraindications, precautions, structured rules, regression-test coverage
build/audit_manifest.json — machine-readable manifest with source-PDF SHA-256 hashes
build/regression_inventory.md — catalogue of every regression test, parsed from test_regressions.py
source-pdfs/MANIFEST.md — SHA-256 of every source PDF for drift detection
- Four generator scripts in
build/ (generate_pdf_manifest.py, generate_regression_inventory.py, generate_audit_manifest.py, generate_verified_drugs_md.py). All deterministic — re-running against the same dataset produces byte-identical output. Wired into run_all_tests.py to run after every successful build, so the artefacts always reflect the just-promoted snapshot.
- Drift-detection foundation. The audit manifest captures source-PDF SHA-256 hashes alongside each drug's
last_reviewed date. Future re-audits compare manifest hashes between commits to surface clinical-data drift instantly. Reviewers can git diff v1.67-audit-baseline -- build/audit_manifest.json to see exactly what audit-state field changed between any commit and the closeout baseline.
- No behavioural changes. Bit-identical UI for every patient + drug combination. All 33 regressions (R1–R32 + R34) green. 10/10 safety pipeline green.
app_drug_info and app_table unchanged.
v1.67
SAFETY
drug_info audit complete — all 18 in-scope drugs Verified
- Audit closeout — all 18 in-scope drugs now Verified against their source DTPs. drug_info audit complete. The 10-release audit sequence that began with Ondansetron in v1.58 reaches its end here. Every drug in the paediatric ACP2 scope now carries
validation_status = "Verified against DTP_xxx_..." with the audit footer on each drug page citing the manual paramedic review.
- Dexamethasone drug_info verified verbatim against DTP_CAG_0326. Manual paramedic review of
source-pdfs/DTP_Dexamethosone.pdf confirmed the existing entry is accurate: 6 indication-prefixed absolute contraindications (Asthma + Croup variants with consultation thresholds), 0 relative contras, 1 precaution (Nil in this setting). No clinical-data changes. The age-based consultation thresholds are correctly enforced at the dose-row level via consult_required_below_months (existing R5 + R7 + v1.58 runtime path); no drug-level contraindication_rule needed because Asthma and Croup have different age windows.
- Glucagon drug_info verified verbatim against DTP_GLC_0825 with one cosmetic case-fix. DTP review confirmed: 1 absolute contraindication, 0 relative contras, 1 precaution ("Nil"). The contra entry was refreshed from "Allergy AND/OR adverse drug reaction" (lowercase) to "Allergy AND/OR Adverse Drug Reaction" (Title Case) for visual consistency with every other Verified drug's ADR entry. Same rendering; cosmetic only.
- Oxygen drug_info — 2 missing DTP precautions added + 1 wording typo fixed. Per DTP_OXYG_1024 the precautions block has 6 entries; pre-v1.67 the dataset had 4 (missing the BVM/IPPV note and the high-flow-false-reassurance warning) and contained an "ECPG" typo in the newly-born reference (DTP uses "CPG: Resuscitation – Newly born"; "ECPG" is not a QAS term). v1.67 restores the verbatim DTP list: Paraquat/bleomycin · Premature neonates · Newly born SpO₂ behaviour (refer to CPG) · Cyanotic heart disease saturation targets · A BVM will not supply adequate oxygen unless IPPV is provided · High flow oxygen as protection against potential deterioration may delay recognition. Contraindications, indications, and dose rows unchanged.
- Regression tests R32 + R34 added. R32 asserts the 6 verbatim DTP precautions are present on Oxygen and the "ECPG" typo is gone (negative assertion); R34 asserts Glucagon's contra is in Title Case (negative assertion against drift back to lowercase). The full regression family is now R1–R32 + R34 (R33 was not allocated; kept reserved for a future Dexamethasone-specific drift guard if needed).
- No schema, runtime, or test-infrastructure changes. All other drugs unaffected. Dose math, consult-required paths, and contraindication overlays bit-identical to v1.66 for every patient/drug combination.
- Audit progress: 18 of 18 in-scope drugs Verified. 0 Drafts remain. The
app_drug_info sheet's validation_status column now reads "Verified against DTP_..." for every row. Test results: 10/10 safety steps pass; 33 regressions (R1–R32 + R34) pass.
v1.66
SAFETY
Naloxone "newly born" contraindication now enforced at runtime
- Clinical-safety fix — newly-born patients now correctly see RED Contraindicated on Naloxone. Pre-v1.66 the Naloxone drug-info Reference accordion listed "The newly born patient" as an absolute contraindication (verbatim per DTP_NAL_0924) — but the runtime had no structured rule to enforce it. A paramedic selecting the Newborn age band saw an autonomous Naloxone dose (e.g. 100 microg IM for < 5 kg) presented as a normal route. Per the DTP Special Notes: "Naloxone should not be administered to the newly born, even in the setting of suspected or identified opiate exposure/overdose, as administration of naloxone may lead to acute withdrawal and seizures."
- v1.66 adds a structured
contraindication_rule with type:age_min_months, value:1, label:"The newly born patient". Newborn patients (age_months=0) now trigger the RED Contraindicated overlay on every Naloxone route — same pattern and severity tier as Ondansetron <2yr (v1.58), Ipratropium <1yr (v1.60), Paracetamol <1mo, Methoxyflurane/Salbutamol <1yr, Droperidol <8yr.
- Behaviour preserved for ≥1-month patients. The under-1-month threshold is the cleanest mapping from the DTP's "newly born" phrasing to the app's age dropdown (the Newborn band sits below 1 month; every other paediatric/adult band is ≥1 month). A 1-month-old or older patient still sees normal Naloxone route bars and autonomous dosing per the weight-band dose table.
- Contraindication text unchanged — the Reference accordion still shows the same 2 verbatim DTP items (Allergy AND/OR Adverse Drug Reaction; The newly born patient). The schema change is purely the addition of the structured rule alongside the text.
- Regression tests R29–R31 added: R29 asserts the 2 verbatim DTP contras; R30 asserts the
age_min_months: 1 rule is wired with verbose label; R31 confirms the rule threshold causes the Newborn band to trigger contra and the 1-month band to pass — negative-assertion paired guard against any drift that would re-introduce the pre-v1.66 autonomous-dose-to-newborn bug.
- No schema, runtime, or test-infrastructure changes outside the generic existing
contraindication_rules machinery (v1.39+). Ondansetron, Ipratropium, Methoxyflurane, Salbutamol, Paracetamol, Droperidol contraindication behaviour bit-identical to v1.65. validation_status promoted from "Draft – needs paramedic review" to "Verified against DTP_NAL_0924".
- Audit progress checkpoint: 15 of 18 in-scope drugs Verified. 3 Drafts remain (Dexamethasone, Glucagon, Oxygen) — closeout planned in v1.67.
v1.65
DATA-ONLY
Salbutamol + Methoxyflurane verified · contra-rule labels refreshed
- Salbutamol drug_info verified verbatim against DTP_SAL_0825. Manual paramedic review of
source-pdfs/DTP_Salbutamol.pdf confirmed the existing entry is accurate: 2 absolute contraindications (Allergy AND/OR Adverse Drug Reaction; Patients less than 1 year of age); no relative contras; 2 precautions (Acute pulmonary oedema; Ischaemic heart disease). No clinical-data changes — validation_status promoted from "Draft – needs paramedic review" to "Verified against DTP_SAL_0825".
- Methoxyflurane drug_info verified verbatim against DTP_METH_1025. Manual paramedic review of
source-pdfs/DTP_Methoxyflurane.pdf confirmed the existing entry is accurate: 4 absolute contraindications (Allergy AND/OR Adverse Drug Reaction; Patients less than 1 year of age; Known liver OR renal disease; Malignant hyperthermia (known or genetic susceptibility)); no relative contras; 4 precautions (Altered level of consciousness; Intoxicated or drug affected patients; Cardiac instability; Respiratory depression). No clinical-data changes — validation_status promoted to "Verified against DTP_METH_1025".
- Cosmetic label refresh on both drugs. The under-1-year
contraindication_rule.label on both Salbutamol and Methoxyflurane has been refreshed from terse "<1 year" to verbose DTP-verbatim "Patients less than 1 year of age". Route-bar Contraindicated tag now reads consistently with Ondansetron (<2yr), Ipratropium (<1yr), Paracetamol (<1mo), Droperidol (<8yr). Same rule type/value/reason; only the display label changes. Behaviour for under-1-year-old patients on Salbutamol and Methoxyflurane is bit-identical: still RED Contraindicated overlay, no dose/prep/calc cards.
- Regression tests R27 and R28 added as negative-assertion drift guards on the verbose-label invariant — same shape as R24 (Droperidol). With v1.65 the label-consistency invariant is now enforced across every drug with an
age_min_months rule: Ondansetron, Ipratropium, Paracetamol, Droperidol, Salbutamol, Methoxyflurane.
- No schema, runtime, or test-infrastructure changes. All other drugs unaffected.
- Audit progress checkpoint: 14 of 18 in-scope drugs now Verified. 4 remain Draft — Dexamethasone, Glucagon, Naloxone, Oxygen.
v1.64
SAFETY
Morphine drug_info corrected — Head injury removed from contras, DTP precautions populated
- Morphine contraindications reduced from 4 items to 2, matching DTP_MOR_0722 verbatim. Per
source-pdfs/dtp_morphine.pdf the DTP's Contraindications block lists exactly two items: Allergy AND/OR Adverse Drug Reaction and Kidney disease (renal failure). Pre-v1.64 the dataset listed 4 — one was a wording mismatch (Allergy/Hypersensitivity to opioids), one was actually a DTP Precaution (Respiratory depression/failure), and one was a textbook entry not in the QAS DTP at all (Head injury/unconsciousness).
- Clinical-safety impact — HIGH severity, same class as the Fentanyl/Hypotension fix in v1.62. Pre-v1.64 a paramedic looking at the Morphine drug page would see Head injury/unconsciousness in the red Contraindications pills — implying "do not give". The DTP does not contraindicate Morphine in head injury; presenting it as an absolute contra could lead to withholding authorised analgesia from a head-injured patient.
- Precautions rewritten verbatim from the DTP's Precautions block (6 items). Pre-v1.64 the precautions column contained 5 items, of which 4 were not in the DTP at all (Hepatic disease, Respiratory disease, Shock, Head injury) and 1 was the legitimate Hypotension entry. v1.64 replaces the list with the 6 verbatim DTP precautions: Hypotension; Respiratory tract burns; Respiratory depression and/or failure; Known addiction to narcotics; Concurrent MAOI therapy; Cardiac chest pain. The Cardiac chest pain precaution reflects the DTP's preference for Fentanyl (not Morphine) in ACS.
- Wording fixes. Allergy entry expanded from "Allergy/Hypersensitivity to opioids" (abbreviated, textbook-style) to verbatim "Allergy AND/OR Adverse Drug Reaction". Respiratory depression entry expanded to "Respiratory depression and/or failure" (DTP-verbatim).
- Existing v1.57 Morphine <1 year consult-required behaviour preserved. No
contraindication_rule added — the DTP defines no age-based hard contra for Morphine. The under-1-year consult requirement is enforced at the dose-row level via consult_required_below_months: 12 set in v1.57 on the 3 Significant Pain rows; that path remains intact. A 4-month-old on Morphine Significant Pain still sees the amber ⚠ Consult required tag (NOT red, NOT autonomous dose), exactly as v1.57 intended.
- Regression tests R25 and R26 added: R25 asserts exactly the 2 verbatim DTP allergy + renal contras; no leak of any precaution or textbook item. R26 asserts the 6 verbatim DTP precautions; no non-DTP entry (Hepatic disease, Respiratory disease, Shock, Head injury) leaks through.
- No schema, runtime, or test-infrastructure changes. All other drugs unaffected.
validation_status promoted from "Draft – needs paramedic review" to "Verified against DTP_MOR_0722". The "Memory file partial — verify against source PDF before shipping" notes flag (honest pre-v1.64 warning) is now cleared.
v1.63
SAFETY
Adrenaline verified · Droperidol relative-contras reclassified · age rule label refreshed
- Adrenaline (epinephrine) drug_info verified verbatim against DTP_ADR_0825. Manual paramedic review of
source-pdfs/DTP_Adrenaline.pdf confirmed the existing entry is accurate: the DTP literally lists "Nil" as the only contraindication entry, no relative contras, and 4 precautions (Hypertension; Hypovolaemic shock; Concurrent MAOI therapy; Quetiapine toxicity). No data changes — validation_status promoted to "Verified against DTP_ADR_0825".
- Droperidol — 2 relative contras reclassified from the absolute list. Pre-v1.63 the dataset jammed Suspected sepsis and Patient being physically restrained and no CCP on scene into the absolute
contraindications list with a hand-typed "(relative)" suffix on each (same anti-pattern as Ceftriaxone before v1.60). Per DTP_DRO_0326 these two entries are explicitly classified as Relative Contraindications requiring QAS Clinical Consultation & Advice Line approval. v1.63 moves them to the relative_contraindications column verbatim (no suffix) and prunes the absolute list down to the 5 actual DTP absolute items.
- Droperidol — contraindication_rule label refreshed. The under-8-year rule (
age_min_months: 96) previously rendered on the route bar as "⚠ Contraindicated · <8 years". v1.63 refreshes the label to verbose DTP wording: "⚠ Contraindicated · Patients less than 8 years of age" — consistent with Ondansetron (<2yr), Ipratropium (<1yr), Paracetamol (<1mo). Same rule type/value/reason; only the display label changes.
- No schema, runtime, or test-infrastructure changes. Behaviour for under-8-year-old patients on Droperidol is bit-identical: route bars still render RED Contraindicated and suppress the dose, prep, and calculation cards — only the on-screen tag wording becomes verbose.
validation_status promoted to "Verified against DTP_DRO_0326".
- Regression tests R22–R24 added. R22 asserts exactly the 5 verbatim DTP absolute items in
contraindications, no "(relative)" suffix anywhere, and no sepsis/restraint leak from the relative section into the absolute list. R23 asserts the 2 verbatim relative contras live in relative_contraindications with no suffix. R24 is a negative assertion against label drift back to the terse "<8 years" form.
- Audit progress: 11 of 18 in-scope drugs now Verified (Ondansetron, Midazolam, Hydrocortisone, Ipratropium, Ceftriaxone, Glucose 10%, Glucose gel, Paracetamol, Fentanyl, Adrenaline, Droperidol). 7 remain Draft — Dexamethasone, Glucagon, Methoxyflurane, Morphine, Naloxone, Oxygen, Salbutamol.
v1.62
SAFETY
Fentanyl contraindications corrected — Hypotension is a Precaution, not a Contra
- Fentanyl contraindications reduced from 6 items to 1, matching DTP_FEN_0722 verbatim. Per
source-pdfs/dtp_fentanyl.pdf the DTP's Contraindications block lists exactly one entry: Allergy AND/OR Adverse Drug Reaction. Pre-v1.62 the dataset listed 6 items in the contraindications column; 5 of those (Hypotension, Respiratory tract burns, Respiratory depression and/or failure, Known addiction to narcotics, Current MAOI therapy) are actually DTP Precautions, not absolute contraindications.
- Clinical-safety impact. Pre-v1.62 a paramedic looking at the Fentanyl drug page would see Hypotension in the red Contraindications pills — implying "do not give". The DTP authorises Fentanyl in hypotensive adults at reduced incremental doses (≤25 microg IV / ≤50 microg IM) with CCP backup. Listing Hypotension as an absolute contra could lead to withholding authorised analgesia from a trauma patient with low BP. v1.62 corrects this by moving the entry to Precautions, where it sits alongside the other operational cautions.
- Precautions replaced with the verbatim DTP list of 5 items. Pre-v1.62 the precautions column contained 3 paraphrased sentences from the DTP's Special Notes section (hypotensive-dose protocol, CCP backup requirement). Special Notes is operational guidance, not classified as a clinical Precaution per the DTP. v1.62 replaces those paraphrases with the DTP's actual Precautions block: Hypotension; Respiratory tract burns; Respiratory depression and/or failure; Known addiction to narcotics; Current MAOI therapy.
- Wording fix. The single absolute contra entry expanded from terse "Allergy AND/OR ADR" to verbatim "Allergy AND/OR Adverse Drug Reaction" for consistency with every other drug's allergy entry.
- Regression tests R20 and R21 added: R20 asserts exactly the one DTP allergy item in contraindications, no leak of any of the 5 misclassified precaution items; R21 asserts the 5 verbatim DTP precautions are present and no Special-Notes-paraphrase entries leak through.
- No schema, runtime, or test-infrastructure changes. All other drugs unaffected.
contraindication_rules remains empty for Fentanyl — the DTP has no age- or weight-based hard contras. validation_status promoted from "Draft – needs paramedic review" to "Verified against DTP_FEN_0722".
v1.61
DATA-ONLY
Drug-info verifications — Glucose 10%, Glucose gel, Paracetamol confirmed
- Glucose 10% drug_info verified verbatim against DTP_GL10_0223. Manual paramedic review of
source-pdfs/dtp_glucose-10.pdf confirmed the existing entry is accurate: the DTP literally lists "Nil" as the only contraindication entry, no relative contras, and one precaution (Hyperglycaemia). No data changes — validation_status promoted from "Draft – needs paramedic review" to "Verified against DTP_GL10_0223".
- Glucose gel drug_info verified verbatim against DTP_GLG_0326. Manual paramedic review of
source-pdfs/DTP_Glucose_gel.pdf confirmed the existing entry is accurate: two absolute contraindications (Unconsciousness; Patients with difficulty swallowing); one precaution (Patients less than 2 years of age). Important nuance — Glucose gel's "<2 years" sits in Precautions, NOT Contraindications. Same phrasing as Ondansetron's absolute-contra entry, but classified at a different severity tier per its DTP; the data correctly preserves that distinction. No data changes — validation_status promoted to "Verified against DTP_GLG_0326".
- Paracetamol drug_info verified verbatim against DTP_PAR_1025 with one cosmetic label tweak. DTP review confirmed: two absolute contras (Allergy AND/OR Adverse Drug Reaction; Patients less than 1 month of age); one precaution (Hepatic dysfunction). The
contraindication_rule for under-1-month was already wired correctly (age_min_months: 1). Updated the rule's label from terse "<1 month" to verbatim DTP wording "Patients less than 1 month of age" so the route-bar tag reads consistently with Ondansetron ("Patients less than 2 years of age") and Ipratropium ("Patients less than 1 year of age"). Behaviour unchanged — under-1-month patients still see the RED Contraindicated overlay; only the on-screen wording on the route-bar tag becomes more verbose. validation_status promoted to "Verified against DTP_PAR_1025".
- No behavioural changes beyond the cosmetic Paracetamol label refresh. Dose math, prep flows, route bars, contra overlays, and consult banners are bit-identical to v1.60 for every other patient/drug combination. Audit and Validation footer on each drug page now reads "Verified…" instead of "Draft…".
- Audit progress: after the v1.58/v1.59/v1.60/v1.61 sequence, 6 of 18 in-scope drugs have been manually verified (Ondansetron, Midazolam, Hydrocortisone, Ipratropium, Ceftriaxone, Glucose 10%, Glucose gel, Paracetamol). 10 remain flagged "Draft – needs paramedic review" — Adrenaline, Dexamethasone, Droperidol, Fentanyl, Glucagon, Methoxyflurane, Morphine, Naloxone, Oxygen, Salbutamol.
v1.60
SAFETY
Ipratropium corrected · Ceftriaxone relative-contra reclassified
- Ipratropium bromide — clinical-data corrections against DTP_IPB_0722. Pre-v1.60 entry carried 3 contraindications and 3 precautions, two-thirds of which were textbook generic anticholinergic content (Narrow angle glaucoma, Prostatic hypertrophy/urinary retention, Prostatic disease, Bladder outlet obstruction) not listed in the QAS DTP. The DTP lists exactly two absolute contras: Allergy AND/OR Adverse Drug Reaction and Patients less than 1 year of age; and exactly one precaution: Glaucoma. xlsx
app_drug_info Ipratropium row replaced verbatim. validation_status promoted to "Verified against DTP_IPB_0722".
- Hard age contraindication for under-1-year-old patients. Added structured
contraindication_rule with type:age_min_months, value:12. Route bars now render RED ⚠ Contraindicated · Patients less than 1 year of age instead of silently hiding via the v1.52 out-of-age filter. Dose-detail page shows the red overlay and suppresses the dose/prep/calculation cards — identical pattern to Ondansetron <2yr.
- Ceftriaxone — relative-contra reclassification per DTP_CEF_0924. Pre-v1.60 entry jammed the under-1-month case into the absolute
contraindications list with a hand-typed "(relative)" suffix, violating the v1.58 "do not mix" rule. v1.60 moves the entry to the relative_contraindications column (verbatim DTP wording, no suffix). Absolute contras now show 2 outlined-teal pills; Relative Contraindications now shows 1 amber-tinted pill. Visual hierarchy matches the DTP's Absolute/Relative split.
- Ceftriaxone consult-required behaviour preserved. No new
contraindication_rule added — the DTP defines under-1-month as a RELATIVE contra requiring consultation, NOT an absolute hard contra. Ceftriaxone <1 month continues to render as the amber ⚠ Consult required tag (NOT the red Contraindicated overlay used for Ondansetron <2yr or Ipratropium <1yr). Regression test R17 prevents accidental upgrade to a hard rule.
- Runtime fix — generic MATCH_FOUND → CONSULT_ONLY downgrade. Pre-commit verification of the v1.60 plan discovered a runtime gap: the v1.58
OUT_OF_BAND → CONSULT_ONLY translation only fired when the patient was outside the outer age gate. Ceftriaxone's "Paediatric" keyword parses to age_min_months: 0, so a 2-week-old MATCHED the outer band, the OUT_OF_BAND path never ran, and the renderer silently presented the autonomous 50 mg/kg dose — directly contradicting the DTP's "Patients less than 1 month of age" relative contraindication. v1.60 extends evaluateDoseLookup: even on MATCH_FOUND, if any matching row carries a consult_required_below_months or consult_required_above_months threshold the patient violates, the route downgrades to CONSULT_ONLY (amber Consult Required tag, banner, no autonomous dose). Generic — applies to any drug with this pattern, not Ceftriaxone-specific.
- Precaution wording fix. Ceftriaxone precaution string restored to verbatim DTP wording (the en-dash
− before the parenthetical aside was missing pre-v1.60).
- Regression tests R12–R19 added to
build/test_regressions.py: Ipratropium absolute-contras verbatim (R12); Ipratropium age rule wired (R13); Ipratropium age-gate enforces under-1yr exclusion (R14); Ceftriaxone absolute-contras verbatim + no relative leak (R15); Ceftriaxone relative-contra populated (R16); Ceftriaxone NO age contraindication_rule, negative assertion (R17); Ceftriaxone dose rows preserve consult_required_below_months, negative assertion (R18); Ceftriaxone 2-week-old must hit the MATCH_FOUND→CONSULT_ONLY downgrade path (R19) — protects against data drift that would re-introduce the autonomous-dose-for-neonate bug.
v1.59
DATA-ONLY
Drug-info verifications — Midazolam and Hydrocortisone confirmed
- Midazolam drug_info verified verbatim against DTP_MID_0922. Manual paramedic review of the source PDF confirmed the existing entry is accurate: one absolute contraindication (Allergy AND/OR Adverse Drug Reaction); no relative contraindications in the DTP; four precautions (reduced dosages in low body weight / older / cachectic / frail patients, chronic renal failure / CCF / shock, severe respiratory depression in COPD, myasthenia gravis, multiple sclerosis). No data changes —
validation_status promoted from "Draft – needs paramedic review" to "Verified against DTP_MID_0922".
- Hydrocortisone drug_info verified verbatim against DTP_HYDC_0326. Manual paramedic review confirmed the existing entry is accurate: one absolute contraindication (Allergy AND/OR Adverse Drug Reaction); no relative contraindications in the DTP; one precaution (Hypertension). No data changes —
validation_status promoted from "Draft – needs paramedic review" to "Verified against DTP_HYDC_0326".
- No behavioural change. Dose math, prep flows, route bars, contraindication overlays, and consult banners are bit-identical to v1.58. The verification flips are visible only in the Audit & Validation accordion footer on each drug page (now reads "Verified…" instead of "Draft…").
- Background: 17 of 18 drugs were still flagged "Draft – needs paramedic review" after v1.58. A whole-dataset audit identified Midazolam and Hydrocortisone as the cleanest cases (entries already matched their DTPs verbatim — the HIGH-priority audit flags were heuristic PDF-extraction noise, not real gaps). Ipratropium and Ceftriaxone require real data corrections and ship separately in v1.60. The remaining 13 Draft drugs queue for follow-up verification.
v1.58
SAFETY
Ondansetron contras corrected · Relative Contraindications · Morphine <1yr renderer completed
- Ondansetron absolute contraindications now match the DTP verbatim. Previous build listed only "Allergy/Hypersensitivity to ondansetron". Now lists all four per DTP_OND_0722: Allergy AND/OR Adverse Drug Reaction; Congenital long QT syndrome; Current apomorphine therapy (used in severe Parkinson's disease); Patients less than 2 years of age. xlsx
app_drug_info row 16 updated; validation_status promoted from "Draft" to "Verified against DTP_OND_0722".
- Hard age contraindication for under-2-year-old patients. Added a
contraindication_rule with type:age_min_months, value:24. Route bars now show RED ⚠ Contraindicated · Patients less than 2 years of age instead of silently hiding. The dose-detail page renders the red overlay and suppresses the dose, prep, and calculation cards.
- New
relative_contraindications schema column on app_drug_info. Ondansetron's first-trimester pregnancy entry now lives in its own column — never mixed with the absolute "do not give" list. Renderer surfaces it as a new Relative contraindications subsection between Contraindications and Precautions, using a softer amber pill so paramedics can't conflate "absolute" with "exercise judgement". build_app.py reads the new column via _split_bullets; runtime exposes it as drug_info[].relative_contraindications.
- Morphine <1yr renderer fixes now actually shipped. v1.57 promoted the xlsx data change for Morphine but a build-pipeline mistake meant the runtime translation (
OUT_OF_BAND → CONSULT_ONLY when the row carries consult_required_below_months) didn't land in the V1.57 snapshot. v1.58 ports those edits forward. A 4-month-old on Morphine Significant Pain now correctly sees three route bars with the amber ⚠ Consult required tag instead of an empty list. Defence-in-depth empty-route fallback also lands.
- Regression tests R8–R11 added for Ondansetron contraindication accuracy: all four absolute items present;
relative_contraindications has the first-trimester entry; the under-24-month contraindication_rule is wired; and a 1-year-old patient fails the age gate on every Ondansetron dose row.
v1.57
SAFETY
Morphine <1 year — data fix (renderer carry-over deferred to v1.58)
- xlsx data fix landed: Morphine Significant Pain rows (IM ACP, IM CCP, IV ACP) updated to
≥1 year; consultation/approval <1 year, which sets consult_required_below_months: 12 on the parsed row. Per QAS DTP: "< 1 year — QAS Clinical Consultation and Advice Line approval required."
- Renderer changes (OUT_OF_BAND → CONSULT_ONLY translation, empty-route fallback) were authored but didn't make it into the V1.57 build because the build pipeline took its source from V1.56 before the renderer edits landed. v1.58 ports them properly so the Morphine <1yr UI matches the intent.
- Regression test R7 (
morphine_pain_under_1yr) added: asserts the 3 Morphine Significant Pain rows have age_min_months=12 and consult_required_below_months=12; a 4mo / 5mo patient fails every row's age gate; a 12mo patient passes.
v1.56
UI
Show calculation redesigned — second-dose math + dose ceilings split out
- Repeat-dose working added. For the seven routes that use a genuinely different second dose — Fentanyl IV (ACP+CCP), Fentanyl IM (ACP), Midazolam NAS/IM (ACP), and Morphine IV/IM (ACP+CCP) — the Show calculation drawer now walks through the repeat-dose math alongside the initial-dose math. New
INITIAL DOSE, REPEAT DOSE, and DOSE CEILINGS section headings (in the calc-final accent colour with a soft top-border) make the structure scan-able at a glance.
- Dose ceilings moved to their own section at the bottom. The Single Max / Total Max per-kg working used to sit between Final Dose and Concentration, breaking the dose-to-volume reading flow. It now lives in a dedicated
DOSE CEILINGS section after the dose math. Total Max is cumulative across the encounter, so it belongs OUTSIDE any single dose's calculation.
- Inline cap line only appears when the cap actually bit. The old "(under cap)" line is gone — under-cap doses now read as a clean
rule → × weight → final dose → concentration → volume flow with no detour. The hard ceiling is always still visible in the Dose Ceilings section below.
- Same step layout for both doses — rule → × weight (or × initial for the half-dose Midazolam path) → single-max cap (only if it fired) → final dose → ÷ concentration → volume.
- Single-dose drugs, same-as-initial repeats, unstructured repeats unchanged beyond the new ceilings layout. No extra noise for the common case.
v1.55
UI
Icon filenames cleaned up — no behavioural changes
- Renamed icon assets for clarity:
icon-v45.png → icon-180.png (180×180, iOS home-screen) and icon-v45-512.png → icon-512.png (512×512, high-DPI / PWA install). The legacy "v45" suffix was a stale version-45-era cache-buster that no longer meant anything; names now describe the icon's size.
- Cache-buster bumped
?v=45 → ?v=155 on all three <link rel="icon"> tags so any installed home-screen tile picks up the new asset on next visit.
- Live app unchanged from v1.54 — same dose-card logic, same data, same colour palette. Pure rename + cache-buster.
v1.54
Documentation drift swept — no behavioural changes
- Audit pass after v1.53's folder rename. Four doc references to the old
untitled folder/Can Calculate now/ path updated to snapshots/: project README.md (2×), build/README.md (2×), build/snapshot_versions.py module docstring, and build/promote_to_index.py header. The only surviving mention is the rename-explanation comment immediately above SNAPSHOT_DIR — kept intentionally as history.
- Single source of truth confirmed.
SNAPSHOT_DIR defined exactly once in build/snapshot_versions.py; every other reference flows through sv.SNAPSHOT_DIR or sv.path_for(). No hardcoded snapshot paths anywhere in active code.
- Full safety suite passes against the new layout: 10/10 tests green via
python3 build/run_all_tests.py --quick --no-promote.
- Live app unchanged from v1.52 / v1.53 — same dose-card logic, same data, same colour palette. Just doc maintenance.
v1.53
Project folder cleanup — no behavioural changes
- Folder reorganisation — no functional changes to the app. Source PDFs moved into
source-pdfs/; the cardiac-arrest references folder (which had a trailing space in its name) is now source-pdfs/cardiac-arrest/. The snapshot archive is now plainly snapshots/. Older design mockups and one-off build scripts moved into _archive/ subfolders. A top-level README.md now maps the structure.
- One-line code change:
SNAPSHOT_DIR in build/snapshot_versions.py updated to ROOT / "snapshots". All 10 safety tests pass against the new layout.
- Live app unchanged from v1.52 — same dose-card logic, same data, same colour palette. Just paint and bookkeeping.
v1.52
Hide out-of-age routes · descriptive dose-rule subtitle
- Out-of-age routes are now hidden on the drug-detail Administration Route section. Surfaced via Glucose Gel: a 5-year-old used to see both PO (in band) and BUCC (out of band, <2 yr) with a "Consult required" tag on the BUCC row. Now BUCC simply doesn't render — only the route that applies to the patient is offered. Contraindicated rows are NOT hidden (paramedic must see "Contraindicated · <8 years" for Droperidol etc.).
- Filter rule: skip the route iff
_safety_fail.kind === "out_of_band" AND no contraindication_rules fire for the patient. Other consult cases (scope_note, consult_only) still show with their amber tag.
- Descriptive dose-rule subtitle on the dose-detail page. When
dose_rule_original carries text beyond the basic <number> <unit> pattern, the renderer surfaces it as a small muted line under the big dose number. So Glucose gel BUCC now reads:
1 cm · 1 cm strip (approximately 0.5 mL) on gloved finger.
- No database change.
dose_rule_original already contained the full sentence — the renderer was just throwing the descriptive part away. The detection heuristic suppresses the subtitle for typical drugs whose original is just 15 g or 1 microg/kg.
v1.51
IM goes rose · prep card stale-state fixed · consult reason surfaced
- IM colour swapped from amber to rose (
#be185d). Was reading as yellow against the cream cards; rose is distinct from blue/purple/cyan/sienna and stays in the no-green/no-red palette.
- Prep card no longer leaks between drugs. Surfaced via Dexamethasone Croup PO for a 2-month-old: the safety_fail block returned early before the prep-card render, so the previous drug's prep flow stayed on screen (Ceftriaxone IM water/lidocaine bleeding into a Dex view). Fix: reset the prep card + contra overlay at the top of
renderDoseDetail, so safety-fail rows always start clean.
- Consult banner now shows the reason — when the row hits out_of_band or consult_only, the renderer matches
drug_info.contraindications entries that contain "(consultation required)" against the row's indication and surfaces them under the generic message. So a 4yo Croup query on Dexamethasone now reads: "Reason: Croup: Paediatrics less than 6 months OR greater than 8 years of age (consultation required)".
v1.50
Resus toggle is now a mode flag — calc is the default action
- Toggle stays on the home page. Tapping "Resus" no longer jumps to the resus deck — it just sets the mode. You still pick age + weight on the home page; CALCULATE then opens the right destination.
- Indication field hides in Resus mode. Resus is age + weight driven; the indication picker doesn't apply, so it disappears from the form and reappears when you toggle back to Drug calc.
- CALCULATE button label swaps —
CALCULATE → in Drug calc mode, OPEN RESUS REFERENCE → in Resus mode. Same enable rules (age set + weight resolved). One press, the right destination.
- Wiring — toggle sets
state.mode; CALCULATE branches by it. The resus open path still runs through the (hidden) btnResus handler so there's a single source of truth for _populateResus + _wireResusChips + showView("resus").
v1.49
Drug calc / Resus toggle · ABD indication · dotted filenames
- Segmented Drug calc / Resus toggle on the home page replaces the standalone RESUS REFERENCE button. Drug calc is selected by default — primary use is paediatric drug dosing; the resus reference is one tap away but stops competing for attention. Tapping the back arrow on the resus page resets the toggle.
- "Acute behavioural disturbance" added to the indication dropdown on the home screen. Matches Droperidol's Acute behavioural disturbance SAT ≥2 indication; the matcher is tolerant ("ABD", "agitation", "behavioural disturbance") so future ACP rows surface automatically.
- Snapshot filenames switched to dotted format from v1.49 onward —
QAS_Ped_DrugV1.49.html instead of QAS_Ped_DrugV149.html. Older files keep their integer names; the build pipeline reads both. Internal integer N is unchanged — just the on-disk name.
v1.48
Version label switches to v1.NN format (was v147 → now v1.47)
- New display format. The version chip on the home page, the title bar, the "Version history vN" button, and every entry in this changelog now read as
v1.48 instead of v148. Same number, just split into a (major.minor) form: n // 100 . n % 100. So v147 → v1.47, v100 → v1.00, v99 → v0.99.
- Filenames unchanged. Snapshots on disk still use the integer (
QAS_Ped_DrugV148.html) and the build pipeline still talks in plain N — only the user-visible labels switch.
- Build scripts updated.
swap_dataset_into_snapshot.py writes the new format on every bump; promote_to_index.py's verification regex accepts both the legacy vN and the new v1.NN shapes so old snapshots still validate.
v1.47
Hot fix — resus view was leaking behind every screen
- Critical fix. v1.46's
.view-resus { display: flex } rule overrode the view-switching default (.view { display: none }), so the resus deck stayed mounted behind every view. Symptoms: scrolling down on home jumped into the resus page, and tapping CALCULATE landed on resus instead of the drug menu.
- Fix: moved the flex declaration into
body.v-resus .view-resus — same selector pattern the rest of the views use. Resus now appears only when the user taps the RESUS REFERENCE button.
v1.46
Resus reference rebuilt as a swipeable 7-page deck
- New layout. Resus reference is now a horizontal swipe deck — 7 pages: Defib, CPR, i-gel, OPA, Adrenaline, Pads, 4H/4T. Drag/swipe to change page; tapping a chip in the strip up top jumps directly. Active chip highlights as you scroll.
- Forest + mint palette matching the dose-detail page — feels like a sibling "active medical action" view rather than a settings screen.
- Per-page big-number headlines: defib J for the patient (calculated 4 J/kg rounded up), CPR ratio, i-gel size, OPA size + colour, adrenaline microg + draw-up volume, paeds pad placement.
- New
app_resus_causes sheet in the xlsx — 4 H's + 4 T's with per-row source/version columns. Renderer reads from QAS_DATASET.resus.causes.
- Single source of truth still holds: every page is built at render time from
QAS_DATASET.resus.{igel, opa, defib, cpr, causes}. No hard-coded ratios, sizes, or J in the HTML.
v1.45
Manual joule settings (4 J/kg <6 yr) added to resus DB
- app_resus_defib extended with
mode (auto/manual), age_min_months, age_max_months, dose_per_kg_j, round_to_nearest_j columns. Existing AED rows tagged mode=auto; two new manual rows added.
- Manual mode (ZOLL X-Series Advanced / corpuls3): children <6 years = 4 J/kg all shocks (rounded UP to next 5 J defib step); adult or children ≥6 years = 200 J all shocks.
- Defib mini-card now prefers manual J for paeds in arrest — calculates
4 × weight rounded up. Falls back to AED defaults when age unknown.
- Reference table grouped by mode — "Manual mode" and "AED auto mode" sub-headings between rows so the paramedic can see both pathways at a glance.
- Source:
CPP_Defibrillation.pdf p.892 Joule settings section.
v1.44
Resus data migrated to xlsx — single source of truth
- 4 new sheets in the xlsx:
app_resus_igel, app_resus_opa, app_resus_defib, app_resus_cpr. Each row carries source_pdf / pdf_pages / version / last_reviewed audit columns — same provenance schema as the drug rows.
- build_app.py reads them and emits under
QAS_DATASET.resus.{igel, opa, defib, cpr}.
- All hard-coded constants removed from the V143 renderer. The Resus reference page now reads exclusively from the dataset; the i-gel / OPA / defib tables are rebuilt at render time so xlsx edits flow through with a rebuild + swap.
- Sources captured per row: i-gel from CPP_Supraglottic-airway_i-gel (Dec 2022), OPA from CPP_Oropharyngeal-airway-device (Jul 2022), Defib from CPP_Defibrillation (Mar 2026), CPR ratios from ARC 2021.
v1.43
New Resus reference page — paediatric cardiac arrest, ACP2 scope
- New "Resus reference" page reachable from a button below CALCULATE on the home screen. Patient-aware mini-cards at the top: defibrillation J, i-gel size, OPA size + colour, CPR ratio — all resolved against the selected age/weight.
- Reference tables below — full i-gel sizes by weight (1 / 1.5 / 2 / 2.5 / 3 / 4 / 5 with OG-tube sizes), QAS OPA colour codes (3–10 cm: lilac → red), and the ZOLL X Series default energy table (paeds <25 kg = 50 J, adult = 200 J). The row matching the current patient is highlighted.
- Adrenaline-arrest callout with one-tap link to the Adrenaline drug page (10 microg/kg IV/IO every 4 min, 1:10,000).
- ACP2 scope only — ETT and other CCP-restricted procedures are intentionally excluded.
- Sources: QAS CPP_Defibrillation, CPP_Supraglottic-airway_i-gel, CPP_Oropharyngeal-airway-device · ARC 2021 paediatric resus.
v1.42
Red contraindication overlay — ⚠ + reason inside the dose card
- Red overlay replaces the dose card when a route is contraindicated. Shows a red ⚠ icon, the heading CONTRAINDICATED, and the short reason underneath (e.g.
<8 years) — same panel for every contraindicated drug, not just amber text in a white card.
- Inline route tag in Administration Route is now red too — `tag-contra` uses crimson on a red-tinted pill while the consult tag stays amber. Paramedics can tell "do not give" from "needs CCAL approval" at a glance.
- Bottom safety banner switches to red for contraindicated lines (new
.contra-banner CSS class via BANNER_COPY) — same wording, distinct palette.
- Applies to all four drugs already covered by
contraindication_rules: Droperidol <8 yr, Methoxyflurane <1 yr, Salbutamol <1 yr, Paracetamol <1 mo. Future rules added to the xlsx automatically inherit the same treatment.
v1.41
Fixes: contra-rules now actually fire on drug-detail · text-dose font sized
- Drug-detail Administration Route now shows "⚠ Contraindicated" when it should. The v139 lookup was checking
drug.name but inside renderDrug() the variable is d — so the contraindication_rules array always came back empty and the bars stayed on "Consult required" for under-age patients. Switched to d.name / d.id.
- "Contraindicated" text dose now fits the card. When the dose-detail card carries text instead of a number (Contraindicated / Consult Required / etc.), the font drops from 76 px to 36 px and word-wraps so long words don't overflow the white card.
v1.40
Contra-rules propagated to dose-detail page
- Dose-detail page now respects absolute contraindications. Tapping a route on a contraindicated drug used to land on a giant "Consult Required" dose card with an orange "Outside approved age/weight range" banner — even when the patient was inside an absolute contraindication (e.g. Droperidol IV for a 9-month-old). v140 swaps the line into a "contraindicated" safety_fail so the dose card reads "Contraindicated" and the banner says "Contraindicated — do not administer".
- New
contraindicated banner copy in BANNER_COPY — same amber visual weight as the consult banner, but the wording is "do not give" rather than "outside the approved range".
v1.39
Absolute age-based contraindications surfaced as Contraindicated, not Consult
- New
contraindication_rules column on app_drug_info sheet — structured rules the runtime evaluates against the patient's age. Format: JSON array of {type:"age_min_months", value, label, reason} objects.
- Drugs marked with absolute age contraindications: Droperidol (<8 years), Methoxyflurane (<1 year), Paracetamol (<1 month), Salbutamol (<1 year). When the patient is below the threshold, the route pill shows "⚠ Contraindicated · <8 years" instead of the softer "⚠ Consult required" — matching the QAS DTPs.
- Absolute contra wins over consult. If a row would normally fire the consult tag (out-of-band or scope_note "Consult required") but the drug also has an absolute age rule that matches the patient, the contraindicated tag fires instead.
v1.38
Contras same style · new route palette · admin-route warning tags
- Contraindications match Indications and Precautions. Pills now use the same outlined teal style — no red. The section header and chevron are enough to signal "do not give"; the colour was redundant and visually noisy.
- Route pills re-coloured (no green, no red). IV/IO royal blue, IM amber, PO bright purple, BUCC sienna, NAS deep purple, NEB/MDI/INH cyan, EM dark amber, NONE slate. Same scheme on the dose-detail hero pill.
- Inline route warning tags. In the Administration Route section on the drug-detail page, when a route is contraindicated or requires consult, an amber "⚠ Contraindicated" or "⚠ Consult required" tag now sits inline between the route pill and the chevron — so the warning rides with the route, no separate banner needed.
v1.37
Fixes — banner leak between routes · friendly volume reasons
- Safety banner no longer leaks between routes. Surfaced via Glucose Gel: a 6 yr old on PO (≥2 yr — match) was showing the orange "Outside approved age/weight range" banner from the BUCC <2 yr line that lived next to it. Fix: the banner now lives in its own child div inside the audit card so showing it doesn't destroy the audit DOM.
- Friendly volume reasons. Raw enums like
oral_liquid_given_as_dose_not_draw_up are now translated to readable text (e.g. "Oral liquid — give the labelled dose, no draw-up") wherever the volume reason is displayed.
v1.36
Drug Preparation card — 3-cell dilution flow
- Visual dilution flow. The Drug Preparation Required card now renders a 3-cell flow when the drug needs dilution or reconstitution: source ampoule/vial + additive → final concentration. Replaces the long sentence with a glanceable diagram.
- Covers all 10 prepared drugs in the dataset — Fentanyl IV, Midazolam IV/IO, Morphine IV, Adrenaline IV/IO infusion + low-dose, Glucagon IM/IV, Hydrocortisone IM/IV, Ceftriaxone IM, Ceftriaxone IV/IO <20 kg + ≥20 kg.
- Falls back to the original free-text sentence for any prep that doesn't have a structured step list yet, so nothing breaks if a new drug is added before its steps are filled in.
v1.35
Cleaner max-dose chips · max calc moved to Show calculation
- Max chips are now clean. Single Max and Total Max under the dose card show only the calculated value (e.g. "5 mg" or "56 microg"). The "(Calculated · 0.1 mg/kg)" working that used to be appended is gone from the chips.
- Working moved to Show calculation. The per-kg max math (factor × weight = absolute value) now appears as separate rows in the Show calculation card alongside the rest of the dose maths.
- Single Max always sits on the left, Total Max on the right in the chip strip — consistent regardless of which is present.
- Max strip moved beneath the last dose. When a different repeat dose is shown, the max strip now sits under the repeat card rather than between initial and repeat — so SINGLE/TOTAL maxes are always under the last dose displayed on the page.
v1.08
Changelog backfill — entries for v94 through v108
- Version chip auto-bumps on every rebuild via the swap script. Changelog entries themselves are hand-written and need to be added explicitly when each release ships. Backfilling everything from this session.
v1.05
Drug Preparation Required card body collapsed by default
- Tap the orange pill to expand. Card stays visible for prepared drugs (so the trigger word is always there) but the dilution detail collapses by default. Chevron rotates 90 deg on expand.
- Resets on every dose-detail render — switching drug or route always opens the next page collapsed.
v1.04
Repeat dose card matches initial — fonts, divider, format, plain-English interval
- Same fonts as initial. Number 76px, unit 30px, label 12px / 2px letter-spacing.
- Dashed divider between amount and draw-up line.
- Same Draw up + Dilution format for prepared drugs (Fentanyl IV, Morphine IV, etc.).
- Plain-English interval. "q5 min" replaced with "every 5 minutes" so the time between doses reads at a glance.
v1.03
Details card (renamed from ddInfoCard) — collapsed by default
- Patient / Presentation / Note / Single max / Total max now lives behind a "Details ›" head. Tap to expand. Resets to collapsed on each render.
v1.01
Prepared-drug text standardised
- One pattern across all prepared drugs. Hydrocortisone, Glucagon, Ceftriaxone, Adrenaline IV INF, Adrenaline IV low-dose, Morphine IV, Midazolam IV, Fentanyl IV — all read "Reconstitute / Dilute / Add X with Y (Z mL total)".
v1.00
Two-line dose format with Dilution label
- Drug Preparation Required heading rendered as an orange pill (white text on #d97706 bg).
- Dose card now reads on two lines for prepared drugs — "Draw up X in Y mL" then "Dilution — Z".
v0.99
Per-kg total max bug fixed — Fentanyl IV total now correct
- Bug. The build-time parser silently dropped "/kg" off cap strings, so "Total maximum dose 2 microg/kg" was stored as a flat "2 microg" — clinically nonsensical at the patient's weight.
- Fix. Parser now captures
is_per_kg and is_per_minute; runtime multiplies by patient weight before display ("Total · 68 microg (2 microg/kg × 34 kg)") and labels infusion rates separately ("Max rate · 0.5 microg/kg/min" — never multiplied).
- Five rows fixed: Fentanyl NAS/IM, Fentanyl IV, Morphine IM, Morphine IV, Adrenaline IV INF.
v0.98
Prep card moved above dose; Fentanyl IV prep math corrected
- Card position. Drug Preparation Required now sits above the Initial dose card so paramedics see prep needed before reading dose.
- Fentanyl IV prep fix. Was "100 microg with 10 mL saline" (wrong by 2 mL of diluent). Now: "Add 8 mL sodium chloride 0.9% to the 100 microg / 2 mL ampoule (10 mL total)".
v0.97
Drug Preparation Required card + "preparation" trigger word
- New
requires_preparation flag on the prep table. Set TRUE for any drug requiring dilution or reconstitution.
- Trigger word visible. Volume line for prepared drugs reads "X mL of Y preparation" so the paramedic knows the concentration shown is the prepared one.
v0.96
Droperidol / Morphine / Naloxone preps; Morphine ACP/CCP scope split; range-aware dosing
- New preps: PREP_DROPERIDOL_IV_IM (10 mg in 2 mL), PREP_MORPHINE_IM (10 mg/mL ampoule), PREP_MORPHINE_IV (1 mg/mL diluted), PREP_NALOXONE_IM_IV (400 microg/mL).
- Morphine IM scope fixed. Row 043 reclassified ACP -> CCP with the q10min repeat schedule. Row 044 stays ACP single dose 100–200 microg/kg.
- Range-aware dosing. When the rule is a numeric range ("100–200 microg/kg"), the dose card shows both ends — e.g. 2.5–5 mg / 0.25–0.5 mL.
v0.95
Adrenaline concentrations in clinically familiar units
- Format. IM rows show "1 mg/mL" instead of "1000 microg/mL"; cardiac arrest IV/IO shows "1 mg in 10 mL (100 microg/mL)".
v0.94
Adrenaline preparations split by clinical context
- IM (anaphylaxis + severe bronchospasm) uses 1:1,000 only. Volumes 0.05–0.3 mL across the paed dose ladder.
- IV/IO cardiac arrest uses 1:10,000 only.
- CCP-only IV/IO low-dose bolus uses an explicitly prepared 1:100,000 dilution.
- Removed the clinically inappropriate 30 mL IM volume option that the runtime had been surfacing for the 8 yr / 300 microg anaphylaxis dose.
v0.93
Hydrocortisone Adrenal-crisis IV — added 5–10 yr and >10 yr bands
- Filled the v92 known gap. Adrenal-crisis IV had only the 0–4 yr (25 mg) band in the dataset — 5+ yr patients asking for adrenal crisis IV were getting the consult banner. Added the missing rows per DTP_Hydrocortisone:
QAS_ACPII_065 — Adrenal crisis IV, 5–10 yr → 50 mg (slow push, single dose)
QAS_ACPII_066 — Adrenal crisis IV, >10 yr → 100 mg (slow push, single dose)
- Both rows mirror the existing IM weight-band protocol (rows 027/028) and the 0–4 yr IV row (029) — only the row_id, age band, and fixed_dose_value differ. Validation_status: Verified. Source:
DTP_Hydrocortisone.pdf p.4.
- Test T21: 6 yr (60 mo) + Adrenal IV → matches new row 065 → 50 mg. 12 yr → matches 066 → 100 mg. 39/39 PASS.
v0.92
Hydrocortisone indication swap fix + prominent SINGLE DOSE ONLY badge
- SAFETY: Hydrocortisone indications corrected. The dataset had the Asthma and Adrenal crisis indications swapped on rows 026–031. Per DTP_Hydrocortisone: Asthma/refractory anaphylaxis is 4 mg/kg (IM and IV, single max 100 mg); Adrenal crisis is weight-banded fixed (0–4 yr 25 mg, 5–10 yr 50 mg, >10 yr 100 mg, IM and IV, single dose). Before v92 a 5-year-old asking for "Asthma" would have been shown the Adrenal crisis 50 mg fixed dose — wrong protocol entirely. Source-data fix applied to the spreadsheet, dataset rebuilt.
- Prominent SINGLE DOSE ONLY badge. When the row's repeat protocol is "Single dose only" (e.g. dexamethasone, ondansetron, hydrocortisone), the dose-detail page now shows a bold red badge directly under the initial dose card: "SINGLE DOSE ONLY — DO NOT REPEAT". Easy to miss when buried in the bar-note line; impossible to miss now.
- Known data gap (not a bug): Adrenal crisis IV currently has only the 0–4 yr band; the 5–10 yr (50 mg) and >10 yr (100 mg) IV bands are missing from the spreadsheet. A 5+ yr patient asking for Adrenal crisis IV will see the consult banner. Flagged for next dataset edit.
v0.91
Consult-only protocols → amber banner instead of giant rule text
- Bug fix. Midazolam Acute behavioural disturbance (IM/IV, row 042) used to render the literal consult sentence — "QAS Clinical Consultation and Advice Line approval required in all situations" — as the giant dose amount on the dose-detail card. Confusing and visually wrong.
- New
CONSULT_ONLY state in the dose-lookup machine. When a row's dose_type is non_calculable_or_reference (i.e. there's no per-kg or fixed-dose value, just a directive to phone CCAL), the dose-line emitter now produces a _safety_fail of kind consult_only instead of trying to render a numeric dose. The renderer treats it like the existing OUT_OF_BAND consult — amber banner with title "Consultation required" and the row's actual rule text as the body.
- Self-tests: T18 (midazolam ABD IM/IV at 25 kg → CONSULT_ONLY safety_fail with the consult banner kind). Self-test now reports 31/31 PASS.
- Doesn't affect normal MATCH_FOUND rows — only fires for rows the spreadsheet flagged as non-calculable.
v0.90
Audit & validation card collapsed by default on dose-detail
- UI quietening. The Audit & Validation block at the bottom of every dose-detail page used to render fully expanded — pills, grid of source-PDF metadata, version stamp, dataset hash, etc. Visually distracting on a screen the user is reading at speed.
- Now: on a normal MATCH_FOUND dose the audit card is collapsed by default — only the header bar shows, with the status dot (green / amber / red) and a chevron. Tap the header to expand and see the full audit grid. Tap again to collapse. Preference is preserved per-page via a toggle on the header element only — doesn't persist across drugs.
- Safety fail banners stay expanded. When the lookup state is OUT_OF_BAND, NO_PROTOCOL or DATASET_ERROR, the audit card IS the message — it never starts collapsed. (Detected via the same
_safety_fail path the v80 banner renderer uses.)
- Self-tests unchanged — collapse is purely presentational. 30/30 PASS.
v0.89
SCOPE FIX — Midazolam: ACP2 NAS/IM 200 µg/kg; IV/IO is CCP-only
- Verified against DTP_Midazolam. The QAS DTP is unambiguous: ACP2 paediatric midazolam for seizures is NAS/IM at 200 microg/kg (single max 5 mg, repeat half initial max 2.5 mg q10min, total max 10 mg). IV/IO is CCP-only — ACP2 must use NAS/IM unless a patent IV cannula is already in situ.
- Source-data fix. The previous dataset carried the ACP1 weight-banded fixed-dose protocol (1/2/3/4/5 mg by weight band, rows QAS_ACPII_035–039) tagged as ACP scope, which is wrong for ACP2. Edited row 035 to the canonical ACP2 weight-based 200 microg/kg rule and deactivated rows 036–039. Reassigned row 040 (IV/IO seizure) from ACP → CCP via
build/reassign_row_to_ccp.py.
- Result for ACP2 view: only one Midazolam seizure row remains visible — NAS/IM 200 microg/kg, single max 5 mg. IV/IO is filtered out by the existing scope filter (level_rank > 1). Sedation row was already CCP-only.
- Numeric continuity: at integer weight thresholds the new per-kg rule gives the same dose as the old weight-bands (5 kg → 1 mg, 10 kg → 2 mg, 25 kg → 5 mg). For weights between bands the new rule is more precise (12 kg ACP1 → 3 mg vs. ACP2 → 2.4 mg).
- New regression tests: T15 (22 kg + midazolam NAS/IM → 4.4 mg uncapped). T16 (28 kg → 5 mg capped). T17 (midazolam IV/IO seizure must NOT appear in ACP2 scope after the reassignment). 26/26 PASS.
v0.88
Display unit follows the cap's unit (mg/microg consistency)
- Why: v87 fixed the cap-fires-with-wrong-unit bug, but only switched the display unit when the cap actually fired. For uncapped patients (e.g. 22 kg + midazolam IV = 2200 microg, below the 2500 microg cap) the display still showed "2200 microg" instead of "2.2 mg". Inconsistent — small/medium kids saw microg, ≥25 kg kids saw mg, all on the same drug + route.
- Now: when a row's
single_max is explicit and in a different mass unit from the dose rule, the display ALWAYS uses the cap's unit. So midazolam IV (rule 100 microg/kg, cap 2.5 mg) now displays in mg for every weight band: 1 kg → 0.1 mg; 12 kg → 1.2 mg; 25 kg → 2.5 mg (capped). Same for morphine IV. Fentanyl IV cap is in microg so its display stays microg, unchanged.
- Repeat-dose card uses the same logic so it doesn't drift. e.g. midazolam IV repeat (same as initial, q5min) shows the same mg-unit number as the initial card.
- New regression tests: T13 (22 kg + midazolam IV uncapped → 2.2 mg, NOT 2200 microg). T14 (repeat card unit matches initial card unit). 19/19 PASS.
v0.87
SAFETY FIX — single_max cap unit conversion
- Critical safety fix. The initial-dose cap comparison in
calcDoseFromRow compared the calculated dose against single_max.value without converting units first. When the calculated dose was in microg and the cap was specified in mg (e.g. midazolam IV 100 microg/kg with single_max 2.5 mg), the comparison 2800 microg > 2.5 mg.value hit because the numeric comparison ignored units, then assigned dose = 2.5 while the unit stayed microg. Result: a 28 kg patient on midazolam IV showed "2.5 microg" on the dose card instead of "2.5 mg" — a 1000× under-display.
- Affected drugs / scenarios: midazolam IV/IO seizure (≥25 kg patients), morphine IV (≥25 kg patients). Same pattern (microg/kg dose with mg cap). All other rows unaffected because their cap and dose units already matched.
- Fix: convert
single_max.value from its unit into the dose unit via _safeMassConvert before the comparison. The runtime's "unit-conversion lockdown" already restricted us to the mass family (g ↔ mg ↔ microg), so the conversion is bounded and safe.
- New regression test T12: 28 kg + midazolam IV/IO seizure → initial dose must be 2.5 with unit "mg" (not "microg"). Self-test now reports 14/14 PASS.
- Note: the v86 repeat-dose calc (
_computeRepeatDose) was already doing the correct conversion — only the existing initial-dose path had the bug. So the repeat card was right while the initial card was wrong on these rows.
v0.86
Repeat dose card on dose-detail — calculated, no mental math
- Repeat dose now has its own card. When a protocol has a non-trivial repeat (midazolam ½ initial; fentanyl IV 0.5 µg/kg q5min; morphine IV 50 µg/kg q5min; methoxyflurane once after 20 min; salbutamol q10min; etc.), the dose-detail page stacks a second card under the initial card with the resolved repeat amount, repeat volume, interval, and any cap. Amber left accent so it reads as second-stage, not error.
- Coverage: midazolam, fentanyl, morphine, methoxyflurane, salbutamol, ondansetron — all parsed from the existing free-text
repeat_interval column into a structured repeat_protocol field. 26 of 27 active rows parse cleanly; the 2 unstructured rows (1 morphine flagged "Check row context", 1 CCP infusion) hide the repeat card rather than guess.
- Eight repeat kinds supported:
none (single dose), same_as_initial (fixed interval or range), same_as_initial+PRN, fraction_of_initial (with cap), different_amount (per-kg or fixed, with cap), repeat_once (with explicit amount or same as initial), and unstructured (hide the card).
- Drugs without meaningful repeats are unchanged — ondansetron, dexamethasone, paracetamol etc. don't show a second card. The renderer hides it when
repeat_protocol.kind is none or unstructured.
- Self-test extended with new T8–T11 covering each repeat kind end-to-end (25 kg + midazolam NAS → 2.5 mg / 0.5 mL; 25 kg + fentanyl IV → repeat 12.5 µg / 0.25 mL; etc.).
v0.85
Age selection now mandatory on the start screen
- Mandatory age. CALCULATE button now stays disabled until an age is selected. Previously the button enabled as soon as any usable weight was available — meaning a paramedic who picked a weight override but skipped the age dropdown could proceed without the app knowing the patient's age. Age gates the entire app (it picks the right protocol band for every drug), so it must be set.
- Visible cue. While age is unset, a small hint "Select an age to continue" is shown under the disabled button. Once age is picked the hint disappears and the button activates.
- Defense in depth. The click handler also early-returns when
state.ageKey is empty — even if the disabled attribute is bypassed, the menu won't render without age.
- Weight override remains optional. If you don't enter one, the estimated weight from the QAS pediatric formula
(age × 3) + 7 is used.
v0.84
Build provenance — source hash, git commit, row counts visible in-app
- Why this exists. The app embeds its dataset at build time, but until now there was no way for a paramedic in the field to see which spreadsheet bytes and which git commit produced the data they're holding. v84 surfaces that.
- New "Build provenance" page linked from the start screen alongside Version history and Feedback. Shows: build date and timestamp; source spreadsheet filename + sha256 (short and full); git commit (short, full, branch, dirty flag); active row count split by clinical level (ACP / CCP); and the list of QAS DTP source PDFs the dataset draws from.
- Reproducibility: any V<N>.html opened on any device in the future will show the exact provenance of its dataset — making "is this current?" or "what changed since last time?" answerable without git access.
- Best-practice rationale. Considered separately versioning the dataset (e.g. dataset-v3.0, dataset-v3.1) but rejected: each app snapshot already embeds its dataset immutably, and adding a second version axis would duplicate the existing per-row
audit.version field and the v3.0-ccp dataset_version. Provenance metadata achieves the same reproducibility/audit goal without the extra ceremony.
v0.83
Fentanyl: rogue "Significant pain (context with no max)" row reassigned to CCP
- Data fix. Fentanyl had a duplicate IV row (QAS_ACPII_018) with the indication awkwardly renamed to "Significant pain (context with no max)" and total_max = "No maximum dose". Its own row notes flagged it as needing review. Per DTP_FEN_0722 the ACP2 IV total max for Significant pain is 2 microg/kg — the row was contradicting the protocol.
- Reassigned to CCP scope (clinical_level=CCP, level_rank=2) at the source spreadsheet via
build/reassign_row_to_ccp.py. The runtime ACP2 scope filter now drops it — the rogue indication no longer appears on the Fentanyl page, and the only Significant pain IV row remaining at ACP2 is QAS_ACPII_017 (the protocol-correct 2 microg/kg total max).
- New regression test in-app: after the ACP2 scope filter, no fentanyl row with indication "(context with no max)" should exist. Self-test now reports 7/7 PASS.
v0.82
Drop "CCAL" acronym from consult bar
- Wording fix. The orange consult tag on a route pill (shown when the patient is outside the approved age/weight band) used to read "Consult QAS CCAL". The "CCAL" acronym for Clinical Consultation and Advice Line isn't universal in the field and was ambiguous. Replaced with "Consult Required".
- Banner unchanged. The full audit-card banner already spelled out "QAS Clinical Consultation and Advice Line" — no acronyms there. Only the small bar caption changed.
v0.81
Parser fix · "X months to Y years" · Dexamethasone Croup unblocked
- Parser fix. The age-criteria parser in
build_app.py now handles inclusive cross-unit ranges like "6 months to 8 years" — previously it required the explicit "<" form ("6 months to <8 years") and silently failed otherwise. The Dexamethasone Croup row was the only row in the dataset hitting this case; under v80 it tripped the new DATASET_ERROR integrity check.
- Behaviour now: Dexamethasone + Croup parses to
age_min=6 mo, age_max=108 mo (i.e. 6 months through end of 8th year), matching the convention used by all other inclusive ranges in this parser.
- New regression tests in-app: 3mo + Dexamethasone + Croup + PO →
OUT_OF_BAND; 6mo + Dexamethasone + Croup + PO → MATCH_FOUND. Both verified end-to-end through evaluateDoseLookup. Self-test now reports 6/6 PASS on load.
- Why GPT's bucket-end "exact" semantics were rejected: Their spec mapped "6–12 years" to
age_max=144. Under the runtime's half-open gate (age >= age_max → reject) that would reject a 12-year-1-month-old from a "6–12 years" protocol. Clinically wrong; also breaks the existing age_6_dash_12y parser test which expects age_max=156. v81 keeps the existing whole-bucket convention.
v0.80
Explicit 4-state dose-lookup machine
- Refactor. The dose-row matching logic is now a single function
evaluateDoseLookup(baseRows, weight, ageMonths) that returns one of four explicit outcomes: MATCH_FOUND, OUT_OF_BAND, NO_PROTOCOL, DATASET_ERROR. The renderer is a flat switch on that state — no nested conditionals.
- MATCH_FOUND — exactly one eligible row (or any number of legitimate
multi_step_protocol rows). Show the calculated dose.
- OUT_OF_BAND — base rows exist for this drug + indication + route + level, but the patient's age/weight is outside every band. Amber consult banner: "Outside approved age range — Consultation required" with the approved bands listed.
- NO_PROTOCOL — no rows exist for this drug + indication + route + level at all. Neutral grey banner: "No QAS protocol available for this scenario." Not an error — there's just nothing to show.
- DATASET_ERROR — rows exist but are unusable. Triggered by parser failure (e.g. unparsed age string), missing structured fields (e.g.
dose_type=weight_based with no dose_per_kg), duplicate eligible rows without multi_step_protocol, or a thrown calculation. Red banner with the specific reason and offending row IDs.
- What this fixes: the v78 behaviour where any "no match" for the patient's age was labelled DATASET ERROR. That conflated three very different situations (out-of-range patient, missing protocol, broken data). v79 split out OUT_OF_BAND; v80 also splits out NO_PROTOCOL and tightens DATASET_ERROR to mean only genuine data integrity failures.
v0.79
Out-of-age-band → Consult, not "DATASET ERROR"
- Separated clinical out-of-band cases from true dataset errors. Patients outside approved protocol range now receive a consult message rather than a dataset error.
- Bug fix. Searching a 3-month-old for Dexamethasone (or any drug where the patient falls outside the QAS-approved age band) used to surface a red "DATASET ERROR · no_match" banner on the dose-detail page. That wording suggested an app/spreadsheet bug when in reality it was a clinical out-of-range — the right answer is to phone the QAS Clinical Consultation and Advice Line, not to hunt a software bug in the back of the truck.
- New behaviour. When rows exist for the indication+route but none cover the patient's age/weight, the app now shows an amber "Outside approved age range — Consultation required" banner that lists the approved age bands and tells you to contact QAS CCAL. True dataset bugs (e.g. overlapping rows that should be one) still surface as a red DATASET ERROR.
- Specific case fixed: 3 mo + Dexamethasone — Croup is approved 6 months–8 years, Asthma (PO) requires consultation under 6 years. Both indications now display the consult banner instead of a dataset error.
- Carried from v78. Route-only large drug-admin pills with full dose detail behind a tap. ACP2 scope filter. Audit/validation card. Indication-driven Drug admin.
v0.78
Drug admin · route-only large pills
- Drug admin row is now a single big route badge. Each line in the Drug admin section shows ONLY the route — IM, IV, IO, PO, NEB, IN — as a large, prominent, tappable pill. No dose, volume, concentration, or notes inline. Tap the pill to bring up the full dose-detail page with all the calculated information.
- Why this change. The previous compact bar tried to surface dose + volume + concentration + notes in a single row. On a phone in the back of an ambulance that's a lot of small text to parse. A big route pill is unambiguous from across the cab and one tap reveals every detail you need.
- Carried from v77. Indication-driven Drug admin (no "All indications" pill, empty until a pill is tapped), silent-chest Adrenaline rows (4 age bands), ACP2 scope filter, audit/validation card on every dose-detail page.
v0.77
Indication-driven Drug admin · silent chest activated
- Drug admin is now indication-driven. The "All indications" leader pill has been removed. When you open a drug page without a specific indication selected, the Drug admin section is empty with a prompt: "No indication selected. Tap an indication pill above to see the dose admin for that scenario."
- Tap an indication → only that scenario's doses appear. Each pill carries the literal indication text; tapping sets
state.indication = "literal:<text>" and the Drug admin filters to exactly that indication. Tap the active pill again to clear the selection.
- New indication: Adrenaline IM Severe life-threatening bronchospasm / silent chest. 4 paediatric age bands sourced verbatim from DTP_ADR_0825 page 6 (Aug 2025): <6 mo → 50 microg, 6 mo–<1 y → 100 microg, 1–<6 y → 150 microg, ≥6 y → 300 microg. All Verified.
- ACP2 scope filter (carried from v76). CCP rows still filtered from the runtime; green
✓ ACP2 scope chip on the home screen.
- Audit card + dataset chip on every page — version, build date, source DTP, row ID, calculated dose, volume, concentration, scope note, clinical level — unchanged from v76.
v0.76
Safety regime · multi-option volumes · ACP2 scope
- Strict whitelist parser, multi-option concentration (Option A), mass-only unit conversion, ACP2 scope filter, source traceability + inclusive flags, VNA enum, 9-step automated test regime + clinical sanity layer, failing-row reporting, Indications/Contras placeholders.
v0.74
CCP scope tag on indications
- Indications outside ACP2 scope now show a small CCP tag before the label, so it's obvious at a glance which uses require CCP-level clinician.
- Tagged so far: Adrenaline (Bradycardia), Salbutamol (Suspected hyperkalaemia), Hydrocortisone (Refractory anaphylaxis, Adrenal crisis), Midazolam (Sedation), Fentanyl (Sedation adjunct), Saline (Drug carrier, Maintenance).
- Tag is purple (CCP convention) and inverts to white-on-purple when the pill is selected (filled teal).
v0.73
Bigger route pill on dose-detail
- The admin-method pill (IM, IV, NEB, etc.) on the dose-detail page bumped from 16 px → 30 px with deeper padding and a stronger drop-shadow. Reads instantly across the room.
v0.72
Swipe between routes on dose-detail
- Dose-detail page (the dark-teal "give this dose" screen) now supports horizontal swipe to move between routes within the same indication — e.g., Dexamethasone PO → IV → IM for Croup.
- Pagination dots appear under the route pill when there's more than one route, with the active dot stretched into a pill to show position.
- Subtle "‹ swipe to change route ›" hint shows when more routes are available.
- Left/right arrow keys also navigate between routes (handy on desktop for testing).
v0.71
Stat-tile vitals page · clickable indications
- Vital Signs page redesigned to "Stat-tile" on dark teal — minimal: target range as a huge headline number per vital, critical-low / critical-high cut-offs in the corner. Matches the dose-detail page palette.
- Indication pills on every drug page are now clickable buttons — tap any pill (or the leading "All indications" pill) to switch the global indication filter. Same effect as the home-screen dropdown.
- The active indication stays filled in teal; the home-screen dropdown stays in sync as you switch from inside a drug page.
v0.70
Olanzapine added
- New drug: Olanzapine (Antipsychotic · S4). Indications: acute behavioural disturbance / acute psychosis with severe agitation. PO wafer (5 mg) and IM injection (5 mg in 1 mL after reconstitution) — single max 10 mg, 24-hr total max 20 mg. Under 12 years routes to "Consult — outside ACP2 scope."
- Includes Precautions: postural hypotension, QT prolongation, CNS depressant interactions, hepatic impairment, slow IM onset.
v0.69
Dose-detail header + selected-indication highlight
- Dose-detail (full-screen dark-teal) page now keeps the same top bar pattern as the rest of the app: back button + patient chip (age · weight) + Vitals button, all restyled for the dark background.
- Indications section is now expanded by default on every drug page.
- The indication selected on the home screen is rendered as a filled teal pill; non-selected indications stay outlined. If no indication was chosen, an "All indications" leader pill is shown filled instead.
v0.68
Dose-detail page · larger title · cleaner pills
- New dose-detail page: tap any dose row to open a full-screen dark-teal "give this dose" view — indication kicker, big drug name, large coloured route pill, white DOSE card with a huge numeric dose, draw-up line, and patient/presentation/max info card.
- Home title "Paediatric Drug Calculator" bumped to ~40 px for stronger landing-screen presence.
- Age dropdown no longer shows weights — reads cleanly as Newborn, 1 month, …, 12 years.
- Indications and Precautions pills are now outlined (transparent fill, teal border & text). Contraindications stay solid red so they remain visually loudest.
v0.67
Slate-blue moved to per-indication headers
- Per-indication subsections inside Drug admin (e.g. Anaphylaxis, Cardiac arrest, Seizure — IV access established) now use the slate-blue tone — so each clinical scenario stands out clearly within the dose section.
- Precautions section now uses the standard teal-green styling — same as Indications, Contraindications header, and Drug admin — for visual consistency with the rest of the page.
v0.66
Precautions reordered + recoloured
- Section order on the drug page is now Indications → Contraindications → Precautions → Drug admin — the don't-give list reads before precautions.
- Precautions pills and subsection header recoloured from amber to a neutral slate-blue tone (later moved in v67 to the indication sub-headers).
v0.65
Plain-English footer disclaimer
- Footer blurb on every page replaced with a plain-English disclaimer: "This tool is built to make guideline-based dosing faster and easier, but it's not a replacement for your training or protocols. Always double-check against current ambulance guidelines and use your clinical judgement."
- Footer styling tweaked — longer line-height and a constrained max-width so the paragraph reads cleanly on every screen size.
v0.64
Precautions section · larger route badges
- New Precautions collapsible section under Indications on every drug page — drug-specific cautions in an amber pill style.
- Precaution items added for all 18 drugs (e.g. adrenaline cardiac/MAOI cautions, methoxyflurane hepatic/total-dose limits, ondansetron QT, naloxone re-sedation, oxygen titration in COPD).
- Route badges (IM, IV, NEB, IO, PO, NAS etc.) bumped from 11 px → 16 px with more padding — much more visible on every dose row.
v0.63
Vital Signs page · cleaner section headers
- New Vitals button to the right of the patient chip on every drug page (menu and detail) — opens a dedicated vital signs view for the selected age.
- Vitals view shows reference ranges for Heart rate, Respiratory rate, and Systolic BP, each as a colour-coded bar (red low · green normal · orange high) with the numerical cut-offs.
- Tap Back to return to whichever page you came from (drug menu or drug detail).
- Reference values approximate APLS / PALS paediatric ranges and should always be interpreted in clinical context.
- Cleaner section headers — count chips removed from Indications, Contraindications, Drug admin, and per-indication subsections so the headers read more like clinical labels.
v0.62
Mockup-1 "Field-teal" redesign
- Whole-app palette swap to the field-teal mockup: cream surfaces (
#faf7f1), deep-teal headings (#003d36), white cards on cream, soft sage page background.
- Header is a clean cream nav-bar with a teal-50 back pill and a white patient chip — no more saturated teal gradient header.
- Home screen is now a series of white field cards on cream with a teal-700 Calculate button.
- Drug list cards: small letter icon on teal-50, drug name, drug class subtitle, chevron — much lighter visual weight.
- Drug detail hero: kicker (drug class) + big deep-teal name + presentation sub-line, on a teal-50 → cream gradient.
- Dose rows: route badge (red IM, blue IV, green NEB) followed by big black dose, sub volume line.
- Sections (Indications, Contraindications, Drug admin) sit as rounded white cards with teal-50 heads.
- Filter banner toned down to clinical teal-50.
v0.61
Nested indication collapsibles fixed
- Switched the open/closed CSS rule from a descendant selector to a direct-child combinator — when a parent section opens, it no longer forces every nested subsection inside to open as well.
- Anaphylaxis, Cardiac arrest, Croup etc. inside Drug admin now expand and collapse independently as expected.
v0.60
Contraindications above Drug admin
- Section order on the drug page is now: Indications → Contraindications → Drug admin → Notes.
- Contraindications now reads before any dose blocks, so the don't-give list is seen before calculation.
- All three top-level sections are collapsible — tap the header bar to open or close.
v0.59
New "Drug admin" section for dose blocks
- Per-indication dose collapsibles moved out of the Indications section into a new "Drug admin" section that sits beneath it.
- Indications now shows just the pills (quick "what does this drug treat" reference).
- Drug admin opens by default and contains one collapsible per indication (Anaphylaxis, Cardiac arrest, Croup, etc.). Children stay closed when filter is "All", open when a filter is set.
- Page hierarchy: Indications [closed] → Drug admin [open] → Contraindications [closed] → Notes.
v0.58
Indications nested under main Indications header
- Each individual indication (Anaphylaxis, Cardiac arrest, etc.) is now nested inside the parent "Indications" section, parallel to how Contraindications works.
- Page hierarchy: Indications [open] → pills + collapsibles per indication; Contraindications [closed].
- Inside the Indications section, the per-indication children stay closed by default when filter is "All", open when a filter is set.
- Cleaner mental model — taps go through the same heading regardless of context.
v0.57
Per-indication collapsibles when no filter
- Each indication on the drug page is now its own collapsible section (e.g. "ANAPHYLAXIS", "CARDIAC ARREST"…).
- If the user picked an indication on the home screen, that section opens automatically.
- If the user left it as "All indications", every section is closed by default — the user taps the indication name they need to expand its dose bars.
- Stops drug pages with many indications (Adrenaline, Hydrocortisone) from being a wall of doses on first load.
v0.56
New dose card — mono-teal Variant C
- Dose layout switched to a clean two-column card (Variant C).
- Route chips all share a 70 px min-width, so they line up across rows. Mono-teal palette: light teal (IM/PO/MDI/INH/NAS), mid teal (IV/IO/NEB/BUCC), deep teal (auto-injector).
- Presentation pill now in cream so it stands out against the teal route chips.
- Single Max / Total Max badges full-width with a fixed 88 px prefix column — left and right edges line up top-to-bottom.
- Contraindicated bars stay red, consult bars stay amber — colour-flag intact.
v0.55
Total Max badge always present
- Every dose line that has a Single Max badge now also shows a Total Max badge.
- If the QAS DTP didn't specify a total cap, the calculator fills the badge with "No max dose".
- True single-dose-only entries (e.g. EpiPen, glucagon, naloxone) skip the synthetic — the Single Max already represents the total.
v0.54
Tightest "Paediatric" spacing
- "Paediatric" letter-spacing dropped 0.32 em → 0.28 em.
v0.53
Even tighter "Paediatric" spacing
- "Paediatric" letter-spacing dropped 0.38 em → 0.32 em.
v0.52
Tighter "Paediatric" spacing
- Reduced "Paediatric" letter-spacing 0.42 em → 0.38 em.
v0.51
Title spaced, not sized
- "Paediatric" now uses the same font size as "Drug Calculator" — the matched width is achieved with letter-spacing (0.42 em) instead of a bigger font.
- Trailing-space compensation keeps the line centred.
v0.50
Edge-to-edge bg · matched title widths
- The teal background now extends under the iOS home-indicator safe area — the frosted strip at the bottom of the home screen in standalone PWA mode is gone.
- Used
background-attachment: fixed + a deep-teal <html> bg + min-height: 100dvh so the colour fills the full viewport on modern iOS.
- "Paediatric" now renders bigger (46–60 px) than "Drug Calculator" (30–40 px) so the two lines have matching visual widths — the title stack reads as a centred block.
v0.49
Title rename · hero shifted down
- Home-screen title now reads Paediatric Drug Calculator (replaces "PED Drug Calculator").
- Title scales down to fit the longer wording — caps at 40 px on big screens.
- Hero pushed down ~40 px so the headline doesn't sit right against the status bar.
v0.48
Consistent max-dose terminology
- Every max-dose badge now starts with either Single Max or Total Max — consistent across all drugs.
- When the QAS limit is per-kg (e.g. "0.3 mg/kg total"), the badge now shows the calculated absolute amount for the current patient and tags it "Calculated · 0.3 mg/kg" so the clinician can verify.
- Single-Max prefix uses warm amber, Total-Max a darker amber so they're easy to distinguish.
- Non-numeric guidance (e.g. "titrate to BGL >4.0 mmol/L") shows without a prefix — kept as-is.
v0.47
Fentanyl IM dose added
- Added the missing IM dose for fentanyl: ACP2 paediatric ≥1 yr at 1–2 microg/kg.
- Single max 50 microg, total max 2 microg/kg (or MME).
- Sits as a separate "Significant pain — IM (≥1 yr)" indication block alongside Intranasal and IV.
- <1 yr still requires QAS Clinical Consultation Line approval (handled by existing consult fallback).
v0.46
Icon now hosted as a real PNG file
- Switched the home-screen icon from an inline base64 data-URI to an actual PNG file hosted in the repo (
icon-v45.png) with a ?v=45 cache-buster.
- iOS doesn't recognise it as the same resource as the previous icon, so it'll fetch fresh on the next "Add to Home Screen".
- Larger 512 px PNG also included for Android / browser tab favicons.
v0.45
New home-screen icon
- Replaced the home-screen icon with the new "calculator with a smiling baby on the LCD" mark — teal gradient body, dark display, amber baby face, amber accent column on the button grid.
- Both the apple-touch-icon and the favicon are updated. Removing and re-adding the page to your phone home screen will pick up the new icon.
v0.44
Frosted glass home (Design G)
- Home screen now uses Design G — a solid teal canvas with a soft radial glow.
- Hero text floats directly on the canvas (no white card cut-off), with an amber kicker, big stacked white title, and white subtitle.
- Form sits in a frosted-glass card (translucent white + 18 px backdrop blur) with amber labels.
- Field selects: dark transparent with white text and amber dropdown arrow.
- Calculate button: solid amber with deep-teal text — the strongest visual element on the screen.
v0.43
Big-Hero home screen
- Home screen redesigned to "Design A — Big Hero":
- Full-width teal canvas with a stacked 50 px headline ("PED Drug / Calculator"), kicker text above, and subtitle below.
- White rounded form card slips up over the hero with a deep drop shadow.
- Calculate button: green-gradient with amber-yellow text — visually the strongest action on the screen.
- Version history + Feedback now sit as small pill links on the green canvas below the card.
v0.42
Bigger active-filter banner
- The yellow "Filter: indication" banner on the menu and drug pages is now ~2× larger — indication name 12 → 24 px, FILTER label 9 → 14 px, Clear button 11 → 16 px.
- Once an indication is set, it's hard to miss which clinical context the dosing is filtered for.
v0.41
Smaller patient header text
- Patient age and weight in the menu / drug page header dropped ~25 % — age 28 → 21 px, separator 26 → 20 px, weight pill 28 → 21 px.
v0.40
Distinct presentation vs max colours
- Presentation pill changed to a clinical info blue (bg #e7f0fd, text #0a4d8a) so it visually separates from the max-dose badge.
- Max-dose badge stays in amber (bg #fff7e6, text #8a5a00) — caution / limit colour.
v0.39
Reverted dose-bar look
- Dose-bar body back to white with bold black dose / volume text — the dark-fill version was unreadable.
- Dose & volume back to left-justified.
- Presentation pill, max badge, and note return to their warm-amber-on-white styling.
- Patient header in the top bar still stays amber yellow on the green gradient.
v0.38
Doses right-justified · sections collapsed by default
- Drug dose & volume text is now right-justified within the dose bar — the critical numbers hug the right edge for instant pickup.
- Presentation pill and max badges also flow to the right for visual coherence.
- Indications and Contraindications sub-sections back to collapsed by default; Doses still opens by default.
v0.37
Yellow doses · taller drug cards
- Dose bars repainted: dark teal-black body so the dose + volume can render in the same warm amber yellow (#ffd54f) as the patient header, with a soft drop shadow for legibility.
- Dose font 24 → 26 px, volume 18 → 20 px.
- Presentation pill, max badge, and note adjusted to read on the dark body.
- Coloured-route block on the left and the red/orange contraindicated/consult states are unchanged.
- Drug menu cards taller again (padding 32 → 44 px) and drug detail hero card taller (36 → 50 px).
v0.36
Cleaner home + feedback screens
- Home screen: removed the patient summary readout (the coloured "Patient · Weight" panel) below the Indication dropdown — straight from the form into the Calculate button.
- Feedback header no longer shows "Sent to adamwynn@gmail.com".
- Feedback intro updated to "Describe any problems, errors, or suggestions so that we can continue to improve this app."
- Thank-you screen no longer names a specific recipient.
v0.35
Yellow patient header · taller drug cards · simplified labels
- "Edit" button on the drug menu renamed to Back with a chevron icon.
- Patient age + weight in the menu/drug headers now rendered in warm amber yellow (#ffd54f) instead of white — clearly more prominent against the green header.
- Drug menu cards taller (padding 22 → 32 px) and drug detail hero card taller (22 → 36 px) so they have more visual weight.
- Drug class subtitle removed from menu cards and detail hero — drug name only now.
- Contraindications subsection header reverts to the default teal styling; the contraindication pills below stay red.
v0.34
Age = weight font size
- Patient age in the menu / drug page header bumped 24 → 28 px to match the weight pill.
- Separator dot bumped 22 → 26 px so it sits proportionally between the two values.
v0.33
Bigger headers · sections expanded by default
- Patient age + weight on the menu page is now right-justified, matching the drug detail page.
- Header font sizes bumped on both pages: age 19 → 24 px, weight pill 22 → 28 px, separator 18 → 22 px.
- Subsection headers ("Indications", "Contraindications", "Doses") bumped from 13 px to 17 px; count badges 11 → 13 px; chevron 16 → 20 px; padding 13 → 16 px.
- All three subsections now expanded by default on the drug page (previously only Doses was open).
v0.32
Home banner becomes a card
- Green "PED Drug Calculator" banner on the home screen now sits as a rounded card with a 16 px gap each side — left and right edges line up with the white patient card below.
- Vertical padding bumped (44 px top & bottom) so the title has more breathing room.
- Soft shadow added so the banner feels lifted off the page.
v0.31
Swipe back · right-aligned patient header
- Swipe gesture reinstated on the drug detail page — swipe left for next, right for previous (within the filtered drug list). Slide-in animation matches the swipe direction.
- No Prev / Next buttons or position counter — just the gesture.
- Patient age + weight in the drug-page header is now right-justified, hugging the right edge of the blue bar.
v0.30
Bigger drug label cards
- Drug menu cards: padding 22 px (was 16), icon badge 62×62 (was 50×50), drug name 26 px (was 22 px), class subtitle 14 px.
- Drug detail hero card: padding 22 px (was 16), icon badge 72×72 (was 56×56), drug name 32 px (was 26 px), class subtitle 14 px.
- Heavier shadow + bumped border radius so the dark-green card doesn't look weak alongside the dose bars.
v0.29
Emoji prefix on dose text
- ⛔️ and 🗣️ emojis are now prefixed inline before the dose text (e.g. "⛔️ Contraindicated <1 yr") instead of replacing the left route block.
- The route block on the left is back to its normal "—" placeholder for these states.
v0.28
Emoji icons on contraindicated / consult bars
- Contraindicated bars now show a ⛔️ emoji instead of "✕".
- Consult-required bars now show a 🗣️ emoji instead of "?".
- Emojis replace the dash placeholder entirely so the visual cue is unmissable from across the room.
v0.27
Red / orange dose-bar states
- Dose bars now colour-shift to flag clinical state at a glance:
- Red — when the line is "Contraindicated…" / "Not in…" (red route block with ✕, pink body, red dose text).
- Orange — when the line is "Consult required…" / "Outside … scope" (orange route block with ?, amber body, amber dose text).
- Standard route-coloured bars unchanged.
v0.26
No swipe / no prev-next
- Removed swipe-between-drugs gestures from the drug detail page.
- Removed the Prev / Next pagination buttons and the "X / Y" position counter at the bottom of each drug.
- Removed the slide-in animation that played on drug change.
- Drug detail page is now a single static screen — back button at the top is the only way to return to the menu (default behaviour).
v0.25
Age dropdown trimmed to 12 yr
- Age dropdown now stops at 12 years — matches the QAS paeds scope.
- 13 yr (46 kg) and 14 yr (50 kg) entries removed; for adolescents, switch to the ACP2 adult reference rather than relying on weight-based paediatric calc (which is at adult caps anyway).
- Weight override dropdown is unchanged — heavier-than-estimate 12 year olds can still use a measured weight.
v0.24
Refractory anaphylaxis NEB added
- Added the missing NEB 5 mg dose for adrenaline under Anaphylaxis — used for upper airway obstruction unresponsive to 3 × IM adrenaline (per QAS DTP_ADR_0825).
- Volume: 5 mL of 1:1,000 (use 5 ampoules) · Single dose only · Use 1:1,000 (NOT 1:10,000) for nebulisation.
- Now sits as the third line under the Anaphylaxis indication, after the IM ampoule and EpiPen lines.
v0.23
Highway-sign dose layout
- All drug doses now use the "Highway-sign bars" layout (Design 1):
- Each dose is a horizontal bar — thick coloured route block on the left (orange = IM, teal = IV/IO, cyan = NEB, green = PO, purple = NAS, brown = auto-injector, red = BUCC), white card on the right.
- The white card stacks dose, "in volume", presentation badge, note, and max badges in that order.
- Auto-injector lines (EpiPen) use a distinct brown route tone so they're not confused with regular IM ampoule lines.
- The previous bordered "indication card" wrapper is replaced by a clean stack — bars are themselves the visual unit.
v0.22
Larger age on patient card
- Patient age on the home-screen summary card now renders at 32px / 800-weight to match the weight on the right.
- Empty-state placeholder ("Select age above") stays small so it doesn't wrap before a value is chosen.
v0.21
Hidden elements actually hidden
- Restored the missing
.hidden { display: none !important; } rule that was lost when the styles were rewritten in v8.
- The red feedback error banner no longer shows on first load — only when an actual send failure occurs.
- The "Thank you" screen no longer appears below the form before the user hits send — only after a successful submission.
- Filter bars on menu / drug pages also hide correctly when no indication filter is active.
v0.20
Feedback works on any origin
- Feedback form now uses a real
<form> POST targeted at a hidden iframe instead of fetch().
- Fixes "Couldn't send" errors when the page is loaded from
file:// or the cowork preview (computer://) — those origins blocked the previous AJAX call via CORS.
- Submission still goes to adamwynn@gmail.com with subject PED APP FEEDBACK; thank-you screen still shows on success.
- Includes a 12-second timeout so a dead network shows the error banner instead of hanging.
v0.19
In-app feedback form
- Feedback no longer opens a mail client. Tapping the home-screen button now opens an in-app form.
- User types their message, taps Send →, and it's posted directly to adamwynn@gmail.com (subject: PED APP FEEDBACK) via formsubmit.co.
- After a successful send the user sees a thank-you screen with a button back to home.
- Submission also includes the app version and the device user-agent so issues can be traced.
v0.18
Right-justified doses
- Dose and volume inside the rectangle now right-aligned instead of left, putting the critical numbers at the edge where the eye lands fastest.
v0.17
Dark green cards · stacked dose · feedback link
- Drug cards (menu and drug detail hero) now use the dark menu green gradient with white text and frosted icon badges.
- Dose + volume now stacked vertically inside the rectangle — dose on top, volume on the line below.
- New "Please provide feedback" link at the bottom of the home screen — opens an email to adamwynn@gmail.com.
v0.16
Teal green theme
- App-wide colour swap from blue to teal green: header gradient, accents, route tags, pills, badges, and dark-mode background.
- Functionality unchanged — only colours.
v0.15
Patient header on drug page
- Drug name removed from the header on the drug detail page.
- The header now shows just age + weight (in title size/font), matching the menu's patient readout.
- Drug name is still visible in the hero card on the page body.
v0.14
Boxed dose + volume
- Every dose + volume line (e.g. "150 microg in 0.15 mL") now sits inside a thick black rectangle so it stands out at a glance.
- Helps the eye lock onto the critical number under stress, separated visually from the route, presentation, and notes.
v0.13
One-line title
- "PED Drug Calculator" now sits on a single line across the home screen.
- Title scales fluidly between 22px and 32px so it always fits without wrapping.
v0.12
Indication filter
- Optional Indication dropdown on the home screen (between Weight and Calculate).
- When an indication is selected, the menu only shows drugs relevant to that indication.
- On the drug page, only the matching dose blocks are shown (e.g. "Anaphylaxis" hides cardiac arrest dosing for adrenaline).
- Filter pill on the menu and drug pages, with a Clear shortcut on the menu.
- Swipe / Prev / Next navigate inside the filtered subset.
v0.11
Cleaner start screen
- Large centred "PED Drug Calculator" headline on the home screen.
- Removed the instructional paragraph above the patient card.
- Added this Version history page (linked at the bottom of the home screen).
v0.10
Swipe between drugs
- Swipe left/right on the drug page to move between drugs.
- Slide-in animation matches the swipe direction.
- Prev / Next buttons + position counter (e.g. 5 / 19) at the bottom of each drug.
- Vertical scrolling protected by a dominant-axis check.
v0.09
Simpler menu header
- Removed the "Drugs" title — the menu header now shows age + weight prominently.
- Removed category filter chips (Respiratory, Allergy, Cardiac, etc.).
- Edit button (top-left) still jumps back to the home screen.
v0.08
Three-screen flow
- Dedicated start screen for age + weight, then Calculate.
- Drug menu lives on its own screen, distinct from the home screen.
- Tapping a drug opens a full phone page instead of expanding inline.
v0.07
Bigger drug labels
- Drug name 17px → 22px (heavier weight).
- 3-letter code badge 38×38 → 48×48.
- Class subtitle and chevron bumped up too.
v0.06
PRESENTATION badge
- Renamed "STRENGTH" → "PRESENTATION".
- Now always shown per dose line, depicting what's in the vial / ampoule / device (e.g. "Ampoule 1:1,000 (1 mg in 1 mL)", "EpiPen® Jr 150 microg auto-injector").
- Sits on its own row above the dose for fast scanning.
v0.05
High-stress readability
- Dose output shown as one big black bold line: e.g. "150 microg in 0.15 mL".
- Drug presentation pulled out of the dose sentence into a prominent badge.
- Indication titles in UPPERCASE for instant recognition.
v0.04
Collapsible subsections
- Indications, Contraindications, Doses are collapsible dropdowns inside each drug card.
- Doses open by default (primary field info); other sections collapsed.
- Count badges on each subsection header.
v1 – v3
Initial calculator builds
- Paediatric weight estimates by age + manual weight override.
- Drug list with category chips and per-drug expansion.
- Per-indication dose blocks with route, amount, volume, max, and notes.
- Single-open card mode with focus fade for the surrounding list.