Merge pull request 'spec: refine 057 + extend 058' (#67) from 058-tenant-ui-polish into dev

This commit is contained in:
ahmido 2026-01-21 11:29:41 +00:00
commit 5745461654
21 changed files with 2513 additions and 126 deletions

View File

@ -0,0 +1,173 @@
## Source of Truth
If any Filament behavior is uncertain, lookup the exact section in:
- docs/research/filament-v5-notes.md
and prefer that over guesses.
# SECTION B — FILAMENT V5 BLUEPRINT (EXECUTABLE RULES)
# Filament Blueprint (v5)
## 1) Non-negotiables
- Filament v5 requires Livewire v4.0+.
- Laravel 11+: register panel providers in `bootstrap/providers.php` (never `bootstrap/app.php`).
- Global search hard rule: If a Resource should appear in Global Search, it must have an Edit or View page; otherwise it will return no results.
- Destructive actions must execute via `Action::make(...)->action(...)` and include `->requiresConfirmation()` (no exceptions).
- Prefer render hooks + CSS hook classes over publishing Filament internal views.
Sources:
- https://filamentphp.com/docs/5.x/upgrade-guide
- https://filamentphp.com/docs/5.x/panel-configuration
- https://filamentphp.com/docs/5.x/resources/global-search
- https://filamentphp.com/docs/5.x/actions/modals
- https://filamentphp.com/docs/5.x/advanced/render-hooks
- https://filamentphp.com/docs/5.x/styling/css-hooks
## 2) Directory & naming conventions
- Default to Filament discovery conventions for Resources/Pages/Widgets unless you adopt modular architecture.
- Clusters: directory layout is recommended, not mandatory; functional behavior depends on `$cluster`.
Sources:
- https://filamentphp.com/docs/5.x/navigation/clusters
- https://filamentphp.com/docs/5.x/advanced/modular-architecture
## 3) Panel setup defaults
- Default to a single `/admin` panel unless multiple audiences/configs demand multiple panels.
- Verify provider registration (Laravel 11+: `bootstrap/providers.php`) when adding a panel.
- Use `path()` carefully; treat `path('')` as a high-risk change requiring route conflict review.
- Assets policy:
- Panel-only assets: register via panel config.
- Shared/plugin assets: register via `FilamentAsset::register()`.
- Deployment must include `php artisan filament:assets`.
Sources:
- https://filamentphp.com/docs/5.x/panel-configuration
- https://filamentphp.com/docs/5.x/advanced/assets
## 4) Navigation & information architecture
- Use nav groups + sort order intentionally; apply conditional visibility for clarity, but enforce authorization separately.
- Use clusters to introduce hierarchy and sub-navigation when sidebar complexity grows.
- Treat cluster code structure as a recommendation (organizational benefit), not a required rule.
- User menu:
- Configure via `userMenuItems()` with Action objects.
- Never put destructive actions there without confirmation + authorization.
Sources:
- https://filamentphp.com/docs/5.x/navigation/overview
- https://filamentphp.com/docs/5.x/navigation/clusters
- https://filamentphp.com/docs/5.x/navigation/user-menu
## 5) Resource patterns
- Default to Resources for CRUD; use custom pages for non-CRUD tools/workflows.
- Global search:
- If a resource is intended for global search: ensure Edit/View page exists.
- Otherwise disable global search for that resource (dont “expect it to work”).
- If global search renders relationship-backed details: eager-load via global search query override.
- For very large datasets: consider disabling term splitting (only when needed).
Sources:
- https://filamentphp.com/docs/5.x/resources/overview
- https://filamentphp.com/docs/5.x/resources/global-search
## 6) Page lifecycle & query rules
- Treat relationship-backed rendering in aggregate contexts (global search details, list summaries) as requiring eager loading.
- Prefer render hooks for layout injection; avoid publishing internal views.
Sources:
- https://filamentphp.com/docs/5.x/resources/global-search
- https://filamentphp.com/docs/5.x/advanced/render-hooks
## 7) Infolists vs RelationManagers (decision tree)
- Interactive CRUD / attach / detach under owner record → RelationManager.
- Pick existing related record(s) inside owner form → Select / CheckboxList relationship fields.
- Inline CRUD inside owner form → Repeater.
- Default performance stance: RelationManagers stay lazy-loaded unless explicit UX justification exists.
Sources:
- https://filamentphp.com/docs/5.x/resources/managing-relationships
- https://filamentphp.com/docs/5.x/infolists/overview
## 8) Form patterns (validation, reactivity, state)
- Default: minimize server-driven reactivity; only use it when schema/visibility/requirements must change server-side.
- Prefer “on blur” semantics for chatty inputs when using reactive behavior (per docs patterns).
- Custom field views must obey state binding modifiers.
Sources:
- https://filamentphp.com/docs/5.x/forms/overview
- https://filamentphp.com/docs/5.x/forms/custom-fields
## 9) Table & action patterns
- Tables: always define a meaningful empty state (and empty-state actions where appropriate).
- Actions:
- Execution actions use `->action(...)`.
- Destructive actions add `->requiresConfirmation()`.
- Navigation-only actions should use `->url(...)`.
- UNVERIFIED: do not assert modal/confirmation behavior for URL-only actions unless verified.
Sources:
- https://filamentphp.com/docs/5.x/tables/empty-state
- https://filamentphp.com/docs/5.x/actions/modals
## 10) Authorization & security
- Enforce panel access in non-local environments as documented.
- UI visibility is not security; enforce policies/access checks in addition to hiding UI.
- Bulk operations: explicitly decide between “Any” policy methods vs per-record authorization.
Sources:
- https://filamentphp.com/docs/5.x/users/overview
- https://filamentphp.com/docs/5.x/resources/deleting-records
## 11) Notifications & UX feedback
- Default to explicit success/error notifications for user-triggered mutations that arent instantly obvious.
- Treat polling as a cost; set intervals intentionally where polling is used.
Sources:
- https://filamentphp.com/docs/5.x/notifications/overview
- https://filamentphp.com/docs/5.x/widgets/stats-overview
## 12) Performance defaults
- Heavy assets: prefer on-demand loading (`loadedOnRequest()` + `x-load-css` / `x-load-js`) for heavy dependencies.
- Styling overrides use CSS hook classes; layout injection uses render hooks; avoid view publishing.
Sources:
- https://filamentphp.com/docs/5.x/advanced/assets
- https://filamentphp.com/docs/5.x/styling/css-hooks
- https://filamentphp.com/docs/5.x/advanced/render-hooks
## 13) Testing requirements
- Test pages/relation managers/widgets as Livewire components.
- Test actions using Filaments action testing guidance.
- Do not mount non-Livewire classes in Livewire tests.
Sources:
- https://filamentphp.com/docs/5.x/testing/overview
- https://filamentphp.com/docs/5.x/testing/testing-actions
## 14) Forbidden patterns
- Mixing Filament v3/v4 APIs into v5 code.
- Any mention of Livewire v3 for Filament v5.
- Registering panel providers in `bootstrap/app.php` on Laravel 11+.
- Destructive actions without `->requiresConfirmation()`.
- Shipping heavy assets globally when on-demand loading fits.
- Publishing Filament internal views as a default customization technique.
Sources:
- https://filamentphp.com/docs/5.x/upgrade-guide
- https://filamentphp.com/docs/5.x/panel-configuration
- https://filamentphp.com/docs/5.x/actions/modals
- https://filamentphp.com/docs/5.x/advanced/assets
## 15) Agent output contract
For any implementation request, the agent must explicitly state:
1) Livewire v4.0+ compliance.
2) Provider registration location (Laravel 11+: `bootstrap/providers.php`).
3) For each globally searchable resource: whether it has Edit/View page (or global search is disabled).
4) Which actions are destructive and how confirmation + authorization is handled.
5) Asset strategy: global vs on-demand and where `filament:assets` runs in deploy.
6) Testing plan: which pages/widgets/relation managers/actions are covered.
Sources:
- https://filamentphp.com/docs/5.x/upgrade-guide
- https://filamentphp.com/docs/5.x/panel-configuration
- https://filamentphp.com/docs/5.x/resources/global-search
- https://filamentphp.com/docs/5.x/advanced/assets
- https://filamentphp.com/docs/5.x/testing/testing-actions

View File

@ -0,0 +1,79 @@
# SECTION C — AI REVIEW CHECKLIST (STRICT CHECKBOXES)
## Version Safety
- [ ] Filament v5 explicitly targets Livewire v4.0+ (no Livewire v3 references anywhere).
- Source: https://filamentphp.com/docs/5.x/upgrade-guide — “Upgrading Livewire”
- [ ] All references are Filament `/docs/5.x/` only (no v3/v4 docs, no legacy APIs).
- [ ] Upgrade assumptions match the v5 upgrade guide requirements and steps.
- Source: https://filamentphp.com/docs/5.x/upgrade-guide — “New requirements”
## Panel & Navigation
- [ ] Laravel 11+: panel providers are registered in `bootstrap/providers.php` (not `bootstrap/app.php`).
- Source: https://filamentphp.com/docs/5.x/panel-configuration — “Creating a new panel”
- [ ] Panel `path()` choices are intentional and do not conflict with existing routes (especially `path('')`).
- Source: https://filamentphp.com/docs/5.x/panel-configuration — “Changing the path”
- [ ] Cluster usage is correctly configured (discovery + `$cluster` assignments).
- Source: https://filamentphp.com/docs/5.x/navigation/clusters — “Creating a cluster”
- [ ] Cluster semantics (sub-navigation + grouped navigation behavior) are understood and verified against the clusters docs.
- Source: https://filamentphp.com/docs/5.x/navigation/clusters — “Introduction”
- [ ] Cluster directory structure is treated as recommended, not mandatory.
- Source: https://filamentphp.com/docs/5.x/navigation/clusters — “Code structure recommendations for panels using clusters”
- [ ] User menu items are registered via `userMenuItems()` and permission-gated where needed.
- Source: https://filamentphp.com/docs/5.x/navigation/user-menu — “Introduction”
## Resource Structure
- [ ] `$recordTitleAttribute` is set for any resource intended for global search.
- Source: https://filamentphp.com/docs/5.x/resources/overview — “Record titles”
- [ ] Hard rule enforced: every globally searchable resource has an Edit or View page; otherwise global search is disabled for it.
- Source: https://filamentphp.com/docs/5.x/resources/global-search — “Setting global search result titles”
- [ ] Relationship-backed global search details are eager-loaded via the global search query override.
- Source: https://filamentphp.com/docs/5.x/resources/global-search — “Adding extra details to global search results”
## Infolists & Relations
- [ ] Each relationship uses the correct tool (RelationManager vs Select/CheckboxList vs Repeater) based on required interaction.
- Source: https://filamentphp.com/docs/5.x/resources/managing-relationships — “Choosing the right tool for the job”
- [ ] RelationManagers remain lazy-loaded by default unless theres an explicit UX justification.
- Source: https://filamentphp.com/docs/5.x/resources/managing-relationships — “Disabling lazy loading”
## Forms
- [ ] Server-driven reactivity is minimal; chatty inputs do not trigger network requests unnecessarily.
- Source: https://filamentphp.com/docs/5.x/forms/overview — “Reactive fields on blur”
- [ ] Custom field views obey state binding modifiers (no hardcoded `wire:model` without modifiers).
- Source: https://filamentphp.com/docs/5.x/forms/custom-fields — “Obeying state binding modifiers”
## Tables & Actions
- [ ] Tables define a meaningful empty state (and empty-state actions where appropriate).
- Source: https://filamentphp.com/docs/5.x/tables/empty-state — “Adding empty state actions”
- [ ] All destructive actions execute via `->action(...)` and include `->requiresConfirmation()`.
- Source: https://filamentphp.com/docs/5.x/actions/modals — “Confirmation modals”
- [ ] No checklist rule assumes confirmation/modals for `->url(...)` actions unless verified in docs (UNVERIFIED behavior must not be asserted as fact).
- Source: https://filamentphp.com/docs/5.x/actions/modals — “Confirmation modals”
## Authorization & Security
- [ ] Panel access is enforced for non-local environments as documented.
- Source: https://filamentphp.com/docs/5.x/users/overview — “Authorizing access to the panel”
- [ ] UI visibility is not treated as authorization; policies/access checks still enforce boundaries.
- [ ] Bulk operations intentionally choose between “Any” policy methods vs per-record authorization where required.
- Source: https://filamentphp.com/docs/5.x/resources/deleting-records — “Authorization”
## UX & Notifications
- [ ] User-triggered mutations provide explicit success/error notifications when outcomes arent instantly obvious.
- Source: https://filamentphp.com/docs/5.x/notifications/overview — “Introduction”
- [ ] Polling (widgets/notifications) is configured intentionally (interval set or disabled) to control load.
- Source: https://filamentphp.com/docs/5.x/widgets/stats-overview — “Live updating stats (polling)”
## Performance
- [ ] Heavy frontend assets are loaded on-demand using `loadedOnRequest()` + `x-load-css` / `x-load-js` where appropriate.
- Source: https://filamentphp.com/docs/5.x/advanced/assets — “Lazy loading CSS” / “Lazy loading JavaScript”
- [ ] Styling overrides use CSS hook classes discovered via DevTools (no brittle selectors by default).
- Source: https://filamentphp.com/docs/5.x/styling/css-hooks — “Discovering hook classes”
## Testing
- [ ] Livewire tests mount Filament pages/relation managers/widgets (Livewire components), not static resource classes.
- Source: https://filamentphp.com/docs/5.x/testing/overview — “What is a Livewire component when using Filament?”
- [ ] Actions that mutate data are covered using Filaments action testing guidance.
- Source: https://filamentphp.com/docs/5.x/testing/testing-actions — “Testing actions”
## Deployment / Ops
- [ ] `php artisan filament:assets` is included in the deployment process when using registered assets.
- Source: https://filamentphp.com/docs/5.x/advanced/assets — “The FilamentAsset facade”

View File

@ -1,5 +1,14 @@
{ {
"general": { "general": {
"previewFeatures": false "previewFeatures": false
},
"mcpServers": {
"laravel-boost": {
"command": "vendor/bin/sail",
"args": [
"artisan",
"boost:mcp"
]
}
} }
} }

View File

@ -11,6 +11,7 @@ ## Active Technologies
- PHP 8.4.x (Laravel 12) + Laravel 12, Filament v4, Livewire v3 (feat/047-inventory-foundations-nodes) - PHP 8.4.x (Laravel 12) + Laravel 12, Filament v4, Livewire v3 (feat/047-inventory-foundations-nodes)
- PostgreSQL (JSONB for `InventoryItem.meta_jsonb`) (feat/047-inventory-foundations-nodes) - PostgreSQL (JSONB for `InventoryItem.meta_jsonb`) (feat/047-inventory-foundations-nodes)
- PostgreSQL (JSONB in `operation_runs.context`, `operation_runs.summary_counts`) (056-remove-legacy-bulkops) - PostgreSQL (JSONB in `operation_runs.context`, `operation_runs.summary_counts`) (056-remove-legacy-bulkops)
- PHP 8.4.15 (Laravel 12.47.0) + Filament v5.0.0, Livewire v4.0.1 (058-tenant-ui-polish)
- PHP 8.4.15 (feat/005-bulk-operations) - PHP 8.4.15 (feat/005-bulk-operations)
@ -30,9 +31,9 @@ ## Code Style
PHP 8.4.15: Follow standard conventions PHP 8.4.15: Follow standard conventions
## Recent Changes ## Recent Changes
- 058-tenant-ui-polish: Added PHP 8.4.15 (Laravel 12.47.0) + Filament v5.0.0, Livewire v4.0.1
- 058-tenant-ui-polish: Added [if applicable, e.g., PostgreSQL, CoreData, files or N/A]
- 056-remove-legacy-bulkops: Added PHP 8.4.x + Laravel 12, Filament v4, Livewire v3 - 056-remove-legacy-bulkops: Added PHP 8.4.x + Laravel 12, Filament v4, Livewire v3
- feat/047-inventory-foundations-nodes: Added PHP 8.4.x (Laravel 12) + Laravel 12, Filament v4, Livewire v3
- feat/042-inventory-dependencies-graph: Added PHP 8.4.x + Laravel 12, Filament v4, Livewire v3
<!-- MANUAL ADDITIONS START --> <!-- MANUAL ADDITIONS START -->

683
.github/copilot-instructions.md vendored Normal file
View File

@ -0,0 +1,683 @@
<laravel-boost-guidelines>
=== .ai/filament-v5-blueprint rules ===
## Source of Truth
If any Filament behavior is uncertain, lookup the exact section in:
- docs/research/filament-v5-notes.md
and prefer that over guesses.
# SECTION B — FILAMENT V5 BLUEPRINT (EXECUTABLE RULES)
# Filament Blueprint (v5)
## 1) Non-negotiables
- Filament v5 requires Livewire v4.0+.
- Laravel 11+: register panel providers in `bootstrap/providers.php` (never `bootstrap/app.php`).
- Global search hard rule: If a Resource should appear in Global Search, it must have an Edit or View page; otherwise it will return no results.
- Destructive actions must execute via `Action::make(...)->action(...)` and include `->requiresConfirmation()` (no exceptions).
- Prefer render hooks + CSS hook classes over publishing Filament internal views.
Sources:
- https://filamentphp.com/docs/5.x/upgrade-guide
- https://filamentphp.com/docs/5.x/panel-configuration
- https://filamentphp.com/docs/5.x/resources/global-search
- https://filamentphp.com/docs/5.x/actions/modals
- https://filamentphp.com/docs/5.x/advanced/render-hooks
- https://filamentphp.com/docs/5.x/styling/css-hooks
## 2) Directory & naming conventions
- Default to Filament discovery conventions for Resources/Pages/Widgets unless you adopt modular architecture.
- Clusters: directory layout is recommended, not mandatory; functional behavior depends on `$cluster`.
Sources:
- https://filamentphp.com/docs/5.x/navigation/clusters
- https://filamentphp.com/docs/5.x/advanced/modular-architecture
## 3) Panel setup defaults
- Default to a single `/admin` panel unless multiple audiences/configs demand multiple panels.
- Verify provider registration (Laravel 11+: `bootstrap/providers.php`) when adding a panel.
- Use `path()` carefully; treat `path('')` as a high-risk change requiring route conflict review.
- Assets policy:
- Panel-only assets: register via panel config.
- Shared/plugin assets: register via `FilamentAsset::register()`.
- Deployment must include `php artisan filament:assets`.
Sources:
- https://filamentphp.com/docs/5.x/panel-configuration
- https://filamentphp.com/docs/5.x/advanced/assets
## 4) Navigation & information architecture
- Use nav groups + sort order intentionally; apply conditional visibility for clarity, but enforce authorization separately.
- Use clusters to introduce hierarchy and sub-navigation when sidebar complexity grows.
- Treat cluster code structure as a recommendation (organizational benefit), not a required rule.
- User menu:
- Configure via `userMenuItems()` with Action objects.
- Never put destructive actions there without confirmation + authorization.
Sources:
- https://filamentphp.com/docs/5.x/navigation/overview
- https://filamentphp.com/docs/5.x/navigation/clusters
- https://filamentphp.com/docs/5.x/navigation/user-menu
## 5) Resource patterns
- Default to Resources for CRUD; use custom pages for non-CRUD tools/workflows.
- Global search:
- If a resource is intended for global search: ensure Edit/View page exists.
- Otherwise disable global search for that resource (dont “expect it to work”).
- If global search renders relationship-backed details: eager-load via global search query override.
- For very large datasets: consider disabling term splitting (only when needed).
Sources:
- https://filamentphp.com/docs/5.x/resources/overview
- https://filamentphp.com/docs/5.x/resources/global-search
## 6) Page lifecycle & query rules
- Treat relationship-backed rendering in aggregate contexts (global search details, list summaries) as requiring eager loading.
- Prefer render hooks for layout injection; avoid publishing internal views.
Sources:
- https://filamentphp.com/docs/5.x/resources/global-search
- https://filamentphp.com/docs/5.x/advanced/render-hooks
## 7) Infolists vs RelationManagers (decision tree)
- Interactive CRUD / attach / detach under owner record → RelationManager.
- Pick existing related record(s) inside owner form → Select / CheckboxList relationship fields.
- Inline CRUD inside owner form → Repeater.
- Default performance stance: RelationManagers stay lazy-loaded unless explicit UX justification exists.
Sources:
- https://filamentphp.com/docs/5.x/resources/managing-relationships
- https://filamentphp.com/docs/5.x/infolists/overview
## 8) Form patterns (validation, reactivity, state)
- Default: minimize server-driven reactivity; only use it when schema/visibility/requirements must change server-side.
- Prefer “on blur” semantics for chatty inputs when using reactive behavior (per docs patterns).
- Custom field views must obey state binding modifiers.
Sources:
- https://filamentphp.com/docs/5.x/forms/overview
- https://filamentphp.com/docs/5.x/forms/custom-fields
## 9) Table & action patterns
- Tables: always define a meaningful empty state (and empty-state actions where appropriate).
- Actions:
- Execution actions use `->action(...)`.
- Destructive actions add `->requiresConfirmation()`.
- Navigation-only actions should use `->url(...)`.
- UNVERIFIED: do not assert modal/confirmation behavior for URL-only actions unless verified.
Sources:
- https://filamentphp.com/docs/5.x/tables/empty-state
- https://filamentphp.com/docs/5.x/actions/modals
## 10) Authorization & security
- Enforce panel access in non-local environments as documented.
- UI visibility is not security; enforce policies/access checks in addition to hiding UI.
- Bulk operations: explicitly decide between “Any” policy methods vs per-record authorization.
Sources:
- https://filamentphp.com/docs/5.x/users/overview
- https://filamentphp.com/docs/5.x/resources/deleting-records
## 11) Notifications & UX feedback
- Default to explicit success/error notifications for user-triggered mutations that arent instantly obvious.
- Treat polling as a cost; set intervals intentionally where polling is used.
Sources:
- https://filamentphp.com/docs/5.x/notifications/overview
- https://filamentphp.com/docs/5.x/widgets/stats-overview
## 12) Performance defaults
- Heavy assets: prefer on-demand loading (`loadedOnRequest()` + `x-load-css` / `x-load-js`) for heavy dependencies.
- Styling overrides use CSS hook classes; layout injection uses render hooks; avoid view publishing.
Sources:
- https://filamentphp.com/docs/5.x/advanced/assets
- https://filamentphp.com/docs/5.x/styling/css-hooks
- https://filamentphp.com/docs/5.x/advanced/render-hooks
## 13) Testing requirements
- Test pages/relation managers/widgets as Livewire components.
- Test actions using Filaments action testing guidance.
- Do not mount non-Livewire classes in Livewire tests.
Sources:
- https://filamentphp.com/docs/5.x/testing/overview
- https://filamentphp.com/docs/5.x/testing/testing-actions
## 14) Forbidden patterns
- Mixing Filament v3/v4 APIs into v5 code.
- Any mention of Livewire v3 for Filament v5.
- Registering panel providers in `bootstrap/app.php` on Laravel 11+.
- Destructive actions without `->requiresConfirmation()`.
- Shipping heavy assets globally when on-demand loading fits.
- Publishing Filament internal views as a default customization technique.
Sources:
- https://filamentphp.com/docs/5.x/upgrade-guide
- https://filamentphp.com/docs/5.x/panel-configuration
- https://filamentphp.com/docs/5.x/actions/modals
- https://filamentphp.com/docs/5.x/advanced/assets
## 15) Agent output contract
For any implementation request, the agent must explicitly state:
1) Livewire v4.0+ compliance.
2) Provider registration location (Laravel 11+: `bootstrap/providers.php`).
3) For each globally searchable resource: whether it has Edit/View page (or global search is disabled).
4) Which actions are destructive and how confirmation + authorization is handled.
5) Asset strategy: global vs on-demand and where `filament:assets` runs in deploy.
6) Testing plan: which pages/widgets/relation managers/actions are covered.
Sources:
- https://filamentphp.com/docs/5.x/upgrade-guide
- https://filamentphp.com/docs/5.x/panel-configuration
- https://filamentphp.com/docs/5.x/resources/global-search
- https://filamentphp.com/docs/5.x/advanced/assets
- https://filamentphp.com/docs/5.x/testing/testing-actions
=== .ai/filament-v5-checklist rules ===
# SECTION C — AI REVIEW CHECKLIST (STRICT CHECKBOXES)
## Version Safety
- [ ] Filament v5 explicitly targets Livewire v4.0+ (no Livewire v3 references anywhere).
- Source: https://filamentphp.com/docs/5.x/upgrade-guide — “Upgrading Livewire”
- [ ] All references are Filament `/docs/5.x/` only (no v3/v4 docs, no legacy APIs).
- [ ] Upgrade assumptions match the v5 upgrade guide requirements and steps.
- Source: https://filamentphp.com/docs/5.x/upgrade-guide — “New requirements”
## Panel & Navigation
- [ ] Laravel 11+: panel providers are registered in `bootstrap/providers.php` (not `bootstrap/app.php`).
- Source: https://filamentphp.com/docs/5.x/panel-configuration — “Creating a new panel”
- [ ] Panel `path()` choices are intentional and do not conflict with existing routes (especially `path('')`).
- Source: https://filamentphp.com/docs/5.x/panel-configuration — “Changing the path”
- [ ] Cluster usage is correctly configured (discovery + `$cluster` assignments).
- Source: https://filamentphp.com/docs/5.x/navigation/clusters — “Creating a cluster”
- [ ] Cluster semantics (sub-navigation + grouped navigation behavior) are understood and verified against the clusters docs.
- Source: https://filamentphp.com/docs/5.x/navigation/clusters — “Introduction”
- [ ] Cluster directory structure is treated as recommended, not mandatory.
- Source: https://filamentphp.com/docs/5.x/navigation/clusters — “Code structure recommendations for panels using clusters”
- [ ] User menu items are registered via `userMenuItems()` and permission-gated where needed.
- Source: https://filamentphp.com/docs/5.x/navigation/user-menu — “Introduction”
## Resource Structure
- [ ] `$recordTitleAttribute` is set for any resource intended for global search.
- Source: https://filamentphp.com/docs/5.x/resources/overview — “Record titles”
- [ ] Hard rule enforced: every globally searchable resource has an Edit or View page; otherwise global search is disabled for it.
- Source: https://filamentphp.com/docs/5.x/resources/global-search — “Setting global search result titles”
- [ ] Relationship-backed global search details are eager-loaded via the global search query override.
- Source: https://filamentphp.com/docs/5.x/resources/global-search — “Adding extra details to global search results”
## Infolists & Relations
- [ ] Each relationship uses the correct tool (RelationManager vs Select/CheckboxList vs Repeater) based on required interaction.
- Source: https://filamentphp.com/docs/5.x/resources/managing-relationships — “Choosing the right tool for the job”
- [ ] RelationManagers remain lazy-loaded by default unless theres an explicit UX justification.
- Source: https://filamentphp.com/docs/5.x/resources/managing-relationships — “Disabling lazy loading”
## Forms
- [ ] Server-driven reactivity is minimal; chatty inputs do not trigger network requests unnecessarily.
- Source: https://filamentphp.com/docs/5.x/forms/overview — “Reactive fields on blur”
- [ ] Custom field views obey state binding modifiers (no hardcoded `wire:model` without modifiers).
- Source: https://filamentphp.com/docs/5.x/forms/custom-fields — “Obeying state binding modifiers”
## Tables & Actions
- [ ] Tables define a meaningful empty state (and empty-state actions where appropriate).
- Source: https://filamentphp.com/docs/5.x/tables/empty-state — “Adding empty state actions”
- [ ] All destructive actions execute via `->action(...)` and include `->requiresConfirmation()`.
- Source: https://filamentphp.com/docs/5.x/actions/modals — “Confirmation modals”
- [ ] No checklist rule assumes confirmation/modals for `->url(...)` actions unless verified in docs (UNVERIFIED behavior must not be asserted as fact).
- Source: https://filamentphp.com/docs/5.x/actions/modals — “Confirmation modals”
## Authorization & Security
- [ ] Panel access is enforced for non-local environments as documented.
- Source: https://filamentphp.com/docs/5.x/users/overview — “Authorizing access to the panel”
- [ ] UI visibility is not treated as authorization; policies/access checks still enforce boundaries.
- [ ] Bulk operations intentionally choose between “Any” policy methods vs per-record authorization where required.
- Source: https://filamentphp.com/docs/5.x/resources/deleting-records — “Authorization”
## UX & Notifications
- [ ] User-triggered mutations provide explicit success/error notifications when outcomes arent instantly obvious.
- Source: https://filamentphp.com/docs/5.x/notifications/overview — “Introduction”
- [ ] Polling (widgets/notifications) is configured intentionally (interval set or disabled) to control load.
- Source: https://filamentphp.com/docs/5.x/widgets/stats-overview — “Live updating stats (polling)”
## Performance
- [ ] Heavy frontend assets are loaded on-demand using `loadedOnRequest()` + `x-load-css` / `x-load-js` where appropriate.
- Source: https://filamentphp.com/docs/5.x/advanced/assets — “Lazy loading CSS” / “Lazy loading JavaScript”
- [ ] Styling overrides use CSS hook classes discovered via DevTools (no brittle selectors by default).
- Source: https://filamentphp.com/docs/5.x/styling/css-hooks — “Discovering hook classes”
## Testing
- [ ] Livewire tests mount Filament pages/relation managers/widgets (Livewire components), not static resource classes.
- Source: https://filamentphp.com/docs/5.x/testing/overview — “What is a Livewire component when using Filament?”
- [ ] Actions that mutate data are covered using Filaments action testing guidance.
- Source: https://filamentphp.com/docs/5.x/testing/testing-actions — “Testing actions”
## Deployment / Ops
- [ ] `php artisan filament:assets` is included in the deployment process when using registered assets.
- Source: https://filamentphp.com/docs/5.x/advanced/assets — “The FilamentAsset facade”
=== foundation rules ===
# Laravel Boost Guidelines
The Laravel Boost guidelines are specifically curated by Laravel maintainers for this application. These guidelines should be followed closely to enhance the user's satisfaction building Laravel applications.
## Foundational Context
This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions.
- php - 8.4.15
- filament/filament (FILAMENT) - v5
- laravel/framework (LARAVEL) - v12
- laravel/prompts (PROMPTS) - v0
- livewire/livewire (LIVEWIRE) - v4
- laravel/mcp (MCP) - v0
- laravel/pint (PINT) - v1
- laravel/sail (SAIL) - v1
- pestphp/pest (PEST) - v4
- phpunit/phpunit (PHPUNIT) - v12
- tailwindcss (TAILWINDCSS) - v4
## Conventions
- You must follow all existing code conventions used in this application. When creating or editing a file, check sibling files for the correct structure, approach, naming.
- Use descriptive names for variables and methods. For example, `isRegisteredForDiscounts`, not `discount()`.
- Check for existing components to reuse before writing a new one.
## Verification Scripts
- Do not create verification scripts or tinker when tests cover that functionality and prove it works. Unit and feature tests are more important.
## Application Structure & Architecture
- Stick to existing directory structure - don't create new base folders without approval.
- Do not change the application's dependencies without approval.
## Frontend Bundling
- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `vendor/bin/sail npm run build`, `vendor/bin/sail npm run dev`, or `vendor/bin/sail composer run dev`. Ask them.
## Replies
- Be concise in your explanations - focus on what's important rather than explaining obvious details.
## Documentation Files
- You must only create documentation files if explicitly requested by the user.
=== boost rules ===
## Laravel Boost
- Laravel Boost is an MCP server that comes with powerful tools designed specifically for this application. Use them.
## Artisan
- Use the `list-artisan-commands` tool when you need to call an Artisan command to double check the available parameters.
## URLs
- Whenever you share a project URL with the user you should use the `get-absolute-url` tool to ensure you're using the correct scheme, domain / IP, and port.
## Tinker / Debugging
- You should use the `tinker` tool when you need to execute PHP to debug code or query Eloquent models directly.
- Use the `database-query` tool when you only need to read from the database.
## Reading Browser Logs With the `browser-logs` Tool
- You can read browser logs, errors, and exceptions using the `browser-logs` tool from Boost.
- Only recent browser logs will be useful - ignore old logs.
## Searching Documentation (Critically Important)
- Boost comes with a powerful `search-docs` tool you should use before any other approaches. This tool automatically passes a list of installed packages and their versions to the remote Boost API, so it returns only version-specific documentation specific for the user's circumstance. You should pass an array of packages to filter on if you know you need docs for particular packages.
- The 'search-docs' tool is perfect for all Laravel related packages, including Laravel, Inertia, Livewire, Filament, Tailwind, Pest, Nova, Nightwatch, etc.
- You must use this tool to search for Laravel-ecosystem documentation before falling back to other approaches.
- Search the documentation before making code changes to ensure we are taking the correct approach.
- Use multiple, broad, simple, topic based queries to start. For example: `['rate limiting', 'routing rate limiting', 'routing']`.
- Do not add package names to queries - package information is already shared. For example, use `test resource table`, not `filament 4 test resource table`.
### Available Search Syntax
- You can and should pass multiple queries at once. The most relevant results will be returned first.
1. Simple Word Searches with auto-stemming - query=authentication - finds 'authenticate' and 'auth'
2. Multiple Words (AND Logic) - query=rate limit - finds knowledge containing both "rate" AND "limit"
3. Quoted Phrases (Exact Position) - query="infinite scroll" - Words must be adjacent and in that order
4. Mixed Queries - query=middleware "rate limit" - "middleware" AND exact phrase "rate limit"
5. Multiple Queries - queries=["authentication", "middleware"] - ANY of these terms
=== php rules ===
## PHP
- Always use curly braces for control structures, even if it has one line.
### Constructors
- Use PHP 8 constructor property promotion in `__construct()`.
- <code-snippet>public function __construct(public GitHub $github) { }</code-snippet>
- Do not allow empty `__construct()` methods with zero parameters.
### Type Declarations
- Always use explicit return type declarations for methods and functions.
- Use appropriate PHP type hints for method parameters.
<code-snippet name="Explicit Return Types and Method Params" lang="php">
protected function isAccessible(User $user, ?string $path = null): bool
{
...
}
</code-snippet>
## Comments
- Prefer PHPDoc blocks over comments. Never use comments within the code itself unless there is something _very_ complex going on.
## PHPDoc Blocks
- Add useful array shape type definitions for arrays when appropriate.
## Enums
- Typically, keys in an Enum should be TitleCase. For example: `FavoritePerson`, `BestLake`, `Monthly`.
=== sail rules ===
## Laravel Sail
- This project runs inside Laravel Sail's Docker containers. You MUST execute all commands through Sail.
- Start services using `vendor/bin/sail up -d` and stop them with `vendor/bin/sail stop`.
- Open the application in the browser by running `vendor/bin/sail open`.
- Always prefix PHP, Artisan, Composer, and Node commands** with `vendor/bin/sail`. Examples:
- Run Artisan Commands: `vendor/bin/sail artisan migrate`
- Install Composer packages: `vendor/bin/sail composer install`
- Execute node commands: `vendor/bin/sail npm run dev`
- Execute PHP scripts: `vendor/bin/sail php [script]`
- View all available Sail commands by running `vendor/bin/sail` without arguments.
=== tests rules ===
## Test Enforcement
- Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass.
- Run the minimum number of tests needed to ensure code quality and speed. Use `vendor/bin/sail artisan test` with a specific filename or filter.
=== laravel/core rules ===
## Do Things the Laravel Way
- Use `vendor/bin/sail artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool.
- If you're creating a generic PHP class, use `vendor/bin/sail artisan make:class`.
- Pass `--no-interaction` to all Artisan commands to ensure they work without user input. You should also pass the correct `--options` to ensure correct behavior.
### Database
- Always use proper Eloquent relationship methods with return type hints. Prefer relationship methods over raw queries or manual joins.
- Use Eloquent models and relationships before suggesting raw database queries
- Avoid `DB::`; prefer `Model::query()`. Generate code that leverages Laravel's ORM capabilities rather than bypassing them.
- Generate code that prevents N+1 query problems by using eager loading.
- Use Laravel's query builder for very complex database operations.
### Model Creation
- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `vendor/bin/sail artisan make:model`.
### APIs & Eloquent Resources
- For APIs, default to using Eloquent API Resources and API versioning unless existing API routes do not, then you should follow existing application convention.
### Controllers & Validation
- Always create Form Request classes for validation rather than inline validation in controllers. Include both validation rules and custom error messages.
- Check sibling Form Requests to see if the application uses array or string based validation rules.
### Queues
- Use queued jobs for time-consuming operations with the `ShouldQueue` interface.
### Authentication & Authorization
- Use Laravel's built-in authentication and authorization features (gates, policies, Sanctum, etc.).
### URL Generation
- When generating links to other pages, prefer named routes and the `route()` function.
### Configuration
- Use environment variables only in configuration files - never use the `env()` function directly outside of config files. Always use `config('app.name')`, not `env('APP_NAME')`.
### Testing
- When creating models for tests, use the factories for the models. Check if the factory has custom states that can be used before manually setting up the model.
- Faker: Use methods such as `$this->faker->word()` or `fake()->randomDigit()`. Follow existing conventions whether to use `$this->faker` or `fake()`.
- When creating tests, make use of `vendor/bin/sail artisan make:test [options] {name}` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests.
### Vite Error
- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `vendor/bin/sail npm run build` or ask the user to run `vendor/bin/sail npm run dev` or `vendor/bin/sail composer run dev`.
=== laravel/v12 rules ===
## Laravel 12
- Use the `search-docs` tool to get version specific documentation.
- Since Laravel 11, Laravel has a new streamlined file structure which this project uses.
### Laravel 12 Structure
- No middleware files in `app/Http/Middleware/`.
- `bootstrap/app.php` is the file to register middleware, exceptions, and routing files.
- `bootstrap/providers.php` contains application specific service providers.
- **No app\Console\Kernel.php** - use `bootstrap/app.php` or `routes/console.php` for console configuration.
- **Commands auto-register** - files in `app/Console/Commands/` are automatically available and do not require manual registration.
### Database
- When modifying a column, the migration must include all of the attributes that were previously defined on the column. Otherwise, they will be dropped and lost.
- Laravel 11 allows limiting eagerly loaded records natively, without external packages: `$query->latest()->limit(10);`.
### Models
- Casts can and likely should be set in a `casts()` method on a model rather than the `$casts` property. Follow existing conventions from other models.
=== livewire/core rules ===
## Livewire Core
- Use the `search-docs` tool to find exact version specific documentation for how to write Livewire & Livewire tests.
- Use the `vendor/bin/sail artisan make:livewire [Posts\CreatePost]` artisan command to create new components
- State should live on the server, with the UI reflecting it.
- All Livewire requests hit the Laravel backend, they're like regular HTTP requests. Always validate form data, and run authorization checks in Livewire actions.
## Livewire Best Practices
- Livewire components require a single root element.
- Use `wire:loading` and `wire:dirty` for delightful loading states.
- Add `wire:key` in loops:
```blade
@foreach ($items as $item)
<div wire:key="item-{{ $item->id }}">
{{ $item->name }}
</div>
@endforeach
```
- Prefer lifecycle hooks like `mount()`, `updatedFoo()` for initialization and reactive side effects:
<code-snippet name="Lifecycle hook examples" lang="php">
public function mount(User $user) { $this->user = $user; }
public function updatedSearch() { $this->resetPage(); }
</code-snippet>
## Testing Livewire
<code-snippet name="Example Livewire component test" lang="php">
Livewire::test(Counter::class)
->assertSet('count', 0)
->call('increment')
->assertSet('count', 1)
->assertSee(1)
->assertStatus(200);
</code-snippet>
<code-snippet name="Testing a Livewire component exists within a page" lang="php">
$this->get('/posts/create')
->assertSeeLivewire(CreatePost::class);
</code-snippet>
=== pint/core rules ===
## Laravel Pint Code Formatter
- You must run `vendor/bin/sail bin pint --dirty` before finalizing changes to ensure your code matches the project's expected style.
- Do not run `vendor/bin/sail bin pint --test`, simply run `vendor/bin/sail bin pint` to fix any formatting issues.
=== pest/core rules ===
## Pest
### Testing
- If you need to verify a feature is working, write or update a Unit / Feature test.
### Pest Tests
- All tests must be written using Pest. Use `vendor/bin/sail artisan make:test --pest {name}`.
- You must not remove any tests or test files from the tests directory without approval. These are not temporary or helper files - these are core to the application.
- Tests should test all of the happy paths, failure paths, and weird paths.
- Tests live in the `tests/Feature` and `tests/Unit` directories.
- Pest tests look and behave like this:
<code-snippet name="Basic Pest Test Example" lang="php">
it('is true', function () {
expect(true)->toBeTrue();
});
</code-snippet>
### Running Tests
- Run the minimal number of tests using an appropriate filter before finalizing code edits.
- To run all tests: `vendor/bin/sail artisan test`.
- To run all tests in a file: `vendor/bin/sail artisan test tests/Feature/ExampleTest.php`.
- To filter on a particular test name: `vendor/bin/sail artisan test --filter=testName` (recommended after making a change to a related file).
- When the tests relating to your changes are passing, ask the user if they would like to run the entire test suite to ensure everything is still passing.
### Pest Assertions
- When asserting status codes on a response, use the specific method like `assertForbidden` and `assertNotFound` instead of using `assertStatus(403)` or similar, e.g.:
<code-snippet name="Pest Example Asserting postJson Response" lang="php">
it('returns all', function () {
$response = $this->postJson('/api/docs', []);
$response->assertSuccessful();
});
</code-snippet>
### Mocking
- Mocking can be very helpful when appropriate.
- When mocking, you can use the `Pest\Laravel\mock` Pest function, but always import it via `use function Pest\Laravel\mock;` before using it. Alternatively, you can use `$this->mock()` if existing tests do.
- You can also create partial mocks using the same import or self method.
### Datasets
- Use datasets in Pest to simplify tests which have a lot of duplicated data. This is often the case when testing validation rules, so consider going with this solution when writing tests for validation rules.
<code-snippet name="Pest Dataset Example" lang="php">
it('has emails', function (string $email) {
expect($email)->not->toBeEmpty();
})->with([
'james' => 'james@laravel.com',
'taylor' => 'taylor@laravel.com',
]);
</code-snippet>
=== pest/v4 rules ===
## Pest 4
- Pest v4 is a huge upgrade to Pest and offers: browser testing, smoke testing, visual regression testing, test sharding, and faster type coverage.
- Browser testing is incredibly powerful and useful for this project.
- Browser tests should live in `tests/Browser/`.
- Use the `search-docs` tool for detailed guidance on utilizing these features.
### Browser Testing
- You can use Laravel features like `Event::fake()`, `assertAuthenticated()`, and model factories within Pest v4 browser tests, as well as `RefreshDatabase` (when needed) to ensure a clean state for each test.
- Interact with the page (click, type, scroll, select, submit, drag-and-drop, touch gestures, etc.) when appropriate to complete the test.
- If requested, test on multiple browsers (Chrome, Firefox, Safari).
- If requested, test on different devices and viewports (like iPhone 14 Pro, tablets, or custom breakpoints).
- Switch color schemes (light/dark mode) when appropriate.
- Take screenshots or pause tests for debugging when appropriate.
### Example Tests
<code-snippet name="Pest Browser Test Example" lang="php">
it('may reset the password', function () {
Notification::fake();
$this->actingAs(User::factory()->create());
$page = visit('/sign-in'); // Visit on a real browser...
$page->assertSee('Sign In')
->assertNoJavascriptErrors() // or ->assertNoConsoleLogs()
->click('Forgot Password?')
->fill('email', 'nuno@laravel.com')
->click('Send Reset Link')
->assertSee('We have emailed your password reset link!')
Notification::assertSent(ResetPassword::class);
});
</code-snippet>
<code-snippet name="Pest Smoke Testing Example" lang="php">
$pages = visit(['/', '/about', '/contact']);
$pages->assertNoJavascriptErrors()->assertNoConsoleLogs();
</code-snippet>
=== tailwindcss/core rules ===
## Tailwind Core
- Use Tailwind CSS classes to style HTML, check and use existing tailwind conventions within the project before writing your own.
- Offer to extract repeated patterns into components that match the project's conventions (i.e. Blade, JSX, Vue, etc..)
- Think through class placement, order, priority, and defaults - remove redundant classes, add classes to parent or child carefully to limit repetition, group elements logically
- You can use the `search-docs` tool to get exact examples from the official documentation when needed.
### Spacing
- When listing items, use gap utilities for spacing, don't use margins.
<code-snippet name="Valid Flex Gap Spacing Example" lang="html">
<div class="flex gap-8">
<div>Superior</div>
<div>Michigan</div>
<div>Erie</div>
</div>
</code-snippet>
### Dark Mode
- If existing pages and components support dark mode, new pages and components must support dark mode in a similar way, typically using `dark:`.
=== tailwindcss/v4 rules ===
## Tailwind 4
- Always use Tailwind CSS v4 - do not use the deprecated utilities.
- `corePlugins` is not supported in Tailwind v4.
- In Tailwind v4, configuration is CSS-first using the `@theme` directive — no separate `tailwind.config.js` file is needed.
<code-snippet name="Extending Theme in CSS" lang="css">
@theme {
--color-brand: oklch(0.72 0.11 178);
}
</code-snippet>
- In Tailwind v4, you import Tailwind using a regular CSS `@import` statement, not using the `@tailwind` directives used in v3:
<code-snippet name="Tailwind v4 Import Tailwind Diff" lang="diff">
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
+ @import "tailwindcss";
</code-snippet>
### Replaced Utilities
- Tailwind v4 removed deprecated utilities. Do not use the deprecated option - use the replacement.
- Opacity values are still numeric.
| Deprecated | Replacement |
|------------+--------------|
| bg-opacity-* | bg-black/* |
| text-opacity-* | text-black/* |
| border-opacity-* | border-black/* |
| divide-opacity-* | divide-black/* |
| ring-opacity-* | ring-black/* |
| placeholder-opacity-* | placeholder-black/* |
| flex-shrink-* | shrink-* |
| flex-grow-* | grow-* |
| overflow-ellipsis | text-ellipsis |
| decoration-slice | box-decoration-slice |
| decoration-clone | box-decoration-clone |
</laravel-boost-guidelines>

344
Agents.md
View File

@ -386,6 +386,266 @@ ## Reference Materials
=== ===
<laravel-boost-guidelines> <laravel-boost-guidelines>
=== .ai/filament-v5-blueprint rules ===
## Source of Truth
If any Filament behavior is uncertain, lookup the exact section in:
- docs/research/filament-v5-notes.md
and prefer that over guesses.
# SECTION B — FILAMENT V5 BLUEPRINT (EXECUTABLE RULES)
# Filament Blueprint (v5)
## 1) Non-negotiables
- Filament v5 requires Livewire v4.0+.
- Laravel 11+: register panel providers in `bootstrap/providers.php` (never `bootstrap/app.php`).
- Global search hard rule: If a Resource should appear in Global Search, it must have an Edit or View page; otherwise it will return no results.
- Destructive actions must execute via `Action::make(...)->action(...)` and include `->requiresConfirmation()` (no exceptions).
- Prefer render hooks + CSS hook classes over publishing Filament internal views.
Sources:
- https://filamentphp.com/docs/5.x/upgrade-guide
- https://filamentphp.com/docs/5.x/panel-configuration
- https://filamentphp.com/docs/5.x/resources/global-search
- https://filamentphp.com/docs/5.x/actions/modals
- https://filamentphp.com/docs/5.x/advanced/render-hooks
- https://filamentphp.com/docs/5.x/styling/css-hooks
## 2) Directory & naming conventions
- Default to Filament discovery conventions for Resources/Pages/Widgets unless you adopt modular architecture.
- Clusters: directory layout is recommended, not mandatory; functional behavior depends on `$cluster`.
Sources:
- https://filamentphp.com/docs/5.x/navigation/clusters
- https://filamentphp.com/docs/5.x/advanced/modular-architecture
## 3) Panel setup defaults
- Default to a single `/admin` panel unless multiple audiences/configs demand multiple panels.
- Verify provider registration (Laravel 11+: `bootstrap/providers.php`) when adding a panel.
- Use `path()` carefully; treat `path('')` as a high-risk change requiring route conflict review.
- Assets policy:
- Panel-only assets: register via panel config.
- Shared/plugin assets: register via `FilamentAsset::register()`.
- Deployment must include `php artisan filament:assets`.
Sources:
- https://filamentphp.com/docs/5.x/panel-configuration
- https://filamentphp.com/docs/5.x/advanced/assets
## 4) Navigation & information architecture
- Use nav groups + sort order intentionally; apply conditional visibility for clarity, but enforce authorization separately.
- Use clusters to introduce hierarchy and sub-navigation when sidebar complexity grows.
- Treat cluster code structure as a recommendation (organizational benefit), not a required rule.
- User menu:
- Configure via `userMenuItems()` with Action objects.
- Never put destructive actions there without confirmation + authorization.
Sources:
- https://filamentphp.com/docs/5.x/navigation/overview
- https://filamentphp.com/docs/5.x/navigation/clusters
- https://filamentphp.com/docs/5.x/navigation/user-menu
## 5) Resource patterns
- Default to Resources for CRUD; use custom pages for non-CRUD tools/workflows.
- Global search:
- If a resource is intended for global search: ensure Edit/View page exists.
- Otherwise disable global search for that resource (dont “expect it to work”).
- If global search renders relationship-backed details: eager-load via global search query override.
- For very large datasets: consider disabling term splitting (only when needed).
Sources:
- https://filamentphp.com/docs/5.x/resources/overview
- https://filamentphp.com/docs/5.x/resources/global-search
## 6) Page lifecycle & query rules
- Treat relationship-backed rendering in aggregate contexts (global search details, list summaries) as requiring eager loading.
- Prefer render hooks for layout injection; avoid publishing internal views.
Sources:
- https://filamentphp.com/docs/5.x/resources/global-search
- https://filamentphp.com/docs/5.x/advanced/render-hooks
## 7) Infolists vs RelationManagers (decision tree)
- Interactive CRUD / attach / detach under owner record → RelationManager.
- Pick existing related record(s) inside owner form → Select / CheckboxList relationship fields.
- Inline CRUD inside owner form → Repeater.
- Default performance stance: RelationManagers stay lazy-loaded unless explicit UX justification exists.
Sources:
- https://filamentphp.com/docs/5.x/resources/managing-relationships
- https://filamentphp.com/docs/5.x/infolists/overview
## 8) Form patterns (validation, reactivity, state)
- Default: minimize server-driven reactivity; only use it when schema/visibility/requirements must change server-side.
- Prefer “on blur” semantics for chatty inputs when using reactive behavior (per docs patterns).
- Custom field views must obey state binding modifiers.
Sources:
- https://filamentphp.com/docs/5.x/forms/overview
- https://filamentphp.com/docs/5.x/forms/custom-fields
## 9) Table & action patterns
- Tables: always define a meaningful empty state (and empty-state actions where appropriate).
- Actions:
- Execution actions use `->action(...)`.
- Destructive actions add `->requiresConfirmation()`.
- Navigation-only actions should use `->url(...)`.
- UNVERIFIED: do not assert modal/confirmation behavior for URL-only actions unless verified.
Sources:
- https://filamentphp.com/docs/5.x/tables/empty-state
- https://filamentphp.com/docs/5.x/actions/modals
## 10) Authorization & security
- Enforce panel access in non-local environments as documented.
- UI visibility is not security; enforce policies/access checks in addition to hiding UI.
- Bulk operations: explicitly decide between “Any” policy methods vs per-record authorization.
Sources:
- https://filamentphp.com/docs/5.x/users/overview
- https://filamentphp.com/docs/5.x/resources/deleting-records
## 11) Notifications & UX feedback
- Default to explicit success/error notifications for user-triggered mutations that arent instantly obvious.
- Treat polling as a cost; set intervals intentionally where polling is used.
Sources:
- https://filamentphp.com/docs/5.x/notifications/overview
- https://filamentphp.com/docs/5.x/widgets/stats-overview
## 12) Performance defaults
- Heavy assets: prefer on-demand loading (`loadedOnRequest()` + `x-load-css` / `x-load-js`) for heavy dependencies.
- Styling overrides use CSS hook classes; layout injection uses render hooks; avoid view publishing.
Sources:
- https://filamentphp.com/docs/5.x/advanced/assets
- https://filamentphp.com/docs/5.x/styling/css-hooks
- https://filamentphp.com/docs/5.x/advanced/render-hooks
## 13) Testing requirements
- Test pages/relation managers/widgets as Livewire components.
- Test actions using Filaments action testing guidance.
- Do not mount non-Livewire classes in Livewire tests.
Sources:
- https://filamentphp.com/docs/5.x/testing/overview
- https://filamentphp.com/docs/5.x/testing/testing-actions
## 14) Forbidden patterns
- Mixing Filament v3/v4 APIs into v5 code.
- Any mention of Livewire v3 for Filament v5.
- Registering panel providers in `bootstrap/app.php` on Laravel 11+.
- Destructive actions without `->requiresConfirmation()`.
- Shipping heavy assets globally when on-demand loading fits.
- Publishing Filament internal views as a default customization technique.
Sources:
- https://filamentphp.com/docs/5.x/upgrade-guide
- https://filamentphp.com/docs/5.x/panel-configuration
- https://filamentphp.com/docs/5.x/actions/modals
- https://filamentphp.com/docs/5.x/advanced/assets
## 15) Agent output contract
For any implementation request, the agent must explicitly state:
1) Livewire v4.0+ compliance.
2) Provider registration location (Laravel 11+: `bootstrap/providers.php`).
3) For each globally searchable resource: whether it has Edit/View page (or global search is disabled).
4) Which actions are destructive and how confirmation + authorization is handled.
5) Asset strategy: global vs on-demand and where `filament:assets` runs in deploy.
6) Testing plan: which pages/widgets/relation managers/actions are covered.
Sources:
- https://filamentphp.com/docs/5.x/upgrade-guide
- https://filamentphp.com/docs/5.x/panel-configuration
- https://filamentphp.com/docs/5.x/resources/global-search
- https://filamentphp.com/docs/5.x/advanced/assets
- https://filamentphp.com/docs/5.x/testing/testing-actions
=== .ai/filament-v5-checklist rules ===
# SECTION C — AI REVIEW CHECKLIST (STRICT CHECKBOXES)
## Version Safety
- [ ] Filament v5 explicitly targets Livewire v4.0+ (no Livewire v3 references anywhere).
- Source: https://filamentphp.com/docs/5.x/upgrade-guide — “Upgrading Livewire”
- [ ] All references are Filament `/docs/5.x/` only (no v3/v4 docs, no legacy APIs).
- [ ] Upgrade assumptions match the v5 upgrade guide requirements and steps.
- Source: https://filamentphp.com/docs/5.x/upgrade-guide — “New requirements”
## Panel & Navigation
- [ ] Laravel 11+: panel providers are registered in `bootstrap/providers.php` (not `bootstrap/app.php`).
- Source: https://filamentphp.com/docs/5.x/panel-configuration — “Creating a new panel”
- [ ] Panel `path()` choices are intentional and do not conflict with existing routes (especially `path('')`).
- Source: https://filamentphp.com/docs/5.x/panel-configuration — “Changing the path”
- [ ] Cluster usage is correctly configured (discovery + `$cluster` assignments).
- Source: https://filamentphp.com/docs/5.x/navigation/clusters — “Creating a cluster”
- [ ] Cluster semantics (sub-navigation + grouped navigation behavior) are understood and verified against the clusters docs.
- Source: https://filamentphp.com/docs/5.x/navigation/clusters — “Introduction”
- [ ] Cluster directory structure is treated as recommended, not mandatory.
- Source: https://filamentphp.com/docs/5.x/navigation/clusters — “Code structure recommendations for panels using clusters”
- [ ] User menu items are registered via `userMenuItems()` and permission-gated where needed.
- Source: https://filamentphp.com/docs/5.x/navigation/user-menu — “Introduction”
## Resource Structure
- [ ] `$recordTitleAttribute` is set for any resource intended for global search.
- Source: https://filamentphp.com/docs/5.x/resources/overview — “Record titles”
- [ ] Hard rule enforced: every globally searchable resource has an Edit or View page; otherwise global search is disabled for it.
- Source: https://filamentphp.com/docs/5.x/resources/global-search — “Setting global search result titles”
- [ ] Relationship-backed global search details are eager-loaded via the global search query override.
- Source: https://filamentphp.com/docs/5.x/resources/global-search — “Adding extra details to global search results”
## Infolists & Relations
- [ ] Each relationship uses the correct tool (RelationManager vs Select/CheckboxList vs Repeater) based on required interaction.
- Source: https://filamentphp.com/docs/5.x/resources/managing-relationships — “Choosing the right tool for the job”
- [ ] RelationManagers remain lazy-loaded by default unless theres an explicit UX justification.
- Source: https://filamentphp.com/docs/5.x/resources/managing-relationships — “Disabling lazy loading”
## Forms
- [ ] Server-driven reactivity is minimal; chatty inputs do not trigger network requests unnecessarily.
- Source: https://filamentphp.com/docs/5.x/forms/overview — “Reactive fields on blur”
- [ ] Custom field views obey state binding modifiers (no hardcoded `wire:model` without modifiers).
- Source: https://filamentphp.com/docs/5.x/forms/custom-fields — “Obeying state binding modifiers”
## Tables & Actions
- [ ] Tables define a meaningful empty state (and empty-state actions where appropriate).
- Source: https://filamentphp.com/docs/5.x/tables/empty-state — “Adding empty state actions”
- [ ] All destructive actions execute via `->action(...)` and include `->requiresConfirmation()`.
- Source: https://filamentphp.com/docs/5.x/actions/modals — “Confirmation modals”
- [ ] No checklist rule assumes confirmation/modals for `->url(...)` actions unless verified in docs (UNVERIFIED behavior must not be asserted as fact).
- Source: https://filamentphp.com/docs/5.x/actions/modals — “Confirmation modals”
## Authorization & Security
- [ ] Panel access is enforced for non-local environments as documented.
- Source: https://filamentphp.com/docs/5.x/users/overview — “Authorizing access to the panel”
- [ ] UI visibility is not treated as authorization; policies/access checks still enforce boundaries.
- [ ] Bulk operations intentionally choose between “Any” policy methods vs per-record authorization where required.
- Source: https://filamentphp.com/docs/5.x/resources/deleting-records — “Authorization”
## UX & Notifications
- [ ] User-triggered mutations provide explicit success/error notifications when outcomes arent instantly obvious.
- Source: https://filamentphp.com/docs/5.x/notifications/overview — “Introduction”
- [ ] Polling (widgets/notifications) is configured intentionally (interval set or disabled) to control load.
- Source: https://filamentphp.com/docs/5.x/widgets/stats-overview — “Live updating stats (polling)”
## Performance
- [ ] Heavy frontend assets are loaded on-demand using `loadedOnRequest()` + `x-load-css` / `x-load-js` where appropriate.
- Source: https://filamentphp.com/docs/5.x/advanced/assets — “Lazy loading CSS” / “Lazy loading JavaScript”
- [ ] Styling overrides use CSS hook classes discovered via DevTools (no brittle selectors by default).
- Source: https://filamentphp.com/docs/5.x/styling/css-hooks — “Discovering hook classes”
## Testing
- [ ] Livewire tests mount Filament pages/relation managers/widgets (Livewire components), not static resource classes.
- Source: https://filamentphp.com/docs/5.x/testing/overview — “What is a Livewire component when using Filament?”
- [ ] Actions that mutate data are covered using Filaments action testing guidance.
- Source: https://filamentphp.com/docs/5.x/testing/testing-actions — “Testing actions”
## Deployment / Ops
- [ ] `php artisan filament:assets` is included in the deployment process when using registered assets.
- Source: https://filamentphp.com/docs/5.x/advanced/assets — “The FilamentAsset facade”
=== foundation rules === === foundation rules ===
# Laravel Boost Guidelines # Laravel Boost Guidelines
@ -396,10 +656,10 @@ ## Foundational Context
This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions. This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions.
- php - 8.4.15 - php - 8.4.15
- filament/filament (FILAMENT) - v4 - filament/filament (FILAMENT) - v5
- laravel/framework (LARAVEL) - v12 - laravel/framework (LARAVEL) - v12
- laravel/prompts (PROMPTS) - v0 - laravel/prompts (PROMPTS) - v0
- livewire/livewire (LIVEWIRE) - v3 - livewire/livewire (LIVEWIRE) - v4
- laravel/mcp (MCP) - v0 - laravel/mcp (MCP) - v0
- laravel/pint (PINT) - v1 - laravel/pint (PINT) - v1
- laravel/sail (SAIL) - v1 - laravel/sail (SAIL) - v1
@ -411,7 +671,6 @@ ## Conventions
- You must follow all existing code conventions used in this application. When creating or editing a file, check sibling files for the correct structure, approach, naming. - You must follow all existing code conventions used in this application. When creating or editing a file, check sibling files for the correct structure, approach, naming.
- Use descriptive names for variables and methods. For example, `isRegisteredForDiscounts`, not `discount()`. - Use descriptive names for variables and methods. For example, `isRegisteredForDiscounts`, not `discount()`.
- Check for existing components to reuse before writing a new one. - Check for existing components to reuse before writing a new one.
- UI consistency: Prefer Filament components (`<x-filament::section>`, infolist/table entries, etc.) over custom HTML/Tailwind for admin UI; only roll custom markup when Filament cannot express the UI.
## Verification Scripts ## Verification Scripts
- Do not create verification scripts or tinker when tests cover that functionality and prove it works. Unit and feature tests are more important. - Do not create verification scripts or tinker when tests cover that functionality and prove it works. Unit and feature tests are more important.
@ -421,7 +680,7 @@ ## Application Structure & Architecture
- Do not change the application's dependencies without approval. - Do not change the application's dependencies without approval.
## Frontend Bundling ## Frontend Bundling
- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `npm run build`, `npm run dev`, or `composer run dev`. Ask them. - If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `vendor/bin/sail npm run build`, `vendor/bin/sail npm run dev`, or `vendor/bin/sail composer run dev`. Ask them.
## Replies ## Replies
- Be concise in your explanations - focus on what's important rather than explaining obvious details. - Be concise in your explanations - focus on what's important rather than explaining obvious details.
@ -499,20 +758,35 @@ ## Enums
- Typically, keys in an Enum should be TitleCase. For example: `FavoritePerson`, `BestLake`, `Monthly`. - Typically, keys in an Enum should be TitleCase. For example: `FavoritePerson`, `BestLake`, `Monthly`.
=== sail rules ===
## Laravel Sail
- This project runs inside Laravel Sail's Docker containers. You MUST execute all commands through Sail.
- Start services using `vendor/bin/sail up -d` and stop them with `vendor/bin/sail stop`.
- Open the application in the browser by running `vendor/bin/sail open`.
- Always prefix PHP, Artisan, Composer, and Node commands** with `vendor/bin/sail`. Examples:
- Run Artisan Commands: `vendor/bin/sail artisan migrate`
- Install Composer packages: `vendor/bin/sail composer install`
- Execute node commands: `vendor/bin/sail npm run dev`
- Execute PHP scripts: `vendor/bin/sail php [script]`
- View all available Sail commands by running `vendor/bin/sail` without arguments.
=== tests rules === === tests rules ===
## Test Enforcement ## Test Enforcement
- Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass. - Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass.
- Run the minimum number of tests needed to ensure code quality and speed. Use `php artisan test` with a specific filename or filter. - Run the minimum number of tests needed to ensure code quality and speed. Use `vendor/bin/sail artisan test` with a specific filename or filter.
=== laravel/core rules === === laravel/core rules ===
## Do Things the Laravel Way ## Do Things the Laravel Way
- Use `php artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool. - Use `vendor/bin/sail artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool.
- If you're creating a generic PHP class, use `php artisan make:class`. - If you're creating a generic PHP class, use `vendor/bin/sail artisan make:class`.
- Pass `--no-interaction` to all Artisan commands to ensure they work without user input. You should also pass the correct `--options` to ensure correct behavior. - Pass `--no-interaction` to all Artisan commands to ensure they work without user input. You should also pass the correct `--options` to ensure correct behavior.
### Database ### Database
@ -523,7 +797,7 @@ ### Database
- Use Laravel's query builder for very complex database operations. - Use Laravel's query builder for very complex database operations.
### Model Creation ### Model Creation
- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `php artisan make:model`. - When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `vendor/bin/sail artisan make:model`.
### APIs & Eloquent Resources ### APIs & Eloquent Resources
- For APIs, default to using Eloquent API Resources and API versioning unless existing API routes do not, then you should follow existing application convention. - For APIs, default to using Eloquent API Resources and API versioning unless existing API routes do not, then you should follow existing application convention.
@ -547,10 +821,10 @@ ### Configuration
### Testing ### Testing
- When creating models for tests, use the factories for the models. Check if the factory has custom states that can be used before manually setting up the model. - When creating models for tests, use the factories for the models. Check if the factory has custom states that can be used before manually setting up the model.
- Faker: Use methods such as `$this->faker->word()` or `fake()->randomDigit()`. Follow existing conventions whether to use `$this->faker` or `fake()`. - Faker: Use methods such as `$this->faker->word()` or `fake()->randomDigit()`. Follow existing conventions whether to use `$this->faker` or `fake()`.
- When creating tests, make use of `php artisan make:test [options] {name}` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests. - When creating tests, make use of `vendor/bin/sail artisan make:test [options] {name}` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests.
### Vite Error ### Vite Error
- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `npm run build` or ask the user to run `npm run dev` or `composer run dev`. - If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `vendor/bin/sail npm run build` or ask the user to run `vendor/bin/sail npm run dev` or `vendor/bin/sail composer run dev`.
=== laravel/v12 rules === === laravel/v12 rules ===
@ -579,7 +853,7 @@ ### Models
## Livewire Core ## Livewire Core
- Use the `search-docs` tool to find exact version specific documentation for how to write Livewire & Livewire tests. - Use the `search-docs` tool to find exact version specific documentation for how to write Livewire & Livewire tests.
- Use the `php artisan make:livewire [Posts\CreatePost]` artisan command to create new components - Use the `vendor/bin/sail artisan make:livewire [Posts\CreatePost]` artisan command to create new components
- State should live on the server, with the UI reflecting it. - State should live on the server, with the UI reflecting it.
- All Livewire requests hit the Laravel backend, they're like regular HTTP requests. Always validate form data, and run authorization checks in Livewire actions. - All Livewire requests hit the Laravel backend, they're like regular HTTP requests. Always validate form data, and run authorization checks in Livewire actions.
@ -622,48 +896,12 @@ ## Testing Livewire
</code-snippet> </code-snippet>
=== livewire/v3 rules ===
## Livewire 3
### Key Changes From Livewire 2
- These things changed in Livewire 2, but may not have been updated in this application. Verify this application's setup to ensure you conform with application conventions.
- Use `wire:model.live` for real-time updates, `wire:model` is now deferred by default.
- Components now use the `App\Livewire` namespace (not `App\Http\Livewire`).
- Use `$this->dispatch()` to dispatch events (not `emit` or `dispatchBrowserEvent`).
- Use the `components.layouts.app` view as the typical layout path (not `layouts.app`).
### New Directives
- `wire:show`, `wire:transition`, `wire:cloak`, `wire:offline`, `wire:target` are available for use. Use the documentation to find usage examples.
### Alpine
- Alpine is now included with Livewire, don't manually include Alpine.js.
- Plugins included with Alpine: persist, intersect, collapse, and focus.
### Lifecycle Hooks
- You can listen for `livewire:init` to hook into Livewire initialization, and `fail.status === 419` for the page expiring:
<code-snippet name="livewire:load example" lang="js">
document.addEventListener('livewire:init', function () {
Livewire.hook('request', ({ fail }) => {
if (fail && fail.status === 419) {
alert('Your session expired');
}
});
Livewire.hook('message.failed', (message, component) => {
console.error(message);
});
});
</code-snippet>
=== pint/core rules === === pint/core rules ===
## Laravel Pint Code Formatter ## Laravel Pint Code Formatter
- You must run `vendor/bin/pint --dirty` before finalizing changes to ensure your code matches the project's expected style. - You must run `vendor/bin/sail bin pint --dirty` before finalizing changes to ensure your code matches the project's expected style.
- Do not run `vendor/bin/pint --test`, simply run `vendor/bin/pint` to fix any formatting issues. - Do not run `vendor/bin/sail bin pint --test`, simply run `vendor/bin/sail bin pint` to fix any formatting issues.
=== pest/core rules === === pest/core rules ===
@ -673,7 +911,7 @@ ### Testing
- If you need to verify a feature is working, write or update a Unit / Feature test. - If you need to verify a feature is working, write or update a Unit / Feature test.
### Pest Tests ### Pest Tests
- All tests must be written using Pest. Use `php artisan make:test --pest {name}`. - All tests must be written using Pest. Use `vendor/bin/sail artisan make:test --pest {name}`.
- You must not remove any tests or test files from the tests directory without approval. These are not temporary or helper files - these are core to the application. - You must not remove any tests or test files from the tests directory without approval. These are not temporary or helper files - these are core to the application.
- Tests should test all of the happy paths, failure paths, and weird paths. - Tests should test all of the happy paths, failure paths, and weird paths.
- Tests live in the `tests/Feature` and `tests/Unit` directories. - Tests live in the `tests/Feature` and `tests/Unit` directories.
@ -686,9 +924,9 @@ ### Pest Tests
### Running Tests ### Running Tests
- Run the minimal number of tests using an appropriate filter before finalizing code edits. - Run the minimal number of tests using an appropriate filter before finalizing code edits.
- To run all tests: `php artisan test`. - To run all tests: `vendor/bin/sail artisan test`.
- To run all tests in a file: `php artisan test tests/Feature/ExampleTest.php`. - To run all tests in a file: `vendor/bin/sail artisan test tests/Feature/ExampleTest.php`.
- To filter on a particular test name: `php artisan test --filter=testName` (recommended after making a change to a related file). - To filter on a particular test name: `vendor/bin/sail artisan test --filter=testName` (recommended after making a change to a related file).
- When the tests relating to your changes are passing, ask the user if they would like to run the entire test suite to ensure everything is still passing. - When the tests relating to your changes are passing, ask the user if they would like to run the entire test suite to ensure everything is still passing.
### Pest Assertions ### Pest Assertions

343
GEMINI.md
View File

@ -226,6 +226,266 @@ ## Reference Materials
=== ===
<laravel-boost-guidelines> <laravel-boost-guidelines>
=== .ai/filament-v5-blueprint rules ===
## Source of Truth
If any Filament behavior is uncertain, lookup the exact section in:
- docs/research/filament-v5-notes.md
and prefer that over guesses.
# SECTION B — FILAMENT V5 BLUEPRINT (EXECUTABLE RULES)
# Filament Blueprint (v5)
## 1) Non-negotiables
- Filament v5 requires Livewire v4.0+.
- Laravel 11+: register panel providers in `bootstrap/providers.php` (never `bootstrap/app.php`).
- Global search hard rule: If a Resource should appear in Global Search, it must have an Edit or View page; otherwise it will return no results.
- Destructive actions must execute via `Action::make(...)->action(...)` and include `->requiresConfirmation()` (no exceptions).
- Prefer render hooks + CSS hook classes over publishing Filament internal views.
Sources:
- https://filamentphp.com/docs/5.x/upgrade-guide
- https://filamentphp.com/docs/5.x/panel-configuration
- https://filamentphp.com/docs/5.x/resources/global-search
- https://filamentphp.com/docs/5.x/actions/modals
- https://filamentphp.com/docs/5.x/advanced/render-hooks
- https://filamentphp.com/docs/5.x/styling/css-hooks
## 2) Directory & naming conventions
- Default to Filament discovery conventions for Resources/Pages/Widgets unless you adopt modular architecture.
- Clusters: directory layout is recommended, not mandatory; functional behavior depends on `$cluster`.
Sources:
- https://filamentphp.com/docs/5.x/navigation/clusters
- https://filamentphp.com/docs/5.x/advanced/modular-architecture
## 3) Panel setup defaults
- Default to a single `/admin` panel unless multiple audiences/configs demand multiple panels.
- Verify provider registration (Laravel 11+: `bootstrap/providers.php`) when adding a panel.
- Use `path()` carefully; treat `path('')` as a high-risk change requiring route conflict review.
- Assets policy:
- Panel-only assets: register via panel config.
- Shared/plugin assets: register via `FilamentAsset::register()`.
- Deployment must include `php artisan filament:assets`.
Sources:
- https://filamentphp.com/docs/5.x/panel-configuration
- https://filamentphp.com/docs/5.x/advanced/assets
## 4) Navigation & information architecture
- Use nav groups + sort order intentionally; apply conditional visibility for clarity, but enforce authorization separately.
- Use clusters to introduce hierarchy and sub-navigation when sidebar complexity grows.
- Treat cluster code structure as a recommendation (organizational benefit), not a required rule.
- User menu:
- Configure via `userMenuItems()` with Action objects.
- Never put destructive actions there without confirmation + authorization.
Sources:
- https://filamentphp.com/docs/5.x/navigation/overview
- https://filamentphp.com/docs/5.x/navigation/clusters
- https://filamentphp.com/docs/5.x/navigation/user-menu
## 5) Resource patterns
- Default to Resources for CRUD; use custom pages for non-CRUD tools/workflows.
- Global search:
- If a resource is intended for global search: ensure Edit/View page exists.
- Otherwise disable global search for that resource (dont “expect it to work”).
- If global search renders relationship-backed details: eager-load via global search query override.
- For very large datasets: consider disabling term splitting (only when needed).
Sources:
- https://filamentphp.com/docs/5.x/resources/overview
- https://filamentphp.com/docs/5.x/resources/global-search
## 6) Page lifecycle & query rules
- Treat relationship-backed rendering in aggregate contexts (global search details, list summaries) as requiring eager loading.
- Prefer render hooks for layout injection; avoid publishing internal views.
Sources:
- https://filamentphp.com/docs/5.x/resources/global-search
- https://filamentphp.com/docs/5.x/advanced/render-hooks
## 7) Infolists vs RelationManagers (decision tree)
- Interactive CRUD / attach / detach under owner record → RelationManager.
- Pick existing related record(s) inside owner form → Select / CheckboxList relationship fields.
- Inline CRUD inside owner form → Repeater.
- Default performance stance: RelationManagers stay lazy-loaded unless explicit UX justification exists.
Sources:
- https://filamentphp.com/docs/5.x/resources/managing-relationships
- https://filamentphp.com/docs/5.x/infolists/overview
## 8) Form patterns (validation, reactivity, state)
- Default: minimize server-driven reactivity; only use it when schema/visibility/requirements must change server-side.
- Prefer “on blur” semantics for chatty inputs when using reactive behavior (per docs patterns).
- Custom field views must obey state binding modifiers.
Sources:
- https://filamentphp.com/docs/5.x/forms/overview
- https://filamentphp.com/docs/5.x/forms/custom-fields
## 9) Table & action patterns
- Tables: always define a meaningful empty state (and empty-state actions where appropriate).
- Actions:
- Execution actions use `->action(...)`.
- Destructive actions add `->requiresConfirmation()`.
- Navigation-only actions should use `->url(...)`.
- UNVERIFIED: do not assert modal/confirmation behavior for URL-only actions unless verified.
Sources:
- https://filamentphp.com/docs/5.x/tables/empty-state
- https://filamentphp.com/docs/5.x/actions/modals
## 10) Authorization & security
- Enforce panel access in non-local environments as documented.
- UI visibility is not security; enforce policies/access checks in addition to hiding UI.
- Bulk operations: explicitly decide between “Any” policy methods vs per-record authorization.
Sources:
- https://filamentphp.com/docs/5.x/users/overview
- https://filamentphp.com/docs/5.x/resources/deleting-records
## 11) Notifications & UX feedback
- Default to explicit success/error notifications for user-triggered mutations that arent instantly obvious.
- Treat polling as a cost; set intervals intentionally where polling is used.
Sources:
- https://filamentphp.com/docs/5.x/notifications/overview
- https://filamentphp.com/docs/5.x/widgets/stats-overview
## 12) Performance defaults
- Heavy assets: prefer on-demand loading (`loadedOnRequest()` + `x-load-css` / `x-load-js`) for heavy dependencies.
- Styling overrides use CSS hook classes; layout injection uses render hooks; avoid view publishing.
Sources:
- https://filamentphp.com/docs/5.x/advanced/assets
- https://filamentphp.com/docs/5.x/styling/css-hooks
- https://filamentphp.com/docs/5.x/advanced/render-hooks
## 13) Testing requirements
- Test pages/relation managers/widgets as Livewire components.
- Test actions using Filaments action testing guidance.
- Do not mount non-Livewire classes in Livewire tests.
Sources:
- https://filamentphp.com/docs/5.x/testing/overview
- https://filamentphp.com/docs/5.x/testing/testing-actions
## 14) Forbidden patterns
- Mixing Filament v3/v4 APIs into v5 code.
- Any mention of Livewire v3 for Filament v5.
- Registering panel providers in `bootstrap/app.php` on Laravel 11+.
- Destructive actions without `->requiresConfirmation()`.
- Shipping heavy assets globally when on-demand loading fits.
- Publishing Filament internal views as a default customization technique.
Sources:
- https://filamentphp.com/docs/5.x/upgrade-guide
- https://filamentphp.com/docs/5.x/panel-configuration
- https://filamentphp.com/docs/5.x/actions/modals
- https://filamentphp.com/docs/5.x/advanced/assets
## 15) Agent output contract
For any implementation request, the agent must explicitly state:
1) Livewire v4.0+ compliance.
2) Provider registration location (Laravel 11+: `bootstrap/providers.php`).
3) For each globally searchable resource: whether it has Edit/View page (or global search is disabled).
4) Which actions are destructive and how confirmation + authorization is handled.
5) Asset strategy: global vs on-demand and where `filament:assets` runs in deploy.
6) Testing plan: which pages/widgets/relation managers/actions are covered.
Sources:
- https://filamentphp.com/docs/5.x/upgrade-guide
- https://filamentphp.com/docs/5.x/panel-configuration
- https://filamentphp.com/docs/5.x/resources/global-search
- https://filamentphp.com/docs/5.x/advanced/assets
- https://filamentphp.com/docs/5.x/testing/testing-actions
=== .ai/filament-v5-checklist rules ===
# SECTION C — AI REVIEW CHECKLIST (STRICT CHECKBOXES)
## Version Safety
- [ ] Filament v5 explicitly targets Livewire v4.0+ (no Livewire v3 references anywhere).
- Source: https://filamentphp.com/docs/5.x/upgrade-guide — “Upgrading Livewire”
- [ ] All references are Filament `/docs/5.x/` only (no v3/v4 docs, no legacy APIs).
- [ ] Upgrade assumptions match the v5 upgrade guide requirements and steps.
- Source: https://filamentphp.com/docs/5.x/upgrade-guide — “New requirements”
## Panel & Navigation
- [ ] Laravel 11+: panel providers are registered in `bootstrap/providers.php` (not `bootstrap/app.php`).
- Source: https://filamentphp.com/docs/5.x/panel-configuration — “Creating a new panel”
- [ ] Panel `path()` choices are intentional and do not conflict with existing routes (especially `path('')`).
- Source: https://filamentphp.com/docs/5.x/panel-configuration — “Changing the path”
- [ ] Cluster usage is correctly configured (discovery + `$cluster` assignments).
- Source: https://filamentphp.com/docs/5.x/navigation/clusters — “Creating a cluster”
- [ ] Cluster semantics (sub-navigation + grouped navigation behavior) are understood and verified against the clusters docs.
- Source: https://filamentphp.com/docs/5.x/navigation/clusters — “Introduction”
- [ ] Cluster directory structure is treated as recommended, not mandatory.
- Source: https://filamentphp.com/docs/5.x/navigation/clusters — “Code structure recommendations for panels using clusters”
- [ ] User menu items are registered via `userMenuItems()` and permission-gated where needed.
- Source: https://filamentphp.com/docs/5.x/navigation/user-menu — “Introduction”
## Resource Structure
- [ ] `$recordTitleAttribute` is set for any resource intended for global search.
- Source: https://filamentphp.com/docs/5.x/resources/overview — “Record titles”
- [ ] Hard rule enforced: every globally searchable resource has an Edit or View page; otherwise global search is disabled for it.
- Source: https://filamentphp.com/docs/5.x/resources/global-search — “Setting global search result titles”
- [ ] Relationship-backed global search details are eager-loaded via the global search query override.
- Source: https://filamentphp.com/docs/5.x/resources/global-search — “Adding extra details to global search results”
## Infolists & Relations
- [ ] Each relationship uses the correct tool (RelationManager vs Select/CheckboxList vs Repeater) based on required interaction.
- Source: https://filamentphp.com/docs/5.x/resources/managing-relationships — “Choosing the right tool for the job”
- [ ] RelationManagers remain lazy-loaded by default unless theres an explicit UX justification.
- Source: https://filamentphp.com/docs/5.x/resources/managing-relationships — “Disabling lazy loading”
## Forms
- [ ] Server-driven reactivity is minimal; chatty inputs do not trigger network requests unnecessarily.
- Source: https://filamentphp.com/docs/5.x/forms/overview — “Reactive fields on blur”
- [ ] Custom field views obey state binding modifiers (no hardcoded `wire:model` without modifiers).
- Source: https://filamentphp.com/docs/5.x/forms/custom-fields — “Obeying state binding modifiers”
## Tables & Actions
- [ ] Tables define a meaningful empty state (and empty-state actions where appropriate).
- Source: https://filamentphp.com/docs/5.x/tables/empty-state — “Adding empty state actions”
- [ ] All destructive actions execute via `->action(...)` and include `->requiresConfirmation()`.
- Source: https://filamentphp.com/docs/5.x/actions/modals — “Confirmation modals”
- [ ] No checklist rule assumes confirmation/modals for `->url(...)` actions unless verified in docs (UNVERIFIED behavior must not be asserted as fact).
- Source: https://filamentphp.com/docs/5.x/actions/modals — “Confirmation modals”
## Authorization & Security
- [ ] Panel access is enforced for non-local environments as documented.
- Source: https://filamentphp.com/docs/5.x/users/overview — “Authorizing access to the panel”
- [ ] UI visibility is not treated as authorization; policies/access checks still enforce boundaries.
- [ ] Bulk operations intentionally choose between “Any” policy methods vs per-record authorization where required.
- Source: https://filamentphp.com/docs/5.x/resources/deleting-records — “Authorization”
## UX & Notifications
- [ ] User-triggered mutations provide explicit success/error notifications when outcomes arent instantly obvious.
- Source: https://filamentphp.com/docs/5.x/notifications/overview — “Introduction”
- [ ] Polling (widgets/notifications) is configured intentionally (interval set or disabled) to control load.
- Source: https://filamentphp.com/docs/5.x/widgets/stats-overview — “Live updating stats (polling)”
## Performance
- [ ] Heavy frontend assets are loaded on-demand using `loadedOnRequest()` + `x-load-css` / `x-load-js` where appropriate.
- Source: https://filamentphp.com/docs/5.x/advanced/assets — “Lazy loading CSS” / “Lazy loading JavaScript”
- [ ] Styling overrides use CSS hook classes discovered via DevTools (no brittle selectors by default).
- Source: https://filamentphp.com/docs/5.x/styling/css-hooks — “Discovering hook classes”
## Testing
- [ ] Livewire tests mount Filament pages/relation managers/widgets (Livewire components), not static resource classes.
- Source: https://filamentphp.com/docs/5.x/testing/overview — “What is a Livewire component when using Filament?”
- [ ] Actions that mutate data are covered using Filaments action testing guidance.
- Source: https://filamentphp.com/docs/5.x/testing/testing-actions — “Testing actions”
## Deployment / Ops
- [ ] `php artisan filament:assets` is included in the deployment process when using registered assets.
- Source: https://filamentphp.com/docs/5.x/advanced/assets — “The FilamentAsset facade”
=== foundation rules === === foundation rules ===
# Laravel Boost Guidelines # Laravel Boost Guidelines
@ -236,10 +496,10 @@ ## Foundational Context
This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions. This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions.
- php - 8.4.15 - php - 8.4.15
- filament/filament (FILAMENT) - v4 - filament/filament (FILAMENT) - v5
- laravel/framework (LARAVEL) - v12 - laravel/framework (LARAVEL) - v12
- laravel/prompts (PROMPTS) - v0 - laravel/prompts (PROMPTS) - v0
- livewire/livewire (LIVEWIRE) - v3 - livewire/livewire (LIVEWIRE) - v4
- laravel/mcp (MCP) - v0 - laravel/mcp (MCP) - v0
- laravel/pint (PINT) - v1 - laravel/pint (PINT) - v1
- laravel/sail (SAIL) - v1 - laravel/sail (SAIL) - v1
@ -260,7 +520,7 @@ ## Application Structure & Architecture
- Do not change the application's dependencies without approval. - Do not change the application's dependencies without approval.
## Frontend Bundling ## Frontend Bundling
- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `npm run build`, `npm run dev`, or `composer run dev`. Ask them. - If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `vendor/bin/sail npm run build`, `vendor/bin/sail npm run dev`, or `vendor/bin/sail composer run dev`. Ask them.
## Replies ## Replies
- Be concise in your explanations - focus on what's important rather than explaining obvious details. - Be concise in your explanations - focus on what's important rather than explaining obvious details.
@ -338,20 +598,35 @@ ## Enums
- Typically, keys in an Enum should be TitleCase. For example: `FavoritePerson`, `BestLake`, `Monthly`. - Typically, keys in an Enum should be TitleCase. For example: `FavoritePerson`, `BestLake`, `Monthly`.
=== sail rules ===
## Laravel Sail
- This project runs inside Laravel Sail's Docker containers. You MUST execute all commands through Sail.
- Start services using `vendor/bin/sail up -d` and stop them with `vendor/bin/sail stop`.
- Open the application in the browser by running `vendor/bin/sail open`.
- Always prefix PHP, Artisan, Composer, and Node commands** with `vendor/bin/sail`. Examples:
- Run Artisan Commands: `vendor/bin/sail artisan migrate`
- Install Composer packages: `vendor/bin/sail composer install`
- Execute node commands: `vendor/bin/sail npm run dev`
- Execute PHP scripts: `vendor/bin/sail php [script]`
- View all available Sail commands by running `vendor/bin/sail` without arguments.
=== tests rules === === tests rules ===
## Test Enforcement ## Test Enforcement
- Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass. - Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass.
- Run the minimum number of tests needed to ensure code quality and speed. Use `php artisan test` with a specific filename or filter. - Run the minimum number of tests needed to ensure code quality and speed. Use `vendor/bin/sail artisan test` with a specific filename or filter.
=== laravel/core rules === === laravel/core rules ===
## Do Things the Laravel Way ## Do Things the Laravel Way
- Use `php artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool. - Use `vendor/bin/sail artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool.
- If you're creating a generic PHP class, use `php artisan make:class`. - If you're creating a generic PHP class, use `vendor/bin/sail artisan make:class`.
- Pass `--no-interaction` to all Artisan commands to ensure they work without user input. You should also pass the correct `--options` to ensure correct behavior. - Pass `--no-interaction` to all Artisan commands to ensure they work without user input. You should also pass the correct `--options` to ensure correct behavior.
### Database ### Database
@ -362,7 +637,7 @@ ### Database
- Use Laravel's query builder for very complex database operations. - Use Laravel's query builder for very complex database operations.
### Model Creation ### Model Creation
- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `php artisan make:model`. - When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `vendor/bin/sail artisan make:model`.
### APIs & Eloquent Resources ### APIs & Eloquent Resources
- For APIs, default to using Eloquent API Resources and API versioning unless existing API routes do not, then you should follow existing application convention. - For APIs, default to using Eloquent API Resources and API versioning unless existing API routes do not, then you should follow existing application convention.
@ -386,10 +661,10 @@ ### Configuration
### Testing ### Testing
- When creating models for tests, use the factories for the models. Check if the factory has custom states that can be used before manually setting up the model. - When creating models for tests, use the factories for the models. Check if the factory has custom states that can be used before manually setting up the model.
- Faker: Use methods such as `$this->faker->word()` or `fake()->randomDigit()`. Follow existing conventions whether to use `$this->faker` or `fake()`. - Faker: Use methods such as `$this->faker->word()` or `fake()->randomDigit()`. Follow existing conventions whether to use `$this->faker` or `fake()`.
- When creating tests, make use of `php artisan make:test [options] {name}` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests. - When creating tests, make use of `vendor/bin/sail artisan make:test [options] {name}` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests.
### Vite Error ### Vite Error
- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `npm run build` or ask the user to run `npm run dev` or `composer run dev`. - If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `vendor/bin/sail npm run build` or ask the user to run `vendor/bin/sail npm run dev` or `vendor/bin/sail composer run dev`.
=== laravel/v12 rules === === laravel/v12 rules ===
@ -418,7 +693,7 @@ ### Models
## Livewire Core ## Livewire Core
- Use the `search-docs` tool to find exact version specific documentation for how to write Livewire & Livewire tests. - Use the `search-docs` tool to find exact version specific documentation for how to write Livewire & Livewire tests.
- Use the `php artisan make:livewire [Posts\CreatePost]` artisan command to create new components - Use the `vendor/bin/sail artisan make:livewire [Posts\CreatePost]` artisan command to create new components
- State should live on the server, with the UI reflecting it. - State should live on the server, with the UI reflecting it.
- All Livewire requests hit the Laravel backend, they're like regular HTTP requests. Always validate form data, and run authorization checks in Livewire actions. - All Livewire requests hit the Laravel backend, they're like regular HTTP requests. Always validate form data, and run authorization checks in Livewire actions.
@ -461,48 +736,12 @@ ## Testing Livewire
</code-snippet> </code-snippet>
=== livewire/v3 rules ===
## Livewire 3
### Key Changes From Livewire 2
- These things changed in Livewire 2, but may not have been updated in this application. Verify this application's setup to ensure you conform with application conventions.
- Use `wire:model.live` for real-time updates, `wire:model` is now deferred by default.
- Components now use the `App\Livewire` namespace (not `App\Http\Livewire`).
- Use `$this->dispatch()` to dispatch events (not `emit` or `dispatchBrowserEvent`).
- Use the `components.layouts.app` view as the typical layout path (not `layouts.app`).
### New Directives
- `wire:show`, `wire:transition`, `wire:cloak`, `wire:offline`, `wire:target` are available for use. Use the documentation to find usage examples.
### Alpine
- Alpine is now included with Livewire, don't manually include Alpine.js.
- Plugins included with Alpine: persist, intersect, collapse, and focus.
### Lifecycle Hooks
- You can listen for `livewire:init` to hook into Livewire initialization, and `fail.status === 419` for the page expiring:
<code-snippet name="livewire:load example" lang="js">
document.addEventListener('livewire:init', function () {
Livewire.hook('request', ({ fail }) => {
if (fail && fail.status === 419) {
alert('Your session expired');
}
});
Livewire.hook('message.failed', (message, component) => {
console.error(message);
});
});
</code-snippet>
=== pint/core rules === === pint/core rules ===
## Laravel Pint Code Formatter ## Laravel Pint Code Formatter
- You must run `vendor/bin/pint --dirty` before finalizing changes to ensure your code matches the project's expected style. - You must run `vendor/bin/sail bin pint --dirty` before finalizing changes to ensure your code matches the project's expected style.
- Do not run `vendor/bin/pint --test`, simply run `vendor/bin/pint` to fix any formatting issues. - Do not run `vendor/bin/sail bin pint --test`, simply run `vendor/bin/sail bin pint` to fix any formatting issues.
=== pest/core rules === === pest/core rules ===
@ -512,7 +751,7 @@ ### Testing
- If you need to verify a feature is working, write or update a Unit / Feature test. - If you need to verify a feature is working, write or update a Unit / Feature test.
### Pest Tests ### Pest Tests
- All tests must be written using Pest. Use `php artisan make:test --pest {name}`. - All tests must be written using Pest. Use `vendor/bin/sail artisan make:test --pest {name}`.
- You must not remove any tests or test files from the tests directory without approval. These are not temporary or helper files - these are core to the application. - You must not remove any tests or test files from the tests directory without approval. These are not temporary or helper files - these are core to the application.
- Tests should test all of the happy paths, failure paths, and weird paths. - Tests should test all of the happy paths, failure paths, and weird paths.
- Tests live in the `tests/Feature` and `tests/Unit` directories. - Tests live in the `tests/Feature` and `tests/Unit` directories.
@ -525,9 +764,9 @@ ### Pest Tests
### Running Tests ### Running Tests
- Run the minimal number of tests using an appropriate filter before finalizing code edits. - Run the minimal number of tests using an appropriate filter before finalizing code edits.
- To run all tests: `php artisan test`. - To run all tests: `vendor/bin/sail artisan test`.
- To run all tests in a file: `php artisan test tests/Feature/ExampleTest.php`. - To run all tests in a file: `vendor/bin/sail artisan test tests/Feature/ExampleTest.php`.
- To filter on a particular test name: `php artisan test --filter=testName` (recommended after making a change to a related file). - To filter on a particular test name: `vendor/bin/sail artisan test --filter=testName` (recommended after making a change to a related file).
- When the tests relating to your changes are passing, ask the user if they would like to run the entire test suite to ensure everything is still passing. - When the tests relating to your changes are passing, ask the user if they would like to run the entire test suite to ensure everything is still passing.
### Pest Assertions ### Pest Assertions

View File

@ -1,7 +1,16 @@
{ {
"agents": [ "agents": [
"codex", "codex",
"copilot",
"gemini",
"opencode" "opencode"
], ],
"guidelines": [] "editors": [
"codex",
"gemini",
"opencode",
"vscode"
],
"guidelines": [],
"sail": true
} }

View File

@ -0,0 +1,114 @@
# SECTION A — FILAMENT V5 NOTES (BULLETS + SOURCES)
## Versioning & Base Requirements
- Rule: Filament v5 requires Livewire v4.0+.
When: Always when installing/upgrading Filament v5. When NOT: Never target Livewire v3 in a v5 codebase.
Source: https://filamentphp.com/docs/5.x/upgrade-guide — “Upgrading Livewire”
## Panels — Configuration
- Rule: Panels are configured via dedicated panel providers; the default admin panel ships at `/admin`.
When: Always—centralize panel setup (resources/pages/widgets/plugins) here. When NOT: Dont scatter core panel configuration across unrelated providers.
Source: https://filamentphp.com/docs/5.x/panel-configuration — “The default admin panel”
- Rule: New panel providers must be registered in `bootstrap/providers.php` for Laravel 11+ (or `config/app.php` for Laravel 10 and below).
When: When adding a new panel or if panel creation didnt auto-register. When NOT: Dont register panel providers in `bootstrap/app.php`.
Source: https://filamentphp.com/docs/5.x/panel-configuration — “Creating a new panel”
- Rule: Use `path()` to change a panels URL prefix; `path('')` mounts at `/` and can conflict with existing routes.
When: When `/admin` is not desired. When NOT: Dont set `path('')` if `/` is already routed in `routes/web.php`.
Source: https://filamentphp.com/docs/5.x/panel-configuration — “Changing the path”
- Rule: Use render hooks to inject Blade content into Filament layouts without publishing internal views.
When: When inserting banners/scripts/partials into specific layout locations. When NOT: Dont publish Filament views for small layout tweaks.
Source: https://filamentphp.com/docs/5.x/advanced/render-hooks — “Registering render hooks”
## Navigation — Groups, Ordering, Visibility
- Rule: Navigation is built from resources/pages/clusters and can be grouped, sorted, and conditionally shown/hidden.
When: Always—use groups/sorting for predictable IA. When NOT: Dont treat “hidden navigation” as authorization; still enforce policies/access checks.
Source: https://filamentphp.com/docs/5.x/navigation/overview — “Introduction”
- Rule: Clusters group resources/pages under a single nav item and provide sub-navigation.
When: When the sidebar becomes too large and needs hierarchy. When NOT: Dont cluster if it reduces discoverability for your users.
Source: https://filamentphp.com/docs/5.x/navigation/clusters — “Introduction”
- Rule: Cluster code structure (moving pages/resources into a cluster directory) is recommended, not required; behavior depends on `$cluster`.
When: When you want consistent organization. When NOT: Dont treat the directory layout as mandatory.
Source: https://filamentphp.com/docs/5.x/navigation/clusters — “Code structure recommendations for panels using clusters”
- Rule: The user menu is configured via `userMenuItems()` with Action objects and supports conditional visibility and sidebar placement.
When: When adding account-related links/actions. When NOT: Dont put destructive actions there without confirmation + authorization.
Source: https://filamentphp.com/docs/5.x/navigation/user-menu — “Introduction”
## Theming & Branding — CSS Hooks, Colors, Icons
- Rule: Filament exposes CSS hook classes (prefixed `fi-`) and recommends discovering them via browser DevTools.
When: When styling core UI safely. When NOT: Dont rely on brittle selectors; request/PR missing hooks instead of hacking around them.
Source: https://filamentphp.com/docs/5.x/styling/css-hooks — “Discovering hook classes”
- Rule: Apply styling by targeting hook classes (including Tailwind `@apply`) and use `!important` sparingly.
When: When overriding default spacing/appearance in a maintainable way. When NOT: Dont blanket `!important` your theme.
Source: https://filamentphp.com/docs/5.x/styling/css-hooks — “Applying styles to hook classes”
- Rule: Customize the default semantic color palettes via `FilamentColor::register()` (e.g., primary, danger, etc.).
When: When aligning Filament semantics to your brand palette. When NOT: Dont hardcode per-component hex values when semantics suffice.
Source: https://filamentphp.com/docs/5.x/styling/colors — “Customizing the default colors”
- Rule: Replace default UI icons globally via `FilamentIcon::register()` (icon aliases map to your chosen icons).
When: When standardizing iconography or switching icon sets. When NOT: Dont hardcode SVGs everywhere when aliases cover the UI.
Source: https://filamentphp.com/docs/5.x/styling/icons — “Replacing the default icons”
## Asset System — Global + Lazy / On-Demand
- Rule: Register shared assets via `FilamentAsset::register()`; assets are published into `/public` when `php artisan filament:assets` runs.
When: For app/plugin assets loaded by Filament. When NOT: Dont assume assets exist in production without the publish step.
Source: https://filamentphp.com/docs/5.x/advanced/assets — “The FilamentAsset facade”
- Rule: Lazy-load CSS with `x-load-css` and prevent auto-loading via `loadedOnRequest()` for page-specific styles.
When: For heavy CSS used only on certain pages/components. When NOT: Dont lazy-load tiny styles used everywhere.
Source: https://filamentphp.com/docs/5.x/advanced/assets — “Lazy loading CSS”
- Rule: Lazy-load JavaScript with `x-load-js` and prevent auto-loading via `loadedOnRequest()` for page-specific scripts.
When: For heavy JS libraries used only on a subset of pages/widgets. When NOT: Dont lazy-load scripts required for baseline panel behavior.
Source: https://filamentphp.com/docs/5.x/advanced/assets — “Lazy loading JavaScript”
## Plugin System — Mechanics + Panel Scoping
- Rule: Panel plugins integrate via a Plugin object; standalone plugins integrate via a service provider (no panel binding).
When: When deciding whether a package needs panel-specific registration. When NOT: Dont force a Plugin object for standalone-only components.
Source: https://filamentphp.com/docs/5.x/plugins/getting-started — “The Plugin object”
- Rule: Plugin assets should be registered in the plugins service provider so Filament can manage publishing/loading.
When: When shipping CSS/JS/Alpine components with a package. When NOT: Dont register package assets ad-hoc only in app panel providers.
Source: https://filamentphp.com/docs/5.x/plugins/getting-started — “Registering assets”
- Rule: Panel-scoped module/plugin registration can be centralized using `Panel::configureUsing()` for modular architectures.
When: When modules should enable Filament artifacts only for specific panels. When NOT: Dont register all modules globally if panels target different audiences.
Source: https://filamentphp.com/docs/5.x/advanced/modular-architecture — “Registering plugins conditionally for specific panels”
- Rule: Panel plugins are configurable per panel; treat “enabled in panel A” and “enabled in panel B” as independent configuration contexts.
When: When the same app has multiple panels with different modules enabled. When NOT: Dont assume a plugin is active across all panels.
Source: https://filamentphp.com/docs/5.x/plugins/panel-plugins — “Introduction”
## Actions — Modals, Confirmation, Execution
- Rule: Use `Action::make(...)->action(...)` to execute logic; use `requiresConfirmation()` for confirmation modals.
When: For destructive or high-impact actions. When NOT: Dont skip confirmation for destructive operations.
Source: https://filamentphp.com/docs/5.x/actions/modals — “Confirmation modals”
- Rule: Action modals can render a schema to collect input; submitted modal data is available to the action handler.
When: When you need “form-in-action” workflows. When NOT: Dont create bespoke modals when schema-in-modal is sufficient.
Source: https://filamentphp.com/docs/5.x/actions/modals — “Rendering a form in a modal”
## Resources & Pages — Global Search, Queries
- Rule: Global search returns results for a resource only if it has an Edit or View page; otherwise the resource returns no results.
When: When enabling global search for a resource. When NOT: Dont expect global search on resources without Edit/View pages.
Source: https://filamentphp.com/docs/5.x/resources/global-search — “Setting global search result titles”
- Rule: If global search result details access relationships, override the global search query to eager-load them to avoid N+1.
When: Any time you return relationship-backed details. When NOT: Dont lazy-load relationships in global search rendering.
Source: https://filamentphp.com/docs/5.x/resources/global-search — “Adding extra details to global search results”
- Rule: You can disable search term splitting for performance on large datasets.
When: When global search becomes expensive. When NOT: Dont disable if multi-word relevance is important.
Source: https://filamentphp.com/docs/5.x/resources/global-search — “Disabling search term splitting”
## Tables — Empty States
- Rule: Tables support empty-state content and can add empty-state actions to guide first-time users.
When: Always—empty-state guidance improves “first run” UX. When NOT: Dont leave empty lists without a clear next step when creation is expected.
Source: https://filamentphp.com/docs/5.x/tables/empty-state — “Adding empty state actions”
## Testing
- Rule: Filament testing distinguishes Livewire components (pages/relation managers/widgets) from non-Livewire classes (resources/actions/schema objects).
When: When choosing what to mount with `Livewire::test()`. When NOT: Dont try to Livewire-test non-Livewire classes directly.
Source: https://filamentphp.com/docs/5.x/testing/overview — “What is a Livewire component when using Filament?”
- Rule: Actions have dedicated testing guidance; follow it for asserting action execution and side effects.
When: When actions drive critical business workflows. When NOT: Dont leave actions untested if they mutate data or trigger external effects.
Source: https://filamentphp.com/docs/5.x/testing/testing-actions — “Testing actions”
## OPINIONATED — best practices not explicitly mandated by docs
- Rule: Standardize destructive actions: danger styling + requiresConfirmation() + policy authorization + success/error notification.
When: For delete/force-delete/restore/bulk destructive ops. When NOT: For trivial, reversible toggles.
## UNVERIFIED — not explicitly stated in the cited v5 pages above
- Rule: If you need confirmation or a modal, prefer `->action(...)` + `->requiresConfirmation()`; use `->url(...)` for navigation links.
When: When deciding whether an action should execute vs navigate. When NOT: Dont assume modal/confirmation behavior for URL-only actions without verifying in docs.
Source: https://filamentphp.com/docs/5.x/actions/modals — “Confirmation modals”

14
opencode.json Normal file
View File

@ -0,0 +1,14 @@
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"laravel-boost": {
"type": "local",
"enabled": true,
"command": [
"vendor/bin/sail",
"artisan",
"boost:mcp"
]
}
}
}

View File

@ -31,5 +31,5 @@ ## Feature Readiness
## Notes ## Notes
- This is a technical upgrade, but the spec intentionally describes outcomes and guardrails rather than implementation steps. - This is a technical upgrade, but the spec describes outcomes and guardrails rather than implementation steps.
- Proceed to planning: identify concrete packages, versions, and migration steps in plan/tasks. - Planning can capture concrete versions, dependency changes, and migration steps.

View File

@ -3,7 +3,7 @@ # Feature Specification: Admin UI Stack Upgrade (Panel + Suite)
**Feature Branch**: `057-filament-v5-upgrade` **Feature Branch**: `057-filament-v5-upgrade`
**Created**: 2026-01-20 **Created**: 2026-01-20
**Status**: Draft **Status**: Draft
**Input**: Upgrade the existing admin UI stack to the next supported major release to maintain compatibility and support, and ensure no regressions for tenant isolation and Ops-UX/Monitoring guardrails. **Input**: Upgrade the existing admin UI stack to the next supported major release to maintain compatibility and support, and ensure no regressions for tenant isolation and Monitoring/Operations safety guardrails.
## Clarifications ## Clarifications
@ -19,7 +19,7 @@ ## User Scenarios & Testing *(mandatory)*
### User Story 1 - Admin UI keeps working after upgrade (Priority: P1) ### User Story 1 - Admin UI keeps working after upgrade (Priority: P1)
Administrators can sign in and use the admin panel (navigation, resources, forms, tables, actions) without runtime errors after the upgrade. Administrators can sign in and use the admin panel (navigation, lists, forms, actions) without runtime errors after the upgrade.
**Why this priority**: This is the minimum bar for the upgrade to be safe; if the admin UI is unstable, all operational work stops. **Why this priority**: This is the minimum bar for the upgrade to be safe; if the admin UI is unstable, all operational work stops.
@ -75,18 +75,18 @@ ## Requirements *(mandatory)*
### Functional Requirements ### Functional Requirements
- **FR-001**: The application MUST upgrade the admin panel stack to the next supported major release and its required compatible reactive UI layer. - **FR-001**: The system MUST upgrade the admin UI stack to the next supported major release and remain fully functional for all in-scope admin workflows.
- **FR-002**: The application MUST continue to support the existing styling system without asset build failures. - **FR-002**: The system MUST continue to support the existing styling and asset pipeline without build failures.
- **FR-003**: All existing Filament panels MUST load successfully for authorized users and preserve core interactions (navigation, tables, forms, actions, notifications, widgets). - **FR-003**: All existing admin pages MUST load successfully for authorized users and preserve core interactions (navigation, lists, forms, actions, notifications, and global UI elements).
- **FR-004**: SPA-style navigation flows MUST continue to work, including global widget mounting and event delivery across navigation. - **FR-004**: In-app navigation between admin pages MUST continue to work reliably, including any global progress indicators and event-driven UI behavior.
- **FR-005**: Everything rendered under the Monitoring → Operations navigation section (including widgets/partials/tabs) MUST remain DB-only: no outbound HTTP requests are permitted during page render or during background/automatic Livewire requests (polling/auto-refresh/hydration). - **FR-005**: Everything rendered under the Monitoring → Operations navigation section (including widgets/partials/tabs) MUST remain DB-only: no outbound HTTP requests are permitted during page render or during background/automatic requests (polling/auto-refresh/hydration).
- **FR-010**: Any remote work initiated from Monitoring/Operations pages MUST be triggered only by explicit user actions (e.g., buttons) and MUST enqueue tracked operations (e.g., `OperationRun`-backed jobs) rather than performing outbound HTTP inline. - **FR-006**: Tenant isolation MUST be preserved across requests and interactive UI behavior: all reads/writes/events/caches MUST scope to the active tenant.
- **FR-006**: Tenant isolation MUST be preserved across requests and Live UI interactions: all reads/writes/events/caches MUST scope to the active tenant. - **FR-007**: Compatibility risks MUST be managed by producing an explicit inventory of affected third-party dependencies and documenting upgrade/replacement decisions.
- **FR-007**: Compatibility risks MUST be managed by producing an explicit inventory of affected third-party packages and documenting upgrade/replacement decisions.
- **FR-011**: If a third-party package is incompatible with the upgraded stack, the system MUST preserve equivalent functionality by upgrading or replacing the package; mixed-version pinning is not allowed. Any unavoidable feature loss MUST be handled as an explicit scope/decision change.
- **FR-012**: Database migrations are allowed only if strictly required for compatibility; they MUST be reversible and non-destructive (no data loss). Migrations MUST include a safe `down()` path and avoid drops without an explicit plan, and MUST be mentioned in release notes.
- **FR-008**: The upgrade MUST not introduce new Microsoft Graph read/write behavior; if any Graph-touching behavior changes are required, they MUST be explicitly specified with safety gates and observability updates. - **FR-008**: The upgrade MUST not introduce new Microsoft Graph read/write behavior; if any Graph-touching behavior changes are required, they MUST be explicitly specified with safety gates and observability updates.
- **FR-009**: The upgrade MUST include a documented rollback procedure that restores the previous working state. - **FR-009**: The upgrade MUST include a documented rollback procedure that restores the previous working state.
- **FR-010**: Any remote work initiated from Monitoring/Operations pages MUST be triggered only by explicit user actions and MUST enqueue a tracked operation (with an observable run record) rather than performing outbound HTTP inline.
- **FR-011**: If a third-party dependency is incompatible with the upgraded stack, the system MUST preserve equivalent functionality by upgrading or replacing the dependency; mixed-version pinning is not allowed. Any unavoidable feature loss MUST be handled as an explicit scope/decision change.
- **FR-012**: Database migrations are allowed only if strictly required for compatibility; they MUST be reversible and non-destructive (no data loss) and MUST be mentioned in release notes.
### Assumptions & Dependencies ### Assumptions & Dependencies
@ -97,7 +97,7 @@ ### Assumptions & Dependencies
### Key Entities *(include if feature involves data)* ### Key Entities *(include if feature involves data)*
- **Tenant**: The active tenant context that scopes all data access and UI state. - **Tenant**: The active tenant context that scopes all data access and UI state.
- **OperationRun**: The persisted record of long-running operations shown in Monitoring/Operations views. - **Run Record**: The persisted record of long-running operations shown in Monitoring/Operations views.
- **AuditLog**: The tenant-scoped audit trail used to retain accountability for sensitive actions. - **AuditLog**: The tenant-scoped audit trail used to retain accountability for sensitive actions.
## Success Criteria *(mandatory)* ## Success Criteria *(mandatory)*

View File

@ -0,0 +1,35 @@
# Specification Quality Checklist: Tenant UI Polish (v1)
**Purpose**: Validate specification completeness and quality before proceeding to planning
**Created**: 2026-01-20
**Feature**: [specs/058-tenant-ui-polish/spec.md](../spec.md)
## Content Quality
- [x] No implementation details (languages, frameworks, APIs)
- [x] Focused on user value and business needs
- [x] Written for non-technical stakeholders
- [x] All mandatory sections completed
## Requirement Completeness
- [x] No [NEEDS CLARIFICATION] markers remain
- [x] Requirements are testable and unambiguous
- [x] Success criteria are measurable
- [x] Success criteria are technology-agnostic (no implementation details)
- [x] All acceptance scenarios are defined
- [x] Edge cases are identified
- [x] Scope is clearly bounded
- [x] Dependencies and assumptions identified
## Feature Readiness
- [x] All functional requirements have clear acceptance criteria
- [x] User scenarios cover primary flows
- [x] Feature meets measurable outcomes defined in Success Criteria
- [x] No implementation details leak into specification
## Notes
- Validation run: 2026-01-20
- Items marked incomplete require spec updates before `/speckit.clarify` or `/speckit.plan`

View File

@ -0,0 +1,22 @@
# Polling Contract — Calm UI Rules
## Principle
Polling is allowed only when it materially improves UX (active operations). It must be DB-only and must stop when no longer needed.
## Dashboard
- Polling is enabled only while active runs exist (queued/running) for the current tenant.
- Polling is disabled when:
- No active runs exist.
## Operations index
- Polling is enabled only while active runs exist.
- Polling is disabled when:
- No active runs exist.
## Modals
- No polling inside modals.
- When a modal is open, polling should not cause churn in the background.
## Technical approach
- Widgets: use `$pollingInterval = null` to disable polling.
- Tables: apply `$table->poll('10s')` only when active runs exist.

View File

@ -0,0 +1,28 @@
# UI Contracts — Tenant UI Polish
This feature does not introduce HTTP APIs. These contracts describe UI routing, filters, and definitions that must remain stable.
## Routes (tenant-scoped)
- Dashboard: tenant dashboard page (new custom page; replaces default dashboard entry).
- Inventory hub: Inventory cluster root (routes to first page/resource in cluster).
- Inventory items: Inventory items resource index, under cluster prefix.
- Inventory sync runs: Inventory sync runs resource index, under cluster prefix.
- Inventory coverage: Inventory coverage page, under cluster prefix.
- Operations index: `OperationRunResource` index (`/operations`).
- Operation run detail: `OperationRunResource` view page.
## Operations Tabs (FR-009)
Tabs filter the Operations table by:
- All: no extra constraints.
- Active: `status IN ('queued','running')`
- Succeeded: `status = 'completed' AND outcome = 'succeeded'`
- Partial: `status = 'completed' AND outcome = 'partial'`
- Failed: `status = 'completed' AND outcome = 'failed'`
## KPI Definitions
- Inventory coverage % = Restorable / Total (Partial is separate, does not inflate %).
- Drift stale threshold = 7 days.
- “Recent” lists default size = 10.
- “Active operations” shows two counts:
- All active runs (queued + running)
- Inventory-active runs (type = `inventory.sync`, queued + running)

View File

@ -0,0 +1,76 @@
# Data Model — Tenant UI Polish
This feature is read-only. It introduces no schema changes.
## Entities
### Tenant
- **Role**: scope boundary for all queries.
- **Source**: `Tenant::current()` (Filament tenancy).
### OperationRun
- **Role**: operations feed, KPIs, and canonical “View run” destinations.
- **Key fields used** (existing):
- `tenant_id`
- `type`
- `status` (`queued|running|completed`)
- `outcome` (`succeeded|partial|failed|...`)
- `created_at`, `started_at`, `completed_at`
- `summary_counts`, `failure_summary` (JSONB)
**Derived values**:
- **Active**: `status IN ('queued','running')`
- **Terminal**: `status = 'completed'`
- **Avg duration (7 days)**: only terminal runs with `started_at` and `completed_at`.
### InventoryItem
- **Role**: inventory totals and coverage chips.
- **Key fields used** (existing, inferred from resources):
- `tenant_id`
- coverage-related flags / fields used to categorize: Restorable, Partial, Risk, Dependencies
**Derived values**:
- Total items
- Coverage % = `restorable / total` (if total > 0)
- Chip counts: Restorable, Partial, Risk, Dependencies
### InventorySyncRun
- **Role**: “Last Inventory Sync” and “Sync Runs” list.
- **Key fields used**:
- `tenant_id`
- status + timestamps
- any “selection_hash / selection payload” metadata used for display
### Finding (Drift Finding)
- **Role**: drift KPIs and “Needs Attention”.
- **Key fields used** (existing migration):
- `tenant_id`
- `severity` (enum-like string)
- `status` (open/closed)
- timestamps
- `scope_key` for grouping
**Derived values**:
- Open findings by severity
- Staleness: last drift scan older than 7 days
## KPI Queries (read-only)
### Dashboard
- Drift KPIs: counts of open findings by severity + stale drift indicator.
- Operations health: counts of active runs + failed/partial recent.
- Recent lists: latest 10 findings + latest 10 operation runs.
### Inventory hub
- Total items
- Coverage % (restorable/total)
- Last inventory sync (status + timestamp)
- Active operations: (all active runs) + (inventory.sync active runs)
### Operations index
- Total runs (30d)
- Active runs (queued + running)
- Failed/partial (7d)
- Avg duration (7d, terminal runs only)
All queries must be tenant-scoped.

View File

@ -0,0 +1,164 @@
# Implementation Plan: Tenant UI Polish (Dashboard + Inventory Hub + Operations)
**Branch**: `058-tenant-ui-polish` | **Date**: 2026-01-20 | **Spec**: `specs/058-tenant-ui-polish/spec.md`
**Input**: Feature specification from `specs/058-tenant-ui-polish/spec.md`
**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/scripts/` for helper scripts.
## Summary
- Build a drift-first, tenant-scoped dashboard with “Needs Attention” and recent lists.
- Make Inventory a hub using a Filament cluster to provide consistent left-side sub-navigation across Items / Sync Runs / Coverage.
- Upgrade Operations index to “orders-style” with KPIs + status tabs filtering the existing `OperationRunResource` table.
- Enforce DB-only renders (and DB-only polling) and a calm UI: polling only while active runs exist, and no polling churn in modals.
## Technical Context
<!--
ACTION REQUIRED: Replace the content in this section with the technical details
for the project. The structure here is presented in advisory capacity to guide
the iteration process.
-->
**Language/Version**: PHP 8.4.15 (Laravel 12.47.0)
**Primary Dependencies**: Filament v5.0.0, Livewire v4.0.1
**Storage**: PostgreSQL
**Testing**: Pest v4 (+ PHPUnit v12 runtime)
**Target Platform**: Web application (Filament admin panel)
**Project Type**: Web (Laravel monolith)
**Performance Goals**: Dashboard/Inventory/Operations render quickly (target <2s for typical tenants) with efficient tenant-scoped queries and no N+1.
**Constraints**: DB-only for all page renders and any polling/auto-refresh; avoid UI churn in modals.
**Scale/Scope**: Tenant-scoped surfaces; KPI math on existing `operation_runs`, `findings`, inventory tables.
## Constitution Check
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
- Inventory-first: clarify what is “last observed” vs snapshots/backups
- Read/write separation: any writes require preview + confirmation + audit + tests
- Graph contract path: Graph calls only via `GraphClientInterface` + `config/graph_contracts.php`
- Deterministic capabilities: capability derivation is testable (snapshot/golden tests)
- Tenant isolation: all reads/writes tenant-scoped; cross-tenant views are explicit and access-checked
- Run observability: long-running/remote/queued work creates/reuses `OperationRun`; start surfaces enqueue-only; Monitoring is DB-only; DB-only <2s actions may skip runs but security-relevant ones still audit-log
- Automation: queued/scheduled ops use locks + idempotency; handle 429/503 with backoff+jitter
- Data minimization: Inventory stores metadata + whitelisted meta; logs contain no secrets/tokens
Status: ✅ No constitution violations for this feature (read-only, DB-only, tenant-scoped; no Graph calls added).
## Project Structure
### Documentation (this feature)
```text
specs/058-tenant-ui-polish/
├── plan.md # This file (/speckit.plan command output)
├── research.md # Phase 0 output (/speckit.plan command)
├── data-model.md # Phase 1 output (/speckit.plan command)
├── quickstart.md # Phase 1 output (/speckit.plan command)
├── contracts/ # Phase 1 output (/speckit.plan command)
└── tasks.md # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan)
```
### Source Code (repository root)
<!--
ACTION REQUIRED: Replace the placeholder tree below with the concrete layout
for this feature. Delete unused options and expand the chosen structure with
real paths (e.g., apps/admin, packages/something). The delivered plan must
not include Option labels.
-->
```text
app/
├── Filament/
│ ├── Clusters/ # New: Inventory cluster
│ ├── Pages/ # New/updated: tenant dashboard, inventory landing, coverage
│ ├── Resources/ # Updated: attach inventory resources to cluster; operations tabs/KPIs
│ └── Widgets/ # New/updated: KPI header widgets
├── Models/ # Existing: Tenant, OperationRun, Finding, InventoryItem, InventorySyncRun
└── Providers/Filament/
└── AdminPanelProvider.php # Update: discoverClusters(), dashboard page class
resources/
└── views/ # Optional: partials/views for dashboard sections
tests/
└── Feature/
├── Monitoring/ # Existing: Operations DB-only + tenant scope tests
└── Filament/ # Existing + new: Inventory/Dashboard page tests
```
**Structure Decision**: Laravel monolith + Filament (v5) conventions. Implement UI changes via:
- Filament Pages (dashboard + inventory pages)
- Filament Clusters (inventory sub-navigation)
- Filament Widgets (KPI headers / recent lists)
- Filament Resource list tabs (operations index filtering)
## Complexity Tracking
> **Fill ONLY if Constitution Check has violations that must be justified**
None.
## Phase 0 — Outline & Research (complete)
- Output: `specs/058-tenant-ui-polish/research.md`
- Key decisions captured:
- Use Filament clusters for the Inventory hub sub-navigation.
- Use Filament widgets for KPI headers.
- Enable polling only while active runs exist.
## Phase 1 — Design & Contracts (complete)
### Data model
- Output: `specs/058-tenant-ui-polish/data-model.md`
- No schema changes required.
### UI contracts
- Output: `specs/058-tenant-ui-polish/contracts/ui.md`
- Output: `specs/058-tenant-ui-polish/contracts/polling.md`
### Provider registration (Laravel 11+)
- Panel providers remain registered in `bootstrap/providers.php` (no changes required for this feature unless adding a new provider).
### Livewire / Filament version safety
- Livewire v4.0+ (required by Filament v5) is in use.
### Asset strategy
- Prefer existing Filament theme CSS and hook classes; avoid publishing Filament internal views.
- No heavy assets expected; if any new panel assets are added, ensure deployment runs `php artisan filament:assets`.
### Destructive actions
- None introduced in this feature.
### Constitution re-check (post-design)
- ✅ Inventory-first: dashboard uses Inventory/Findings/OperationRun as last-observed state.
- ✅ Read/write separation: this feature is read-only.
- ✅ Graph contract path: no Graph calls added.
- ✅ Tenant isolation: all queries remain tenant-scoped.
- ✅ Run observability: only consumes existing `OperationRun` records; no new long-running work is introduced.
- ✅ Data minimization: no new payload storage.
## Phase 2 — Implementation Plan (next)
### Story 1 (P1): Drift-first tenant dashboard
- Create a custom Filament dashboard page (tenant-scoped) and wire it in `AdminPanelProvider` instead of the default `Dashboard::class`.
- Implement drift + ops KPIs and “Needs Attention” + recent lists using DB-only Eloquent queries.
- Implement conditional polling (only while active runs exist) using widget polling controls.
- Tests:
- Add DB-only coverage tests for the dashboard (no outbound HTTP; no queued jobs on render).
- Add tenant scope tests for the dashboard.
### Story 2 (P2): Inventory becomes a hub
- Add `discoverClusters()` to `AdminPanelProvider`.
- Create `InventoryCluster` and assign `$cluster` on inventory pages/resources.
- Add a shared inventory KPI header (widget) across the cluster surfaces.
- Tests:
- Extend existing inventory page tests to assert cluster pages load and remain tenant-scoped.
### Story 3 (P3): Operations index “orders-style”
- Update `OperationRunResource` list page to:
- Add KPI header widgets.
- Add tabs: All / Active / Succeeded / Partial / Failed.
- Enable table polling only while active runs exist.
- Tests:
- Extend operations tests to assert page renders with tabs and remains DB-only/tenant-scoped.

View File

@ -0,0 +1,27 @@
# Quickstart — Tenant UI Polish
## Prereqs
- Run everything via Sail.
## Setup
- `vendor/bin/sail up -d`
- `vendor/bin/sail composer install`
## Run tests (targeted)
- `vendor/bin/sail artisan test tests/Feature/Monitoring/OperationsDbOnlyTest.php`
- `vendor/bin/sail artisan test tests/Feature/Monitoring/OperationsTenantScopeTest.php`
- `vendor/bin/sail artisan test tests/Feature/Filament/InventoryPagesTest.php`
When the feature is implemented, add + run:
- Dashboard DB-only + tenant scope tests (new).
## Manual QA (tenant-scoped)
- Sign in, select a tenant.
- Visit Dashboard: verify drift/ops KPIs, needs attention, and recent lists.
- Visit Inventory cluster: Items / Sync Runs / Coverage share left sub-navigation and KPI header.
- Visit Operations (`/operations`): KPI header + tabs filter table.
## Frontend assets
If UI changes dont show:
- `vendor/bin/sail npm run dev`
- or `vendor/bin/sail npm run build`

View File

@ -0,0 +1,66 @@
# Research — Tenant UI Polish (Dashboard + Inventory Hub + Operations)
## Goal
Deliver a drift-first, tenant-scoped UI polish pass that is:
- DB-only on render and on any auto-refresh.
- Calm (polling only when needed; no modal churn).
- Consistent IA (Inventory hub sub-navigation; canonical Operations).
## Existing Code & Patterns (to reuse)
### Operations
- Canonical list/detail already exist via `OperationRunResource` (`/operations`).
- Tenant scoping already enforced in `OperationRunResource::getEloquentQuery()`.
- Detail view already uses conditional polling with safeguards (tab hidden / modal open) via `RunDetailPolling`.
### Inventory
- Inventory entry page exists as `InventoryLanding`.
- Inventory “Items” and “Sync Runs” are currently resources (`InventoryItemResource`, `InventorySyncRunResource`).
- Inventory sync “start surface” already follows constitution rules: authorize → create/reuse `OperationRun` → enqueue job → “View run”.
### Monitoring DB-only + Tenant isolation tests
- Monitoring/Operations has DB-only tests and tenant scope tests.
- Inventory landing + coverage have basic smoke tests.
## Key Decisions
### Decision: Use Filament clusters to implement the Inventory “hub” navigation
- **Decision**: Create an Inventory cluster and attach:
- `InventoryLanding` (page)
- Inventory items resource
- Inventory sync runs resource
- `InventoryCoverage` (page)
- **Rationale**: Filament clusters are designed for “common sub-navigation between pages”, including mixing pages and resources.
- **Notes**:
- Requires enabling cluster discovery in the panel provider.
- Sub-navigation position will be set to `Start` to achieve left-side navigation.
### Decision: Implement KPI headers as widgets (StatsOverviewWidget / TableWidget)
- **Decision**: Use Filament widgets for KPI headers on:
- Tenant dashboard (drift + ops)
- Inventory hub (inventory KPIs)
- Operations index (ops KPIs)
- **Rationale**: Widgets are first-class, composable, and can optionally poll (with `$pollingInterval`) while remaining DB-only.
### Decision: “Calm UI” auto-refresh strategy
- **Decision**:
- Dashboard + Operations index: enable polling only while active runs exist.
- Widgets/tables: polling is disabled when no active runs exist.
- No polling inside modals.
- **Rationale**: Matches FR-012 and avoids background churn.
- **Implementation approach**:
- Use Filament polling mechanisms:
- Widgets: `$pollingInterval = null | '10s'` depending on “active runs exist”.
- Tables: enable `$table->poll('10s')` only when “active runs exist”.
### Decision: No Graph / remote dependencies
- **Decision**: All queries for this feature are Eloquent/PostgreSQL queries.
- **Rationale**: Matches constitution and SC-005.
## Alternatives Considered
- **Custom Blade layouts for hub navigation**: Rejected because clusters provide consistent sub-nav across resources/pages without fragile view overrides.
- **Always-on polling**: Rejected to comply with calm UI rules and avoid waste.
- **Keep `Monitoring/Operations` as canonical**: Rejected because `OperationRunResource` is already the canonical Operations surface with correct routing and detail pages.
## Open Questions
None — all “NEEDS CLARIFICATION” items are resolved for planning.

View File

@ -0,0 +1,218 @@
# Feature Specification: Tenant UI Polish (Dashboard + Inventory Hub + Operations)
**Feature Branch**: `058-tenant-ui-polish`
**Created**: 2026-01-20
**Status**: Draft
**Input**: User description: "Feature 058 — Tenant UI Polish: Dashboard + Inventory Hub + Operations \"Orders-style\" (v1)"
## Clarifications
### Session 2026-01-20
- Q: Coverage % definition for Inventory KPI header? → A: Coverage % = Restorable / Total (Partial remains a separate chip/number; main % stays conservative)
- Q: Drift stale threshold (last scan older than X days)? → A: 7 days
- Q: Inventory KPI “Active Operations” definition? → A: Show both counts: All active runs (queued + running) and Inventory-active runs (queued + running)
- Q: How many rows in “Recent” lists by default? → A: 10
### Session 2026-01-21
- Q: Operations index "Stuck" tab in v1? -> A: No "Stuck" tab in v1
## User Scenarios & Testing *(mandatory)*
<!--
IMPORTANT: User stories should be PRIORITIZED as user journeys ordered by importance.
Each user story/journey must be INDEPENDENTLY TESTABLE - meaning if you implement just ONE of them,
you should still have a viable MVP (Minimum Viable Product) that delivers value.
Assign priorities (P1, P2, P3, etc.) to each story, where P1 is the most critical.
Think of each story as a standalone slice of functionality that can be:
- Developed independently
- Tested independently
- Deployed independently
- Demonstrated to users independently
-->
### User Story 1 - Drift-first tenant dashboard (Priority: P1)
As a tenant admin, I can open a tenant-scoped dashboard that immediately surfaces drift risk and operations health, without triggering any remote calls.
**Why this priority**: This is the primary entry point for day-to-day operations and should be actionable at a glance.
**Independent Test**: Visiting the dashboard shows drift + operations KPIs, a “needs attention” list with working CTAs, and recent lists, while confirming no outbound HTTP happens during render and any background UI updates.
**Acceptance Scenarios**:
1. **Given** I am signed in to a tenant, **When** I open the Dashboard, **Then** I see tenant-scoped drift KPIs, operations health KPIs, and recent lists.
2. **Given** there are urgent drift issues (e.g., high severity open findings), **When** I view the Dashboard, **Then** they appear in the “Needs Attention” section with a CTA that navigates to a filtered view.
3. **Given** drift generation has a recent failed run, **When** I view the Dashboard, **Then** I can navigate from “Needs Attention” to the related operation run details.
4. **Given** there is no drift data yet, **When** I view the Dashboard, **Then** the dashboard renders calmly with empty-state messaging and no errors.
5. **Given** the last drift scan is older than 7 days, **When** I view the Dashboard, **Then** “Needs Attention” includes a “Drift stale” item with a CTA to investigate.
6. **Given** there are more than 10 drift findings and operation runs, **When** I view the Dashboard, **Then** each “Recent” list shows the 10 most recent items.
---
### User Story 2 - Inventory becomes a hub module (Priority: P2)
As a tenant admin, I can use Inventory as a “hub” with consistent sub-navigation and a shared KPI header across Inventory subpages.
**Why this priority**: Inventory is a high-traffic area; a hub layout reduces cognitive load and makes it easier to find the right view quickly.
**Independent Test**: Navigating Inventory Items / Sync Runs / Coverage keeps the same shared KPI header, the left sub-navigation is consistent, and all data remains tenant-scoped and DB-only.
**Acceptance Scenarios**:
1. **Given** I am signed in to a tenant, **When** I open Inventory, **Then** I see hub navigation (Items / Sync Runs / Coverage) and a shared KPI header.
2. **Given** I switch between Inventory subpages, **When** I navigate Items → Sync Runs → Coverage, **Then** the KPI header remains visible and consistent.
3. **Given** the tenant has an inventory sync run history, **When** I open “Sync Runs”, **Then** I see only sync runs relevant to inventory synchronization.
---
### User Story 3 - Operations index “Orders-style” (Priority: P3)
As a tenant admin, I can view Operations in an “orders-style” overview (KPIs + status tabs + table) to quickly assess activity and failures.
**Why this priority**: Operations is the canonical place to investigate work; better scanning and filtering reduces time-to-triage.
**Independent Test**: Visiting Operations index shows KPI cards and status tabs that correctly filter the table without introducing polling churn or any remote calls.
**Acceptance Scenarios**:
1. **Given** I am signed in to a tenant, **When** I open Operations, **Then** I see KPIs, status tabs, and the operations table.
2. **Given** there are active runs, **When** I click the “Active” tab, **Then** the table filters to queued + running runs only.
3. **Given** there are failed runs, **When** I click the “Failed” tab, **Then** the table filters to failed runs only.
4. **Given** I navigate away and back, **When** I return to Operations, **Then** the UI remains calm (no refresh loops) and loads quickly.
---
[Add more user stories as needed, each with an assigned priority]
### Edge Cases
- No data yet: dashboard/inventory/operations render with empty states and helpful CTAs.
- Large tenants: KPI calculations remain fast enough to keep pages responsive.
- Mixed outcomes: partial/failed/succeeded runs are correctly categorized and discoverable via tabs/filters.
- Tenant switching: no cross-tenant leakage of KPIs, lists, or links.
- Time windows: KPI windows (e.g., last 7/30 days) handle timezones consistently.
- “Unknown” states: missing duration/end time renders gracefully (e.g., avg duration excludes non-terminal runs).
## Requirements *(mandatory)*
**Constitution alignment (required):** If this feature introduces any Microsoft Graph calls, any write/change behavior,
or any long-running/queued/scheduled work, the spec MUST describe contract registry updates, safety gates
(preview/confirmation/audit), tenant isolation, run observability (`OperationRun` type/identity/visibility), and tests.
If security-relevant DB-only actions intentionally skip `OperationRun`, the spec MUST describe `AuditLog` entries.
<!--
ACTION REQUIRED: The content in this section represents placeholders.
Fill them out with the right functional requirements.
-->
### Functional Requirements
- **FR-001 (Tenant scope)**: System MUST ensure the Dashboard, Inventory hub, and Operations views are tenant-scoped, with no cross-tenant visibility.
- **FR-002 (DB-only surfaces)**: System MUST keep Dashboard, Inventory hub header, and Operations index DB-only during render and any background UI updates.
- **FR-003 (Placement policy)**: System MUST show KPI cards only on these entry-point pages: Dashboard, Inventory hub (shared header), and Operations index.
- **FR-004 (Inventory hub layout)**: System MUST provide an Inventory hub with left sub-navigation for Items, Sync Runs, and Coverage.
- **FR-005 (Inventory KPIs)**: Inventory hub MUST show a shared KPI header across Inventory subpages with:
- Total Items
- Coverage % (restorable items / total items; partial shown separately)
- Last Inventory Sync (status + timestamp)
- Active Operations (queued + running), showing both:
- All active runs
- Inventory-active runs
- **FR-006 (Inventory sync runs view)**: System MUST provide a “Sync Runs” view that lists only inventory synchronization runs.
- **FR-007 (Coverage chips)**: System MUST standardize coverage chips to this set only: Restorable, Partial, Risk, Dependencies.
- **FR-008 (Operations index KPIs)**: Operations index MUST show tenant-scoped KPIs:
- Total Runs (30 days)
- Active Runs (queued + running)
- Failed/Partial (7 days)
- Avg Duration (7 days, terminal runs only)
- **FR-009 (Operations tabs)**: Operations index MUST provide status tabs that filter the operations table: All, Active, Succeeded, Partial, Failed. No "Stuck" tab in v1.
- **FR-010 (Canonical terminology)**: System MUST use “Operations” as the canonical label (no legacy naming on these surfaces).
- **FR-011 (Canonical links)**: “View run” links MUST always navigate to the canonical operation run detail view.
- **FR-012 (Calm UI rules)**: System MUST avoid polling/churn in modals and avoid refresh loops; background updates should be used only where clearly necessary. Auto-refresh on Dashboard and Operations index is allowed only while active runs (queued/running) exist, and MUST stop when there are no active runs.
- **FR-013 (Drift stale rule)**: System MUST flag drift as “stale” when the last drift scan is older than 7 days and surface it in “Needs Attention” with an investigation CTA.
- **FR-014 (Recent list sizing)**: System MUST show 10 rows by default for “Recent Drift Findings” and “Recent Operations”.
### OperationRun status mapping (for tabs and KPIs)
OperationRun uses two canonical fields that drive UI filters:
- `status`: execution lifecycle (e.g., queued/running/completed)
- `outcome`: terminal result (e.g., succeeded/partially_succeeded/failed/cancelled)
Tab filters MUST map exactly as:
- **All**: no status/outcome filter
- **Active**: `status IN (queued, running)`
- **Succeeded**: `status = completed AND outcome = succeeded`
- **Partial**: `status = completed AND outcome = partially_succeeded`
- **Failed**: `status = completed AND outcome = failed`
Notes:
- No “Stuck” tab in v1.
- Runs with `outcome = cancelled` appear under **All** only (unless a future “Cancelled” tab is added).
- Any legacy status/outcome values must already be normalized before reaching this UI (out of scope for this feature).
### KPI window definitions (timestamp basis)
All KPI windows are tenant-scoped and DB-only.
- **Total Runs (30 days)**: count OperationRuns by `created_at` within the last 30 days (includes all statuses/outcomes).
- **Active Runs**: current count where `status IN (queued, running)` (no time window).
- **Failed/Partial (7 days)**: count terminal runs where `status = completed AND outcome IN (failed, partially_succeeded)` and `completed_at` is within the last 7 days.
- **Avg Duration (7 days)**: average of `(completed_at - started_at)` for runs where `status = completed`, `started_at` and `completed_at` are present, and `completed_at` is within the last 7 days.
### Inventory coverage classification (Restorable/Partial/Risk/Dependencies)
Coverage chips and KPI aggregation MUST derive from the existing “policy type meta” and dependency capability signals (DB-only):
- `inventory_items.policy_type`
- `config('tenantpilot.supported_policy_types')` meta fields:
- `restore` (e.g., enabled / preview-only)
- `risk` (e.g., medium / medium-high / high)
- Dependency support computed via the existing coverage dependency resolver (based on contracts/config).
Definitions:
- **Restorable**: inventory items whose policy type meta has `restore = enabled`
- **Partial**: inventory items whose policy type meta has `restore = preview-only`
- **Risk**: inventory items whose policy type meta has `risk IN (medium-high, high)`
- **Dependencies**: inventory items whose policy type supports dependencies per the existing dependency capability resolver
Notes:
- This feature does not redefine coverage semantics; it standardizes UI rendering and KPI aggregation based on the existing policy type meta.
- If a policy type is unknown/missing meta, it MUST be treated conservatively (non-restorable) for KPI aggregation.
**Assumptions**:
- Drift findings, inventory items, and operation runs already exist as tenant-scoped data sources.
- “Coverage %” is Restorable/Total; Partial is shown separately (e.g., chips/secondary metric). If total is 0, coverage shows as not available.
- “Drift stale” default threshold is 7 days.
- “Recent” list default size is 10.
- Auto-refresh behavior (DB-only): Dashboard and Operations index auto-refresh only while active runs exist; otherwise it stops.
- Creating/generating drift is out of scope unless it can be performed as an explicit, enqueue-only user action that results in an operation run.
### Key Entities *(include if feature involves data)*
- **Tenant**: The scope boundary for all dashboards and lists in this feature.
- **Operation Run**: A tenant-scoped record of work execution, including status, timestamps, and outcomes used for Operations KPIs and recent lists.
- **Drift Finding**: A tenant-scoped record representing detected drift, including severity and state (open/closed) used for Dashboard KPIs and “Needs Attention”.
- **Inventory Item**: A tenant-scoped record representing inventory coverage and totals used in the Inventory hub.
## Success Criteria *(mandatory)*
<!--
ACTION REQUIRED: Define measurable success criteria.
These must be technology-agnostic and measurable.
-->
### Measurable Outcomes
- **SC-001 (Task speed)**: A tenant admin can reach “what needs attention” (drift/ops) from the dashboard within 30 seconds.
- **SC-002 (Discoverability)**: A tenant admin can find inventory sync runs within 2 clicks from Inventory.
- **SC-003 (Triage efficiency)**: A tenant admin can filter Operations to “Active” or “Failed” within 1 click and identify a run to investigate within 60 seconds.
- **SC-004 (Calm UI)**: No refresh loops are observed on Dashboard, Inventory hub pages, or Operations index during normal navigation.
- **SC-005 (Safety)**: Viewing Dashboard, Inventory hub pages, and Operations index does not trigger any outbound HTTP.

View File

@ -0,0 +1,192 @@
---
description: "Task list for feature implementation"
---
# Tasks: Tenant UI Polish (Dashboard + Inventory Hub + Operations)
**Input**: Design documents from `specs/058-tenant-ui-polish/`
**Tests**: Required (Pest) — this feature changes runtime UI behavior.
---
## Phase 1: Setup (Shared Infrastructure)
- [ ] T001 Confirm feature inputs exist: specs/058-tenant-ui-polish/spec.md, specs/058-tenant-ui-polish/plan.md
- [ ] T002 Confirm Phase 0/1 artifacts exist: specs/058-tenant-ui-polish/research.md, specs/058-tenant-ui-polish/data-model.md, specs/058-tenant-ui-polish/contracts/ui.md, specs/058-tenant-ui-polish/contracts/polling.md, specs/058-tenant-ui-polish/quickstart.md
---
## Phase 2: Foundational (Blocking Prerequisites)
- [ ] T003 Create shared helper to detect “active runs exist” for tenant polling in app/Support/OpsUx/ActiveRuns.php
- [ ] T004 [P] Add focused tests for the helper in tests/Feature/OpsUx/ActiveRunsTest.php
**Checkpoint**: Shared polling predicate exists and is covered.
---
## Phase 3: User Story 1 — Drift-first tenant dashboard (Priority: P1) 🎯 MVP
**Goal**: Tenant-scoped dashboard that surfaces drift + ops KPIs and “Needs Attention”, DB-only.
**Independent Test**: Visiting the dashboard renders drift KPIs, ops KPIs, needs-attention CTAs, and recent lists (10 rows), with no outbound HTTP and no background work dispatched.
### Tests (US1)
- [ ] T005 [P] [US1] Add DB-only render test (no outbound HTTP, no background work) in tests/Feature/Filament/TenantDashboardDbOnlyTest.php
- [ ] T006 [P] [US1] Add tenant isolation test (no cross-tenant leakage) in tests/Feature/Filament/TenantDashboardTenantScopeTest.php
### Implementation (US1)
- [ ] T007 [US1] Create tenant dashboard page in app/Filament/Pages/TenantDashboard.php
- [ ] T008 [US1] Register the tenant dashboard page in app/Providers/Filament/AdminPanelProvider.php (replace default Dashboard page entry)
- [ ] T009 [P] [US1] Create dashboard KPI widget(s) in app/Filament/Widgets/Dashboard/DashboardKpis.php
- [ ] T010 [P] [US1] Create “Needs Attention” widget in app/Filament/Widgets/Dashboard/NeedsAttention.php
- [ ] T011 [P] [US1] Create “Recent Drift Findings” widget (10 rows) in app/Filament/Widgets/Dashboard/RecentDriftFindings.php
- [ ] T012 [P] [US1] Create “Recent Operations” widget (10 rows) in app/Filament/Widgets/Dashboard/RecentOperations.php
- [ ] T013 [US1] Wire widgets into the dashboard page in app/Filament/Pages/TenantDashboard.php (header/sections) and implement conditional polling per specs/058-tenant-ui-polish/contracts/polling.md
- [ ] T014 [US1] Implement drift stale rule (7 days) + CTA wiring in app/Filament/Widgets/Dashboard/NeedsAttention.php
- [ ] T015 [US1] Ensure all dashboard queries are tenant-scoped + DB-only in app/Filament/Pages/TenantDashboard.php and app/Filament/Widgets/Dashboard/*.php
**Checkpoint**: US1 is shippable as an MVP.
---
## Phase 4: User Story 2 — Inventory becomes a hub module (Priority: P2)
**Goal**: Inventory becomes a cluster “hub” with consistent left sub-navigation and a shared KPI header across Items / Sync Runs / Coverage.
**Independent Test**: Navigating Items → Sync Runs → Coverage keeps consistent sub-navigation and shared KPI header, tenant-scoped and DB-only.
### Tests (US2)
- [ ] T016 [P] [US2] Add DB-only render test for Inventory hub surfaces in tests/Feature/Filament/InventoryHubDbOnlyTest.php
- [ ] T017 [P] [US2] Extend/adjust inventory navigation smoke coverage in tests/Feature/Filament/InventoryPagesTest.php
### Implementation (US2)
- [ ] T018 [US2] Enable cluster discovery in app/Providers/Filament/AdminPanelProvider.php (add `discoverClusters(...)`)
- [ ] T019 [US2] Create Inventory cluster class in app/Filament/Clusters/Inventory/InventoryCluster.php
- [ ] T020 [US2] Assign Inventory cluster to inventory pages in app/Filament/Pages/InventoryLanding.php and app/Filament/Pages/InventoryCoverage.php
- [ ] T021 [US2] Assign Inventory cluster to inventory resources in app/Filament/Resources/InventoryItemResource.php and app/Filament/Resources/InventorySyncRunResource.php
- [ ] T022 [P] [US2] Create shared Inventory KPI header widget in app/Filament/Widgets/Inventory/InventoryKpiHeader.php
- [ ] T023 [US2] Add Inventory KPI header widget to InventoryLanding in app/Filament/Pages/InventoryLanding.php
- [ ] T024 [US2] Add Inventory KPI header widget to InventoryCoverage in app/Filament/Pages/InventoryCoverage.php
- [ ] T025 [US2] Add Inventory KPI header widget to Inventory items list in app/Filament/Resources/InventoryItemResource.php (or its list page)
- [ ] T026 [US2] Add Inventory KPI header widget to Inventory sync runs list in app/Filament/Resources/InventorySyncRunResource.php (or its list page)
- [ ] T027 [US2] Ensure Inventory KPI definitions match specs/058-tenant-ui-polish/contracts/ui.md (coverage % restorable/total; partial separate; two active operations counts)
- [ ] T041 [US2] Inventory coverage semantics reference (A2)
- Identify and document the exact source-of-truth fields for Inventory KPI aggregation:
- `inventory_items.policy_type`
- `config('tenantpilot.supported_policy_types')` meta fields (`restore`, `risk`)
- Dependency support via existing dependency capability resolver
- Ensure KPI and chips read from this source only (DB-only).
- DoD:
- One canonical place documented and referenced by inventory KPIs.
- No “magic” or duplicated classification logic across pages/widgets.
- [ ] T028 [US2] Ensure “Sync Runs” view is inventory-only per spec in app/Filament/Resources/InventorySyncRunResource.php (query/filter by run type/intent if needed)
- [ ] T029 [US2] Standardize coverage chips set on coverage-related surfaces in app/Filament/Pages/InventoryCoverage.php (Restorable, Partial, Risk, Dependencies only)
**Checkpoint**: Inventory hub behaves as a module with consistent sub-navigation + header.
---
## Phase 5: User Story 3 — Operations index “Orders-style” (Priority: P3)
**Goal**: Operations index shows KPIs + status tabs + table, with calm conditional polling.
**Independent Test**: Visiting Operations index shows KPIs and tabs that filter runs per specs/058-tenant-ui-polish/contracts/ui.md, DB-only, calm.
### Tests (US3)
- [ ] T030 [P] [US3] Extend Operations DB-only test assertions in tests/Feature/Monitoring/OperationsDbOnlyTest.php (assert tabs/KPI labels appear)
- [ ] T031 [P] [US3] Extend Operations tenant isolation coverage in tests/Feature/Monitoring/OperationsTenantScopeTest.php (assert tab views dont leak)
### Implementation (US3)
- [ ] T032 [P] [US3] Create Operations KPI header widget in app/Filament/Widgets/Operations/OperationsKpiHeader.php
- [ ] T033 [US3] Add KPIs to the Operations list page in app/Filament/Resources/OperationRunResource/Pages/ListOperationRuns.php
- [ ] T034 [US3] Implement status tabs (All/Active/Succeeded/Partial/Failed) on Operations list page in app/Filament/Resources/OperationRunResource/Pages/ListOperationRuns.php
- [ ] T035 [US3] Ensure tab filter logic matches specs/058-tenant-ui-polish/contracts/ui.md by adjusting queries in app/Filament/Resources/OperationRunResource/Pages/ListOperationRuns.php
- [ ] T036 [US3] Implement conditional polling for Operations list (only while active runs exist) by wiring table polling in app/Filament/Resources/OperationRunResource.php and/or app/Filament/Resources/OperationRunResource/Pages/ListOperationRuns.php
- [ ] T037 [US3] Ensure canonical “View run” links still route to OperationRunResource view pages (no legacy routes)
- Verify existing canonical link helper `App\Support\OperationRunLinks` is used consistently.
- If no suitable helper exists for a given surface, add a minimal equivalent and use it everywhere.
- [ ] T042 [US3] Operations terminology sweep (FR-010)
- Goal: The UI uses the canonical label “Operations” consistently; no legacy naming remains.
- Audit + fix in:
- Navigation label(s)
- Page titles / breadcrumbs
- Resource titles / headings
- Any links mentioning “Bulk Operation Runs” or legacy run naming
- DoD:
- No occurrences of legacy labels remain in UI surfaces for Monitoring/Operations.
- `rg -n "Bulk Operation|BulkOperationRun|Bulk Operation Runs" app resources` returns 0 matches (or matches only in tests explicitly allowed).
- If ripgrep is unavailable, use `grep -R` with the same patterns.
**Checkpoint**: Operations index is “orders-style” with calm refresh behavior.
---
## Phase 6: Polish & Cross-Cutting Concerns
- [ ] T038 [P] Run formatting on changed files in app/** and tests/** via `vendor/bin/sail bin pint --dirty`
- [ ] T039 Run targeted tests from specs/058-tenant-ui-polish/quickstart.md and ensure green
- [ ] T040 [P] Smoke-check key pages render for a tenant in tests/Feature/Filament/AdminSmokeTest.php (add assertions only if gaps are found)
---
## Dependencies & Execution Order
### User Story Dependencies
- US1 (P1) is standalone and can ship first.
- US2 (P2) can be implemented after foundational polling helper; it touches the panel provider and inventory pages/resources.
- US3 (P3) can be implemented after foundational polling helper; it touches the operations resource list page.
Suggested order (MVP-first): Phase 1 → Phase 2 → US1 → US2 → US3 → Polish.
### Parallel Opportunities (examples)
- US1: T009T012 can be developed in parallel (different widget files).
- US2: T022 can be developed in parallel while T018T021 are in review.
- US3: T032 can be developed in parallel with test updates (T030T031).
---
## Parallel Execution Examples (per user story)
### US1
- T005 [P] [US1] tests/Feature/Filament/TenantDashboardDbOnlyTest.php
- T006 [P] [US1] tests/Feature/Filament/TenantDashboardTenantScopeTest.php
- T009 [P] [US1] app/Filament/Widgets/Dashboard/DashboardKpis.php
- T010 [P] [US1] app/Filament/Widgets/Dashboard/NeedsAttention.php
- T011 [P] [US1] app/Filament/Widgets/Dashboard/RecentDriftFindings.php
- T012 [P] [US1] app/Filament/Widgets/Dashboard/RecentOperations.php
### US2
- T016 [P] [US2] tests/Feature/Filament/InventoryHubDbOnlyTest.php
- T022 [P] [US2] app/Filament/Widgets/Inventory/InventoryKpiHeader.php
### US3
- T030 [P] [US3] tests/Feature/Monitoring/OperationsDbOnlyTest.php
- T031 [P] [US3] tests/Feature/Monitoring/OperationsTenantScopeTest.php
- T032 [P] [US3] app/Filament/Widgets/Operations/OperationsKpiHeader.php
---
## Implementation Strategy
### MVP First (US1 only)
1. Complete Phase 1 + Phase 2
2. Implement US1 (dashboard)
3. Run: `vendor/bin/sail artisan test tests/Feature/Filament/TenantDashboardDbOnlyTest.php`
4. Run: `vendor/bin/sail artisan test tests/Feature/Filament/TenantDashboardTenantScopeTest.php`
### Incremental Delivery
- Add US2 next (Inventory hub), then US3 (Operations index).
- After each story, run its targeted tests + the cross-cutting DB-only tests.