spec: refine 057 + extend 058 #67

Merged
ahmido merged 28 commits from 058-tenant-ui-polish into dev 2026-01-21 11:29:42 +00:00
11 changed files with 672 additions and 22 deletions
Showing only changes of commit 8b17bbe9be - Show all commits

View File

@ -11,6 +11,7 @@ ## Active Technologies
- PHP 8.4.x (Laravel 12) + Laravel 12, Filament v4, Livewire v3 (feat/047-inventory-foundations-nodes)
- PostgreSQL (JSONB for `InventoryItem.meta_jsonb`) (feat/047-inventory-foundations-nodes)
- PostgreSQL (JSONB in `operation_runs.context`, `operation_runs.summary_counts`) (056-remove-legacy-bulkops)
- PHP 8.4.15 (Laravel 12.47.0) + Filament v5.0.0, Livewire v4.0.1 (058-tenant-ui-polish)
- PHP 8.4.15 (feat/005-bulk-operations)
@ -30,9 +31,9 @@ ## Code Style
PHP 8.4.15: Follow standard conventions
## Recent Changes
- 058-tenant-ui-polish: Added PHP 8.4.15 (Laravel 12.47.0) + Filament v5.0.0, Livewire v4.0.1
- 058-tenant-ui-polish: Added [if applicable, e.g., PostgreSQL, CoreData, files or N/A]
- 056-remove-legacy-bulkops: Added PHP 8.4.x + Laravel 12, Filament v4, Livewire v3
- feat/047-inventory-foundations-nodes: Added PHP 8.4.x (Laravel 12) + Laravel 12, Filament v4, Livewire v3
- feat/042-inventory-dependencies-graph: Added PHP 8.4.x + Laravel 12, Filament v4, Livewire v3
<!-- MANUAL ADDITIONS START -->

View File

@ -31,5 +31,5 @@ ## Feature Readiness
## Notes
- This is a technical upgrade, but the spec intentionally describes outcomes and guardrails rather than implementation steps.
- Proceed to planning: identify concrete packages, versions, and migration steps in plan/tasks.
- This is a technical upgrade, but the spec describes outcomes and guardrails rather than implementation steps.
- Planning can capture concrete versions, dependency changes, and migration steps.

View File

@ -3,7 +3,7 @@ # Feature Specification: Admin UI Stack Upgrade (Panel + Suite)
**Feature Branch**: `057-filament-v5-upgrade`
**Created**: 2026-01-20
**Status**: Draft
**Input**: Upgrade the existing admin UI stack to the next supported major release to maintain compatibility and support, and ensure no regressions for tenant isolation and Ops-UX/Monitoring guardrails.
**Input**: Upgrade the existing admin UI stack to the next supported major release to maintain compatibility and support, and ensure no regressions for tenant isolation and Monitoring/Operations safety guardrails.
## Clarifications
@ -19,7 +19,7 @@ ## User Scenarios & Testing *(mandatory)*
### User Story 1 - Admin UI keeps working after upgrade (Priority: P1)
Administrators can sign in and use the admin panel (navigation, resources, forms, tables, actions) without runtime errors after the upgrade.
Administrators can sign in and use the admin panel (navigation, lists, forms, actions) without runtime errors after the upgrade.
**Why this priority**: This is the minimum bar for the upgrade to be safe; if the admin UI is unstable, all operational work stops.
@ -75,18 +75,18 @@ ## Requirements *(mandatory)*
### Functional Requirements
- **FR-001**: The application MUST upgrade the admin panel stack to the next supported major release and its required compatible reactive UI layer.
- **FR-002**: The application MUST continue to support the existing styling system without asset build failures.
- **FR-003**: All existing Filament panels MUST load successfully for authorized users and preserve core interactions (navigation, tables, forms, actions, notifications, widgets).
- **FR-004**: SPA-style navigation flows MUST continue to work, including global widget mounting and event delivery across navigation.
- **FR-005**: Everything rendered under the Monitoring → Operations navigation section (including widgets/partials/tabs) MUST remain DB-only: no outbound HTTP requests are permitted during page render or during background/automatic Livewire requests (polling/auto-refresh/hydration).
- **FR-010**: Any remote work initiated from Monitoring/Operations pages MUST be triggered only by explicit user actions (e.g., buttons) and MUST enqueue tracked operations (e.g., `OperationRun`-backed jobs) rather than performing outbound HTTP inline.
- **FR-006**: Tenant isolation MUST be preserved across requests and Live UI interactions: all reads/writes/events/caches MUST scope to the active tenant.
- **FR-007**: Compatibility risks MUST be managed by producing an explicit inventory of affected third-party packages and documenting upgrade/replacement decisions.
- **FR-011**: If a third-party package is incompatible with the upgraded stack, the system MUST preserve equivalent functionality by upgrading or replacing the package; mixed-version pinning is not allowed. Any unavoidable feature loss MUST be handled as an explicit scope/decision change.
- **FR-012**: Database migrations are allowed only if strictly required for compatibility; they MUST be reversible and non-destructive (no data loss). Migrations MUST include a safe `down()` path and avoid drops without an explicit plan, and MUST be mentioned in release notes.
- **FR-001**: The system MUST upgrade the admin UI stack to the next supported major release and remain fully functional for all in-scope admin workflows.
- **FR-002**: The system MUST continue to support the existing styling and asset pipeline without build failures.
- **FR-003**: All existing admin pages MUST load successfully for authorized users and preserve core interactions (navigation, lists, forms, actions, notifications, and global UI elements).
- **FR-004**: In-app navigation between admin pages MUST continue to work reliably, including any global progress indicators and event-driven UI behavior.
- **FR-005**: Everything rendered under the Monitoring → Operations navigation section (including widgets/partials/tabs) MUST remain DB-only: no outbound HTTP requests are permitted during page render or during background/automatic requests (polling/auto-refresh/hydration).
- **FR-006**: Tenant isolation MUST be preserved across requests and interactive UI behavior: all reads/writes/events/caches MUST scope to the active tenant.
- **FR-007**: Compatibility risks MUST be managed by producing an explicit inventory of affected third-party dependencies and documenting upgrade/replacement decisions.
- **FR-008**: The upgrade MUST not introduce new Microsoft Graph read/write behavior; if any Graph-touching behavior changes are required, they MUST be explicitly specified with safety gates and observability updates.
- **FR-009**: The upgrade MUST include a documented rollback procedure that restores the previous working state.
- **FR-010**: Any remote work initiated from Monitoring/Operations pages MUST be triggered only by explicit user actions and MUST enqueue a tracked operation (with an observable run record) rather than performing outbound HTTP inline.
- **FR-011**: If a third-party dependency is incompatible with the upgraded stack, the system MUST preserve equivalent functionality by upgrading or replacing the dependency; mixed-version pinning is not allowed. Any unavoidable feature loss MUST be handled as an explicit scope/decision change.
- **FR-012**: Database migrations are allowed only if strictly required for compatibility; they MUST be reversible and non-destructive (no data loss) and MUST be mentioned in release notes.
### Assumptions & Dependencies
@ -97,7 +97,7 @@ ### Assumptions & Dependencies
### Key Entities *(include if feature involves data)*
- **Tenant**: The active tenant context that scopes all data access and UI state.
- **OperationRun**: The persisted record of long-running operations shown in Monitoring/Operations views.
- **Run Record**: The persisted record of long-running operations shown in Monitoring/Operations views.
- **AuditLog**: The tenant-scoped audit trail used to retain accountability for sensitive actions.
## Success Criteria *(mandatory)*

View File

@ -0,0 +1,22 @@
# Polling Contract — Calm UI Rules
## Principle
Polling is allowed only when it materially improves UX (active operations). It must be DB-only and must stop when no longer needed.
## Dashboard
- Polling is enabled only while active runs exist (queued/running) for the current tenant.
- Polling is disabled when:
- No active runs exist.
## Operations index
- Polling is enabled only while active runs exist.
- Polling is disabled when:
- No active runs exist.
## Modals
- No polling inside modals.
- When a modal is open, polling should not cause churn in the background.
## Technical approach
- Widgets: use `$pollingInterval = null` to disable polling.
- Tables: apply `$table->poll('10s')` only when active runs exist.

View File

@ -0,0 +1,28 @@
# UI Contracts — Tenant UI Polish
This feature does not introduce HTTP APIs. These contracts describe UI routing, filters, and definitions that must remain stable.
## Routes (tenant-scoped)
- Dashboard: tenant dashboard page (new custom page; replaces default dashboard entry).
- Inventory hub: Inventory cluster root (routes to first page/resource in cluster).
- Inventory items: Inventory items resource index, under cluster prefix.
- Inventory sync runs: Inventory sync runs resource index, under cluster prefix.
- Inventory coverage: Inventory coverage page, under cluster prefix.
- Operations index: `OperationRunResource` index (`/operations`).
- Operation run detail: `OperationRunResource` view page.
## Operations Tabs (FR-009)
Tabs filter the Operations table by:
- All: no extra constraints.
- Active: `status IN ('queued','running')`
- Succeeded: `status = 'completed' AND outcome = 'succeeded'`
- Partial: `status = 'completed' AND outcome = 'partial'`
- Failed: `status = 'completed' AND outcome = 'failed'`
## KPI Definitions
- Inventory coverage % = Restorable / Total (Partial is separate, does not inflate %).
- Drift stale threshold = 7 days.
- “Recent” lists default size = 10.
- “Active operations” shows two counts:
- All active runs (queued + running)
- Inventory-active runs (type = `inventory.sync`, queued + running)

View File

@ -0,0 +1,76 @@
# Data Model — Tenant UI Polish
This feature is read-only. It introduces no schema changes.
## Entities
### Tenant
- **Role**: scope boundary for all queries.
- **Source**: `Tenant::current()` (Filament tenancy).
### OperationRun
- **Role**: operations feed, KPIs, and canonical “View run” destinations.
- **Key fields used** (existing):
- `tenant_id`
- `type`
- `status` (`queued|running|completed`)
- `outcome` (`succeeded|partial|failed|...`)
- `created_at`, `started_at`, `completed_at`
- `summary_counts`, `failure_summary` (JSONB)
**Derived values**:
- **Active**: `status IN ('queued','running')`
- **Terminal**: `status = 'completed'`
- **Avg duration (7 days)**: only terminal runs with `started_at` and `completed_at`.
### InventoryItem
- **Role**: inventory totals and coverage chips.
- **Key fields used** (existing, inferred from resources):
- `tenant_id`
- coverage-related flags / fields used to categorize: Restorable, Partial, Risk, Dependencies
**Derived values**:
- Total items
- Coverage % = `restorable / total` (if total > 0)
- Chip counts: Restorable, Partial, Risk, Dependencies
### InventorySyncRun
- **Role**: “Last Inventory Sync” and “Sync Runs” list.
- **Key fields used**:
- `tenant_id`
- status + timestamps
- any “selection_hash / selection payload” metadata used for display
### Finding (Drift Finding)
- **Role**: drift KPIs and “Needs Attention”.
- **Key fields used** (existing migration):
- `tenant_id`
- `severity` (enum-like string)
- `status` (open/closed)
- timestamps
- `scope_key` for grouping
**Derived values**:
- Open findings by severity
- Staleness: last drift scan older than 7 days
## KPI Queries (read-only)
### Dashboard
- Drift KPIs: counts of open findings by severity + stale drift indicator.
- Operations health: counts of active runs + failed/partial recent.
- Recent lists: latest 10 findings + latest 10 operation runs.
### Inventory hub
- Total items
- Coverage % (restorable/total)
- Last inventory sync (status + timestamp)
- Active operations: (all active runs) + (inventory.sync active runs)
### Operations index
- Total runs (30d)
- Active runs (queued + running)
- Failed/partial (7d)
- Avg duration (7d, terminal runs only)
All queries must be tenant-scoped.

View File

@ -0,0 +1,164 @@
# Implementation Plan: Tenant UI Polish (Dashboard + Inventory Hub + Operations)
**Branch**: `058-tenant-ui-polish` | **Date**: 2026-01-20 | **Spec**: `specs/058-tenant-ui-polish/spec.md`
**Input**: Feature specification from `specs/058-tenant-ui-polish/spec.md`
**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/scripts/` for helper scripts.
## Summary
- Build a drift-first, tenant-scoped dashboard with “Needs Attention” and recent lists.
- Make Inventory a hub using a Filament cluster to provide consistent left-side sub-navigation across Items / Sync Runs / Coverage.
- Upgrade Operations index to “orders-style” with KPIs + status tabs filtering the existing `OperationRunResource` table.
- Enforce DB-only renders (and DB-only polling) and a calm UI: polling only while active runs exist, and no polling churn in modals.
## Technical Context
<!--
ACTION REQUIRED: Replace the content in this section with the technical details
for the project. The structure here is presented in advisory capacity to guide
the iteration process.
-->
**Language/Version**: PHP 8.4.15 (Laravel 12.47.0)
**Primary Dependencies**: Filament v5.0.0, Livewire v4.0.1
**Storage**: PostgreSQL
**Testing**: Pest v4 (+ PHPUnit v12 runtime)
**Target Platform**: Web application (Filament admin panel)
**Project Type**: Web (Laravel monolith)
**Performance Goals**: Dashboard/Inventory/Operations render quickly (target <2s for typical tenants) with efficient tenant-scoped queries and no N+1.
**Constraints**: DB-only for all page renders and any polling/auto-refresh; avoid UI churn in modals.
**Scale/Scope**: Tenant-scoped surfaces; KPI math on existing `operation_runs`, `findings`, inventory tables.
## Constitution Check
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
- Inventory-first: clarify what is “last observed” vs snapshots/backups
- Read/write separation: any writes require preview + confirmation + audit + tests
- Graph contract path: Graph calls only via `GraphClientInterface` + `config/graph_contracts.php`
- Deterministic capabilities: capability derivation is testable (snapshot/golden tests)
- Tenant isolation: all reads/writes tenant-scoped; cross-tenant views are explicit and access-checked
- Run observability: long-running/remote/queued work creates/reuses `OperationRun`; start surfaces enqueue-only; Monitoring is DB-only; DB-only <2s actions may skip runs but security-relevant ones still audit-log
- Automation: queued/scheduled ops use locks + idempotency; handle 429/503 with backoff+jitter
- Data minimization: Inventory stores metadata + whitelisted meta; logs contain no secrets/tokens
Status: ✅ No constitution violations for this feature (read-only, DB-only, tenant-scoped; no Graph calls added).
## Project Structure
### Documentation (this feature)
```text
specs/058-tenant-ui-polish/
├── plan.md # This file (/speckit.plan command output)
├── research.md # Phase 0 output (/speckit.plan command)
├── data-model.md # Phase 1 output (/speckit.plan command)
├── quickstart.md # Phase 1 output (/speckit.plan command)
├── contracts/ # Phase 1 output (/speckit.plan command)
└── tasks.md # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan)
```
### Source Code (repository root)
<!--
ACTION REQUIRED: Replace the placeholder tree below with the concrete layout
for this feature. Delete unused options and expand the chosen structure with
real paths (e.g., apps/admin, packages/something). The delivered plan must
not include Option labels.
-->
```text
app/
├── Filament/
│ ├── Clusters/ # New: Inventory cluster
│ ├── Pages/ # New/updated: tenant dashboard, inventory landing, coverage
│ ├── Resources/ # Updated: attach inventory resources to cluster; operations tabs/KPIs
│ └── Widgets/ # New/updated: KPI header widgets
├── Models/ # Existing: Tenant, OperationRun, Finding, InventoryItem, InventorySyncRun
└── Providers/Filament/
└── AdminPanelProvider.php # Update: discoverClusters(), dashboard page class
resources/
└── views/ # Optional: partials/views for dashboard sections
tests/
└── Feature/
├── Monitoring/ # Existing: Operations DB-only + tenant scope tests
└── Filament/ # Existing + new: Inventory/Dashboard page tests
```
**Structure Decision**: Laravel monolith + Filament (v5) conventions. Implement UI changes via:
- Filament Pages (dashboard + inventory pages)
- Filament Clusters (inventory sub-navigation)
- Filament Widgets (KPI headers / recent lists)
- Filament Resource list tabs (operations index filtering)
## Complexity Tracking
> **Fill ONLY if Constitution Check has violations that must be justified**
None.
## Phase 0 — Outline & Research (complete)
- Output: `specs/058-tenant-ui-polish/research.md`
- Key decisions captured:
- Use Filament clusters for the Inventory hub sub-navigation.
- Use Filament widgets for KPI headers.
- Enable polling only while active runs exist.
## Phase 1 — Design & Contracts (complete)
### Data model
- Output: `specs/058-tenant-ui-polish/data-model.md`
- No schema changes required.
### UI contracts
- Output: `specs/058-tenant-ui-polish/contracts/ui.md`
- Output: `specs/058-tenant-ui-polish/contracts/polling.md`
### Provider registration (Laravel 11+)
- Panel providers remain registered in `bootstrap/providers.php` (no changes required for this feature unless adding a new provider).
### Livewire / Filament version safety
- Livewire v4.0+ (required by Filament v5) is in use.
### Asset strategy
- Prefer existing Filament theme CSS and hook classes; avoid publishing Filament internal views.
- No heavy assets expected; if any new panel assets are added, ensure deployment runs `php artisan filament:assets`.
### Destructive actions
- None introduced in this feature.
### Constitution re-check (post-design)
- ✅ Inventory-first: dashboard uses Inventory/Findings/OperationRun as last-observed state.
- ✅ Read/write separation: this feature is read-only.
- ✅ Graph contract path: no Graph calls added.
- ✅ Tenant isolation: all queries remain tenant-scoped.
- ✅ Run observability: only consumes existing `OperationRun` records; no new long-running work is introduced.
- ✅ Data minimization: no new payload storage.
## Phase 2 — Implementation Plan (next)
### Story 1 (P1): Drift-first tenant dashboard
- Create a custom Filament dashboard page (tenant-scoped) and wire it in `AdminPanelProvider` instead of the default `Dashboard::class`.
- Implement drift + ops KPIs and “Needs Attention” + recent lists using DB-only Eloquent queries.
- Implement conditional polling (only while active runs exist) using widget polling controls.
- Tests:
- Add DB-only coverage tests for the dashboard (no outbound HTTP; no queued jobs on render).
- Add tenant scope tests for the dashboard.
### Story 2 (P2): Inventory becomes a hub
- Add `discoverClusters()` to `AdminPanelProvider`.
- Create `InventoryCluster` and assign `$cluster` on inventory pages/resources.
- Add a shared inventory KPI header (widget) across the cluster surfaces.
- Tests:
- Extend existing inventory page tests to assert cluster pages load and remain tenant-scoped.
### Story 3 (P3): Operations index “orders-style”
- Update `OperationRunResource` list page to:
- Add KPI header widgets.
- Add tabs: All / Active / Succeeded / Partial / Failed.
- Enable table polling only while active runs exist.
- Tests:
- Extend operations tests to assert page renders with tabs and remains DB-only/tenant-scoped.

View File

@ -0,0 +1,27 @@
# Quickstart — Tenant UI Polish
## Prereqs
- Run everything via Sail.
## Setup
- `vendor/bin/sail up -d`
- `vendor/bin/sail composer install`
## Run tests (targeted)
- `vendor/bin/sail artisan test tests/Feature/Monitoring/OperationsDbOnlyTest.php`
- `vendor/bin/sail artisan test tests/Feature/Monitoring/OperationsTenantScopeTest.php`
- `vendor/bin/sail artisan test tests/Feature/Filament/InventoryPagesTest.php`
When the feature is implemented, add + run:
- Dashboard DB-only + tenant scope tests (new).
## Manual QA (tenant-scoped)
- Sign in, select a tenant.
- Visit Dashboard: verify drift/ops KPIs, needs attention, and recent lists.
- Visit Inventory cluster: Items / Sync Runs / Coverage share left sub-navigation and KPI header.
- Visit Operations (`/operations`): KPI header + tabs filter table.
## Frontend assets
If UI changes dont show:
- `vendor/bin/sail npm run dev`
- or `vendor/bin/sail npm run build`

View File

@ -0,0 +1,66 @@
# Research — Tenant UI Polish (Dashboard + Inventory Hub + Operations)
## Goal
Deliver a drift-first, tenant-scoped UI polish pass that is:
- DB-only on render and on any auto-refresh.
- Calm (polling only when needed; no modal churn).
- Consistent IA (Inventory hub sub-navigation; canonical Operations).
## Existing Code & Patterns (to reuse)
### Operations
- Canonical list/detail already exist via `OperationRunResource` (`/operations`).
- Tenant scoping already enforced in `OperationRunResource::getEloquentQuery()`.
- Detail view already uses conditional polling with safeguards (tab hidden / modal open) via `RunDetailPolling`.
### Inventory
- Inventory entry page exists as `InventoryLanding`.
- Inventory “Items” and “Sync Runs” are currently resources (`InventoryItemResource`, `InventorySyncRunResource`).
- Inventory sync “start surface” already follows constitution rules: authorize → create/reuse `OperationRun` → enqueue job → “View run”.
### Monitoring DB-only + Tenant isolation tests
- Monitoring/Operations has DB-only tests and tenant scope tests.
- Inventory landing + coverage have basic smoke tests.
## Key Decisions
### Decision: Use Filament clusters to implement the Inventory “hub” navigation
- **Decision**: Create an Inventory cluster and attach:
- `InventoryLanding` (page)
- Inventory items resource
- Inventory sync runs resource
- `InventoryCoverage` (page)
- **Rationale**: Filament clusters are designed for “common sub-navigation between pages”, including mixing pages and resources.
- **Notes**:
- Requires enabling cluster discovery in the panel provider.
- Sub-navigation position will be set to `Start` to achieve left-side navigation.
### Decision: Implement KPI headers as widgets (StatsOverviewWidget / TableWidget)
- **Decision**: Use Filament widgets for KPI headers on:
- Tenant dashboard (drift + ops)
- Inventory hub (inventory KPIs)
- Operations index (ops KPIs)
- **Rationale**: Widgets are first-class, composable, and can optionally poll (with `$pollingInterval`) while remaining DB-only.
### Decision: “Calm UI” auto-refresh strategy
- **Decision**:
- Dashboard + Operations index: enable polling only while active runs exist.
- Widgets/tables: polling is disabled when no active runs exist.
- No polling inside modals.
- **Rationale**: Matches FR-012 and avoids background churn.
- **Implementation approach**:
- Use Filament polling mechanisms:
- Widgets: `$pollingInterval = null | '10s'` depending on “active runs exist”.
- Tables: enable `$table->poll('10s')` only when “active runs exist”.
### Decision: No Graph / remote dependencies
- **Decision**: All queries for this feature are Eloquent/PostgreSQL queries.
- **Rationale**: Matches constitution and SC-005.
## Alternatives Considered
- **Custom Blade layouts for hub navigation**: Rejected because clusters provide consistent sub-nav across resources/pages without fragile view overrides.
- **Always-on polling**: Rejected to comply with calm UI rules and avoid waste.
- **Keep `Monitoring/Operations` as canonical**: Rejected because `OperationRunResource` is already the canonical Operations surface with correct routing and detail pages.
## Open Questions
None — all “NEEDS CLARIFICATION” items are resolved for planning.

View File

@ -5,6 +5,19 @@ # Feature Specification: Tenant UI Polish (Dashboard + Inventory Hub + Operation
**Status**: Draft
**Input**: User description: "Feature 058 — Tenant UI Polish: Dashboard + Inventory Hub + Operations \"Orders-style\" (v1)"
## Clarifications
### Session 2026-01-20
- Q: Coverage % definition for Inventory KPI header? → A: Coverage % = Restorable / Total (Partial remains a separate chip/number; main % stays conservative)
- Q: Drift stale threshold (last scan older than X days)? → A: 7 days
- Q: Inventory KPI “Active Operations” definition? → A: Show both counts: All active runs (queued + running) and Inventory-active runs (queued + running)
- Q: How many rows in “Recent” lists by default? → A: 10
### Session 2026-01-21
- Q: Operations index "Stuck" tab in v1? -> A: No "Stuck" tab in v1
## User Scenarios & Testing *(mandatory)*
<!--
@ -34,6 +47,8 @@ ### User Story 1 - Drift-first tenant dashboard (Priority: P1)
2. **Given** there are urgent drift issues (e.g., high severity open findings), **When** I view the Dashboard, **Then** they appear in the “Needs Attention” section with a CTA that navigates to a filtered view.
3. **Given** drift generation has a recent failed run, **When** I view the Dashboard, **Then** I can navigate from “Needs Attention” to the related operation run details.
4. **Given** there is no drift data yet, **When** I view the Dashboard, **Then** the dashboard renders calmly with empty-state messaging and no errors.
5. **Given** the last drift scan is older than 7 days, **When** I view the Dashboard, **Then** “Needs Attention” includes a “Drift stale” item with a CTA to investigate.
6. **Given** there are more than 10 drift findings and operation runs, **When** I view the Dashboard, **Then** each “Recent” list shows the 10 most recent items.
---
@ -101,9 +116,11 @@ ### Functional Requirements
- **FR-004 (Inventory hub layout)**: System MUST provide an Inventory hub with left sub-navigation for Items, Sync Runs, and Coverage.
- **FR-005 (Inventory KPIs)**: Inventory hub MUST show a shared KPI header across Inventory subpages with:
- Total Items
- Coverage % (covered items / total items)
- Coverage % (restorable items / total items; partial shown separately)
- Last Inventory Sync (status + timestamp)
- Active Operations (queued + running)
- Active Operations (queued + running), showing both:
- All active runs
- Inventory-active runs
- **FR-006 (Inventory sync runs view)**: System MUST provide a “Sync Runs” view that lists only inventory synchronization runs.
- **FR-007 (Coverage chips)**: System MUST standardize coverage chips to this set only: Restorable, Partial, Risk, Dependencies.
- **FR-008 (Operations index KPIs)**: Operations index MUST show tenant-scoped KPIs:
@ -111,14 +128,71 @@ ### Functional Requirements
- Active Runs (queued + running)
- Failed/Partial (7 days)
- Avg Duration (7 days, terminal runs only)
- **FR-009 (Operations tabs)**: Operations index MUST provide status tabs that filter the operations table: All, Active, Succeeded, Partial, Failed.
- **FR-009 (Operations tabs)**: Operations index MUST provide status tabs that filter the operations table: All, Active, Succeeded, Partial, Failed. No "Stuck" tab in v1.
- **FR-010 (Canonical terminology)**: System MUST use “Operations” as the canonical label (no legacy naming on these surfaces).
- **FR-011 (Canonical links)**: “View run” links MUST always navigate to the canonical operation run detail view.
- **FR-012 (Calm UI rules)**: System MUST avoid polling/churn in modals and avoid refresh loops; background updates should be used only where clearly necessary.
- **FR-012 (Calm UI rules)**: System MUST avoid polling/churn in modals and avoid refresh loops; background updates should be used only where clearly necessary. Auto-refresh on Dashboard and Operations index is allowed only while active runs (queued/running) exist, and MUST stop when there are no active runs.
- **FR-013 (Drift stale rule)**: System MUST flag drift as “stale” when the last drift scan is older than 7 days and surface it in “Needs Attention” with an investigation CTA.
- **FR-014 (Recent list sizing)**: System MUST show 10 rows by default for “Recent Drift Findings” and “Recent Operations”.
### OperationRun status mapping (for tabs and KPIs)
OperationRun uses two canonical fields that drive UI filters:
- `status`: execution lifecycle (e.g., queued/running/completed)
- `outcome`: terminal result (e.g., succeeded/partially_succeeded/failed/cancelled)
Tab filters MUST map exactly as:
- **All**: no status/outcome filter
- **Active**: `status IN (queued, running)`
- **Succeeded**: `status = completed AND outcome = succeeded`
- **Partial**: `status = completed AND outcome = partially_succeeded`
- **Failed**: `status = completed AND outcome = failed`
Notes:
- No “Stuck” tab in v1.
- Runs with `outcome = cancelled` appear under **All** only (unless a future “Cancelled” tab is added).
- Any legacy status/outcome values must already be normalized before reaching this UI (out of scope for this feature).
### KPI window definitions (timestamp basis)
All KPI windows are tenant-scoped and DB-only.
- **Total Runs (30 days)**: count OperationRuns by `created_at` within the last 30 days (includes all statuses/outcomes).
- **Active Runs**: current count where `status IN (queued, running)` (no time window).
- **Failed/Partial (7 days)**: count terminal runs where `status = completed AND outcome IN (failed, partially_succeeded)` and `completed_at` is within the last 7 days.
- **Avg Duration (7 days)**: average of `(completed_at - started_at)` for runs where `status = completed`, `started_at` and `completed_at` are present, and `completed_at` is within the last 7 days.
### Inventory coverage classification (Restorable/Partial/Risk/Dependencies)
Coverage chips and KPI aggregation MUST derive from the existing “policy type meta” and dependency capability signals (DB-only):
- `inventory_items.policy_type`
- `config('tenantpilot.supported_policy_types')` meta fields:
- `restore` (e.g., enabled / preview-only)
- `risk` (e.g., medium / medium-high / high)
- Dependency support computed via the existing coverage dependency resolver (based on contracts/config).
Definitions:
- **Restorable**: inventory items whose policy type meta has `restore = enabled`
- **Partial**: inventory items whose policy type meta has `restore = preview-only`
- **Risk**: inventory items whose policy type meta has `risk IN (medium-high, high)`
- **Dependencies**: inventory items whose policy type supports dependencies per the existing dependency capability resolver
Notes:
- This feature does not redefine coverage semantics; it standardizes UI rendering and KPI aggregation based on the existing policy type meta.
- If a policy type is unknown/missing meta, it MUST be treated conservatively (non-restorable) for KPI aggregation.
**Assumptions**:
- Drift findings, inventory items, and operation runs already exist as tenant-scoped data sources.
- “Coverage %” defaults to covered/total; if total is 0, coverage shows as not available.
- “Coverage %” is Restorable/Total; Partial is shown separately (e.g., chips/secondary metric). If total is 0, coverage shows as not available.
- “Drift stale” default threshold is 7 days.
- “Recent” list default size is 10.
- Auto-refresh behavior (DB-only): Dashboard and Operations index auto-refresh only while active runs exist; otherwise it stops.
- Creating/generating drift is out of scope unless it can be performed as an explicit, enqueue-only user action that results in an operation run.
### Key Entities *(include if feature involves data)*

View File

@ -0,0 +1,192 @@
---
description: "Task list for feature implementation"
---
# Tasks: Tenant UI Polish (Dashboard + Inventory Hub + Operations)
**Input**: Design documents from `specs/058-tenant-ui-polish/`
**Tests**: Required (Pest) — this feature changes runtime UI behavior.
---
## Phase 1: Setup (Shared Infrastructure)
- [ ] T001 Confirm feature inputs exist: specs/058-tenant-ui-polish/spec.md, specs/058-tenant-ui-polish/plan.md
- [ ] T002 Confirm Phase 0/1 artifacts exist: specs/058-tenant-ui-polish/research.md, specs/058-tenant-ui-polish/data-model.md, specs/058-tenant-ui-polish/contracts/ui.md, specs/058-tenant-ui-polish/contracts/polling.md, specs/058-tenant-ui-polish/quickstart.md
---
## Phase 2: Foundational (Blocking Prerequisites)
- [ ] T003 Create shared helper to detect “active runs exist” for tenant polling in app/Support/OpsUx/ActiveRuns.php
- [ ] T004 [P] Add focused tests for the helper in tests/Feature/OpsUx/ActiveRunsTest.php
**Checkpoint**: Shared polling predicate exists and is covered.
---
## Phase 3: User Story 1 — Drift-first tenant dashboard (Priority: P1) 🎯 MVP
**Goal**: Tenant-scoped dashboard that surfaces drift + ops KPIs and “Needs Attention”, DB-only.
**Independent Test**: Visiting the dashboard renders drift KPIs, ops KPIs, needs-attention CTAs, and recent lists (10 rows), with no outbound HTTP and no background work dispatched.
### Tests (US1)
- [ ] T005 [P] [US1] Add DB-only render test (no outbound HTTP, no background work) in tests/Feature/Filament/TenantDashboardDbOnlyTest.php
- [ ] T006 [P] [US1] Add tenant isolation test (no cross-tenant leakage) in tests/Feature/Filament/TenantDashboardTenantScopeTest.php
### Implementation (US1)
- [ ] T007 [US1] Create tenant dashboard page in app/Filament/Pages/TenantDashboard.php
- [ ] T008 [US1] Register the tenant dashboard page in app/Providers/Filament/AdminPanelProvider.php (replace default Dashboard page entry)
- [ ] T009 [P] [US1] Create dashboard KPI widget(s) in app/Filament/Widgets/Dashboard/DashboardKpis.php
- [ ] T010 [P] [US1] Create “Needs Attention” widget in app/Filament/Widgets/Dashboard/NeedsAttention.php
- [ ] T011 [P] [US1] Create “Recent Drift Findings” widget (10 rows) in app/Filament/Widgets/Dashboard/RecentDriftFindings.php
- [ ] T012 [P] [US1] Create “Recent Operations” widget (10 rows) in app/Filament/Widgets/Dashboard/RecentOperations.php
- [ ] T013 [US1] Wire widgets into the dashboard page in app/Filament/Pages/TenantDashboard.php (header/sections) and implement conditional polling per specs/058-tenant-ui-polish/contracts/polling.md
- [ ] T014 [US1] Implement drift stale rule (7 days) + CTA wiring in app/Filament/Widgets/Dashboard/NeedsAttention.php
- [ ] T015 [US1] Ensure all dashboard queries are tenant-scoped + DB-only in app/Filament/Pages/TenantDashboard.php and app/Filament/Widgets/Dashboard/*.php
**Checkpoint**: US1 is shippable as an MVP.
---
## Phase 4: User Story 2 — Inventory becomes a hub module (Priority: P2)
**Goal**: Inventory becomes a cluster “hub” with consistent left sub-navigation and a shared KPI header across Items / Sync Runs / Coverage.
**Independent Test**: Navigating Items → Sync Runs → Coverage keeps consistent sub-navigation and shared KPI header, tenant-scoped and DB-only.
### Tests (US2)
- [ ] T016 [P] [US2] Add DB-only render test for Inventory hub surfaces in tests/Feature/Filament/InventoryHubDbOnlyTest.php
- [ ] T017 [P] [US2] Extend/adjust inventory navigation smoke coverage in tests/Feature/Filament/InventoryPagesTest.php
### Implementation (US2)
- [ ] T018 [US2] Enable cluster discovery in app/Providers/Filament/AdminPanelProvider.php (add `discoverClusters(...)`)
- [ ] T019 [US2] Create Inventory cluster class in app/Filament/Clusters/Inventory/InventoryCluster.php
- [ ] T020 [US2] Assign Inventory cluster to inventory pages in app/Filament/Pages/InventoryLanding.php and app/Filament/Pages/InventoryCoverage.php
- [ ] T021 [US2] Assign Inventory cluster to inventory resources in app/Filament/Resources/InventoryItemResource.php and app/Filament/Resources/InventorySyncRunResource.php
- [ ] T022 [P] [US2] Create shared Inventory KPI header widget in app/Filament/Widgets/Inventory/InventoryKpiHeader.php
- [ ] T023 [US2] Add Inventory KPI header widget to InventoryLanding in app/Filament/Pages/InventoryLanding.php
- [ ] T024 [US2] Add Inventory KPI header widget to InventoryCoverage in app/Filament/Pages/InventoryCoverage.php
- [ ] T025 [US2] Add Inventory KPI header widget to Inventory items list in app/Filament/Resources/InventoryItemResource.php (or its list page)
- [ ] T026 [US2] Add Inventory KPI header widget to Inventory sync runs list in app/Filament/Resources/InventorySyncRunResource.php (or its list page)
- [ ] T027 [US2] Ensure Inventory KPI definitions match specs/058-tenant-ui-polish/contracts/ui.md (coverage % restorable/total; partial separate; two active operations counts)
- [ ] T041 [US2] Inventory coverage semantics reference (A2)
- Identify and document the exact source-of-truth fields for Inventory KPI aggregation:
- `inventory_items.policy_type`
- `config('tenantpilot.supported_policy_types')` meta fields (`restore`, `risk`)
- Dependency support via existing dependency capability resolver
- Ensure KPI and chips read from this source only (DB-only).
- DoD:
- One canonical place documented and referenced by inventory KPIs.
- No “magic” or duplicated classification logic across pages/widgets.
- [ ] T028 [US2] Ensure “Sync Runs” view is inventory-only per spec in app/Filament/Resources/InventorySyncRunResource.php (query/filter by run type/intent if needed)
- [ ] T029 [US2] Standardize coverage chips set on coverage-related surfaces in app/Filament/Pages/InventoryCoverage.php (Restorable, Partial, Risk, Dependencies only)
**Checkpoint**: Inventory hub behaves as a module with consistent sub-navigation + header.
---
## Phase 5: User Story 3 — Operations index “Orders-style” (Priority: P3)
**Goal**: Operations index shows KPIs + status tabs + table, with calm conditional polling.
**Independent Test**: Visiting Operations index shows KPIs and tabs that filter runs per specs/058-tenant-ui-polish/contracts/ui.md, DB-only, calm.
### Tests (US3)
- [ ] T030 [P] [US3] Extend Operations DB-only test assertions in tests/Feature/Monitoring/OperationsDbOnlyTest.php (assert tabs/KPI labels appear)
- [ ] T031 [P] [US3] Extend Operations tenant isolation coverage in tests/Feature/Monitoring/OperationsTenantScopeTest.php (assert tab views dont leak)
### Implementation (US3)
- [ ] T032 [P] [US3] Create Operations KPI header widget in app/Filament/Widgets/Operations/OperationsKpiHeader.php
- [ ] T033 [US3] Add KPIs to the Operations list page in app/Filament/Resources/OperationRunResource/Pages/ListOperationRuns.php
- [ ] T034 [US3] Implement status tabs (All/Active/Succeeded/Partial/Failed) on Operations list page in app/Filament/Resources/OperationRunResource/Pages/ListOperationRuns.php
- [ ] T035 [US3] Ensure tab filter logic matches specs/058-tenant-ui-polish/contracts/ui.md by adjusting queries in app/Filament/Resources/OperationRunResource/Pages/ListOperationRuns.php
- [ ] T036 [US3] Implement conditional polling for Operations list (only while active runs exist) by wiring table polling in app/Filament/Resources/OperationRunResource.php and/or app/Filament/Resources/OperationRunResource/Pages/ListOperationRuns.php
- [ ] T037 [US3] Ensure canonical “View run” links still route to OperationRunResource view pages (no legacy routes)
- Verify existing canonical link helper `App\Support\OperationRunLinks` is used consistently.
- If no suitable helper exists for a given surface, add a minimal equivalent and use it everywhere.
- [ ] T042 [US3] Operations terminology sweep (FR-010)
- Goal: The UI uses the canonical label “Operations” consistently; no legacy naming remains.
- Audit + fix in:
- Navigation label(s)
- Page titles / breadcrumbs
- Resource titles / headings
- Any links mentioning “Bulk Operation Runs” or legacy run naming
- DoD:
- No occurrences of legacy labels remain in UI surfaces for Monitoring/Operations.
- `rg -n "Bulk Operation|BulkOperationRun|Bulk Operation Runs" app resources` returns 0 matches (or matches only in tests explicitly allowed).
- If ripgrep is unavailable, use `grep -R` with the same patterns.
**Checkpoint**: Operations index is “orders-style” with calm refresh behavior.
---
## Phase 6: Polish & Cross-Cutting Concerns
- [ ] T038 [P] Run formatting on changed files in app/** and tests/** via `vendor/bin/sail bin pint --dirty`
- [ ] T039 Run targeted tests from specs/058-tenant-ui-polish/quickstart.md and ensure green
- [ ] T040 [P] Smoke-check key pages render for a tenant in tests/Feature/Filament/AdminSmokeTest.php (add assertions only if gaps are found)
---
## Dependencies & Execution Order
### User Story Dependencies
- US1 (P1) is standalone and can ship first.
- US2 (P2) can be implemented after foundational polling helper; it touches the panel provider and inventory pages/resources.
- US3 (P3) can be implemented after foundational polling helper; it touches the operations resource list page.
Suggested order (MVP-first): Phase 1 → Phase 2 → US1 → US2 → US3 → Polish.
### Parallel Opportunities (examples)
- US1: T009T012 can be developed in parallel (different widget files).
- US2: T022 can be developed in parallel while T018T021 are in review.
- US3: T032 can be developed in parallel with test updates (T030T031).
---
## Parallel Execution Examples (per user story)
### US1
- T005 [P] [US1] tests/Feature/Filament/TenantDashboardDbOnlyTest.php
- T006 [P] [US1] tests/Feature/Filament/TenantDashboardTenantScopeTest.php
- T009 [P] [US1] app/Filament/Widgets/Dashboard/DashboardKpis.php
- T010 [P] [US1] app/Filament/Widgets/Dashboard/NeedsAttention.php
- T011 [P] [US1] app/Filament/Widgets/Dashboard/RecentDriftFindings.php
- T012 [P] [US1] app/Filament/Widgets/Dashboard/RecentOperations.php
### US2
- T016 [P] [US2] tests/Feature/Filament/InventoryHubDbOnlyTest.php
- T022 [P] [US2] app/Filament/Widgets/Inventory/InventoryKpiHeader.php
### US3
- T030 [P] [US3] tests/Feature/Monitoring/OperationsDbOnlyTest.php
- T031 [P] [US3] tests/Feature/Monitoring/OperationsTenantScopeTest.php
- T032 [P] [US3] app/Filament/Widgets/Operations/OperationsKpiHeader.php
---
## Implementation Strategy
### MVP First (US1 only)
1. Complete Phase 1 + Phase 2
2. Implement US1 (dashboard)
3. Run: `vendor/bin/sail artisan test tests/Feature/Filament/TenantDashboardDbOnlyTest.php`
4. Run: `vendor/bin/sail artisan test tests/Feature/Filament/TenantDashboardTenantScopeTest.php`
### Incremental Delivery
- Add US2 next (Inventory hub), then US3 (Operations index).
- After each story, run its targeted tests + the cross-cutting DB-only tests.