Merge branch 'perf/lag': dashboard lag fixes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	client/src/components/Sagi/PetSprite.tsx
#	client/src/components/Sagi/useSagiBrain.ts
This commit is contained in:
2026-10-14 12:26:41 +07:00
36 changed files with 676 additions and 101 deletions
@@ -24,6 +24,7 @@ import {
readRunMenu,
registerPane,
takeRunInput,
MENU_CURSOR_ROW,
} from "../panePaste";
const RULE = "─".repeat(60);
@@ -311,3 +312,17 @@ const CODEX_APPROVAL = [
// "Press enter to confirm or esc to cancel", its dim cells left out.
" enteresc",
];
describe("MENU_CURSOR_ROW", () => {
// The chat's 700 ms poll reads the screen cell by cell only when a row
// matches it, so every menu parsePaneMenu finds must have such a row.
it("matches a row of every menu, and no row of an idle prompt", () => {
const has = (rows: string[]) => rows.some((r) => MENU_CURSOR_ROW.test(r));
for (const menu of [ASK, MULTI, PERMISSION, CODEX_APPROVAL]) {
expect(parsePaneMenu(menu)).not.toBeNull();
expect(has(menu)).toBe(true);
}
expect(has(CLAUDE)).toBe(false);
expect(has(CODEX)).toBe(false);
});
});
+8 -3
View File
@@ -27,7 +27,8 @@ interface Pane {
paste: (text: string, submit?: boolean) => void;
visible: () => boolean;
key?: (data: string) => void;
screen?: () => PaneScreen | null;
/** `menuOnly`: null at once unless a row could be a menu's cursor row. */
screen?: (menuOnly?: boolean) => PaneScreen | null;
}
const panes = new Map<string, Pane>();
let lastFocused: string | null = null;
@@ -39,7 +40,7 @@ export function registerPane(
paste: (text: string, submit?: boolean) => void,
visible: () => boolean = () => true,
key?: (data: string) => void,
screen?: () => PaneScreen | null
screen?: (menuOnly?: boolean) => PaneScreen | null
): () => void {
const entry = { paste, visible, key, screen };
panes.set(runId, entry);
@@ -175,6 +176,8 @@ export type PaneMenu = {
};
const OPTION = /^ {0,4}([❯›][ \u00a0]?)? *(\d{1,2})\. +(.*)$/;
/** A row a menu's cursor sits on (a numbered option or Submit): a menu has one. */
export const MENU_CURSOR_ROW = /^ *[❯›][ \u00a0]* *(?:\d{1,2}\. |Submit\s*$)/;
const SUBMIT = /^ *([❯›])?[ \u00a0]*Submit$/;
const RULE_ROW = /^ *[─╌]{3,} *$/;
const CHECK = /^\[([ ✔])\] *(.*)$/;
@@ -251,7 +254,9 @@ function typeRow(options: PaneMenuOption[]): number {
/** The menu `runId`'s visible pane shows now, or null. */
export function readRunMenu(runId: string): PaneMenu | null {
const p = panes.get(runId);
const screen = p?.visible() && p.key ? p.screen?.() : null;
// Polled every 700 ms while a chat shows: a screen with no cursor row is
// answered from plain text, without reading it cell by cell.
const screen = p?.visible() && p.key ? p.screen?.(true) : null;
return screen ? parsePaneMenu(screen.rows) : null;
}