Skip to content

Admin Record State UX Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Make page, layout, media, Layout Builder widget, and widget-asset availability, publication, usage, integrity, and bounded relationship counts visible and consistent throughout the Filament admin.

Architecture: Keep domain fact resolution in the owning package and put only the immutable state presentation contract and Filament adapters in packages/admin. Compose availability, publication, usage, integrity, and relationship-count facts as separate ordered chips; preserve PageTableStatusResolver compatibility. Every surface query preloads its facts so rendering performs no database queries.

Tech Stack: PHP 8.4, Laravel, Filament, Spatie Laravel Data, Pest, Blade, Core/Admin packages, and the Layout Builder companion package.


Core Admin files are changed in this repository. Layout Builder files are changed in the sibling ../capell-packages-4 checkout.

  • Create packages/admin/src/Data/RecordStateData.php: immutable state chip data with stable key, translated labels, description, icon, colour, and priority.
  • Create packages/admin/src/Data/RecordRelationshipCountData.php: bounded relationship-count data with label, count, link, and authority wording.
  • Create packages/admin/src/Support/RecordState/RecordStateComposer.php: deterministic ordering, exceptional-state filtering, and surface-specific chip composition.
  • Create packages/admin/resources/views/components/record-state-summary.blade.php: render visible, accessible state text for headers/cards.
  • Modify packages/admin/src/Filament/Concerns/HasCustomSelectOption.php and packages/admin/resources/views/components/forms/select-option.blade.php: accept optional state/count metadata without breaking existing callers.
  • Create page availability and relationship resolvers under packages/admin/src/Actions/Pages and packages/admin/src/Actions/Layouts, with their typed data classes under packages/admin/src/Data/Pages and packages/admin/src/Data/Layouts.
  • Create typed page availability and relationship table columns under packages/admin/src/Filament/Components/Tables/Columns/Page and packages/admin/src/Filament/Components/Tables/Columns.
  • Modify packages/admin/src/Filament/Resources/Pages/Pages/EditPage.php, PagesTable.php, PageResource.php, PageSelect.php, and PageMorphToOptionSelect.php.
  • Modify packages/admin/src/Filament/Resources/Layouts/Pages/EditLayout.php, LayoutsTable.php, LayoutResource.php, LayoutSelect.php, and packages/admin/src/Support/Layouts/LayoutCardData.php.
  • Modify packages/admin/src/Filament/Resources/Media/Tables/MediaTable.php and its media tests.
  • Add focused tests beside each changed Admin surface under packages/admin/tests/Feature/Filament and unit tests under packages/admin/tests/Unit where the repository’s test layout supports them.
  • Modify Layout Builder packages/layout-builder/src/Filament/Resources/Widgets/Pages/EditWidget.php, WidgetsTable.php, WidgetSelectionTable.php, WidgetAssetsTable.php, LayoutsRelationManager.php, WidgetAssetsRelationManager.php, packages/layout-builder/src/Models/Widget.php, packages/layout-builder/src/Models/WidgetAsset.php, and composer.json.
  • Add Layout Builder tests under packages/layout-builder/tests/Feature/Filament and a dependency contract test for the minimum Admin version.

Task 1: Establish the immutable Admin presentation contract

Section titled “Task 1: Establish the immutable Admin presentation contract”

Files:

  • Create: packages/admin/src/Data/RecordStateData.php

  • Create: packages/admin/src/Data/RecordRelationshipCountData.php

  • Create: packages/admin/src/Support/RecordState/RecordStateComposer.php

  • Test: packages/admin/tests/Unit/Support/RecordState/RecordStateComposerTest.php

  • Step 1: Write failing contract tests

    Define tests for a state with a stable key, translated label, short label, description, Filament colour, Heroicon-compatible icon, and priority. Assert that the composer orders availability before publication before usage, removes routine positive states when a compact surface requests exceptional-only output, and retains combinations such as no_active_url plus scheduled.

  • Step 2: Run the focused test and confirm failure

    Run:

    Terminal window
    ./vendor/bin/pest packages/admin/tests/Unit/Support/RecordState/RecordStateComposerTest.php --configuration=phpunit.xml

    Expected: failure because the new data and composer classes do not exist.

  • Step 3: Implement the minimal typed contract

    RecordStateData must expose public readonly string $key, $label, ?string $shortLabel, ?string $description, BackedEnum|string|Htmlable|null $icon, string $color, and int $priority. RecordRelationshipCountData must expose a stable key, translated label, integer count, bool $authoritative, and nullable URL. The composer accepts list<RecordStateData> and returns the ordered list without querying models.

  • Step 4: Run the focused test and confirm success

    Run the command from Step 2. Expected: all contract tests pass.

  • Step 5: Commit the contract

    Terminal window
    git add packages/admin/src/Data packages/admin/src/Support/RecordState packages/admin/tests/Unit/Support/RecordState/RecordStateComposerTest.php
    git commit -m "feat(admin): add composable record state contract"

Task 2: Add shared state and relationship rendering

Section titled “Task 2: Add shared state and relationship rendering”

Files:

  • Create: packages/admin/resources/views/components/record-state-summary.blade.php

  • Create: packages/admin/resources/views/components/record-state-chip.blade.php

  • Modify: packages/admin/src/Filament/Concerns/HasCustomSelectOption.php

  • Modify: packages/admin/resources/views/components/forms/select-option.blade.php

  • Test: packages/admin/tests/Feature/Filament/Components/RecordStatePresentationTest.php

  • Step 1: Write failing rendering tests

    Render the summary with no_active_url and scheduled states and assert visible labels, descriptions, icon markup, and accessible text. Render a select option with a disabled state and relationship count and assert escaped label text, state text, count text, and no output when optional metadata is absent.

  • Step 2: Run the focused test and confirm failure

    Terminal window
    ./vendor/bin/pest packages/admin/tests/Feature/Filament/Components/RecordStatePresentationTest.php --configuration=phpunit.xml

    Expected: failure because the shared views and optional select payload do not exist.

  • Step 3: Implement accessible shared views

    Add visible text for every state, use icons and colour only as supplements, escape labels and descriptions, add accessible labels for compact icons, and preserve the existing count, description, image, prefix, inline, and size select-option props. Add an optional states list and optional relationship-count list without changing existing output when omitted.

  • Step 4: Run the focused test and inspect HTML

    Run the command from Step 2 and inspect the generated HTML for light/dark classes and escaped state text. Expected: all tests pass.

  • Step 5: Commit the shared presentation layer

    Terminal window
    git add packages/admin/src/Data packages/admin/src/Support/RecordState packages/admin/src/Filament/Concerns/HasCustomSelectOption.php packages/admin/resources/views packages/admin/tests/Feature/Filament/Components/RecordStatePresentationTest.php
    git commit -m "feat(admin): render accessible record state summaries"

Task 3: Resolve page availability and bounded page counts

Section titled “Task 3: Resolve page availability and bounded page counts”

Files:

  • Create: packages/admin/src/Actions/Pages/ResolvePageAvailabilityStateAction.php

  • Create: packages/admin/src/Actions/Pages/BuildPageRelationshipCountsAction.php

  • Create: packages/admin/src/Data/Pages/PageAvailabilityData.php

  • Create: packages/admin/src/Data/Pages/PageRelationshipCountsData.php

  • Create: packages/admin/src/Filament/Components/Tables/Columns/Page/PageAvailabilityColumn.php

  • Test: packages/admin/tests/Feature/Actions/Pages/ResolvePageAvailabilityStateActionTest.php

  • Test: packages/admin/tests/Feature/Actions/Pages/BuildPageRelationshipCountsActionTest.php

  • Step 1: Write failing fact-resolution tests

    Freeze time and cover pages with all URLs enabled, all URLs disabled, mixed URL states, no URLs, multiple languages, inaccessible sites, and scheduled publication. Assert that availability says No active URL or Some URLs disabled without inventing a Page.status. Assert child and URL counts are returned from preloaded relations or query aliases.

  • Step 2: Run the focused tests and confirm failure

    Terminal window
    ./vendor/bin/pest packages/admin/tests/Feature/Actions/Pages/ResolvePageAvailabilityStateActionTest.php packages/admin/tests/Feature/Actions/Pages/BuildPageRelationshipCountsActionTest.php --configuration=phpunit.xml

    Expected: failure because the resolvers do not exist.

  • Step 3: Implement page facts without database work in presentation

    Resolve URL availability with site/language scope and preserve deleted or inaccessible records as distinct facts. Extend the existing page table query to preload children and URL aggregates only when the relevant surface needs them. Keep PageTableStatusResolver::modifyQuery() and resolve() untouched.

  • Step 4: Run the focused tests and confirm success

    Run the command from Step 2. Expected: all page fact tests pass, including frozen-time and mixed-language cases.

  • Step 5: Commit page fact resolution

    Terminal window
    git add packages/admin/src/Actions/Pages packages/admin/src/Data/Pages packages/admin/src/Filament/Components/Tables/Columns/Page packages/admin/tests/Feature/Actions/Pages
    git commit -m "feat(admin): resolve page availability and counts"

Task 4: Adopt state and counts on page surfaces

Section titled “Task 4: Adopt state and counts on page surfaces”

Files:

  • Modify: packages/admin/src/Filament/Resources/Pages/Pages/EditPage.php

  • Modify: packages/admin/src/Filament/Resources/Pages/Tables/PagesTable.php

  • Modify: packages/admin/src/Filament/Resources/Pages/PageResource.php

  • Modify: packages/admin/src/Filament/Components/Forms/PageSelect.php

  • Modify: packages/admin/src/Filament/Components/Forms/PageMorphToOptionSelect.php

  • Test: packages/admin/tests/Feature/Filament/Resources/Page/Pages/EditPageTest.php

  • Test: packages/admin/tests/Feature/Filament/Resources/Page/Pages/ListPagesTest.php

  • Test: packages/admin/tests/Feature/Filament/Resources/Page/PageResourceTest.php

  • Test: packages/admin/tests/Feature/Filament/Components/PageSelectTest.php

  • Step 1: Add failing page-surface assertions

    Assert that edit headers show availability before publication and include the scheduled date/time. Assert that list rows show exceptional availability, relationship counts are links or compact metadata, global search includes a compact exceptional summary, and an already-selected unavailable page still renders in PageSelect without a per-option query.

  • Step 2: Run the focused page tests and confirm failure

    Terminal window
    ./vendor/bin/pest packages/admin/tests/Feature/Filament/Resources/Page/Pages/EditPageTest.php packages/admin/tests/Feature/Filament/Resources/Page/Pages/ListPagesTest.php packages/admin/tests/Feature/Filament/Resources/Page/PageResourceTest.php packages/admin/tests/Feature/Filament/Components/PageSelectTest.php --configuration=phpunit.xml

    Expected: the new state text and counts are absent.

  • Step 3: Implement page header, table, search, and selector adapters

    Compose existing publication data with page availability. Use the shared select-option view for labels such as Homepage — No active URL, while retaining current query eligibility rules. Add exceptional filters only where the table query can support them without changing page scope or extender behaviour.

  • Step 4: Run the focused page tests and confirm success

    Run the command from Step 2. Expected: all page surface tests pass and the query-count assertions show no per-option or per-row state queries.

  • Step 5: Commit the page surfaces

    Terminal window
    git add packages/admin/src/Filament/Resources/Pages packages/admin/src/Filament/Components/Forms/PageSelect.php packages/admin/src/Filament/Components/Forms/PageMorphToOptionSelect.php packages/admin/tests/Feature/Filament/Resources/Page packages/admin/tests/Feature/Filament/Components/PageSelectTest.php
    git commit -m "feat(admin): surface page availability in Filament"

Task 5: Resolve layout usage and adopt layout surfaces

Section titled “Task 5: Resolve layout usage and adopt layout surfaces”

Files:

  • Create: packages/admin/src/Actions/Layouts/BuildLayoutRelationshipCountsAction.php

  • Create: packages/admin/src/Data/Layouts/LayoutRelationshipCountsData.php

  • Modify: packages/admin/src/Filament/Resources/Layouts/Pages/EditLayout.php

  • Modify: packages/admin/src/Filament/Resources/Layouts/Tables/LayoutsTable.php

  • Modify: packages/admin/src/Filament/Resources/Layouts/LayoutResource.php

  • Modify: packages/admin/src/Filament/Components/Forms/Page/LayoutSelect.php

  • Modify: packages/admin/src/Support/Layouts/LayoutCardData.php

  • Modify: packages/admin/resources/views/filament/resources/layouts/layout-card.blade.php

  • Test: packages/admin/tests/Feature/Actions/Layouts/BuildLayoutRelationshipCountsActionTest.php

  • Test: packages/admin/tests/Feature/Filament/Resources/Layout/Pages/EditLayoutTest.php

  • Test: packages/admin/tests/Feature/Filament/Resources/Layout/Pages/ListLayoutsTest.php

  • Test: packages/admin/tests/Feature/Filament/Resources/Layout/LayoutResourceTest.php

  • Test: packages/admin/tests/Feature/Filament/Components/LayoutSelectTest.php

  • Step 1: Write failing layout count tests

    Cover zero-page layouts, layouts used by multiple registered page variations, actor-scoped sites, containers, and configured widgets. Assert that the authoritative count is shared by list, header, and selector and that an actor-scoped zero uses No accessible pages rather than Unused.

  • Step 2: Run the focused layout tests and confirm failure

    Terminal window
    ./vendor/bin/pest packages/admin/tests/Feature/Actions/Layouts/BuildLayoutRelationshipCountsActionTest.php packages/admin/tests/Feature/Filament/Resources/Layout/Pages/EditLayoutTest.php packages/admin/tests/Feature/Filament/Resources/Layout/Pages/ListLayoutsTest.php packages/admin/tests/Feature/Filament/Resources/Layout/LayoutResourceTest.php packages/admin/tests/Feature/Filament/Components/LayoutSelectTest.php --configuration=phpunit.xml

    Expected: the new relationship summary and shared variation-aware count assertions fail.

  • Step 3: Implement one variation-aware layout count path

    Extract the existing registered-page-variation SQL/count logic into the Admin-owned action/helper. Return page, container, and widget counts only when their source is loaded or safely queryable. Remove the per-variation-per-row count loop from card/table formatting; usage links consume preloaded counts.

  • Step 4: Add layout headers, cards, lists, search, and selectors

    Add a persistent edit-header summary, image-area exceptional badges, compact page-use counts, and select-option state text. Keep existing card actions, checkbox positioning, site scope, and preview rendering intact.

  • Step 5: Run the focused layout tests and confirm success

    Run the command from Step 2. Expected: all layout tests pass with no N+1 queries and the card remains usable at existing breakpoints.

  • Step 6: Commit the layout surfaces

    Terminal window
    git add packages/admin/src/Actions/Layouts packages/admin/src/Data/Layouts packages/admin/src/Filament/Resources/Layouts packages/admin/src/Filament/Components/Forms/Page/LayoutSelect.php packages/admin/src/Support/Layouts packages/admin/resources/views/filament/resources/layouts packages/admin/tests/Feature/Actions/Layouts packages/admin/tests/Feature/Filament/Resources/Layout packages/admin/tests/Feature/Filament/Components/LayoutSelectTest.php
    git commit -m "feat(admin): surface layout availability and usage"

Files:

  • Modify: packages/admin/src/Filament/Resources/Media/Tables/MediaTable.php

  • Test: packages/admin/tests/Feature/Filament/Resources/Media/Pages/ListMediaTest.php

  • Test: packages/admin/tests/Feature/Filament/Resources/Media/Pages/EditMediaTest.php

  • Step 1: Write failing media usage tests

    Assert that a zero attachment count renders Unused only for the supported exhaustive attachment contract, that non-zero counts link to the usage view, and that a usage filter and sort preserve MediaScope.

  • Step 2: Run the focused media tests and confirm failure

    Terminal window
    ./vendor/bin/pest packages/admin/tests/Feature/Filament/Resources/Media/Pages/ListMediaTest.php packages/admin/tests/Feature/Filament/Resources/Media/Pages/EditMediaTest.php --configuration=phpunit.xml

    Expected: zero usage is currently only a neutral numeric value.

  • Step 3: Implement explicit usage semantics

    Reuse the existing usage_count accessor/query path, add the filter and visible wording, and keep incomplete rich-text/package-owned references out of the authoritative claim.

  • Step 4: Run tests and commit

    Run the command from Step 2. Expected: all media tests pass.

    Terminal window
    git add packages/admin/src/Filament/Resources/Media/Tables/MediaTable.php packages/admin/tests/Feature/Filament/Resources/Media
    git commit -m "feat(admin): make media usage explicit"

Task 7: Adopt the contract in Layout Builder

Section titled “Task 7: Adopt the contract in Layout Builder”

Files:

  • Modify: ../capell-packages-4/packages/layout-builder/composer.json

  • Modify: ../capell-packages-4/packages/layout-builder/src/Filament/Resources/Widgets/Pages/EditWidget.php

  • Modify: ../capell-packages-4/packages/layout-builder/src/Filament/Resources/Widgets/Tables/WidgetsTable.php

  • Modify: ../capell-packages-4/packages/layout-builder/src/Filament/Resources/Widgets/Tables/WidgetSelectionTable.php

  • Modify: ../capell-packages-4/packages/layout-builder/src/Filament/Resources/Widgets/Tables/WidgetAssetsTable.php

  • Modify: ../capell-packages-4/packages/layout-builder/src/Filament/Resources/Widgets/RelationManagers/LayoutsRelationManager.php

  • Modify: ../capell-packages-4/packages/layout-builder/src/Filament/Resources/Widgets/RelationManagers/WidgetAssetsRelationManager.php

  • Create: ../capell-packages-4/packages/layout-builder/src/Actions/BuildWidgetRelationshipCountsAction.php

  • Create: ../capell-packages-4/packages/layout-builder/src/Data/WidgetRelationshipCountsData.php

  • Test: ../capell-packages-4/packages/layout-builder/tests/Feature/Filament/Resources/Widget/Pages/EditWidgetTest.php

  • Create: ../capell-packages-4/packages/layout-builder/tests/Feature/Filament/WidgetRecordStateContractTest.php

  • Step 1: Write failing package adoption tests

    Cover disabled/unavailable widgets, direct and nested widget placement, composed-widget and preset references, positive layout/asset counts, valid layout-level default assets, and broken polymorphic targets. Assert that Unused is emitted only from a complete reference query and that missing references are Broken reference or Orphaned.

  • Step 2: Update the package dependency before implementation

    Raise capell-app/admin from ^1.0.10 to the first Admin release that contains the presentation contract, update the package lock/overlay, and add a Composer contract test that fails when the required Admin API is absent.

  • Step 3: Implement package-owned usage and integrity facts

    Build additive query enrichment for widgets and widget assets. Include every Layout Builder-owned reference form required by the tests; do not infer orphaning from a nullable pageable field. Return preloaded relationship and integrity counts to the shared Admin rendering components.

  • Step 4: Adopt headers, tables, pickers, and relation managers

    Add compact state summaries to EditWidget, exceptional columns and counts to the widget tables, state-aware selection options, and integrity labels to widget-asset tables. Preserve existing bulk layout actions and permissions.

  • Step 5: Run focused package verification and commit

    Terminal window
    ./vendor/bin/pest packages/layout-builder/tests/Feature/Filament/Resources/Widget/Pages/EditWidgetTest.php packages/layout-builder/tests/Feature/Filament/WidgetRecordStateContractTest.php --configuration=phpunit.xml
    composer --working-dir=../capell-packages-4 lint

    Expected: focused tests, formatting, and the dependency contract pass.

    Terminal window
    git -C ../capell-packages-4 add packages/layout-builder/composer.json packages/layout-builder/src packages/layout-builder/tests
    git -C ../capell-packages-4 commit -m "feat(layout-builder): surface widget usage states"

Task 8: Verify the integrated slice and update project evidence

Section titled “Task 8: Verify the integrated slice and update project evidence”

Files:

  • Modify: the internal ledger’s projects/capell/TODO.md only through the ledger workflow

  • Verify: all files changed in Tasks 1–7

  • Step 1: Run focused Admin verification

    Terminal window
    ./vendor/bin/pest packages/admin/tests/Unit/Support/RecordState packages/admin/tests/Feature/Actions/Pages packages/admin/tests/Feature/Actions/Layouts packages/admin/tests/Feature/Filament/Components packages/admin/tests/Feature/Filament/Resources/Page packages/admin/tests/Feature/Filament/Resources/Layout packages/admin/tests/Feature/Filament/Resources/Media --configuration=phpunit.xml

    Expected: all focused state, count, page, layout, media, header, card, search, and selector tests pass.

  • Step 2: Run package focused verification

    Terminal window
    ./vendor/bin/pest packages/layout-builder/tests/Feature/Filament --configuration=phpunit.xml

    Expected: Layout Builder Filament tests pass against the declared Admin version.

  • Step 3: Run formatting and static analysis for changed repositories

    Terminal window
    composer lint
    composer analyze
    composer -d ../capell-packages-4 lint

    Expected: changed-code formatting and PHPStan checks pass. Full repository suites remain a separate evidence boundary if they are not run.

  • Step 4: Run documentation and diff hygiene checks

    Terminal window
    php scripts/check-docs-links.php
    git diff --check main...HEAD

    Expected: documentation links resolve and the diff contains no whitespace errors or unrelated generated files.

  • Step 5: Reconcile CAP-0080 in the internal ledger

    Record the exact Core and Layout Builder commits, focused test commands and results, any full-suite boundary, and remaining package-adoption inventory. Do not claim hosted CI, publication, release, deployment, or live/browser evidence unless those gates actually ran.

  • Step 6: Commit only the final focused verification changes

    Terminal window
    git status --short
    git diff --check
    git commit -m "test(admin): verify record state UX integration"

    Expected: the final commit contains only approved Admin/Layout Builder state work and its tests.

  • State model coverage: Tasks 1–4 cover separate availability and publication chips plus selected-value safety; Tasks 5–7 cover layout, media, widget, and widget-asset usage/integrity.
  • Count coverage: Tasks 3, 5, 6, and 7 cover page URLs/children, layout pages, containers/widgets, widget layouts/assets, widget-asset integrity, and media attachments. Each count is preloaded or explicitly omitted when it cannot be authoritative.
  • Package boundaries: Core remains untouched for UI; Admin owns presentation; Layout Builder owns widget facts and raises its Admin dependency.
  • Compatibility: Existing PageTableStatusResolver signatures and select option props remain stable.
  • Query safety: State and count resolution is tested independently and no rendering step is allowed to query.
  • Accessibility: Shared components require visible text, escaped labels, accessible compact icons, and light/dark contrast assertions.
  • Scope: Later companion adoption inventory remains outside the first slice.
  • Placeholder scan: no unresolved placeholder marker or unspecified implementation step remains in this plan.