fix(coding-agent): toggle summary entries on click

This commit is contained in:
Mario Zechner
2026-09-16 18:08:06 +02:00
parent 01528e2094
commit fde6d778f8
4 changed files with 124 additions and 14 deletions
@@ -1,4 +1,4 @@
import { Box, Markdown, type MarkdownTheme, Spacer, Text } from "@earendil-works/pi-tui";
import { Box, Container, Markdown, type MarkdownTheme, MouseRegion, Spacer, Text } from "@earendil-works/pi-tui";
import type { BranchSummaryMessage } from "../../../core/messages.ts";
import { getMarkdownTheme, theme } from "../theme/theme.ts";
import { keyText } from "./keybinding-hints.ts";
@@ -31,20 +31,21 @@ export class BranchSummaryMessageComponent extends Box {
private updateDisplay(): void {
this.clear();
const content = new Container();
const label = theme.fg("customMessageLabel", `\x1b[1m[branch]\x1b[22m`);
this.addChild(new Text(label, 0, 0));
this.addChild(new Spacer(1));
content.addChild(new Text(label, 0, 0));
content.addChild(new Spacer(1));
if (this.expanded) {
const header = "**Branch Summary**\n\n";
this.addChild(
content.addChild(
new Markdown(header + this.message.summary, 0, 0, this.markdownTheme, {
color: (text: string) => theme.fg("customMessageText", text),
}),
);
} else {
this.addChild(
content.addChild(
new Text(
theme.fg("customMessageText", "Branch summary (") +
theme.fg("dim", keyText("app.tools.expand")) +
@@ -54,5 +55,13 @@ export class BranchSummaryMessageComponent extends Box {
),
);
}
this.addChild(
new MouseRegion(content, (event) => {
if (event.type !== "click" || event.button !== "left") return undefined;
this.setExpanded(!this.expanded);
return { handled: true };
}),
);
}
}
@@ -1,4 +1,4 @@
import { Box, Markdown, type MarkdownTheme, Spacer, Text } from "@earendil-works/pi-tui";
import { Box, Container, Markdown, type MarkdownTheme, MouseRegion, Spacer, Text } from "@earendil-works/pi-tui";
import type { CompactionSummaryMessage } from "../../../core/messages.ts";
import { getMarkdownTheme, theme } from "../theme/theme.ts";
import { keyText } from "./keybinding-hints.ts";
@@ -31,21 +31,22 @@ export class CompactionSummaryMessageComponent extends Box {
private updateDisplay(): void {
this.clear();
const content = new Container();
const tokenStr = this.message.tokensBefore.toLocaleString();
const label = theme.fg("customMessageLabel", `\x1b[1m[compaction]\x1b[22m`);
this.addChild(new Text(label, 0, 0));
this.addChild(new Spacer(1));
content.addChild(new Text(label, 0, 0));
content.addChild(new Spacer(1));
if (this.expanded) {
const header = `**Compacted from ${tokenStr} tokens**\n\n`;
this.addChild(
content.addChild(
new Markdown(header + this.message.summary, 0, 0, this.markdownTheme, {
color: (text: string) => theme.fg("customMessageText", text),
}),
);
} else {
this.addChild(
content.addChild(
new Text(
theme.fg("customMessageText", `Compacted from ${tokenStr} tokens (`) +
theme.fg("dim", keyText("app.tools.expand")) +
@@ -55,5 +56,13 @@ export class CompactionSummaryMessageComponent extends Box {
),
);
}
this.addChild(
new MouseRegion(content, (event) => {
if (event.type !== "click" || event.button !== "left") return undefined;
this.setExpanded(!this.expanded);
return { handled: true };
}),
);
}
}
@@ -1,4 +1,4 @@
import { Box, Markdown, type MarkdownTheme, Text } from "@earendil-works/pi-tui";
import { Box, Container, Markdown, type MarkdownTheme, MouseRegion, Text } from "@earendil-works/pi-tui";
import type { ParsedSkillBlock } from "../../../core/agent-session.ts";
import { getMarkdownTheme, theme } from "../theme/theme.ts";
import { keyText } from "./keybinding-hints.ts";
@@ -32,13 +32,14 @@ export class SkillInvocationMessageComponent extends Box {
private updateDisplay(): void {
this.clear();
const content = new Container();
if (this.expanded) {
// Expanded: label + skill name header + full content
const label = theme.fg("customMessageLabel", `\x1b[1m[skill]\x1b[22m`);
this.addChild(new Text(label, 0, 0));
content.addChild(new Text(label, 0, 0));
const header = `**${this.skillBlock.name}**\n\n`;
this.addChild(
content.addChild(
new Markdown(header + this.skillBlock.content, 0, 0, this.markdownTheme, {
color: (text: string) => theme.fg("customMessageText", text),
}),
@@ -49,7 +50,15 @@ export class SkillInvocationMessageComponent extends Box {
theme.fg("customMessageLabel", `\x1b[1m[skill]\x1b[22m `) +
theme.fg("customMessageText", this.skillBlock.name) +
theme.fg("dim", ` (${keyText("app.tools.expand")} to expand)`);
this.addChild(new Text(line, 0, 0));
content.addChild(new Text(line, 0, 0));
}
this.addChild(
new MouseRegion(content, (event) => {
if (event.type !== "click" || event.button !== "left") return undefined;
this.setExpanded(!this.expanded);
return { handled: true };
}),
);
}
}
@@ -0,0 +1,83 @@
import type { Box, TuiMouseEvent } from "@earendil-works/pi-tui";
import { beforeAll, describe, expect, test } from "vitest";
import { BranchSummaryMessageComponent } from "../src/modes/interactive/components/branch-summary-message.ts";
import { CompactionSummaryMessageComponent } from "../src/modes/interactive/components/compaction-summary-message.ts";
import { SkillInvocationMessageComponent } from "../src/modes/interactive/components/skill-invocation-message.ts";
import { initTheme } from "../src/modes/interactive/theme/theme.ts";
import { stripAnsi } from "../src/utils/ansi.ts";
const WIDTH = 80;
function renderText(component: Box): string {
return stripAnsi(component.render(WIDTH).join("\n"));
}
function clickRow(component: Box, marker: string): void {
const lines = component.render(WIDTH);
const row = lines.findIndex((line) => stripAnsi(line).includes(marker));
expect(row).toBeGreaterThanOrEqual(0);
const event: TuiMouseEvent = {
type: "click",
button: "left",
x: 2,
y: row,
screenX: 2,
screenY: row,
width: WIDTH,
height: lines.length,
shift: false,
alt: false,
ctrl: false,
clickCount: 1,
};
expect(component.handleMouse(event)?.handled).toBe(true);
}
describe("collapsible message components", () => {
beforeAll(() => initTheme("dark"));
test("toggles a compaction summary when clicked", () => {
const component = new CompactionSummaryMessageComponent({
role: "compactionSummary",
summary: "compaction details",
tokensBefore: 1234,
timestamp: Date.now(),
});
expect(renderText(component)).not.toContain("compaction details");
clickRow(component, "[compaction]");
expect(renderText(component)).toContain("compaction details");
clickRow(component, "[compaction]");
expect(renderText(component)).not.toContain("compaction details");
});
test("toggles a branch summary when clicked", () => {
const component = new BranchSummaryMessageComponent({
role: "branchSummary",
summary: "branch details",
fromId: "entry-1",
timestamp: Date.now(),
});
expect(renderText(component)).not.toContain("branch details");
clickRow(component, "[branch]");
expect(renderText(component)).toContain("branch details");
clickRow(component, "[branch]");
expect(renderText(component)).not.toContain("branch details");
});
test("toggles a skill invocation when clicked", () => {
const component = new SkillInvocationMessageComponent({
name: "example-skill",
location: "/tmp/example-skill.md",
content: "skill details",
userMessage: undefined,
});
expect(renderText(component)).not.toContain("skill details");
clickRow(component, "[skill]");
expect(renderText(component)).toContain("skill details");
clickRow(component, "[skill]");
expect(renderText(component)).not.toContain("skill details");
});
});