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 <ahmed.darrazi@live.de>
Reviewed-on: #331
This commit is contained in:
ahmido 2026-05-05 15:40:52 +00:00
parent b2ec2f032f
commit 50bc44cfa0
9 changed files with 903 additions and 18 deletions

View File

@ -17,6 +17,7 @@
$hasActiveVisibleRuns && $hasTerminalFollowUpVisibleRuns => 'Active and recent operation updates that may need review.', $hasActiveVisibleRuns && $hasTerminalFollowUpVisibleRuns => 'Active and recent operation updates that may need review.',
$hasActiveVisibleRuns => 'Queued and running work stays here until diagnostics are needed.', $hasActiveVisibleRuns => 'Queued and running work stays here until diagnostics are needed.',
$hasTerminalFollowUpVisibleRuns => 'Recent operation updates that may need review.', $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.', default => 'Recent operation updates.',
}; };
$primaryActionUrl = null; $primaryActionUrl = null;
@ -27,9 +28,9 @@
$primaryActionUrl = \App\Support\OpsUx\OperationRunUrl::view($primaryRun, $tenant); $primaryActionUrl = \App\Support\OpsUx\OperationRunUrl::view($primaryRun, $tenant);
} }
$tertiaryActionLabel = $hasActiveVisibleRuns $tertiaryActionLabel = $hasTerminalFollowUpVisibleRuns
? 'Hide activity' ? 'Acknowledge'
: ($hasTerminalFollowUpVisibleRuns ? ($visibleRunCount > 1 ? 'Dismiss updates' : 'Dismiss') : 'Dismiss'); : ($hasActiveVisibleRuns ? 'Hide activity' : 'Dismiss');
@endphp @endphp
{{-- Cleanup is delegated to the shared poller helper, which uses teardownObserver and new MutationObserver. --}} {{-- Cleanup is delegated to the shared poller helper, which uses teardownObserver and new MutationObserver. --}}

View File

@ -269,3 +269,87 @@ function operationActivityFeedbackSmokeLoginUrl(User $user, Tenant $tenant, stri
->assertNoJavaScriptErrors() ->assertNoJavaScriptErrors()
->assertNoConsoleLogs(); ->assertNoConsoleLogs();
}); });
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();
});

View File

@ -3,10 +3,90 @@
use App\Filament\Resources\InventoryItemResource; use App\Filament\Resources\InventoryItemResource;
use App\Livewire\BulkOperationProgress; use App\Livewire\BulkOperationProgress;
use App\Models\OperationRun; use App\Models\OperationRun;
use App\Models\Tenant;
use App\Support\OpsUx\OperationRunUrl; use App\Support\OpsUx\OperationRunUrl;
use Filament\Facades\Filament; use Filament\Facades\Filament;
use Livewire\Livewire; 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 { it('renders three visible run summaries while grouping banner actions in one action area', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner'); [$user, $tenant] = createUserWithTenant(role: 'owner');
@ -119,7 +199,7 @@
expect($component->get('hasActiveRuns'))->toBeFalse() expect($component->get('hasActiveRuns'))->toBeFalse()
->and($html)->toContain('Operation updates') ->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('Completed successfully')
->and($pageText)->toContain('No action needed.') ->and($pageText)->toContain('No action needed.')
->and($html)->toContain('View operation') ->and($html)->toContain('View operation')
@ -130,7 +210,7 @@
->and($html)->not->toContain('role="progressbar"'); ->and($html)->not->toContain('role="progressbar"');
})->group('ops-ux'); })->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'); [$user, $tenant] = createUserWithTenant(role: 'owner');
$this->actingAs($user); $this->actingAs($user);
@ -157,10 +237,11 @@
expect($component->get('hasActiveRuns'))->toBeFalse() expect($component->get('hasActiveRuns'))->toBeFalse()
->and($html)->toContain('Operation updates') ->and($html)->toContain('Operation updates')
->and($pageText)->toContain('Recent operation updates that may need review.') ->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)->toContain('View operation')
->and($html)->not->toContain('Review operations') ->and($html)->not->toContain('Review operations')
->and($html)->toContain('Dismiss') ->and($html)->toContain('Acknowledge')
->and($html)->not->toContain('Acknowledge') ->and($html)->not->toContain('Dismiss')
->and($html)->not->toContain('data-testid="ops-ux-activity-feedback-indeterminate"') ->and($html)->not->toContain('data-testid="ops-ux-activity-feedback-indeterminate"')
->and($html)->not->toContain('role="progressbar"') ->and($html)->not->toContain('role="progressbar"')
->and($html)->not->toContain('Hide activity'); ->and($html)->not->toContain('Hide activity');
@ -204,7 +285,8 @@
->and($html)->toContain('Operation updates') ->and($html)->toContain('Operation updates')
->and($pageText)->toContain('Active and recent operation updates that may need review.') ->and($pageText)->toContain('Active and recent operation updates that may need review.')
->and($html)->not->toContain('View operation') ->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)->toContain(OperationRunUrl::index($tenant))
->and($html)->not->toContain(OperationRunUrl::view($runningRun, $tenant)) ->and($html)->not->toContain(OperationRunUrl::view($runningRun, $tenant))
->and($pageText)->toContain('Execution failed'); ->and($pageText)->toContain('Execution failed');

View File

@ -154,8 +154,8 @@
->assertSee('Inventory sync') ->assertSee('Inventory sync')
->assertSee('View operation') ->assertSee('View operation')
->assertDontSee('Review operations') ->assertDontSee('Review operations')
->assertSee('Dismiss') ->assertSee('Acknowledge')
->assertDontSee('Acknowledge') ->assertDontSee('Dismiss')
->assertDontSee('Waiting for worker.') ->assertDontSee('Waiting for worker.')
->assertDontSee('Hide activity'); ->assertDontSee('Hide activity');
})->group('ops-ux'); })->group('ops-ux');
@ -192,7 +192,8 @@
->assertSee('Review operations') ->assertSee('Review operations')
->assertDontSee('View operation') ->assertDontSee('View operation')
->assertSee('Show all operations') ->assertSee('Show all operations')
->assertSee('Hide activity'); ->assertSee('Acknowledge')
->assertDontSee('Dismiss updates');
})->group('ops-ux'); })->group('ops-ux');
it('shows likely stale runs in the progress overlay and keeps polling when only stale runs remain', function () { it('shows likely stale runs in the progress overlay and keeps polling when only stale runs remain', function () {

View File

@ -1318,8 +1318,7 @@ # TenantPilot Enterprise UI Standards**Status:** Active **Owner:** Product / En
- Hide activity for queued or running rows - Hide activity for queued or running rows
- Dismiss or Close for terminal-success rows - Dismiss or Close for terminal-success rows
- Dismiss for a single unresolved terminal follow-up row - Acknowledge for unresolved terminal follow-up rows, including grouped states
- Dismiss updates when multiple unresolved terminal follow-up rows are visible and no active work remains
use Show all operations to open the canonical Operations collection with the current tenant encoded as the contextual tenant_id prefilter 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 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: record these as follow-up work instead of widening the current contract:
- provider health or support-diagnostics progress rollout - 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 - child-run graph persistence or composite child-link expansion
- dashboard cards or workflow-engine generated progress explanations - 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: It MUST NOT:
render as a detached document-level BODY_START banner above the TenantPilot application chrome 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 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 render as a fixed overlay that can cover findings row actions, table actions, or page-primary actions
30. Maintenance Rules 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 Do not imply Customer Review Workspace maturity unless repo-verifiedUse Review Pack / Export Artifact wording when that is what exists
Blade Blade
Render onlyDo not invent domain logicDo not invent routesDo not infer capabilities Render onlyDo not invent domain logicDo not invent routesDo not infer capabilities

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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