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

feat(kora): KR-ALERTS-PANEL — unified operator-attention lens (stub)#134

Merged
rafe-walker merged 1 commit into
feature/phase2-upgradesfrom
feat/kora-KR-ALERTS-PANEL
May 23, 2026
Merged

feat(kora): KR-ALERTS-PANEL — unified operator-attention lens (stub)#134
rafe-walker merged 1 commit into
feature/phase2-upgradesfrom
feat/kora-KR-ALERTS-PANEL

Conversation

@rafe-walker

Copy link
Copy Markdown
Owner

Summary

Single aggregating panel that pulls "things requiring attention" from the 12 existing panels into one place. Today each panel has its own destructive-tone trigger (cost-ladder warned, daemon paused, dead-letters > 5, reasoning halted, capability denials > 10) — operator has to glance at 12 cards to spot trouble. This panel surfaces aggregate alerts at the TOP of the dashboard.

Real alert generation is deferred to a backend bucket once the source panels can expose their alert state to a central collector. Stub-then-real, same pattern as the prior 7 stub panels.

What's in here

  • Backend stub: GET /api/alerts/current returning 4 representative alerts deliberately spanning critical / warning / info AND four distinct categories so the operator's first look exercises:
    • severity sort order (critical → warning → info)
    • banner border-tone mapping (red / yellow / blue)
    • category icon variety (DollarSign / PauseCircle / Inbox / Workflow)
    • click-through nav via source_panel_route
  • AlertsPanel.tsx (new) — title + stub banner + aggregate strip + severity-grouped rows with category icons + expandable detail + "Open {source_panel}" link button → navigates via react-router. Empty state: green CheckCircle2 + "No active alerts. Daemon healthy." (positive reinforcement; no false-alarm trigger from absent data).
  • AlertsBanner.tsx (new component) — compact dashboard-top banner. Hidden when alerts.length === 0 OR data not loaded. Severity-counted summary inline. Worst-severity border tone drives at-a-glance attention. Dismiss button uses sessionStorage (per-tab; resets on tab close) — explicitly NOT localStorage (which would persist across browser sessions and wrongly silence future alerts). Dismissal is keyed on the alert id-set hash so new alerts re-trigger the banner even within an already-dismissed tab.
  • Dashboard placement: banner renders ABOVE the stub-data notice and the HealthHero, becoming the first visible signal when alerts are active. When inactive, dashboard unchanged.
  • Route /alerts + nav entry at the TOP of the sidebar per spec §1(d) — priority position. AlertTriangle icon.

3-layer security contract

  1. title + detail rendered as PLAIN TEXT via React's default child escaping. Real alert text may eventually quote source-panel state which could in theory contain user content. FE pins via dangerouslySetInnerHTML grep on both AlertsPanel.tsx and AlertsBanner.tsx.
  2. Walk-payload sweeps: Anthropic key shapes (sk-ant-), Slack token shapes (xox*-), HMAC-secret shapes (32+ hex), email PII, raw Slack user IDs. Defense-in-depth — alert strings are operator-authored at the source-panel level today but future automated alert generators could leak.
  3. TS interface enforces shape: typed severity + open-enum category (so backend can add new categories without an FE deploy); no raw_payload / user_message companion fields exist on Alert.

Spec divergences flagged

  • source_panel_route uses the FLAT /<panel> FE convention (e.g. /cost-state, /operational-state, /webhook-events, /agent-activity) rather than the spec's /admin/<panel> form. Every panel in this branch mounts at the flat route per App.tsx; using /admin/ would 404 on click-through. Backend test pins the flat shape so a future stub edit can't silently break navigation.
  • Banner uses sessionStorage, not localStorage as the spec briefly mentioned. The spec text was internally consistent: "per-tab; resets on tab close — NOT acknowledged-state" — that's sessionStorage semantics. localStorage would persist across sessions and silence future alerts wrongly. Backend test pins sessionStorage + bans localStorage.

Test plan

  • tests/kora_cli/test_web_server_alerts.py16 tests: shape, 4-alert stub pin, severity tier diversity, per-entry schema + valid severity enum + source_panel_route flat-form pin, all 3 security guards (walk-payload sweeps for token shapes / PII; FE source-pins for dangerouslySetInnerHTML on both AlertsPanel + AlertsBanner; sessionStorage-not-localStorage pin), empty-state positive-reinforcement source-pin, banner-hides-when-empty pin, by_severity reconciliation, cron-regression sanity.
  • Full admin-panel regression: 303/303 across 25 suites.
  • pnpm tsc -b clean.
  • pnpm build clean.
  • Manual smoke: load / with stub alerts present (banner visible) → click "Open alerts" → exercise click-through to source panels → dismiss banner → reload tab (banner returns) → close tab + reopen (banner returns).

🤖 Generated with Claude Code

Single aggregating panel that pulls "things requiring attention"
from the 12 existing panels into one place. Today each panel has
its own destructive-tone trigger (cost-ladder warned, daemon
paused, dead-letters > 5, reasoning halted, capability denials > 10)
— operator has to glance at 12 cards to spot trouble. This panel
surfaces aggregate alerts at the TOP of the dashboard.

Real alert generation is DEFERRED to a backend bucket once the
source panels can expose their alert state to a central collector.
Stub-then-real, same pattern as the prior 7 stub panels.

Single-PR scope:
  * GET /api/alerts/current stub — 4 representative alerts
    deliberately spanning critical / warning / info AND four
    distinct categories so the operator's first look exercises:
      - severity sort order (critical → warning → info)
      - banner border-tone mapping (red / yellow / blue)
      - category icon variety (DollarSign / PauseCircle /
        Inbox / Workflow)
      - click-through nav via source_panel_route
    stub:true keeps the FE banner visible.
  * AlertsPanel.tsx — title + stub banner + aggregate strip +
    severity-grouped rows (critical first, then warning, then
    info) with category icons + expandable detail + "Open
    {source_panel}" link button → navigates via react-router.
    Empty state: green CheckCircle2 + "No active alerts.
    Daemon healthy." (positive reinforcement; no false-alarm
    trigger from absent data).
  * AlertsBanner.tsx (NEW component) — compact dashboard-top
    banner. Hidden when alerts.length === 0 OR data not loaded.
    Severity-counted summary inline. Worst-severity border tone
    drives at-a-glance attention. Dismiss button uses
    SESSIONSTORAGE (per-tab; resets on tab close) — explicitly
    NOT localStorage (which would persist across browser
    sessions and wrongly silence future alerts). Dismissal is
    keyed on the alert id-set hash so new alerts re-trigger the
    banner even within an already-dismissed tab.
  * Dashboard placement: banner renders ABOVE the existing
    stub-data notice and the HealthHero, becoming the FIRST
    visible signal on the page when alerts are active. When
    inactive, dashboard unchanged.
  * Route /alerts + nav entry at the TOP of the sidebar per
    spec §1(d) — priority position. AlertTriangle icon.

3-layer security contract:
  1. title + detail rendered as PLAIN TEXT (React default child
     escaping). Real alert text may eventually quote source-panel
     state which could in theory contain user content. FE pins
     via dangerouslySetInnerHTML grep on BOTH AlertsPanel.tsx
     and AlertsBanner.tsx.
  2. Walk-payload sweeps: Anthropic key shapes (sk-ant-),
     Slack token shapes (xox*-), HMAC-secret shapes (32+ hex),
     email PII, raw Slack user IDs. Defense-in-depth — alert
     strings are operator-authored at the source-panel level
     today but future automated alert generators could leak.
  3. TS interface enforces shape: typed severity + open-enum
     category (so backend can add new categories without an FE
     deploy); no raw_payload / user_message companion fields.

Spec divergence flagged: source_panel_route uses the FLAT
/<panel> FE convention (e.g. "/cost-state", "/operational-state",
"/webhook-events", "/agent-activity") rather than the spec's
/admin/<panel> form. Every panel in this branch mounts at the
flat route per App.tsx; using /admin/ would 404 on click-through.
Backend test pins the flat shape so a future stub edit can't
silently break navigation.

Tests:
  * tests/kora_cli/test_web_server_alerts.py — 16 tests:
    shape, 4-alert stub pin, severity tier diversity, per-entry
    schema + valid severity enum + source_panel_route flat-form
    pin, all 3 security guards (walk-payload sweeps for token
    shapes / PII; FE source-pins for dangerouslySetInnerHTML on
    both AlertsPanel + AlertsBanner; sessionStorage-not-
    localStorage pin), empty-state positive-reinforcement
    source-pin, banner-hides-when-empty pin,
    by_severity reconciliation, cron-regression sanity.
  * Full admin-panel regression: 303/303 across 25 suites.
  * tsc -b + vite build both clean.

Refs:
  * rafe-walker/kora-docs 17_cc_bucket_prompts/KR-ALERTS-PANEL_operator_attention_lens.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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