polish: customer-review workspace density and hierarchy (spec 344) (#416)

Refactored the customer-review workspace to emphasize the Operator Summary, tightening the hierarchy. Readiness flow and acknowledgment details were adjusted, and supporting proof panels moved to secondary visual weight.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #416
This commit is contained in:
ahmido 2026-06-02 00:43:57 +00:00
parent 0987527d0e
commit 90f35b971e
13 changed files with 917 additions and 253 deletions

View File

@ -51,171 +51,127 @@
<div class="grid grid-cols-1 gap-4 md:grid-cols-3 xl:grid-cols-12">
<main class="min-w-0 space-y-4 md:col-span-2 xl:col-span-8">
<div
class="rounded-xl border border-gray-200 bg-white p-4 shadow-sm dark:border-white/10 dark:bg-gray-900"
data-testid="customer-review-decision-card"
>
<div class="flex flex-col gap-4">
<div class="flex flex-wrap items-center gap-2">
<x-filament::badge :color="$readiness['color']">
{{ $readiness['label'] }}
</x-filament::badge>
<x-filament::badge color="gray">
{{ __('localization.review.customer_safe') }}
</x-filament::badge>
</div>
<div class="space-y-2">
<h2 class="text-xl font-semibold text-gray-950 dark:text-white">
{{ $readiness['question'] }}
</h2>
<p class="max-w-3xl text-sm text-gray-600 dark:text-gray-300">
{{ $readiness['reason'] }}
</p>
</div>
<div class="grid gap-3 md:grid-cols-[minmax(0,1fr)_16rem]">
<div class="flex h-full flex-col gap-2 rounded-lg border border-gray-200 bg-gray-50 p-4 text-sm dark:border-white/10 dark:bg-white/5">
<div class="text-[0.7rem] font-semibold uppercase leading-4 tracking-wide text-gray-500 dark:text-gray-400">
{{ __('localization.review.impact') }}
</div>
<p class="text-sm leading-6 text-gray-700 dark:text-gray-200">
{{ $readiness['impact'] }}
</p>
<section class="space-y-4" data-testid="customer-review-operator-summary">
<div
class="rounded-xl border border-gray-200 bg-white p-4 shadow-sm dark:border-white/10 dark:bg-gray-900"
data-testid="customer-review-decision-card"
>
<div class="flex flex-col gap-4">
<div class="flex flex-wrap items-center gap-2">
<x-filament::badge :color="$readiness['color']">
{{ $readiness['label'] }}
</x-filament::badge>
<x-filament::badge color="gray">
{{ __('localization.review.customer_safe') }}
</x-filament::badge>
</div>
<div class="flex h-full flex-col rounded-lg border border-gray-200 p-4 text-sm dark:border-white/10">
<div class="text-[0.7rem] font-semibold uppercase leading-4 tracking-wide text-gray-500 dark:text-gray-400">
{{ __('localization.review.latest_released_review') }}
</div>
<div class="mt-2 font-medium leading-5 text-gray-950 dark:text-white">
{{ $latest['environment_label'] }}
</div>
<div class="mt-1 text-xs leading-5 text-gray-500 dark:text-gray-400">
{{ __('localization.review.published_date', ['date' => $latest['published_label']]) }}
</div>
</div>
</div>
<div class="flex flex-wrap gap-2">
@if ($readiness['primary_action_url'])
<x-filament::button
tag="a"
:href="$readiness['primary_action_url']"
:icon="$readiness['primary_action_icon']"
target="_blank"
data-testid="customer-review-primary-action"
>
{{ $readiness['primary_action_label'] }}
</x-filament::button>
@endif
@if ($latest['secondary_action_url'])
<x-filament::button
tag="a"
:href="$latest['secondary_action_url']"
color="gray"
:icon="$latest['secondary_action_icon']"
data-testid="customer-review-secondary-action"
>
{{ $latest['secondary_action_label'] }}
</x-filament::button>
@endif
</div>
</div>
</div>
<div
class="rounded-xl border border-gray-200 bg-white p-4 shadow-sm dark:border-white/10 dark:bg-gray-900"
data-testid="customer-review-readiness-flow"
>
<div class="flex flex-col gap-3">
<div>
<h2 class="text-sm font-semibold text-gray-950 dark:text-white">
{{ __('localization.review.review_consumption_flow') }}
</h2>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-300">
{{ __('localization.review.review_consumption_flow_description') }}
</p>
</div>
<div class="grid gap-3 sm:grid-cols-2 2xl:grid-cols-3">
@foreach ($readinessFlow as $step)
<div
class="flex min-h-36 flex-col gap-2 rounded-lg border border-gray-200 p-3 text-sm dark:border-white/10"
data-testid="customer-review-readiness-step"
data-step-label="{{ $step['title'] }}"
data-step-state="{{ $step['label'] }}"
data-step-current="{{ $step['is_current'] ? 'true' : 'false' }}"
>
<div class="flex flex-wrap items-start gap-2">
<div class="min-w-0 font-medium text-gray-950 dark:text-white">
{{ $step['title'] }}
</div>
<x-filament::badge :color="$step['color']" size="sm" class="max-w-full whitespace-normal text-left">
{{ $step['label'] }}
</x-filament::badge>
</div>
<p class="text-xs leading-5 text-gray-600 dark:text-gray-300">
{{ $step['description'] }}
</p>
@if ($step['is_current'])
<div class="mt-auto text-xs font-medium text-gray-500 dark:text-gray-400">
{{ __('localization.review.current_attention_point') }}
</div>
@endif
</div>
@endforeach
</div>
</div>
</div>
<div
class="rounded-xl border border-gray-200 bg-white p-4 shadow-sm dark:border-white/10 dark:bg-gray-900"
data-testid="customer-review-acknowledgement-card"
>
<div class="flex flex-col gap-3">
<div class="flex flex-wrap items-start justify-between gap-3">
<div>
<h2 class="text-sm font-semibold text-gray-950 dark:text-white">
{{ __('localization.review.review_acknowledgement') }}
<div class="space-y-2">
<h2 class="text-xl font-semibold text-gray-950 dark:text-white">
{{ $readiness['question'] }}
</h2>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-300">
{{ $acknowledgement['reason'] }}
</p>
</div>
<x-filament::badge :color="$acknowledgement['status_color']">
{{ $acknowledgement['status_label'] }}
</x-filament::badge>
</div>
<div class="grid gap-3 md:grid-cols-[minmax(0,1fr)_16rem]">
<div class="flex h-full flex-col gap-2 rounded-lg border border-gray-200 bg-gray-50 p-4 text-sm dark:border-white/10 dark:bg-white/5">
<div class="text-[0.7rem] font-semibold uppercase leading-4 tracking-wide text-gray-500 dark:text-gray-400">
{{ __('localization.review.impact') }}
</div>
<p class="text-sm leading-6 text-gray-700 dark:text-gray-200">
{{ $acknowledgement['impact'] }}
<p class="max-w-3xl text-sm text-gray-600 dark:text-gray-300">
{{ $readiness['reason'] }}
</p>
</div>
<div class="flex h-full flex-col rounded-lg border border-gray-200 p-4 text-sm dark:border-white/10">
<div class="text-[0.7rem] font-semibold uppercase leading-4 tracking-wide text-gray-500 dark:text-gray-400">
{{ __('localization.review.basis') }}
<div class="grid gap-3 md:grid-cols-[minmax(0,1fr)_16rem]">
<div class="flex h-full flex-col gap-2 rounded-lg border border-gray-200 bg-gray-50 p-4 text-sm dark:border-white/10 dark:bg-white/5">
<div class="text-[0.7rem] font-semibold uppercase leading-4 tracking-wide text-gray-500 dark:text-gray-400">
{{ __('localization.review.impact') }}
</div>
<p class="text-sm leading-6 text-gray-700 dark:text-gray-200">
{{ $readiness['impact'] }}
</p>
</div>
<div class="mt-2 space-y-1.5">
@foreach ($acknowledgement['basis'] as $basis)
<div class="flex items-center justify-between gap-3 text-xs">
<span class="text-gray-500 dark:text-gray-400">{{ $basis['label'] }}</span>
<x-filament::badge :color="$basis['color']" size="sm">
{{ $basis['value'] }}
</x-filament::badge>
</div>
@endforeach
<div class="flex h-full flex-col rounded-lg border border-gray-200 p-4 text-sm dark:border-white/10">
<div class="text-[0.7rem] font-semibold uppercase leading-4 tracking-wide text-gray-500 dark:text-gray-400">
{{ __('localization.review.latest_released_review') }}
</div>
<div class="mt-2 font-medium leading-5 text-gray-950 dark:text-white">
{{ $latest['environment_label'] }}
</div>
<div class="mt-1 text-xs leading-5 text-gray-500 dark:text-gray-400">
{{ __('localization.review.published_date', ['date' => $latest['published_label']]) }}
</div>
</div>
</div>
<div class="flex flex-wrap gap-2">
@if ($readiness['primary_action_url'])
<x-filament::button
tag="a"
:href="$readiness['primary_action_url']"
:icon="$readiness['primary_action_icon']"
target="_blank"
data-testid="customer-review-primary-action"
>
{{ $readiness['primary_action_label'] }}
</x-filament::button>
@endif
@if ($latest['secondary_action_url'])
<x-filament::button
tag="a"
:href="$latest['secondary_action_url']"
color="gray"
:icon="$latest['secondary_action_icon']"
data-testid="customer-review-secondary-action"
>
{{ $latest['secondary_action_label'] }}
</x-filament::button>
@endif
</div>
</div>
</div>
<div
class="rounded-xl border border-gray-200 bg-white p-4 shadow-sm dark:border-white/10 dark:bg-gray-900"
data-testid="customer-review-acknowledgement-card"
>
<div class="flex flex-col gap-3">
<div class="flex flex-wrap items-start justify-between gap-3">
<div>
<h2 class="text-sm font-semibold text-gray-950 dark:text-white">
{{ __('localization.review.review_acknowledgement') }}
</h2>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-300">
{{ $acknowledgement['reason'] }}
</p>
</div>
<x-filament::badge :color="$acknowledgement['status_color']">
{{ $acknowledgement['status_label'] }}
</x-filament::badge>
</div>
<div class="grid gap-3 md:grid-cols-[minmax(0,1fr)_16rem]">
<div class="flex h-full flex-col gap-2 rounded-lg border border-gray-200 bg-gray-50 p-4 text-sm dark:border-white/10 dark:bg-white/5">
<div class="text-[0.7rem] font-semibold uppercase leading-4 tracking-wide text-gray-500 dark:text-gray-400">
{{ __('localization.review.impact') }}
</div>
<p class="text-sm leading-6 text-gray-700 dark:text-gray-200">
{{ $acknowledgement['impact'] }}
</p>
</div>
<div class="flex h-full flex-col rounded-lg border border-gray-200 p-4 text-sm dark:border-white/10">
<div class="text-[0.7rem] font-semibold uppercase leading-4 tracking-wide text-gray-500 dark:text-gray-400">
{{ __('localization.review.basis') }}
</div>
<div class="mt-2 space-y-1.5">
@foreach ($acknowledgement['basis'] as $basis)
<div class="flex items-center justify-between gap-3 text-xs">
<span class="text-gray-500 dark:text-gray-400">{{ $basis['label'] }}</span>
<x-filament::badge :color="$basis['color']" size="sm">
{{ $basis['value'] }}
</x-filament::badge>
</div>
@endforeach
</div>
</div>
</div>
@if ($acknowledgement['acknowledged_at_label'] || $acknowledgement['acknowledged_by_label'])
<dl class="grid gap-2 rounded-lg border border-dashed border-gray-200 bg-gray-50 p-3 text-xs dark:border-white/10 dark:bg-white/5 sm:grid-cols-2">
@ -268,86 +224,162 @@ class="mt-2"
</p>
@endif
</div>
</div>
</div>
<div
class="rounded-xl border border-gray-200 bg-white p-4 shadow-sm dark:border-white/10 dark:bg-gray-900"
data-testid="customer-review-findings-summary"
>
<div class="flex flex-col gap-3">
<div class="flex flex-wrap items-start justify-between gap-3">
<div>
<h2 class="text-sm font-semibold text-gray-950 dark:text-white">
{{ __('localization.review.findings_needing_attention') }}
</h2>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-300">
{{ $findingPanel['summary'] }}
</p>
</div>
<x-filament::badge :color="$findingPanel['status_color']">
{{ $findingPanel['status_label'] }}
</x-filament::badge>
</div>
</div>
<div class="grid gap-2 sm:grid-cols-2 2xl:grid-cols-4">
@foreach ($findingPanel['items'] as $item)
<div class="flex items-center justify-between gap-3 rounded-lg border border-gray-200 px-3 py-2 text-xs dark:border-white/10">
<span class="text-gray-500 dark:text-gray-400">{{ $item['label'] }}</span>
<x-filament::badge :color="$item['color']" size="sm">
{{ $item['value'] }}
</x-filament::badge>
<div
class="rounded-xl border border-gray-200 bg-white p-4 shadow-sm dark:border-white/10 dark:bg-gray-900"
data-testid="customer-review-findings-summary"
>
<div class="flex flex-col gap-3">
<div class="flex flex-wrap items-start justify-between gap-3">
<div>
<h2 class="text-sm font-semibold text-gray-950 dark:text-white">
{{ __('localization.review.findings_needing_attention') }}
</h2>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-300">
{{ $findingPanel['summary'] }}
</p>
</div>
@endforeach
</div>
</div>
</div>
<x-filament::badge :color="$findingPanel['status_color']">
{{ $findingPanel['status_label'] }}
</x-filament::badge>
</div>
<div class="rounded-xl border border-gray-200 bg-white p-4 shadow-sm dark:border-white/10 dark:bg-gray-900">
<div class="flex flex-col gap-3">
<div>
<h2 class="text-sm font-semibold text-gray-950 dark:text-white">
{{ __('localization.review.customer_safe_follow_ups') }}
</h2>
</div>
<div class="grid gap-3 md:grid-cols-2">
@forelse ($followUps['entries'] as $followUp)
<div class="rounded-lg border border-gray-200 p-3 text-sm dark:border-white/10">
<div class="flex flex-wrap items-center gap-2">
<span class="font-medium text-gray-950 dark:text-white">{{ $followUp['title'] }}</span>
<x-filament::badge color="gray" size="sm">
{{ $followUp['priority'] }}
<div class="grid gap-2 sm:grid-cols-2 2xl:grid-cols-4">
@foreach ($findingPanel['items'] as $item)
<div class="flex items-center justify-between gap-3 rounded-lg border border-gray-200 px-3 py-2 text-xs dark:border-white/10">
<span class="text-gray-500 dark:text-gray-400">{{ $item['label'] }}</span>
<x-filament::badge :color="$item['color']" size="sm">
{{ $item['value'] }}
</x-filament::badge>
</div>
<div class="mt-1 text-gray-600 dark:text-gray-300">
{{ $followUp['summary'] }}
</div>
<div class="mt-1 text-xs text-gray-500 dark:text-gray-400">
{{ $followUp['proof'] }} · {{ $followUp['next_action'] }}
</div>
</div>
@empty
<p class="text-sm text-gray-600 dark:text-gray-300">
{{ $followUps['empty_state'] }}
</p>
@endforelse
@endforeach
</div>
</div>
</div>
</div>
</section>
<div class="space-y-3" data-testid="customer-review-package-index">
<div>
<h2 class="text-sm font-semibold text-gray-950 dark:text-white">
{{ __('localization.review.review_package_index') }}
</h2>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-300">
{{ __('localization.review.review_package_index_description') }}
</p>
<section class="space-y-4" data-testid="customer-review-supporting-details">
@php
$currentReadinessStep = collect($readinessFlow)->firstWhere('is_current', true);
@endphp
<details
class="group rounded-xl border border-gray-200 bg-white p-4 shadow-sm dark:border-white/10 dark:bg-gray-900"
data-testid="customer-review-readiness-flow"
>
<summary class="-m-2 cursor-pointer list-none rounded-lg p-2 marker:text-gray-400 transition hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 focus:ring-offset-white dark:hover:bg-white/5 dark:focus:ring-offset-gray-900">
<div class="flex flex-wrap items-start justify-between gap-3">
<div>
<h2 class="text-sm font-semibold text-gray-950 dark:text-white">
{{ __('localization.review.review_consumption_flow') }}
</h2>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-300">
{{ __('localization.review.review_consumption_flow_description') }}
</p>
</div>
<div class="flex items-start gap-3">
@if ($currentReadinessStep !== null)
<div class="shrink-0">
<div class="text-[0.7rem] font-semibold uppercase leading-4 tracking-wide text-gray-500 dark:text-gray-400">
{{ __('localization.review.current_attention_point') }}
</div>
<x-filament::badge :color="$currentReadinessStep['color']" size="sm" class="mt-1 max-w-full whitespace-normal text-left">
{{ $currentReadinessStep['label'] }}
</x-filament::badge>
</div>
@endif
<x-filament::icon
icon="heroicon-m-chevron-down"
class="mt-0.5 h-5 w-5 text-gray-400 transition-transform duration-150 group-open:rotate-180 dark:text-gray-500"
/>
</div>
</div>
</summary>
<ul class="mt-4 divide-y divide-gray-100 text-sm dark:divide-white/10">
@foreach ($readinessFlow as $step)
<li
class="py-3 first:pt-0 last:pb-0"
data-testid="customer-review-readiness-step"
data-step-label="{{ $step['title'] }}"
data-step-state="{{ $step['label'] }}"
data-step-current="{{ $step['is_current'] ? 'true' : 'false' }}"
>
<div class="flex flex-wrap items-start justify-between gap-3">
<div class="min-w-0">
<div class="font-medium text-gray-950 dark:text-white">
{{ $step['title'] }}
</div>
<p class="mt-1 text-xs leading-5 text-gray-600 dark:text-gray-300">
{{ $step['description'] }}
</p>
</div>
<div class="shrink-0 text-right">
<x-filament::badge :color="$step['color']" size="sm" class="max-w-full whitespace-normal text-left">
{{ $step['label'] }}
</x-filament::badge>
@if ($step['is_current'])
<div class="mt-1 text-xs font-medium text-gray-500 dark:text-gray-400">
{{ __('localization.review.current_attention_point') }}
</div>
@endif
</div>
</div>
</li>
@endforeach
</ul>
</details>
<div class="rounded-xl border border-gray-200 bg-white p-4 shadow-sm dark:border-white/10 dark:bg-gray-900">
<div class="flex flex-col gap-3">
<div>
<h2 class="text-sm font-semibold text-gray-950 dark:text-white">
{{ __('localization.review.customer_safe_follow_ups') }}
</h2>
</div>
<div class="grid gap-3 md:grid-cols-2">
@forelse ($followUps['entries'] as $followUp)
<div class="rounded-lg border border-gray-200 p-3 text-sm dark:border-white/10">
<div class="flex flex-wrap items-center gap-2">
<span class="font-medium text-gray-950 dark:text-white">{{ $followUp['title'] }}</span>
<x-filament::badge color="gray" size="sm">
{{ $followUp['priority'] }}
</x-filament::badge>
</div>
<div class="mt-1 text-gray-600 dark:text-gray-300">
{{ $followUp['summary'] }}
</div>
<div class="mt-1 text-xs text-gray-500 dark:text-gray-400">
{{ $followUp['proof'] }} · {{ $followUp['next_action'] }}
</div>
</div>
@empty
<p class="text-sm text-gray-600 dark:text-gray-300">
{{ $followUps['empty_state'] }}
</p>
@endforelse
</div>
</div>
</div>
{{ $this->table }}
</div>
<div class="space-y-3" data-testid="customer-review-package-index">
<div>
<h2 class="text-sm font-semibold text-gray-950 dark:text-white">
{{ __('localization.review.review_package_index') }}
</h2>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-300">
{{ __('localization.review.review_package_index_description') }}
</p>
</div>
{{ $this->table }}
</div>
</section>
</main>
<aside class="space-y-3 md:col-span-1 md:sticky md:top-4 md:self-start xl:col-span-4" data-testid="customer-review-evidence-aside">
@ -497,32 +529,6 @@ class="mt-3 divide-y divide-gray-100 border-t border-gray-200 pt-1 dark:divide-w
</div>
</div>
@endif
<details class="group mt-3 border-t border-gray-200 pt-3 dark:border-white/10">
<summary class="cursor-pointer text-xs font-semibold uppercase text-gray-500 marker:text-gray-400 dark:text-gray-400">
{{ __('localization.review.accepted_risk_records') }}
</summary>
<div class="mt-2 space-y-2">
@forelse ($acceptedRisks['entries'] as $risk)
<div class="text-xs">
<div class="flex flex-wrap items-center gap-2">
<span class="font-medium text-gray-900 dark:text-gray-100">{{ $risk['title'] }}</span>
<x-filament::badge color="gray" size="sm">
{{ $risk['state_label'] }}
</x-filament::badge>
</div>
<div class="mt-1 leading-5 text-gray-500 dark:text-gray-400">
{{ $risk['summary'] }}
</div>
</div>
@empty
<p class="text-xs leading-5 text-gray-500 dark:text-gray-400">
{{ $acceptedRisks['empty_state'] }}
</p>
@endforelse
</div>
</details>
</div>
<div class="rounded-xl border border-gray-200 bg-white p-3 shadow-sm dark:border-white/10 dark:bg-gray-900">

View File

@ -0,0 +1,131 @@
<?php
declare(strict_types=1);
use App\Filament\Pages\Reviews\CustomerReviewWorkspace;
use App\Models\EnvironmentReview;
use App\Models\EvidenceSnapshot;
use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Support\EnvironmentReviewStatus;
use App\Support\Workspaces\WorkspaceContext;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Storage;
uses(RefreshDatabase::class);
pest()->browser()->timeout(60_000);
beforeEach(function (): void {
Storage::fake('exports');
});
it('Spec344 smokes the customer review workspace hierarchy and density changes', function (): void {
[$user, $environment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
$environment->forceFill(['name' => 'Spec344 Browser Workspace'])->save();
$snapshot = seedEnvironmentReviewEvidence($environment, findingCount: 0, driftCount: 0);
spec344BrowserCreatePublishedReview($environment, $user, $snapshot);
spec344AuthenticateBrowser($this, $user, $environment);
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($environment))
->resize(1236, 900)
->waitForText(__('localization.review.review_acknowledgement'))
->assertSee(__('localization.review.review_consumption_flow'))
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
$page->assertScript('document.querySelector("[data-testid=\"customer-review-readiness-flow\"]")?.open === false', true);
$page->assertScript(
'(() => { const ack = document.querySelector("[data-testid=\"customer-review-acknowledgement-card\"]"); const flow = document.querySelector("[data-testid=\"customer-review-readiness-flow\"]"); if (!ack || !flow) return false; return ack.getBoundingClientRect().top < flow.getBoundingClientRect().top; })()',
true,
);
$page->script('window.scrollTo(0, 0);');
$page->screenshot(true, spec344BrowserScreenshotName('01-operator-summary'));
spec344CopyBrowserScreenshot('01-operator-summary');
$page->script('document.querySelector("[data-testid=\"customer-review-acknowledgement-card\"]")?.scrollIntoView({ block: "start" });');
$page->screenshot(true, spec344BrowserScreenshotName('02-acknowledgement-prominent'));
spec344CopyBrowserScreenshot('02-acknowledgement-prominent');
$page->script('document.querySelector("[data-testid=\"customer-review-readiness-flow\"]")?.scrollIntoView({ block: "start" });');
$page->assertScript('document.querySelector("[data-testid=\"customer-review-readiness-flow\"]")?.open === false', true);
$page->screenshot(true, spec344BrowserScreenshotName('03-supporting-details-demoted'));
spec344CopyBrowserScreenshot('03-supporting-details-demoted');
$page->script('document.querySelector("[data-testid=\"customer-review-diagnostics\"]")?.scrollIntoView({ block: "start" });');
$page->assertScript('document.querySelector("[data-testid=\"customer-review-diagnostics\"]")?.open === false', true);
$page->screenshot(true, spec344BrowserScreenshotName('04-diagnostics-collapsed'));
spec344CopyBrowserScreenshot('04-diagnostics-collapsed');
$page->script("document.documentElement.classList.add('dark');");
$page->script('window.scrollTo(0, 0);');
$page->assertScript('document.documentElement.classList.contains("dark")', true);
$page->screenshot(true, spec344BrowserScreenshotName('05-dark-mode'));
spec344CopyBrowserScreenshot('05-dark-mode');
});
function spec344BrowserScreenshotName(string $name): string
{
return 'spec344-customer-review-workspace-'.$name;
}
function spec344CopyBrowserScreenshot(string $name): void
{
$filename = spec344BrowserScreenshotName($name).'.png';
$source = base_path('tests/Browser/Screenshots/'.$filename);
$targetDirectory = repo_path('specs/344-customer-review-workspace-density-audience-polish/artifacts/screenshots');
if (! is_dir($targetDirectory)) {
@mkdir($targetDirectory, 0755, true);
}
if (! is_file($source)) {
$source = \Pest\Browser\Support\Screenshot::path($filename);
}
for ($attempt = 0; $attempt < 10 && ! is_file($source); $attempt++) {
usleep(100_000);
clearstatcache(true, $source);
}
if (is_file($source) && is_dir($targetDirectory) && is_writable($targetDirectory)) {
@copy($source, $targetDirectory.DIRECTORY_SEPARATOR.$name.'.png');
}
}
function spec344AuthenticateBrowser(mixed $test, User $user, ManagedEnvironment $environment): void
{
$workspaceId = (int) $environment->workspace_id;
$test->actingAs($user)->withSession([
WorkspaceContext::SESSION_KEY => $workspaceId,
WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY => [
(string) $workspaceId => (int) $environment->getKey(),
],
]);
session()->put(WorkspaceContext::SESSION_KEY, $workspaceId);
session()->put(WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY, [
(string) $workspaceId => (int) $environment->getKey(),
]);
setAdminPanelContext($environment);
}
function spec344BrowserCreatePublishedReview(ManagedEnvironment $environment, User $user, EvidenceSnapshot $snapshot): EnvironmentReview
{
$review = composeEnvironmentReviewForTest($environment, $user, $snapshot);
$review->forceFill([
'status' => EnvironmentReviewStatus::Published->value,
'generated_at' => now(),
'published_at' => now(),
'published_by_user_id' => (int) $user->getKey(),
])->save();
return $review->refresh();
}

View File

@ -0,0 +1,108 @@
<?php
declare(strict_types=1);
use App\Filament\Pages\Reviews\CustomerReviewWorkspace;
use App\Models\EnvironmentReview;
use App\Models\EvidenceSnapshot;
use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Support\EnvironmentReviewStatus;
use App\Support\Workspaces\WorkspaceContext;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
uses(RefreshDatabase::class);
it('prioritizes acknowledgement inside the operator summary before supporting details', function (): void {
$environment = ManagedEnvironment::factory()->create(['name' => 'Spec344 Operator Summary']);
[$user, $environment] = createUserWithTenant(tenant: $environment, role: 'owner', workspaceRole: 'manager');
$snapshot = seedEnvironmentReviewEvidence($environment, findingCount: 0, driftCount: 0);
spec344CreatePublishedReview($environment, $user, $snapshot);
$this->actingAs($user);
setAdminPanelContext();
session()->put(WorkspaceContext::SESSION_KEY, (int) $environment->workspace_id);
$component = Livewire::actingAs($user)->test(CustomerReviewWorkspace::class)
->assertSee(__('localization.review.review_acknowledgement'));
$html = $component->html();
expect($html)->toContain('data-testid="customer-review-operator-summary"')
->and($html)->toContain('data-testid="customer-review-supporting-details"');
$operatorSummaryStart = strpos($html, 'data-testid="customer-review-operator-summary"');
$supportingDetailsStart = strpos($html, 'data-testid="customer-review-supporting-details"');
expect($operatorSummaryStart)->not->toBeFalse()
->and($supportingDetailsStart)->not->toBeFalse()
->and($operatorSummaryStart)->toBeLessThan($supportingDetailsStart);
$operatorSummaryHtml = substr($html, $operatorSummaryStart, $supportingDetailsStart - $operatorSummaryStart);
expect($operatorSummaryHtml)->toContain('data-testid="customer-review-decision-card"')
->and($operatorSummaryHtml)->toContain('data-testid="customer-review-acknowledgement-card"')
->and($operatorSummaryHtml)->toContain('data-testid="customer-review-findings-summary"');
$acknowledgementCardPosition = strpos($html, 'data-testid="customer-review-acknowledgement-card"');
$readinessFlowPosition = strpos($html, 'data-testid="customer-review-readiness-flow"');
expect($acknowledgementCardPosition)->not->toBeFalse()
->and($readinessFlowPosition)->not->toBeFalse()
->and($acknowledgementCardPosition)->toBeLessThan($readinessFlowPosition);
expect($html)->toContain('data-testid="customer-review-diagnostics"')
->and($html)->not->toContain('data-testid="customer-review-diagnostics" open');
});
it('shows the environment filter chip when environment_id is present', function (): void {
$environment = ManagedEnvironment::factory()->create(['name' => 'Spec344 Environment Filter']);
[$user, $environment] = createUserWithTenant(tenant: $environment, role: 'owner', workspaceRole: 'manager');
$snapshot = seedEnvironmentReviewEvidence($environment, findingCount: 0, driftCount: 0);
spec344CreatePublishedReview($environment, $user, $snapshot);
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $environment->workspace_id])
->get(CustomerReviewWorkspace::environmentFilterUrl($environment))
->assertOk()
->assertSee('Environment filter:')
->assertSee('Spec344 Environment Filter')
->assertDontSee('/admin/t', false)
->assertDontSee('tenant_id=', false);
});
it('disables the acknowledgement action when the actor lacks acknowledge permissions', function (): void {
$environment = ManagedEnvironment::factory()->create(['name' => 'Spec344 Ack Disabled']);
[$publisher, $environment] = createUserWithTenant(tenant: $environment, role: 'owner', workspaceRole: 'manager');
[$actor] = createUserWithTenant(tenant: $environment, role: 'readonly');
$snapshot = seedEnvironmentReviewEvidence($environment, findingCount: 0, driftCount: 0);
spec344CreatePublishedReview($environment, $publisher, $snapshot);
$this->actingAs($actor);
setAdminPanelContext();
session()->put(WorkspaceContext::SESSION_KEY, (int) $environment->workspace_id);
$component = Livewire::actingAs($actor)->test(CustomerReviewWorkspace::class);
$payload = $component->instance()->latestReviewConsumptionPayload();
expect(data_get($payload, 'acknowledgement.action_name'))->toBe('acknowledgeReview')
->and(data_get($payload, 'acknowledgement.action_disabled'))->toBeTrue();
});
function spec344CreatePublishedReview(ManagedEnvironment $environment, User $user, EvidenceSnapshot $snapshot): EnvironmentReview
{
$review = composeEnvironmentReviewForTest($environment, $user, $snapshot);
$review->forceFill([
'status' => EnvironmentReviewStatus::Published->value,
'generated_at' => now(),
'published_at' => now(),
'published_by_user_id' => (int) $user->getKey(),
])->save();
return $review->refresh();
}

View File

@ -15,9 +15,11 @@ ## First Five Seconds
This is the most important customer-safe productization candidate. The page should answer what the customer can trust, what changed, what risks are accepted, which evidence supports the state, and what should happen next.
Spec 344 tightens the hierarchy so the Operator Summary (decision + acknowledgement + findings signal) comes first, while the review consumption flow and proof panels remain available as supporting details.
## Productization Review
- Decision-first: strong candidate, needs final target hierarchy.
- Decision-first: improved by explicit Operator Summary-first hierarchy.
- Evidence-first: must anchor all claims to review/evidence artifacts.
- Context: workspace-level customer view.
- Customer/auditor safety: primary concern.
@ -35,14 +37,14 @@ ## Scores
| IA | Density | User Clarity | Sellability | Disclosure | Hierarchy | DS Fit | A11y | Responsive | Components | UX Writing | Perf |
| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| 3 | 3 | 3 | 5 | 3 | 3 | 4 | 3 | 3 | 4 | 3 | 4 |
| 3 | 4 | 4 | 5 | 3 | 4 | 4 | 3 | 3 | 4 | 3 | 4 |
## Top Issues
1. Acknowledgement copy must remain customer-safe and explicitly non-legal (no compliance certification semantics).
2. Evidence and accepted-risk meaning should be visible without raw diagnostics.
3. Requires individual target mockup, not only cleanup.
3. Sidebar proof panels can still compete visually with the main decision flow; keep them secondary and avoid duplicating “ready/available” signals at equal weight.
## Target Direction
P0 individual target mockup and follow-up implementation wave. This page is a core sellability surface.
Spec 344 implements the first density/hierarchy polish wave. If the surface still feels too dense after real operator use, follow up with a targeted mockup and a second, narrower polish pass rather than adding new workflow surfaces.

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 KiB

View File

@ -0,0 +1,48 @@
# Specification Quality Checklist: Spec 344 - Customer Review Workspace Density & Audience Mode Polish
**Purpose**: Validate Spec 344 preparation completeness before implementation.
**Created**: 2026-06-01
**Feature**: `specs/344-customer-review-workspace-density-audience-polish/spec.md`
## Candidate Selection Gate
- [x] CHK001 The selected candidate is directly provided by the user as Spec 344 (follow-up after Specs 342343).
- [x] CHK002 The candidate aligns with current roadmap direction: customer-safe review consumption and operator workflow maturity on UI-038 (no portal/framework rewrite).
- [x] CHK003 No existing `specs/344-*` package or `344-*` branch was found before this prep.
- [x] CHK004 Related specs were checked for completed-spec signals and are treated as context only (337, 342, 343).
- [x] CHK005 Close alternatives are deferred rather than hidden scope (governance inbox follow-through, localization adoption, provider readiness, PSA handoff).
- [x] CHK006 Scope is narrowed to one strategic surface (`/admin/reviews/workspace`) and UI-only hierarchy changes.
## Content Quality
- [x] CHK007 `spec.md` defines problem, user value, functional requirements, non-goals, acceptance criteria, assumptions, risks, and open questions.
- [x] CHK008 `plan.md` lists likely affected repo surfaces and preserves repo-truth-first execution.
- [x] CHK009 `tasks.md` is ordered into small phases with explicit test/browser/screenshot/validation tasks.
- [x] CHK010 No unresolved template placeholders remain in `spec.md`, `plan.md`, or `tasks.md`.
## Constitution And Scope
- [x] CHK011 Proportionality review is present and explicitly rejects new persistence, new status families, and new frameworks.
- [x] CHK012 Workspace/environment isolation boundaries and deny-as-not-found posture are preserved (no authorization weakening).
- [x] CHK013 UI Surface Impact and UI/Productization Coverage are completed for UI-038 (strategic surface).
- [x] CHK014 Filament v5 / Livewire v4 posture, panel provider location, destructive-action confirmation rules, asset strategy, and testing plan are explicit.
## Plan Quality
- [x] CHK015 Plan sequencing is baseline inventory → hierarchy refactor → keep Spec 343 semantics → tests/browser → UI audit artifact decision.
- [x] CHK016 Deployment/ops impact is explicit (no env/migrations/queues/scheduler/assets expected).
- [x] CHK017 No Graph/provider calls during UI render are enforced by plan constraints.
## Task Quality
- [x] CHK018 Tasks include concrete repo surfaces and avoid inventing new runtime paths beyond UI-038 touch points.
- [x] CHK019 Tasks include Feature/Livewire tests and one bounded Browser smoke (strategic surface).
- [x] CHK020 Tasks include screenshot artifacts and “unreachable state” handling without faking backend truth.
- [x] CHK021 Tasks explicitly forbid new domain model/persistence and forbid rewriting completed specs.
## Spec Readiness Gate
- [x] CHK022 `spec.md`, `plan.md`, and `tasks.md` exist.
- [x] CHK023 Open questions do not block safe implementation.
- [x] CHK024 Scope is bounded enough for a later implementation loop.
- [x] CHK025 Result: ready for implementation loop.

View File

@ -0,0 +1,96 @@
# Implementation Plan: Spec 344 - Customer Review Workspace Density & Audience Mode Polish
**Branch**: `344-customer-review-workspace-density-audience-polish` | **Date**: 2026-06-01 | **Spec**: `specs/344-customer-review-workspace-density-audience-polish/spec.md`
**Input**: User-provided Spec 344 draft (Codex attachment `pasted-text.txt`) + repo truth from Specs 337, 342, 343.
## Summary
Improve the Customer Review Workspace (UI-038) as a daily MSP/operator surface by reducing density and restoring decision hierarchy:
1) **Operator Summary first**: shareability + acknowledgement + primary next action + risk/findings signal.
2) **Supporting details second**: evidence/proof path + review pack state + accepted risks + diagnostics + package index.
This slice is UI/productization polish only:
- preserve all semantics from Specs 342343
- no new persistence or status families
- no new routes or navigation changes
- no Graph/provider calls during render
## Technical Context
- **Language/Version**: PHP 8.4.15, Laravel 12.x.
- **UI**: Filament v5 + Livewire v4 (no Livewire v3 APIs).
- **Panel providers**: remain registered in `apps/platform/bootstrap/providers.php` (no changes expected).
- **Storage**: PostgreSQL (no migrations expected).
- **Testing**: Pest Feature/Livewire tests + one bounded Pest Browser smoke test for this strategic surface.
- **Assets**: no new global assets expected; prefer Filament primitives and Tailwind utilities already in the stack.
## UI / Surface Guardrail Plan
- **Guardrail scope**: material change to an existing strategic customer-safe review surface (UI-038).
- **Primary surface**:
- `apps/platform/app/Filament/Pages/Reviews/CustomerReviewWorkspace.php`
- `apps/platform/resources/views/filament/pages/reviews/customer-review-workspace.blade.php`
- **New/changed surface elements**:
- explicit “Operator Summary” zone (layout + grouping)
- reordering so acknowledgement sits inside the primary decision area when required
- de-duplication and compression of “ready/available/no action needed” signals
- **Disclosure defaults**: diagnostics remain collapsed/secondary and capability-gated (`support_diagnostics.view`).
- **One primary next action**: preserve exactly one dominant next action in the Operator Summary; demote secondary navigation/inspect links.
- **UI audit artifacts**: decide post-diff whether `docs/ui-ux-enterprise-audit/page-reports/ui-006-customer-review-workspace.md` needs updating; route inventory entry (UI-038) should remain stable.
## Repo-Truth-First Execution Rule
Before any refactor, confirm the current runtime surface inventory:
- which cards/sections exist today and what they mean (no invented semantics)
- where acknowledgement, accepted-risk, evidence path, review pack status, disclosure rules, and diagnostics currently render
- which sections are redundant “green noise” vs required evidence/proof state
## Implementation Phases
### Phase 1 — Baseline Inventory + Before/After Proof
- Capture baseline screenshots for the current hierarchy (pre-change).
- Record a short inventory note in the PR description (later), not as a new framework or test snapshot.
### Phase 2 — Layout/Hiearchy Refactor (UI-only)
- Implement the two-layer structure: Operator Summary vs Supporting Details.
- Move acknowledgement into the Operator Summary hierarchy when required.
- Group or demote readiness-only cards so they do not compete with the pending action.
- Preserve environment filter semantics and visibility (local page filter, not global context).
- Preserve customer-safe wording and avoid “legal/compliance certification” phrasing.
### Phase 3 — Preserve Spec 343 Semantics
- Acknowledgement remains:
- capability-gated
- confirmation-gated
- auditable
- behavior-identical (only placement changes)
### Phase 4 — Tests (Pest)
- Keep Spec 343 tests passing; update only assertions that intentionally depend on hierarchy markers.
- Add Spec 344 Feature/Livewire tests focusing on:
- Operator Summary markers exist
- acknowledgement action visibility in the primary zone when required
- diagnostics remain capability-gated and collapsed/secondary
- environment filter remains visible and page-local
- Add one Spec 344 Browser smoke to prove scan-first hierarchy + screenshots.
### Phase 5 — UI Coverage Artifacts (post-diff decision)
- If the layout materially changes what the UI-006 page report describes, update:
- `docs/ui-ux-enterprise-audit/page-reports/ui-006-customer-review-workspace.md`
- Otherwise record a concise “no update needed” note in the active feature PR close-out entry.
## Deployment / Ops Impact
- **Env vars**: none.
- **Migrations**: none expected.
- **Queues/scheduler**: none.
- **Storage/volumes**: none.
- **Filament assets**: not expected; if any registered assets are introduced, include `cd apps/platform && php artisan filament:assets` in deployment/release steps.

View File

@ -0,0 +1,195 @@
# Feature Specification: Spec 344 - Customer Review Workspace Density & Audience Mode Polish
**Feature Branch**: `344-customer-review-workspace-density-audience-polish`
**Created**: 2026-06-01
**Status**: Draft
**Type**: Productization / UX hierarchy / MSP operator workflow polish
**Runtime posture**: UI/productization polish only. Preserve Spec 343 acknowledgement behavior and Customer Review Workspace semantics. Do not introduce new domain models, persisted truth, or status families unless repo truth proves a minimal derived view state is unavoidable (derived-only, not persisted).
**Input**: User-provided Spec 344 draft (Codex attachment `pasted-text.txt`) + repo truth from Specs 337, 342, 343 + current `CustomerReviewWorkspace` UI.
## Spec Candidate Check *(mandatory — SPEC-GATE-001)*
- **Problem**: The Customer Review Workspace (UI-038) is now functionally strong (Specs 342343), but visually dense. Operator decision state, customer-safe sharing state, acknowledgement state, evidence path, review pack state, accepted-risk state, disclosure rules, diagnostics, and package index compete at equal weight.
- **Today's failure**: An operator must scan too many regions before answering the first questions: “Is this review shareable?”, “What action is required now?”, “Is anything risky or blocking?”, “Where is the evidence/proof path?”, “What details can I inspect if needed?”
- **User-visible improvement**: The first screen becomes scan-first and decision-first: one Operator Summary zone with shareability + acknowledgement + primary next action + risk/findings signal, and a clear Supporting Details layer for proof/diagnostics/index.
- **Smallest enterprise-capable version**: Refactor only the existing `/admin/reviews/workspace` layout and hierarchy. Preserve all existing truth sources and actions; change ordering, grouping, and repetition only.
- **Explicit non-goals**: No new customer portal, no new persisted view state, no new domain model, no new review pack format, no new evidence generation, no new accepted-risk lifecycle semantics, no new OperationRun types, no new navigation/shell redesign, no localization project, no “legal sign-off” semantics.
- **Permanent complexity imported**: Targeted UI refactor on one page + small presenter extraction only if needed to prevent duplicated logic, plus focused Feature/Livewire tests and one bounded Browser smoke + screenshots. No migrations, no packages, no new global UI framework.
- **Why now**: Spec 343 added acknowledgement and tightened accepted-risk visibility; the page is v1-usable but now too dense for daily MSP/operator workflows. Productization polish improves operator velocity and reduces false calmness by making the next action unavoidable.
- **Why not local**: Small cosmetic changes cannot fix the decision hierarchy problem; this must be an intentional “Operator Summary first” re-layout to prevent drift and duplicated truth across cards.
- **Approval class**: Workflow Compression.
- **Red flags triggered**: Customer-safe strategic surface changes and risk of “false calmness” if hierarchy is wrong. Defense: preserve truth sources and semantics; keep diagnostics/proof as supporting details; require browser screenshots + targeted tests.
- **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 2 | Komplexität: 1 | Produktnähe: 2 | Wiederverwendung: 1 | **Gesamt: 10/12**
- **Decision**: approve.
## Candidate Source And Completed-Spec Guardrail
- **Candidate source**: Directly user-provided as “Spec 344 — Customer Review Workspace Density & Audience Mode Polish” as the next slice after Spec 343.
- **Completed-spec check**: No `specs/344-*` package existed before this prep. Related Specs 337, 342, and 343 contain completed-task, validation, and/or close-out signals and are treated as historical context only (do not rewrite or normalize them).
- **Roadmap alignment**: Stays inside the “Customer Review Workspace v1 completion / productization” lane by improving operator decision hierarchy and customer-safe disclosure without creating a parallel customer portal.
- **Close alternatives deferred**:
- Decision-based Governance Inbox follow-through (separate strategic workflow surface; defer to keep this slice UI-only on UI-038).
- Customer-facing localization adoption (needs stable hierarchy first).
- Provider readiness / monitoring maturity (unrelated).
- External support desk / PSA handoff (unrelated).
## Spec Scope Fields *(mandatory)*
- **Scope**: workspace hub (existing strategic surface UI-038) with page-level `environment_id` filter.
- **Primary Routes**:
- `/admin/reviews/workspace` (Customer Review Workspace)
- **Data Ownership**: no new persistence; no migrations.
- **RBAC**:
- Preserve existing membership + capability gates for view and for acknowledgement write (Spec 343).
- Diagnostics remain capability-gated (`support_diagnostics.view`).
## UI Surface Impact *(mandatory — UI-COV-001)*
- [ ] No UI surface impact
- [x] Existing page changed
- [ ] New page/route added
- [ ] Navigation changed
- [ ] Filament panel/provider surface changed
- [ ] New modal/drawer/wizard/action added
- [x] New table/form/state added
- [x] Customer-facing surface changed
- [ ] Dangerous action changed
- [x] Status/evidence/review presentation changed
- [x] Workspace/environment context presentation changed
## UI/Productization Coverage *(mandatory)*
- **Route/page/surface**: `/admin/reviews/workspace` (`apps/platform/app/Filament/Pages/Reviews/CustomerReviewWorkspace.php` + `apps/platform/resources/views/filament/pages/reviews/customer-review-workspace.blade.php`)
- **Current page archetype**: `docs/ui-ux-enterprise-audit/route-inventory.md` → UI-038 (Strategic Surface, repo-verified)
- **Design depth**: Strategic Surface (operator/MSP surface that produces customer-safe output)
- **Repo-truth level**: repo-verified surface; no new truth sources
- **Existing pattern reused**: Spec 342 decision-first hierarchy + Spec 343 acknowledgement card/action + existing customer-safe disclosure + existing evidence/proof layout primitives
- **New pattern required**: none; only regrouping and de-duplication + one explicit “Operator Summary” layer marker
- **Screenshot required**: yes (`specs/344-customer-review-workspace-density-audience-polish/artifacts/screenshots/`)
- **Customer-safe review required**: yes (copy + disclosure + prevent false “ready/available” duplication)
- **Dangerous-action review required**: no (no new destructive/high-impact actions; acknowledgement remains confirmed + capability-gated per Spec 343)
- **Coverage artifacts**: decide post-diff whether `docs/ui-ux-enterprise-audit/page-reports/ui-006-customer-review-workspace.md` needs an update; route inventory entry should remain stable.
## Cross-Cutting / Shared Pattern Reuse *(mandatory)*
- **Cross-cutting feature?**: yes.
- **Interaction classes**: status messaging, action hierarchy, proof/evidence disclosure, diagnostics collapse, accepted-risk summary.
- **Shared paths reused**:
- Existing customer-safe disclosure language and copy discipline from Specs 342343 (no legal/compliance certification wording).
- Existing badge/status primitives (no ad-hoc “ready/available” duplication).
- Existing acknowledgement action semantics from Spec 343 (capability + confirmation + audit).
- **Deviations**: none intended. If a new presenter/state helper is introduced to reduce duplicated UI logic, it must remain page-local and derived-only.
## OperationRun UX Impact *(mandatory)*
N/A - no OperationRun creation/completion/dedupe semantics are introduced or changed in this slice.
## Provider Boundary / Platform Core Check *(mandatory)*
N/A - no provider/platform shared boundary is changed.
## Proportionality Review *(mandatory when structural complexity is introduced)*
- **New source of truth?**: no.
- **New persisted entity/table/artifact?**: no.
- **New abstraction?**: no new framework. Optional: extract a page-local derived “summary view state” helper only if it reduces duplicated truth and keeps copy/status consistent.
- **New enum/status family?**: no. All displayed states remain derived from existing review/pack/evidence/acknowledgement/accepted-risk truth.
- **Alternative intentionally rejected**: audience-mode toggle framework, separate customer portal page, new review readiness engine, new accepted-risk workflow surface.
## Testing / Lane / Runtime Impact *(mandatory for runtime behavior changes)*
- **Test purpose / classification**: Feature/Livewire for action visibility + RBAC + disclosure defaults; Browser for scan-first hierarchy and “acknowledgement is visible before supporting details” on a strategic surface.
- **Validation lane(s)**: confidence + browser.
- **Planned validation commands** (later implementation):
- `cd apps/platform && ./vendor/bin/sail artisan test tests/Feature/Filament/Spec343CustomerReviewAttestationAcceptedRiskTest.php --compact`
- `cd apps/platform && ./vendor/bin/sail php vendor/bin/pest tests/Browser/Spec343CustomerReviewAttestationAcceptedRiskSmokeTest.php --compact`
- `cd apps/platform && ./vendor/bin/sail artisan test tests/Feature/Filament/Spec344CustomerReviewWorkspaceDensityTest.php --compact`
- `cd apps/platform && ./vendor/bin/sail php vendor/bin/pest tests/Browser/Spec344CustomerReviewWorkspaceDensitySmokeTest.php --compact`
- `cd apps/platform && ./vendor/bin/sail pint --dirty`
- `git diff --check`
## User Scenarios & Testing *(mandatory)*
### User Story 1 — Understand the main decision quickly (Priority: P1)
An MSP/operator opens Customer Review Workspace and immediately sees shareability status, acknowledgement status, and the one primary next action without scanning multiple equal-weight cards.
**Independent proof**: browser smoke + screenshots showing the Operator Summary zone in realistic fixtures.
### User Story 2 — Acknowledgement appears before supporting details (Priority: P1)
If acknowledgement is required, it appears in the primary decision area (or directly beneath it) and never below secondary proof/flow sections.
**Independent proof**: Livewire test asserts acknowledgement action is present and visible in the Operator Summary; browser smoke confirms hierarchy.
### User Story 3 — Supporting details remain available but secondary (Priority: P2)
Evidence path, review pack state, accepted-risk detail lists, disclosure rules, diagnostics, and the package index remain accessible but are visually secondary and/or progressively disclosed.
**Independent proof**: browser smoke shows details as secondary/collapsed; Livewire test confirms diagnostics remain capability-gated.
## Functional Requirements
- **FR-001**: The page must have two clear layers:
1) **Operator Summary** (decision + acknowledgement + risks/findings + primary next action)
2) **Supporting Details** (proof/evidence path + pack state + accepted risks + diagnostics + index)
- **FR-002**: Acknowledgement must appear in (1) when required.
- **FR-003**: Repetitive “ready/available/no action needed” blocks must be reduced, grouped, or demoted so they do not compete with the true pending action.
- **FR-004**: Accepted-risk and findings signals must not be duplicated in multiple equal-weight regions without distinct purpose.
- **FR-005**: Diagnostics remain collapsed/secondary by default and capability-gated.
- **FR-006**: Environment filter remains a local page filter and stays visible; no new “topbar-as-filter” guidance is introduced.
## Non-Functional Requirements
- Page render remains DB-only: no Graph/provider calls during UI render.
- Preserve Filament v5 + Livewire v4.0+ compliance.
- Keep panel provider registration unchanged in `apps/platform/bootstrap/providers.php`.
- Avoid new frontend assets; if any asset registration becomes necessary, document `php artisan filament:assets`.
## Out Of Scope
- No new portal routes, no audience-mode toggle framework, no new persisted preferences.
- No new review readiness engine, no new accepted-risk workflow surface, no lifecycle or taxonomy rewrite.
- No changes to acknowledgement semantics introduced by Spec 343 (capability/confirmation/audit behavior stays intact).
## Acceptance Criteria
- [ ] AC-001 Operator Summary answers (in order): shareability, acknowledgement, primary next action, blockers/risks, and latest review context.
- [ ] AC-002 If acknowledgement is required, the action is visible before supporting details (no scroll needed).
- [ ] AC-003 Duplicate “ready/available/no action needed” cards are reduced or grouped so the pending action stands out.
- [ ] AC-004 Evidence path remains visible but secondary; diagnostics remain collapsed/secondary.
- [ ] AC-005 Environment filter remains visible and local; the page does not imply global context.
- [ ] AC-006 Spec 343 acknowledgement behavior is preserved end-to-end.
- [ ] AC-007 Tests pass (targeted Spec 343 + Spec 344 feature + browser lanes).
## Success Criteria
- An operator can answer “what do I do now?” within a few seconds on first load.
- Customer-safe claims remain truthful and do not become “green noise”.
- Supporting proof paths remain accessible without competing as primary decision content.
## Assumptions
- The current strategic surface remains `UI-038 /admin/reviews/workspace` and uses the existing page class + Blade view.
- Spec 343 acknowledgement is already repo-real and covered by targeted tests.
- No additional backend truth is needed to improve hierarchy (UI-only slice).
## Open Questions
- OQ-001: Should the “Supporting Details” layer be purely collapsed-by-default, or should it remain visible but visually demoted? (Decision belongs to implementation with screenshots; either is acceptable if diagnostics remain secondary.)
## Spec Artifacts *(required for this package)*
- `specs/344-customer-review-workspace-density-audience-polish/spec.md`
- `specs/344-customer-review-workspace-density-audience-polish/plan.md`
- `specs/344-customer-review-workspace-density-audience-polish/tasks.md`
- `specs/344-customer-review-workspace-density-audience-polish/checklists/requirements.md`
- `specs/344-customer-review-workspace-density-audience-polish/artifacts/screenshots/`
## Follow-Up Spec Candidates
- Decision-based Governance Inbox follow-through (separate surface; keep numbering flexible).
- Customer Review External Portal v1 (separate customer-facing surface, not the operator workspace).
- Customer Review Mode Toggle (only if one page cannot serve operator vs customer-summary needs).
- Review Package Index productization (timeline/history focus).
- Accepted Risk lifecycle detail surface (only if accepted-risk records need their own workflow).

View File

@ -0,0 +1,78 @@
# Tasks: Spec 344 - Customer Review Workspace Density & Audience Mode Polish
**Branch**: `344-customer-review-workspace-density-audience-polish` | **Date**: 2026-06-01
**Spec**: `specs/344-customer-review-workspace-density-audience-polish/spec.md`
**Plan**: `specs/344-customer-review-workspace-density-audience-polish/plan.md`
## Test Governance (TEST-GOV-001)
- **Test purpose / classification**: Feature/Livewire + Browser (strategic surface UI-038).
- **Validation lanes**: confidence + browser.
- **Why sufficient**: this slice changes scan-first hierarchy on a strategic surface; browser proof is required while Feature/Livewire tests protect RBAC + disclosure defaults + action availability.
## Phase 0 — Guardrails + Baseline
- [x] T010 Confirm this slice is UI-only: no persistence, no new status family, no new routes.
- [x] T011 Capture representative screenshots for UI-038 hierarchy under `specs/344-customer-review-workspace-density-audience-polish/artifacts/screenshots/` (pre-change baseline optional; post-change required).
- [x] T012 Re-read Specs 342343 close-out signals to avoid reopening semantics and to preserve acknowledgement behavior.
## Phase 1 — Repo-Truth Inventory (before code changes)
- [x] T020 Inventory current visible regions/cards on Customer Review Workspace and identify:
- which regions are true blockers vs redundant “green noise”
- which regions are operator decision vs supporting proof vs diagnostics/support
- [x] T021 Record the intended before/after hierarchy in the PR description (later) and keep the change bounded to UI-038.
## Phase 2 — UI Refactor (Customer Review Workspace)
- [x] T030 Implement an explicit **Operator Summary** zone on `/admin/reviews/workspace`.
- [x] T031 Move acknowledgement (Spec 343) into the primary decision hierarchy when required (no behavior change; placement only).
- [x] T032 Reduce or group duplicate “ready/available/no action needed” blocks so pending actions dominate.
- [x] T033 Keep Evidence Path visible but secondary; avoid competing primary card weight.
- [x] T034 Keep diagnostics collapsed/secondary and capability-gated (`support_diagnostics.view`).
- [x] T035 Keep `environment_id` filter visible and page-local; avoid “topbar as filter” copy or implied global context.
## Phase 3 — Feature/Livewire Tests (Pest)
- [x] T040 Add `apps/platform/tests/Feature/Filament/Spec344CustomerReviewWorkspaceDensityTest.php` covering:
- Operator Summary markers present
- acknowledgement action visible in the summary zone when required (and disabled when unauthorized)
- diagnostics remain capability-gated and not default-prominent
- environment filter visibility and canonical query behavior
- [x] T041 Keep Spec 343 tests passing; update only assertions that intentionally depend on hierarchy markers.
## Phase 4 — Browser Smoke + Screenshots
- [x] T050 Add `apps/platform/tests/Browser/Spec344CustomerReviewWorkspaceDensitySmokeTest.php` proving:
- Operator Summary is first-screen
- acknowledgement appears before supporting details when required
- diagnostics are collapsed/secondary by default
- [x] T051 Capture screenshots under `specs/344-customer-review-workspace-density-audience-polish/artifacts/screenshots/`:
- `01-operator-summary.png`
- `02-acknowledgement-prominent.png`
- `03-supporting-details-demoted.png`
- `04-diagnostics-collapsed.png`
- `05-dark-mode.png` (if practical)
- [x] T052 If a screenshot state is unreachable, document why in the spec package instead of inventing backend truth.
## Phase 5 — UI Coverage Artifacts (post-diff decision)
- [x] T060 Decide whether `docs/ui-ux-enterprise-audit/page-reports/ui-006-customer-review-workspace.md` requires an update due to hierarchy changes.
- [x] T061 If required, update that page report; otherwise record a concise “no update needed” note in the active feature PR close-out entry.
## Phase 6 — Validation
- [x] T070 Run:
- `cd apps/platform && ./vendor/bin/sail artisan test tests/Feature/Filament/Spec343CustomerReviewAttestationAcceptedRiskTest.php --compact`
- `cd apps/platform && ./vendor/bin/sail php vendor/bin/pest tests/Browser/Spec343CustomerReviewAttestationAcceptedRiskSmokeTest.php --compact`
- `cd apps/platform && ./vendor/bin/sail artisan test tests/Feature/Filament/Spec344CustomerReviewWorkspaceDensityTest.php --compact`
- `cd apps/platform && ./vendor/bin/sail php vendor/bin/pest tests/Browser/Spec344CustomerReviewWorkspaceDensitySmokeTest.php --compact`
- `cd apps/platform && ./vendor/bin/sail pint --dirty`
- `git diff --check`
## Explicit Non-Goals
- [x] NT001 Do not introduce a new domain model, new persisted view state, or new status family.
- [x] NT002 Do not change acknowledgement semantics (capability/confirmation/audit) from Spec 343.
- [x] NT003 Do not add a customer portal, audience-mode toggle framework, or new navigation shell.
- [x] NT004 Do not add new global frontend assets unless explicitly justified and documented.