# Research: Filter UX Standardization ## Decision 1: Use resource-local native Filament filters as the default implementation path - Decision: Implement the standard directly in each resource’s existing `table()` definition using native Filament APIs such as `SelectFilter`, `TrashedFilter`, `Filter::make()`, `DatePicker`, `persistFiltersInSession()`, `persistSearchInSession()`, `persistSortInSession()`, and `indicateUsing()`. - Rationale: The repo already uses these APIs successfully in `OperationRunResource`, `FindingResource`, `PolicyResource`, `TenantResource`, and other resource tables. Keeping filter behavior explicit in each resource matches the convention-first goal and avoids creating a parallel filter configuration system. - Alternatives considered: - Build a generic filter DSL or plugin-backed filter framework: rejected because the spec explicitly forbids heavy abstraction and the audit concluded the work is fully achievable with Filament-native APIs. - Centralize all filter definitions into traits or macros first: rejected because the behavior varies by domain and the review burden would increase immediately. ## Decision 2: Treat Tier 1 and Tier 2 persistence as mandatory and enforce it with the existing guard suite, then add only justified follow-up surfaces - Decision: Expand the persistence expectation from the current seven-resource enforcement set to all Tier 1 and Tier 2 filtered resource lists. - Rationale: The audit identified the main consistency gap as missing state persistence on `InventoryItemResource`, `PolicyVersionResource`, `RestoreRunResource`, `AlertDeliveryResource`, and `EntraGroupResource`. The repo already has both native persistence methods and a guard test structure in `tests/Feature/Guards/FilamentTableStandardsGuardTest.php`, so the smallest reliable move is to extend that existing enforcement. After rollout, operator review exposed the same obvious gap on `BaselineSnapshotResource` and `BackupItemsRelationManager`, which are both low-risk enough to include as explicit follow-ups. - Alternatives considered: - Leave persistence as a review convention only: rejected because the current drift happened under partial convention and weak enforcement. - Add persistence to every relation manager, picker, widget, and system page in the same pass: rejected because the strongest operator pain was on resource lists and immediate broadening would expand scope unnecessarily. - Keep `BaselineSnapshotResource` and `BackupItemsRelationManager` out of scope even after operator feedback: rejected because both surfaces surfaced during real workflows, the filter gaps were obvious, and the implementation remained low-risk and Filament-native. ## Decision 3: Keep the soft-delete pattern exactly as the existing archive standard - Decision: Standardize soft-delete visibility with the current archive pattern: `TrashedFilter::make()->label('Archived')->placeholder('Active')->trueLabel('All')->falseLabel('Archived')`. - Rationale: The audit found this pattern already consistent where used, and the product standard file documents it as canonical. Expanding the existing pattern is lower risk than inventing a new wording or behavior. - Alternatives considered: - Rename the filter to “Deleted” or “Trashed”: rejected because the current enterprise-facing copy is already standardized as “Archived.” - Replace soft-delete visibility with a custom boolean or ternary filter: rejected because Filament’s built-in `TrashedFilter` already expresses the desired semantics cleanly. ## Decision 4: Use centralized vocabularies for status filters, even if the implementation path differs by domain - Decision: Prioritized status filters should derive from central domain sources rather than local arrays. For `BaselineProfileResource`, an enum already exists through `BaselineProfileStatus`. For `FindingResource` and `AlertDeliveryResource`, the repo already has centralized badge/domain mappings in `BadgeCatalog` and domain badge classes even though the list filters still use local arrays today. - Rationale: The audit flags `FindingResource` and `AlertDeliveryResource` as the main inconsistency examples. The repo already centralizes labels and semantics for these statuses in `BadgeCatalog`, `Domains\FindingStatusBadge`, and `Domains\AlertDeliveryStatusBadge`, which means the implementation can align filter options to those existing vocabularies with a thin option-source helper if needed rather than introducing a broad enum migration. - Alternatives considered: - Leave the existing manual arrays in place permanently: rejected because that preserves the exact drift the spec is meant to correct. - Force immediate full enum migrations for findings and alert deliveries: rejected because it may expand scope beyond what is necessary when centralized badge/domain mappings already exist. ## Decision 5: Reuse the existing OperationRun date-range pattern as the reference implementation - Decision: Use the current `OperationRunResource` date-range filter shape as the reference pattern for new date-range filters on `FindingResource`, `AlertDeliveryResource`, `RestoreRunResource`, and `PolicyVersionResource`. - Rationale: `OperationRunResource` already demonstrates a native Filament `Filter::make()` + `DatePicker` + `query()` + `indicateUsing()` pattern that matches the product standard. Reusing that shape minimizes design risk and keeps the UX consistent. - Alternatives considered: - Use custom query-string-only date narrowing: rejected because it would be less discoverable and outside the Filament-native standard. - Add separate “From” and “Until” filters as independent controls: rejected because the repo standard already prefers a single date-range filter with indicators. ## Decision 6: Use a thin shared helper for already-proven repeated mechanics - Decision: Introduce a very small helper layer under `app/Support/Filament/` for centralized option sourcing plus archive and date-range presets. - Rationale: The repeated mechanics are already proven in the target rollout set: the archived filter pattern is standardized, the date-range pattern already exists in `OperationRunResource`, and centralized status labels already exist in shared badge/catalog infrastructure. A tiny helper improves consistency without hiding domain-specific filter intent. - Alternatives considered: - Keep every resource fully duplicated with no helper: rejected because the same archived/date-range/option-source mechanics would be repeated across multiple Tier 1–2 surfaces with no added clarity. - Build a broader filter abstraction layer: rejected because it would violate the spec’s convention-first and no-framework constraints. ## Decision 7: Extend existing feature tests instead of inventing a new test architecture - Decision: Build on the current guard and feature tests, especially `FilamentTableStandardsGuardTest`, `TableStatePersistenceTest`, findings list tests, alert delivery tests, and existing resource-specific table tests. - Rationale: The repo already uses Pest and Livewire effectively for list-table behavior. The smallest robust move is to extend the current suites with filter-specific assertions rather than create a new test layer. - Alternatives considered: - Add broad browser-test coverage first: rejected because this feature is mainly about server-driven list behavior that is already well-covered by Pest and Livewire component tests. - Rely only on guard tests: rejected because functional tests are still needed to prove filters apply, clear, compose, and remain scope-safe. ## Decision 8: Preserve existing tenancy and canonical workspace-view semantics while adding filters - Decision: Treat workspace-scoped monitoring lists and tenant-scoped inventory/governance lists as existing scope boundaries that filters must respect, not reinterpret. - Rationale: The constitution is explicit that tenantless canonical views under `/admin` still require entitlement-safe behavior. The audit also calls out query considerations on `AlertDeliveryResource` and other workspace-scoped lists. This means filter additions must compose with current scoping queries rather than introduce new cross-tenant selectors casually. - Alternatives considered: - Add generic tenant filters to all workspace-context lists: rejected because some canonical views intentionally scope by current entitlement rather than explicit tenant selection. - Skip workspace-scoped lists from the standard: rejected because `AlertDeliveryResource`, `ProviderConnectionResource`, `TenantResource`, and `OperationRunResource` are central to the consistency gap. ## Decision 9: Ship without rollout exceptions and fix discovered scope issues directly - Decision: Do not approve transitional query-risk or centralized-status exceptions for this rollout. Fix any surfaced scope issue in-place before calling the standard complete. - Rationale: The implementation surfaced a tenant-isolation gap on `RestoreRunResource`, and the correct response was to make the base query tenant-scoped rather than document a temporary exception. Likewise, centralized option sources already existed strongly enough to move `FindingResource` and `AlertDeliveryResource` onto the shared helper without deferring that alignment. - Alternatives considered: - Approve a temporary scope exception for `RestoreRunResource`: rejected because the gap affected a core boundary and had a straightforward corrective path. - Leave findings or alert deliveries on local status arrays temporarily: rejected because it would preserve the exact drift this rollout is meant to remove.