fix(tui): preserved pending message on thinking toggle
Updated thinking visibility toggles to refresh assistant blocks in place instead of rebuilding the transcript, preserving pending user submissions and loaders before streaming starts. Added a regression test for the Ctrl+T pre-stream gap.\n\nFixes #2370
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed Ctrl+T thinking-block toggles clearing the pending user message and loader before the assistant stream starts ([#2370](https://github.com/can1357/oh-my-pi/issues/2370)).
|
||||
- Fixed snapcompact archives going partially unreadable on OpenRouter, which hard-caps requests at 8 images and silently drops the excess: compaction now passes a provider-aware frame budget (`providerFrameBudget`) so the archive never exceeds the cap (unknown providers get a safe floor of 5), with overflow kept as a text tail on the summary instead of rendering frames the gateway would drop; inline system-prompt/tool-result imaging shares the same per-provider budget (`providerImageBudget`), so once existing images exhaust it (e.g. 8 archive frames on OpenRouter) tool results ship verbatim as text
|
||||
- Fixed Hindsight retains to send offset-aware local timestamps instead of UTC `Z` strings so extraction prompts keep the user's local time-of-day context ([#2363](https://github.com/can1357/oh-my-pi/issues/2363)).
|
||||
- Fixed tool calls taller than the viewport reading as cut off while streaming (the head reappeared only once the result landed): the 15.11.6 stranded-preview fix marked every collapsed pending tool preview commit-unstable, which also blocked durable top-anchored streams — e.g. a task call's context/assignment markdown — from reaching native scrollback mid-run. Commit stability is now classified per renderer (`ToolRenderer.provisionalPendingPreview`): only the tail-window previews the result render re-anchors (edit/apply_patch streamed-diff tails, bash/ssh command caps, eval cells with interleaved outputs) stay provisional; every other pending preview commits its settled head mid-stream again
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { AutocompleteProvider, SlashCommand } from "@oh-my-pi/pi-tui";
|
||||
import { $env, logger, sanitizeText } from "@oh-my-pi/pi-utils";
|
||||
import { getRoleInfo } from "../../config/model-roles";
|
||||
import { isSettingsInitialized, settings } from "../../config/settings";
|
||||
import { AssistantMessageComponent } from "../../modes/components/assistant-message";
|
||||
import { renderSegmentTrack } from "../../modes/components/segment-track";
|
||||
import { TinyTitleDownloadProgressComponent } from "../../modes/components/tiny-title-download-progress";
|
||||
import { expandEmoticons } from "../../modes/emoji-autocomplete";
|
||||
@@ -1039,18 +1040,19 @@ export class InputController {
|
||||
|
||||
toggleThinkingBlockVisibility(): void {
|
||||
this.ctx.hideThinkingBlock = !this.ctx.hideThinkingBlock;
|
||||
settings.set("hideThinkingBlock", this.ctx.hideThinkingBlock);
|
||||
this.ctx.settings.set("hideThinkingBlock", this.ctx.hideThinkingBlock);
|
||||
this.ctx.session.agent.hideThinkingSummary = this.ctx.hideThinkingBlock;
|
||||
|
||||
// Rebuild chat from session messages
|
||||
this.ctx.chatContainer.clear();
|
||||
this.ctx.rebuildChatFromMessages();
|
||||
for (const child of this.ctx.chatContainer.children) {
|
||||
if (child instanceof AssistantMessageComponent) {
|
||||
child.setHideThinkingBlock(this.ctx.hideThinkingBlock);
|
||||
child.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
// If streaming, re-add the streaming component with updated visibility and re-render
|
||||
if (this.ctx.streamingComponent && this.ctx.streamingMessage) {
|
||||
this.ctx.streamingComponent.setHideThinkingBlock(this.ctx.hideThinkingBlock);
|
||||
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage);
|
||||
this.ctx.chatContainer.addChild(this.ctx.streamingComponent);
|
||||
}
|
||||
|
||||
this.ctx.showStatus(`Thinking blocks: ${this.ctx.hideThinkingBlock ? "hidden" : "visible"}`);
|
||||
|
||||
@@ -316,10 +316,9 @@ export class SelectorController {
|
||||
for (const child of this.ctx.chatContainer.children) {
|
||||
if (child instanceof AssistantMessageComponent) {
|
||||
child.setHideThinkingBlock(value as boolean);
|
||||
child.invalidate();
|
||||
}
|
||||
}
|
||||
this.ctx.chatContainer.clear();
|
||||
this.ctx.rebuildChatFromMessages();
|
||||
break;
|
||||
case "theme": {
|
||||
setTheme(value as string, true).then(result => {
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { describe, expect, it, vi } from "bun:test";
|
||||
import { AssistantMessageComponent } from "@oh-my-pi/pi-coding-agent/modes/components/assistant-message";
|
||||
import { InputController } from "@oh-my-pi/pi-coding-agent/modes/controllers/input-controller";
|
||||
import type { InteractiveModeContext } from "@oh-my-pi/pi-coding-agent/modes/types";
|
||||
|
||||
describe("InputController thinking visibility", () => {
|
||||
it("keeps pre-stream pending transcript content mounted when Ctrl+T toggles thinking blocks", () => {
|
||||
const pendingUserMessage = { kind: "pending-user" };
|
||||
const loadingIndicator = { kind: "loading" };
|
||||
const assistant = new AssistantMessageComponent();
|
||||
const setHideThinkingBlock = vi.spyOn(assistant, "setHideThinkingBlock");
|
||||
const invalidate = vi.spyOn(assistant, "invalidate");
|
||||
const clear = vi.fn();
|
||||
const addChild = vi.fn();
|
||||
const rebuildChatFromMessages = vi.fn();
|
||||
const set = vi.fn();
|
||||
const showStatus = vi.fn();
|
||||
const children = [pendingUserMessage, assistant, loadingIndicator];
|
||||
const chatContainer = { children, clear, addChild };
|
||||
const ctx = {
|
||||
hideThinkingBlock: false,
|
||||
settings: { set },
|
||||
session: { agent: { hideThinkingSummary: false } },
|
||||
chatContainer,
|
||||
streamingComponent: undefined,
|
||||
streamingMessage: undefined,
|
||||
rebuildChatFromMessages,
|
||||
showStatus,
|
||||
} as unknown as InteractiveModeContext;
|
||||
|
||||
new InputController(ctx).toggleThinkingBlockVisibility();
|
||||
|
||||
expect(ctx.hideThinkingBlock).toBe(true);
|
||||
expect(set).toHaveBeenCalledWith("hideThinkingBlock", true);
|
||||
expect(ctx.session.agent.hideThinkingSummary).toBe(true);
|
||||
expect(chatContainer.children).toEqual([pendingUserMessage, assistant, loadingIndicator]);
|
||||
expect(clear).not.toHaveBeenCalled();
|
||||
expect(addChild).not.toHaveBeenCalled();
|
||||
expect(rebuildChatFromMessages).not.toHaveBeenCalled();
|
||||
expect(setHideThinkingBlock).toHaveBeenCalledWith(true);
|
||||
expect(invalidate).toHaveBeenCalledTimes(1);
|
||||
expect(showStatus).toHaveBeenCalledWith("Thinking blocks: hidden");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user