Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Filament Headroom Laravel Package

phoenix1331/filament-headroom

Filament Headroom adds a Headroom.js-powered collapsing header to your Filament panels: the top bar hides as you scroll down and slides back when you scroll up, freeing vertical space while keeping navigation within reach. Lightweight and easy to configure.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Filament 5 panel plugin that renders AI usage dashboards on top of the phoenix1331/laravel-ai-headroom (estimation/calibration) and phoenix1331/laravel-ai-headroom-ledger (recorded requests) packages. It is read-only: it visualises whatever the ledger recorded, so wire up the ledger first and confirm rows are landing before judging the UI.

Requirements are strict and current: PHP 8.3+, Laravel 12 or 13, Filament 5, laravel-ai-headroom ^0.1 and laravel-ai-headroom-ledger ^0.1.1.

composer require phoenix1331/filament-headroom:^0.1
php artisan filament:assets   # publishes the plugin stylesheet

Register the plugin in your panel provider:

use Phoenix1331\FilamentHeadroom\HeadroomPlugin;

public function panel(Panel $panel): Panel
{
    return $panel->plugin(HeadroomPlugin::make());
}

By default the dashboard is only reachable in the local environment. Grant real access immediately, otherwise staging and production will silently 403:

HeadroomPlugin::make()
    ->authorize(fn ($user) => $user->hasRole('admin') || $user->can('view-ai-usage'));

Where to look first: open /headroom/requests and filter to your busiest model. That table is the ground truth — time, tokens, context used, cost, outcome, count method — and every row links to a request detail page showing estimated versus reported input tokens, failover models, per-part input breakdown and, for agent runs, each step's context growth. From there, /headroom/models answers the second question most teams have ("is Headroom's estimate any good yet?") via calibration status: off, no data yet, learning, or active with a correction factor.

A good first use case is a cost/context review: set defaultRange('7d'), watch spend trend and average/peak context on the overview, then use "Closest to the limit" to find the requests that nearly blew their context window and are the obvious candidates for trimming prompts or adding summarisation.

Implementation Patterns

Plugin options are the configuration surface. Everything is chained on HeadroomPlugin::make(): navigationGroup(), navigationIcon() (globally or per page), navigationSort(), defaultRange('7d'|'30d'|'90d'), tagLabel(fn ($tag) => ...), pages([...]), widgets([...]), scopeToTenant(), allowForgettingTags().

Reuse widgets on your own dashboards. Rather than sending people to /headroom, register the package widgets on an existing panel dashboard and let them follow the plugin's authorisation and enabled-widget set. Add the range picker to your dashboard's filters form with RangeSelect::make() so the widgets share the same Range enum and selected period as the built-in pages:

protected function getHeaderWidgets(): array
{
    return [
        // widget classes shipped by the package
    ];
}

public function getFiltersFormSchema(): array
{
    return [RangeSelect::make()];
}

Swap or trim built-in pages. pages() and widgets() accept subclasses, so the common path for customisation is: keep the built-in page, subclass it, override the table query or columns, and register the subclass instead. Disabled pages simply aren't registered (so /headroom/requests 404s rather than erroring), but note the request detail page is registered only when the Requests page is.

Tenant panels. On a multi-tenant panel, scopeToTenant('tenant_id') makes every page, widget, filter option, run and detail view read only ledger entries whose context tag matches the current tenant key. Pass a second argument to map the tenant to a different stored value. Pair it with tagLabel() so filters and detail pages show tenant names instead of raw IDs:

HeadroomPlugin::make()
    ->scopeToTenant('tenant_id', fn ($tenant) => $tenant->getKey())
    ->tagLabel('tenant_id', fn ($value) => Tenant::find($value)?->name ?? $value);

Data subject requests. allowForgettingTags(fn ($user) => ...) enables a "Forget a tag value" action on the Requests page that calls the ledger's forgetTag() after confirmation. Keep it a separate, narrower authorisation than dashboard access — it is a destructive operation.

Grouping by run. The Requests page has a ?group=run mode: one row per agent run with step count, reported tokens, peak context, cost and how many steps warned/blocked/failed. Since it's a query parameter, it is linkable — put ?group=run in runbooks and incident docs.

Translations. Publish and override English strings with php artisan vendor:publish --tag=filament-headroom-translations before shipping to non-English users; every user-facing string is translatable.

Gotchas and Tips

  • "Nothing appears" is almost always authorisation. Access is denied outside local by default. If the pages 404 for everyone including you, check authorize() before anything else.
  • Disabled widgets are hidden everywhere. The widgets() option governs the built-in pages and any dashboard where you registered the widgets yourself. Disabling in one place removes it from all of them — that's intentional, but surprising if you expected per-dashboard control.
  • Currencies are never converted. Spend is shown per currency; a second currency appears as its own named line rather than being summed. Don't expect a single total if your ledger records mixed currencies, and don't build reporting on the assumption that it does.
  • Watch the badges. "Estimate", "Includes estimates" and "Approximate" mean the figure is not billed cost. If finance is reading these screens, be explicit about that; the <x-filament-headroom::cost> component is available if you render costs in your own views and want the same labelling.
  • Tenant scoping with no current tenant matches nothing — an empty dashboard rather than an error. If a tenant panel shows zero rows, check tenant resolution order before assuming the ledger is empty.
  • Forgetting tags and tenant scoping are mutually exclusive. The action is never available under tenant scoping because forgetTag() erases the value across every tenant. If you need DSAR erasure on a tenant panel, it has to be a separate, non-tenant admin surface.
  • Filters are remembered for the session. Range, agent, provider, model, outcome, count method and tag filters persist — so "my numbers differ from yours" is often a stale filter, not a data problem. Clearing the session or the filters is the first debug step.
  • Empty outcome widgets may be a recording gap, not a UI bug. Blocked/warned/failed counts only exist if the ledger records those requests. If you record only successful calls, the widget will be legitimately empty.
  • Re-publish assets on upgrade. The plugin ships a filament-headroom stylesheet; run php artisan filament:assets after every version bump or charts and colour-blind-validated palettes will render unstyled.
  • Extension points: subclass pages/widgets and register via pages()/widgets(), override translations for wording, and use RangeSelect + the shared Range enum to keep any custom widget consistent with the built-in range semantics rather than rolling your own period picker.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core