## Summary - add a shared tenant lifecycle presentation contract and referenced-tenant adapter for canonical lifecycle labels and helper copy - align tenant, chooser, onboarding, archived-banner, and tenantless operation viewer surfaces with the shared lifecycle vocabulary - add Spec 146 design artifacts, audit notes, and regression coverage for lifecycle presentation across Filament and onboarding surfaces ## Validation - `vendor/bin/sail bin pint --dirty --format agent` - `vendor/bin/sail artisan test --compact tests/Feature/Badges/TenantStatusBadgeTest.php tests/Unit/Badges/TenantBadgesTest.php tests/Unit/Tenants/TenantLifecycleTest.php tests/Unit/Support/Tenants/TenantLifecyclePresentationTest.php tests/Feature/Filament/TenantLifecyclePresentationAcrossTenantSurfacesTest.php tests/Feature/Filament/ReferencedTenantLifecyclePresentationTest.php tests/Feature/Filament/TenantLifecycleStatusDomainSeparationTest.php tests/Feature/Filament/TenantViewHeaderUiEnforcementTest.php tests/Feature/Onboarding/TenantLifecyclePresentationCopyTest.php tests/Feature/Onboarding/OnboardingDraftAuthorizationTest.php tests/Feature/Onboarding/OnboardingDraftLifecycleTest.php` ## Notes - Livewire v4.0+ compliance preserved; this change is presentation-only on existing Filament v5 surfaces. - Panel provider registration remains unchanged in `bootstrap/providers.php`. - No global-search behavior changed; no resource was newly made globally searchable or disabled. - No destructive actions were added or changed. - No asset registration strategy changed; existing deploy flow for `php artisan filament:assets` remains unchanged. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #175
6.1 KiB
6.1 KiB
Research: Central Tenant Status Presentation
Decision 1: Build on the existing TenantLifecycle enum and badge catalog instead of creating a parallel status system
- Decision: Treat
App\Support\Tenants\TenantLifecycleas the canonical lifecycle source and extend presentation through a new lifecycle presentation contract layered on top of the existingBadgeCatalogandBadgeRendererpipeline. - Rationale: The codebase already has the correct domain enum (
draft,onboarding,active,archived) and an existing BADGE-001-compliant badge registry. Reusing those pieces avoids semantic drift and keeps lifecycle presentation aligned with the authoritative domain model from Spec 143. - Alternatives considered:
- Add more per-surface
matchorformatStateUsing()closures. Rejected because that would reintroduce scattered UI dictionaries. - Put all presentation logic directly on the
Tenantmodel. Rejected because lifecycle presentation density varies by surface and should not overload the domain model with Filament-specific UI concerns. - Keep using
TenantStatusBadgealone. Rejected because badge label/color/icon is not enough to cover helper text, referenced-tenant notes, and invalid-data distinction.
- Add more per-surface
Decision 2: Separate lifecycle presentation from adjacent status domains explicitly
- Decision: Keep lifecycle presentation as its own contract and do not merge it with provider connection, app consent, RBAC, verification, onboarding progress, or operation-run state.
- Rationale: The tenant detail and tenantless operations viewer surfaces already carry multiple status domains. Spec 146 requires lifecycle to remain honest to lifecycle only, otherwise operators misread onboarding, archived, or active state as health or readiness.
- Alternatives considered:
- Collapse lifecycle and readiness into one composite badge. Rejected because it obscures whether a tenant is active but unhealthy versus onboarding but valid.
- Reuse app-status or RBAC helper copy to explain lifecycle. Rejected because it creates cross-domain semantic leakage.
Decision 3: Use one presentation source with multiple density variants
- Decision: The new presentation contract should return both concise and detailed variants from the same underlying lifecycle meaning.
- Rationale: Tables and selectors need short badge semantics, while detail views and the tenantless operations viewer need helper text that explains why onboarding or archived tenants still appear. One source with multiple densities satisfies the spec without forcing verbose copy into every surface.
- Alternatives considered:
- Make all surfaces badge-only. Rejected because the tenantless operations viewer and detail pages need more context.
- Make all surfaces badge plus paragraph text. Rejected because selector and list surfaces would become noisy.
Decision 4: Keep invalid fallback only for non-canonical data and make it explicit in tests
- Decision: Preserve a reserved fallback path for corrupted or unexpected lifecycle values, but do not allow canonical states to reach it.
- Rationale:
TenantStatusBadgecurrently falls back toBadgeSpec::unknown(). The feature needs a stricter separation so valid canonical states are exhaustive and only truly invalid values use fallback behavior. - Alternatives considered:
- Remove fallback completely. Rejected because legacy or bad data still needs a safe render path.
- Continue allowing normalized aliases like
inactiveto map silently without explicit tests. Rejected because future drift would be hard to detect.
Decision 5: Favor focused regression tests over broad UI rewrites
- Decision: Add unit coverage for the central presentation contract and focused Pest feature tests for the tenant index, tenant detail, choose-tenant/onboarding-linked presentation, and tenantless operations viewer surfaces.
- Rationale: The existing architecture is already partially centralized. The highest-value risk is regression through future local mappings or fallback behavior, so tests should lock down lifecycle semantics where operators actually see them.
- Alternatives considered:
- Add only unit tests. Rejected because cross-surface consistency is the actual acceptance criterion.
- Add a full browser test suite for every surface. Rejected because feature and unit coverage can prove the contract with lower cost and faster CI.
Supporting Findings
App\Support\Tenants\TenantLifecyclealready defines the exact four lifecycle states and canonical labels required by the spec.App\Support\Badges\BadgeCatalogandApp\Support\Badges\Domains\TenantStatusBadgealready centralize badge label/color/icon mapping for tenant lifecycle, but they do not yet provide richer helper semantics.app/Filament/Resources/TenantResource.phpalready renders tenantstatusthroughBadgeRendereron both the table and infolist surfaces.resources/views/filament/pages/choose-tenant.blade.phpuses lifecycle-adjacent prose such as "No active tenants available" and references onboarding/archived records without a dedicated lifecycle presentation object.resources/views/filament/widgets/tenant/tenant-archived-banner.blade.phpalready carries archived-tenant helper copy, which makes it an in-scope surface that must be aligned with the shared lifecycle contract instead of maintaining standalone wording.app/Filament/Pages/Operations/TenantlessOperationRunViewer.phpderives lifecycle-aware canonical-view copy viaTenantOperabilityService, which is a good integration point for the shared presentation contract.resources/views/filament/pages/operations/tenantless-operation-run-viewer.blade.phprenders the canonical context banner body, making it the concrete viewer surface where referenced-tenant lifecycle wording must stay aligned with tenant management pages.app/Filament/Resources/OperationRunResource.phpbuilds the enterprise-detail payload consumed by the tenantless operations viewer, so referenced-tenant lifecycle summary text there is part of the same viewer surface rather than a separate resource rollout.- Filament v5 documentation confirms badge rendering is intended to be driven centrally through shared text/badge semantics rather than repeated local mappings.