Skip to content

Notification displayType is honored in the contract but every type still presents as a toast #3014

Description

@os-zhuang

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:

  1. 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.
  2. Is alert a modal? If so it should probably route through the existing ModalHandler rather than growing a second modal path.
  3. 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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions