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