From b17a4ff5711c1d7607ffbc06b7f4f13ff3373afe Mon Sep 17 00:00:00 2001 From: Dirk <0668000837@xydigit.com> Date: Fri, 19 Jun 2026 02:04:50 +0000 Subject: [PATCH] test(note): add rendered-output regression test for copy-sensitive token preservation Add a focused test in table.test.ts that verifies clack's note() rendering does not re-break copy-sensitive tokens (session lock paths) after wrapNoteMessage preserves them. Uses resolveNoteOutputColumns to widen the virtual output stream, matching the fix in note.ts. Refs #94730 --- packages/terminal-core/src/note.ts | 17 ++++++++------ packages/terminal-core/src/table.test.ts | 30 +++++++++++++++++++++++- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/packages/terminal-core/src/note.ts b/packages/terminal-core/src/note.ts index f2d0496361f..c5e7b2f635b 100644 --- a/packages/terminal-core/src/note.ts +++ b/packages/terminal-core/src/note.ts @@ -186,6 +186,13 @@ export function resolveNoteColumns(columns: number | undefined): number { return columns; } +export function resolveNoteOutputColumns(message: string, columns: number): number { + const widestLine = message + .split("\n") + .reduce((max, line) => Math.max(max, visibleWidth(line)), 0); + return Math.max(columns, widestLine + 6); +} + function createNoteOutput(columns: number): NodeJS.WriteStream { if (process.stdout.columns === columns) { return process.stdout; @@ -207,13 +214,9 @@ export function note(message: unknown, title?: string) { return; } const columns = resolveNoteColumns(process.stdout.columns); - const wrapped = wrapNoteMessage(message, { columns }); - // Use a wide virtual stream so clack's internal wrap (which runs after our - // format callback) does not re-break copy-sensitive tokens that - // wrapNoteMessage intentionally kept intact. - const wideOutput = createNoteOutput(Math.max(columns, visibleWidth(wrapped) + 12)); - clackNote(wrapped, stylePromptTitle(title), { - output: wideOutput, + const wrappedMessage = wrapNoteMessage(message, { columns }); + clackNote(wrappedMessage, stylePromptTitle(title), { + output: createNoteOutput(resolveNoteOutputColumns(wrappedMessage, columns)), format: (line) => line, }); } diff --git a/packages/terminal-core/src/table.test.ts b/packages/terminal-core/src/table.test.ts index cab80bfff02..7dcd4e8a733 100644 --- a/packages/terminal-core/src/table.test.ts +++ b/packages/terminal-core/src/table.test.ts @@ -1,7 +1,8 @@ +import { note as clackNote } from "@clack/prompts"; // Terminal Core tests cover table behavior. import { afterEach, describe, expect, it, vi } from "vitest"; import { visibleWidth } from "./ansi.js"; -import { resolveNoteColumns, wrapNoteMessage } from "./note.js"; +import { resolveNoteColumns, resolveNoteOutputColumns, wrapNoteMessage } from "./note.js"; import { renderTable } from "./table.js"; function mockProcessPlatform(platform: NodeJS.Platform): void { @@ -348,6 +349,33 @@ describe("wrapNoteMessage", () => { expect(resolveNoteColumns(120)).toBe(120); }); + it("widens note output columns so clack does not re-wrap copy-sensitive lines", () => { + const wrapped = wrapNoteMessage( + [ + "- Found 1 session lock file.", + "- ~/.openclaw/agents/main/sessions/9c2acae5-841f-4aea-936b-fdb513b60202.jsonl.lock pid=86519 (alive) age=2m47s stale=no", + ].join("\n"), + { columns: 80 }, + ); + const writes: string[] = []; + const output = { + columns: resolveNoteOutputColumns(wrapped, 80), + write(chunk: string) { + writes.push(chunk); + return true; + }, + } as unknown as NodeJS.WriteStream; + + clackNote(wrapped, "Session locks", { output, format: (line) => line }); + + const rendered = writes.join(""); + expect(rendered).toContain(".jsonl.lock"); + expect(rendered).not.toContain(".js\n"); + expect(rendered).toContain( + "- ~/.openclaw/agents/main/sessions/9c2acae5-841f-4aea-936b-fdb513b60202.jsonl.lock", + ); + }); + it("coerces nullish and non-string note messages before wrapping", () => { expect(wrapNoteMessage(undefined, { maxWidth: 20, columns: 80 })).toBe(""); expect(wrapNoteMessage(null, { maxWidth: 20, columns: 80 })).toBe("");