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

feat(kora): KR-PANEL-USE-INSTRUMENTATION — panel-view event emit (data-driven cut prerequisite)#159

Merged
rafe-walker merged 1 commit into
feature/phase2-upgradesfrom
feat/kora-KR-PANEL-USE-INSTRUMENTATION
May 24, 2026
Merged

feat(kora): KR-PANEL-USE-INSTRUMENTATION — panel-view event emit (data-driven cut prerequisite)#159
rafe-walker merged 1 commit into
feature/phase2-upgradesfrom
feat/kora-KR-PANEL-USE-INSTRUMENTATION

Conversation

@rafe-walker

Copy link
Copy Markdown
Owner

Summary

Per Council R3 lock + sub-cut (c): every top-level *Page.tsx / *Panel.tsx emits a panel_view event on mount to a backend sink that accretes operator-UX telemetry. Builds the data needed for any future panel-shape decisions — no cuts happen blind. Zero LLM cost per emit. Fire-and-forget POST with silent failure (instrumentation MUST NEVER break operator UX).

Separate panel_views.jsonl chosen over SeamName extension; audit log preserved as forensic surface, panel_views as UX telemetry surface.

STOP-ASK + PM endorsement

Surfaced before drafting: extending kora_cli/audit/jsonl_sink.py's SeamName Literal would mix operator-UX telemetry into the forensic/compliance audit log (Pydantic extra="forbid" + tight Literal kept intentionally narrow). A future read_audit_entries(seam=None) query would surface panel-views unexpectedly — latent contract violation. PM endorsed Path B's separate-stream design.

Backend (kora_cli/web_server.py)

New POST /api/panel_view endpoint writing to ${KORA_HOME}/panel_views.jsonl. Write discipline mirrors audit/jsonl_sink.py:

  • mkdir(parents=True, exist_ok=True) — fresh KORA_HOME
  • Atomic single-line append per request
  • Best-effort: OSError → WARN-log + return {ok:True, warning:"write_failed"} so FE caller stays green

Validation: panel_name required + non-empty (else 400), truncated to 128 chars; session_id missing/empty → "unknown", truncated to 64; hardcoded kind="panel_view" (FE can't override — tested).

Reader OUT OF SCOPE per PM direction — we just write; consumers come later when there's data to act on.

Frontend hook (web/src/hooks/usePanelView.ts)

export default function AlertsPanel() {
  usePanelView("AlertsPanel");
  // ... component body ...
}

useEffect with [panelName] deps emits once per mount. getOrCreateSessionId() reads sessionStorage["kora_session_id"], generates per-tab UUID via crypto.randomUUID() if missing, falls back to Math.random base36 on older browsers, and to "unknown" if sessionStorage throws (private browsing / strict cookie modes). .catch() silences errors — instrumentation must never break UX.

Instrumentation pass: 34 panels

All top-level web/src/pages/*.tsx instrumented via mechanical sweep + ChatPage manual fix (multi-line destructured-param signature). No internal components / web/src/components/ui/* touched per spec scope.

Panels (8): AgentActivityPanel, AlertsPanel, EmailPanel, HeartbeatPanel, MCPClientsPanel, ReasoningPanel, SlackDMPanel, WebhookEventsPanel

Pages (26): AnalyticsPage, BootStatusPage, CapabilitiesPage, ChainEventsPage, CharterPage, ChatPage, ConfigPage, CostStatePage, CronPage, DashboardPage, DocsPage, DRStatePage, EnvPage, HealthRollupPage, IdentityPage, KoraControlPage, LogsPage, MCPPage, ModelsPage, OperationalStatePage, PluginsPage, ProfilesPage, RunbooksPage, SeaTicketsPage, SessionsPage, SkillsPage

Test plan

  • tests/kora_cli/test_web_server_panel_view.py18 tests covering: happy-path JSONL append, validation (empty/missing/whitespace/oversized panel_name), session_id semantics (missing/empty → "unknown"; oversized → truncated), multi-POST append-only, entry shape pin, fresh KORA_HOME mkdir, read-only KORA_HOME graceful warning, SECURITY pin against FE-supplied kind override, frontend source pins (hook exists / POSTs / catches), every-page-instrumented inventory pin, page-count snapshot pin.
  • Regression: 210/210 on the panel + audit suites.
  • pnpm tsc -b clean.
  • pnpm build clean.
  • Manual smoke: open each page in browser, confirm panel_views.jsonl accumulates one row per mount with the correct panel_name.

Refs

  • rafe-walker/kora-docs17_cc_bucket_prompts/KR-PANEL-USE-INSTRUMENTATION_view_event_emit_no_llm_cost.md
  • PM Path-B decision: STOP-ASK answered with rationale endorsement
  • Council R3 lock + sub-cut (c) — unified-operator-interface lens prerequisite

🤖 Generated with Claude Code

…a-driven cut prerequisite)

Per Council R3 lock + sub-cut (c): every top-level *Page.tsx /
*Panel.tsx emits a panel_view event on mount to a backend sink
that accretes operator-UX telemetry. Builds the data needed for
any future panel-shape decisions — no cuts happen blind.

Zero LLM cost per emit. Fire-and-forget POST with silent failure
(instrumentation MUST NEVER break operator UX).

PM-confirmed Path B (separate panel_views.jsonl, not SeamName
extension)
=========================================================

Separate panel_views.jsonl chosen over SeamName extension; audit
log preserved as forensic surface, panel_views as UX telemetry
surface.

STOP-ASK surfaced + answered before drafting: extending
kora_cli/audit/jsonl_sink.py's SeamName Literal would mix
operator-UX telemetry into the forensic/compliance audit log
(Pydantic extra="forbid" + tight Literal kept intentionally
narrow). A future read_audit_entries(seam=None) query would
surface panel-views unexpectedly — latent contract violation.
PM endorsed Path B's separate-stream design.

Backend (kora_cli/web_server.py)
=================================

New ``POST /api/panel_view`` endpoint writing to
``${KORA_HOME}/panel_views.jsonl``. Write discipline mirrors
``audit/jsonl_sink.py``:
  * mkdir(parents=True, exist_ok=True) — fresh KORA_HOME
  * Atomic single-line append per request
  * Best-effort: OSError → WARN-log + return ``{ok:True,
    warning:"write_failed"}`` so FE caller stays green
    (instrumentation must never break UX)

Validation:
  * panel_name required + non-empty (stripped) → else 400
  * panel_name truncated to _PANEL_NAME_MAX (128 chars)
  * session_id missing/empty → "unknown" (cold tabs still
    produce countable rows)
  * session_id truncated to _SESSION_ID_MAX (64 chars)
  * Hardcoded kind="panel_view" — FE can't override (test pin)

Reader OUT OF SCOPE for this bucket per PM direction — we just
write; consumers come later when we have data to act on.

Frontend hook (web/src/hooks/usePanelView.ts)
==============================================

  export function usePanelView(panelName: string): void

Usage in every top-level Page/Panel (NOT internal components):
  export default function AlertsPanel() {
    usePanelView("AlertsPanel");
    // ... component body ...
  }

Implementation:
  * useEffect with [panelName] deps — emit once per mount, not
    on re-render (React 18 strict-mode dev double-emit is a
    documented dev-only quirk; production analysis unaffected)
  * getOrCreateSessionId(): reads sessionStorage "kora_session_id";
    generates per-tab UUID via crypto.randomUUID() if missing;
    fallback to Math.random base36 on older browsers;
    fallback to "unknown" if sessionStorage throws (private
    browsing / strict cookie modes)
  * fetchJSON POST with .catch() silencing — instrumentation
    must never break UX

panelName matches component name verbatim so downstream queries
against panel_views.jsonl can group by component without mapping.

Instrumentation pass: 34 panels
================================

All top-level web/src/pages/*.tsx instrumented via mechanical
sweep + ChatPage manual fix (multi-line destructured-param
signature). Inventory:

  Panels (8):  AgentActivityPanel, AlertsPanel, EmailPanel,
               HeartbeatPanel, MCPClientsPanel, ReasoningPanel,
               SlackDMPanel, WebhookEventsPanel
  Pages (26):  AnalyticsPage, BootStatusPage, CapabilitiesPage,
               ChainEventsPage, CharterPage, ChatPage, ConfigPage,
               CostStatePage, CronPage, DashboardPage, DocsPage,
               DRStatePage, EnvPage, HealthRollupPage, IdentityPage,
               KoraControlPage, LogsPage, MCPPage, ModelsPage,
               OperationalStatePage, PluginsPage, ProfilesPage,
               RunbooksPage, SeaTicketsPage, SessionsPage,
               SkillsPage

NO internal components or web/src/components/ui/* instrumented
per spec scope (only top-level pages/panels rendered as routes).

Tests (tests/kora_cli/test_web_server_panel_view.py — 18 tests)
================================================================

Backend endpoint:
  * Happy path appends correct JSONL row
  * Empty/missing/whitespace panel_name → 400
  * Oversized panel_name truncated to 128
  * Missing/empty session_id → "unknown"
  * Oversized session_id truncated to 64
  * Multiple POSTs append separately
  * Entry shape pin (4 keys + Z-suffixed UTC ISO)
  * Fresh KORA_HOME mkdirs before append
  * Read-only KORA_HOME → graceful warning, ok:true
  * SECURITY: FE-supplied "kind" can't override hardcoded
    "panel_view" (downstream queries protected)

Frontend pins:
  * usePanelView hook source exists
  * Posts to /api/panel_view via POST
  * .catch() error-swallow present
  * Every top-level page/panel imports + invokes usePanelView
    with the matching component name (prevents future page
    additions silently skipping instrumentation)
  * Page-count pin: 34 (snapshot; update alongside any page
    add/remove so the instrumentation audit stays accurate)

Verification:
  * 18/18 panel_view tests pass
  * 210/210 panel + audit regression (no breakage of existing
    suites)
  * pnpm tsc -b clean
  * pnpm build clean

Refs:
  * rafe-walker/kora-docs 17_cc_bucket_prompts/KR-PANEL-USE-INSTRUMENTATION_view_event_emit_no_llm_cost.md
  * PM Path-B decision: STOP-ASK answered with rationale
    endorsement
  * Council R3 lock + sub-cut (c) — unified-operator-interface
    lens prerequisite

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