From b2d9dc280db5596ce8412ad1f5657946268806a9 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sun, 11 Oct 2026 21:27:12 -0700 Subject: [PATCH] refactor(tui): drop dead notice field, dedupe helpers --- src/tui/components/at-mention/list.ts | 40 +++++++++++++++++++-------- src/tui/copy-path.ts | 8 ++---- src/tui/markdown-parser.ts | 34 ++++++++++++++++------- src/tui/notice-line.test.ts | 1 - src/tui/notice-line.ts | 1 - src/tui/shell/chrome.ts | 1 - src/tui/tool-formatter.ts | 4 +-- 7 files changed, 56 insertions(+), 33 deletions(-) diff --git a/src/tui/components/at-mention/list.ts b/src/tui/components/at-mention/list.ts index 3dbc5a996..5bb37eb88 100644 --- a/src/tui/components/at-mention/list.ts +++ b/src/tui/components/at-mention/list.ts @@ -39,8 +39,18 @@ export async function listPathSuggestions( // prefix as filter (the `@` alone case, like `ls`). const lastSlash = rest.lastIndexOf("/"); const hasSlash = lastSlash !== -1; - const dir = endsWithSep ? rest || "." : hasSlash ? dirname(rest) : "."; - const fragment = endsWithSep ? "" : hasSlash ? basename(rest) : rest; + let dir: string; + let fragment: string; + if (endsWithSep) { + dir = rest || "."; + fragment = ""; + } else if (hasSlash) { + dir = dirname(rest); + fragment = basename(rest); + } else { + dir = "."; + fragment = rest; + } const realDir = await resolveDirectory(dir, base); if (realDir === null) return []; @@ -56,16 +66,22 @@ export async function listPathSuggestions( // Reconstruct the path the user would type: bare fragments are shown // relative to cwd (dirPrefix ""), and home-relative results keep the // `~/` form so the completion inserts text the submit path expands. - const innerPrefix = endsWithSep - ? rest - : hasSlash - ? rest.slice(0, rest.length - fragment.length) - : ""; - const dirPrefix = homeRelative - ? `~/${innerPrefix}` - : endsWithSep - ? prefix - : innerPrefix; + let innerPrefix: string; + if (endsWithSep) { + innerPrefix = rest; + } else if (hasSlash) { + innerPrefix = rest.slice(0, rest.length - fragment.length); + } else { + innerPrefix = ""; + } + let dirPrefix: string; + if (homeRelative) { + dirPrefix = `~/${innerPrefix}`; + } else if (endsWithSep) { + dirPrefix = prefix; + } else { + dirPrefix = innerPrefix; + } matched.push(dirPrefix + entry.name + (entry.isDirectory() ? "/" : "")); } diff --git a/src/tui/copy-path.ts b/src/tui/copy-path.ts index ce9d2da7a..086c3156d 100644 --- a/src/tui/copy-path.ts +++ b/src/tui/copy-path.ts @@ -6,6 +6,7 @@ import { diffPlainText } from "./diff.js"; import type { StreamRow } from "./stream.js"; +import { abbreviate } from "./tool-formatter.js"; export type CopyKind = "message" | "tool" | "diff" | "system"; @@ -72,11 +73,6 @@ export function createRecordingClipboard(): ClipboardPort & { }; } -function oneLine(text: string, max = 56): string { - const collapsed = text.replace(/\s+/g, " ").trim(); - return collapsed.length > max ? `${collapsed.slice(0, max - 1)}…` : collapsed; -} - /** * Infer copy kind from a stream row. * Rows carrying a rendered edit diff win; otherwise assistant/tool rows whose @@ -154,7 +150,7 @@ export function buildCopyTargets(log: readonly StreamRow[]): CopyTarget[] { targets.push({ id: `row-${i}`, label: copyRowLabel(row), - preview: oneLine(payload.text), + preview: abbreviate(payload.text, 56), text: payload.text, }); } diff --git a/src/tui/markdown-parser.ts b/src/tui/markdown-parser.ts index 6a88ead2c..51ac8b6c8 100644 --- a/src/tui/markdown-parser.ts +++ b/src/tui/markdown-parser.ts @@ -782,6 +782,20 @@ export interface MarkdownSplit { readonly gapRows: number; } +/** Build a split whose live tail starts at `firstContent`; one blank line + * between the frozen boundary and the live tail collapses to a single gap row. */ +function settledSplit( + frozen: string, + rest: string[], + firstContent: number, +): MarkdownSplit { + return { + frozen, + live: rest.slice(firstContent).join("\n"), + gapRows: firstContent > 0 ? 1 : 0, + }; +} + export function splitAtSettledHeading(text: string): MarkdownSplit | null { const lines = text.split("\n"); const insideFence = fencedLineMask(lines); @@ -797,11 +811,11 @@ export function splitAtSettledHeading(text: string): MarkdownSplit | null { const firstContent = rest.findIndex((line) => line.trim().length > 0); // Heading closed but nothing has started under it yet. if (firstContent === -1) return null; - return { - frozen: lines.slice(0, boundary + 1).join("\n"), - live: rest.slice(firstContent).join("\n"), - gapRows: firstContent > 0 ? 1 : 0, - }; + return settledSplit( + lines.slice(0, boundary + 1).join("\n"), + rest, + firstContent, + ); } /** @@ -870,11 +884,11 @@ export function splitAtSettledBlock(withheld: string): MarkdownSplit | null { const rest = lines.slice(boundary + 1); const firstContent = rest.findIndex((entry) => entry.trim().length > 0); if (firstContent === -1) continue; - return { - frozen: lines.slice(0, boundary + 1).join("\n"), - live: rest.slice(firstContent).join("\n"), - gapRows: firstContent > 0 ? 1 : 0, - }; + return settledSplit( + lines.slice(0, boundary + 1).join("\n"), + rest, + firstContent, + ); } return null; } diff --git a/src/tui/notice-line.test.ts b/src/tui/notice-line.test.ts index 52608db42..7267544c0 100644 --- a/src/tui/notice-line.test.ts +++ b/src/tui/notice-line.test.ts @@ -8,7 +8,6 @@ import { const state = (over: Partial = {}): NoticeState => ({ waitingOn: null, - interrupt: false, pinned: false, flash: null, attachments: 0, diff --git a/src/tui/notice-line.ts b/src/tui/notice-line.ts index b6e4841f7..cefe69d38 100644 --- a/src/tui/notice-line.ts +++ b/src/tui/notice-line.ts @@ -25,7 +25,6 @@ export interface NoticeState { * Gated by `resolveWaitingOn`; this field only controls wording. */ readonly waitingOn: string | null; - readonly interrupt: boolean; /** Transcript scrolled off the tail (non-default follow state). */ readonly pinned: boolean; /** Transient feedback (copy result, attach failure, exit arming). */ diff --git a/src/tui/shell/chrome.ts b/src/tui/shell/chrome.ts index 674d4fddc..51fe4adc5 100644 --- a/src/tui/shell/chrome.ts +++ b/src/tui/shell/chrome.ts @@ -136,7 +136,6 @@ export function noticeText(shell: AppShell): string { shell.inFlightTool, shell.lockupNowMs, ), - interrupt: shell.session.interruptFlash, pinned: !isTranscriptFollowing(shell), flash: shell.statusFlash, attachments: shell.pendingAttachments.length, diff --git a/src/tui/tool-formatter.ts b/src/tui/tool-formatter.ts index d2193a5b4..9dffc309d 100644 --- a/src/tui/tool-formatter.ts +++ b/src/tui/tool-formatter.ts @@ -147,8 +147,8 @@ export function describeToolCall( const subject = description.length > 0 ? description : prompt; const first = agentName?.[0]; const display = - first != null && agentName !== undefined - ? first.toUpperCase() + agentName.slice(1) + first != null + ? first.toUpperCase() + (agentName ?? "").slice(1) : "Worker"; // Collapsed row uses the abbreviated subject; Alt+E expands to the full text. return {