feat(m2): interactive TUI — manual event injection + bridge loop integration#12
Conversation
…gration
The TUI is now wired to the bridge daemon end-to-end. Real Claude Code
hook events (when the listener is fed by stdin) drive live UI updates;
in the meantime, keyboard shortcuts let users inject events manually
(PRD §BR-11) and watch resources accrue.
tui/app.py:
- MainApp accepts an optional BridgeLoop; when present, the app's state
and scoring are taken from the loop so all paths converge.
- New BINDINGS:
g inject TaskCompleted (goal +1, momentum +1)
p inject SubagentStart Plan
r inject PermissionDenied (Redirect: momentum → 0)
a inject UserPromptSubmit (mark active)
t advance turn (process_turn → score → save → callbacks fire)
s save now (force write to save exchange)
q quit (auto-saves first if save exchange is attached)
- KingdomView renders per-session goals / momentum / tokens / plan count.
- New EventLogPanel: rolling 8-line log of recent state changes for
visibility (what hook events landed, what the most recent turn
produced, save confirmations).
- HookListener.on_change is wired so every state change ALSO refreshes
the panels — live UI updates from the daemon.
run_tui.py: production wiring — assembles HookListener + SaveExchange
+ BridgeLoop and hands them to MainApp. Auto-loads ./saves/ on start.
tui/tests/test_app.py — adds 4 tests:
- bridge_loop is accepted and overrides state/scoring
- _direct_mutate fallback handles all 4 event types
- _ensure_session creates a default with id=manual-1, status=ACTIVE
- KingdomView render includes goals/momentum/tokens/plans count
Total: 81 passing.
Closes the M1 outstanding "TUI close/reopen" item (auto-load on mount,
auto-save on quit). Toward kanban t_859cbdbf (M2 Core Economy) and
t_cefabee5 (M1 Vertical Slice).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request enhances the Claude Kingdoms TUI by integrating the BridgeLoop and implementing interactive controls for manual event injection. Key updates include new keyboard shortcuts for game actions, an EventLogPanel for real-time visibility of state changes, and improved rendering of session details. Feedback focuses on idiomatic Python and Textual practices, such as using TYPE_CHECKING for clean imports, replacing asyncio.create_task with self.run_worker for better task management, adding type hints, and avoiding broad exception handling.
|
|
||
| import asyncio | ||
| import time | ||
| from typing import Optional |
| try: | ||
| from bridge.bridge_loop import BridgeLoop # noqa: F401 | ||
| except Exception: # pragma: no cover | ||
| BridgeLoop = None # type: ignore |
There was a problem hiding this comment.
Using TYPE_CHECKING is the idiomatic way to handle imports used only for type hinting. This avoids the need for try...except blocks and noqa comments at the top level.
| try: | |
| from bridge.bridge_loop import BridgeLoop # noqa: F401 | |
| except Exception: # pragma: no cover | |
| BridgeLoop = None # type: ignore | |
| if TYPE_CHECKING: | |
| from bridge.bridge_loop import BridgeLoop | |
| from bridge.hook_listener import StateChange |
| except Exception: | ||
| log = None | ||
| if self.bridge_loop is not None: | ||
| asyncio.create_task(self._do_advance_turn(log)) |
There was a problem hiding this comment.
In Textual, it is recommended to use self.run_worker() instead of asyncio.create_task(). run_worker manages the task lifecycle within the app, ensuring it is properly cleaned up on exit and providing better error handling.
| asyncio.create_task(self._do_advance_turn(log)) | |
| self.run_worker(self._do_advance_turn(log)) |
| # Listener callback — fires on every HookListener change. | ||
| # ────────────────────────────────────────────────────────────────── | ||
|
|
||
| def _on_state_change(self, change) -> None: |
| try: | ||
| log = self.query_one(EventLogPanel) | ||
| log.append(f"{change.event_type}: {change.field} {change.before}→{change.after}") | ||
| self.refresh_panels() | ||
| except Exception: | ||
| pass |
…eholder Closes audit P1 acceptance criteria #11 (README GIF placeholder) and #12 (documentation covers all four contribution paths). CONTRIBUTING.md — first-class onboarding doc for new contributors: - Enumerates the four paths: Bridge (Python), TUI (Python/Textual), Unciv mod (JSON+Lua), Documentation (Markdown). Each with a clear files/entrypoint listing and "good for you if" hook. - Reproduces the test ownership matrix from PRD §"Test Ownership Matrix" and points at each path's test file. - PR process aligned with the project's safety rules (no force push to main, no --no-verify, branch naming convention). - Lists standing `good first issue`-style options per path so acceptance #13 has a path forward even before labeled issues exist. - Code style for each language (Python, Markdown, Lua, JSON). README.md — adds a GIF placeholder block at the top with the recording protocol pointer (`docs/media/README.md`). Acceptance criterion #11 calls out a *working* GIF; this lands the placeholder + recording spec so it can be filled in at M4 launch time when the Unciv map is authored. The placeholder is explicit, not deceptive — readers know it's a planned asset. docs/media/README.md — recording protocol for the V1 GIF: 30-second shot list, frame timing, ffmpeg + gifski pipeline. Documents WHY the GIF can't ship today (needs M4 map content first). No code changes. 191 tests still passing. Refs: CTO audit doc PR #22, kanban t_26404be3, acceptance #11 + #12. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
The TUI is wired to the bridge daemon end-to-end. Manual event injection (PRD §BR-11) drives real scoring; auto-save on exit closes the M1 close-reopen loop.
Key bindings:
gGoal +1 (TaskCompleted)pPlan +1 (SubagentStart Plan)rRedirect (PermissionDenied → momentum 0)aActive (UserPromptSubmit)tAdvance turn (process_turn → score → save → callbacks)sSave nowqQuit (auto-saves first)Plus an
EventLogPanelshowing the last 8 state changes for live visibility, andHookListener.on_changewired so every change refreshes panels.run_tui.pyis now production-shaped: assembles HookListener + SaveExchange + BridgeLoop and hands them to MainApp.Closes / Toward
t_859cbdbf(M2) +t_cefabee5(M1)Test plan
pytest -q→ 81 passed (was 77; adds 4)run_tui.pysmoke imports + builds initial state from save (or default)Remaining for M2
🤖 Generated with Claude Code