# Feature Specification: Tenant UI Polish (Dashboard + Inventory Hub + Operations) **Feature Branch**: `058-tenant-ui-polish` **Created**: 2026-01-20 **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)* ### User Story 1 - Drift-first tenant dashboard (Priority: P1) As a tenant admin, I can open a tenant-scoped dashboard that immediately surfaces drift risk and operations health, without triggering any remote calls. **Why this priority**: This is the primary entry point for day-to-day operations and should be actionable at a glance. **Independent Test**: Visiting the dashboard shows drift + operations KPIs, a “needs attention” list with working CTAs, and recent lists, while confirming no outbound HTTP happens during render and any background UI updates. **Acceptance Scenarios**: 1. **Given** I am signed in to a tenant, **When** I open the Dashboard, **Then** I see tenant-scoped drift KPIs, operations health KPIs, and recent lists. 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. --- ### User Story 2 - Inventory becomes a hub module (Priority: P2) As a tenant admin, I can use Inventory as a “hub” with consistent sub-navigation and a shared KPI header across Inventory subpages. **Why this priority**: Inventory is a high-traffic area; a hub layout reduces cognitive load and makes it easier to find the right view quickly. **Independent Test**: Navigating Inventory Items / Sync Runs / Coverage keeps the same shared KPI header, the left sub-navigation is consistent, and all data remains tenant-scoped and DB-only. **Acceptance Scenarios**: 1. **Given** I am signed in to a tenant, **When** I open Inventory, **Then** I see hub navigation (Items / Sync Runs / Coverage) and a shared KPI header. 2. **Given** I switch between Inventory subpages, **When** I navigate Items → Sync Runs → Coverage, **Then** the KPI header remains visible and consistent. 3. **Given** the tenant has an inventory sync run history, **When** I open “Sync Runs”, **Then** I see only sync runs relevant to inventory synchronization. --- ### User Story 3 - Operations index “Orders-style” (Priority: P3) As a tenant admin, I can view Operations in an “orders-style” overview (KPIs + status tabs + table) to quickly assess activity and failures. **Why this priority**: Operations is the canonical place to investigate work; better scanning and filtering reduces time-to-triage. **Independent Test**: Visiting Operations index shows KPI cards and status tabs that correctly filter the table without introducing polling churn or any remote calls. **Acceptance Scenarios**: 1. **Given** I am signed in to a tenant, **When** I open Operations, **Then** I see KPIs, status tabs, and the operations table. 2. **Given** there are active runs, **When** I click the “Active” tab, **Then** the table filters to queued + running runs only. 3. **Given** there are failed runs, **When** I click the “Failed” tab, **Then** the table filters to failed runs only. 4. **Given** I navigate away and back, **When** I return to Operations, **Then** the UI remains calm (no refresh loops) and loads quickly. --- [Add more user stories as needed, each with an assigned priority] ### Edge Cases - No data yet: dashboard/inventory/operations render with empty states and helpful CTAs. - Large tenants: KPI calculations remain fast enough to keep pages responsive. - Mixed outcomes: partial/failed/succeeded runs are correctly categorized and discoverable via tabs/filters. - Tenant switching: no cross-tenant leakage of KPIs, lists, or links. - Time windows: KPI windows (e.g., last 7/30 days) handle timezones consistently. - “Unknown” states: missing duration/end time renders gracefully (e.g., avg duration excludes non-terminal runs). ## Requirements *(mandatory)* **Constitution alignment (required):** If this feature introduces any Microsoft Graph calls, any write/change behavior, or any long-running/queued/scheduled work, the spec MUST describe contract registry updates, safety gates (preview/confirmation/audit), tenant isolation, run observability (`OperationRun` type/identity/visibility), and tests. If security-relevant DB-only actions intentionally skip `OperationRun`, the spec MUST describe `AuditLog` entries. ### Functional Requirements - **FR-001 (Tenant scope)**: System MUST ensure the Dashboard, Inventory hub, and Operations views are tenant-scoped, with no cross-tenant visibility. - **FR-002 (DB-only surfaces)**: System MUST keep Dashboard, Inventory hub header, and Operations index DB-only during render and any background UI updates. - **FR-003 (Placement policy)**: System MUST show KPI cards only on these entry-point pages: Dashboard, Inventory hub (shared header), and Operations index. - **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 % (restorable items / total items; partial shown separately) - Last Inventory Sync (status + timestamp) - 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: - Total Runs (30 days) - 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. 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. 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 %” 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)* - **Tenant**: The scope boundary for all dashboards and lists in this feature. - **Operation Run**: A tenant-scoped record of work execution, including status, timestamps, and outcomes used for Operations KPIs and recent lists. - **Drift Finding**: A tenant-scoped record representing detected drift, including severity and state (open/closed) used for Dashboard KPIs and “Needs Attention”. - **Inventory Item**: A tenant-scoped record representing inventory coverage and totals used in the Inventory hub. ## Success Criteria *(mandatory)* ### Measurable Outcomes - **SC-001 (Task speed)**: A tenant admin can reach “what needs attention” (drift/ops) from the dashboard within 30 seconds. - **SC-002 (Discoverability)**: A tenant admin can find inventory sync runs within 2 clicks from Inventory. - **SC-003 (Triage efficiency)**: A tenant admin can filter Operations to “Active” or “Failed” within 1 click and identify a run to investigate within 60 seconds. - **SC-004 (Calm UI)**: No refresh loops are observed on Dashboard, Inventory hub pages, or Operations index during normal navigation. - **SC-005 (Safety)**: Viewing Dashboard, Inventory hub pages, and Operations index does not trigger any outbound HTTP.