# Implementation Plan: Empty State Consistency Pass

**Branch**: `122-empty-state-consistency` | **Date**: 2026-03-08 | **Spec**: [spec.md](./spec.md)
**Input**: Feature specification from `/specs/122-empty-state-consistency/spec.md`

**Note**: This plan is generated by the `/speckit.plan` workflow and aligned to the current repository constitution.

## Summary

Unify the empty-state experience across six primary Filament list pages so each empty list renders an enterprise-grade icon, heading, description, and exactly one guided CTA. The implementation will prefer resource-level `table()` empty-state definitions, preserve existing capability-aware CTA behavior, reuse local patterns from `BaselineProfileResource` and `ReviewPackResource`, update `AlertDeliveryResource`’s action-surface declaration to remove the empty-state exemption, document Alert Deliveries as the sole explicit UX-001 header-relocation exemption, and extend focused Pest + Livewire coverage for content, CTA outcomes, populated-state regression behavior, RBAC behavior, and action-surface guard compliance.

## Technical Context

**Language/Version**: PHP 8.4.15 / Laravel 12  
**Primary Dependencies**: Filament v5, Livewire v4.0+, Tailwind CSS v4, Pest v4  
**Storage**: PostgreSQL + existing workspace/tenant session context; no schema changes  
**Testing**: Pest v4 feature and Livewire component tests, plus manual light/dark visual QA evidence attached to PR/review  
**Target Platform**: Laravel Sail web admin + tenant panels (`/admin`, `/admin/t/{tenant}/...`) in containerized deployment  
**Project Type**: Laravel monolith / Filament web application  
**Performance Goals**: No additional remote calls, no new queued work, and no material increase in list-render query cost; empty-state rendering remains a table-configuration concern only  
**Constraints**: Preserve existing RBAC/UI enforcement, keep populated-table behavior unchanged, use exactly one empty-state CTA per surface, prefer resource `table()` definitions, keep screenshots out of the repo, and treat Alert Deliveries as the only explicit exemption from header CTA relocation once rows exist  
**Scale/Scope**: 6 list surfaces, 6 resource/page empty-state retrofits, 1 action-surface declaration update for Alert Deliveries, and focused regression tests; no migrations, no new services, no new routes

## Constitution Check

*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*

- Inventory-first: PASS (feature only affects empty-state presentation on existing inventory/backup/monitoring/workspace lists; no inventory-vs-snapshot semantics change).
- Read/write separation: PASS (no new writes or operational flows are introduced; existing mutating/create/start actions keep their current behavior).
- Graph contract path: PASS (no Microsoft Graph endpoints, contracts, or client usage change).
- Deterministic capabilities: PASS (no new capabilities; existing canonical capability checks and enforcement helpers remain in place).
- RBAC-UX: PASS (non-member 404 and member-without-capability 403 semantics remain unchanged; the feature only changes empty-state presentation).
- Workspace isolation: PASS (workspace-scoped resources remain workspace-safe; no chooser or context rules are altered).
- Destructive confirmation: PASS / N/A (no destructive actions are added or modified).
- Global search: PASS / N/A (no global search behavior changes).
- Tenant isolation: PASS (tenant-owned lists stay tenant-scoped; Alert Deliveries remains entitlement-filtered in workspace context).
- Run observability: PASS / N/A (feature introduces no new long-running, remote, or queued work; the existing policy sync CTA keeps its current operation-run behavior unchanged).
- Ops-UX 3-surface feedback: PASS / N/A for new work; preserved for existing queue-operation CTA on policies.
- Ops-UX lifecycle: PASS / N/A (no new `OperationRun` lifecycle changes).
- Ops-UX summary counts: PASS / N/A (no new `OperationRun` metrics producers).
- Ops-UX guards: PASS (existing guards remain relevant; plan includes updating action-surface guard coverage where the declared contract changes).
- Ops-UX system runs: PASS / N/A (no system-run behavior changes).
- Automation: PASS / N/A (no scheduled or queued logic added).
- Data minimization: PASS (no new persisted state or logging payloads).
- Badge semantics (BADGE-001): PASS / N/A (no new status-like badges are added; empty-state icons are not governed by BADGE-001).
- Filament UI Action Surface Contract: PASS with required implementation update. The six in-scope list surfaces already have action surfaces; the plan must update empty-state declarations/content and remove the current `AlertDeliveryResource` empty-state exemption.
- Filament UI UX-001: PASS. The feature directly advances UX-001 by ensuring each in-scope list empty state has a specific title, explanation, and one CTA.

## Project Structure

### Documentation (this feature)

```text
specs/122-empty-state-consistency/
├── plan.md
├── research.md
├── data-model.md
├── quickstart.md
├── contracts/
│   └── empty-states.openapi.yaml
└── tasks.md
```

### Source Code (repository root)

```text
app/
├── Filament/
│   └── Resources/
│       ├── PolicyResource.php                           # MODIFY — move complete empty state into table() and keep sync CTA semantics
│       ├── BackupSetResource.php                        # MODIFY — define complete empty state in table()
│       ├── RestoreRunResource.php                       # MODIFY — define complete empty state in table()
│       ├── BackupScheduleResource.php                   # MODIFY — define complete empty state in table()
│       ├── AlertDeliveryResource.php                    # MODIFY — define complete empty state + update actionSurfaceDeclaration()
│       ├── PolicyResource/
│       │   └── Pages/
│       │       └── ListPolicies.php                     # MODIFY — reduce helper-only empty-state responsibility if needed
│       ├── BackupSetResource/
│       │   └── Pages/
│       │       └── ListBackupSets.php                   # MODIFY — align empty-state CTA ownership with resource table()
│       ├── RestoreRunResource/
│       │   └── Pages/
│       │       └── ListRestoreRuns.php                  # MODIFY — align empty-state CTA ownership with resource table()
│       ├── BackupScheduleResource/
│       │   └── Pages/
│       │       └── ListBackupSchedules.php              # MODIFY — align empty-state CTA ownership with resource table()
│       └── Workspaces/
│           ├── WorkspaceResource.php                    # MODIFY — define complete empty state in table()
│           └── Pages/
│               └── ListWorkspaces.php                   # MODIFY — align empty-state CTA ownership with resource table()

tests/
├── Feature/
│   ├── Filament/
│   │   ├── CreateCtaPlacementTest.php                   # MODIFY — preserve empty-state/header CTA relocation behavior
│   │   ├── Alerts/
│   │   │   └── AlertDeliveryViewerTest.php              # MODIFY — extend empty-state / authorization coverage for alert deliveries
│   │   └── [new or updated empty-state tests]           # ADD/MODIFY — per-resource empty-state content coverage
│   ├── BackupScheduling/
│   │   └── BackupScheduleLifecycleAuthorizationTest.php # MODIFY or reference — disabled tooltip behavior remains intact
│   ├── PolicySyncStartSurfaceTest.php                   # MODIFY or reference — policy sync CTA capability behavior remains intact
│   └── Guards/
│       └── ActionSurfaceContractTest.php                # MODIFY — cover updated AlertDelivery empty-state declaration

resources/
└── views/
  └── [no new view templates expected]                 # prefer native Filament table empty-state configuration
```

**Structure Decision**: Keep the work inside the existing Laravel/Filament monolith. The feature is a resource-level UI consistency pass with focused list-page helper cleanup and targeted Pest/Livewire regression coverage; no new base folders, services, routes, or data layer objects are needed.

## Complexity Tracking

> No Constitution Check violations. No justifications needed.

| Violation | Why Needed | Simpler Alternative Rejected Because |
|-----------|------------|-------------------------------------|
| — | — | — |

## Phase 0 — Research (output: `research.md`)

See: [research.md](./research.md)

Research goals:
- Confirm the preferred source of truth for complete Filament empty-state definitions in this repo.
- Confirm how to preserve existing capability-aware CTA behavior without creating RBAC regressions.
- Confirm the correct next-step CTA for the read-only Alert Deliveries surface.
- Confirm the smallest reliable test strategy using existing Pest + Livewire patterns and existing action-surface guards.

## Phase 1 — Design & Contracts (outputs: `data-model.md`, `contracts/`, `quickstart.md`)

See:
- [data-model.md](./data-model.md)
- [contracts/empty-states.openapi.yaml](./contracts/empty-states.openapi.yaml)
- [quickstart.md](./quickstart.md)

Design focus:
- Model each list empty state as a UI contract over existing resource tables rather than a new domain or persistence object.
- Move in-scope surfaces toward resource-level `table()` ownership of empty-state heading, description, icon, and CTA.
- Preserve header-vs-empty-state CTA placement behavior already guarded by existing tests.
- Update `AlertDeliveryResource` from an empty-state exemption to an explicit guided empty-state declaration while preserving its documented no-header-action exemption once rows exist.
- Keep screenshots and dark-mode QA as review artifacts rather than committed documentation files.

## Phase 2 — Implementation Outline (tasks created in `/speckit.tasks`)

### Resource empty-state retrofit
- Add or complete `emptyStateHeading()`, `emptyStateDescription()`, `emptyStateIcon()`, and `emptyStateActions()` on the six in-scope list surfaces.
- Reuse each surface’s existing primary CTA behavior (`create`, `navigate`, or `queue operation`) rather than inventing new workflows.
- Keep copy concise, enterprise-oriented, and module-specific.

### Ownership consolidation
- Move empty-state contract ownership into each resource `table()` configuration where Filament supports it.
- Reduce or remove list-page helper duplication so copy, icon, and CTA do not drift across files.
- Preserve existing empty/header CTA relocation behavior already covered by the repo.

### Contract and guard alignment
- Update `AlertDeliveryResource::actionSurfaceDeclaration()` to satisfy `ListEmptyState` rather than exempt it.
- Verify the changed surfaces still satisfy the Action Surface Contract and do not require new baseline exemptions.

### Regression protection
- Add or extend per-resource empty-state rendering assertions for heading, description, icon-related presence, and CTA labels.
- Add explicit CTA outcome coverage so empty-state actions are proven to navigate or queue the intended existing workflows.
- Add populated-state regression coverage for representative create-capable surfaces plus the Alert Deliveries header-action exemption.
- Extend capability tests so disabled or hidden CTA behavior remains aligned with the current resource-specific model.
- Keep response/feature coverage for workspace-context and alert-delivery entitlement behavior unchanged except for the new empty-state guidance.

### Verification
- Run focused Pest suites for CTA placement, authorization behavior, alert deliveries, and action-surface guards.
- Run Pint on dirty files through Sail.
- Capture light/dark visual QA evidence for all affected surfaces in PR/review artifacts.

## Constitution Check (Post-Design)

Re-check result: PASS. The design adds no new routes, data model changes, background work, or authorization semantics. It strengthens UX-001 compliance, preserves canonical RBAC/UI-enforcement behavior, and explicitly updates the Alert Deliveries action-surface declaration so the implemented UI stays aligned with the repository’s guard-enforced contract.
