self.mount(widget, before=self._current_stream) elif self._insertion_anchor is not None: self.mount(widget, before=self._insertion_anchor) else: self.mount(widget) if self._auto_scroll: self.scroll_end(animate=False) Acceptance Test: Tool panel arriving after end_stream() appears before the finalized agent response, not at bottom. Dependencies: Task 1 Milestone: M2 Task 3: Clear insertion anchor on new request Summary: Reset anchor when starting a new stream to prevent stale positioning. File...
Installs into .claude/skills of the current project.
Are you the author of 558 Reserch 4fb53593?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/tools-only-558-reserch-4fb53593)
Goal
Fix the race condition where tool panels append to the end instead of inline when tools complete after stream cancellation or end.
Non-Goals
No deployment/ops changes
No observability/metrics additions
No new dependencies
Scope & Assumptions
In Scope:
Fix insert_before_stream() to correctly position tool panels even after stream ends
Track insertion point when stream ends so late-arriving tool panels insert correctly
Handle cancel scenario where tools complete post-cancel
Out of Scope:
Tool cancellation signal propagation (separate concern)
Throttling/batching of tool panels (LOW priority per research)
Assumptions:
Textual's mount(before=widget) works correctly when widget exists
Tool panels arrive via post_message() which is async (confirmed in research)
Single-threaded event loop; no concurrent message handling
Deliverables
Modified ChatContainer class with insertion point tracking
Updated insert_before_stream() to use tracked position
Updated end_stream() and cancel_stream() to preserve insertion context
Readiness
Research complete with flow analysis and root cause identified
Code locations verified in chat.py (lines 137-268)
No external dependencies required
Milestones
M1: Insertion point tracking infrastructure
M2: Fix insert_before_stream() logic
M3: Basic test scenario (manual verification)
Work Breakdown (Tasks)
Task 1: Add insertion anchor tracking to ChatContainer
Summary: Track the last widget before which tool panels should insert, persisting after stream ends.
Files: src/tunacode/ui/widgets/chat.py
Changes:
Add _insertion_anchor: Widget | None attribute in __init__
In start_stream(): set _insertion_anchor = None (panels go before stream widget)
In end_stream(): capture the finalized _current_stream widget as _insertion_anchor BEFORE setting _current_stream = None
In cancel_stream(): capture widget reference before removal, set _insertion_anchor to next sibling if exists
Acceptance Test: After calling end_stream(), _insertion_anchor holds reference to the finalized message widget.
Dependencies: None Milestone: M1
Task 2: Update insert_before_stream() to use insertion anchor
Summary: Modify logic to insert before anchor when stream is None but anchor exists.
Files: src/tunacode/ui/widgets/chat.py
Changes:
def insert_before_stream(self, renderable: RenderableType) -> None:
widget = Static(renderable)
widget.add_class("chat-message")
if self._current_stream is not None:
# Active stream: insert before streaming widget
self.mount(widget, before=self._current_stream)
elif self._insertion_anchor is not None:
# Stream ended: insert before the finalized message
self.mount(widget, before=self._insertion_anchor)
else:
# No context: append
self.mount(widget)
if self._auto_scroll:
self.scroll_end(animate=False)
Acceptance Test: Tool panel arriving after end_stream() appears before the finalized agent response, not at bottom.
Dependencies: Task 1 Milestone: M2
Task 3: Clear insertion anchor on new request
Summary: Reset anchor when starting a new stream to prevent stale positioning.
Files: src/tunacode/ui/widgets/chat.py
Changes:
In start_stream(): set _insertion_anchor = None at the start
In clear(): set _insertion_anchor = None
Acceptance Test: New request does not use stale anchor from previous request.
Dependencies: Task 1 Milestone: M2
Task 4: Handle cancel scenario insertion anchor
Summary: On cancel, preserve insertion context for late-arriving tool panels.
Files: src/tunacode/ui/widgets/chat.py
Changes:
In cancel_stream(): before removing _current_stream, find its next sibling
Set _insertion_anchor to next sibling (or None if last child)
Alternative: Keep _current_stream hidden instead of removing? (research: simpler to just track position)
Design Decision: Track position via index, not sibling reference. Siblings can be fragile.
Better approach:
def cancel_stream(self) -> None:
if self._current_stream is not None:
# Find the index of the stream widget
children = list(self.children)
try:
idx = children.index(self._current_stream)
# Anchor is widget that will be at this position after removal
if idx > 0:
self._insertion_anchor = children[idx - 1]
except ValueError:
pass
self._current_stream.remove()
self._current_stream = None
self.remove_class("streaming")
Wait - this anchors to widget BEFORE, but we want to insert AFTER it. Let me reconsider.
Revised approach: Use after= parameter for mount instead:
def cancel_stream(self) -> None:
if self._current_stream is not None:
children = list(self.children)
try:
idx = children.index(self._current_stream)
if idx > 0:
self._insertion_after_anchor = children[idx - 1]
except ValueError:
pass
self._current_stream.remove()
self._current_stream = None
self.remove_class("streaming")
Then in insert_before_stream():
elif self._insertion_after_anchor is not None:
self.mount(widget, after=self._insertion_after_anchor)
Acceptance Test: Tool panel arriving after cancel_stream() appears at the position where streaming was occurring.
Dependencies: Task 2 Milestone: M2
Task 5: Manual verification
Summary: Verify fix with real tool calls in TUI.
Files: None (manual testing)
Steps:
Start tunacode TUI
Run request that triggers tool (e.g., "read file X")
Wait for tool panel to appear inline during streaming
Cancel mid-stream (Ctrl+C or escape)
Observe: any late tool panels should NOT appear at bottom
Acceptance Test: Visual confirmation that tool panels maintain correct ordering in all scenarios.
Dependencies: Tasks 1-4 Milestone: M3
Risks & Mitigations
Risk Likelihood Impact Mitigation
Textual after= behavior differs from expected Low Medium Verify Textual docs; test in isolation
Removed widget reference causes issues Low Low Clear anchor when widget removed by other means
Stale anchor persists across sessions Low Low Clear in clear() and start_stream()
Test Strategy
Task 2 acceptance: Create unit test that mounts ChatContainer, starts stream, ends stream, then calls insert_before_stream() and verifies widget order.
Single test file: tests/ui/test_chat_container.py
References
Research doc: memory-bank/research/2026-01-27_insert-before-stream-investigation.md
Code: src/tunacode/ui/widgets/chat.py:244-261 (current implementation)
Textual mount docs: https://textual.textualize.io/api/widget/#textual.widget.Widget.mount
Final Gate
Plan path: memory-bank/plan/2026-01-27_17-41-42_insert-before-stream-fix.md
Milestone count: 3
Tasks ready for coding: 5
Next command: /context-engineer:execute "memory-bank/plan/2026-01-27_17-41-42_insert-before-stream-fix.md"