geekcodev/filament-max-chat
Filament plugin for an operator chat with MAX messenger users. View dialogs with unread badges, reply with HTML formatting and media attachments, receive real-time Echo/Reverb updates with a poll fallback, and configure permissions, channel, disk and navigation via config.
MaxMessageService::totalUnreadCount()), a JSON unread endpoint (UnreadCountController), and read-state transitions (markRead()). That shifts the build-vs-buy question from "can we avoid writing a chat backend?" to "do we only need to write the differentiated layer?" — typically the composer UI, notification preferences, and integration with your existing user/tenant model.direction = 'in', read_at) are exposed and indexable rather than buried in a black box. A PM can approve extending this with per-channel or per-tenant unread rules without a rewrite, as long as custom conditions are backed by indexes — the failure mode below is precisely unindexed predicate drift./up healthcheck misses → container restarts. For a PM, that is a capacity-planning input, not just a bug fix: any messaging surface that renders a badge on every page view is a per-request cost multiplied by traffic, and it belongs in launch readiness reviews alongside load testing.Schema::hasIndex guard means host applications that already added an equivalent index won't fail on install. That removes a common blocker for platform teams shipping shared infrastructure to multiple downstream products.Consider it when:
composer audit clean.php artisan migrate.Look elsewhere when:
(direction, read_at); richer predicates will need their own indexing strategy and you should plan for it deliberately rather than discover it under load.vendor/), leaving clean-resolution verification to CI.For executives — one paragraph: This is a low-risk maintenance release for our chat/inbox dependency that fixes a real production failure mode rather than adding features. The unread-message badge was being counted with a full table scan on every page render; as message history grew, that scan occupied application workers long enough that the container's health check failed and the container was restarted. The fix is a single database index, delivered as an additive migration. There are no breaking changes, no new configuration, no new environment variables, and no new translation keys — the update is a routine php artisan migrate during the next deploy. The strategic point for us is that a feature as small as an unread badge can take down a service as history accumulates, so message-volume growth belongs in capacity planning rather than being treated as a background concern.
For engineering — the substance:
MaxMessageService::totalUnreadCount() and UnreadCountController filter on direction = 'in' and read_at is null. None of the existing indexes — (max_chat_id, created_at), (user_id, chat_id, created_at), (created_at) — cover that predicate, so the count ran as a sequential scan. markRead() was never the problem; it leads with max_chat_id and still uses the existing index.max_chat_messages_unread_index on (direction, read_at), added in a separate additive migration (0000_02_000003_add_unread_index_to_max_chat_messages.php). It serves both the navigation badge and the JSON unread endpoint. A partial index on read_at is null was considered and rejected as non-portable to MySQL.up() guards on Schema::hasIndex, so it is idempotent and will not fail where a host application already added an equivalent index. down() drops it cleanly. No public signatures changed, no config keys or values changed, no translation keys touched.MaxChatMessagesSchemaTest asserts index presence, removal on down(), re-creation on up(), and idempotency when the index already exists. Full suite: 271 tests / 628 assertions, 98.31% line coverage (989/1006), PHPStan max and PHP CS Fixer clean, composer audit clean.vendor/; that step is delegated to CI. Also note composer audit required COMPOSER_IPRESOLVE=4 in this environment due to Packagist reachability — an environment quirk, not a package defect, but worth knowing if your CI network is similarly constrained.How can I help you explore Laravel packages today?