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