Follow-up from #2942 / PR #3008, which closed the contract half of this and deliberately left the presentation half open. Recording it so it doesn't get lost.
What #3008 fixed
NotificationContext used to store displayType on the item and never read it, under a union that claimed alignment with NotificationTypeSchema while carrying a renderer-local modal and missing alert / inline. That is now correct:
- the union matches the spec enum (
toast | snackbar | banner | alert | inline), with modal kept as a deprecated legacy spelling that presents as alert;
notify() materializes the declared type (spec default toast), so a consumer can branch on it;
- positions match
NotificationPositionSchema (underscores), hyphens kept for stored items;
- a parity guard fails if either vocabulary drifts.
What is still open
Nothing distinguishes the five types at render time. NotificationProvider delegates every notification to the host's onToast callback regardless of displayType, so a banner and an inline notification both surface as a toast. The value is no longer inert — it reaches the delegate — but no delegate acts on it.
Concretely, packages/react/src/context/NotificationContext.tsx calls onToast(notification) for every item. The console's handler (useConsoleActionRuntime) routes that to sonner.
What "done" would look like
Each spec type gets a presentation that is actually distinct:
| Type |
Expected presentation |
toast |
transient overlay (today's behavior — already correct) |
snackbar |
bottom-anchored transient with an optional single action |
banner |
persistent, page-width strip at the top of the content area |
alert |
modal-ish blocking acknowledgement |
inline |
rendered in place by the surface that raised it, not floating |
Open questions worth settling before implementing:
- Who owns banner/inline placement? They are not overlays — a banner needs a slot in the app shell's content area, and
inline needs the raising surface to render it. That likely means the context exposes the items and the shell/surface subscribes, rather than a single onToast-style delegate.
- Is
alert a modal? If so it should probably route through the existing ModalHandler rather than growing a second modal path.
- Does
snackbar earn a distinct component, or is it a sonner variant? If the latter, say so in the spec's describe() rather than implying a separate surface.
Why it is worth doing
An author picking banner gets a toast — the output is plausible and wrong, which is the #2901 Tier 1 shape rather than Tier 2. It did not rank as Tier 1 in the audit only because displayType was inert at the time (nothing read it at all), so the audit filed it under "silently absent". Now that the contract is live, the mismatch is a wrong-presentation bug.
Refs #2942, #2901
Follow-up from #2942 / PR #3008, which closed the contract half of this and deliberately left the presentation half open. Recording it so it doesn't get lost.
What #3008 fixed
NotificationContextused to storedisplayTypeon the item and never read it, under a union that claimed alignment withNotificationTypeSchemawhile carrying a renderer-localmodaland missingalert/inline. That is now correct:toast | snackbar | banner | alert | inline), withmodalkept as a deprecated legacy spelling that presents asalert;notify()materializes the declared type (spec defaulttoast), so a consumer can branch on it;NotificationPositionSchema(underscores), hyphens kept for stored items;What is still open
Nothing distinguishes the five types at render time.
NotificationProviderdelegates every notification to the host'sonToastcallback regardless ofdisplayType, so abannerand aninlinenotification both surface as a toast. The value is no longer inert — it reaches the delegate — but no delegate acts on it.Concretely,
packages/react/src/context/NotificationContext.tsxcallsonToast(notification)for every item. The console's handler (useConsoleActionRuntime) routes that to sonner.What "done" would look like
Each spec type gets a presentation that is actually distinct:
toastsnackbarbanneralertinlineOpen questions worth settling before implementing:
inlineneeds the raising surface to render it. That likely means the context exposes the items and the shell/surface subscribes, rather than a singleonToast-style delegate.alerta modal? If so it should probably route through the existingModalHandlerrather than growing a second modal path.snackbarearn a distinct component, or is it a sonner variant? If the latter, say so in the spec'sdescribe()rather than implying a separate surface.Why it is worth doing
An author picking
bannergets a toast — the output is plausible and wrong, which is the #2901 Tier 1 shape rather than Tier 2. It did not rank as Tier 1 in the audit only becausedisplayTypewas inert at the time (nothing read it at all), so the audit filed it under "silently absent". Now that the contract is live, the mismatch is a wrong-presentation bug.Refs #2942, #2901