From 4dc0c01e7aaf83fb163a1c7f9073062bbe8274af Mon Sep 17 00:00:00 2001 From: Tim Waugh Date: Sat, 22 Nov 2025 17:31:20 +0000 Subject: [PATCH] fix(phase3): enable polling when decisions stream from Phase 2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 3 wasn't updating the UI for blocks 2+ when decisions were streaming in from Phase 2's background worker. The polling timer never started because initialization logic incorrectly marked all blocks as "ready" when it detected a non-empty decisions list. Root Cause: The initialization code (lines 167-174) assumed only two scenarios: 1. Empty decisions → streaming will start, don't mark blocks ready 2. Non-empty decisions → complete pre-generated list (tests), mark ALL ready But there was a third scenario it didn't handle: 3. Partially populated decisions → streaming from Phase 2, only mark blocks WITH decisions as ready This caused on_mount() to calculate all_complete=True (5 ready / 5 total), preventing the polling timer from starting. Fix: 1. Use auto_start_workers flag to distinguish test mode (True) from production mode with shared streaming list (False) 2. In production mode, only mark blocks with decisions as ready 3. Change task check from isinstance() to hasattr() to support test MockApp Result: - Polling starts correctly when blocks_ready < total_blocks - UI updates as new decisions stream in from Phase 2 - All 56 Phase 3 tests pass Assisted-by: Claude Code --- .../tui/screens/integration_review.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/logsqueak/tui/screens/integration_review.py b/src/logsqueak/tui/screens/integration_review.py index 68bb160..7fbd5d8 100644 --- a/src/logsqueak/tui/screens/integration_review.py +++ b/src/logsqueak/tui/screens/integration_review.py @@ -162,16 +162,21 @@ def __init__( self._test_background_tasks: Dict[str, BackgroundTask] = {} # Track which blocks have decisions ready (for navigation blocking) - # If decisions are pre-generated, mark all blocks as ready (including those with no decisions) + # In test mode (decisions is a static list), mark all blocks as ready + # In production mode (decisions is shared streaming list), only mark blocks with decisions self.decisions_ready: Dict[str, bool] = {} if decisions: # Mark blocks with decisions as ready for block_id in self.decisions_by_block.keys(): self.decisions_ready[block_id] = True - # Mark blocks without decisions as ready too (allows navigation through empty blocks) - for ec in edited_content: - if ec.block_id not in self.decisions_ready: - self.decisions_ready[ec.block_id] = True + + # Only mark all blocks ready if NOT using shared streaming list + # (auto_start_workers=False indicates we're using shared list from Phase 2) + if auto_start_workers: + # Test mode with pre-generated decisions - mark all blocks ready + for ec in edited_content: + if ec.block_id not in self.decisions_ready: + self.decisions_ready[ec.block_id] = True # Track decision count for polling updates (when using shared list from Phase 2) self._last_known_decision_count = len(self.decisions) @@ -294,7 +299,8 @@ def on_mount(self) -> None: from logsqueak.tui.app import LogsqueakApp task = None - if isinstance(self.app, LogsqueakApp): + # Check for background_tasks attribute (works for both LogsqueakApp and test MockApp) + if hasattr(self.app, 'background_tasks'): task = self.app.background_tasks.get("llm_decisions") if task and task.status == "running":