feat: add review shortcut confirmation setting

This commit is contained in:
Erik Kotsiuba
2026-04-29 02:40:29 +02:00
parent 2bf85b3f6f
commit 67891d147f
16 changed files with 385 additions and 54 deletions
+5 -5
View File
@@ -35,7 +35,7 @@ test("dirty flow state is true for saved answers or editor drafts", () => {
assert.equal(hasDirtyFlowState(noteEditing, "draft note"), true);
});
test("confirm dismiss when dirty is opt-in and respects dirty state", () => {
test("confirm dismiss when dirty respects dirty state and config", () => {
const state = applyNumberShortcut(
createInitialState({
questions: [
@@ -48,20 +48,20 @@ test("confirm dismiss when dirty is opt-in and respects dirty state", () => {
}),
1
);
const enabledConfig = {
const disabledConfig = {
...DEFAULT_ASK_CONFIG,
behaviour: {
...DEFAULT_ASK_CONFIG.behaviour,
confirmDismissWhenDirty: true,
confirmDismissWhenDirty: false,
},
};
assert.equal(
shouldConfirmDirtyDismiss({ config: DEFAULT_ASK_CONFIG, state }),
shouldConfirmDirtyDismiss({ config: disabledConfig, state }),
false
);
assert.equal(
shouldConfirmDirtyDismiss({ config: enabledConfig, state }),
shouldConfirmDirtyDismiss({ config: DEFAULT_ASK_CONFIG, state }),
true
);
});
+2 -2
View File
@@ -56,7 +56,7 @@ test("wide header keeps all tabs and framing arrows on the tab row", () => {
editor: mockEditor(),
});
assert.equal(lines[3], " ← ☐ One ☐ Two ✔ Submit →");
assert.equal(lines[3], " ← ☐ One ☐ Two ☰ Review →");
});
test("narrow tab strip keeps active middle tab visible", () => {
@@ -143,7 +143,7 @@ test("narrow tab strip keeps submit tab visible when active", () => {
editor: mockEditor(),
});
assert.equal(lines[3], " ← ✔ Submit →");
assert.equal(lines[3], " ← ☰ Review →");
});
test("tab strip avoids truncation at narrow boundary widths", () => {
+28
View File
@@ -111,6 +111,34 @@ test("submit screen stacks review above actions on narrow screens", () => {
]);
});
test("submit screen can show a review shortcut hint below the actions", () => {
const state = createInitialState({
questions: [
{
id: "q1",
label: "Color",
prompt: "Pick one color.",
options: [{ value: "blue", label: "Blue" }],
},
],
});
const lines: string[] = [];
renderSubmitScreen(
lines,
state,
plainTheme(),
UI_DIMENSIONS.submitWideMinWidth - 14,
"Press 1, 2, or 3 twice to confirm a review action."
);
assert.equal(
lines.includes(" Press 1, 2, or 3 twice to confirm a review"),
true
);
assert.equal(lines.includes(" action."), true);
});
test("submit screen shows notes only for answered questions in submit mode", () => {
let state = createInitialState({
questions: [
+35
View File
@@ -0,0 +1,35 @@
import assert from "node:assert/strict";
import test from "node:test";
import {
getReviewShortcutHint,
resolveReviewShortcutDoublePress,
} from "../src/ui/review-shortcuts.ts";
test("review shortcut double press requires the same digit twice", () => {
const first = resolveReviewShortcutDoublePress(2);
assert.equal(first.confirmed, false);
assert.equal(first.actionIndex, 1);
assert.equal(first.pendingActionIndex, 1);
const second = resolveReviewShortcutDoublePress(2, first.pendingActionIndex);
assert.equal(second.confirmed, true);
assert.equal(second.actionIndex, 1);
assert.equal(second.pendingActionIndex, undefined);
});
test("review shortcut double press switches pending action when digit changes", () => {
const first = resolveReviewShortcutDoublePress(1);
const second = resolveReviewShortcutDoublePress(3, first.pendingActionIndex);
assert.equal(second.confirmed, false);
assert.equal(second.actionIndex, 2);
assert.equal(second.pendingActionIndex, 2);
});
test("review shortcut hint reflects pending confirmation state", () => {
assert.equal(
getReviewShortcutHint(),
"Press 1, 2, or 3 twice to confirm a review action."
);
assert.equal(getReviewShortcutHint(1), "Press 2 again to Elaborate.");
});
+22 -6
View File
@@ -7,7 +7,8 @@ import { AskSettingsModal } from "../src/ui/settings-modal.ts";
const savedConfig: AskConfig = {
behaviour: {
autoSubmitWhenAnsweredWithoutNotes: false,
confirmDismissWhenDirty: false,
confirmDismissWhenDirty: true,
doublePressReviewShortcuts: true,
showFooterHints: true,
},
keymaps: {
@@ -59,12 +60,15 @@ function createModal(
test("settings modal renders compactly on phone-width screens", () => {
const modal = createModal();
const lines = modal.render(28);
const text = lines.join("\n");
assert(lines.every((line) => visibleWidth(line) <= 28));
assert(lines.join("\n").includes("@eko24ive/pi-ask"));
assert(lines.length <= 24);
assert(text.includes("@eko24ive/pi-ask"));
assert(lines[1]?.includes("Keymaps"));
assert(lines.join("\n").includes("/reload after editing."));
assert(lines.join("\n").includes("Open ask settings modal"));
assert(text.includes("Edit keymaps there, then"));
assert(text.includes("? settings"));
assert(text.includes("Esc cancel"));
assert.equal(lines[0]?.includes("├"), false);
});
@@ -77,12 +81,23 @@ test("settings modal renders behaviour tab with checkbox and dirty notice", () =
assert(text.includes("Behaviour"));
assert(text.includes("[x] Auto-submit when answered without notes"));
assert(text.includes("[ ] Confirm dismiss when dirty"));
assert(text.includes("[x] Confirm dismiss when dirty"));
assert(text.includes("[x] Double-press review shortcuts"));
assert(text.includes("[x] Show footer hints"));
assert(text.includes("Unsaved changes. Press Ctrl+S to save."));
assert(text.includes("Ctrl+S save · Esc/Ctrl+C/? twice discard"));
});
test("settings modal keeps behaviour tab compact on narrow screens", () => {
const modal = createModal();
modal.handleInput("\t");
const lines = modal.render(36);
assert(lines.every((line) => visibleWidth(line) <= 36));
assert(lines.length <= 21);
assert(lines.join("\n").includes("Show footer hints"));
});
test("settings modal keymaps tab shows invalid-keymaps session warning", () => {
const modal = new AskSettingsModal(plainTheme(), {
configPath: "/tmp/eko24ive-pi-ask.json",
@@ -135,7 +150,8 @@ test("settings modal saves and clears dirty state", async () => {
const text = modal.render(72).join("\n");
assert.equal(saved?.behaviour.autoSubmitWhenAnsweredWithoutNotes, true);
assert.equal(saved?.behaviour.confirmDismissWhenDirty, false);
assert.equal(saved?.behaviour.confirmDismissWhenDirty, true);
assert.equal(saved?.behaviour.doublePressReviewShortcuts, true);
assert.equal(saved?.behaviour.showFooterHints, true);
assert(text.includes("Saved"));
assert.equal(text.includes("Unsaved changes. Press Ctrl+S to save."), false);