6.8 KiB
Research: Empty State Consistency Pass
Feature: 122-empty-state-consistency | Date: 2026-03-08
R1: Source of truth for complete empty-state definitions
Decision: Prefer resource-level table() definitions for emptyStateHeading(), emptyStateDescription(), emptyStateIcon(), and emptyStateActions(), and use list-page helpers only where Filament or existing code structure makes that technically necessary.
Rationale: The repo already has strong resource-level empty-state examples in BaselineProfileResource, ReviewPackResource, and AlertDestinationResource. Resource-level definitions keep the empty-state contract close to the table configuration and reduce drift between copy, icon, and CTA placement. Existing list-page helpers can remain only as a compatibility layer when moving behavior fully into table() is impractical.
Alternatives considered:
- Keep all behavior in list-page helpers: rejected because it spreads the empty-state contract across resource and page classes.
- Split heading/description/icon in the resource and CTA in the page: rejected because the spec explicitly aims to avoid inconsistent ownership of the empty-state contract.
R2: Canonical visual pattern reference in the current codebase
Decision: Use BaselineProfileResource as the tone and structural reference for concise enterprise empty-state copy, and use ReviewPackResource as the local example for an explicit emptyStateIcon() call.
Rationale: BaselineProfileResource already demonstrates the desired guidance-oriented structure: concise heading, short explanatory description, and one clear CTA. However, the current codebase’s clearest resource-level example that also includes emptyStateIcon() is ReviewPackResource. Combining those patterns matches the feature goal without inventing a new UI convention.
Alternatives considered:
- Treat
BaselineProfileResourceas a literal one-to-one implementation reference: rejected because it does not currently include an explicit icon call. - Introduce a new shared helper abstraction first: rejected because the pass is intentionally low-risk and focused on consistency, not abstraction work.
R3: Capability-aware CTA behavior must stay resource-specific
Decision: Preserve each resource’s current capability behavior for the primary CTA (disabled vs hidden), and require explanatory helper text or tooltips when the CTA is shown disabled.
Rationale: The repo already uses UiEnforcement and WorkspaceUiEnforcement patterns plus explicit disabled tooltip checks in Pest tests. Standardizing every resource to one visibility pattern would create avoidable RBAC regressions. The spec’s consistency target is visual and content consistency, not authorization behavior uniformity.
Alternatives considered:
- Force all CTAs to be visible-but-disabled: rejected because some resources intentionally hide actions for certain contexts.
- Force all CTAs to be hidden: rejected because it removes guidance for legitimate members who simply lack one capability.
R4: Alert Deliveries requires a navigational next step, not a mutation
Decision: The read-only Alert Deliveries list should use View alert rules as its single empty-state CTA, and that CTA should exist only in the empty state.
Rationale: AlertDeliveryResource is a generated-history surface, so an empty state does not indicate a missing CRUD record the user should create directly. Routing the user to alert rule configuration is the clearest “why is this empty / what do I do next?” step while keeping the list read-only and consistent with monitoring semantics. Once delivery history exists, the explanatory CTA is no longer the primary page action, so the surface keeps its intentional “no header actions” design by explicit exemption rather than adding a persistent header action.
Alternatives considered:
View alert destinations: rejected because destinations alone do not explain why no deliveries have fired.- No CTA: rejected because this feature explicitly removes scaffold-like empty states from in-scope pages.
R5: Existing test patterns support this pass without browser-only coverage
Decision: Use Pest + Livewire list-page tests as the primary verification mechanism, extending existing CTA placement tests and adding per-resource empty-state assertions for content plus capability behavior. Keep dark-mode verification as manual PR/review evidence.
Rationale: The repo already uses assertTableEmptyStateActionsExistInOrder(), assertActionDisabled(), assertTableActionDisabled(), response assertions, and action-surface guard tests. This makes the feature testable with focused component and feature coverage, while visual hierarchy and dark mode remain best validated through attached review evidence.
Alternatives considered:
- Rely on screenshots only: rejected because the constitution and repo rules require programmatic testing.
- Add browser testing as the default path: rejected because the existing list-page and response-level tests already cover the required behavior at lower cost.
R6: Action-surface declarations must be updated alongside UI changes
Decision: Treat AlertDeliveryResource::actionSurfaceDeclaration() as part of the implementation scope, because it currently exempts the ListEmptyState slot even though this feature adds a guided empty state.
Rationale: The repo enforces the Action Surface Contract through guard tests. Leaving the old exemption in place would create a mismatch between the declared UI contract and the implemented behavior.
Alternatives considered:
- Only change visible UI and skip declaration updates: rejected because the guard suite treats declarations as part of the feature contract.
- Broaden the feature to retrofit more action-surface declarations: rejected because only the in-scope resources need updates for this pass.
R7: CTA outcome and populated-state regressions need explicit automated coverage
Decision: The implementation must add targeted automated assertions for (a) empty-state CTA outcomes and (b) representative populated-state behavior, instead of relying on final manual smoke checks alone.
Rationale: The spec requires users to reach the intended next step when they activate the CTA, and the constitution requires create-capable surfaces to relocate their primary CTA to the table header when rows exist. Existing guard and placement tests provide the base pattern, but this pass still needs explicit test tasks so those requirements remain enforceable during implementation.
Alternatives considered:
- Rely on rendering assertions plus manual QA: rejected because it leaves navigation/queue behavior and populated-state regressions implicit.
- Add full browser coverage for all six surfaces: rejected because focused Pest + Livewire assertions can cover the contract with lower cost.