Skip to content

Commit 01f223a

Browse files
committed
Surface /yolo on landing and permission prompts
1 parent 163246e commit 01f223a

5 files changed

Lines changed: 72 additions & 15 deletions

File tree

docs/TUI.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,11 @@ because an earlier version could abandon the awaited promise on Escape and
282282
leave the session parked with no recovery path short of killing the process;
283283
Escape must always settle the promise it is dismissing.
284284

285+
The permissions prompt's title line names `/yolo` as the way to skip further
286+
prompts, longest first as the terminal narrows: `Esc cancel · Enter choose ·
287+
/yolo skip prompts`, then `Esc · Enter · /yolo`, then `Esc · Enter`. Operator
288+
questions and the model/provider picker do not advertise `/yolo`.
289+
285290
Once a permission or operator prompt is answered — or cancelled, timed out,
286291
or auto-settled by a grant / abort / teardown — it leaves the screen and
287292
does **not** replay the request, the command, or the chosen option into the
@@ -356,6 +361,12 @@ or transcript. The shortcut list it used to open is still reachable, as
356361
via `openCommandSurface`'s `"help"` case, `command-surfaces.ts`); the `/` row
357362
in `SHELL_SHORTCUTS` documents that in place of a dedicated `?` row.
358363

364+
The idle landing paints two doors beside the mark, keys aligned so the
365+
descriptions share a column (`LANDING_HINTS` in `src/tui/landing.ts`): `/`
366+
for commands, and `/yolo` so Corbits Code does not have to ask for
367+
permissions. An 80-column terminal still seats the compact mark next to
368+
them; when the terminal is too narrow, the hints win and the mark drops.
369+
359370
The running build version is chrome, not part of the landing composition:
360371
`shell.ts`'s `versionRow`/`versionBadge`, a dedicated row pinned to the
361372
terminal's last line and right-aligned, distinct from `landing.ts`'s hero and

src/tui/landing.test.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -105,17 +105,24 @@ describe("landing layout math", () => {
105105
expect(text.some((line) => line.includes("telemetry"))).toBe(false);
106106
});
107107

108+
test("the two doors are commands and /yolo", () => {
109+
expect(LANDING_HINTS).toEqual([
110+
{ key: "/", rest: "for commands" },
111+
{ key: "/yolo", rest: "so Corbits Code doesn't have to ask for permissions" },
112+
]);
113+
});
114+
108115
test("the mark degrades through its tiers and then disappears", () => {
109116
// Roomy: the hero grid, which is the only size that reads unambiguously.
110-
expect(resolveMarkGrid(20, 96)).toBe(MARK_LARGE);
117+
expect(resolveMarkGrid(20, 120)).toBe(MARK_LARGE);
111118
// A row short of the hero, a tier down rather than a clipped hero.
112-
expect(resolveMarkGrid(12, 96)).toBe(MARK_MID);
113-
expect(resolveMarkGrid(9, 96)).toBe(MARK_SMALL);
119+
expect(resolveMarkGrid(12, 120)).toBe(MARK_MID);
120+
expect(resolveMarkGrid(9, 120)).toBe(MARK_SMALL);
121+
// 80-column terminal: contentWidth is 78 after gutters; the compact mark
122+
// still seats beside the two doors.
123+
expect(resolveMarkGrid(20, 78)).toBe(MARK_SMALL);
114124
// Narrow enough that the mark would crowd the hints: the hints win.
115-
// (The version moved off this hint block into the shell's own chrome —
116-
// CL-5736 — so the block is narrower and a bit more room stays for the
117-
// mark at this width than before.)
118-
expect(resolveMarkGrid(20, 50)).toBe(MARK_MID);
125+
expect(resolveMarkGrid(20, 50)).toBeNull();
119126
expect(resolveMarkGrid(20, 30)).toBeNull();
120127
expect(resolveMarkGrid(3, 96)).toBeNull();
121128
});

src/tui/landing.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@
99
* ──── the prompt box and its hint row (owned by the shell)
1010
* below the telemetry disclosure, then a few selectable starter prompts
1111
*
12-
* The mark is the screen. Beside it sit exactly two lines — the command menu
13-
* and the shortcut sheet — because those two are the only doors an operator
14-
* needs on a screen where nothing has happened yet; every other key is behind
15-
* one of them, and listing keys here would trade the one legible thing on the
16-
* screen for a reference card nobody reads twice.
12+
* The mark is the screen. Beside it sit exactly two lines — `/` for commands
13+
* and `/yolo` so permission prompts are not required — because those two are
14+
* the only doors an operator needs on a screen where nothing has happened yet;
15+
* every other key is behind one of them, and listing keys here would trade the
16+
* one legible thing on the screen for a reference card nobody reads twice.
1717
*
1818
* The disclosure sits directly under the box rather than at the bottom edge
1919
* because it has to be read, not discovered.
@@ -70,13 +70,19 @@ export function versionBadgeVisible(columns: number, rows: number): boolean {
7070
}
7171

7272
/**
73-
* The one door off the landing screen — `/help` (listed among the commands
74-
* `/` opens) is the other, so this stays a single row rather than growing.
73+
* The two doors off the landing screen. `/help` is among the commands `/`
74+
* opens; `/yolo` is the other way in, so permission prompts do not have to be
75+
* discovered the hard way.
7576
*/
7677
export const LANDING_HINTS: readonly {
7778
readonly key: string;
7879
readonly rest: string;
79-
}[] = [{ key: "/", rest: "for commands" }];
80+
}[] = [
81+
{ key: "/", rest: "for commands" },
82+
// One character shorter than the spoken line ("does not") so an 80-column
83+
// terminal still seats the compact mark beside the aligned descriptions.
84+
{ key: "/yolo", rest: "so Corbits Code doesn't have to ask for permissions" },
85+
];
8086

8187
/**
8288
* Columns held for the key, so the descriptions beside them start on one

src/tui/overlays.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ describe("permissions overlay", () => {
9999
// First option is in the list model (may clip if body short).
100100
expect(shell.overlayItems[0]).toBe("Allow once");
101101
expect(frame).toMatch(/Allow/);
102+
expect(frame).toContain("/yolo");
103+
expect(frame).toContain("Esc cancel · Enter choose · /yolo skip prompts");
102104

103105
// Navigate deep enough that window must scroll (keep-active-visible).
104106
const listH = shell.overlayList!.height;
@@ -132,6 +134,27 @@ describe("permissions overlay", () => {
132134
);
133135
});
134136

137+
test("key hints drop to Esc · Enter on a narrow interior", async () => {
138+
await withTestRenderer(
139+
async (h) => {
140+
const shell = createAppShell(h.renderer, {
141+
terminal: { columns: 32, rows: 24 },
142+
wireKeys: false,
143+
});
144+
try {
145+
openPermissionsOverlay(shell, { items: makePermissionItems(4) });
146+
await h.renderOnce();
147+
const frame = h.captureCharFrame();
148+
expect(frame).toContain("Esc · Enter");
149+
expect(frame).not.toContain("/yolo");
150+
} finally {
151+
shell.dispose();
152+
}
153+
},
154+
{ width: 32, height: 24 },
155+
);
156+
});
157+
135158
test("Esc key closes permissions overlay via wireKeys", async () => {
136159
await withTestRenderer(
137160
async (h) => {
@@ -208,6 +231,7 @@ describe("operator question overlay", () => {
208231
expect(frame).toMatch(/Cancel|Allow/);
209232
// The overlay carries its own keys now that there is no hint strip.
210233
expect(frame).toContain("Esc cancel");
234+
expect(frame).not.toContain("/yolo");
211235
// Empty title must not leave a leading middle-dot before the hints.
212236
expect(frame).not.toMatch(/·\s*Esc cancel/);
213237

@@ -245,6 +269,7 @@ describe("model / provider picker", () => {
245269
let frame = h.captureCharFrame();
246270
expect(frame).toContain("model");
247271
expect(frame).toMatch(/anthropic|openai|claude/i);
272+
expect(frame).not.toContain("/yolo");
248273

249274
moveOverlaySelection(shell, 2);
250275
acceptOverlaySelection(shell);

src/tui/shell.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1337,6 +1337,13 @@ const MODEL_PICKER_HINTS = [
13371337
"Esc · Enter",
13381338
] as const;
13391339

1340+
/** Permissions only: name `/yolo` so skip-prompts is discoverable at the ask. */
1341+
const PERMISSIONS_HINTS = [
1342+
"Esc cancel · Enter choose · /yolo skip prompts",
1343+
"Esc · Enter · /yolo",
1344+
"Esc · Enter",
1345+
] as const;
1346+
13401347
function overlayHints(shell: AppShell): readonly string[] {
13411348
const answer = overlayAnswerState(shell);
13421349
const hasChoices = shell.overlayItems.length > 0;
@@ -1363,6 +1370,7 @@ function overlayHints(shell: AppShell): readonly string[] {
13631370
];
13641371
}
13651372
}
1373+
if (shell.overlayKind === "permissions") return PERMISSIONS_HINTS;
13661374
return DEFAULT_OVERLAY_HINTS;
13671375
}
13681376
if (answer.active) {

0 commit comments

Comments
 (0)