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