fix(ui): lazy load usage dashboard

This commit is contained in:
Vincent Koc
2026-06-03 07:41:27 +02:00
parent c0b05a2100
commit f8fcb35064
4 changed files with 26 additions and 11 deletions

View File

@@ -50,6 +50,7 @@ Docs: https://docs.openclaw.ai
- Release/CI/E2E: reset shared Crabbox pnpm hydrate state before installs so stale `/var/tmp` stores cannot leave `pnpm install` spinning after completion.
- Release/CI/E2E: print heartbeat progress during centralized Docker builds while keeping successful build logs quiet.
- Release/CI/E2E: avoid heartbeat-tail delays in Docker E2E log wrappers while reporting captured log bytes during long runs.
- Control UI: lazy-load the usage view so the initial app bundle stays below the chunk warning threshold.
- Build: render independent CLI startup metadata help snapshots concurrently to cut cold build-all metadata time.
- Plugins: stop timed-out package-boundary prep steps by process group so descendant TypeScript/helper processes do not survive local check cleanup.
- Control UI: serve static assets asynchronously after safe-open checks so large UI files do not block Gateway request handling.

View File

@@ -2,6 +2,7 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { renderUsageTab } from "./app-render-usage-tab.ts";
import type { AppViewState } from "./app-view-state.ts";
import type { LazyView } from "./lazy-view.ts";
import type { UsageProps } from "./views/usageTypes.ts";
const loadUsageMock = vi.hoisted(() => vi.fn(async () => {}));
@@ -15,9 +16,17 @@ vi.mock("./controllers/usage.ts", async (importOriginal) => {
};
});
vi.mock("./views/usage.ts", () => ({
renderUsage: renderUsageMock,
}));
type UsageViewModule = typeof import("./views/usage.ts");
function createLoadedUsageView(): LazyView<UsageViewModule> {
return {
read: () => ({ renderUsage: renderUsageMock }) as unknown as UsageViewModule,
retry: () => {},
error: () => undefined,
hasError: () => false,
pending: () => false,
};
}
function createState(overrides: Partial<AppViewState> = {}): AppViewState {
return {
@@ -52,7 +61,7 @@ describe("renderUsageTab", () => {
});
it("passes configured agents to the usage view", () => {
renderUsageTab(createState());
renderUsageTab(createState(), createLoadedUsageView());
expect(renderUsageMock).toHaveBeenCalledWith(
expect.objectContaining({
@@ -64,7 +73,7 @@ describe("renderUsageTab", () => {
it("reloads usage when selecting an agent scope", () => {
const state = createState();
renderUsageTab(state);
renderUsageTab(state, createLoadedUsageView());
expect(renderUsageMock).toHaveBeenCalled();
const props = renderUsageMock.mock.calls[0]?.[0];
if (!props) {

View File

@@ -2,7 +2,11 @@ import { nothing } from "lit";
import type { AppViewState } from "./app-view-state.ts";
import type { UsageState } from "./controllers/usage.ts";
import { loadUsage, loadSessionTimeSeries, loadSessionLogs } from "./controllers/usage.ts";
import { renderUsage } from "./views/usage.ts";
import type { LazyView } from "./lazy-view.ts";
import { renderLazyView } from "./lazy-view.ts";
import type { UsageColumnId } from "./views/usageTypes.ts";
type UsageViewModule = typeof import("./views/usage.ts");
type UsageCacheStatus = NonNullable<NonNullable<UsageState["usageResult"]>["cacheStatus"]>;
@@ -40,12 +44,12 @@ const debouncedLoadUsage = (state: UsageState) => {
usageDateDebounceTimeout = window.setTimeout(() => void loadUsage(state), 400);
};
export function renderUsageTab(state: AppViewState) {
export function renderUsageTab(state: AppViewState, usageView: LazyView<UsageViewModule>) {
if (state.tab !== "usage") {
return nothing;
}
return renderUsage({
return renderLazyView(usageView, ({ renderUsage }) => renderUsage({
data: {
loading: state.usageLoading,
error: state.usageError,
@@ -79,7 +83,7 @@ export function renderUsageTab(state: AppViewState) {
sessionSortDir: state.usageSessionSortDir,
recentSessions: state.usageRecentSessions,
sessionsTab: state.usageSessionsTab,
visibleColumns: state.usageVisibleColumns as import("./views/usage.ts").UsageColumnId[],
visibleColumns: state.usageVisibleColumns as UsageColumnId[],
contextExpanded: state.usageContextExpanded,
headerPinned: state.usageHeaderPinned,
},
@@ -333,5 +337,5 @@ export function renderUsageTab(state: AppViewState) {
},
},
},
});
}));
}

View File

@@ -531,6 +531,7 @@ const lazySkillWorkshop = createLazyView(
notifyLazyViewChanged,
);
const lazySkills = createLazyView(() => import("./views/skills.ts"), notifyLazyViewChanged);
const lazyUsage = createLazyView(() => import("./views/usage.ts"), notifyLazyViewChanged);
const lazyWorkboard = createLazyView(() => import("./views/workboard.ts"), notifyLazyViewChanged);
type ChatWorkspaceFilesState = {
@@ -2642,7 +2643,7 @@ export function renderApp(state: AppViewState) {
});
})
: nothing}
${renderUsageTab(state)}
${renderUsageTab(state, lazyUsage)}
${state.tab === "cron" ? renderCronQuickCreateForTab(state, requestHostUpdate) : nothing}
${state.tab === "cron"
? renderLazyView(lazyCron, (m) =>