Getting Started
You are two security-tagged patch releases behind at this point: v2.2.6 carried a security fix and v2.2.7 ships "[security] Improved in Security" again. Treat v2.2.7 as the release you want on production soon, and read both changelogs/issues before rolling:
composer update <vendor>/<package> --with-dependencies
php artisan vendor:publish --tag=<package>-config # review diff; new notifications + search keys likely
php artisan vendor:publish --tag=<package>-lang # menu/search/notification strings
php artisan vendor:publish --tag=<package>-assets # modal mega search + quick add JS/CSS changed
php artisan migrate # notifications table, quotes tables if package-owned
php artisan config:clear && php artisan route:clear && php artisan view:clear
In production, follow with php artisan optimize and make sure a queue worker is running — the new notifications feature and PDF exports both expect one.
Where to look first:
- Published config — new keys for notifications (channels, queue, throttling), mega search (per-section limits, enabled sections), dashboard data filter defaults, and Quick Add entries. Diff against the previous published config rather than overwriting.
- Migrations — the notifications feature usually needs the
notifications table (or a package-owned equivalent). Confirm whether the package ships its own migration or relies on your app's.
- Routes/middleware — the mega search endpoint and the modal partial are the new attack surface. Confirm both are behind auth, tenant scoping, and rate limiting.
- Published assets/views — the mega search modal is front-end heavy; stale published assets will render a broken or non-functional modal.
- Quotes in Quick Add — check whether quotes are a new entity (permissions, policies, numbering) or just a new Quick Add shortcut onto existing models.
Best first use case: on staging, open the mega search modal as a low-privilege user in a non-default locale, confirm the per-section counts match what that user is allowed to see, then Quick Add a quote and trigger one notification. That flow exercises all four headline features and surfaces the two most common failure modes (unscoped search results, missing queue worker).
Implementation Patterns
Notifications
Assume the package dispatches Laravel notifications and let it — don't rebuild delivery yourself. Your job is wiring, preferences, and queue hygiene:
// config/<package>.php
'notifications' => [
'queue' => 'notifications',
'channels' => ['database', 'mail'],
'throttle_per_minute' => 10,
],
- Run a dedicated worker:
php artisan queue:work --queue=notifications,exports.
- If channels are user-configurable, persist preferences in your own table and override the package's
via() through a published notification class or an event listener — not by editing vendor files.
- Make notifications idempotent-ish in your listeners: retries after a timeout can double-send mail. Store a marker keyed by the domain event ID.
- If the package supports broadcasting, add the private channel authorization callback before enabling it; otherwise every authenticated user can subscribe to another tenant's channel.
Notification::fake();
// act
Notification::assertSentTo($user, DashboardReportReady::class);
Mega search (modal + per-section counts)
This is the biggest new integration risk. A search endpoint that fans out across sections and returns counts is a data-exposure surface as much as a performance one.
- Scope before you search. Every section's query must go through the same policy/tenant scope used by its normal index page. Enforce this centrally rather than trusting the package's per-section resolvers:
Search::section('leads', fn ($q, $term) => Lead::query()
->whereBelongsTo($tenant)
->visibleTo($request->user())
->where('name', 'like', "%{$term}%"));
- Counts need the same scope as results. A count of 412 records the user cannot open is still an information leak. If the package computes counts separately, verify they use the identical constraints.
- Cap and paginate. Per-section limits (e.g. 5 shown, 50 max scanned) plus a global debounce (250–350ms) in the modal JS. Search-as-you-type against
LIKE '%term%' will not use indexes — consider Laravel Scout for the large entities and keep the package search for the rest.
- Rate limit the endpoint (
throttle:30,1 at minimum) and log slow searches. Search is the easiest endpoint to accidentally DoS with a single open modal.
- Feature-test the counts, not just the results: assert a user sees
0 for sections they cannot access.
Quick Add quotes
Quotes are a different shape from the other Quick Add entries: they carry a customer, line items, currency, tax, and a numbering sequence.
- If quotes are a new entity in this release, publish/run migrations first, then confirm the package creates the quote number server-side (not from the request). Never accept a
quote_number from the modal payload.
- Quick Add is a fast path, so validation must still be server-side and complete. A modal that only validates a customer name will happily create quotes with no line items.
- Add a policy and a
QuotePolicy before exposing the button broadly; check the create route directly (the button being hidden is not authorization).
- Decide early whether "convert quote to invoice" exists — if not, plan the follow-up yourself rather than assuming the feature set is complete.
Dashboard data filter
The filter was reworked, so anything you built around the old range/filter behavior needs re-verification.
- Normalize filters in your own layer; don't pass raw query strings into aggregate queries:
$filter = DashboardFilter::fromRequest($request); // validated value object
$key = "dash:{$tenant->id}:{$filter->hash()}:{$filter->timezone}";
- Cache aggregates keyed by
tenant + filter + timezone and invalidate on data change; the new filter makes it much easier for users to re-slice data and hit the DB repeatedly.
- If you had custom filter options or a saved-filter UI, diff your published views/config against the new stubs — a reworked filter often renames the config keys and emits different request parameters.
MariaDB, locale, PDF
These carry over unchanged from the previous release and are still worth exercising once per upgrade:
mysql driver pointed at MariaDB, or the mariadb connection type on newer Laravel; run php artisan db:show and php artisan migrate --pretend.
- Locale set in middleware via
app()->setLocale(), with overrides in lang/vendor/<package>/.
- PDF exports queued, written to a private disk, served via short-lived signed URLs.
Gotchas and Tips
- Two security releases in a row. v2.2.6 patched something; v2.2.7 says "[security] Improved in Security" again. That pattern often means the first fix was incomplete. Read both advisories/PRs, then re-audit export routes, file downloads, and now the mega search endpoint — it's the newest and broadest read path in the package.
- Search counts as an access-control bug. The most likely regression in this release is a count or result leaking across tenants or past a policy. Test with a deliberately under-privileged user and assert zeros, not just "no results rendered."
- Notifications silently do nothing. If the queue worker isn't running or
QUEUE_CONNECTION=sync is set with a mail driver that fails, notifications vanish or 500 on the request. Check failed_jobs first when "notifications don't arrive."
- Missing notifications table. If the package expects the standard
notifications table and you never ran php artisan notifications:table, database-channel notifications fail at dispatch time, often only for some users.
- Stale published assets. A changed mega search modal with cached JS looks like "the feature is broken." Republish assets, then clear view cache and confirm your Vite/Mix build picks them up in production.
- Config cache hides new keys. Same as last time: new notification/search/filter keys resolve to
null until you clear and rebuild config cache. Symptom is usually a silently disabled feature, not an exception.
- Quick Add bypasses your UX assumptions. Quotes created through the modal may skip screens where you enforce required fields or side effects (tax calc, approvals). Audit the create path, not just the full form.
- Reworked dashboard filter breaks saved state. If you persisted filter state, hash it into cache keys and treat old keys as stale. Verify timezone handling explicitly — it's the most common off-by-a-day bug in date filters.
- Menu/translation overrides from the v2.2.6 upgrade still apply. Re-diff published config and lang files; this release adds search and notification strings that will fall back to raw keys if you only partially published.
- PDF + CJK still needs fonts. Locale support is not PDF font support; register a CJK font in the PDF template CSS and test with real Japanese/Chinese report data.
- MariaDB dialect testing. Reports and search queries that pass on SQLite/MySQL can fail under MariaDB's
ONLY_FULL_GROUP_BY or strict JSON handling. Run at least one CI job against a real MariaDB service.
- Prefer extension points: published config, published lang, custom report classes, export drivers, notification channel overrides via events, and per-section search resolvers. The package is moving quickly (menus, locales, search, filters all touched in two releases) — vendor edits will cost you on every patch.
- Test hooks that pay off:
Notification::fake(), Storage::fake('exports') plus %PDF magic-byte assertions, app()->setLocale() in feature tests, and a search test asserting per-section counts per role.