13 KiB
Implementation Plan: Filter UX Standardization
Branch: 126-filter-ux-standardization | Date: 2026-03-09 | Spec: spec.md
Input: Feature specification from /specs/126-filter-ux-standardization/spec.md
Note: This plan is generated by the /speckit.plan workflow and aligned to the current repository constitution.
Summary
Standardize filter UX across TenantPilot’s important Filament list surfaces using native Filament filters, native session persistence, and light regression guards. The implementation will add the persistence trio to all Tier 1 and Tier 2 filtered resource lists, expand consistent TrashedFilter usage, add missing date-range and essential domain filters on the highest-value surfaces, align prioritized status filters to centralized vocabularies, and extend existing Pest guard and Livewire coverage without introducing a custom filter framework. A thin shared helper layer is intentionally selected for repeated option-source, archived-filter, and date-range mechanics because those repetitions are already proven across the target surfaces.
Technical Context
Language/Version: PHP 8.4.15
Primary Dependencies: Laravel 12, Filament v5, Livewire v4.0+, Tailwind CSS v4, Pest v4, existing BadgeCatalog / BadgeRenderer, existing TagBadgeCatalog / TagBadgeRenderer, existing Filament resource tables
Storage: PostgreSQL remains unchanged; session persistence uses Filament-native session keys and existing workspace/tenant context
Testing: Pest v4 feature tests, Livewire component tests for Filament list pages, existing guard tests in tests/Feature/Guards, and focused manual QA for persistence and date-range indicators
Target Platform: Laravel Sail web application with Filament admin and system panels under /admin, /admin/t/{tenant}/..., and /system
Project Type: Laravel monolith / Filament web application
Performance Goals: No material query regression on existing list pages; new date-range and status filters must compose with existing scopes without introducing obvious N+1 or high-cost relation queries; list-state persistence remains native and server-driven
Constraints: Filament-native first, no plugin dependency, no heavy helper framework, no grouped custom filter UI, no authorization behavior changes, no schema changes, no new asset pipeline work, and any extracted helper must remain thin and mechanically scoped
Scale/Scope: 12 Tier 1–2 resource lists define the core standard; 5 immediate persistence-gap resources, 6 immediate essential-filter targets, 2 prioritized status-source alignments, 1 existing table guard suite to expand, and optional low-effort Tier 3 cleanup only after core consistency is stable
Constitution Check
GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.
| Gate | Pre-Research | Post-Design | Notes |
|---|---|---|---|
| Inventory-first / snapshots-second | PASS | PASS | The feature changes only list filtering behavior on existing inventory, backup, governance, directory, and monitoring surfaces; no snapshot or inventory semantics change. |
| Read/write separation | PASS | PASS | No new write workflows or operation starts are introduced. Existing mutations remain unchanged and outside this feature’s scope. |
| Graph contract path | N/A | N/A | No Microsoft Graph calls, GraphClientInterface usage, or contract registry changes are involved. |
| Deterministic capabilities | PASS | PASS | No new capabilities, role checks, or permission derivation logic are introduced. Existing capability registries remain authoritative. |
| Workspace + tenant isolation | PASS | PASS | The design explicitly keeps existing workspace and tenant query scopes intact while adding filters and persistence. |
| RBAC-UX authorization semantics | PASS | PASS | Non-member 404 and member-without-capability 403 semantics remain unchanged; filter additions must not widen visibility. |
| Destructive confirmation | PASS / N/A | PASS / N/A | No destructive actions are added or modified. Existing destructive actions remain subject to ->requiresConfirmation(). |
| Global search tenant safety | PASS | PASS | This feature does not expand global search. In-scope resources that are globally searchable already have View pages; resources not intended for global search remain unchanged. |
| Tenant isolation | PASS | PASS | Tenant-owned list queries remain tenant-scoped, and workspace-scoped canonical monitoring screens remain entitlement-safe. |
| Run observability / Ops-UX | N/A | N/A | No new long-running, remote, or queued work is introduced. |
| Data minimization and safe logging | PASS | PASS | No new persistence model or logging payloads are added. |
| BADGE-001 centralized semantics | PASS | PASS | Status and outcome vocabularies are aligned through existing central badge/catalog infrastructure or thin option-source additions, not ad-hoc local arrays. |
| Filament Action Surface Contract | PASS | PASS | The feature changes list filters only. Existing header, row, bulk, empty-state, and detail actions remain resource-local. |
| UX-001 table obligations | PASS | PASS | The feature directly strengthens the table-filter portion of UX-001 by standardizing important list filtering and persistence. |
| Filament v5 / Livewire v4 compliance | PASS | PASS | All work remains within Filament v5 and Livewire v4-native APIs. |
| Panel provider registration | PASS | PASS | No panel-provider changes are needed; Laravel 11+ provider registration remains in bootstrap/providers.php. |
| Asset strategy | PASS | PASS | No new global or on-demand assets are required. Existing deploy-time php artisan filament:assets expectations remain unchanged. |
Implementation Notes
- Livewire v4.0+ compliance: All planned filters, session persistence, and list tests use existing Filament v5 and Livewire v4 patterns already present in the repo.
- Provider registration location: Unchanged. Filament panel providers remain registered in
bootstrap/providers.php. - Global search rule: This feature does not make new resources globally searchable. In-scope resources with existing View pages such as
FindingResource,PolicyVersionResource,RestoreRunResource,ProviderConnectionResource,InventoryItemResource,AlertDeliveryResource, andEntraGroupResourceremain compliant if searched;OperationRunResourceremains a non-navigation canonical resource and is not being expanded for global search here. - Destructive actions: No new destructive actions are added. Existing destructive actions on changed resources must keep their current authorization and
->requiresConfirmation()handling. - Asset strategy: No new assets. Deployment continues to rely on the existing Filament asset process, including
php artisan filament:assetswhere already required. - Testing plan: Extend
FilamentTableStandardsGuardTest, add focused filter behavior and persistence coverage on changed list pages, and keep representative tenant/workspace scope regression checks.
Project Structure
Documentation (this feature)
specs/126-filter-ux-standardization/
├── plan.md
├── research.md
├── data-model.md
├── quickstart.md
├── contracts/
│ └── filament-filter-state.openapi.yaml
└── tasks.md
Source Code (repository root)
app/
├── Filament/
│ └── Resources/
│ ├── FindingResource.php
│ ├── InventoryItemResource.php
│ ├── OperationRunResource.php
│ ├── PolicyResource.php
│ ├── TenantResource.php
│ ├── BackupScheduleResource.php
│ ├── BackupSetResource.php
│ ├── RestoreRunResource.php
│ ├── PolicyVersionResource.php
│ ├── ProviderConnectionResource.php
│ ├── AlertDeliveryResource.php
│ ├── EntraGroupResource.php
│ ├── BaselineProfileResource.php
│ ├── AlertRuleResource.php
│ ├── AlertDestinationResource.php
│ └── Workspaces/WorkspaceResource.php
├── Models/
│ ├── Finding.php
│ ├── AlertDelivery.php
│ └── BaselineProfile.php
└── Support/
├── Badges/
│ ├── BadgeCatalog.php
│ ├── BadgeRenderer.php
│ ├── TagBadgeCatalog.php
│ └── Domains/
└── Baselines/
└── BaselineProfileStatus.php
tests/
├── Feature/
│ ├── Guards/
│ │ └── FilamentTableStandardsGuardTest.php
│ ├── Filament/
│ │ ├── TableStatePersistenceTest.php
│ │ ├── Findings/
│ │ ├── Alerts/
│ │ └── [resource-specific table tests]
│ └── Rbac/
└── Browser/
└── [not expected for first pass]
Structure Decision: Keep the work in the existing Laravel/Filament monolith and update resource-local table() definitions plus the current guard and feature test suites. The thin shared helper layer should live under app/Support/Filament/ and remain limited to repeated mechanical presets such as centralized option sourcing plus archive and date-range filters.
Complexity Tracking
| Violation | Why Needed | Simpler Alternative Rejected Because |
|---|---|---|
| None | Not applicable | The design stays within the constitution and the spec’s no-framework constraint |
Approved Exceptions
- None approved. The only implementation risk that surfaced was
RestoreRunResourceneeding an explicit tenant-scoped base query to preserve intended isolation. - No transitional status-source exceptions remain for
FindingResourceorAlertDeliveryResource; both now use the thin shared option catalog.
Phase 0 — Research (output: research.md)
See: research.md
Research goals:
- Confirm the native Filament patterns already used for persistence, date-range filters, and archive visibility.
- Confirm the current resource-level gaps on the Tier 1 and Tier 2 targets.
- Confirm the existing centralized badge and enum sources that can back status and outcome filter options.
- Confirm the current guard and functional tests that should be extended rather than replaced.
Phase 1 — Design & Contracts (outputs: data-model.md, contracts/, quickstart.md)
See:
Design focus:
- Model filter behavior as a contract over existing resource tables rather than as a new runtime subsystem.
- Keep the standard resource-local and Filament-native, with only thin preset extraction for repeated mechanical patterns.
- Make persistence mandatory on Tier 1 and Tier 2 resource lists while keeping relation managers, widgets, and pickers out of scope unless already justified.
- Treat centralized status vocabularies as existing domain assets that may need thin option helpers rather than broad enum migrations everywhere.
- Preserve query safety and tenancy/workspace boundaries while adding date-range and essential domain filters.
Phase 2 — Implementation Outline (tasks created in /speckit.tasks)
Persistence and guard foundation
- Add
persistFiltersInSession(),persistSearchInSession(), andpersistSortInSession()to the missing Tier 1 and Tier 2 filtered resource lists. - Expand
FilamentTableStandardsGuardTestso Tier 1 and Tier 2 persistence expectations are enforced consistently.
Essential filter rollout
- Add the missing date-range and essential domain filters on
RestoreRunResource,PolicyVersionResource,FindingResource,AlertDeliveryResource,InventoryItemResource, andBaselineProfileResource. - Reuse the existing
OperationRunResourcedate-range pattern as the native model where feasible.
Consistency alignment
- Replace prioritized manual status arrays with centralized option sources, starting with
FindingResourceandAlertDeliveryResource. - Keep archive visibility and recurring labels aligned with the documented filter standard.
- Introduce only the small preset/helper layer already justified by repeated archived-filter, date-range, and centralized-option patterns.
Regression protection
- Extend guard coverage for archive visibility and prioritized centralized status sourcing.
- Add focused Livewire and feature tests that prove filters apply, clear, compose, and remain scope-safe on representative surfaces.
Verification
- Run focused Pest suites for guards and changed resource table behavior.
- Run Pint on dirty files through Sail.
- Perform manual QA on refresh/navigation persistence, date-range indicators, and archive semantics on representative tenant and workspace lists.
Constitution Check (Post-Design)
Re-check result: PASS. The design introduces no new routes, storage, authorization rules, background work, or asset requirements. It strengthens UX consistency through existing Filament-native table APIs, preserves current RBAC and tenancy boundaries, and keeps the implementation bounded to list behavior plus regression guards.