Skip to content

feat(useNotifications): add new plugin composable#146

Merged
johnleider merged 67 commits intomasterfrom
feat/notifications-plugin
Mar 23, 2026
Merged

feat(useNotifications): add new plugin composable#146
johnleider merged 67 commits intomasterfrom
feat/notifications-plugin

Conversation

@johnleider
Copy link
Copy Markdown
Member

@johnleider johnleider commented Mar 5, 2026

Summary

Adds useNotifications composable and Snackbar compound component for notification lifecycle management.

useNotifications

  • Registry + Queue architecture: registry stores full notification lifecycle (permanent record), queue manages toast display surface with FIFO ordering and auto-dismiss
  • State mutations: read, unread, seen, archive, unarchive, snooze, wake
  • Bulk operations: readAll, archiveAll, clear
  • Plugin system via createNotificationsPlugin with createPluginContext trinity pattern
  • send() creates notification + enqueues for toast; seed() creates notification in registry only (no toast) for historical items
  • Adapter interface for third-party integration (Novu, Knock) with inbound/outbound event sync
  • Novu adapter: seeds from novu.notifications.list(), real-time via notification_received, outbound sync for all 5 lifecycle events
  • Knock adapter: seeds from feed.fetch() page events, real-time via items.received.realtime, outbound sync for read/archive

Snackbar

  • Compound component: Portal, Queue, Root, Content, Action, Close
  • Portal teleports to body, coordinates z-index via useStack
  • Queue connects to useNotifications by namespace, exposes items via slot, pause/resume on hover and focus (WCAG 2.2.1) with reference-counted reasons to prevent mouse/focus race
  • Root provides dismiss context, defaults to role="status" (overridable via attrs)
  • Dynamic context keys with suffix pattern matching Dialog/Pagination — all sub-components accept namespace prop
  • Content and Action are headless Atom wrappers
  • Close auto-wires to nearest Root context with noop fallback for standalone use

Quality

  • Full test coverage: composable lifecycle, adapter inbound/outbound/dispose, component rendering/context/integration
  • Knock adapter tests, Novu adapter tests (including seed, dispose race, all 5 outbound events)
  • Typecheck, lint, all 3999 tests passing
  • Documentation pages for both useNotifications and Snackbar with live examples
  • Maturity entries added (no since — will be set at release)

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Mar 5, 2026

Open in StackBlitz

commit: 63ed230

@johnleider johnleider force-pushed the feat/notifications-plugin branch 15 times, most recently from a54eb68 to 50d5acc Compare March 12, 2026 00:18
- Types: NotificationInput, NotificationTicket, NotificationsContext
- Core: createNotifications wrapping createQueue with lifecycle state
  mutations (read/seen/archive/snooze), bulk ops, reactive counts
- Plugin: createNotificationsContext, createNotificationsPlugin, useNotifications
- Adapter: event-driven integration hook for external services
- Barrel export from composables/index.ts
Core lifecycle, state mutations, events, and adapter integration.
Built-in adapters for popular notification services:
- createFcmAdapter: Firebase Cloud Messaging (inbound)
- createOneSignalAdapter: OneSignal Web SDK (inbound)
- createKnockAdapter: Knock feeds (inbound + outbound)

Available via `@vuetify/v0/notifications` subpath export.
primaryAction/secondaryAction are component-level concerns.
Use the data bag for custom payloads instead.
Aligns with plugin convention — useTheme, useLogger, useFeatures, etc.
- Add fallback to createPluginContext (useNotifications works without provider)
- Remove double onScopeDispose (queue already self-disposes)
- Fix stale "actions" references in JSDoc and docs
- Fix mermaid diagram arrow direction
- Fix dismiss(id) → unregister(id) in docs
- Remove redundant type assertions
- Add @see URL to @module JSDoc
- unreadItems: notifications without readAt
- archivedItems: notifications with archivedAt
- snoozedItems: notifications with snoozedUntil

unreadCount now derives from unreadItems.length for efficiency.
- Adapter: interface with setup/dispose, wired in plugin setup callback
  with app.onUnmount cleanup (matches useFeatures/useTheme pattern)
- Adapter removed from NotificationsOptions, lives on NotificationsPluginOptions
- createNotifications() is now adapter-agnostic
- Bulk ops (readAll/archiveAll) wrapped in queue.batch()
- Updated all 3 adapters (FCM, OneSignal, Knock) to implement interface
- Manual sync pattern confirmed correct (reactive:true doesn't help)
Interactive example demonstrating derived collections, ticket
convenience methods, seen/read distinction, bulk operations,
and state lifecycle diagram.
Eliminates shallowRef + triggerRef + event subscriptions in favor of
useProxyRegistry which handles reactive state via shallowReactive.
Derived collections (unreadItems, archivedItems, snoozedItems) now
compute from proxy.values instead of items.value.
Bulk ops (readAll/archiveAll) now emit per-item domain events so
adapters like Knock correctly sync state. Fixes type safety issues,
adapter memory leaks, and aligns fallback with established patterns.
…ean itemStyle

- SnackbarRoot: use useId() as prop default (stable) instead of inside toRef getter
- NotificationProvider example: remove stale :severity prop (no longer on SnackbarRoot)
- queue.vue example: remove unused _total param from itemStyle
- index.test.ts: fix scoped slot render syntax in integration test
… Novu tests, docs fixes

- Add app.onUnmount for context/queue disposal before adapter guard
- Add tests for 6 untested events (unread, seen, archived, unarchived, snoozed, unsnoozed)
- Add Novu adapter test suite (8 tests)
- Strengthen persistent timeout test to verify timer survival
- Document Snackbar.Close queue removal behavior
- Replace inline inject type with SnackbarQueueContext
- Remove empty SnackbarCloseSlotProps interface
- Add JSDoc to NotificationTicket.dismiss
- Simplify Knock adapter dispose with single ctx guard
…s, Knock tests

Snackbar contexts switched from static to dynamic keys with suffix
pattern (matching Pagination/Dialog). All sub-components now accept
namespace prop defaulting to 'v0:notifications'.

Inspection fixes: Novu adapter disposed guard for async race, default
role="status" on SnackbarRoot, isNull type guards, fallback stub
unregister/batch, Novu adapter docs, queue anatomy v-slot.

Added Knock adapter tests and missing Novu outbound sync tests.
…e, remove dead Portal context

Add seed() method to NotificationsAdapterContext — enriches like send()
but bypasses the toast queue. Adapters now use seed() for initial fetch
and send() for realtime, preventing historical notification toast flood.

SnackbarQueue pause/resume now uses reference-counted reasons (hover,
focus) so mouse-leave doesn't resume while keyboard focus is inside.
focusout checks relatedTarget to avoid spurious cycles from internal
focus movement (WCAG 2.2.1).

Remove dead SnackbarContext from Portal — z-index is applied via
inline style and slot props, no child consumed the context.
…attrs, pause fixes

Override register and onboard in createNotifications return to route
through enrich(), matching the createQueue precedent. Raw registry
methods would produce tickets without createdAt or lifecycle methods.

SnackbarQueue: move event handlers to slotProps.attrs pattern.
Re-pause queue after dismiss when hover/focus is active. Resume on
unmount to prevent permanently paused queue.

SnackbarRoot: move role="status" to slotProps.attrs for explicit
override. Rename queueContext to queue.

Add defineSlots to SnackbarContent and SnackbarAction. Add Novu
keyword to docs frontmatter.
Adds regex for `export const [createXContext, createXPlugin, useX]`
pattern. Picks up plugin trinity exports (useNotifications,
useTheme, useLocale, etc.) that were missed by the function-only scan.
…, Close attrs, JSDoc

Drop seed() in favor of register() — matches every other registry-based
composable. register() overrides the base to hydrate with timestamps
and lifecycle methods. onboard() is the bulk variant.

Remove SnackbarAction — no precedent across components, zero behavior,
users use native <button> or their own component.

SnackbarClose: move onClick, aria-label, type to slotProps.attrs for
renderless mode support. Export SnackbarCloseSlotProps.

NotificationSeverity now uses Extensible<> — users can add custom
values like 'critical' with autocomplete for defaults.

Add full JSDoc with @param, @remarks, @see, @example on all
useNotifications functions and interfaces.
…cs links

NotificationSeverity uses Extensible<> for custom values with
autocomplete defaults.

Snackbar barrel: full JSDoc with script imports, descriptions
mentioning key behaviors, and renderless mode notes.

Snackbar docs: link useNotifications in intro, queue example,
and dismiss warning.
…plugin

# Conflicts:
#	packages/0/src/maturity.json
@johnleider johnleider self-assigned this Mar 23, 2026
@johnleider johnleider added this to the v0.2.0 milestone Mar 23, 2026
Remove @mdi/js from playground dependencies (flagged by knip).

Add tests for useNotifications fallback, onboard hydration,
SnackbarQueue focusin/focusout/unmount cleanup, and Portal
default teleport path.
…nd components

Add tests for fallback queue/context stubs, hydrate auto-ID, queue
cascade on unregister, Close branch for custom as prop, Portal
slotProps, Queue focusout with null relatedTarget, Queue unmount
when not paused, Knock page event with missing items, Novu empty
list response, and Novu disposed flag on realtime events.
@johnleider johnleider changed the title feat(useNotifications): notification lifecycle management with adapters feat(useNotifications): add new plugin composable Mar 23, 2026
@johnleider johnleider merged commit e6dcb42 into master Mar 23, 2026
12 of 14 checks passed
@johnleider johnleider deleted the feat/notifications-plugin branch March 23, 2026 20:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant