Skip to content

feat(gm-panel): dual-dial encounter timeline tab#164

Merged
slabgorb merged 1 commit intodevelopfrom
feat/dual-track-momentum-phase-2
Apr 25, 2026
Merged

feat(gm-panel): dual-dial encounter timeline tab#164
slabgorb merged 1 commit intodevelopfrom
feat/dual-track-momentum-phase-2

Conversation

@slabgorb
Copy link
Copy Markdown
Owner

Summary

Phase 2 UI deliverable for dual-track momentum. Adds an "⑧ Encounters" tab to the GM Dashboard that renders the new ENCOUNTER_* event timeline from the server-side SQLite events table.

Companion to sidequest-server#54, which adds the GET /api/sessions/{slug}/encounter_events REST endpoint this tab consumes.

What's in here

  • New EncounterEventKind union (9 kinds) and EncounterEvent interface in src/types/payloads.ts.
  • New src/components/Dashboard/tabs/EncounterTab.tsx:
    • EncounterTimeline (pure renderer) — ordered <ol> with per-kind row functions for STARTED, BEAT_APPLIED, METRIC_ADVANCE, BEAT_SKIPPED, TAG_CREATED, STATUS_ADDED, YIELD, RESOLVED. Renders nothing for the internal RESOLUTION_SIGNAL kind.
    • EncounterTab (fetch wrapper) — calls /api/sessions/{slug}/encounter_events on slug change with proper unmount cancellation; surfaces fetch errors rather than swallowing them (no silent fallbacks).
  • Tab registered in DashboardApp.tsx at index 7; label "⑧ Encounters" added to DashboardTabs.tsx. Active session slug derived from debugState using the same newest-last_activity_ts logic as StateTab.
  • Vitest tests in src/__tests__/EncounterTab.test.tsx: row rendering, dial-pair view, exported-and-importable wiring test, no-session placeholder.

Visual context

This is the lie-detector tab from CLAUDE.md's OTEL principle: "If a subsystem isn't emitting OTEL spans, you can't tell whether it's engaged or whether Claude is just improvising." Phase 2 wired the encounter subsystem to write typed event rows; this tab makes them readable post-hoc.

Test plan

  • npx vitest run src/__tests__/EncounterTab.test.tsx — 4/4 pass.
  • npx tsc --noEmit — clean.
  • Browser verification deferred to Phase 3 Task 31 (regression playtest), which boots the full stack.

🤖 Generated with Claude Code

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@slabgorb slabgorb merged commit fa5b2da into develop Apr 25, 2026
1 check failed
slabgorb added a commit that referenced this pull request Apr 25, 2026
* feat(gm-panel): render dual-dial encounter timeline with new event kinds (#164)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(ui): yield button + /yield command

Add YieldButton component, wire it into the confrontation overlay, add
/yield slash command that sends a YIELD wire message, and thread the
onYield callback from App.tsx through GameBoard → ConfrontationWidget →
ConfrontationOverlay. MessageType.YIELD added to protocol.ts to mirror
the server-side type added in Task 23.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ui): forward slash-command messages to server send pipeline

handleSend was appending slash-command-produced messages (e.g. /yield →
YIELD) to local state but never calling send(), so the server's
_handle_yield never fired. Add send(msg) for-loop inside the
slashResult.messages.length > 0 block; intentionally omit setCanType /
setThinking — slash commands do not start a narration turn.

Add wiring test (slash-command-send-wiring.test.ts) asserting the
for-loop and send(msg) are present inside the slashResult.handled branch
and that setCanType/setThinking are absent from that branch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant