abortTurn winds down a turn that did not complete normally: flush in-flight text so the partial block lands in scrollback, post the explanatory banner, drop pending tool calls, reset per-turn counters and context. Pair to applyDone for the happy path.
(banner string)
| 847 | // block ends it's flushed through glamour into scrollback via tea.Println. |
| 848 | func (m *Model) applyContent(e llm.Event) { |
| 849 | if m.phase == phaseThinking { |
| 850 | m.phase = phaseStreaming |
| 851 | } |
| 852 | // Expand tabs on the way into the display buffer: terminals advance a |
| 853 | // literal tab to the next 8-column stop while every width computation |
| 854 | // downstream (View's live ansi.Wrap, glamour's code-fence padding, |
| 855 | // wrapForScrollback) counts it as one cell, so a tab-indented code block |
| 856 | // passes the width checks yet physically overflows and drifts the |
| 857 | // renderer's cursor math. Display-only: history keeps e.Final untouched. |
| 858 | m.streaming.WriteString(strings.ReplaceAll(e.Content, "\t", " ")) |
| 859 | m.streamingEstimate += len(e.Content) / 4 |
| 860 | } |
| 861 | |
| 862 | // applyToolCall queues a streamed tool call for later dispatch. flushStreaming |
| 863 | // up front commits this round's text to scroll before the inline tool-call |
| 864 | // status lands, so the user sees styled text *before* the "▶ bash: ..." line, |
| 865 | // not all at once at turn end. |
| 866 | func (m *Model) applyToolCall(e llm.Event) { |
| 867 | m.flushStreaming() |
| 868 | m.pending = append(m.pending, *e.ToolCall) |
| 869 | } |
| 870 | |
| 871 | // applyDone closes one LLM round: harvest the live context window, accumulate |
| 872 | // turn/session tokens, append the assistant message, flush streaming. A turn |
| 873 | // with tool calls fires one EventDone per round; counters accumulate so the |
no test coverage detected