Task 1: Add insertion anchor tracking to ChatContainer
Status: COMPLETED
Implemented in ChatContainer:
_insertion_anchor: Widget | None attribute
end_stream() captures finalized stream widget as anchor
cancel_stream() captures previous sibling as anchor
Task 2: Update insert_before_stream() to use insertion anchor
Status: COMPLETED
insert_before_stream() now handles three cases:
Active stream: insert before _current_stream
Stream ended: insert before _insertion_anchor (finalized message)
No context: append to end
Task 3: Clear insertion anchor on new request
Status: COMPLETED
start_stream() clears _insertion_anchor = None to prevent stale positioning.
clear() also clears the anchor.
Task 4: Handle cancel scenario insertion anchor
Status: COMPLETED
cancel_stream() finds the widget that was BEFORE the stream widget and sets it as anchor.
This allows late-arriving tool panels to insert at the correct position after cancel.
Task 5: Wire into app.py
Status: COMPLETED
Changes to src/tunacode/ui/app.py:
Replaced RichLog import with ChatContainer
Added ChatContainer to widget imports
Changed self.rich_log: RichLog to self.chat_container: ChatContainer
Added rich_log property alias for backward compatibility
Updated compose() to create ChatContainer instead of RichLog
Added chat_container.start_stream() at request start
Added chat_container.end_stream() in finally block before writing response
Added chat_container.cancel_stream() in action_cancel_stream()
Changed on_tool_result_display() to use insert_before_stream()
Files modified:
src/tunacode/ui/app.py
src/tunacode/ui/welcome.py - Updated type hint to use WriteableLog protocol
Gate Results
Tests: PASS (512 passed)
Type checks: PASS (no new errors introduced)
Linters: PASS (all checks passed)
Files Changed Summary
File
Action
Description
src/tunacode/ui/widgets/chat.py
Created
ChatContainer widget with insertion tracking
src/tunacode/ui/widgets/__init__.py
Modified
Export ChatContainer
src/tunacode/ui/app.py
Modified
Replace RichLog with ChatContainer, wire up lifecycle
src/tunacode/ui/welcome.py
Modified
Update type hint for compatibility
Notes
Branch: fix/insert-before-stream-race
Rollback point: 5efc5423
End commit: 9be710d9
The rich_log property alias ensures backward compatibility for code that references app.rich_log
Execution Report
Date: 2026-01-27
Plan Source: memory-bank/plan/reserch.md
Start commit: 5efc5423
End commit: 9be710d9
Branch: fix/insert-before-stream-race
Final status: SUCCESS
Outcomes
Tasks attempted: 6 (0 + 5 from plan)
Tasks completed: 6
Rollbacks: None
All gates passed
1---2name: 569-2026-01-27-17-52-14-insert-before-stream-fix-f3d11f8a3description: Pre-Flight Checks4---56## Pre-Flight Checks78- [x] DoR satisfied? Yes - plan is complete with acceptance tests9- [x] Access/secrets present? N/A - no secrets needed10- [x] Fixtures/data ready? N/A1112**Note:** Plan references `ChatContainer` which doesn't exist. User approved creating minimal ChatContainer first.1314## Execution Progress1516### Task 0: Create Minimal ChatContainer Widget1718**Status:** COMPLETED1920Created `src/tunacode/ui/widgets/chat.py` with:21- `ChatContainer` class extending `VerticalScroll`22- `_current_stream` tracking for active streaming widget23- `_insertion_anchor` tracking for post-stream insertion point24- `write()` method for backward compat with RichLog25- `start_stream()`, `update_stream()`, `end_stream()`, `cancel_stream()` lifecycle methods26- `insert_before_stream()` for positioning tool panels correctly2728Files created:29- `src/tunacode/ui/widgets/chat.py`3031Files modified:32- `src/tunacode/ui/widgets/__init__.py` - added ChatContainer export3334---3536### Task 1: Add insertion anchor tracking to ChatContainer3738**Status:** COMPLETED3940Implemented in ChatContainer:41- `_insertion_anchor: Widget | None` attribute42- `end_stream()` captures finalized stream widget as anchor43- `cancel_stream()` captures previous sibling as anchor4445---4647### Task 2: Update insert_before_stream() to use insertion anchor4849**Status:** COMPLETED5051`insert_before_stream()` now handles three cases:521. Active stream: insert before `_current_stream`532. Stream ended: insert before `_insertion_anchor` (finalized message)543. No context: append to end5556---5758### Task 3: Clear insertion anchor on new request5960**Status:** COMPLETED6162`start_stream()` clears `_insertion_anchor = None` to prevent stale positioning.63`clear()` also clears the anchor.6465---6667### Task 4: Handle cancel scenario insertion anchor6869**Status:** COMPLETED7071`cancel_stream()` finds the widget that was BEFORE the stream widget and sets it as anchor.72This allows late-arriving tool panels to insert at the correct position after cancel.7374---7576### Task 5: Wire into app.py7778**Status:** COMPLETED7980Changes to `src/tunacode/ui/app.py`:81- Replaced `RichLog` import with `ChatContainer`82- Added `ChatContainer` to widget imports83- Changed `self.rich_log: RichLog` to `self.chat_container: ChatContainer`84- Added `rich_log` property alias for backward compatibility85- Updated `compose()` to create `ChatContainer` instead of `RichLog`86- Added `chat_container.start_stream()` at request start87- Added `chat_container.end_stream()` in finally block before writing response88- Added `chat_container.cancel_stream()` in `action_cancel_stream()`89- Changed `on_tool_result_display()` to use `insert_before_stream()`9091Files modified:92- `src/tunacode/ui/app.py`93- `src/tunacode/ui/welcome.py` - Updated type hint to use `WriteableLog` protocol9495---9697## Gate Results9899- Tests: PASS (512 passed)100- Type checks: PASS (no new errors introduced)101- Linters: PASS (all checks passed)102103## Files Changed Summary104105| File | Action | Description |106|------|--------|-------------|107| `src/tunacode/ui/widgets/chat.py` | Created | ChatContainer widget with insertion tracking |108| `src/tunacode/ui/widgets/__init__.py` | Modified | Export ChatContainer |109| `src/tunacode/ui/app.py` | Modified | Replace RichLog with ChatContainer, wire up lifecycle |110| `src/tunacode/ui/welcome.py` | Modified | Update type hint for compatibility |111112## Notes113114- Branch: `fix/insert-before-stream-race`115- Rollback point: `5efc5423`116- End commit: `9be710d9`117- The `rich_log` property alias ensures backward compatibility for code that references `app.rich_log`118119## Execution Report120121**Date:** 2026-01-27122**Plan Source:** memory-bank/plan/reserch.md123**Start commit:** 5efc5423124**End commit:** 9be710d9125**Branch:** fix/insert-before-stream-race126**Final status:** SUCCESS127128### Outcomes129- Tasks attempted: 6 (0 + 5 from plan)130- Tasks completed: 6131- Rollbacks: None132- All gates passed
Run npx skillmds@latest add tools-only/569-2026-01-27-17-52-14-insert-before-stream-fix-f3d11f8a in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Pre-Flight Checks It is listed under Coding & Dev Tools on SkillMD.
This skill has not completed SkillMD's automated safety review yet. Independent scanners report: SkillSpector: PASS, Skill Scanner: PASS. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free, and the skill stays under its author's original license.
tools-only (@tools-only) published this skill. Their other Agent Skills are listed on their SkillMD profile.