diff --git a/.ai/guidelines/filament-v5-blueprint.md b/.ai/guidelines/filament-v5-blueprint.md
new file mode 100644
index 0000000..a3a558a
--- /dev/null
+++ b/.ai/guidelines/filament-v5-blueprint.md
@@ -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 (don’t “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 aren’t 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 Filament’s 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
\ No newline at end of file
diff --git a/.ai/guidelines/filament-v5-checklist.md b/.ai/guidelines/filament-v5-checklist.md
new file mode 100644
index 0000000..a19366f
--- /dev/null
+++ b/.ai/guidelines/filament-v5-checklist.md
@@ -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 there’s 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 aren’t 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 Filament’s 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”
\ No newline at end of file
diff --git a/.gemini/settings.json b/.gemini/settings.json
index 7f50738..dfcb3db 100644
--- a/.gemini/settings.json
+++ b/.gemini/settings.json
@@ -1,5 +1,14 @@
{
- "general": {
- "previewFeatures": false
- }
+ "general": {
+ "previewFeatures": false
+ },
+ "mcpServers": {
+ "laravel-boost": {
+ "command": "vendor/bin/sail",
+ "args": [
+ "artisan",
+ "boost:mcp"
+ ]
+ }
+ }
}
\ No newline at end of file
diff --git a/.github/agents/copilot-instructions.md b/.github/agents/copilot-instructions.md
index 310d6a9..f1ecfec 100644
--- a/.github/agents/copilot-instructions.md
+++ b/.github/agents/copilot-instructions.md
@@ -11,6 +11,7 @@ ## Active Technologies
- 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 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)
@@ -30,9 +31,9 @@ ## Code Style
PHP 8.4.15: Follow standard conventions
## 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
-- 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
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
new file mode 100644
index 0000000..b0091a0
--- /dev/null
+++ b/.github/copilot-instructions.md
@@ -0,0 +1,683 @@
+
+=== .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 (don’t “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 aren’t 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 Filament’s 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 there’s 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 aren’t 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 Filament’s 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()`.
+ - public function __construct(public GitHub $github) { }
+- 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.
+
+
+protected function isAccessible(User $user, ?string $path = null): bool
+{
+ ...
+}
+
+
+## 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)
+
+ {{ $item->name }}
+
+ @endforeach
+ ```
+
+- Prefer lifecycle hooks like `mount()`, `updatedFoo()` for initialization and reactive side effects:
+
+
+ public function mount(User $user) { $this->user = $user; }
+ public function updatedSearch() { $this->resetPage(); }
+
+
+
+## Testing Livewire
+
+
+ Livewire::test(Counter::class)
+ ->assertSet('count', 0)
+ ->call('increment')
+ ->assertSet('count', 1)
+ ->assertSee(1)
+ ->assertStatus(200);
+
+
+
+
+ $this->get('/posts/create')
+ ->assertSeeLivewire(CreatePost::class);
+
+
+
+=== 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:
+
+it('is true', function () {
+ expect(true)->toBeTrue();
+});
+
+
+### 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.:
+
+it('returns all', function () {
+ $response = $this->postJson('/api/docs', []);
+
+ $response->assertSuccessful();
+});
+
+
+### 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.
+
+
+it('has emails', function (string $email) {
+ expect($email)->not->toBeEmpty();
+})->with([
+ 'james' => 'james@laravel.com',
+ 'taylor' => 'taylor@laravel.com',
+]);
+
+
+
+=== 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
+
+
+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);
+});
+
+
+
+$pages = visit(['/', '/about', '/contact']);
+
+$pages->assertNoJavascriptErrors()->assertNoConsoleLogs();
+
+
+
+=== 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.
+
+
+
+
Superior
+
Michigan
+
Erie
+
+
+
+
+### 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.
+
+@theme {
+ --color-brand: oklch(0.72 0.11 178);
+}
+
+
+- In Tailwind v4, you import Tailwind using a regular CSS `@import` statement, not using the `@tailwind` directives used in v3:
+
+
+ - @tailwind base;
+ - @tailwind components;
+ - @tailwind utilities;
+ + @import "tailwindcss";
+
+
+
+### 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 |
+
diff --git a/Agents.md b/Agents.md
index 2993b9d..2f6e883 100644
--- a/Agents.md
+++ b/Agents.md
@@ -386,6 +386,266 @@ ## Reference Materials
===
+=== .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 (don’t “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 aren’t 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 Filament’s 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 there’s 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 aren’t 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 Filament’s 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
@@ -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.
- php - 8.4.15
-- filament/filament (FILAMENT) - v4
+- filament/filament (FILAMENT) - v5
- laravel/framework (LARAVEL) - v12
- laravel/prompts (PROMPTS) - v0
-- livewire/livewire (LIVEWIRE) - v3
+- livewire/livewire (LIVEWIRE) - v4
- laravel/mcp (MCP) - v0
- laravel/pint (PINT) - 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.
- Use descriptive names for variables and methods. For example, `isRegisteredForDiscounts`, not `discount()`.
- Check for existing components to reuse before writing a new one.
-- UI consistency: Prefer Filament components (``, infolist/table entries, etc.) over custom HTML/Tailwind for admin UI; only roll custom markup when Filament cannot express the UI.
## 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.
@@ -421,7 +680,7 @@ ## Application Structure & Architecture
- 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 `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
- 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`.
+=== 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 `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 ===
## 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.
-- If you're creating a generic PHP class, use `php artisan make:class`.
+- 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
@@ -523,7 +797,7 @@ ### Database
- 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 `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
- 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
- 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 `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
-- 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 ===
@@ -579,7 +853,7 @@ ### Models
## Livewire Core
- 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.
- 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
-=== 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:
-
-
-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);
- });
-});
-
-
-
=== pint/core rules ===
## Laravel Pint Code Formatter
-- You must run `vendor/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.
+- 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 ===
@@ -673,7 +911,7 @@ ### 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 `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.
- Tests should test all of the happy paths, failure paths, and weird paths.
- Tests live in the `tests/Feature` and `tests/Unit` directories.
@@ -686,9 +924,9 @@ ### Pest Tests
### Running Tests
- 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 in a file: `php 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 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
diff --git a/GEMINI.md b/GEMINI.md
index 220d86e..e1aafa1 100644
--- a/GEMINI.md
+++ b/GEMINI.md
@@ -226,6 +226,266 @@ ## Reference Materials
===
+=== .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 (don’t “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 aren’t 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 Filament’s 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 there’s 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 aren’t 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 Filament’s 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
@@ -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.
- php - 8.4.15
-- filament/filament (FILAMENT) - v4
+- filament/filament (FILAMENT) - v5
- laravel/framework (LARAVEL) - v12
- laravel/prompts (PROMPTS) - v0
-- livewire/livewire (LIVEWIRE) - v3
+- livewire/livewire (LIVEWIRE) - v4
- laravel/mcp (MCP) - v0
- laravel/pint (PINT) - v1
- laravel/sail (SAIL) - v1
@@ -260,7 +520,7 @@ ## Application Structure & Architecture
- 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 `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
- 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`.
+=== 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 `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 ===
## 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.
-- If you're creating a generic PHP class, use `php artisan make:class`.
+- 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
@@ -362,7 +637,7 @@ ### Database
- 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 `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
- 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
- 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 `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
-- 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 ===
@@ -418,7 +693,7 @@ ### Models
## Livewire Core
- 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.
- 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
-=== 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:
-
-
-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);
- });
-});
-
-
-
=== pint/core rules ===
## Laravel Pint Code Formatter
-- You must run `vendor/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.
+- 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 ===
@@ -512,7 +751,7 @@ ### 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 `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.
- Tests should test all of the happy paths, failure paths, and weird paths.
- Tests live in the `tests/Feature` and `tests/Unit` directories.
@@ -525,9 +764,9 @@ ### Pest Tests
### Running Tests
- 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 in a file: `php 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 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
diff --git a/boost.json b/boost.json
index e337384..0a16d2e 100644
--- a/boost.json
+++ b/boost.json
@@ -1,7 +1,16 @@
{
"agents": [
"codex",
+ "copilot",
+ "gemini",
"opencode"
],
- "guidelines": []
+ "editors": [
+ "codex",
+ "gemini",
+ "opencode",
+ "vscode"
+ ],
+ "guidelines": [],
+ "sail": true
}
diff --git a/docs/research/filament-v5-notes.md b/docs/research/filament-v5-notes.md
new file mode 100644
index 0000000..c7dc91a
--- /dev/null
+++ b/docs/research/filament-v5-notes.md
@@ -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: Don’t 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 didn’t auto-register. When NOT: Don’t 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 panel’s URL prefix; `path('')` mounts at `/` and can conflict with existing routes.
+ When: When `/admin` is not desired. When NOT: Don’t 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: Don’t 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: Don’t 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: Don’t 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: Don’t 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: Don’t 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: Don’t 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: Don’t 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: Don’t 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: Don’t 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: Don’t 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: Don’t 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: Don’t 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: Don’t 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 plugin’s service provider so Filament can manage publishing/loading.
+ When: When shipping CSS/JS/Alpine components with a package. When NOT: Don’t 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: Don’t 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: Don’t 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: Don’t 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: Don’t 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: Don’t 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: Don’t 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: Don’t 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: Don’t 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: Don’t 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: Don’t 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: Don’t assume modal/confirmation behavior for URL-only actions without verifying in docs.
+ Source: https://filamentphp.com/docs/5.x/actions/modals — “Confirmation modals”
\ No newline at end of file
diff --git a/opencode.json b/opencode.json
new file mode 100644
index 0000000..6f800ed
--- /dev/null
+++ b/opencode.json
@@ -0,0 +1,14 @@
+{
+ "$schema": "https://opencode.ai/config.json",
+ "mcp": {
+ "laravel-boost": {
+ "type": "local",
+ "enabled": true,
+ "command": [
+ "vendor/bin/sail",
+ "artisan",
+ "boost:mcp"
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/specs/057-filament-v5-upgrade/checklists/requirements.md b/specs/057-filament-v5-upgrade/checklists/requirements.md
index 3ceac5d..7cb996b 100644
--- a/specs/057-filament-v5-upgrade/checklists/requirements.md
+++ b/specs/057-filament-v5-upgrade/checklists/requirements.md
@@ -31,5 +31,5 @@ ## Feature Readiness
## Notes
-- This is a technical upgrade, but the spec intentionally describes outcomes and guardrails rather than implementation steps.
-- Proceed to planning: identify concrete packages, versions, and migration steps in plan/tasks.
+- This is a technical upgrade, but the spec describes outcomes and guardrails rather than implementation steps.
+- Planning can capture concrete versions, dependency changes, and migration steps.
diff --git a/specs/057-filament-v5-upgrade/spec.md b/specs/057-filament-v5-upgrade/spec.md
index 06208b0..4bc3e16 100644
--- a/specs/057-filament-v5-upgrade/spec.md
+++ b/specs/057-filament-v5-upgrade/spec.md
@@ -3,7 +3,7 @@ # Feature Specification: Admin UI Stack Upgrade (Panel + Suite)
**Feature Branch**: `057-filament-v5-upgrade`
**Created**: 2026-01-20
**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
@@ -19,7 +19,7 @@ ## User Scenarios & Testing *(mandatory)*
### 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.
@@ -75,18 +75,18 @@ ## Requirements *(mandatory)*
### 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-002**: The application MUST continue to support the existing styling system without asset 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-004**: SPA-style navigation flows MUST continue to work, including global widget mounting and event delivery across navigation.
-- **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-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 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 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-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 system MUST continue to support the existing styling and asset pipeline without build failures.
+- **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**: 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 requests (polling/auto-refresh/hydration).
+- **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-007**: Compatibility risks MUST be managed by producing an explicit inventory of affected third-party dependencies and documenting upgrade/replacement decisions.
- **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-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
@@ -97,7 +97,7 @@ ### Assumptions & Dependencies
### Key Entities *(include if feature involves data)*
- **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.
## Success Criteria *(mandatory)*
diff --git a/specs/058-tenant-ui-polish/checklists/requirements.md b/specs/058-tenant-ui-polish/checklists/requirements.md
new file mode 100644
index 0000000..e4bf746
--- /dev/null
+++ b/specs/058-tenant-ui-polish/checklists/requirements.md
@@ -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`
diff --git a/specs/058-tenant-ui-polish/contracts/polling.md b/specs/058-tenant-ui-polish/contracts/polling.md
new file mode 100644
index 0000000..30ee404
--- /dev/null
+++ b/specs/058-tenant-ui-polish/contracts/polling.md
@@ -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.
diff --git a/specs/058-tenant-ui-polish/contracts/ui.md b/specs/058-tenant-ui-polish/contracts/ui.md
new file mode 100644
index 0000000..b6b6256
--- /dev/null
+++ b/specs/058-tenant-ui-polish/contracts/ui.md
@@ -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)
diff --git a/specs/058-tenant-ui-polish/data-model.md b/specs/058-tenant-ui-polish/data-model.md
new file mode 100644
index 0000000..d328232
--- /dev/null
+++ b/specs/058-tenant-ui-polish/data-model.md
@@ -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.
diff --git a/specs/058-tenant-ui-polish/plan.md b/specs/058-tenant-ui-polish/plan.md
new file mode 100644
index 0000000..540c767
--- /dev/null
+++ b/specs/058-tenant-ui-polish/plan.md
@@ -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
+
+
+
+**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)
+
+
+```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.
diff --git a/specs/058-tenant-ui-polish/quickstart.md b/specs/058-tenant-ui-polish/quickstart.md
new file mode 100644
index 0000000..70a0ffb
--- /dev/null
+++ b/specs/058-tenant-ui-polish/quickstart.md
@@ -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 don’t show:
+- `vendor/bin/sail npm run dev`
+- or `vendor/bin/sail npm run build`
diff --git a/specs/058-tenant-ui-polish/research.md b/specs/058-tenant-ui-polish/research.md
new file mode 100644
index 0000000..b436fe7
--- /dev/null
+++ b/specs/058-tenant-ui-polish/research.md
@@ -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.
diff --git a/specs/058-tenant-ui-polish/spec.md b/specs/058-tenant-ui-polish/spec.md
new file mode 100644
index 0000000..eed7ac4
--- /dev/null
+++ b/specs/058-tenant-ui-polish/spec.md
@@ -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)*
+
+
+
+### 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.
+
+
+
+### 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)*
+
+
+
+### 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.
diff --git a/specs/058-tenant-ui-polish/tasks.md b/specs/058-tenant-ui-polish/tasks.md
new file mode 100644
index 0000000..c4ba39c
--- /dev/null
+++ b/specs/058-tenant-ui-polish/tasks.md
@@ -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 don’t 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: T009–T012 can be developed in parallel (different widget files).
+- US2: T022 can be developed in parallel while T018–T021 are in review.
+- US3: T032 can be developed in parallel with test updates (T030–T031).
+
+---
+
+## 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.