refactor(coding-agent): standardized ui dialogs to use overlay panel base

- Introduce OverlayPanel and PanelDivider classes to standardize rounded-box inline overlays and section rules.
- Migrate various UI dialog, selector, and panel components to inherit from OverlayPanel instead of Container.
- Remove manual DynamicBorder elements and custom title/header components across migrated components.
- Update test helpers and assertions to match the new overlay panel structure and rendering methods.
This commit is contained in:
can1357
2026-08-20 04:44:39 +02:00
parent 072f1a8034
commit 2d56a37334
32 changed files with 395 additions and 409 deletions
+20 -21
View File
@@ -348,7 +348,7 @@ describe("HookEditorComponent prompt-style mode", () => {
expect(onCancel).not.toHaveBeenCalled();
});
it("renders prompt-style editor with legacy ask chrome", () => {
it("renders prompt-style editor with rounded overlay chrome", () => {
const component = new HookEditorComponent(createTui(), "Prompt", undefined, vi.fn(), vi.fn(), {
promptStyle: true,
});
@@ -356,9 +356,9 @@ describe("HookEditorComponent prompt-style mode", () => {
const rendered = renderText(component);
const lines = renderLines(component);
expect(lines[0]).toMatch(/^─+$/);
expect(lines.at(-1)).toMatch(/^─+$/);
expect(lines[4]?.startsWith("> ")).toBe(true);
expect(lines[0]).toMatch(/^╭─ Prompt .*╮$/);
expect(lines.at(-1)).toMatch(/^╰.*╯$/);
expect(lines.some(line => line.includes("> "))).toBe(true);
expect(rendered).toContain("enter or ctrl+q submit esc cancel");
expect(rendered).not.toContain("shift+enter newline");
expect(rendered).toContain("ctrl+g external editor");
@@ -386,8 +386,9 @@ describe("HookEditorComponent prompt-style mode", () => {
}
const lines = renderLines(component);
expect(lines[4]?.startsWith("> hello")).toBe(true);
expect(lines[4]?.startsWith("hello")).toBe(false);
expect(lines.some(line => line.includes("> hello"))).toBe(true);
expect(lines.some(line => line.includes("hello") && !line.includes(">"))).toBe(false);
});
it("aligns wrapped prompt-style continuation rows under the text column", () => {
@@ -396,9 +397,11 @@ describe("HookEditorComponent prompt-style mode", () => {
});
const lines = renderLines(component, 12);
expect(lines[4]).toBe("> abcdefghij");
expect(lines[5]?.startsWith(" klm")).toBe(true);
expect(lines[5]?.startsWith(">")).toBe(false);
expect(lines.some(line => line.includes("> abcdef"))).toBe(true);
const continuation = lines.find(line => line.includes("ghijkl"));
expect(continuation).toBeDefined();
expect(continuation).not.toContain(">");
});
it("cancels on Escape", () => {
@@ -432,24 +435,20 @@ describe("HookEditorComponent prompt-style mode", () => {
expect(onSubmit).not.toHaveBeenCalled();
});
it("aligns the title and hint with the editor prompt gutter at column zero (#5313)", () => {
it("renders the title in the border, detail lines, hint, and prompt gutter", () => {
const title = "◆ Other (type your own)\nEnter your response:";
const component = new HookEditorComponent(createTui(), title, "不太清楚,", vi.fn(), vi.fn(), {
promptStyle: true,
});
const lines = renderLines(component);
const titleRow = lines.find(line => line.includes("Enter your response:"));
const gutterRow = lines.find(line => line.startsWith("> "));
const hintRow = lines.find(line => line.includes("esc cancel"));
expect(titleRow).toBeDefined();
expect(gutterRow).toBeDefined();
expect(hintRow).toBeDefined();
// The borderless prompt-style editor renders `> ` starting at column 0, so
// the surrounding title/hint chrome must not carry a leading indent.
expect(titleRow!.startsWith("Enter your response:")).toBe(true);
expect(hintRow!.startsWith(" ")).toBe(false);
// First title line insets into the top border row.
expect(lines[0]).toContain("Other (type your own)");
// Remaining title lines, gutter, and hint are body rows.
const content = component.renderContent(80).map(line => Bun.stripANSI(line));
expect(content.some(line => line.startsWith("Enter your response:"))).toBe(true);
expect(content.some(line => line.startsWith("> "))).toBe(true);
expect(content.some(line => line.includes("esc cancel"))).toBe(true);
});
});
@@ -32,6 +32,19 @@ describe("HookSelectorComponent", () => {
}
});
it("renders a multiline title in the border and body without dropping detail", () => {
const component = new HookSelectorComponent(
"Delete session?\nsession-2026-08-20",
["Delete", "Cancel"],
() => {},
() => {},
);
const lines = component.render(80).map(line => Bun.stripANSI(line));
expect(lines[0]).toContain("Delete session?");
expect(lines.some(line => line.includes("session-2026-08-20"))).toBe(true);
});
it("wraps outlined option text without omitting the tail", () => {
const options = [
"Option A: Move to OMP-native only by migrating reusable shared AI instructions into .omp/AGENTS.md, .omp/rules, .omp/skills, and .omp/agents while deliberately not creating a root .github directory.",
@@ -32,7 +32,7 @@ describe("LoginDialogComponent", () => {
dialog.showAuth(authorizationUrl);
const linkTarget = `${authorizationUrl}\x07`;
const urlRows = dialog
.render(40)
.renderContent(40)
.filter(line => line.includes(linkTarget) && !Bun.stripANSI(line).includes("click to open"));
expect(urlRows.length).toBeGreaterThan(1);
@@ -34,7 +34,7 @@ function renderStripped(tree: SessionTreeNode[], leafId: string, width = 120): s
() => {},
() => {},
);
return selector.render(width).map(line => Bun.stripANSI(line));
return selector.renderContent(width).map(line => Bun.stripANSI(line));
}
describe("issue #7332: linear branch continuations stay compact", () => {
@@ -44,7 +44,7 @@ function renderStripped(tree: SessionTreeNode[], leafId: string, width = 120): s
() => {},
() => {},
);
return selector.render(width).map(line => Bun.stripANSI(line));
return selector.renderContent(width).map(line => Bun.stripANSI(line));
}
// A terminal branch whose linear chain branches again must keep every row at
@@ -39,7 +39,7 @@ function renderSelector(tree: SessionTreeNode, leafId: string, width: number): s
() => {},
() => {},
);
return selector.render(width).map(line => Bun.stripANSI(line));
return selector.renderContent(width).map(line => Bun.stripANSI(line));
}
describe("TreeSelectorComponent deep branching overflow", () => {