Maintenance Hub
Route /maintenance?tab=open|history — the launch screen (lib/features/maintenance/presentation/...). Aggregates booking, inventory, inspections, tasks, charges, notifications from LocalStore.hubSnapshotStream.
Anatomy
KxHeroAppBar("Maintenance Hub")
BookingSelector (KxSelectorField) — propertyCode / roomName / dates
Tabs (KxTabs) — Open | History (query param ?tab= derived from BookingCubit)
Outstanding banner (KxBanner.info, blue) — deadline + "View charge" when any `outstanding`
Recommendations banner (inspection-driven)
Inventory card(s) — status tag, location, "Working report" → /reports/:id (External link)
Inspection card(s) — status, location, date, → /inspections/:id
Task lane — Raise Task + TaskCard{category, notes, location, date, status}
Charge section — ChargeCard{amount, status pill, location} per charge
Bottom: KxNavShell destinations [Home, Book, Maintenance*, Notifications(badge)]
Per-spec: Home/Book are inert; Maintenance is selected.
Banners & actions
Banner policy mirrors domain/charge_state.py:
| Hub state | Banner | Action |
|---|---|---|
any outstanding | Outstanding blue | View (routes to first outstanding charge) |
any contested in history | Contested warning | Review pending |
accepted accepted | Accepted green outline | Pay (routes to statement or charge pay) |
| no open | Empty message state | — |
Cards never duplicate business logic — charge_card_header.dart maps ChargeStatus→KxTone + pill text; maintenance_banner.dart composes KxBanner.
Tabs contract
Open→isOpen(status) = outstanding|accepted|contested. Ordered bydeadlineAtascending thenraisedAt.History→isHistory(status) = accepted|contested|resolved|paid, ordered by latest event timestamp (acceptedAt??contestedAt??resolvedAt??paidAt??raisedAt) descending, includes contest reason snapshot when present.acceptedappears on both.
BookingCubit.rememberHubTab(tab) persists last tab per booking; redirect in app_router.dart repairs unknown ?tab= to fallback.
Data wiring
LocalStore.hubSnapshotStream → MaintenanceHubCubit → BlocBuilder → MaintenanceHubPage
local_annotations (L10n strings) injected via BuildContext.l10n
tool/check_evidence.dart guards HubTab routing contract via test/router/*.
Responsive
- 360–599: single-column cards, selector stacked, horizontal no-overflow (photo strip is the only horizontal scroll).
- 600–839: cards 2-per-row.
- ≥840: two-column
KxContentContainersplit (list + detail hint). Breakpoints fromKxBreakpoints.
Tests
test/features/maintenance/hub_page_test.dart— header, selector switch, banner variants, inventory/inspection/task/charge lanes, nav shell destinations.test/golden/...banner and card variants in light/dark.