Skip to content

feat(m2): interactive TUI — manual event injection + bridge loop integration#12

Merged
SolshineCode merged 1 commit into
mainfrom
feat/m2-tui-interactive
May 8, 2026
Merged

feat(m2): interactive TUI — manual event injection + bridge loop integration#12
SolshineCode merged 1 commit into
mainfrom
feat/m2-tui-interactive

Conversation

@SolshineCode
Copy link
Copy Markdown
Owner

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:

  • g Goal +1 (TaskCompleted)
  • p Plan +1 (SubagentStart Plan)
  • r Redirect (PermissionDenied → momentum 0)
  • a Active (UserPromptSubmit)
  • t Advance turn (process_turn → score → save → callbacks)
  • s Save now
  • q Quit (auto-saves first)

Plus an EventLogPanel showing the last 8 state changes for live visibility, and HookListener.on_change wired so every change refreshes panels.

run_tui.py is now production-shaped: assembles HookListener + SaveExchange + BridgeLoop and hands them to MainApp.

Closes / Toward

  • Closes M1 outstanding "TUI close/reopen state preservation" item
  • Toward kanban t_859cbdbf (M2) + t_cefabee5 (M1)

Test plan

  • pytest -q → 81 passed (was 77; adds 4)
  • run_tui.py smoke imports + builds initial state from save (or default)

Remaining for M2

  • City-tile session-status indicator (Unciv-side, M3 territory)
  • Onboarding tutorial flow (M3)

🤖 Generated with Claude Code

…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>
@SolshineCode SolshineCode merged commit 95827a4 into main May 8, 2026
1 check passed
@SolshineCode SolshineCode deleted the feat/m2-tui-interactive branch May 8, 2026 05:26
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread tui/app.py

import asyncio
import time
from typing import Optional
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Add TYPE_CHECKING to allow for clean forward-reference type hints without runtime overhead or circular dependencies.

Suggested change
from typing import Optional
from typing import Optional, TYPE_CHECKING

Comment thread tui/app.py
Comment on lines +32 to +35
try:
from bridge.bridge_loop import BridgeLoop # noqa: F401
except Exception: # pragma: no cover
BridgeLoop = None # type: ignore
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
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

Comment thread tui/app.py
except Exception:
log = None
if self.bridge_loop is not None:
asyncio.create_task(self._do_advance_turn(log))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
asyncio.create_task(self._do_advance_turn(log))
self.run_worker(self._do_advance_turn(log))

Comment thread tui/app.py
# Listener callback — fires on every HookListener change.
# ──────────────────────────────────────────────────────────────────

def _on_state_change(self, change) -> None:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Add a type hint for the change parameter to improve code clarity and enable better IDE support.

Suggested change
def _on_state_change(self, change) -> None:
def _on_state_change(self, change: StateChange) -> None:

Comment thread tui/app.py
Comment on lines +251 to +256
try:
log = self.query_one(EventLogPanel)
log.append(f"{change.event_type}: {change.field} {change.before}→{change.after}")
self.refresh_panels()
except Exception:
pass
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Avoid using a broad except Exception: pass as it can hide legitimate bugs. If the goal is to handle cases where the UI might be shutting down, consider catching specific exceptions like textual.css.query.NoMatches or checking if the app is still running.

SolshineCode added a commit that referenced this pull request May 8, 2026
…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>
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