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
11 changes: 11 additions & 0 deletions extensions/btw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1083,6 +1083,17 @@ class BtwOverlayComponent extends Container implements Focusable {

const originalHandleInput = this.input.handleInput.bind(this.input);
this.input.handleInput = (data: string) => {
if (keybindings.matches(data, "app.clear")) {
if (this.input.getValue().length > 0) {
this.input.setValue("");
this.tui.requestRender();
return;
}

this.onDismissCallback();
return;
}

if (keybindings.matches(data, "tui.select.cancel")) {
this.onDismissCallback();
return;
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "pi-btw",
"version": "0.3.7",
"version": "0.3.8",
"description": "A pi extension for parallel side conversations with /btw",
"type": "module",
"license": "MIT",
Expand Down
51 changes: 50 additions & 1 deletion tests/btw.runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,7 @@ function createHarness(
italic: (text: string) => string;
bold: (text: string) => string;
};
keybindingMatches?: (data: string, id: string) => boolean;
} = {},
) {
const commands = new Map<string, RegisteredCommand>();
Expand All @@ -473,7 +474,7 @@ function createHarness(
bold: (text: string) => text,
};
const keybindings = {
matches: (_data: string, _id: string) => false,
matches: options.keybindingMatches ?? ((_data: string, _id: string) => false),
};

const sessionManager = {
Expand Down Expand Up @@ -975,6 +976,54 @@ describe("btw runtime behavior", () => {
expect(record.session.prompt).not.toHaveBeenCalled();
});

it("clears a non-empty BTW composer on app.clear without dismissing the overlay", async () => {
const harness = createHarness([], {
keybindingMatches: (_data, id) => id === "app.clear" || id === "tui.select.cancel",
});

await harness.runSessionStart();
await harness.command("btw", "");

const overlay = harness.latestOverlayComponent();
overlay.input.setValue("draft follow-up");
overlay.input.handleInput("\x03");

expect(overlay.input.getValue()).toBe("");
expect(harness.overlayHandles.at(-1)?.hideCalls).toBe(0);
});

it("dismisses the BTW overlay on app.clear when the composer is empty", async () => {
const harness = createHarness([], {
keybindingMatches: (_data, id) => id === "app.clear" || id === "tui.select.cancel",
});

await harness.runSessionStart();
await harness.command("btw", "");

const overlay = harness.latestOverlayComponent();
overlay.input.setValue("");
overlay.input.handleInput("\x03");
await flushAsyncWork();

expect(harness.overlayHandles.at(-1)?.hideCalls).toBe(1);
});

it("still dismisses the BTW overlay on select cancel", async () => {
const harness = createHarness([], {
keybindingMatches: (_data, id) => id === "tui.select.cancel",
});

await harness.runSessionStart();
await harness.command("btw", "");

const overlay = harness.latestOverlayComponent();
overlay.input.setValue("draft follow-up");
overlay.input.handleInput("\x1b");
await flushAsyncWork();

expect(harness.overlayHandles.at(-1)?.hideCalls).toBe(1);
});

it("aborts, disposes, and unsubscribes the active BTW sub-session when Escape dismisses mid-stream", async () => {
const harness = createHarness();
const blocking = createBlockingToolStream();
Expand Down
Loading