# Feature Specification: Filter UX Standardization

**Feature Branch**: `126-filter-ux-standardization`  
**Created**: 2026-03-09  
**Status**: Proposed  
**Input**: User description: "Spec 126 — Filter UX Standardization"

## Spec Scope Fields *(mandatory)*

- **Scope**: workspace
- **Primary Routes**: Tier 1 and Tier 2 Filament resource list pages under `/admin`, `/admin/t/{tenant}/...`, selected low-effort Tier 3 list pages where the current filter gap is obvious and low risk, and the high-value backup-set item relation table when operator review exposes the same gap during backup inspection
- **Data Ownership**: Both workspace-owned and tenant-owned records are affected at the list-behavior layer only; this feature does not create new business entities or change underlying ownership
- **RBAC**: Existing workspace membership, tenant membership, plane separation, and capability gates remain authoritative; this feature standardizes list filtering behavior only within already-authorized surfaces

## User Scenarios & Testing *(mandatory)*

### User Story 1 - Keep Investigation Context (Priority: P1)

As an operator moving across key TenantPilot lists, I want important lists to remember my filter, search, and sort choices so I can continue an investigation without repeatedly rebuilding the same narrowed view.

**Why this priority**: Losing list context is the most direct source of friction identified by the audit and affects core operator workflows across inventory, monitoring, backups, and governance.

**Independent Test**: This can be tested independently by updating one Tier 1 or Tier 2 list to preserve its narrowed state across refresh and navigation, then verifying the same filtered result set returns within the same session.

**Acceptance Scenarios**:

1. **Given** a user narrows a Tier 1 or Tier 2 list with search, sort, and filters, **When** the user refreshes the page, **Then** the same narrowed view remains active.
2. **Given** a user narrows a Tier 1 or Tier 2 list and navigates away, **When** the user returns to that list within the same session, **Then** the previous narrowing remains in place.

---

### User Story 2 - Filter Similar Lists the Same Way (Priority: P2)

As an operator comparing similar record types, I want archived, status, and time-based filters to behave consistently across lists so I do not need to relearn filter semantics on each screen.

**Why this priority**: Cross-product inconsistency weakens trust in the UI even when filtering is technically present. Consistent semantics produce immediate value on every affected list.

**Independent Test**: This can be tested independently by standardizing one soft-deletable list, one status-driven list, and one time-based list, then verifying that archived visibility, status labels, and date-range behavior match the shared standard.

**Acceptance Scenarios**:

1. **Given** two comparable list pages that expose archived records, **When** the user opens their archive visibility filter, **Then** both pages present the same Active, All, and Archived semantics.
2. **Given** a time-based list with records across multiple dates, **When** the user narrows the list to a date window, **Then** the list shows only records in that window and clearly displays the active date constraint.
3. **Given** a status-driven list backed by a stable domain vocabulary, **When** the user opens the status filter, **Then** the available choices match the shared domain labels rather than list-local wording.

---

### User Story 3 - Prevent Filter Drift (Priority: P3)

As a platform maintainer, I want automated guard coverage around the agreed filter standard so future list changes do not quietly reintroduce inconsistency.

**Why this priority**: The audit shows the platform drifted because conventions were only partially enforced. Lightweight automated guards keep the standard durable without introducing a filter framework.

**Independent Test**: This can be tested independently by extending guard coverage so it fails when a required list drops persistence, omits a required archive filter, or uses a non-standard status source where the standard requires a centralized source.

**Acceptance Scenarios**:

1. **Given** an in-scope list drops required list-state persistence, **When** the guard suite runs, **Then** the suite fails with an actionable message.
2. **Given** a soft-deletable in-scope list omits the standard archive visibility behavior, **When** the guard suite runs, **Then** the suite fails before drift reaches production.

### Edge Cases

- When a list has too few records for additional filtering to provide user value, the standard allows that list to remain lightly filtered rather than forcing unnecessary controls.
- When a resource has no centralized enum or catalog yet, the rollout may retain a stable temporary option source only if replacing it would materially expand the current scope.
- When a time-based list already applies a default time window, the default must remain obvious and reversible so users do not mistake hidden records for missing data.
- When a resource uses relation-heavy or computed filters, the added filters must not weaken workspace or tenant scoping or impose disproportionate query cost.
- When a workspace-scoped monitoring list can contain tenant-bound and tenantless rows, new filters must not reveal cross-tenant values outside the user’s existing entitlement boundaries.

### Approved Exceptions

- No query-risk exceptions were approved for this rollout. `RestoreRunResource` required an explicit tenant-scoped base query during implementation so the standard could ship without weakening isolation.
- No transitional centralized status-source exceptions were approved. `FindingResource` and `AlertDeliveryResource` both moved to the thin shared option catalog during the rollout.

## Requirements *(mandatory)*

**Constitution alignment (required):** This feature does not introduce Microsoft Graph calls, new write behavior, queue or schedule behavior, or new operational workflows. It standardizes list filtering behavior on existing surfaces only.

**Constitution alignment (RBAC-UX):** This feature touches both tenant/admin and workspace-scoped list surfaces, but it does not change authorization semantics. Non-membership remains deny-as-not-found, capability checks remain server-side on existing actions, and filter additions must not widen result visibility across workspace or tenant boundaries. The test plan must include positive and negative scope coverage on representative tenant-scoped and workspace-scoped lists.

**Constitution alignment (BADGE-001):** This feature may align status and outcome filter option sources with centralized enums or catalogs, but it does not create new badge vocabularies. If a centralized status source is introduced or expanded as part of rollout, related display semantics must remain centralized and covered by regression tests.

**Constitution alignment (Filament Action Surfaces):** The Action Surface Contract remains satisfied. This feature modifies list filtering behavior only; existing list header actions, row actions, bulk actions, inspection affordances, empty-state CTAs, and detail-page actions remain resource-local and keep their current authorization and audit behavior.

**Constitution alignment (UX-001 — Layout & Information Architecture):** This feature directly strengthens the table portion of UX-001 by standardizing when important lists preserve state and expose core filters. It does not redesign create, edit, or view layouts, and it does not alter existing empty-state structure beyond preserving current list compliance.

### Functional Requirements

- **FR-001**: The system MUST define a single repo-wide filter UX standard for in-scope Filament resource lists.
- **FR-002**: The standard MUST apply mandatory list-state persistence to all Tier 1 and Tier 2 resource lists that expose filters.
- **FR-003**: Tier 1 resources for this standard are `PolicyResource`, `FindingResource`, `OperationRunResource`, `TenantResource`, and `InventoryItemResource`.
- **FR-004**: Tier 2 resources for this standard are `BackupScheduleResource`, `BackupSetResource`, `RestoreRunResource`, `PolicyVersionResource`, `ProviderConnectionResource`, `AlertDeliveryResource`, and `EntraGroupResource`.
- **FR-005**: Tier 3 resources and low-effort relation-manager tables may receive high-value improvements when the UX gap is obvious, but that follow-up work MUST NOT delay Tier 1 or Tier 2 rollout.
- **FR-006**: Every in-scope soft-deletable resource list MUST expose a consistent archive visibility filter using the shared Active, All, and Archived semantics.
- **FR-007**: Archived visibility wording MUST use “Archived” rather than resource-local alternatives such as “Trashed” or “Deleted.”
- **FR-008**: Status and outcome filters on prioritized resources MUST source their options from a centralized enum or domain catalog when such a source exists.
- **FR-009**: `FindingResource` and `AlertDeliveryResource` status filtering MUST be aligned to centralized option sources as part of the standardization pass unless a documented transitional exception is required.
- **FR-010**: Time-based Tier 1 and Tier 2 lists MUST expose a native date-range narrowing pattern when time is a primary investigation dimension.
- **FR-011**: At minimum, `FindingResource`, `AlertDeliveryResource`, `RestoreRunResource`, and `PolicyVersionResource` MUST gain date-range filtering aligned to their primary time field.
- **FR-012**: Every newly added date-range filter MUST display active filter indicators so users can immediately see the applied date window.
- **FR-013**: The rollout MUST close the highest-value missing essential filters on `RestoreRunResource`, `PolicyVersionResource`, `InventoryItemResource`, and `BaselineProfileResource`.
- **FR-014**: Essential filter additions MUST prioritize domain-appropriate dimensions such as status, outcome, policy type, platform, sync freshness, and primary time windows rather than speculative filter expansion.
- **FR-015**: Smart defaults may be used to reduce noise on lists dominated by inactive, archived, ignored, open, or recently changed records, but every default MUST be obvious and easy to clear.
- **FR-016**: The standard MUST preserve existing workspace and tenant scoping behavior, including on monitoring surfaces that operate without an active tenant in the URL.
- **FR-017**: The standard MUST avoid introducing a new filter framework, plugin dependency, or custom grouped-filter UI layer.
- **FR-018**: Any shared support extracted during implementation MUST remain thin, explicit, and limited to repeated mechanical presets.
- **FR-019**: Guard coverage MUST be extended so required Tier 1 and Tier 2 persistence cannot regress silently.
- **FR-020**: Guard coverage MUST detect missing standardized archive visibility on in-scope soft-deletable resource lists.
- **FR-021**: Guard coverage MUST detect prioritized status filters that drift away from their required centralized option source.
- **FR-022**: Functional tests for changed surfaces MUST cover filter application, filter clearing, multi-filter composition, and representative scope safety.
- **FR-023**: The rollout MUST be phaseable, with persistence and guard coverage delivered before lower-priority polish.

## Rollout Priorities

### Phase 1 - Persistence and Guard Coverage

- Close the highest-value persistence gaps on `InventoryItemResource`, `PolicyVersionResource`, `RestoreRunResource`, `AlertDeliveryResource`, and `EntraGroupResource`.
- Extend table guard coverage so Tier 1 and Tier 2 persistence expectations are enforced consistently.

### Phase 2 - Essential Filters

- Add missing status, outcome, platform, policy type, sync freshness, and date-range filters on the highest-value Tier 1 and Tier 2 lists.
- Preserve existing query scopes and tenancy boundaries while doing so.

### Phase 3 - Consistency and Polish

- Align prioritized status sources to centralized enums or catalogs.
- Normalize labels on recurring dimensions such as status, outcome, archived visibility, platform, and policy type.
- Use a thin shared helper for repeated mechanical patterns such as centralized option sourcing plus archived and date-range presets.

### Phase 4 - Optional Low-Priority Follow-ups

- Consider low-effort Tier 3 additions and relation-manager follow-ups only after Tier 1 and Tier 2 consistency is stable.
- `BaselineSnapshotResource` and `BackupItemsRelationManager` are approved examples of these follow-ups because operator review exposed immediate filtering friction on active governance and backup-inspection workflows.

## UI Action Matrix *(mandatory when Filament is changed)*

If this feature adds/modifies any Filament Resource / RelationManager / Page, fill out the matrix below.

For each surface, list the exact action labels, whether they are destructive (confirmation? typed confirmation?),
RBAC gating (capability + enforcement helper), and whether the mutation writes an audit log.

| Surface | Location | Header Actions | Inspect Affordance (List/Table) | Row Actions (max 2 visible) | Bulk Actions (grouped) | Empty-State CTA(s) | View Header Actions | Create/Edit Save+Cancel | Audit log? | Notes / Exemptions |
|---|---|---|---|---|---|---|---|---|---|---|
| Finding list | `app/Filament/Resources/FindingResource.php` + `app/Filament/Resources/FindingResource/Pages/ListFindings.php` | Existing finding header actions retained | Existing record inspection affordance retained | Existing workflow row actions retained | Existing grouped bulk actions retained | Existing empty-state CTA structure retained | Existing `ViewFinding` header actions retained | Not changed by this spec | Unchanged | Filter-only change; existing workflow actions, audit behavior, and authorization remain resource-local |
| Inventory item list | `app/Filament/Resources/InventoryItemResource.php` + `app/Filament/Resources/InventoryItemResource/Pages/ListInventoryItems.php` | Existing resource header actions retained | Existing record inspection affordance retained | Existing row actions retained | Existing grouped bulk actions retained where supported | Existing empty-state CTA structure retained | Existing `ViewInventoryItem` header actions retained | Not changed by this spec | Unchanged | Filter-only change |
| Policy version list | `app/Filament/Resources/PolicyVersionResource.php` + `app/Filament/Resources/PolicyVersionResource/Pages/ListPolicyVersions.php` | Existing policy-version header actions retained | Existing record inspection affordance retained | Existing row actions retained | Existing grouped bulk actions retained | Existing empty-state CTA structure retained | Existing `ViewPolicyVersion` header actions retained | Not changed by this spec | Unchanged | Filter-only change; existing destructive actions keep confirmation + authorization |
| Restore run list | `app/Filament/Resources/RestoreRunResource.php` + `app/Filament/Resources/RestoreRunResource/Pages/ListRestoreRuns.php` | Existing restore-run header actions retained | Existing record inspection affordance retained | Existing row actions retained | Existing grouped bulk actions retained | Existing empty-state CTA structure retained | Existing `ViewRestoreRun` header actions retained | Existing create flow retained | Unchanged | Filter-only change |
| Alert delivery list | `app/Filament/Resources/AlertDeliveryResource.php` + `app/Filament/Resources/AlertDeliveryResource/Pages/ListAlertDeliveries.php` | Existing monitoring header actions retained | Existing record inspection affordance retained | Existing row actions retained | Existing grouped bulk actions retained where supported | Existing empty-state CTA structure retained | Existing `ViewAlertDelivery` header actions retained | Not changed by this spec | Unchanged | Filter-only change; workspace-context entitlement behavior remains unchanged |
| Entra group list | `app/Filament/Resources/EntraGroupResource.php` + `app/Filament/Resources/EntraGroupResource/Pages/ListEntraGroups.php` | Existing resource header actions retained | Existing record inspection affordance retained | Existing row actions retained | Existing grouped bulk actions retained where supported | Existing empty-state CTA structure retained | Existing `ViewEntraGroup` header actions retained | Not changed by this spec | Unchanged | Filter-only change |
| Baseline profile list | `app/Filament/Resources/BaselineProfileResource.php` + `app/Filament/Resources/BaselineProfileResource/Pages/ListBaselineProfiles.php` | Existing baseline-profile header actions retained | Existing record inspection affordance retained | Existing row actions retained | Existing grouped bulk actions retained | Existing empty-state CTA structure retained | Existing `ViewBaselineProfile` header actions retained | Existing create/edit flows retained | Unchanged | Filter-only change |
| Baseline snapshot list | `app/Filament/Resources/BaselineSnapshotResource.php` + `app/Filament/Resources/BaselineSnapshotResource/Pages/ListBaselineSnapshots.php` | Existing list header remains empty because snapshots are capture outputs | Existing record inspection affordance retained | Existing `View` row action retained | No bulk actions by design | Existing empty-state CTA structure retained | Existing `ViewBaselineSnapshot` header remains informational | Not changed by this spec | Unchanged | Low-effort follow-up filter-only change on a workspace-scoped governance list |
| Backup set items relation table | `app/Filament/Resources/BackupSetResource/RelationManagers/BackupItemsRelationManager.php` | Existing `Refresh` and `Add Policies` header actions retained | Existing relation-table inspection affordance retained | Existing grouped row actions retained, including destructive remove with confirmation | Existing grouped bulk remove retained | Existing empty-state CTA retained | Uses parent backup-set view header; no new detail-header action | Not changed by this spec | Unchanged | Low-effort follow-up filter-only change on the backup inspection table; existing authorization and confirmation behavior remain unchanged |
| Operation run canonical table | `app/Filament/Resources/OperationRunResource.php` and workspace monitoring pages that reuse it | Existing canonical page actions retained | Existing record inspection affordance retained in monitoring surfaces | Existing row actions retained | Existing grouped bulk actions retained where supported | Existing empty-state CTA structure retained | No new view/create/edit change in this spec | Not applicable | Unchanged | Filter option-label alignment only; canonical workspace view remains DB-only and entitlement-safe |

### Key Entities *(include if feature involves data)*

- **Filter UX Tier**: The priority classification that decides whether a resource list requires mandatory persistence and essential filter coverage.
- **Filter Behavior Profile**: The combination of persistence, archive visibility, status sourcing, date-range support, defaults, labels, and active indicators expected on a given list.
- **Centralized Status Source**: The shared domain vocabulary used to keep status and outcome filter options consistent across comparable resources.
- **Filter Guard Rule**: The automated enforcement rule that protects the agreed filter standard from drifting over time.

## Assumptions

- The comprehensive filter audit remains an accurate baseline for the 36 current table surfaces, even if minor counts shift during implementation.
- Existing list actions, empty states, and inspection affordances remain correct unless a separate spec changes them.
- Time-based resources already expose sufficient timestamp data to support user-visible date narrowing without introducing new data structures.
- Optional Tier 3 work is limited to clearly beneficial, low-risk additions such as simple status or state filters.

## Dependencies

- The feature depends on the current audit inventory of filter coverage and persistence gaps remaining available as the planning baseline.
- The feature depends on existing centralized enums and catalogs where they already exist for status or outcome vocabularies.
- The feature depends on current workspace and tenant scoping rules remaining intact across affected list queries.
- The feature depends on existing table guard infrastructure being extended rather than replaced.

## Success Criteria *(mandatory)*

### Measurable Outcomes

- **SC-001**: In the post-rollout audit, 100% of Tier 1 and Tier 2 resource lists that expose filters preserve filter, search, and sort state within the same session.
- **SC-002**: In the post-rollout audit, 100% of in-scope soft-deletable resource lists present the same Archived visibility semantics.
- **SC-003**: `FindingResource`, `AlertDeliveryResource`, `RestoreRunResource`, and `PolicyVersionResource` all expose user-visible date-range narrowing with active indicators.
- **SC-004**: The prioritized lists identified for this feature expose the required essential filters without introducing documented workspace or tenant scope regressions.
- **SC-005**: Automated guard coverage fails whenever an in-scope list drops required persistence, archive visibility, or mandated centralized status sourcing.
- **SC-006**: In manual QA, users can refresh or revisit an in-scope important list without reapplying their previously chosen narrowing during the same session.
