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
Notifications toolkit: a BaseNotification that formats one message for every channel, database notification views and a latest-notifications component, model associations, icons and redirects, branded mail, optional translatable and Reverb.
Built to the next-gen general rules: CI with test and coverage badges, full docs for people and agents, Laravel Boost guideline and skill, Javaabu codestyle, PHP 8.4 / Laravel 12+ (Laravel packages) or TypeScript (JS packages), Dhivehi and RTL, Blade and Inertia React for views, overridable controllers and policies with javaabu/permissions.
Built like javaabu/exports v2 (the reference for routes, overridable controllers, views in both stacks, Reverb with a polling fallback), on Laravel's own notification system rather than beside it.
Architecture
BaseNotification (extends Illuminate\Notifications\Notification, implements sms-notifications' SmsNotification). A notification defines message(object $notifiable): NotificationMessage once. The base class turns it into toMail() (branded MailMessage), toArray()/toDatabase(), toBroadcast(), toFcm() (when laravel-notification-channels/fcm is installed) and toSms() (through javaabu/sms-notifications, which this package requires). Per-channel tweaks go on the message (->sms('…'), ->mail(fn (MailMessage $mail) => …), ->fcm(fn …)), or by overriding toMail() etc. as usual.
Channels from preferences: via() takes the notification's channels ($channels, default from config), drops the ones the notifiable can't receive (no email, no phone number, FCM not installed), then asks the notifiable: a HasNotificationPreferences contract with a ready trait storing opt-outs in a JSON column. $required_channels can't be opted out of. Channel aliases sms and fcm map to the real channel classes.
NotificationMessage: title, body (lines), action text/URL, icon, colour, thumbnail, the subject model, extra data. Notification types can set defaults ($icon, $color).
Database modelJavaabu\Notifications\Models\DatabaseNotification (extends Laravel's, configurable), with a subject morph (new nullable columns, written by a database channel that extends Laravel's). A Notifiable trait replaces Laravel's so the relation, broadcast channel and preferences use the package's model. Click redirects: GET notifications/{id} marks it read and redirects to the action URL (or the subject's notificationUrl()).
Routes by Route::notifications([...guard, prefix, name, middleware, controller]): index, latest (bell JSON), read/unread, read-all, destroy, the redirect. One controller, overridable from config, the macro or the container. JSON for expectsJson(), Blade or Inertia pages otherwise (notifications.views).
Policy and permissions: users always manage their own notifications (no permission needed, so the bell works for everyone). view_others_notifications and delete_others_notifications are registered with javaabu/permissions for support/admin use. Overridable via config or Gate::policy().
Views: Blade with javaabu/forms v2 components (notifications::notifications.index, <x-notifications::bell>, and a small notifications::bell script through manifest); Inertia React with @javaabu/forms and Onu (notifications::Notifications/Index, NotificationBell built on Onu's NotificationCenter, useNotifications hook), shipped as @javaabu/notifications through javaabu/manifest.
Live updates: when Reverb is installed (or forced), Laravel's broadcast channel sends new notifications on a private channel per notifiable, authorized for multi-auth; the bell listens through window.Echo. Otherwise it polls the latest route. Same switch as exports (notifications.broadcasting.enabled = auto/true/false).
Branded mail: notifications.mail.branding (name, URL, logo, colours, footer). A javaabu mail theme rendered from that config, and <x-mail::branded>, a message layout with the logo and footer. dir="rtl" and right-aligned text for Dhivehi. Apps' Mailables can use the same theme and layout.
Translatable (optional): the notifiable's locale (HasLocalePreference, or a locale attribute) is used for every channel. When javaabu/translatable is enabled, the locale is checked against its active languages, and database notifications store the title/body in every active language, so the bell and the page show the reader's current language. Detected through Support\Translations, as the contract describes.
The view namespace is notifications, shared with Laravel's own notifications::email. The package never ships email.blade.php, so Laravel's stays in place.
javaabu/stencil isn't used: the only form is a two-field filter, which doesn't meet the "larger forms" bar.
FCM is optional (suggest) because its channel brings in the Firebase SDK. SMS is required, since javaabu/sms-notifications has no dependencies beyond illuminate/support.
No notification-preferences page in v1: the API and storage are in place for apps (and a later version) to build one.
Final summary: javaabu/notifications v1.0 is done on main
Every next-gen issue is closed except #14 (registering with docs.javaabu.com, for you). CI is green on every job: PHP 8.4/8.5 × Laravel 12/13 × lowest/stable, Pint, the React and Blade-script job (typecheck and Vitest), a leg without translatable, FCM and Reverb, and a check that javaabu/helpers and javaabu/settings ^1 aren't production dependencies. 78 PHPUnit tests and 16 Vitest tests.
What was built
BaseNotification: one definition, every channel #2BaseNotification: a notification defines message() once (NotificationMessage: title, lines, action, icon, colour, thumbnail, subject, data, plus per-channel sms(), mail(), fcm()), and gets toMail() (branded), toDatabase()/toArray(), toBroadcast(), toFcm() (laravel-notification-channels/fcm, optional) and toSms() (through javaabu/sms-notifications' configured driver). via() keeps the listed channels the notifiable has a route for and hasn't turned off (HasNotificationPreferences, with the ManagesNotificationPreferences JSON-column trait; * and per-type keys). notifications.required_channels can't be turned off. Every channel is built in the notifiable's locale (HasLocalePreference or a locale attribute).
Model associations, icons, colours, thumbnails and click redirects #3 Associations and redirects: a subject morph (new nullable columns, written by a database channel that extends Laravel's), NotificationSubject::notificationUrl()/notificationThumbnail() fallbacks, icons (Onu semantic names), colours mapped to Onu tones, and notifications.show, which marks a notification read and redirects.
Overridable controllers, policies and default permissions (RBAC, multi-auth) #12 Routes, controller, RBAC: Route::notifications([guard, prefix, name, middleware, controller]); one overridable controller (config, macro option or container); NotificationPolicy (a permissions ResourcePolicy) overridable from config, permissions.policies or Gate::policy(). Everyone manages their own notifications without a permission; view_others_notifications and delete_others_notifications are registered for support roles. Tested on two guards, including a user and an admin with the same id.
Live notifications with Reverb (optional) #7 Live updates: with Reverb installed (or forced), new database notifications broadcast on notifications.{type}.{id}, authorized for that notifiable whatever its guard. The bell and page listen with Echo, or poll latest otherwise (skipped while the tab is hidden).
Branded mail #6 Branded mail: notifications.mail.branding (name, URL, logo, colours, footer); a theme rendered from those colours; <x-mail::branded> (HTML and text) for notification mail and Mailables. Mail is right to left for RTL locales.
Optional javaabu/translatable support #13 Translatable (optional): with javaabu/translatable on, notifiable locales are checked against its active languages, and database notifications store their title, body and action text in every active language so each reader sees their own. Detected in Support\Translations. Tested with it, switched off, and in CI without it installed.
Dhivehi localization and RTL #10 Dhivehi and RTL: every string is in lang/en.json and lang/dv.json, kept in sync by a test. Views use logical utilities only (also tested), and the tests use Thaana.
Dhivehi review: have a native speaker check the table in docs/advanced-usage/dhivehi-and-rtl.md (for example ނޯޓިފިކޭޝަން vs. އެންގުން, and the mail greeting).
Release: once forms v2, manifest, permissions v3 and translatable v3 are on Packagist, drop the VCS repositories (and the github: npm dev dependency on @javaabu/forms), then tag v1.0.0 and submit to Packagist.
Follow-up from Javaabu/manifest#17 (719f99c): the React page and components' npm packages (@javaabu/forms, @javaabu/onu, React, @inertiajs/react) are now declared with for: 'pages'. The Blade script imports none of them, so a Blade app's manifest:check only asks for Vite. Tests cover a Blade-only app and an Inertia app; the docs, Boost skill and changelog are updated. CI is green.
Activity
n4ff4h commentedon Nov 5, 2026
Implementation plan
Built like
javaabu/exportsv2 (the reference for routes, overridable controllers, views in both stacks, Reverb with a polling fallback), on Laravel's own notification system rather than beside it.Architecture
BaseNotification(extendsIlluminate\Notifications\Notification, implements sms-notifications'SmsNotification). A notification definesmessage(object $notifiable): NotificationMessageonce. The base class turns it intotoMail()(brandedMailMessage),toArray()/toDatabase(),toBroadcast(),toFcm()(whenlaravel-notification-channels/fcmis installed) andtoSms()(throughjavaabu/sms-notifications, which this package requires). Per-channel tweaks go on the message (->sms('…'),->mail(fn (MailMessage $mail) => …),->fcm(fn …)), or by overridingtoMail()etc. as usual.via()takes the notification's channels ($channels, default from config), drops the ones the notifiable can't receive (no email, no phone number, FCM not installed), then asks the notifiable: aHasNotificationPreferencescontract with a ready trait storing opt-outs in a JSON column.$required_channelscan't be opted out of. Channel aliasessmsandfcmmap to the real channel classes.NotificationMessage: title, body (lines), action text/URL, icon, colour, thumbnail, the subject model, extra data. Notification types can set defaults ($icon,$color).Javaabu\Notifications\Models\DatabaseNotification(extends Laravel's, configurable), with asubjectmorph (new nullable columns, written by a database channel that extends Laravel's). ANotifiabletrait replaces Laravel's so the relation, broadcast channel and preferences use the package's model. Click redirects:GET notifications/{id}marks it read and redirects to the action URL (or the subject'snotificationUrl()).Route::notifications([...guard, prefix, name, middleware, controller]): index, latest (bell JSON), read/unread, read-all, destroy, the redirect. One controller, overridable from config, the macro or the container. JSON forexpectsJson(), Blade or Inertia pages otherwise (notifications.views).view_others_notificationsanddelete_others_notificationsare registered with javaabu/permissions for support/admin use. Overridable via config orGate::policy().notifications::notifications.index,<x-notifications::bell>, and a smallnotifications::bellscript through manifest); Inertia React with @javaabu/forms and Onu (notifications::Notifications/Index,NotificationBellbuilt on Onu'sNotificationCenter,useNotificationshook), shipped as@javaabu/notificationsthrough javaabu/manifest.window.Echo. Otherwise it polls thelatestroute. Same switch as exports (notifications.broadcasting.enabled= auto/true/false).notifications.mail.branding(name, URL, logo, colours, footer). Ajavaabumail theme rendered from that config, and<x-mail::branded>, a message layout with the logo and footer.dir="rtl"and right-aligned text for Dhivehi. Apps' Mailables can use the same theme and layout.HasLocalePreference, or alocaleattribute) is used for every channel. When javaabu/translatable is enabled, the locale is checked against its active languages, and database notifications store the title/body in every active language, so the bell and the page show the reader's current language. Detected throughSupport\Translations, as the contract describes.Order of work
BaseNotification,NotificationMessage, preferences, SMS and FCMDecisions / open questions
notifications, shared with Laravel's ownnotifications::email. The package never shipsemail.blade.php, so Laravel's stays in place.suggest) because its channel brings in the Firebase SDK. SMS is required, since javaabu/sms-notifications has no dependencies beyondilluminate/support.n4ff4h commentedon Nov 5, 2026
Final summary: javaabu/notifications v1.0 is done on
mainEvery
next-genissue is closed except #14 (registering with docs.javaabu.com, for you). CI is green on every job: PHP 8.4/8.5 × Laravel 12/13 × lowest/stable, Pint, the React and Blade-script job (typecheck and Vitest), a leg without translatable, FCM and Reverb, and a check thatjavaabu/helpersandjavaabu/settings ^1aren't production dependencies. 78 PHPUnit tests and 16 Vitest tests.What was built
BaseNotification: a notification definesmessage()once (NotificationMessage: title, lines, action, icon, colour, thumbnail, subject, data, plus per-channelsms(),mail(),fcm()), and getstoMail()(branded),toDatabase()/toArray(),toBroadcast(),toFcm()(laravel-notification-channels/fcm, optional) andtoSms()(through javaabu/sms-notifications' configured driver).via()keeps the listed channels the notifiable has a route for and hasn't turned off (HasNotificationPreferences, with theManagesNotificationPreferencesJSON-column trait;*and per-type keys).notifications.required_channelscan't be turned off. Every channel is built in the notifiable's locale (HasLocalePreferenceor alocaleattribute).subjectmorph (new nullable columns, written by a database channel that extends Laravel's),NotificationSubject::notificationUrl()/notificationThumbnail()fallbacks, icons (Onu semantic names), colours mapped to Onu tones, andnotifications.show, which marks a notification read and redirects.Route::notifications([guard, prefix, name, middleware, controller]); one overridable controller (config, macro option or container);NotificationPolicy(a permissionsResourcePolicy) overridable from config,permissions.policiesorGate::policy(). Everyone manages their own notifications without a permission;view_others_notificationsanddelete_others_notificationsare registered for support roles. Tested on two guards, including a user and an admin with the same id.<x-notifications::bell>with a manifest script. The React pagenotifications::Notifications/Index(@javaabu/forms and Onu), plusNotificationBell(Onu'sNotificationCenter),NotificationListItemanduseNotifications, all as@javaabu/notificationsthrough javaabu/manifest.BellData::make()supplies Inertia's shared props.notifications.{type}.{id}, authorized for that notifiable whatever its guard. The bell and page listen with Echo, or polllatestotherwise (skipped while the tab is hidden).notifications.mail.branding(name, URL, logo, colours, footer); a theme rendered from those colours;<x-mail::branded>(HTML and text) for notification mail and Mailables. Mail is right to left for RTL locales.Support\Translations. Tested with it, switched off, and in CI without it installed.lang/en.jsonandlang/dv.json, kept in sync by a test. Views use logical utilities only (also tested), and the tests use Thaana.llms.txt,AGENTS.md/CLAUDE.md, the Boost guideline, and thejavaabu-notificationsskill with a common-mistakes reference.Decisions / intentionally out of scope
Javaabu\Notifications\Concerns\Notifiableinstead of Laravel's trait (same API, the package's model, a guard-safe broadcast channel).notifications, shared with Laravel's ownnotifications::email. The package never shipsemail.blade.php.label(),preferenceKey(),setNotificationPreference()).illuminate/support.<x-forms::icon>, whose icon set is smaller than Onu's. The icon partial can be overridden.Cross-package
NotificationCentershould take anunreadCountprop, because its badge counts only the loaded items (10 by default). Not blocking.For you
CODECOV_TOKENavailable to this repo (org secret). The badge stays blank until then.docs/advanced-usage/dhivehi-and-rtl.md(for example ނޯޓިފިކޭޝަން vs. އެންގުން, and the mail greeting).repositories(and thegithub:npm dev dependency on @javaabu/forms), then tag v1.0.0 and submit to Packagist.n4ff4h commentedon Nov 6, 2026
Follow-up from Javaabu/manifest#17 (719f99c): the React page and components' npm packages (
@javaabu/forms,@javaabu/onu, React,@inertiajs/react) are now declared withfor: 'pages'. The Blade script imports none of them, so a Blade app'smanifest:checkonly asks for Vite. Tests cover a Blade-only app and an Inertia app; the docs, Boost skill and changelog are updated. CI is green.