Public: anyone can read this; the crew can change it.

Sentinel: shared:01m23vygyffx5hg2f5vm8r74sr:11t6cevjos is past its budget: trevato/forge: "egress grants in the terminal > a non-admin is not offered it, and the key does nothing" failed on 16 p… #8120

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/8094-sentinel-shared-01m23vygyffx5hg2f5vm8r74 → main 3 commits fork

  • Merged. scout/8094-sentinel-shared-01m23vygyffx5hg2f5vm8r74 was merged into main as fd794a7.

Review

Sign in to review this change.
4 files changed +71 −2
src/tui/app/app.ts +13
@@ -338,6 +338,15 @@ export class App {
338338 return !this.dirty && !this.busy;
339339 }
340340
341 /**
342 * No palette search waiting on its debounce or in flight. `settled` does
343 * not count it, so typing in the palette stays snappy under a harness; a
344 * test that acts on the results waits on this, not on a sleep.
345 */
346 get paletteSettled(): boolean {
347 return this.paletteTimers.length === 0;
348 }
349
341350 /** Frames actually painted. A harness watches it stop changing. */
342351 get frameCount(): number {
343352 return this.frames;
@@ -845,6 +854,10 @@ export class App {
845854 })
846855 .catch(() => {
847856 if (!abort.signal.aborted) this.palette.searching = false;
857 })
858 .finally(() => {
859 // The live run is the last; once it lands this query has nothing left to do.
860 if (live && this.paletteAbort === abort) this.paletteTimers = [];
848861 });
849862 };
850863 this.paletteTimers = [
test/egress-admin.test.ts +4 −2
@@ -17,7 +17,7 @@ import { startEgress } from "../src/apps/egress.ts";
1717 import { createForgeApp } from "../src/tui/forge.ts";
1818 import { headlessTerminal } from "../src/tui/term/terminal.ts";
1919 import { quietLogs } from "./helpers/globals.ts";
20 import { settleApp, settleUntil } from "./helpers/tui.ts";
20 import { settleApp, settlePalette, settleUntil } from "./helpers/tui.ts";
2121 import { seedAdmin } from "./helpers/admin.ts";
2222
2323 quietLogs();
@@ -76,7 +76,9 @@ describe("egress grants in the terminal", () => {
7676 await settleApp(tui);
7777 await press("/");
7878 for (const ch of "arm") await press(ch);
79 // The palette searches on a debounce no settle can see; enter on an empty list opens nothing.
79 // The palette searches twice, at 120ms and live at 400ms, and redraws the list each time;
80 // enter before the second can open a row the redraw moves.
81 await settlePalette(tui);
8082 await settleUntil(tui, "ada/arm");
8183 await press("enter", "enter");
8284 // "ada/arm" is on screen from the palette and the dashboard before the repository opens; its one-line file is the sign it has.
test/helpers/tui.ts +22
@@ -102,3 +102,25 @@ export async function settleUntil(
102102 await turn();
103103 }
104104 }
105
106 /**
107 * Settle once the palette's searches have all landed — the 120ms local one
108 * and the 400ms live re-run that redraws the same list. Enter pressed before
109 * the second can open a row the redraw then moves.
110 */
111 export async function settlePalette(
112 app: App,
113 options: SettleOptions = {},
114 ): Promise<void> {
115 const timeoutMs = options.timeoutMs ?? 20_000;
116 const deadline = Date.now() + timeoutMs;
117 while (!app.paletteSettled) {
118 if (Date.now() > deadline) {
119 throw new Error(
120 `the palette's search never landed in ${timeoutMs}ms:\n${app.screen.toText()}`,
121 );
122 }
123 await turn();
124 }
125 await settleApp(app, { timeoutMs: Math.max(1_000, deadline - Date.now()) });
126 }
test/tui-app.test.ts +32
@@ -140,3 +140,35 @@ describe("the app loop", () => {
140140 app.stop();
141141 });
142142 });
143
144 describe("the palette's search", () => {
145 test("is not settled until its live re-run has landed", async () => {
146 const asked: boolean[] = [];
147 const app = new App({
148 terminal: headlessTerminal(80, 24),
149 theme: "dark",
150 root: () => ({ title: "Root", keys: [], render() {}, key: () => false }),
151 search: () => async (_query, live) => {
152 asked.push(live);
153 return [];
154 },
155 });
156 app.start();
157 try {
158 expect(app.paletteSettled).toBe(true);
159 const key = (k: string) => app.dispatch({ type: "key", key: k, ctrl: false, alt: false, shift: false });
160 key(":");
161 key("a");
162 expect(app.paletteSettled).toBe(false);
163 await Bun.sleep(200);
164 expect(asked).toEqual([false]);
165 expect(app.paletteSettled).toBe(false);
166 const deadline = Date.now() + 5_000;
167 while (!app.paletteSettled && Date.now() < deadline) await Bun.sleep(10);
168 expect(asked).toEqual([false, true]);
169 expect(app.paletteSettled).toBe(true);
170 } finally {
171 app.stop();
172 }
173 });
174 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs