TUI: waiting shimmer helper + tests

This commit is contained in:
Vignesh Natarajan
2026-01-18 13:14:58 -08:00
committed by Peter Steinberger
parent 2e99369113
commit fac66d4dda
3 changed files with 97 additions and 34 deletions

View File

@@ -0,0 +1,41 @@
import { describe, expect, it } from "vitest";
import { buildWaitingStatusMessage, pickWaitingPhrase } from "./tui-waiting.js";
const theme = {
dim: (s: string) => `<d>${s}</d>`,
bold: (s: string) => `<b>${s}</b>`,
accentSoft: (s: string) => `<a>${s}</a>`,
} as any;
describe("tui-waiting", () => {
it("pickWaitingPhrase rotates every 10 ticks", () => {
const phrases = ["a", "b", "c"];
expect(pickWaitingPhrase(0, phrases)).toBe("a");
expect(pickWaitingPhrase(9, phrases)).toBe("a");
expect(pickWaitingPhrase(10, phrases)).toBe("b");
expect(pickWaitingPhrase(20, phrases)).toBe("c");
expect(pickWaitingPhrase(30, phrases)).toBe("a");
});
it("buildWaitingStatusMessage includes shimmer markup and metadata", () => {
const msg = buildWaitingStatusMessage({
theme,
tick: 1,
elapsed: "3s",
connectionStatus: "connected",
phrases: ["hello"],
});
expect(msg).toContain("connected");
expect(msg).toContain("3s");
// text is wrapped per-char; check it appears in order
expect(msg).toContain("h");
expect(msg).toContain("e");
expect(msg).toContain("l");
expect(msg).toContain("o");
// shimmer should contain both highlighted and dim parts
expect(msg).toContain("<b><a>");
expect(msg).toContain("<d>");
});
});

47
src/tui/tui-waiting.ts Normal file
View File

@@ -0,0 +1,47 @@
import type { ClawdbotTheme } from "./theme/theme.js";
export const defaultWaitingPhrases = [
"flibbertigibbeting",
"kerfuffling",
"dillydallying",
"twiddling thumbs",
"noodling",
"bamboozling",
"moseying",
"hobnobbing",
"pondering",
"conjuring",
];
export function pickWaitingPhrase(tick: number, phrases = defaultWaitingPhrases) {
const idx = Math.floor(tick / 10) % phrases.length;
return phrases[idx] ?? phrases[0] ?? "waiting";
}
export function shimmerText(theme: ClawdbotTheme, text: string, tick: number) {
const width = 6;
const hi = (ch: string) => theme.bold(theme.accentSoft(ch));
const pos = tick % (text.length + width);
const start = Math.max(0, pos - width);
const end = Math.min(text.length - 1, pos);
let out = "";
for (let i = 0; i < text.length; i++) {
const ch = text[i];
out += i >= start && i <= end ? hi(ch) : theme.dim(ch);
}
return out;
}
export function buildWaitingStatusMessage(params: {
theme: ClawdbotTheme;
tick: number;
elapsed: string;
connectionStatus: string;
phrases?: string[];
}) {
const phrase = pickWaitingPhrase(params.tick, params.phrases);
const cute = shimmerText(params.theme, `${phrase}`, params.tick);
return `${cute}${params.elapsed} | ${params.connectionStatus}`;
}

View File

@@ -22,6 +22,7 @@ import { editorTheme, theme } from "./theme/theme.js";
import { createCommandHandlers } from "./tui-command-handlers.js";
import { createEventHandlers } from "./tui-event-handlers.js";
import { formatTokens } from "./tui-formatters.js";
import { buildWaitingStatusMessage } from "./tui-waiting.js";
import { createOverlayHandlers } from "./tui-overlays.js";
import { createSessionActions } from "./tui-session-actions.js";
import type {
@@ -286,49 +287,23 @@ export async function runTui(opts: TuiOptions) {
statusContainer.addChild(statusLoader);
};
const waitingPhrases = [
"flibbertigibbeting",
"kerfuffling",
"dillydallying",
"twiddling thumbs",
"noodling",
"bamboozling",
"moseying",
"hobnobbing",
"pondering",
"conjuring",
"vibing",
"clawding",
];
let waitingTick = 0;
let waitingTimer: NodeJS.Timeout | null = null;
const shimmerWaitingText = (text: string, tick: number) => {
const width = 6;
const hi = (ch: string) => theme.bold(theme.accentSoft(ch));
const pos = tick % (text.length + width);
const start = Math.max(0, pos - width);
const end = Math.min(text.length - 1, pos);
let out = "";
for (let i = 0; i < text.length; i++) {
const ch = text[i];
out += i >= start && i <= end ? hi(ch) : theme.dim(ch);
}
return out;
};
const updateBusyStatusMessage = () => {
if (!statusLoader || !statusStartedAt) return;
const elapsed = formatElapsed(statusStartedAt);
if (activityStatus === "waiting") {
waitingTick++;
const phrase = waitingPhrases[Math.floor(waitingTick / 10) % waitingPhrases.length];
const cute = shimmerWaitingText(`${phrase}`, waitingTick);
statusLoader.setMessage(`${cute}${elapsed} | ${connectionStatus}`);
statusLoader.setMessage(
buildWaitingStatusMessage({
theme,
tick: waitingTick,
elapsed,
connectionStatus,
}),
);
return;
}