You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(notifications): the config, position and action variant are read instead of forked or ignored (#3014 follow-up)
The last of the notification contract. After displayType (#3071) and icon
(#3076), four gaps of the same family were left:
- the config was 3/4 inert: only `defaultDuration` was ever read, while
`maxVisible` / `stacking` were carried and ignored and NotificationBanners
capped at a hard-coded 3 of its own;
- its field names forked from `NotificationConfigSchema` (`position` vs
`defaultPosition`, a renderer-local `stacking` boolean, no `pauseOnHover`);
- a notification could not declare a `position` at all — the #3008 parity
guard asserted the position VOCABULARY while nothing positioned anything
by it;
- `NotificationActionButton.variant` was the shadcn Button vocabulary
(`default | destructive | outline`) under a spec-shaped name, forking
`NotificationActionSchema.variant` (`primary | secondary | link`).
Positioning resolves as `notification.position ?? config.defaultPosition ??
nothing`, and "nothing" is a real answer: declared → the surface pins itself
there and `presentNotificationToast` passes it per-toast so the contract beats
the container; undeclared → the surface keeps its own anchor, or defers to the
host's toast chrome. That asymmetry is the decision — the sonner container also
serves toasts that are NOT spec notifications (the action runtime's own
`toast.*` calls), so it stays the fallback authority for placement, never a
competing one. Hence `defaultPosition` has no fabricated default: "the host
didn't say" has to be representable.
`maxVisible` / `stackDirection` now drive every stacking surface through one
shared `visibleNotificationStack`; `pauseOnHover` holds a transient timer and
resumes it with the time it had left, which needed the provider to track live
timers instead of fire-and-forget setTimeouts. Legacy spellings still resolve:
`position` folds into `defaultPosition`, `stacking: false` reads as
`maxVisible: 1`.
`onToast` gains the resolved config as a second argument (one-arg handlers are
unaffected), and the spec-parity guard gained the action-variant vocabulary —
the one notification enum it did not cover.
Verified in the running console, in one frame: an undeclared toast stays where
the sonner container puts it (bottom-right), a toast declaring `top_left` moves
there, and a snackbar declaring `top_right` leaves its bottom anchor.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
0 commit comments