# Implementation Plan: Filter UX Standardization

**Branch**: `126-filter-ux-standardization` | **Date**: 2026-03-09 | **Spec**: [spec.md](./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. After the main rollout, the plan also allows two low-risk follow-ups where operator review exposed the same gap: `BaselineSnapshotResource` and `BackupItemsRelationManager`.

## 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 2 approved low-effort follow-up surfaces 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`, and `EntraGroupResource` remain compliant if searched; `OperationRunResource` remains 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:assets` where 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)

```text
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)

```text
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 `RestoreRunResource` needing an explicit tenant-scoped base query to preserve intended isolation.
- No transitional status-source exceptions remain for `FindingResource` or `AlertDeliveryResource`; both now use the thin shared option catalog.

## Phase 0 — Research (output: `research.md`)

See: [research.md](./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:
- [data-model.md](./data-model.md)
- [contracts/filament-filter-state.openapi.yaml](./contracts/filament-filter-state.openapi.yaml)
- [quickstart.md](./quickstart.md)

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. `BackupItemsRelationManager` is the explicit justified exception because the gap surfaced during real backup review, and `BaselineSnapshotResource` is the matching workspace-scoped follow-up.
- 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()`, and `persistSortInSession()` to the missing Tier 1 and Tier 2 filtered resource lists.
- Expand `FilamentTableStandardsGuardTest` so 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`, and `BaselineProfileResource`.
- Reuse the existing `OperationRunResource` date-range pattern as the native model where feasible.

### Consistency alignment
- Replace prioritized manual status arrays with centralized option sources, starting with `FindingResource` and `AlertDeliveryResource`.
- 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.
- Perform targeted follow-up QA on `BaselineSnapshotResource` and `BackupItemsRelationManager` once the core Tier 1–2 rollout is stable.

## 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.
