9.0 KiB
9.0 KiB
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 asSelectFilter,TrashedFilter,Filter::make(),DatePicker,persistFiltersInSession(),persistSearchInSession(),persistSortInSession(), andindicateUsing(). - 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
- 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, andEntraGroupResource. The repo already has both native persistence methods and a guard test structure intests/Feature/Guards/FilamentTableStandardsGuardTest.php, so the smallest reliable move is to extend that existing enforcement. - Alternatives considered:
- Leave persistence as a review convention only: rejected because the current drift happened under partial convention and weak enforcement.
- Add persistence to relation managers, pickers, widgets, and system pages in the same pass: rejected because the strongest operator pain is on resource lists and immediate broadening would expand scope unnecessarily.
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
TrashedFilteralready 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 throughBaselineProfileStatus. ForFindingResourceandAlertDeliveryResource, the repo already has centralized badge/domain mappings inBadgeCatalogand domain badge classes even though the list filters still use local arrays today. - Rationale: The audit flags
FindingResourceandAlertDeliveryResourceas the main inconsistency examples. The repo already centralizes labels and semantics for these statuses inBadgeCatalog,Domains\FindingStatusBadge, andDomains\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
OperationRunResourcedate-range filter shape as the reference pattern for new date-range filters onFindingResource,AlertDeliveryResource,RestoreRunResource, andPolicyVersionResource. - Rationale:
OperationRunResourcealready demonstrates a native FilamentFilter::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
/adminstill require entitlement-safe behavior. The audit also calls out query considerations onAlertDeliveryResourceand 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, andOperationRunResourceare 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 moveFindingResourceandAlertDeliveryResourceonto 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.
- Approve a temporary scope exception for