opsinjs
HealthColour, status and attention

Notifications and off-screen alerts

A push notification is a clinical statement delivered to a lock screen the product does not control, in front of people the reader did not choose.

What this means

A push notification is a clinical statement that the product hands to the operating system and then stops controlling. The system composes it in a layout the implementing team does not choose and truncates it at a length that team cannot predict. It may be handed to a car, a watch or a smart speaker to be read aloud, and it stays on the lock screen until someone dismisses it. It arrives unrequested, on a device that may be face-up on a kitchen table beside a person the reader has never told about their condition.

The first constraint that follows is disclosure, and it applies to every message the channel carries. "Your test result is ready" tells anyone within reading distance that the reader is waiting on an investigation, and "Your glucose has been high for three days" discloses a condition, a direction and a duration at once. Both messages are displayed without any action by the reader, so the disclosure is settled in the payload the product composes rather than at the moment the reader picks up the phone.

The second constraint is that a delivered notification cannot be recalled. The product can stop sending and can change what the app shows, and it cannot edit or withdraw a message already sitting on a lock screen. A reader may tap that message long after the state it described has changed, which is why the in-app surface rather than the notification carries the authoritative version.

The third constraint is that the reader's remedy is not per-message. Notification permission is revoked at the operating-system level, so a reader who turns the product off has turned off its clinical alerts along with its reminders. A cluttered dashboard can be ignored one screen at a time, whereas an over-used notification channel is closed once and for everything the product might later need to say. The budget governing how often any surface may escalate is set out on Alarm fatigue.

The rule

A notification is a clinical surface with three extra constraints: it discloses in public, it cannot be recalled, and its permission is spent once.

A push is the top rung of the escalation ladder on Alarm fatigue, and it is available once the rungs below it have been used and found insufficient.

Sixteen requirements follow, in the five groups named in the first column. Each row states the obligation, the specific thing that is never permitted, and where the obligation sits. The last column names the page that specifies the matter in full, and is empty where no other page does.

GroupRequirementNever permittedEnforced bySpecified in full
ContentThe title names the subject at the level of a category: "Blood pressure".A value in the title: "Blood pressure 178/104".The payload the product composes
ContentThe visible body carries the subject and an action, and the value lives behind authentication in the app.A result value, a diagnosis, a medication name or a condition name in the visible body.The payload, with the value held in the app
ContentThe visible body stays accurate and non-disclosing when spoken aloud by a car, a watch or a smart speaker.Wording that is safe to read on a silent screen and discloses when spoken in a room.The platform, which decides where the text is spoken
ContentA per-user setting controls how much a notification may reveal, and it defaults to the least.A disclosure level the product raises on the reader's behalf.The product's settings surfaceOn-screen privacy
EscalationAt most one non-clinical push is sent per day.A second non-clinical push in the same day, or a non-clinical message sent on the clinical channel.The escalation budget, held per day across the productAlarm fatigue
EscalationClinical and non-clinical messages are registered as separate operating-system notification channels wherever the platform provides them, so the reader can silence one and keep the other.One channel carrying both kinds of message on a platform that offers more than one.The product's channel registration, within what the platform provides
EscalationA steady result is pushed only where the reader has asked to be told.A steady result pushed because the product had something to say, such as "Everything is fine" sent unasked.The product's send logicClinical status semantics
ContinuityEvery push resolves to a specific in-app surface carrying the full message at the same status level.A tap that lands on a dashboard or a home screen.The product's deep-link routing
ContinuityThe in-app surface is authoritative, and where the state changed between the send and the tap the app says so.A push presented as current after the state it described has changed.The in-app surfaceUncertainty and staleness
ContinuityThe notification is stamped with the time the underlying reading was taken.The delivery time shown where the measurement time belongs.The payload the product composesUncertainty and staleness
ContinuityThe in-app alert survives the notification being cleared, because clearing a lock screen records no acknowledgement of the message.An in-app alert cleared by a swipe on the lock screen.The in-app surface's own dismissal state
TimingSystem quiet hours and platform interruption levels are respected, and the highest interruption level is reserved for the clinical urgent path and used rarely enough to stay meaningful.A critical or time-sensitive interruption level attached to a reminder, a digest or a steady result.The platform sets the levels, and the product chooses one
TimingA health alert is sent at 03:00 only where it is genuinely actionable at 03:00, because waking someone to tell them something they can only act on at 09:00 is a net harm.An overnight send for a message whose action first becomes available in the morning.The product's send scheduling
TimingA clinical alert is sent on its own.A clinical alert batched inside an engagement digest.The channel split registered with the platform
Badges and widgetsThe badge count is treated as a disclosure surface, because it is visible to anyone who can see the phone.A badge that appears only when something needs attention, since its presence alone carries the message.The product's badge logic
Badges and widgetsA widget defaults to category-level information, with values hidden until the device is unlocked wherever the platform allows it.A reading, a trend or a status word legible on a widget while the device is locked.The platform's redaction capability, where it has one

None of the sixteen is checked by opsinjs tooling. Notification payloads, channels and scheduling are platform code outside React, so the Enforced by column names the place the obligation sits rather than a test that fails in a build.

Why (evidence)

Applying it

Do

"Blood pressure. There is a reading to look at. Open the app to see it." The body names the category and an action, carries no value and no diagnosis, and is safe to read aloud in a room.

Don’t

"Warning: your blood pressure is 178/104, which is very high." The message states a clinical judgement at a value, on a lock screen, to a reader who cannot ask a question and may not be alone.

Do

Register two notification channels named "Health alerts" and "Reminders and updates". Label them plainly in settings so the user can keep the first and mute the second.

Don’t

Send everything on one channel. A single channel leaves the reader no way to mute the reminders without muting the clinical alerts, so the only control the platform gives them covers both.

Do

Deep-link straight to the surface the notification is about, and render the same status there, so the two surfaces carry one message at one level.

Don’t

Open the home screen and hope the reader finds it. A notification that resolves to a dashboard leaves the reader alerted to something and without the message they were alerted to.

Do

Show when the reading was taken: "Reading from yesterday, 21:40." The stamp names the measurement time, which is a different fact from the delivery time the platform records.

Don’t

Imply the reading is live. A delayed sync that presents a three-day-old value as news invites the reader to act on an old measurement as though it had just been taken.

Components that implement this

opsinjs renders no notifications. That is platform code outside React. The system provides the in-app half of the pair: the surface a notification resolves to, carrying the same status vocabulary, the same tone rules and the same timestamp discipline, so that the two halves cannot drift apart.

What this does not cover

  • Sending notifications. Scheduling, delivery, tokens, platform APIs and reliability belong to the implementing team's infrastructure.
  • Email and SMS. Both channels are outside this system, and we judge them worse on disclosure because their content persists where the product can neither redact it nor withdraw it.
  • The alarm-fatigue budget itself. The budget and the escalation ladder are specified on Alarm fatigue.
  • Watch and wearable surfaces. These surfaces are glanceable, frequently public and often haptic-first, and they are not yet specified here.
Not written yet. Specify the watch and wearable surface: what may appear on a wrist, and how the haptic budget interacts with the visual one.

Updates to this page

Last read through against the system on 2026-09-02. Due for review every 6 months; expiry is reported by pnpm run check:freshness.

On this page