fix(tui): multi-select ask dialog submits on Enter instead of dead-ending
Enter in a multi-select ask question toggled the focused option exactly like Space, with the only submit path hidden behind the Submit tab that appears only for multi/multi-question dialogs. Enter now submits the current selection (matching single-select), Space still toggles, and the footer hint reflects it. Fixes #8252
This commit is contained in:
@@ -31,6 +31,7 @@
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed the ask dialog's multi-select mode dead-ending on Enter: Space toggles options and Enter submits the current selection (matching single-select), instead of Enter silently toggling the focused option with the only submit path hidden behind the Submit tab ([#8252](https://github.com/can1357/oh-my-pi/issues/8252)).
|
||||
- Fixed `/usage`, `/advisor status`, and every other panel command answering only after the agent stopped working. Since `17.0.1` their output was queued until the turn settled (to stop mid-turn transcript mounts duplicating rows in native scrollback, issues #4806/#6767), and the deferral was silent, so on a long turn the command was indistinguishable from a dead one. The panel now renders immediately above the editor in an anchored container that is cleared and rebuilt in place, never entering the transcript, and the full output still lands in the transcript at the next settle. The preview is capped to 40% of the viewport (minimum 6 rows) so a tall report cannot push the prompt off screen.
|
||||
- Fixed the todo panel showing no progress while the agent worked through a plan: every sub-todo read as unchecked no matter how far along the run was. Three causes, all in the collapsed (default) view — the walking viewport dropped *every* closed row, so a completion only ever removed a line and the card's strike-reveal animation ran against a row nobody rendered; the phase the agent was actually in was the one phase header rendered without a `done/total` count; and the 60s todo auto-clear deleted closed tasks from an unfinished plan, resetting the phase counter to `0/n` and renumbering the stages until the next `todo` call restored the real snapshot. The viewport now keeps the newest closed task as a checked lead row (additive to the open-task cap), every phase header carries its progress, counts include abandoned tasks, and auto-clear only fires once the whole list is settled.
|
||||
- Status-line `usage` now renders monthly Cursor quotas (`mo N%`) in addition to the existing `5h` / `7d` windows ([#7998](https://github.com/can1357/oh-my-pi/pull/7998) by [@dnth](https://github.com/dnth)).
|
||||
|
||||
@@ -602,7 +602,7 @@ export class AskDialogComponent implements Component {
|
||||
return `Enter submit · ↑/↓ scroll ·${scroll} ${cancel}`;
|
||||
}
|
||||
const question = this.#questions[this.#currentQuestionIndex()];
|
||||
const action = question?.multi ? "Space/Enter toggle · n note" : "Enter select · n note";
|
||||
const action = question?.multi ? "Space toggle · Enter submit" : "Enter select · n note";
|
||||
const tabs = this.#hasSubmitTab() ? " · Tab/←/→" : "";
|
||||
if (this.#questionCanPage && indicator) {
|
||||
return `${action} · ↑/↓${tabs} · ${cancel} · ${pageKeysLabel()} ${indicator}`;
|
||||
@@ -680,8 +680,14 @@ export class AskDialogComponent implements Component {
|
||||
const option = question.options[rowItem.optionIndex ?? -1];
|
||||
if (!option) return;
|
||||
if (question.multi) {
|
||||
// Multi is toggle-only: Enter and Space both toggle, and the
|
||||
// answer is confirmed from the Submit tab.
|
||||
if (isEnter) {
|
||||
// Enter confirms the current selection without toggling the
|
||||
// focused option; Space toggles. Matches single-select
|
||||
// Enter-to-submit so the multi dialog never dead-ends on an
|
||||
// undiscoverable Submit tab (#8252).
|
||||
this.#finishSubmit();
|
||||
return;
|
||||
}
|
||||
if (state.selectedOptions.has(option.label)) {
|
||||
state.selectedOptions.delete(option.label);
|
||||
clearNoteIfRow(state, rowItem.key);
|
||||
|
||||
@@ -187,7 +187,7 @@ describe("AskDialogComponent", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("multi-select: Space and Enter both toggle without advancing; Submit tab confirms", () => {
|
||||
it("multi-select: Space toggles options; Enter submits the current selection", () => {
|
||||
const onSubmit = vi.fn();
|
||||
const onCancel = vi.fn();
|
||||
const onPrompt = vi.fn();
|
||||
@@ -207,22 +207,24 @@ describe("AskDialogComponent", () => {
|
||||
onPrompt,
|
||||
});
|
||||
|
||||
// Space on Option A - toggles without advancing
|
||||
// Space toggles without submitting.
|
||||
component.handleInput(SPACE);
|
||||
expect(onSubmit).not.toHaveBeenCalled();
|
||||
|
||||
// Down to Option B, Enter - toggles B, still no submit and no movement
|
||||
component.handleInput(DOWN);
|
||||
component.handleInput(ENTER);
|
||||
// Space again toggles the same option back off.
|
||||
component.handleInput(SPACE);
|
||||
expect(onSubmit).not.toHaveBeenCalled();
|
||||
|
||||
// Tab to the Submit tab (present even for a single multi question),
|
||||
// Enter confirms the selection.
|
||||
component.handleInput(TAB);
|
||||
// Space once more re-selects it.
|
||||
component.handleInput(SPACE);
|
||||
expect(onSubmit).not.toHaveBeenCalled();
|
||||
|
||||
// Enter submits the current selection without toggling the focused
|
||||
// option (issue #8252).
|
||||
component.handleInput(ENTER);
|
||||
|
||||
expect(onSubmit).toHaveBeenCalledTimes(1);
|
||||
expect(onSubmit.mock.calls[0][0].results[0].selectedOptions).toEqual(["Option A", "Option B"]);
|
||||
expect(onSubmit.mock.calls[0][0].results[0].selectedOptions).toEqual(["Option A"]);
|
||||
});
|
||||
|
||||
it("tab-state persistence: answer question 0, Tab forward, Tab back, answer still present", () => {
|
||||
@@ -933,7 +935,7 @@ describe("AskDialogComponent", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("single-question multi-select: Enter toggles instead of submitting", () => {
|
||||
it("single-question multi-select: Enter submits the current selection immediately", () => {
|
||||
const onSubmit = vi.fn();
|
||||
const questions: ExtensionAskDialogQuestion[] = [
|
||||
{
|
||||
@@ -950,42 +952,34 @@ describe("AskDialogComponent", () => {
|
||||
onPrompt: vi.fn(),
|
||||
});
|
||||
|
||||
// Enter on Option B toggles it — no submit, no tab movement.
|
||||
component.handleInput(DOWN);
|
||||
component.handleInput(ENTER);
|
||||
expect(onSubmit).not.toHaveBeenCalled();
|
||||
|
||||
// The toggle registered: Submit tab confirms only Option B.
|
||||
component.handleInput(TAB);
|
||||
component.handleInput(ENTER);
|
||||
expect(onSubmit).toHaveBeenCalledTimes(1);
|
||||
expect(onSubmit.mock.calls[0][0].results[0].selectedOptions).toEqual(["Option B"]);
|
||||
});
|
||||
|
||||
it("multi-select: Enter on a checked option toggles it off; empty answer submits from Submit tab", () => {
|
||||
const onSubmit = vi.fn();
|
||||
const questions: ExtensionAskDialogQuestion[] = [
|
||||
{
|
||||
id: "q1",
|
||||
question: "Choose multiple?",
|
||||
options: [{ label: "Option A" }, { label: "Option B" }],
|
||||
multi: true,
|
||||
},
|
||||
];
|
||||
|
||||
const component = new AskDialogComponent(questions, {
|
||||
onSubmit,
|
||||
onCancel: vi.fn(),
|
||||
onPrompt: vi.fn(),
|
||||
});
|
||||
|
||||
// Space checks Option A, Enter on the same row unchecks it.
|
||||
// Space selects Option A; Enter submits right away — no need to
|
||||
// discover the Submit tab (issue #8252).
|
||||
component.handleInput(SPACE);
|
||||
component.handleInput(ENTER);
|
||||
|
||||
// Submit tab warns about the unanswered question but still submits.
|
||||
component.handleInput(TAB);
|
||||
expect(render(component).toLowerCase()).toContain("unanswered");
|
||||
expect(onSubmit).toHaveBeenCalledTimes(1);
|
||||
expect(onSubmit.mock.calls[0][0].results[0].selectedOptions).toEqual(["Option A"]);
|
||||
});
|
||||
|
||||
it("multi-select: Enter submits an empty selection instead of dead-ending", () => {
|
||||
const onSubmit = vi.fn();
|
||||
const questions: ExtensionAskDialogQuestion[] = [
|
||||
{
|
||||
id: "q1",
|
||||
question: "Choose multiple?",
|
||||
options: [{ label: "Option A" }, { label: "Option B" }],
|
||||
multi: true,
|
||||
},
|
||||
];
|
||||
|
||||
const component = new AskDialogComponent(questions, {
|
||||
onSubmit,
|
||||
onCancel: vi.fn(),
|
||||
onPrompt: vi.fn(),
|
||||
});
|
||||
|
||||
// Enter with nothing selected submits the empty selection rather than
|
||||
// toggling or blocking on the Submit tab.
|
||||
component.handleInput(ENTER);
|
||||
|
||||
expect(onSubmit).toHaveBeenCalledTimes(1);
|
||||
@@ -1385,3 +1379,4 @@ describe("AskDialogComponent", () => {
|
||||
expect(result.selectedOptions).toEqual(["Option A"]);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user