# Research: Filament Table UX Standardization & List Consistency ## Decision 1: Use native Filament table configuration as the primary implementation path - Decision: Standardize behavior directly in each table’s existing `table()` definition and page-level empty-state hooks, using native Filament methods as the default approach. - Rationale: The repo already defines table behavior locally across resources, relation managers, widgets, system pages, and Livewire picker tables. Keeping the standard inline preserves clarity, aligns with the spec’s convention-first goal, and avoids introducing a second configuration language on top of Filament. - Alternatives considered: - Build a generic table DSL with primary/context/detail declarations: rejected because it would hide normal Filament behavior and create a parallel framework to maintain. - Make macros the default rollout path: rejected because the current inconsistencies are mostly judgment and information-architecture issues, not missing framework capability. ## Decision 2: Keep shared support intentionally tiny and mechanical - Decision: Do not introduce a large shared base class or trait hierarchy. Only allow tiny shared support where duplication is purely mechanical, with pagination-profile helpers as the most likely candidate. - Rationale: The workspace currently has no shared `StandardTableDefaults` or equivalent helper. Introducing a broad helper layer at the same time as a repo-wide cleanup would increase migration risk and make reviews harder. - Alternatives considered: - Add a `StandardTableDefaults` trait and force every table through it: rejected because it would centralize too many domain-specific decisions and make exceptions harder to reason about. - Keep every pagination and persistence setting fully duplicated forever: rejected because a very small helper for page-size arrays may be justified once the first rollout wave proves the pattern is stable. ## Decision 3: Treat resource-list persistence as mandatory and relation-manager persistence as optional - Decision: Enable session persistence for search, sort, and filters on resource list tables in the first rollout wave. Leave relation managers, widgets, picker tables, and custom pages on an opt-in basis where the behavior fits naturally. - Rationale: The audit gap is strongest on resource lists, and Filament-native session persistence maps directly to that need. Extending persistence to every table surface immediately would expand scope and create more state-management edge cases than the spec requires. - Alternatives considered: - Add persistence to every table surface immediately: rejected because it increases rollout complexity without matching the strongest user pain first. - Skip persistence and rely only on query-string state: rejected because the spec explicitly targets keeping list context across refreshes. ## Decision 4: Use a documented Primary / Context / Detail model rather than code-level column metadata - Decision: Express Primary, Context, and Detail as a repo review convention backed by examples, not as a new code abstraction. - Rationale: The missing consistency is mostly a design-review problem. A documented tier model gives reviewers and implementers a common language while letting each table remain explicit and readable. - Alternatives considered: - Add a `primaryColumn()` / `detailColumn()` API wrapper: rejected because it would obscure normal Filament column configuration and encourage over-abstraction. - Leave visibility choices fully ad hoc: rejected because that is the exact drift pattern the spec is meant to stop. ## Decision 5: Standardize timestamps, nulls, and IDs using native column methods - Decision: Use native Filament column behavior for relative timestamps, placeholders, toggle-hidden detail fields, copyable identifiers, truncation, and tooltips instead of custom renderers wherever possible. - Rationale: The repo already uses `since()`, `dateTime()`, `toggleable()`, `copyable()`, and empty-state APIs in several places. Extending those patterns is lower risk than inventing custom rendering helpers. - Alternatives considered: - Introduce custom Blade column views for standard timestamp and ID rendering: rejected because it would be more fragile and harder to apply consistently across many surfaces. - Leave timestamp and null formatting untouched during rollout: rejected because inconsistent rendering is one of the audited usability defects. ## Decision 6: Preserve existing badge, action, and RBAC architecture - Decision: Do not redesign actions, badge mappings, or authorization mechanics as part of this feature. Keep actions and empty-state CTAs table-local, preserve centralized badge rendering through `BadgeCatalog` and `BadgeRenderer`, and maintain existing UI enforcement helpers. - Rationale: The repo already has centralized badge infrastructure and explicit action-level UI enforcement patterns. The spec explicitly excludes action redesign, and mixing that work into the rollout would create unnecessary risk. - Alternatives considered: - Standardize row actions and header actions in the same feature: rejected because actions are comparatively consistent and would create avoidable scope creep. - Rebuild badges as part of a broader “table facelift”: rejected because BADGE-001 requires centralized semantics and the current badge layer already exists. ## Decision 7: Use performance exceptions deliberately and document them per table - Decision: Any new sort or search behavior on relation-backed or computed columns requires explicit query review, and some tables may keep documented exceptions instead of forcing full compliance. - Rationale: Direct code inspection already shows query-sensitive surfaces. `app/Filament/System/Pages/Directory/Workspaces.php` computes health and recent failures per row, and `app/Filament/Resources/BackupSetResource/RelationManagers/BackupItemsRelationManager.php` already mixes operational actions with a dense relation-backed list. The standard must not worsen those hotspots. - Alternatives considered: - Force every meaningful-looking column to become sortable or searchable: rejected because that would create hidden N+1 or aggregate query regressions. - Exclude risk tables from the rollout entirely: rejected because the spec requires broad alignment, but exceptions can be documented where needed. ## Decision 8: Roll out by table class and business criticality, not alphabetically - Decision: Implement the standard in phases: documentation and baseline, first-wave critical resource tables, then remaining resources and relation managers, then widgets, custom pages, and picker tables, followed by performance hardening. - Rationale: The current table surface spans roughly three dozen screens with uneven complexity. A phased rollout lets the project prove the standard on high-value tables before applying it repo-wide. - Alternatives considered: - Update every table in one large pass: rejected because it would be hard to review and too risky for query behavior. - Limit the effort to a small set of flagship resources: rejected because the spec is explicitly repo-wide and aims to stop future drift.