Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/coding-agent/.changes/eng-6201-touch-features.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Made the collapsed summary/header rows clickable in fullscreen chats: tools and IPython cells, agent messages, refinement and compaction/branch summaries, skill and injected-prompt cards, bash and shell blocks (including inside side-question popups), collapsible errors, startup resource sections, and custom messages toggle only the clicked item; Ctrl+O still resets all conversation detail globally.
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,18 @@ export class AgentMessageComponent extends Container {
override render(width: number): string[] {
const lines = super.render(width);
const leadingSpace = this.shouldAddLeadingSpace?.(this.expanded) ?? true;
this.clickRegions =
lines.length > 0
? [
{
line: leadingSpace ? 1 : 0,
col: 0,
width,
height: this.header.render(width).length,
onClick: () => this.setExpanded(!this.expanded),
},
]
: [];
Comment thread
cursor[bot] marked this conversation as resolved.
return leadingSpace ? ["", ...lines] : lines;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Container, Loader, Spacer, Text, type TUI } from "@earendil-works/pi-tui";
import { Clickable, Container, Loader, Spacer, Text, type TUI } from "@earendil-works/pi-tui";
import stripAnsi from "strip-ansi";
import {
DEFAULT_MAX_BYTES,
Expand Down Expand Up @@ -41,8 +41,9 @@ export class BashExecutionComponent extends Container {
this.contentContainer = new Container();
this.addChild(this.contentContainer);

const header = new Text(theme.fg(colorKey, `$ ${command}`), 1, 0);
this.contentContainer.addChild(header);
this.contentContainer.addChild(
new Clickable(new Text(theme.fg(colorKey, `$ ${command}`), 1, 0), () => this.setExpanded(!this.expanded)),
);

this.loader = new Loader(
ui,
Expand Down Expand Up @@ -127,8 +128,11 @@ export class BashExecutionComponent extends Container {

this.contentContainer.clear();

const header = new Text(theme.fg("bashMode", `$ ${this.command}`), 1, 0);
this.contentContainer.addChild(header);
this.contentContainer.addChild(
new Clickable(new Text(theme.fg("bashMode", `$ ${this.command}`), 1, 0), () =>
this.setExpanded(!this.expanded),
),
);

if (availableLines.length > 0) {
if (this.expanded) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Box, Markdown, type MarkdownTheme, Spacer, Text } from "@earendil-works/pi-tui";
import { Box, Clickable, Markdown, type MarkdownTheme, Spacer, Text } from "@earendil-works/pi-tui";
import type { BranchSummaryMessage } from "../../../core/messages.js";
import { getMarkdownTheme, theme } from "../theme/theme.js";
import { expandCollapseHint } from "./keybinding-hints.js";
Expand Down Expand Up @@ -33,7 +33,7 @@ export class BranchSummaryMessageComponent extends Box {
this.clear();

const label = theme.fg("customMessageLabel", `\x1b[1m[branch]\x1b[22m`);
this.addChild(new Text(label, 0, 0));
this.addChild(new Clickable(new Text(label, 0, 0), () => this.setExpanded(!this.expanded)));
this.addChild(new Spacer(1));

if (this.expanded) {
Expand All @@ -45,10 +45,13 @@ export class BranchSummaryMessageComponent extends Box {
);
} else {
this.addChild(
new Text(
`${theme.fg("customMessageText", "Branch summary")} ${expandCollapseHint("app.tools.expand", false)}`,
0,
0,
new Clickable(
new Text(
`${theme.fg("customMessageText", "Branch summary")} ${expandCollapseHint("app.tools.expand", false)}`,
0,
0,
),
() => this.setExpanded(!this.expanded),
),
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { type Component, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
import {
type ClickRegion,
type Component,
truncateToWidth,
visibleWidth,
wrapTextWithAnsi,
} from "@earendil-works/pi-tui";
import stripAnsi from "strip-ansi";
import { theme } from "../theme/theme.js";
import { expandCollapseHint } from "./keybinding-hints.js";
Expand Down Expand Up @@ -77,6 +83,7 @@ export function shouldCollapseErrorDetails(text: string): boolean {

export class CollapsibleErrorComponent implements Component {
private expanded: boolean;
private clickRegions: ClickRegion[] = [];

constructor(private readonly options: CollapsibleErrorOptions) {
this.expanded = options.expanded ?? false;
Expand All @@ -90,20 +97,30 @@ export class CollapsibleErrorComponent implements Component {
// Render output is derived from constructor options and expansion state.
}

getClickRegions(): ReadonlyArray<ClickRegion> {
return this.clickRegions;
}

render(width: number): string[] {
const text = normalizeErrorDetails(this.options.text);
if (!text) {
this.clickRegions = [];
return [];
}

const collapsible = this.options.forceCollapse ?? shouldCollapseErrorDetails(text);
if (!collapsible || this.expanded) {
this.clickRegions = [{ line: 0, col: 0, width, height: 1, onClick: () => this.setExpanded(!this.expanded) }];
return this.renderText(text, width);
}

const summary = normalizeErrorDetails(this.options.summary ?? summarizeErrorDetails(text));
const inlineHint = `${summary} ${expandCollapseHint("app.tools.expand", false)}`;
return this.renderText(inlineHint, width, "error");
const lines = this.renderText(inlineHint, width, "error");
this.clickRegions = [
{ line: 0, col: 0, width, height: lines.length, onClick: () => this.setExpanded(!this.expanded) },
];
return lines;
}

private renderText(text: string, width: number, color: "error" | "muted" = "error"): string[] {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Markdown, type MarkdownTheme, Spacer, Text } from "@earendil-works/pi-tui";
import { Clickable, Markdown, type MarkdownTheme, Spacer, Text } from "@earendil-works/pi-tui";
import type { CompactionSummaryMessage } from "../../../core/messages.js";
import { getMarkdownTheme, theme } from "../theme/theme.js";
import { ExpandableEventMessage } from "./expandable-event-message.js";
Expand All @@ -15,7 +15,11 @@ export class CompactionSummaryMessageComponent extends ExpandableEventMessage {

protected updateDisplay(): void {
this.clear();
this.addChild(new Text(theme.fg("refinementHeader", "◆ Context compacted"), 1, 0));
this.addChild(
new Clickable(new Text(theme.fg("refinementHeader", "◆ Context compacted"), 1, 0), () =>
this.toggleExpanded(),
),
);
const summary = this.message.summary.trim()
? this.message.summary
: "No summary was recorded for this compaction.";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ export class CustomEditor extends Editor {
this.actionHandlers.set(action, handler);
}

protected override getContentLineOffset(): number {
return this.getHeaderLine?.() !== undefined ? 2 : 0;
}

override render(width: number): string[] {
const commandMatch = COMMAND_TOKEN_PATTERN.exec(this.getLines()[0] ?? "");
const isArgumentCommandLine = commandMatch !== null && this.isArgumentCommand(commandMatch[2]!);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { TextContent } from "@earendil-works/pi-ai";
import type { Component } from "@earendil-works/pi-tui";
import { Box, Container, Markdown, type MarkdownTheme, Spacer, Text } from "@earendil-works/pi-tui";
import { Box, Clickable, Container, Markdown, type MarkdownTheme, Spacer, Text } from "@earendil-works/pi-tui";
import type { MessageRenderer } from "../../../core/extensions/types.js";
import type { CustomMessage } from "../../../core/messages.js";
import { getMarkdownTheme, theme } from "../theme/theme.js";
Expand Down Expand Up @@ -66,7 +66,7 @@ export class CustomMessageComponent extends Container {
this.box.clear();

const label = theme.fg("customMessageLabel", `\x1b[1m[${this.message.customType}]\x1b[22m`);
this.box.addChild(new Text(label, 0, 0));
this.box.addChild(new Clickable(new Text(label, 0, 0), () => this.setExpanded(!this._expanded)));
this.box.addChild(new Spacer(1));

let text: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type Component, Container, Text, truncateToWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
import { Clickable, type Component, Container, Text, truncateToWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
import { type ThemeColor, theme } from "../theme/theme.js";

class EventSummary implements Component {
Expand Down Expand Up @@ -34,13 +34,17 @@ export abstract class ExpandableEventMessage extends Container {
this.updateDisplay();
}

toggleExpanded(): void {
this.setExpanded(!this.expanded);
}

Comment thread
cursor[bot] marked this conversation as resolved.
override invalidate(): void {
super.invalidate();
this.updateDisplay();
}

protected addSummary(summary: string, metadata?: string, color: ThemeColor = "customMessageText"): void {
this.addChild(new EventSummary(summary, this.expanded, color));
this.addChild(new Clickable(new EventSummary(summary, this.expanded, color), () => this.toggleExpanded()));
if (metadata) this.addChild(new Text(theme.fg("dim", metadata), 1, 0));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { AgentMessage } from "@earendil-works/pi-agent-core";
import {
Clickable,
Container,
Markdown,
type MarkdownTheme,
Expand Down Expand Up @@ -121,7 +122,7 @@ export class InjectedPromptMessageComponent extends Container {
return;
}
this.header.setText(this.headerText());
this.content.addChild(this.header);
this.content.addChild(new Clickable(this.header, () => this.setExpanded(!this.expanded)));
if (this.expanded && this.message.customType !== IPYTHON_STATE_RESTORED_CUSTOM_TYPE) {
this.content.addChild(
new Markdown(readCustomText(this.message), 1, 0, this.markdownTheme, {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type Component, Spacer, Text } from "@earendil-works/pi-tui";
import { Clickable, type Component, Spacer, Text } from "@earendil-works/pi-tui";
import type { RefinementOutcomeMessage } from "../../../core/messages.js";
import type { AppliedRefinementEdit, HarnessEntry } from "../../../core/refinement/refinement.js";
import { generateDiffString } from "../../../core/tools/edit-diff.js";
Expand Down Expand Up @@ -195,7 +195,9 @@ export class RefinementOutcomeMessageComponent extends ExpandableEventMessage {
this.addChild(new Spacer(1));
const outcome = refinementHeader(this.message);
const header = outcome.startsWith("Harness refined ·") ? "Harness refined" : outcome;
this.addChild(new Text(theme.fg("refinementHeader", `◆ ${header}`), 1, 0));
this.addChild(
new Clickable(new Text(theme.fg("refinementHeader", `◆ ${header}`), 1, 0), () => this.toggleExpanded()),
);
this.addSummary(
summary.trim() || "No summary was recorded for this harness change.",
undefined,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Component } from "@earendil-works/pi-tui";
import type { ClickRegion, Component } from "@earendil-works/pi-tui";
import { Text, truncateToWidth } from "@earendil-works/pi-tui";
import {
ASYNC_BASH_COMPLETION_CUSTOM_TYPE,
Expand Down Expand Up @@ -112,6 +112,7 @@ export function shellCompletionLabel(completion: ShellCompletion | undefined): s

export class ShellCompletionComponent implements Component {
private expanded = false;
private clickRegions: ClickRegion[] = [];
constructor(
private readonly message: CustomMessage,
private attached = false,
Expand All @@ -127,8 +128,14 @@ export class ShellCompletionComponent implements Component {
return !this.attached || this.expanded;
}
invalidate(): void {}
getClickRegions(): ReadonlyArray<ClickRegion> {
return this.clickRegions;
}
render(width: number): string[] {
if (this.attached && !this.expanded) return [];
if (this.attached && !this.expanded) {
this.clickRegions = [];
return [];
}
const completion = readShellCompletion(this.message);
const color = completion?.details.exitCode ? "error" : "muted";
const label = shellCompletionLabel(completion);
Expand All @@ -137,6 +144,9 @@ export class ShellCompletionComponent implements Component {
: `${completion?.details.exitCode ? "✗" : "✓"} ${label}`;
const header = truncateToWidth(theme.fg(color, ` ${heading}`), width, "");
const leadingSpace = this.options.shouldAddLeadingSpace?.(this.expanded) ?? this.expanded;
this.clickRegions = [
{ line: leadingSpace ? 1 : 0, col: 0, width, height: 1, onClick: () => this.setExpanded(!this.expanded) },
];
if (!this.expanded) return leadingSpace ? ["", header] : [header];
const raw = completion
? shellCompletionText(completion)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Box, type Component, Markdown, Text, visibleWidth } from "@earendil-works/pi-tui";
import { Box, type ClickRegion, type Component, Markdown, Text, visibleWidth } from "@earendil-works/pi-tui";
import type { AgentConnectionSideQuestionEvent } from "../../agent-connection/types.js";
import { getMarkdownTheme, theme } from "../theme/theme.js";

Expand All @@ -21,6 +21,7 @@ export class SideQuestionComponent implements Component {
private readonly paddingX: number;
private readonly entries: (SideQuestionTurnState | SideQuestionBashState)[] = [];
private expanded = false;
private clickRegions: ClickRegion[] = [];

constructor(event: AgentConnectionSideQuestionEvent, paddingX = 2) {
this.paddingX = Math.max(2, paddingX);
Expand Down Expand Up @@ -89,9 +90,14 @@ export class SideQuestionComponent implements Component {
}
}

getClickRegions(): ReadonlyArray<ClickRegion> {
return this.clickRegions;
}

render(width: number): string[] {
const blank = " ".repeat(Math.max(1, width));
const lines: string[] = [];
const clickRegions: ClickRegion[] = [];
const pushSurfaced = (raw: string[]) => {
for (const line of raw) {
lines.push(this.applySurface(line, width));
Expand All @@ -101,7 +107,11 @@ export class SideQuestionComponent implements Component {
pushSurfaced([blank]);
for (const entry of this.entries) {
if (entry.kind === "bash") {
const lineOffset = lines.length;
pushSurfaced([...entry.component.render(width), blank]);
for (const region of entry.component.getClickRegions?.() ?? []) {
clickRegions.push({ ...region, line: region.line + lineOffset });
}
continue;
}
if (entry.questionBubble) {
Expand All @@ -118,6 +128,7 @@ export class SideQuestionComponent implements Component {
pushSurfaced([blank, ...this.renderAnswer(entry, width), blank]);
}
pushSurfaced([...this.renderHint(width), blank]);
this.clickRegions = clickRegions;
return lines;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Markdown, type MarkdownTheme, Text } from "@earendil-works/pi-tui";
import { Clickable, Markdown, type MarkdownTheme, Text } from "@earendil-works/pi-tui";
import type { ParsedSkillBlock } from "../../../core/skill-blocks.js";
import { getMarkdownTheme, theme } from "../theme/theme.js";
import { customMessageLabel, ExpandableCustomMessageBox } from "./expandable-custom-message.js";
Expand All @@ -16,9 +16,10 @@ export class SkillInvocationMessageComponent extends ExpandableCustomMessageBox

protected updateDisplay(): void {
this.clear();
const toggle = () => this.setExpanded(!this.expanded);

if (this.expanded) {
this.addChild(new Text(customMessageLabel("skill"), 0, 0));
this.addChild(new Clickable(new Text(customMessageLabel("skill"), 0, 0), toggle));
const header = `**${this.skillBlock.name}**\n\n`;
this.addChild(
new Markdown(header + this.skillBlock.content, 0, 0, this.markdownTheme, {
Expand All @@ -30,7 +31,7 @@ export class SkillInvocationMessageComponent extends ExpandableCustomMessageBox
`${customMessageLabel("skill")} ` +
theme.fg("customMessageText", this.skillBlock.name) +
` ${expandCollapseHint("app.tools.expand", false)}`;
this.addChild(new Text(line, 0, 0));
this.addChild(new Clickable(new Text(line, 0, 0), toggle));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@ export class ToolExecutionComponent extends Container {

override render(width: number): string[] {
if (this.hideComponent) {
this.clickRegions = [];
return [];
}
// Refresh the animated glyph without rebuilding the whole panel, for as long
Expand All @@ -376,9 +377,19 @@ export class ToolExecutionComponent extends Container {
this.contentPanel.setHeader(this.panelHeader());
}
const lines = super.render(width);
return this.expanded && this.shouldUseIpythonRenderer() && this.shouldAddLeadingSpace?.()
? ["", ...lines]
: lines;
// The header row toggles only this component: panel header line for the
// default shell, the fixed summary line for self-rendered ipython cells.
// That ipython shell prepends a blank row, so aggregated child regions
// shift with it.
const leadingBlank = this.expanded && this.shouldUseIpythonRenderer() && this.shouldAddLeadingSpace?.() ? 1 : 0;
this.clickRegions =
lines.length > 0
? [
...this.clickRegions.map((region) => ({ ...region, line: region.line + leadingBlank })),
{ line: leadingBlank, col: 0, width, height: 1, onClick: () => this.setExpanded(!this.expanded) },
]
: [];
return leadingBlank ? ["", ...lines] : lines;
}

private isStatusAnimating(): boolean {
Expand Down
Loading
Loading