From 50bc44cfa07e74cd9dfa93eac289213a28fd73eb Mon Sep 17 00:00:00 2001 From: ahmido Date: Tue, 5 May 2026 15:40:52 +0000 Subject: [PATCH] Merge 269-operationrun-terminal-outcome-feedback into platform-dev (#331) Automated PR created via Copilot per user request: merge current branch into platform-dev. Co-authored-by: Ahmed Darrazi Reviewed-on: https://git.cloudarix.de/ahmido/TenantAtlas/pulls/331 --- .../bulk-operation-progress.blade.php | 7 +- .../OperationActivityFeedbackSmokeTest.php | 86 +++++- .../OpsUx/ActivityFeedbackSurfaceTest.php | 94 ++++++- .../OpsUx/BulkOperationProgressDbOnlyTest.php | 7 +- .../ui/tenantpilot-enterprise-ui-standards.md | 15 +- .../checklists/requirements.md | 56 ++++ .../plan.md | 260 ++++++++++++++++++ .../spec.md | 237 ++++++++++++++++ .../tasks.md | 159 +++++++++++ 9 files changed, 903 insertions(+), 18 deletions(-) create mode 100644 specs/269-operationrun-terminal-outcome-feedback/checklists/requirements.md create mode 100644 specs/269-operationrun-terminal-outcome-feedback/plan.md create mode 100644 specs/269-operationrun-terminal-outcome-feedback/spec.md create mode 100644 specs/269-operationrun-terminal-outcome-feedback/tasks.md diff --git a/apps/platform/resources/views/livewire/bulk-operation-progress.blade.php b/apps/platform/resources/views/livewire/bulk-operation-progress.blade.php index 2be47955..60bcef5d 100644 --- a/apps/platform/resources/views/livewire/bulk-operation-progress.blade.php +++ b/apps/platform/resources/views/livewire/bulk-operation-progress.blade.php @@ -17,6 +17,7 @@ $hasActiveVisibleRuns && $hasTerminalFollowUpVisibleRuns => 'Active and recent operation updates that may need review.', $hasActiveVisibleRuns => 'Queued and running work stays here until diagnostics are needed.', $hasTerminalFollowUpVisibleRuns => 'Recent operation updates that may need review.', + $hasTerminalVisibleRuns => 'Successful operation updates stay briefly visible so you can confirm completion and keep working.', default => 'Recent operation updates.', }; $primaryActionUrl = null; @@ -27,9 +28,9 @@ $primaryActionUrl = \App\Support\OpsUx\OperationRunUrl::view($primaryRun, $tenant); } - $tertiaryActionLabel = $hasActiveVisibleRuns - ? 'Hide activity' - : ($hasTerminalFollowUpVisibleRuns ? ($visibleRunCount > 1 ? 'Dismiss updates' : 'Dismiss') : 'Dismiss'); + $tertiaryActionLabel = $hasTerminalFollowUpVisibleRuns + ? 'Acknowledge' + : ($hasActiveVisibleRuns ? 'Hide activity' : 'Dismiss'); @endphp {{-- Cleanup is delegated to the shared poller helper, which uses teardownObserver and new MutationObserver. --}} diff --git a/apps/platform/tests/Browser/OpsUx/OperationActivityFeedbackSmokeTest.php b/apps/platform/tests/Browser/OpsUx/OperationActivityFeedbackSmokeTest.php index fe679e00..08814fc5 100644 --- a/apps/platform/tests/Browser/OpsUx/OperationActivityFeedbackSmokeTest.php +++ b/apps/platform/tests/Browser/OpsUx/OperationActivityFeedbackSmokeTest.php @@ -268,4 +268,88 @@ function operationActivityFeedbackSmokeLoginUrl(User $user, Tenant $tenant, stri ->waitForText('Operation #'.(int) $run->getKey()) ->assertNoJavaScriptErrors() ->assertNoConsoleLogs(); -}); \ No newline at end of file +}); + +it('keeps terminal follow-up acknowledge local to the browser session and reopens for new work', function (): void { + [$user, $tenant] = createUserWithTenant(role: 'owner'); + + $failedRun = OperationRun::factory()->create([ + 'tenant_id' => (int) $tenant->getKey(), + 'workspace_id' => (int) $tenant->workspace_id, + 'user_id' => (int) $user->getKey(), + 'type' => 'inventory_sync', + 'status' => 'completed', + 'outcome' => 'failed', + 'started_at' => now()->subMinutes(3), + 'completed_at' => now()->subSeconds(8), + ]); + + visit(operationActivityFeedbackSmokeLoginUrl($user, $tenant)) + ->waitForText('Dashboard') + ->assertNoJavaScriptErrors() + ->assertNoConsoleLogs(); + + $page = visit(InventoryItemResource::getUrl('index', panel: 'tenant', tenant: $tenant)) + ->resize(1440, 1200) + ->waitForText('Inventory Items') + ->waitForText('Acknowledge') + ->assertSee('Recent operation updates that may need review.') + ->assertNoJavaScriptErrors() + ->assertNoConsoleLogs(); + + $page + ->click('[data-testid="ops-ux-activity-feedback-toggle"]') + ->wait(1) + ->assertScript(<<<'JS' +(() => { + const banner = document.querySelector('[data-testid="ops-ux-activity-feedback-banner"]'); + + return banner !== null && window.getComputedStyle(banner).display === 'none'; +})() +JS, true) + ->refresh() + ->waitForText('Inventory Items') + ->assertScript(<<<'JS' +(() => { + const banner = document.querySelector('[data-testid="ops-ux-activity-feedback-banner"]'); + + return banner !== null && window.getComputedStyle(banner).display === 'none'; +})() +JS, true) + ->assertNoJavaScriptErrors() + ->assertNoConsoleLogs(); + + expect($failedRun->refresh()->status)->toBe('completed') + ->and($failedRun->outcome)->toBe('failed'); + + OperationRun::factory()->create([ + 'tenant_id' => (int) $tenant->getKey(), + 'workspace_id' => (int) $tenant->workspace_id, + 'user_id' => (int) $user->getKey(), + 'type' => 'inventory_sync', + 'status' => 'running', + 'outcome' => 'pending', + 'started_at' => now()->subSeconds(10), + ]); + + $page->script(<<<'JS' +window.dispatchEvent(new CustomEvent('ops-ux:run-enqueued', { + detail: { + tenantId: Number(document.querySelector('[data-testid="ops-ux-activity-feedback-root"]')?.dataset.tenantId || 0), + }, +})); +JS); + + $page + ->waitForText('Review operations') + ->waitForText('Acknowledge') + ->assertScript(<<<'JS' +(() => { + const banner = document.querySelector('[data-testid="ops-ux-activity-feedback-banner"]'); + + return banner !== null && window.getComputedStyle(banner).display !== 'none'; +})() +JS, true) + ->assertNoJavaScriptErrors() + ->assertNoConsoleLogs(); +}); diff --git a/apps/platform/tests/Feature/OpsUx/ActivityFeedbackSurfaceTest.php b/apps/platform/tests/Feature/OpsUx/ActivityFeedbackSurfaceTest.php index ce70044e..22c2a89b 100644 --- a/apps/platform/tests/Feature/OpsUx/ActivityFeedbackSurfaceTest.php +++ b/apps/platform/tests/Feature/OpsUx/ActivityFeedbackSurfaceTest.php @@ -3,10 +3,90 @@ use App\Filament\Resources\InventoryItemResource; use App\Livewire\BulkOperationProgress; use App\Models\OperationRun; +use App\Models\Tenant; use App\Support\OpsUx\OperationRunUrl; use Filament\Facades\Filament; use Livewire\Livewire; +it('keeps the shell inert when no tenant context is selected', function (): void { + [$user, $tenant] = createUserWithTenant(role: 'owner'); + + $this->actingAs($user); + Filament::setTenant(null, true); + + OperationRun::factory()->create([ + 'tenant_id' => (int) $tenant->getKey(), + 'workspace_id' => (int) $tenant->workspace_id, + 'user_id' => (int) $user->getKey(), + 'type' => 'inventory_sync', + 'status' => 'running', + 'outcome' => 'pending', + 'started_at' => now()->subMinute(), + ]); + + Livewire::actingAs($user) + ->test(BulkOperationProgress::class) + ->call('refreshRuns') + ->assertSet('disabled', true) + ->assertSet('hasVisibleRuns', false) + ->assertDontSee('Inventory sync') + ->assertDontSee('Active operations'); +})->group('ops-ux'); + +it('keeps the shell inert when the actor cannot view operation runs', function (): void { + [$user, $tenant] = createUserWithTenant(role: 'owner'); + + $this->actingAs($user); + Filament::setTenant($tenant, true); + session()->forget(\App\Support\Workspaces\WorkspaceContext::SESSION_KEY); + + OperationRun::factory()->create([ + 'tenant_id' => (int) $tenant->getKey(), + 'workspace_id' => (int) $tenant->workspace_id, + 'user_id' => (int) $user->getKey(), + 'type' => 'inventory_sync', + 'status' => 'running', + 'outcome' => 'pending', + 'started_at' => now()->subMinute(), + ]); + + Livewire::actingAs($user) + ->test(BulkOperationProgress::class) + ->call('refreshRuns') + ->assertSet('disabled', true) + ->assertSet('hasVisibleRuns', false) + ->assertDontSee('Inventory sync') + ->assertDontSee('Active operations'); +})->group('ops-ux'); + +it('does not expose another tenant run through the selected tenant shell', function (): void { + [$user, $tenant] = createUserWithTenant(role: 'owner'); + $foreignTenant = Tenant::factory()->create([ + 'workspace_id' => (int) $tenant->workspace_id, + ]); + + $this->actingAs($user); + Filament::setTenant($tenant, true); + + OperationRun::factory()->create([ + 'tenant_id' => (int) $foreignTenant->getKey(), + 'workspace_id' => (int) $foreignTenant->workspace_id, + 'type' => 'policy.sync', + 'status' => 'running', + 'outcome' => 'pending', + 'initiator_name' => 'Foreign tenant run', + 'started_at' => now()->subMinute(), + ]); + + Livewire::actingAs($user) + ->test(BulkOperationProgress::class) + ->call('refreshRuns') + ->assertSet('disabled', false) + ->assertSet('hasVisibleRuns', false) + ->assertDontSee('Policy sync') + ->assertDontSee('Foreign tenant run'); +})->group('ops-ux'); + it('renders three visible run summaries while grouping banner actions in one action area', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); @@ -119,7 +199,7 @@ expect($component->get('hasActiveRuns'))->toBeFalse() ->and($html)->toContain('Operation updates') - ->and($pageText)->toContain('Recent operation updates.') + ->and($pageText)->toContain('Successful operation updates stay briefly visible so you can confirm completion and keep working.') ->and($pageText)->toContain('Completed successfully') ->and($pageText)->toContain('No action needed.') ->and($html)->toContain('View operation') @@ -130,7 +210,7 @@ ->and($html)->not->toContain('role="progressbar"'); })->group('ops-ux'); -it('renders terminal follow-up states with dismiss instead of hide activity', function (): void { +it('renders terminal follow-up states with acknowledge instead of dismiss semantics', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); $this->actingAs($user); @@ -157,10 +237,11 @@ expect($component->get('hasActiveRuns'))->toBeFalse() ->and($html)->toContain('Operation updates') ->and($pageText)->toContain('Recent operation updates that may need review.') + ->and($pageText)->toContain('Review the operation details before retrying.') ->and($html)->toContain('View operation') ->and($html)->not->toContain('Review operations') - ->and($html)->toContain('Dismiss') - ->and($html)->not->toContain('Acknowledge') + ->and($html)->toContain('Acknowledge') + ->and($html)->not->toContain('Dismiss') ->and($html)->not->toContain('data-testid="ops-ux-activity-feedback-indeterminate"') ->and($html)->not->toContain('role="progressbar"') ->and($html)->not->toContain('Hide activity'); @@ -204,7 +285,8 @@ ->and($html)->toContain('Operation updates') ->and($pageText)->toContain('Active and recent operation updates that may need review.') ->and($html)->not->toContain('View operation') - ->and($html)->toContain('Hide activity') + ->and($html)->toContain('Acknowledge') + ->and($html)->not->toContain('Dismiss updates') ->and($html)->toContain(OperationRunUrl::index($tenant)) ->and($html)->not->toContain(OperationRunUrl::view($runningRun, $tenant)) ->and($pageText)->toContain('Execution failed'); @@ -545,4 +627,4 @@ ->and($component->html())->toContain('data-testid="ops-ux-activity-feedback-expand"') ->and($script)->toContain('sessionStorage') ->and($script)->toContain('ops-ux:run-enqueued'); -})->group('ops-ux'); \ No newline at end of file +})->group('ops-ux'); diff --git a/apps/platform/tests/Feature/OpsUx/BulkOperationProgressDbOnlyTest.php b/apps/platform/tests/Feature/OpsUx/BulkOperationProgressDbOnlyTest.php index 7047a337..26990144 100644 --- a/apps/platform/tests/Feature/OpsUx/BulkOperationProgressDbOnlyTest.php +++ b/apps/platform/tests/Feature/OpsUx/BulkOperationProgressDbOnlyTest.php @@ -154,8 +154,8 @@ ->assertSee('Inventory sync') ->assertSee('View operation') ->assertDontSee('Review operations') - ->assertSee('Dismiss') - ->assertDontSee('Acknowledge') + ->assertSee('Acknowledge') + ->assertDontSee('Dismiss') ->assertDontSee('Waiting for worker.') ->assertDontSee('Hide activity'); })->group('ops-ux'); @@ -192,7 +192,8 @@ ->assertSee('Review operations') ->assertDontSee('View operation') ->assertSee('Show all operations') - ->assertSee('Hide activity'); + ->assertSee('Acknowledge') + ->assertDontSee('Dismiss updates'); })->group('ops-ux'); it('shows likely stale runs in the progress overlay and keeps polling when only stale runs remain', function () { diff --git a/docs/ui/tenantpilot-enterprise-ui-standards.md b/docs/ui/tenantpilot-enterprise-ui-standards.md index 45e52f79..453a5de3 100644 --- a/docs/ui/tenantpilot-enterprise-ui-standards.md +++ b/docs/ui/tenantpilot-enterprise-ui-standards.md @@ -1318,8 +1318,7 @@ # TenantPilot Enterprise UI Standards**Status:** Active **Owner:** Product / En - Hide activity for queued or running rows - Dismiss or Close for terminal-success rows -- Dismiss for a single unresolved terminal follow-up row -- Dismiss updates when multiple unresolved terminal follow-up rows are visible and no active work remains +- Acknowledge for unresolved terminal follow-up rows, including grouped states use Show all operations to open the canonical Operations collection with the current tenant encoded as the contextual tenant_id prefilter @@ -1361,6 +1360,13 @@ # TenantPilot Enterprise UI Standards**Status:** Active **Owner:** Product / En switch terminal-success rows to success-state copy instead of showing active progress after completion +separate successful completion from unresolved terminal follow-up in the shell helper copy: + +- successful terminal rows use no-action-needed completion copy and remain locally dismissible +- unresolved terminal follow-up rows use review-needed copy and MUST NOT inherit generic Dismiss semantics + +keep hide, dismiss, or acknowledge behavior browser-session-only and re-open the hint when a new run-enqueued event is accepted for the current tenant + record these as follow-up work instead of widening the current contract: - provider health or support-diagnostics progress rollout @@ -1368,8 +1374,6 @@ # TenantPilot Enterprise UI Standards**Status:** Active **Owner:** Product / En - child-run graph persistence or composite child-link expansion - dashboard cards or workflow-engine generated progress explanations -keep hide or dismiss behavior browser-session-only and re-open the hint when a new run-enqueued event is accepted for the current tenant - It MUST NOT: render as a detached document-level BODY_START banner above the TenantPilot application chrome @@ -1386,6 +1390,8 @@ # TenantPilot Enterprise UI Standards**Status:** Active **Owner:** Product / En persist hide or dismiss state in the database or on the OperationRun record +persist acknowledge state in the database or on the OperationRun record + render as a fixed overlay that can cover findings row actions, table actions, or page-primary actions 30. Maintenance Rules @@ -1451,4 +1457,3 @@ ## UI Standards CheckChanged custom UI surfaces:- ...Patterns used:- Card / Surf Do not imply Customer Review Workspace maturity unless repo-verifiedUse Review Pack / Export Artifact wording when that is what exists Blade Render onlyDo not invent domain logicDo not invent routesDo not infer capabilities - diff --git a/specs/269-operationrun-terminal-outcome-feedback/checklists/requirements.md b/specs/269-operationrun-terminal-outcome-feedback/checklists/requirements.md new file mode 100644 index 00000000..3356d671 --- /dev/null +++ b/specs/269-operationrun-terminal-outcome-feedback/checklists/requirements.md @@ -0,0 +1,56 @@ +# Specification Quality Checklist: OperationRun Terminal Outcome Feedback v1 + +**Purpose**: Validate specification completeness, boundedness, and repo fit before implementation +**Created**: 2026-05-05 +**Feature**: [spec.md](../spec.md) + +## Content Quality + +- [x] The package stays on one bounded terminal-outcome refinement over the existing shell activity hint instead of widening into a new activity center, dashboard card, or notification rewrite. +- [x] The spec remains product- and behavior-oriented and does not read like a low-level implementation diff. +- [x] The package explicitly names the repo-real anchors it builds on: `BulkOperationProgress`, the current shell activity view, `OperationUxPresenter`, `OperationRunProgressContract`, `ActiveRuns`, and the existing browser-session event flow. +- [x] Mandatory repo sections for scope, RBAC, Ops-UX, testing, proportionality, and candidate rationale are completed. + +## Requirement Completeness + +- [x] No `[NEEDS CLARIFICATION]` markers remain. +- [x] Requirements are testable and stay bounded to current shell terminal-success and terminal-follow-up semantics only. +- [x] The package explicitly preserves canonical `View operation` and `Show all operations` link generation instead of reopening routing decisions. +- [x] The package makes the lifecycle-sensitive tertiary contract explicit: `Hide activity` while active, `Dismiss` or `Close` for terminal success, and `Acknowledge` for unresolved terminal follow-up. +- [x] The package keeps acknowledgement browser-session-only and forbids DB-backed review state. +- [x] Planned validation commands match across `spec.md`, `plan.md`, and `tasks.md`. + +## Candidate Selection Gate + +- [x] The selected candidate exists in `docs/product/spec-candidates.md` and aligns with the roadmap's manual-promotion backlog. +- [x] The active queue remains intentionally empty, so this package records itself as a deliberate manual promotion rather than an automatic next-best-prep target. +- [x] No existing `269-*` spec package exists, and the current repo truth still leaves an open semantic gap even though Spec 268 and current code already introduced the broader shell activity surface. +- [x] The chosen slice is smaller and more repo-ready than deferred alternatives because it tightens one live shell contract before dashboard or broader progress follow-ups. + +## Feature Readiness + +- [x] The package reuses existing `OperationRun` truth and current shell helpers instead of introducing a second lifecycle or persisted projection. +- [x] The package explicitly leaves dashboard, tray, notification-policy, and progress-rollout work out of scope. +- [x] The package preserves current tenant-bound visibility and current Operations drill-through ownership. +- [x] The standards document update is included in the same slice so the terminal outcome rule becomes durable repo guidance. + +## Test Governance + +- [x] Planned proof stays bounded to focused Feature coverage plus one named browser smoke for the shell interaction contract. +- [x] No new heavy-governance, discovery, or browser family is introduced by default. +- [x] Fixture growth stays bounded to current `OperationRun` factories, tenant helpers, and existing shell smoke helpers. +- [x] The review outcome, workflow outcome, and test-governance outcome are carried into `plan.md` and `tasks.md`. + +## Notes + +- Reviewed against `docs/product/spec-candidates.md`, `docs/product/roadmap.md`, `.specify/memory/constitution.md`, `apps/platform/app/Livewire/BulkOperationProgress.php`, `apps/platform/resources/views/livewire/bulk-operation-progress.blade.php`, `apps/platform/tests/Feature/OpsUx/ActivityFeedbackSurfaceTest.php`, `apps/platform/tests/Feature/OpsUx/BulkOperationProgressDbOnlyTest.php`, and `apps/platform/tests/Browser/OpsUx/OperationActivityFeedbackSmokeTest.php` on 2026-05-05. +- Related-spec guardrail: Spec 268 remains the current shell-activity foundation and must not be rewritten back into prep-only state. This package only narrows the still-open terminal-outcome semantics seam. +- No application implementation was performed while preparing this package. + +## Review Outcome + +- **Outcome class**: `acceptable-special-case` +- **Workflow outcome**: `keep` +- **Test-governance outcome**: `keep` +- **Reason**: The automatic queue is intentionally empty, but this manual promotion is still justified because current repo truth shows an open terminal-outcome semantics gap on a live shell surface that is smaller and safer than the deferred dashboard or broader progress follow-ups. +- **Workflow result**: Ready for implementation. \ No newline at end of file diff --git a/specs/269-operationrun-terminal-outcome-feedback/plan.md b/specs/269-operationrun-terminal-outcome-feedback/plan.md new file mode 100644 index 00000000..98d28789 --- /dev/null +++ b/specs/269-operationrun-terminal-outcome-feedback/plan.md @@ -0,0 +1,260 @@ +# Implementation Plan: OperationRun Terminal Outcome Feedback v1 + +**Branch**: `269-operationrun-terminal-outcome-feedback` | **Date**: 2026-05-05 | **Spec**: [spec.md](./spec.md) +**Input**: Feature specification from `/specs/269-operationrun-terminal-outcome-feedback/spec.md` + +## Summary + +This plan prepares one bounded refinement over the repo's existing shell activity feedback. The current `BulkOperationProgress` surface already shows active work and recent terminal rows, but terminal success and unresolved follow-up still share too much generic terminal-update behavior. The implementation path is to keep the existing shell host, links, and progress contract intact while splitting terminal success from terminal follow-up copy and browser-session tertiary semantics, then record the final rule in `docs/ui/tenantpilot-enterprise-ui-standards.md`. + +Filament remains on Livewire v4, no panel-provider registration changes are required (`apps/platform/bootstrap/providers.php` remains authoritative), no globally searchable resource is added, no asset registration change is expected, and no destructive action is introduced. + +## Inherited Baseline / Explicit Delta + +### Inherited baseline + +- `OperationRun` already provides the execution-truth model and current tenant/workspace scoping. +- `BulkOperationProgress` already hosts the shell activity surface on tenant-scoped pages. +- `OperationUxPresenter`, `OperationStatusNormalizer`, `OperationRunProgressContract`, `OperationRunLinks`, `OperationRunUrl`, and `ActiveRuns` already own the current status, progress, and link semantics. +- `ActivityFeedbackSurfaceTest`, `BulkOperationProgressDbOnlyTest`, and `OperationActivityFeedbackSmokeTest` already prove core shell behavior. + +### Explicit delta in this plan + +- split terminal-success shell behavior from unresolved terminal-follow-up shell behavior +- preserve active-state helper, progress, and canonical-link behavior unchanged unless terminal-outcome refinement requires a local adjustment +- keep unresolved terminal follow-up explicitly reviewable instead of generically dismissible +- keep terminal success briefly dismissible and clearly no-action-needed during the current 30-second shell-visible success window from `ActiveRuns` +- keep acknowledge and dismiss behavior browser-session-only +- update `docs/ui/tenantpilot-enterprise-ui-standards.md` with the terminal outcome contract + +## Technical Context + +**Language/Version**: PHP 8.4, Laravel 12, Filament v5, Livewire v4 +**Primary Dependencies**: current Ops-UX support classes, native Filament widgets or Blade, current badge infrastructure, Pest v4 +**Storage**: PostgreSQL via existing `operation_runs`; browser-session state only for current shell collapse or acknowledgement behavior; no new DB storage +**Testing**: Pest Feature coverage plus one required browser smoke +**Validation Lanes**: fast-feedback, confidence, browser +**Target Platform**: existing Laravel monolith in `apps/platform`, admin or operator plane only +**Project Type**: Web application (Laravel monolith with Filament) +**Performance Goals**: no new query families, no duplicate polling loops, and no additional shell host surfaces +**Constraints**: no new persistence, no new lifecycle family, no new notification policy, no dashboard card, and no panel/provider/asset changes +**Scale/Scope**: one current shell host, one standards update, and one browser-session tertiary-action rule + +## Likely Affected Repo Surfaces + +- `apps/platform/app/Livewire/BulkOperationProgress.php` +- `apps/platform/resources/views/livewire/bulk-operation-progress.blade.php` +- `apps/platform/app/Support/OpsUx/OperationUxPresenter.php` +- `apps/platform/app/Support/OpsUx/ActiveRuns.php` +- `apps/platform/public/js/tenantpilot/ops-ux-progress-widget-poller.js` +- `apps/platform/tests/Feature/OpsUx/ActivityFeedbackSurfaceTest.php` +- `apps/platform/tests/Feature/OpsUx/BulkOperationProgressDbOnlyTest.php` +- `apps/platform/tests/Browser/OpsUx/OperationActivityFeedbackSmokeTest.php` +- `docs/ui/tenantpilot-enterprise-ui-standards.md` + +## UI / Filament & Livewire Fit + +- Keep the changed shell host Filament-native and local to the existing Livewire component. +- The shell remains decision-first: one dominant primary action, one concise helper line, and one lifecycle-sensitive tertiary action. +- Monitoring collection and detail pages remain diagnostics-first drill-through targets. The shell must not restate their evidence-heavy content. +- Successful terminal rows stay briefly visible with no-action-needed semantics; unresolved terminal follow-up stays explicitly reviewable. +- Browser-session `Acknowledge` stays local to the current browser session and must not become a server-side workflow state. +- No new asset registration, panel configuration, or provider registration change is planned. + +## RBAC / Policy Fit + +- Existing `OperationRun` policies remain the first and only visibility gate. +- The shell continues to derive rows only after tenant context and policy filtering. +- No plane expansion, no new action surface, and no new authorization rule are introduced. +- Canonical Operations collection and detail pages remain the only drill-through owners for deeper diagnostics. + +## Audit / Logging Fit + +- Existing queued toasts, browser events, and terminal DB notifications remain authoritative and unchanged. +- Existing Monitoring and audit behavior remain the only audit trail. No page-view audit stream is introduced. +- Because the slice changes presentation only, `OperationRun.status` and `OperationRun.outcome` ownership remain service-owned and unchanged. + +## Data & Query Fit + +- The shell host continues to derive rows from current-tenant `OperationRun` truth. +- The current 30-second terminal-success grace window in `ActiveRuns::shellVisibleQueryForTenantId()` remains the source of truth for recent successful terminal visibility in this slice. +- Terminal success and terminal follow-up remain derived display states only; they do not create new persisted fields. +- Acknowledge and dismiss remain browser-session scoped and must not be written to the database or to the run record. + +## UI / Surface Guardrail Plan + +- **Guardrail scope**: changed surfaces +- **Native vs custom classification summary**: native Filament plus a bounded local shell refinement +- **Shared-family relevance**: Ops UX lifecycle feedback, canonical run links +- **State layers in scope**: shell, page, browser-session +- **Audience modes in scope**: operator-MSP +- **Decision/diagnostic/raw hierarchy plan**: decision-first on the shell host, diagnostics-first on Operations collection/detail +- **Raw/support gating plan**: raw and support detail stay on existing diagnostics surfaces only +- **One-primary-action / duplicate-truth control**: each visible shell state keeps one dominant primary action; grouped `Review operations` remains only the label variant of the canonical collection link also exposed as `Show all operations`, and the shell differentiates `success` from `follow-up` through helper and tertiary wording instead of duplicate diagnostics +- **Handling modes by drift class or surface**: review-mandatory +- **Repository-signal treatment**: review-mandatory +- **Special surface test profiles**: global-context-shell +- **Required tests**: functional-core, named-browser-smoke +- **Exception path and spread control**: none planned; any attempt to add persisted acknowledgement, dashboard work, or a second lifecycle surface resolves as `reject-or-split` +- **Active feature PR close-out entry**: Guardrail / Smoke Coverage + +## Shared Pattern & System Fit + +- **Cross-cutting feature marker**: yes +- **Systems touched**: current shell hint, canonical run links, current run guidance, current progress contract, current shell poller, UI standards +- **Shared abstractions reused**: `OperationUxPresenter`, `OperationStatusNormalizer`, `OperationRunProgressContract`, `OperationRunLinks`, `OperationRunUrl`, `ActiveRuns`, `OpsUxBrowserEvents` +- **New abstraction introduced? why?**: none planned; keep the change local to the existing shell component and helper seams unless a tiny readability helper becomes necessary during implementation +- **Why the existing abstraction was sufficient or insufficient**: the repo already owns truthful state and links; the missing piece is the terminal-outcome decision contract on the shell +- **Bounded deviation / spread control**: do not create a second terminal-outcome presenter family, a registry, or a persisted acknowledgement model + +## OperationRun UX Impact + +- **Touches OperationRun start/completion/link UX?**: yes, for post-completion shell follow-through and drill-through links only +- **Central contract reused**: current Ops-UX shell contract via `OperationUxPresenter`, `OperationRunLinks`, `OperationRunUrl`, `OperationRunProgressContract`, and `OpsUxBrowserEvents` +- **Delegated UX behaviors**: current queued toast wording, canonical `View operation`, grouped `Review operations`, and `Show all operations` links, current browser-event dispatch, and existing terminal DB-notification lifecycle remain delegated to the shared contract +- **Surface-owned behavior kept local**: success vs follow-up helper copy and lifecycle-sensitive tertiary action wording only +- **Queued DB-notification policy**: `N/A` - unchanged +- **Terminal notification path**: unchanged central lifecycle mechanism +- **Exception path**: none + +## Provider Boundary & Portability Fit + +- **Shared provider/platform boundary touched?**: no +- **Provider-owned seams**: `N/A` +- **Platform-core seams**: existing `OperationRun` truth, links, and operator vocabulary only +- **Neutral platform terms / contracts preserved**: `Operation`, `View operation`, `Review operations`, `Show all operations`, `completed successfully`, `review needed` +- **Retained provider-specific semantics and why**: none +- **Bounded extraction or follow-up path**: none + +## Constitution Check + +*GATE: Must pass before implementation begins and again before merge.* + +- Inventory-first: PASS. The slice is fully derived from existing `OperationRun` truth. +- Read/write separation: PASS. No new write path or retry surface is introduced. +- Graph contract path: PASS. No Graph/provider interaction is added. +- Deterministic capabilities: PASS. Existing `OperationRun` policies remain authoritative. +- RBAC-UX: PASS. No plane expansion; tenant/admin visibility stays on current guards and deny-as-not-found semantics. +- Run observability: PASS. Existing start contract, terminal notifications, and Monitoring ownership remain unchanged while the shell only refines terminal-outcome meaning. +- Ops-UX lifecycle: PASS. No change to service-owned status/outcome transitions or `summary_counts` semantics. +- Data minimization: PASS. The shell remains compact and does not surface raw evidence by default. +- Test governance: PASS. Proof stays bounded to focused Feature coverage plus one browser smoke. +- Proportionality / no premature abstraction: PASS. The default implementation stays local to the existing shell host and standards document. +- Persisted truth / behavioral state: PASS. No new table, no new lifecycle, no new persisted acknowledgement or dismiss state. +- Shared pattern first / UI semantics / Filament-native UI: PASS. Existing helpers and badge semantics stay central, and the shell moves closer to a precise decision-first contract. +- Provider boundary: PASS. No provider/platform seam changes. +- Filament/Laravel panel safety: PASS. Filament v5 stays on Livewire v4, provider registration remains in `apps/platform/bootstrap/providers.php`, no globally searchable resource is introduced, and no new assets are planned. + +**Gate evaluation**: PASS. + +## Test Governance Check + +- **Test purpose / classification by changed surface**: Feature for terminal-outcome shell behavior; one required browser smoke for live shell interaction +- **Affected validation lanes**: fast-feedback, confidence, browser +- **Why this lane mix is the narrowest sufficient proof**: Feature coverage proves success vs follow-up copy, tertiary actions, browser-session-only acknowledgement, and the absence of progress UI after completion. The browser smoke remains reserved for real shell interaction and re-open behavior. +- **Narrowest proving command(s)**: + - `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/OpsUx/ActivityFeedbackSurfaceTest.php tests/Feature/OpsUx/BulkOperationProgressDbOnlyTest.php` + - `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Browser/OpsUx/OperationActivityFeedbackSmokeTest.php` +- **Fixture / helper / factory / seed / context cost risks**: low to moderate; reuse current `OperationRun` factories, tenant helpers, and shell smoke helpers instead of introducing new provider-heavy defaults +- **Expensive defaults or shared helper growth introduced?**: no +- **Heavy-family additions, promotions, or visibility changes**: none +- **Surface-class relief / special coverage rule**: `global-context-shell` +- **Closing validation and reviewer handoff**: reviewers should rerun the focused commands above, then confirm that unresolved terminal follow-up uses `Acknowledge` or review wording, successful completion stays dismissible, and new run-enqueue events still reopen the shell. +- **Budget / baseline / trend follow-up**: none expected beyond a small feature-local increase +- **Review-stop questions**: did the shell stay bounded, did unresolved follow-up stop using generic dismiss semantics, did terminal rows stay progress-free, and did acknowledgement remain browser-session-only? +- **Escalation path**: `reject-or-split` for any dashboard expansion, persisted acknowledgement state, or notification-policy change +- **Active feature PR close-out entry**: Guardrail / Smoke Coverage +- **Why no dedicated follow-up spec is needed**: this package already narrows the live shell contract to the remaining terminal-outcome seam; larger dashboard or progress topics remain explicit follow-up candidates instead of hidden work here. + +## Authorization Verification Fit + +- Reuse the existing shell Feature proof to verify no-tenant and no-capability suppression, tenant-safe filtering, and the absence of inaccessible run exposure on the shell host. +- Keep that verification local to the current shell test family; do not create a separate authorization test family for this slice. + +## Preparation Review Outcome + +- **Review outcome class**: `acceptable-special-case` +- **Workflow outcome**: `keep` +- **Test-governance outcome**: `keep` +- **Reason**: the automatic candidate queue is intentionally empty, but this manual promotion is still justified because current repo truth leaves a bounded terminal-outcome gap on a live shell surface that is smaller and safer than the deferred dashboard or broader progress follow-ups. + +## Project Structure + +### Documentation (this feature) + +```text +specs/269-operationrun-terminal-outcome-feedback/ +├── spec.md +├── plan.md +├── tasks.md +└── checklists/ + └── requirements.md +``` + +This preparation package intentionally stays on the core artifacts plus the readiness checklist. The repo already contains the relevant Ops-UX truth, shell host, and proof surfaces, so no extra research, data-model, quickstart, or contracts package is required for a bounded implementation handoff. + +### Source Code (expected implementation surfaces) + +```text +apps/platform/app/Livewire/BulkOperationProgress.php +apps/platform/resources/views/livewire/bulk-operation-progress.blade.php +apps/platform/app/Support/OpsUx/OperationUxPresenter.php +apps/platform/app/Support/OpsUx/ActiveRuns.php +apps/platform/public/js/tenantpilot/ops-ux-progress-widget-poller.js +apps/platform/tests/Feature/OpsUx/ActivityFeedbackSurfaceTest.php +apps/platform/tests/Feature/OpsUx/BulkOperationProgressDbOnlyTest.php +apps/platform/tests/Browser/OpsUx/OperationActivityFeedbackSmokeTest.php +docs/ui/tenantpilot-enterprise-ui-standards.md +``` + +**Structure Decision**: keep the implementation local to the existing Ops-UX shell seams and standards document. Do not introduce a second activity framework or a dashboard-owned run-state model in this slice. + +## Data / Migration Implications + +- No migration or new table is planned. +- No new persisted user preference is allowed. +- No new cache layer, backfill, or deploy step should be required. + +## Rollout Considerations + +- Filament remains v5 on Livewire v4. No panel-provider change is required, and provider registration remains in `apps/platform/bootstrap/providers.php`. +- No global search change is required because the slice changes a shell widget, not a resource. +- No destructive action is added. +- No new asset registration is expected; if future work ever registers assets, deployment still runs `cd apps/platform && php artisan filament:assets` outside this slice. + +## Risk Controls + +- Reject any implementation that leaves unresolved terminal follow-up on generic `Dismiss` semantics. +- Reject any implementation that introduces a new `OperationRun` lifecycle, a persisted acknowledgement model, or a dashboard active-operations card in this slice. +- Reject any implementation that reintroduces progress UI after terminal transition. +- Reject any implementation that changes notification policy, route ownership, or shell polling scope. + +## Implementation Phases + +### Phase 0 - Confirm Current Terminal Outcome Truth + +- Verify the current shell host, current helper seams, and current proof owners for successful terminal items, unresolved terminal follow-up, and browser-session collapse or acknowledge behavior. + +### Phase 1 - Split Success From Follow-Up Semantics + +- Keep terminal success calm and dismissible. +- Make unresolved terminal follow-up explicitly reviewable and acknowledge-only at the shell level. + +### Phase 2 - Keep Browser-Session Outcome Actions Local + +- Preserve current browser-session-only shell calmness. +- Ensure new run-enqueue events reopen the shell. + +### Phase 3 - Record The Guardrail And Validate + +- Update the standards document. +- Run the focused Feature proof and the named browser smoke. + +## Proportionality Review + +- **Current operator problem**: terminal follow-up still looks too much like harmless completion in the live shell surface. +- **Existing structure is insufficient because**: the broader shell feedback exists already, but its current terminal-outcome semantics are still too generic for honest decision-first behavior. +- **Narrowest correct implementation**: refine the existing shell component, helper text, and browser-session tertiary actions only. +- **Ownership cost created**: minimal shell branching plus focused tests and one standards update. +- **Alternative intentionally rejected**: a tiny copy-only patch was rejected because it would not define the durable `success vs follow-up` action contract or prevent regression. +- **Release truth**: current-release truth. The repo already ships the shell activity surface; this slice only hardens the remaining terminal-outcome seam. \ No newline at end of file diff --git a/specs/269-operationrun-terminal-outcome-feedback/spec.md b/specs/269-operationrun-terminal-outcome-feedback/spec.md new file mode 100644 index 00000000..8a7f9925 --- /dev/null +++ b/specs/269-operationrun-terminal-outcome-feedback/spec.md @@ -0,0 +1,237 @@ +# Feature Specification: OperationRun Terminal Outcome Feedback v1 + +**Feature Branch**: `269-operationrun-terminal-outcome-feedback` +**Created**: 2026-05-05 +**Status**: Ready for implementation +**Input**: Manual promotion from `docs/product/spec-candidates.md` after repo-based verification against the current shell implementation, tests, and roadmap backlog. + +## Spec Candidate Check *(mandatory - SPEC-GATE-001)* + +- **Problem**: The tenant shell already shows recent terminal `OperationRun` states, but the current surface still collapses successful completion and unresolved follow-up into one generic terminal-update model. Terminal follow-up rows can still read like they are safely dismissible instead of clearly needing review. +- **Today's failure**: A blocked, partial, or failed run can render with the same generic terminal banner framing and `Dismiss` semantics as a no-action-needed success. That weakens the shell's decision-first promise because unresolved work can look like harmless noise. +- **User-visible improvement**: Operators can immediately distinguish `done, no action needed` from `completed, review still needed` in the shell without opening Monitoring first. Successful terminal items stay briefly calm and dismissible, while unresolved follow-up states use explicit acknowledge or review wording. +- **Smallest enterprise-capable version**: tighten the current tenant-shell activity hint only: keep active-state behavior intact, keep canonical links intact, split terminal success from terminal follow-up copy and tertiary actions, and keep acknowledgement browser-session-only. +- **Explicit non-goals**: no new activity tray, no dashboard active-operations card, no new `OperationRun` lifecycle, no new progress contract, no notification-policy rewrite, no persisted acknowledgement state, and no new diagnostics surface. +- **Permanent complexity imported**: one tighter shell-state contract, small copy or action branching in the existing shell helper or view, focused Feature plus browser proof, and one standards-document update. +- **Why now**: repo truth already includes the broader shell activity surface from Spec 268, and the next visible gap is no longer active-work feedback. It is terminal-outcome honesty on the same surface. +- **Why not local**: a one-line copy tweak would not encode the lifecycle-specific action contract, the `success vs follow-up` distinction, or the durable UI guardrail that stops this seam from drifting again. +- **Approval class**: Workflow Compression +- **Red flags triggered**: shared interaction family, shell-level operator surface +- **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 2 | Komplexitaet: 2 | Produktnaehe: 2 | Wiederverwendung: 1 | **Gesamt: 11/12** +- **Decision**: approve + +## Spec Scope Fields *(mandatory)* + +- **Scope**: tenant +- **Primary Routes**: + - `/admin/t/{tenant}/...` tenant-scoped start and work surfaces that host the shell activity hint + - `/admin/operations` remains the canonical collection drill-through + - `/admin/operations/{run}` remains the canonical detail drill-through +- **Data Ownership**: existing tenant-owned `OperationRun` truth only; no new persisted projection, no new user preference store, and no new queue or notification state +- **RBAC**: current `OperationRun` policies remain authoritative. Non-members or out-of-scope tenant contexts stay deny-as-not-found (`404` semantics through current tenant/admin boundaries). In-scope actors only see terminal states they can already view through the canonical Operations routes. + +## Cross-Cutting / Shared Pattern Reuse *(mandatory)* + +- **Cross-cutting feature?**: yes +- **Interaction class(es)**: shell status messaging, navigation links, lifecycle-sensitive tertiary actions +- **Systems touched**: `BulkOperationProgress`, `OperationUxPresenter`, `OperationStatusNormalizer`, `OperationRunProgressContract`, `ActiveRuns`, `OperationRunLinks`, `OperationRunUrl`, `OpsUxBrowserEvents`, and `docs/ui/tenantpilot-enterprise-ui-standards.md` +- **Existing pattern(s) to extend**: current shell activity feedback, canonical `View operation` and `Show all operations` links, current progress-contract enforcement, and browser-session collapse behavior +- **Shared contract / presenter / builder / renderer to reuse**: `App\Support\OpsUx\OperationUxPresenter`, `App\Support\OpsUx\OperationStatusNormalizer`, `App\Support\OpsUx\OperationRunProgressContract`, `App\Support\OpsUx\OperationRunUrl`, `App\Support\OpsUx\ActiveRuns`, `App\Support\OpsUx\OpsUxBrowserEvents`, and `App\Support\OperationRunLinks` +- **Why the existing shared path is sufficient or insufficient**: the repo already owns truthful link, status, and progress semantics. What remains open is a narrower shell-level terminal-outcome contract, not a new framework. +- **Allowed deviation and why**: none planned. Keep the refinement local to the existing shell component and its helpers. +- **Consistency impact**: `Hide activity`, `Dismiss` or `Close`, `Acknowledge`, `View operation`, grouped `Review operations`, and the banner helper copy must describe active, successful, and unresolved follow-up states consistently across Feature proof, browser smoke, and the standards document. `Review operations` is the grouped label variant of the same canonical collection link also exposed as `Show all operations`, not a second collection action. +- **Review focus**: verify that unresolved follow-up no longer inherits generic dismiss semantics, that terminal success stays no-action-needed, and that the shell still uses canonical links with no new lifecycle or persistence. + +## OperationRun UX Impact *(mandatory)* + +- **Touches OperationRun start/completion/link UX?**: yes +- **Shared OperationRun UX contract/layer reused**: existing Ops-UX shell contract through `OperationUxPresenter`, `OperationRunUrl`, `OperationRunLinks`, `OperationRunProgressContract`, and `OpsUxBrowserEvents` +- **Delegated start/completion UX behaviors**: current queued-toast wording, canonical Operations links, current browser event contract, and existing terminal DB-notification lifecycle remain delegated to the shared path +- **Local surface-owned behavior that remains**: terminal success vs follow-up copy, lifecycle-sensitive tertiary action wording, and browser-session-only acknowledge or dismiss behavior on the shell host +- **Queued DB-notification policy**: `N/A` - unchanged +- **Terminal notification path**: unchanged central lifecycle mechanism +- **Exception required?**: none + +## Provider Boundary / Platform Core Check *(mandatory)* + +N/A - no shared provider or platform-core seam changes. The slice only tightens shell semantics over existing platform-owned `OperationRun` truth. + +## UI / Surface Guardrail Impact *(mandatory)* + +| Surface / Change | Operator-facing surface change? | Native vs Custom | Shared-Family Relevance | State Layers Touched | Exception Needed? | Low-Impact / `N/A` Note | +|---|---|---|---|---|---|---| +| Tenant shell activity hint | yes | Native Filament + existing Livewire/Blade shell surface | Ops UX lifecycle feedback, canonical run links, browser-session calmness | shell, page, browser-session | no | Tightens terminal outcome semantics only; no new surface family | + +## Decision-First Surface Role *(mandatory)* + +| Surface | Decision Role | Human-in-the-loop Moment | Immediately Visible for First Decision | On-Demand Detail / Evidence | Why This Is Primary or Why Not | Workflow Alignment | Attention-load Reduction | +|---|---|---|---|---|---|---|---| +| Tenant shell activity hint | Primary Decision Surface | Decide whether the most recent terminal outcome needs no action, acknowledgement, or immediate review | Operation label, lifecycle state, terminal recency, one next-step cue, and one canonical primary action | Full diagnostics, logs, payloads, and evidence stay in Operations collection/detail | Primary because this is the post-start and post-completion surface where operators decide whether to keep working or inspect a run | Follows start-surface workflow, not diagnostics-page storage structure | Prevents unresolved follow-up from blending into harmless completion noise | + +## Audience-Aware Disclosure *(mandatory)* + +| Surface | Audience Modes In Scope | Decision-First Default-Visible Content | Operator Diagnostics | Support / Raw Evidence | One Dominant Next Action | Hidden / Gated By Default | Duplicate-Truth Prevention | +|---|---|---|---|---|---|---|---| +| Tenant shell activity hint | operator-MSP | Operation label, terminal success or follow-up state, short recency line, concise next-step cue, and canonical primary action | Detailed failure context, run history, and full outcome diagnostics remain on Operations pages | Raw payloads, log details, and support-only evidence stay off the shell | `View operation` for one visible item or `Review operations` when unresolved follow-up is mixed or grouped | Raw and support detail stay diagnostics-only; acknowledge stays browser-session-only | The shell states the next decision once and does not restate full Monitoring prose | + +## UI/UX Surface Classification *(mandatory)* + +| Surface | Action Surface Class | Surface Type | Likely Next Operator Action | Primary Inspect/Open Model | Row Click | Secondary Actions Placement | Destructive Actions Placement | Canonical Collection Route | Canonical Detail Route | Scope Signals | Canonical Noun | Critical Truth Visible by Default | Exception Type / Justification | +|---|---|---|---|---|---|---|---|---|---|---|---|---|---| +| Tenant shell activity hint | Monitoring hint | Activity shell hint | Decide whether to review a just-finished run or keep working | One explicit `View operation` or grouped `Review operations` action | forbidden | One grouped overflow path to `Show all operations` plus one lifecycle-sensitive tertiary action | none | `/admin/operations` in current tenant context | `/admin/operations/{run}` in current tenant context | Current tenant shell context | Operations / Operation | Terminal success vs unresolved follow-up, recency, and the next action | none | + +## Operator Surface Contract *(mandatory)* + +| Surface | Primary Persona | Decision / Operator Action Supported | Surface Type | Primary Operator Question | Default-visible Information | Diagnostics-only Information | Status Dimensions Used | Mutation Scope | Primary Actions | Dangerous Actions | +|---|---|---|---|---|---|---|---|---|---|---| +| Tenant shell activity hint | Tenant operator | Decide whether a recent terminal outcome is safely complete or still needs review | Start-surface shell hint | Does this run need anything from me now? | Operation label, terminal outcome, recency, concise guidance, and canonical open or review action | Operations detail, logs, and evidence | lifecycle, follow-up-needed, progress-availability | none | `View operation`, `Review operations`, `Show all operations` | none | + +**UI Action Matrix**: `N/A - no Filament Resource, RelationManager, or Page action matrix changes are introduced. The shell hint remains a widget-level monitoring hint with one dominant navigation action and one browser-session tertiary affordance.` + +## Proportionality Review *(mandatory when structural complexity is introduced)* + +- **New source of truth?**: no +- **New persisted entity/table/artifact?**: no +- **New abstraction?**: no +- **New enum/state/reason family?**: no +- **New cross-domain UI framework/taxonomy?**: no +- **Current operator problem**: terminal follow-up still inherits generic dismiss semantics on the shell, which blurs `review needed` with `done, no action needed`. +- **Existing structure is insufficient because**: the current shell already has terminal rows, but its copy and tertiary action rules are still too broad to encode lifecycle-correct operator decisions. +- **Narrowest correct implementation**: refine the existing shell component, helper text, and browser-session tertiary actions only, then document the rule in the standards file. +- **Ownership cost**: small shell branching plus focused Feature and browser coverage. +- **Alternative intentionally rejected**: a copy-only tweak was rejected because it would not define the durable `success vs follow-up` action contract or stop future regression. +- **Release truth**: current-release truth. The repo already ships the shell activity surface; this slice only hardens its terminal semantics. + +### Compatibility posture + +This feature assumes a pre-production environment. + +Backward compatibility, legacy aliases, migration shims, historical fixtures, and compatibility-specific tests are out of scope unless explicitly required by this spec. + +Canonical replacement is preferred over preservation. + +## Testing / Lane / Runtime Impact *(mandatory)* + +- **Test purpose / classification**: Feature plus one required browser smoke for the shell action and overlap contract +- **Validation lane(s)**: fast-feedback, confidence, browser +- **Why this classification and these lanes are sufficient**: Feature coverage proves the lifecycle-specific terminal copy, tertiary actions, and browser-session-only acknowledgement semantics on the current shell host. The browser smoke is the narrowest credible proof that the tightened terminal actions stay reachable without reintroducing obstruction. +- **New or expanded test families**: extend `apps/platform/tests/Feature/OpsUx/ActivityFeedbackSurfaceTest.php`, extend `apps/platform/tests/Feature/OpsUx/BulkOperationProgressDbOnlyTest.php`, and extend `apps/platform/tests/Browser/OpsUx/OperationActivityFeedbackSmokeTest.php` +- **Fixture / helper cost impact**: low to moderate. Reuse current `OperationRun` factories, tenant helpers, and shell smoke helpers; do not add provider-heavy setup or persisted acknowledgement fixtures. +- **Heavy-family visibility / justification**: no heavy-governance family. The browser proof stays explicit and bounded to the shell overlap plus action-label contract. +- **Special surface test profile**: global-context-shell +- **Standard-native relief or required special coverage**: standard Feature coverage is primary; one browser smoke remains required for live shell interaction +- **Reviewer handoff**: reviewers must confirm that successful terminal items stay dismissible, unresolved follow-up uses acknowledge or review semantics, terminal rows never show progressbars, and the shell still reopens on new run-enqueue events. +- **Budget / baseline / trend impact**: small feature-local increase only +- **Escalation needed**: `reject-or-split` if implementation widens into dashboard cards, new notification policy, new persistence, or a second `OperationRun` lifecycle surface +- **Active feature PR close-out entry**: Guardrail / Smoke Coverage +- **Planned validation commands**: + - `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/OpsUx/ActivityFeedbackSurfaceTest.php tests/Feature/OpsUx/BulkOperationProgressDbOnlyTest.php` + - `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Browser/OpsUx/OperationActivityFeedbackSmokeTest.php` + +## User Scenarios & Testing *(mandatory)* + +### User Story 1 - Terminal Success Feels Complete, Not Risky (Priority: P1) + +As a tenant operator, I need successful terminal runs to stay briefly visible with calm success semantics, so I can see that work finished and dismiss it without confusing it with unresolved follow-up. + +**Why this priority**: post-completion trust is the first decision point after work finishes, and it should stay explicitly no-action-needed. + +**Independent Test**: seed one recently completed successful run, open a tenant-scoped shell surface, and verify the shell shows success-specific copy, no active progress UI, and `Dismiss` or `Close` semantics during the current 30-second shell-visible success window preserved by `ActiveRuns`. + +**Acceptance Scenarios**: + +1. **Given** a run completes successfully, **When** the shell refreshes during the current 30-second terminal-success grace window from `ActiveRuns::shellVisibleQueryForTenantId()`, **Then** the shell shows success-specific copy, keeps `View operation` as the canonical primary action, and uses `Dismiss` or `Close` as the tertiary action. +2. **Given** a recent successful terminal item is visible, **When** the shell renders it, **Then** it does not show a determinate or indeterminate progress bar. + +--- + +### User Story 2 - Terminal Follow-Up Stays Explicitly Reviewable (Priority: P1) + +As a tenant operator, I need unresolved terminal runs to stay clearly review-worthy on the shell, so I do not treat a failed or partial outcome as safely dismissible noise. + +**Why this priority**: unresolved terminal outcomes are the real decision risk in the current shell semantics. + +**Independent Test**: seed one failed, partial, or blocked terminal run, open a tenant-scoped shell surface, and verify the shell shows follow-up-specific copy, canonical review or detail navigation, and `Acknowledge` instead of generic dismiss semantics. + +**Acceptance Scenarios**: + +1. **Given** a run completes with a follow-up-needed outcome, **When** the shell refreshes, **Then** the shell keeps the run visible with follow-up-specific guidance and uses `Acknowledge` as the local tertiary action. +2. **Given** both active work and unresolved terminal follow-up are visible, **When** the shell chooses its grouped primary action, **Then** the grouped action favors review-oriented wording over no-action-needed success wording. +3. **Given** a terminal follow-up item is visible, **When** the shell renders it, **Then** it does not show active-progress UI and does not use `Dismiss` as the tertiary label. + +--- + +### User Story 3 - Acknowledge Stays Browser-Session Only (Priority: P1) + +As a tenant operator, I need terminal follow-up acknowledgement to stay local to my current browser session, so the shell can stay calm without inventing a server-side review state. + +**Why this priority**: the slice must remain a presentation refinement, not a new workflow system. + +**Independent Test**: acknowledge an unresolved terminal item in the current browser session, then trigger a new run-enqueued event and verify the shell reopens without any database-backed acknowledgement state. + +**Acceptance Scenarios**: + +1. **Given** an unresolved terminal item is visible, **When** the operator clicks `Acknowledge`, **Then** the shell hides that item only for the current browser session. +2. **Given** the shell is hidden or acknowledged in the current browser session, **When** a new run is enqueued for the current tenant, **Then** the shell reopens so the operator does not miss new work. + +### Edge Cases + +- Successful terminal items must not overwrite unresolved follow-up emphasis when both types are visible in the same shell state. +- Unresolved terminal follow-up must not inherit generic `Dismiss updates` copy when the shell shows a grouped primary action. +- Terminal outcome items must not reintroduce progressbars, indeterminate bars, or fake percentages after completion. +- No tenant context or no `viewAny OperationRun` capability keeps the shell inert and leak-free. +- Browser-session acknowledgement must not mutate the `OperationRun` record or persist across new browser sessions. +- The current 30-second terminal-success grace window from `ActiveRuns::shellVisibleQueryForTenantId()` remains the source of truth unless a later spec deliberately changes it. + +## Requirements *(mandatory)* + +**Constitution alignment summary**: This feature adds no Graph calls, no new write path, no new `OperationRun` lifecycle, no new notification policy, and no new persistence. It reuses the current shell activity surface, current progress contract, current canonical links, and current browser-session event flow. + +### Functional Requirements + +- **FR-001**: The shell MUST continue deriving terminal outcome presentation from existing `OperationRun` truth and current shared helpers. It MUST NOT create a second lifecycle, status taxonomy, or persisted shell state. +- **FR-002**: Recent successful terminal items MUST use success-specific shell copy and a no-action-needed posture, keep canonical Operations navigation, and use `Dismiss` or `Close` as the browser-session tertiary action during the current 30-second terminal-success grace window preserved in `ActiveRuns::shellVisibleQueryForTenantId()`. +- **FR-003**: Unresolved terminal follow-up items that still require operator review MUST use follow-up-specific shell copy and `Acknowledge` as the browser-session tertiary action. They MUST NOT reuse generic dismiss semantics. +- **FR-004**: Mixed shell states that include unresolved terminal follow-up MUST preserve review-oriented emphasis in grouped helper copy and primary action wording. Successful completion MUST NOT dominate unresolved follow-up messaging. +- **FR-005**: Terminal success and terminal follow-up items MUST NOT render determinate or indeterminate progress UI after completion. +- **FR-006**: Canonical `View operation`, grouped `Review operations`, and `Show all operations` links MUST remain helper-generated through the existing shared path. `Review operations` is only the grouped label variant of the canonical collection link. The slice MUST NOT introduce raw route strings. +- **FR-007**: Browser-session `Hide activity`, `Dismiss` or `Close`, and `Acknowledge` behavior MUST remain browser-session-only. The implementation MUST NOT persist acknowledgement or dismiss state on the `OperationRun` record or in a new table. +- **FR-008**: A new accepted run in the current browser session MUST reopen the shell if it was previously hidden or acknowledged locally. +- **FR-009**: `docs/ui/tenantpilot-enterprise-ui-standards.md` MUST record the terminal outcome contract for the shell activity hint, including the difference between success dismissal and follow-up acknowledgement semantics. + +### Authorization and Safety Requirements + +- **AR-001**: Current tenant/admin-plane authorization semantics remain unchanged: out-of-scope tenant access stays deny-as-not-found (`404` semantics) and in-scope visibility continues to reuse server-side `OperationRun` policies. +- **AR-002**: No shell state in this slice may expose a run the actor cannot already reach through the canonical Operations routes. +- **AR-003**: No destructive or mutating action is introduced. Acknowledge and dismiss are browser-session-only presentation controls. + +### Non-Functional Requirements + +- **NFR-001**: Filament remains v5 on Livewire v4. No panel-provider registration change is allowed, and `apps/platform/bootstrap/providers.php` remains authoritative. +- **NFR-002**: No new panel, no new globally searchable resource, and no new asset registration strategy are allowed. +- **NFR-003**: Polling remains bounded to the current shell host. The slice MUST NOT add a second polling loop or a second active-awareness host surface. +- **NFR-004**: Existing progress-contract and badge semantics remain authoritative. No page-local terminal-status color mapping or progress heuristics may be introduced. + +## Deferred Follow-Ups / Explicit Non-Goals + +- Tenant dashboard active-operations summary card +- New `OperationRun` progress-contract or counted-progress rollout work +- Phase or composite progress modeling +- A persisted reviewed, investigated, or acknowledged state over terminal follow-up +- Activity tray or inbox v2 +- Notification-policy changes for queued or terminal lifecycle events + +## Key Entities + +- **Terminal success shell item**: a derived, non-persisted shell item for a recently completed successful run that stays briefly visible, conveys no-action-needed completion, and remains locally dismissible. +- **Terminal follow-up shell item**: a derived, non-persisted shell item for a terminal run that still needs operator review and therefore uses acknowledge or review semantics instead of generic dismissal. +- **Browser-session terminal-outcome state**: non-persisted shell-level state that remembers local hide, dismiss, and acknowledge choices only for the current browser session. + +## Success Criteria *(mandatory)* + +### Measurable Outcomes + +- **SC-001**: Focused Feature proof shows a recent successful terminal item with success-specific copy, no active-progress UI, and `Dismiss` or `Close` semantics. +- **SC-002**: Focused Feature proof shows an unresolved terminal follow-up item with review-specific copy and `Acknowledge` semantics instead of generic dismiss wording. +- **SC-003**: Focused Feature proof shows mixed active-plus-follow-up shell states keeping follow-up emphasis in grouped helper copy and grouped primary action wording. +- **SC-004**: The named browser smoke continues to prove that the shell actions remain reachable, collapse or acknowledge behavior stays browser-session-only, and new run-enqueue events reopen the shell. \ No newline at end of file diff --git a/specs/269-operationrun-terminal-outcome-feedback/tasks.md b/specs/269-operationrun-terminal-outcome-feedback/tasks.md new file mode 100644 index 00000000..a93973ed --- /dev/null +++ b/specs/269-operationrun-terminal-outcome-feedback/tasks.md @@ -0,0 +1,159 @@ +--- +description: "Task list for OperationRun Terminal Outcome Feedback v1" +--- + +# Tasks: OperationRun Terminal Outcome Feedback v1 + +**Input**: Design documents from `specs/269-operationrun-terminal-outcome-feedback/` +**Prerequisites**: `specs/269-operationrun-terminal-outcome-feedback/spec.md`, `specs/269-operationrun-terminal-outcome-feedback/plan.md`, `specs/269-operationrun-terminal-outcome-feedback/checklists/requirements.md` + +**Review Artifact**: `specs/269-operationrun-terminal-outcome-feedback/checklists/requirements.md` is the outcome-of-record for the review outcome class, workflow outcome, and test-governance outcome. If implementation widens into dashboard work, new persistence, or notification-policy changes, update that artifact before continuing. + +**Tests**: REQUIRED (Pest). Keep proof bounded to focused Feature coverage for terminal-outcome shell semantics plus one named browser smoke for the live shell interaction contract. +**Operations**: No new `OperationRun` type, no new queue family, no new notification policy, and no new lifecycle ownership. Existing queued toasts, terminal notifications, canonical Operations drill-through routes, and browser enqueue events remain authoritative. +**RBAC**: Reuse current `OperationRun` policies and tenant context guards. No tenantless leakage from tenant surfaces; the shell stays inert when no selected tenant or `viewAny` capability exists. +**Shared Pattern Reuse**: Reuse `BulkOperationProgress`, `OperationUxPresenter`, `OperationStatusNormalizer`, `OperationRunProgressContract`, `OperationRunLinks`, `OperationRunUrl`, `ActiveRuns`, `OpsUxBrowserEvents`, and `docs/ui/tenantpilot-enterprise-ui-standards.md`. Do not create a second lifecycle, a second shell host, or a DB-backed acknowledgement model. +**Filament / Panel Guardrails**: Filament remains v5 on Livewire v4. Provider registration remains unchanged in `apps/platform/bootstrap/providers.php`. No new panel, resource, or asset strategy is allowed. +**Organization**: Tasks are grouped by user story so terminal success, unresolved follow-up, and browser-session acknowledgement remain independently reviewable. + +## Test Governance Notes + +- Lane mix stays Feature plus one named browser smoke for live shell interaction. +- Prefer extending `ActivityFeedbackSurfaceTest`, `BulkOperationProgressDbOnlyTest`, and `OperationActivityFeedbackSmokeTest` before adding any new families. +- Planned proving test commands must stay identical to `spec.md` and `plan.md` and run through Sail. Formatting remains a separate repo-hygiene step. + +## Phase 1: Setup (Shared Context) + +**Purpose**: confirm the bounded slice, the current shell truth, and the current proof owners before runtime edits begin. + +- [x] T001 Review `specs/269-operationrun-terminal-outcome-feedback/spec.md`, `specs/269-operationrun-terminal-outcome-feedback/plan.md`, `specs/269-operationrun-terminal-outcome-feedback/checklists/requirements.md`, `docs/product/spec-candidates.md`, `docs/product/roadmap.md`, and `.specify/memory/constitution.md` together so the slice stays on the bounded manual-promotion target. +- [x] T002 [P] Confirm the current terminal-outcome seams in `apps/platform/app/Livewire/BulkOperationProgress.php`, `apps/platform/resources/views/livewire/bulk-operation-progress.blade.php`, `apps/platform/app/Support/OpsUx/OperationUxPresenter.php`, `apps/platform/app/Support/OpsUx/ActiveRuns.php`, and `apps/platform/public/js/tenantpilot/ops-ux-progress-widget-poller.js`. +- [x] T003 [P] Confirm the current proof owners in `apps/platform/tests/Feature/OpsUx/ActivityFeedbackSurfaceTest.php`, `apps/platform/tests/Feature/OpsUx/BulkOperationProgressDbOnlyTest.php`, `apps/platform/tests/Browser/OpsUx/OperationActivityFeedbackSmokeTest.php`, and `docs/ui/tenantpilot-enterprise-ui-standards.md`. + +--- + +## Phase 2: Foundational (Blocking Prerequisites) + +**Purpose**: settle the proof seams before runtime edits widen. + +**Critical**: no user-story runtime work should begin until this phase is complete. + +- [x] T004 [P] Create or extend failing Feature coverage in `apps/platform/tests/Feature/OpsUx/ActivityFeedbackSurfaceTest.php` for no-tenant and no-capability suppression, no inaccessible run exposure, terminal success copy, follow-up-specific `Acknowledge` semantics, grouped follow-up emphasis, and the absence of progress UI after terminal transition. +- [x] T005 [P] Extend `apps/platform/tests/Feature/OpsUx/BulkOperationProgressDbOnlyTest.php` only as needed for single-item versus grouped primary-action wording, grouped `Review operations` as the label variant of the canonical collection link, and terminal tertiary-action labels. +- [x] T006 [P] Extend `apps/platform/tests/Browser/OpsUx/OperationActivityFeedbackSmokeTest.php` for browser-session-only acknowledge or dismiss behavior and shell re-open on new run-enqueue events. + +**Checkpoint**: the success-versus-follow-up proof owner is settled before implementation begins. + +--- + +## Phase 3: User Story 1 - Terminal Success Feels Complete, Not Risky (Priority: P1) + +**Goal**: keep successful terminal runs briefly visible with success-specific copy and dismissible browser-session behavior. + +**Independent Test**: seed a recent successful terminal run, open a tenant-scoped shell surface, and verify the shell shows success-specific copy, no progress UI, and `Dismiss` or `Close` semantics. + +### Implementation for User Story 1 + +- [x] T007 [US1] Refine `apps/platform/resources/views/livewire/bulk-operation-progress.blade.php` and `apps/platform/app/Support/OpsUx/OperationUxPresenter.php` so successful terminal items render success-specific helper or guidance copy and keep `Dismiss` or `Close` semantics without reusing follow-up wording. Existing `OperationUxPresenter` success guidance was sufficient; no presenter edit was needed. +- [x] T008 [US1] Update `apps/platform/app/Livewire/BulkOperationProgress.php` and `apps/platform/app/Support/OpsUx/ActiveRuns.php` only as needed so recent successful terminal items remain visible for the current 30-second `ActiveRuns` grace window without changing active-run truth or polling ownership. Existing `ActiveRuns` grace-window truth remained correct; no runtime edit was needed. + +**Checkpoint**: User Story 1 is independently functional when successful terminal rows stay calm and dismissible. + +--- + +## Phase 4: User Story 2 - Terminal Follow-Up Stays Explicitly Reviewable (Priority: P1) + +**Goal**: keep unresolved terminal outcomes clearly review-worthy instead of generically dismissible. + +**Independent Test**: seed failed, partial, or blocked terminal runs, open a tenant-scoped shell surface, and verify the shell shows follow-up-specific guidance, `Acknowledge` semantics, and review-oriented grouped copy when mixed with active work. + +### Implementation for User Story 2 + +- [x] T009 [US2] Refine `apps/platform/resources/views/livewire/bulk-operation-progress.blade.php` and `apps/platform/app/Support/OpsUx/OperationUxPresenter.php` so unresolved terminal follow-up uses explicit review-needed copy, `Acknowledge` tertiary wording, and review-oriented grouped helper text. Existing `OperationUxPresenter` follow-up guidance was sufficient; the shell view owns the label split. +- [x] T010 [US2] Update `apps/platform/app/Support/OpsUx/ActiveRuns.php` and `apps/platform/app/Livewire/BulkOperationProgress.php` only as needed so unresolved terminal follow-up remains shell-visible until locally acknowledged and never reuses success dismissal semantics. Existing shell-visible query and Livewire state already kept terminal follow-up visible; no edit was needed. + +**Checkpoint**: User Story 2 is independently functional when unresolved terminal follow-up stops looking like harmless completion noise. + +--- + +## Phase 5: User Story 3 - Acknowledge Stays Browser-Session Only (Priority: P1) + +**Goal**: preserve shell calmness without creating a new persisted review state. + +**Independent Test**: acknowledge an unresolved terminal item in the current browser session, trigger a new run-enqueue event, and verify the shell reopens without any DB-backed acknowledgement state. + +### Implementation for User Story 3 + +- [x] T011 [US3] Update `apps/platform/public/js/tenantpilot/ops-ux-progress-widget-poller.js`, `apps/platform/app/Livewire/BulkOperationProgress.php`, and the shell view only as needed so `Hide activity`, `Dismiss` or `Close`, and `Acknowledge` remain browser-session-only and new work reopens the shell. Existing poller sessionStorage and `ops-ux:run-enqueued` reopen behavior covered the renamed tertiary semantics; browser smoke verified no JS edit was needed. +- [x] T012 [US3] Update `docs/ui/tenantpilot-enterprise-ui-standards.md` with the shell terminal-outcome contract, including success dismissal, follow-up acknowledgement, and the ban on generic dismiss semantics for unresolved follow-up. + +**Checkpoint**: User Story 3 is independently functional when terminal-outcome calmness stays local to the browser session and the standards document records the durable rule. + +--- + +## Phase 6: Polish & Cross-Cutting Validation + +**Purpose**: validate the bounded slice, stop drift, and hand off a clean implementation path. + +- [x] T013 [P] Run `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/OpsUx/ActivityFeedbackSurfaceTest.php tests/Feature/OpsUx/BulkOperationProgressDbOnlyTest.php`. +- [x] T014 [P] Run `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Browser/OpsUx/OperationActivityFeedbackSmokeTest.php`. +- [x] T015 [P] Run `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` for touched platform files. +- [x] T016 [P] Review touched code to confirm Filament stays on Livewire v4, provider registration remains unchanged in `apps/platform/bootstrap/providers.php`, no new globally searchable resource was introduced, and no asset registration or notification-policy change slipped into the slice. + +--- + +## Dependencies & Execution Order + +### Phase Dependencies + +- **Phase 1 (Setup)**: no dependencies; start immediately. +- **Phase 2 (Foundational)**: depends on Phase 1 and blocks user-story work. +- **Phase 3 (US1)**: depends on Phase 2 and establishes success-specific terminal semantics. +- **Phase 4 (US2)**: depends on Phase 2 and should ship with US1 so terminal outcomes stay honest across both no-action-needed and follow-up-needed cases. +- **Phase 5 (US3)**: depends on Phase 2 and hardens browser-session-only calmness after terminal semantics exist. +- **Phase 6 (Polish)**: depends on all desired user stories being complete. + +### User Story Dependencies + +- **US1 (P1)**: independently testable after Phase 2 and delivers the no-action-needed terminal success contract. +- **US2 (P1)**: independently testable after Phase 2 and should ship with US1 so unresolved follow-up never inherits success semantics. +- **US3 (P1)**: independently testable after Phase 2 and is required to keep the slice presentation-only instead of inventing a persisted workflow state. + +### Within Each User Story + +- Write or extend the listed Pest coverage first and make it fail for the intended gap. +- Land shell-host runtime changes before widening browser-session action behavior. +- Re-run the narrowest affected validation command after each story checkpoint before moving on. + +--- + +## Implementation Strategy + +### Suggested MVP Scope + +- MVP = **US1 + US2 + US3 together**. The manual-promotion target is only complete when terminal success is calm, unresolved follow-up is explicitly reviewable, and acknowledgement stays browser-session-only. + +### Incremental Delivery + +1. Complete Phase 1 and Phase 2. +2. Deliver US1. +3. Deliver US2 on top of the same shell host. +4. Add US3 browser-session-only action hardening and the standards update. +5. Finish with the focused Feature proof and the named browser smoke. + +### Team Strategy + +1. Settle the shell proof owner first. +2. Parallelize Feature and browser proof updates while keeping the runtime change local to the existing shell component. +3. Serialize merges around `BulkOperationProgress` and the standards document so the terminal-outcome contract stays coherent. + +--- + +## Deferred Follow-Ups / Non-Goals + +- Tenant dashboard active-operations summary card +- New `OperationRun` progress-contract or rollout work +- Phase or composite progress modeling +- Persistent reviewed, investigated, or acknowledged semantics over unresolved terminal runs +- Activity center or tray v2 +- Notification-policy changes for queued or terminal lifecycle events