---
title: "Notifications and off-screen alerts"
description: "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."
url: "https://opsinjs.pensievelabs.org/health/notifications-and-off-screen-alerts"
source: "https://opsinjs.pensievelabs.org/health/notifications-and-off-screen-alerts.md"
section: "Health"
kind: "health"
evidence: "opinion"
reviewed: "2026-09-02"
reviewer: "design"
aliases: ["push notification", "lock screen", "badge count", "off screen", "quiet hours", "widget"]
implements: ["alert-banner", "care-card", "status-pill", "relative-time"]
---

> Elements written as `<PascalCase … />` below are opsinjs documentation
> components. Their attributes are the content: the values they render are
> generated from `tokens/*.json` and `registry/catalogue.ts` and are
> published separately at https://opsinjs.pensievelabs.org/r/index.json and under the Reference
> section.
> Nothing is missing from this page. The data simply does not live in
> the prose.

<PageTemplate kind="health" />

## What this means [#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](./alarm-fatigue.mdx).

## The rule [#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](./alarm-fatigue.mdx), 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.

| Group              | Requirement                                                                                                                                                                                 | Never permitted                                                                                               | Enforced by                                                           | Specified in full                                            |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | ------------------------------------------------------------ |
| Content            | The 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                                      |                                                              |
| Content            | The 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                           |                                                              |
| Content            | The 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                  |                                                              |
| Content            | A 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 surface                                        | [On-screen privacy](./on-screen-privacy.mdx)                 |
| Escalation         | At 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 product                | [Alarm fatigue](./alarm-fatigue.mdx)                         |
| Escalation         | Clinical 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 |                                                              |
| Escalation         | A `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 logic                                              | [Clinical status semantics](./clinical-status-semantics.mdx) |
| Continuity         | Every 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                                       |                                                              |
| Continuity         | The 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 surface                                                    | [Uncertainty and staleness](./uncertainty-and-staleness.mdx) |
| Continuity         | The notification is stamped with the time the underlying reading was taken.                                                                                                                 | The delivery time shown where the measurement time belongs.                                                   | The payload the product composes                                      | [Uncertainty and staleness](./uncertainty-and-staleness.mdx) |
| Continuity         | The 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                              |                                                              |
| Timing             | System 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             |                                                              |
| Timing             | A 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                                         |                                                              |
| Timing             | A clinical alert is sent on its own.                                                                                                                                                        | A clinical alert batched inside an engagement digest.                                                         | The channel split registered with the platform                        |                                                              |
| Badges and widgets | The 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 widgets | A 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.

<SafetyCallout severity="urgent" evidence="opinion">
  A notification must never be the only channel for a message that may require
  emergency care. Notifications fail silently: they are dropped, throttled,
  disabled, or delivered to a device in a drawer. The emergency path is specified
  at [Emergency and escalation](./emergency-and-escalation.mdx) and it is always
  redundant.
</SafetyCallout>

## Why (evidence) [#why-evidence]

<ResearchNote evidence="opinion" date="2026-09-02">
  This page is opinion. We cite no study, and the reasoning is drawn from
  platform guidance and from the structure of the problem rather than from
  measurement.

  Two things are close to fact and worth stating as such. First, notification
  permissions are revoked at the OS level and are not per-message: the reader
  who withdraws permission withdraws it from the clinical alerts and the
  reminders together. Second, lock screen content is visible without
  authentication by default on the major mobile platforms, and users generally
  do not change that. Both are checkable against Apple's Human Interface
  Guidelines and Android's notification documentation, which are the right
  places to look for current platform behaviour and which we cite rather than
  restate, because platform behaviour changes and a copied summary here would
  go out of date without giving any sign that it had.

  The clinical-versus-engagement channel split, the one-push-a-day budget, and
  the rule against putting values in a notification body are our judgements. The
  last of these carries a real cost, because a reader who has to unlock the phone
  to see a number will sometimes not bother, and we accept the cost because the
  disclosure it prevents cannot afterwards be undone.

  What would change our mind: a study in which readers of a health product both
  preferred values in the notification body and could state who else was able to
  read them would revise the Content group's ban on values in the visible body.
  The revision we would make is a per-user setting that defaults to off, not the
  removal of the rule.
</ResearchNote>

## Applying it [#applying-it]

<DoDont>
  <DoDont.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.
  </DoDont.Do>

  <DoDont.Dont>
    "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.
  </DoDont.Dont>
</DoDont>

<DoDont>
  <DoDont.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.
  </DoDont.Do>

  <DoDont.Dont>
    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.
  </DoDont.Dont>
</DoDont>

<DoDont>
  <DoDont.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.
  </DoDont.Do>

  <DoDont.Dont>
    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.
  </DoDont.Dont>
</DoDont>

<DoDont>
  <DoDont.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.
  </DoDont.Do>

  <DoDont.Dont>
    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.
  </DoDont.Dont>
</DoDont>

## Components that implement this [#components-that-implement-this]

{/* Generated from `implements`. Do not restate the list by hand. */}

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 [#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](./alarm-fatigue.mdx).
* **Watch and wearable surfaces.** These surfaces are glanceable, frequently
  public and often haptic-first, and they are not yet specified here.

<Todo>
  Specify the watch and wearable surface: what may appear on a wrist, and
  how the haptic budget interacts with the visual one.
</Todo>

## Updates to this page [#updates-to-this-page]

<Reviewed />
