Skip to content
This repository was archived by the owner on May 26, 2026. It is now read-only.

feat(kora): KR-FE-DASHBOARD-SNAPSHOT-WIRE — $0-cost dashboard via /api/snapshot#162

Merged
rafe-walker merged 1 commit into
feature/phase2-upgradesfrom
feat/kora-KR-FE-DASHBOARD-SNAPSHOT-WIRE
May 24, 2026
Merged

feat(kora): KR-FE-DASHBOARD-SNAPSHOT-WIRE — $0-cost dashboard via /api/snapshot#162
rafe-walker merged 1 commit into
feature/phase2-upgradesfrom
feat/kora-KR-FE-DASHBOARD-SNAPSHOT-WIRE

Conversation

@rafe-walker

Copy link
Copy Markdown
Owner

Summary

Per the unified-operator-interface lens + cheap-substrate thesis (feedback-opus-escalation-must-be-earned): DashboardPage now tries the $0 daemon snapshot first on page load, projects the fields the snapshot covers cleanly, and only fans out to live endpoints for the fields where projection would mislead the operator. Force-refresh button gives the explicit "I want live" path. Cost-economy thesis made visible via the freshness badge.

Field shift table

Field Decision Rationale
operational ✅ shifted to snapshot snapshot.operational_state.{primary, paused, pause_reason} projects cleanly into OperationalStateResponse.{primary_state, is_degraded, claim_permission}. claim_permission defaults via paused → "none" / not paused → "normal" (valid enum values per api.ts:1004).
alerts ✅ shifted to snapshot snapshot.alerts.{active_count, by_severity, by_category} populates AlertsResponse.{total_active, by_severity}. Per-alert array projected empty (snapshot doesn't carry it). AlertsBanner patched to use total_active + aggregate-hash dismissal fall-through.
cost ❌ kept on fan-out snapshot.cost_ladder has {current_tier, monthly_budget_pct_used, model_default} but lacks spent_to_date_usd + credit_pool_usd which CostCardBody renders prominently ($ figures). Defaulting to $0/$0 would scream "no budget used" — actively misleading.
health ❌ kept on fan-out snapshot.service_health is SaaS-dependency health (vercel/sentry/doppler/supabase/fly), NOT Kora's own control_plane/worker daemon health. HealthHero renders the daemon health; semantic mismatch means coercion would conflate dep-health with daemon-health.

Net: 2 of 4 spec-listed fields shifted to snapshot, 2 kept on fan-out per spec §2(b) "prefer to leave the existing fan-out call for that field rather than coerce." Anti-projection tests pin both kept-on-fan-out decisions so a future refactor that tries the coercion gets caught at the test gate.

Freshness badge screenshot

FreshnessBadge — 4 visual states

States rendered (top → bottom):

  1. Snapshot (happy path) — "Snapshot from 2m ago · $0 cost view" with success-toned border
  2. Live (operator clicked Force-refresh) — "Live (just refreshed) · live fetch" with neutral border
  3. Unavailable (backend snapshot missing/stale) — "Live fetch · backend snapshot unavailable" with muted tone
  4. Mixed (per-card refresh overrode snapshot-projected field) — "Snapshot from 2m ago · 1 field force-refreshed · mixed cost view" with warning tone

HTML preview source: web/docs/freshness-badge/preview.html (open in any browser to reproduce).

What's in here

Backend: /api/snapshot already shipped in PR #157 — no backend changes in this PR.

Frontend:

  • web/src/lib/api.tsgetSnapshot() wrapper + SnapshotResponse / SnapshotUnavailable TS types
  • web/src/components/FreshnessBadge.tsx (new) — 4-state cost-economy indicator with Force-refresh button
  • web/src/components/AlertsBanner.tsx — snapshot-compat patch: total_active visibility check + aggregate-hash dismissal fall-through
  • web/src/pages/DashboardPage.tsxloadInitial() (snapshot-first) + forceFullLiveRefresh() + per-card live-override tracking + projection helpers (projectOperationalFromSnapshot, projectAlertsFromSnapshot)

Per-card refresh unchanged for the operator: existing retry buttons still call loadOne(field, fetcher). When that field was snapshot-projected, loadOne adds it to a liveOverrides Set, which drives the badge's "mixed" sub-mode counter.

Notes

Test plan

  • tests/kora_cli/test_dashboard_snapshot_wire.py17 source-pin tests: wrapper signature, type declarations, FreshnessBadge component + 3-mode union + Force-refresh button + $0 cost view literal + mixed mode marker, positive projection pins (operational + alerts), anti-projection pins (cost + health stay on fan-out), loadInitial calls snapshot first, fallback to fanOutAll on unavailable, forceFullLiveRefresh sets mode="live", AlertsBanner total_active check, aggregate-hash dismissal fall-through.
  • pnpm tsc -b clean
  • pnpm build clean
  • Manual smoke: load dashboard with daemon serving fresh snapshot → operational + alerts populated instantly from snapshot, other 17 fields stream in via fan-out, badge shows "Snapshot from N min ago · $0 cost view". Click "Force live refresh" → badge swaps to live mode. Stop daemon's snapshot writer (or wait > 10 min) → reload → badge shows "Live fetch · backend snapshot unavailable" with all fields fan-out.

Refs

🤖 Generated with Claude Code

…i/snapshot

Per the unified-operator-interface lens + cheap-substrate thesis
(feedback-opus-escalation-must-be-earned): DashboardPage now
tries the $0 daemon snapshot first on page load, projects the
fields the snapshot covers cleanly, and only fans out to live
endpoints for the fields where projection would mislead the
operator. Force-refresh button gives the explicit "I want live"
path. Cost-economy thesis made visible via the freshness badge.

Shifted to snapshot (2 of 4 spec-listed fields)
================================================

  * operational — snapshot.operational_state.{primary, paused,
    pause_reason} maps cleanly to OperationalStateResponse's
    {primary_state, is_degraded, claim_permission} fields the
    OperationalCardBody consumes. claim_permission defaults via
    paused → "none" / not paused → "normal" (valid enum values
    per api.ts:1004 ClaimPermission).

  * alerts — snapshot.alerts.{active_count, by_severity,
    by_category} populates AlertsResponse's {total_active,
    by_severity}; per-alert array projected empty (snapshot
    doesn't carry it). AlertsBanner uses total_active for
    visibility check + falls through to aggregate-hash for
    dismissal — change documented inline in the banner.

KEPT on fan-out (2 of 4 spec-listed fields)
============================================

Per spec §2(b) "If field types don't line up cleanly, prefer to
leave the existing fan-out call for that field rather than
coerce." Coercion would actively mislead operator:

  * cost — snapshot.cost_ladder has {current_tier,
    monthly_budget_pct_used, model_default} but LACKS
    spent_to_date_usd + credit_pool_usd which CostCardBody
    renders PROMINENTLY ($ figures). Defaulting to $0 / $0
    would scream "no budget used" — actively misleading.

  * health — snapshot.service_health is SaaS-dependency health
    (vercel/sentry/doppler/supabase/fly), NOT Kora's own
    control_plane / worker daemon health. HealthHero renders
    overall/control_plane/worker; the semantic mismatch means
    a derived "any service unhealthy → overall unhealthy"
    projection would conflate dep-health with daemon-health.

Anti-projection tests pin both (test_dashboard_does_not_project_
cost_from_snapshot + test_dashboard_does_not_project_health_from_
snapshot) so a future refactor that tries the coercion gets
caught at the test gate.

Wired (per spec §3 acceptance)
================================

  * api.getSnapshot() wrapper with typed
    SnapshotResponse | SnapshotUnavailable union (caller
    branches on `"error" in resp`)
  * loadInitial() tries snapshot first; falls back to full
    fan-out on `{error: "no_snapshot"}` OR exception (network /
    5xx). Snapshot success: project ops + alerts, then fan out
    the remaining 17 fields (cost, health, sea, control, boot,
    dr, capabilities, charter, recentEvents, runbooks,
    heartbeat, mcpClients, webhookEvents, agentActivity,
    slackDM, email, reasoning).
  * forceFullLiveRefresh() bypasses snapshot, runs full fan-out.
  * Per-card retry buttons unchanged; loadOne tracks live
    overrides — when a snapshot-projected field gets refreshed
    via per-card button, badge counts it for the "mixed"
    sub-mode.

FreshnessBadge component (new)
================================

web/src/components/FreshnessBadge.tsx — 4 visual states:

  * snapshot — "Snapshot from N min ago · $0 cost view"
    (success-toned border)
  * live     — "Live (just refreshed) · live fetch"
    (neutral border)
  * unavailable — "Live fetch · backend snapshot unavailable"
    (muted; no shame about the fallback)
  * mixed    — "Snapshot from N min ago · K field(s) force-
    refreshed · mixed cost view" (warning-toned)

Force-refresh button always present. Hover tooltip shows the
underlying ISO timestamp (computed_at for snapshot mode; last
live-fetch time for live mode) — operator can grep forensically.

NOTE: timestampAbsoluteUtc inlined locally (KR-FE-OPS-QUALITY-
PASS #155 — which exports it from panelHelpers — hasn't merged
into this branch's base). Follow-on PR can swap to the shared
helper once #155 lands.

AlertsBanner snapshot-compatibility patch
==========================================

Banner now hides on `data.total_active === 0` instead of
`data.alerts.length === 0` so the snapshot path (empty per-alert
array, but total_active > 0) still surfaces the banner.

Dismissal hash falls through from id-set hash to aggregate-shape
hash (`agg:{total_active}:{critical}:{warning}:{info}`) when the
per-alert array is empty — re-dismissal triggers on aggregate
shape change so operator doesn't get silenced across new alert
additions just because we're on the $0 path.

Tests (tests/kora_cli/test_dashboard_snapshot_wire.py — 17 tests)
=================================================================

Source-pin tests (no FE component-runner per the established
CC#2 pattern):
  * api.getSnapshot wrapper signature + URL
  * SnapshotResponse + SnapshotUnavailable types declared with
    required field set
  * FreshnessBadge: component exists, 3-mode union, Force-
    refresh button, $0-cost-view literal, mixed mode marker
  * DashboardPage projects operational + alerts (positive pin)
  * DashboardPage does NOT project cost + health (anti-
    projection pin to lock the spec-§2(b) discipline)
  * loadInitial calls api.getSnapshot FIRST
  * Snapshot unavailable → fanOutAll fallback path
  * forceFullLiveRefresh sets mode="live" + bypasses snapshot
  * AlertsBanner total_active check + aggregate-hash fall-through

Verification:
  * 17/17 source-pin tests pass
  * tsc -b clean
  * vite build clean
  * Screenshot rendered: web/docs/freshness-badge/states.png
    (4 visual states + preview HTML for reproducibility)

Refs:
  * rafe-walker/kora-docs 17_cc_bucket_prompts/KR-FE-DASHBOARD-SNAPSHOT-WIRE_zero_cost_dashboard.md
  * PR #157 — snapshot infrastructure (GET /api/snapshot)
  * PR #134 — AlertsPanel + AlertsBanner (the dismissal logic
    being patched)
  * Council R3 unified-operator-interface lens
  * feedback-opus-escalation-must-be-earned (cost-economy
    visibility thesis)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@rafe-walker rafe-walker merged commit 3a18a6f into feature/phase2-upgrades May 24, 2026
@rafe-walker rafe-walker deleted the feat/kora-KR-FE-DASHBOARD-SNAPSHOT-WIRE branch May 24, 2026 02:01
rafe-walker added a commit that referenced this pull request May 24, 2026
…/errors (schema v4) (#170)

snapshot.daemon_health section: overall_status + boot_at + uptime_seconds + per-listener health (14 listeners — actual inventory via grep) + recent_error_count_5min. Schema_version 3→4.

14-listener inventory: heartbeat_probes, snapshot, purelymail_client, probe_wake, mcp, email_inbound_imap, heartbeat, web, cost_telemetry, slack_client, mcp_consumption, webhooks, reasoning_engine, alert_notifier.

Accessor reuse: current_coordinator() + DaemonCoordinator.get_status() + _startup_completed_at + read_audit_entries(since=...) all pre-existing. Wall-clock boot_at added (_startup_completed_wall_at + get_boot_at).

v1 trade-off: per-listener last_event_at + consecutive_errors deferred — would touch 14 listener files with no consumer today; v1 stubs them 'unknown' so wire shape is forward-compatible (future bucket fills incrementally without breaking).

Completes the snapshot v4 expansion. Unblocks CC#2 KR-FE-DASHBOARD-HEALTH-SNAPSHOT-SHIFT (HealthHero shift to snapshot — the second of PR #162's two anti-coercion holdouts).

22 new daemon_health tests + 47 existing snapshot tests + 182 cross-bucket regression + ruff clean.
rafe-walker added a commit that referenced this pull request May 24, 2026
…n holdouts (#174)

4-of-4 dashboard hero fields snapshot-driven on warm cache. Verified at DashboardPage.tsx:1270-1345 — loadInitial does NOT call api.getOperationalState / getCurrentAlerts / getCostState / getHealthRollup with fresh snapshot.

PR #162 anti-projection tests FLIPPED (not deleted): test_dashboard_does_not_project_*_from_snapshot → test_dashboard_projects_*_from_snapshot with null-return + per-field fallback pins. New pins: badge surfaces N-of-M hero count + DASHBOARD_HERO_FIELD_COUNT===4 literal regression guard.

K-DG drift caught + fixed: SnapshotResponse TS type was stale — missing cost_ladder.spent_to_date_usd + credit_pool_usd (#169) + the entire daemon_health section (#170). Added; required before projection helpers could compile.

Per-card retry buttons + forceFullLiveRefresh still fan out by design (operator-triggered force paths). 19/19 tests pass, tsc + vite build clean.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant