This repository was archived by the owner on May 26, 2026. It is now read-only.
feat(kora): KR-FE-MULTI-TENANT-COCKPIT-AGGREGATE-AND-DEEPLINK — aggregate cost panel + tenant deep-link URLs#208
Merged
rafe-walker merged 1 commit intoMay 24, 2026
Conversation
…gate cost panel + tenant deep-link URLs
Deliverable A — "All tenants" aggregate cost panel:
* AggregateCostCards component renders one card per tenant
from snapshot v6 cost_ladder_by_tenant block (#206 sibling)
* default-first then alphabetical render order; calm empty
card for tenants observed via /api/tenants/list but absent
from the snapshot (no holder activity yet)
* aggregate footer: total spent + combined credit pool +
skipped-count when some tenants have no numeric data
* responsive 1/2/3-up grid (mobile / tablet / desktop)
* CostStatePage early-exits the per-tenant /api/cost-state
fetch when isAllTenants — no misleading default-tenant
payload pinned to page state
* DashboardPage Cost card shows compact one-line aggregate
summary ("$N / $M, K tenants · click for breakdown") when
isAllTenants; full per-tenant grid lives on /cost-state
Deliverable B — Deep-link URLs + active-tenant header badge:
* ?tenant=all URL alias resolves to ALL_TENANTS_SENTINEL
(operator-readable in shared URLs; internal sentinel still
`__all__` to avoid collision with real tenant_id "all")
* tenantToUrlValue() helper round-trips sentinel → alias for
share-URL construction
* ActiveTenantBadge component in every audit + cost +
promotion page header — clickable opens sidebar picker via
OPEN_TENANT_PICKER_EVENT custom event; Copy-share-URL
button with prompt() fallback for blocked clipboard API
* Hidden on single-tenant deployments (isMultiTenant gate
mirrors the picker's auto-hide behavior)
Drift-guard extension (tests/test_tenants_endpoint.py):
* ALL_TENANTS_URL_ALIAS = "all" pin (URL form)
* ALL_TENANTS_SENTINEL = "__all__" pin (internal form)
* OPEN_TENANT_PICKER_EVENT name pin (badge ↔ picker contract)
* ACTIVE_TENANT_BADGE_USES_SENTINEL re-export pin (badge
participates in the same constant set as the hook)
* AggregateCostCards reads cost_ladder_by_tenant block pin
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two follow-on deliverables from the #207 CC#2 hand-off — both small, parallel, ship without waiting on other lanes.
Deliverable A — "All tenants" aggregate cost panel. When the operator picks the aggregate pseudo-option in the tenant picker,
/cost-statenow renders side-by-side per-tenant cost cards (one card per tenant) sourced from the snapshot v6cost_ladder_by_tenantblock. Aggregate footer surfaces total spent + combined credit pool. DashboardPage's small Cost card shows a compact one-line aggregate summary in the same mode, linking through to/cost-statefor the breakdown.Deliverable B — Deep-link URLs + active-tenant header badge.
?tenant=marvin(and?tenant=allfor aggregate) deep-links work on every tenant-scoped page. Every audit + cost + promotion page header gets an<ActiveTenantBadge>that surfaces the current tenant + opens the sidebar picker on click + copies a share-URL via the Clipboard API (withprompt()fallback for blocked contexts). Hidden on single-tenant deployments.What changed
web/src/hooks/useActiveTenant.tsALL_TENANTS_URL_ALIAS = \"all\"— operator-readable URL form for the aggregate sentinel. Internal sentinel stays\"__all__\"so it can't collide with a real tenant_id named "all".useResolvedTenanttranslates the alias on read.tenantToUrlValue()helper round-trips sentinel → alias for share-URL construction.OPEN_TENANT_PICKER_EVENTcustom-event name +requestOpenTenantPicker()helper — page-header badges dispatch this to ask the sidebar picker to open. Decouples badge (per-page) from picker (sidebar) without prop-drilling.web/src/components/TenantPicker.tsxOPEN_TENANT_PICKER_EVENTand setsopen=truewhen fired.web/src/components/ActiveTenantBadge.tsx(new)Viewing: <tenant>chip + Copy-share-URL button.loadingTenants || !isMultiTenant(B.2 single-tenant degradation).navigator.clipboard.writeTextin secure contexts; falls back towindow.prompt()for blocked clipboard / non-HTTPS.ACTIVE_TENANT_BADGE_USES_SENTINEL = ALL_TENANTS_SENTINELso the drift-guard test pins the badge ↔ hook constant tie.web/src/components/AggregateCostCards.tsx(new)/api/snapshotonce (re-fetches on parent-suppliedreloadKeybump).<TenantCostCard>per tenant fromavailableTenants(default-first, then alphabetical). Empty-state card for tenants listed but absent fromcost_ladder_by_tenant.default+ tier badge + spent / pool + pct-used progress bar with tone tracking the rung.web/src/pages/CostStatePage.tsx<ActiveTenantBadge>in the page header.loadStateearly-exits whenisAllTenants(no misleading default-tenant payload pinned to page state); Reload button bumpsaggregateReloadKeyinstead.<AggregateCostCards>whenisAllTenants; otherwise the existing single-tenant burn / deferred / reconciliation sections.web/src/pages/DashboardPage.tsxsnap.cost_ladder_by_tenantinto local state duringloadInitial.\"Cost (all tenants)\"in aggregate mode; body switches to<AggregateCostCardBody>showing\"$N / $M, K tenants · click for breakdown\". Falls back to the single-tenant body when the snapshot's by-tenant block isn't populated (single-tenant deployments).Audit + promotion page headers
ProbeInvestigations, AlertInvestigations, EmailIntentLog, OutboundEmailLog, Autofix, KoraActions, PromotionReview — each H2 now sits inside a
<div className=\"flex items-center gap-2 flex-wrap\">alongside<ActiveTenantBadge />. URL?tenant=deep-link already works (the #207 hook resolves URL → localStorage → default); the badge surfaces which tenant is active in the page chrome.tests/test_tenants_endpoint.pytest_fe_aggregate_and_deeplink_pinspinning:ALL_TENANTS_URL_ALIAS = \"all\"(URL form)ALL_TENANTS_SENTINEL = \"__all__\"(internal form)OPEN_TENANT_PICKER_EVENT = \"kora:open-tenant-picker\"nameACTIVE_TENANT_BADGE_USES_SENTINEL = ALL_TENANTS_SENTINELre-export in badgecost_ladder_by_tenantliteral grep in AggregateCostCards (asserts canonical data-source choice)Deep-link examples
/probe-investigations?tenant=marvin— investigations scoped to marvin (badge: Viewing marvin)/cost-state?tenant=all— aggregate cost cards (badge: Viewing All tenants)/kora-actions?tenant=default— explicit default view (badge: Viewing default, only if multi-tenant)URL precedence (from #207): URL
?tenant=→ localStorage → "default". Picker selections write localStorage but do NOT mutate the URL — a shared deep-link continues to anchor the page even after the operator clicks a different tenant.Build
tsc -b && vite build✓ clean (one pre-existing chunk-size warning; not introduced by this bucket).python3 -m py_compile tests/test_tenants_endpoint.py✓..venv); CI runs it on PR.Test plan
?tenant=marvindirect link → useActiveTenant returns\"marvin\"; picker shows marvin selected; audit endpoints get?tenant_id=marvin?tenant=alldirect link →isAllTenants=true; badge shows "All tenants"; cost-state renders aggregate cards?tenant=→ falls back to localStorage → "default"$X across N tenants this period · $Y combined credit pool$X / $Y, N tenants · click for breakdown<current URL>?tenant=<active>to clipboard; non-secure context falls back to prompt()pytest tests/test_tenants_endpoint.py— 6 tests pass (3 endpoint + 3 drift-guard, +1 new aggregate/deeplink pin)Recommendation for next CC#2 dispatch
Per-tenant audit BE filter verification (depends on CC#1 NousResearch#447 landing). Once CC#1's per-tenant audit JSONL paths +
?tenant_id=BE filter ship, add a small verification bucket: spin up two tenants, fire seam events for each, assert each FE audit page (with the existing?tenant_id=forwarding from #207) renders only the matching subset. ~½ day. Tightens the contract that's currently "BE may ignore the param" into "BE filters correctly."Lighter follow-on if NousResearch#447 hasn't landed yet: TenantPicker keyboard nav + URL-write toggle. Currently the picker is mouse-only and selections never mutate the URL. Add (a) ↑/↓/Enter/Esc keyboard nav and (b) an opt-in "also update URL" toggle in the picker so deep-link-anchored pages can decouple if the operator wants. ~3 hours.
🤖 Generated with Claude Code