[eric] toolui: a failed widget chunk load is forgotten and every chunk is warmed at idle; one blip no longer leaves every later table a couldn't-draw line

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
ciregenz
2026-09-06 15:55:27 -07:00
co-authored by Claude Fable 5.1
parent da56b2cd1a
commit a6f6fbedd7
4 changed files with 41 additions and 2 deletions
+1
View File
@@ -58,6 +58,7 @@ if (typeof window !== 'undefined') {
};
const prefetchAll = () => {
void import('./pages/Analytics/Analytics');
void import('@toolui/registry').then((m) => m.preloadToolUi());
};
const ric = (window as any).requestIdleCallback as
| ((cb: () => void, opts?: { timeout?: number }) => number)
+3 -2
View File
@@ -45,10 +45,11 @@ const warnedShapes = new Set<string>();
// fallback boundary lost its retry above the memoized bubble (measured 2026-09-05: chunk loaded, module resolved, skeleton
// forever), so the component arrives through state, which re-renders THIS component no matter what memo sits above it.
const loadedComponents = new Map<ToolUiEntry, Promise<React.ComponentType<any>>>();
function componentFor(entry: ToolUiEntry): Promise<React.ComponentType<any>> {
export function componentFor(entry: ToolUiEntry): Promise<React.ComponentType<any>> {
let p = loadedComponents.get(entry);
if (!p) {
p = entry.load();
// A rejection is forgotten, so the next widget of this kind fetches again instead of inheriting one blip for the rest of the page.
p = entry.load().catch((e) => { if (loadedComponents.get(entry) === p) loadedComponents.delete(entry); throw e; });
loadedComponents.set(entry, p);
}
return p;
+16
View File
@@ -133,3 +133,19 @@ export const TOOL_UI_REGISTRY: Record<string, ToolUiEntry> = {
export function isToolUiComponent(name: string): boolean {
return Object.prototype.hasOwnProperty.call(TOOL_UI_REGISTRY, name);
}
let warmed = false;
/** Warm every widget chunk and schema once the page is idle, so the first ShowUI in a chat mounts as content rather than a skeleton; a failed warm is silent and the widget's own mount retries it. */
export function preloadToolUi(): void {
if (warmed) return;
warmed = true;
const run = (): void => {
for (const e of Object.values(TOOL_UI_REGISTRY)) {
e.load().catch(() => {});
e.loadSchema().catch(() => {});
}
};
if (typeof requestIdleCallback === 'function') requestIdleCallback(run, { timeout: 4000 });
else setTimeout(run, 1500);
}
@@ -23,3 +23,24 @@ test('every registry entry exposes a plain loader and none is a React.lazy', ()
assert.equal(entries, 26);
assert.equal(loaders, entries, 'one load() per entry');
});
test('a failed chunk load is not remembered: the next mount fetches again', async () => {
const { componentFor } = await import('./VendoredToolUi');
let calls = 0;
const entry = { load: () => { calls += 1; return calls === 1 ? Promise.reject(new Error('blip')) : Promise.resolve(() => null); }, loadSchema: () => Promise.resolve({} as never) };
await assert.rejects(componentFor(entry as never));
const second = await componentFor(entry as never);
assert.equal(typeof second, 'function');
assert.equal(calls, 2, 'the rejection was forgotten and the load ran again');
const third = await componentFor(entry as never);
assert.equal(third, second);
assert.equal(calls, 2, 'a success IS remembered');
});
test('the widget chunks are warmed at idle from the app shell', () => {
const main = fs.readFileSync(path.join(process.cwd(), 'src/app/Main.tsx'), 'utf8');
assert.ok(main.includes("import('@toolui/registry').then((m) => m.preloadToolUi())"));
const reg = fs.readFileSync(path.join(process.cwd(), 'src/toolui/registry.tsx'), 'utf8');
assert.ok(reg.includes('export function preloadToolUi'));
});