Three additions to the operations story, and three pre-existing defects found while building them.
Failed jobs viewer
The queue runs on database and this kit dispatches four scheduled jobs. Failures landed in failed_jobs and nothing surfaced them — no screen, no command, not one reference to the table. The health check reports the queue is alive, which says nothing about the jobs that died on it: a user's scheduled deletion could fail and nobody would know.
Administration → Failed jobs lists the job name and the first line of its exception, with the full trace on demand. Retry, retry-all and delete are gated on manage-failed-jobs; view-failed-jobs is read-only.
Notification preferences
A complete notification system — scheduling, targeting, Reverb broadcast — with no way to opt out of anything. Users can now mute announcement scopes (system, release) and severities from Account → Notifications.
Preferences filter the unread count as well as the list, because a bell showing 2 over a list of 1 is worse than either alone. Muting is stored rather than subscribing, so a severity added in a later release reaches everyone by default instead of being silently missing. Notifications addressed to you personally cannot be muted — silencing those means an account nobody can be told about.
Download your data
Account → Account → Download returns a JSON file with your profile, roles and permissions, notification preferences, sign-in history and the notifications addressed to you. Assembled field by field rather than serialising the model, so a future column cannot leak the password hash or the two-factor seed.
Defects fixed along the way
User declared both $guarded and $fillable. Laravel uses $fillable when both are present, so $guarded = ['id'] was dead config that read as though everything but id were assignable — while any column missing from $fillable was silently dropped on mass assignment. If you added a column to User and its value never saved, this was why.
lang/en/notifications.php defined 'account' twice, identically. PHP keeps the last, so edits to the first block silently did nothing.
DataTableService::process advertises Builder|QueryBuilder but its applySearch only accepts the Eloquent builder, so passing a query builder throws at runtime. Worked around here with a FailedJob model; the signature is still misleading.
Upgrading
One migration adds users.notification_preferences:
php artisan migrate
php artisan db:seed --class=PermissionRoleSeeder # adds view-failed-jobs / manage-failed-jobsExisting users keep every notification until they choose otherwise.
Full Changelog: v3.2.0...v3.3.0