From ee3eaa2c0d9aeb79387f66ce48848db8d8d30fe6 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Thu, 30 Jul 2026 08:43:04 +0200 Subject: [PATCH 1/3] perf(gui): progressive dashboard paint and Startup safety CLS Split slow dashboard peers, cache non-secret page seeds, and keep Startup chrome stable while diagnostics settle. --- .../provider-catalog/ProviderCatalog.tsx | 20 +- .../provider-workspace/ProviderAuthPanel.tsx | 39 +- .../ProviderOverviewDashboard.tsx | 69 +- .../provider-workspace/ProviderRail.tsx | 8 +- .../ProviderWorkspaceShell.tsx | 145 ++-- gui/src/pages/Providers.tsx | 72 +- gui/src/pages/Startup.tsx | 259 +++--- gui/src/pages/dashboard-core-poll.ts | 286 ++++--- gui/src/pages/dashboard-overview-head.tsx | 14 +- gui/src/pages/dashboard-overview-sections.tsx | 6 +- gui/src/pages/startup-sections.tsx | 24 +- gui/src/pages/use-dashboard-data.ts | 242 +++++- gui/src/pages/use-providers-fetch.ts | 47 +- gui/src/provider-workspace/catalog.ts | 29 +- gui/src/provider-workspace/usage.ts | 11 +- gui/src/startup-health-ui.ts | 23 +- gui/src/styles-dashboard-workspace.css | 2 + gui/src/styles.css | 739 ++++++++++++++++-- .../styles/provider-overview-dashboard.css | 56 ++ gui/src/styles/provider-workspace-shell.css | 5 +- gui/src/ui.tsx | 4 +- gui/tests/collapse-store.test.ts | 10 +- gui/tests/dashboard-contracts.test.ts | 100 ++- src/server/management/config-routes.ts | 16 +- src/server/management/context.ts | 5 +- src/server/startup-action-control.ts | 44 +- src/service.ts | 170 +++- tests/provider-workspace-data.test.ts | 18 +- tests/service.test.ts | 97 ++- .../startup-action-control-elevation.test.ts | 11 + tests/startup-action-control.test.ts | 30 +- tests/startup-health-ui.test.ts | 13 +- ...ows-scheduler-install-verification.test.ts | 25 + 33 files changed, 2056 insertions(+), 583 deletions(-) diff --git a/gui/src/components/provider-catalog/ProviderCatalog.tsx b/gui/src/components/provider-catalog/ProviderCatalog.tsx index 1e8a659e8..dc093bf6a 100644 --- a/gui/src/components/provider-catalog/ProviderCatalog.tsx +++ b/gui/src/components/provider-catalog/ProviderCatalog.tsx @@ -62,13 +62,19 @@ export default function ProviderCatalog({ const catalog = useMemo(() => presets.filter(p => p.id !== "custom"), [presets]); - /** Usage-ranked order: requests desc, then label (050a sortPresets is the no-usage fallback). */ - const ranked = useMemo(() => catalog.toSorted((a, b) => { - const ra = usageRank[a.id] ?? 0; - const rb = usageRank[b.id] ?? 0; - if (rb !== ra) return rb - ra; - return a.label.localeCompare(b.label, undefined, { sensitivity: "base" }) || a.id.localeCompare(b.id); - }), [catalog, usageRank]); + /** Usage-ranked order only after usage arrives; until then keep stable label order + * so a slow /api/usage (~5s cold) cannot flash a catalog resort. */ + const ranked = useMemo(() => { + const hasUsage = Object.keys(usageRank).length > 0; + return catalog.toSorted((a, b) => { + if (hasUsage) { + const ra = usageRank[a.id] ?? 0; + const rb = usageRank[b.id] ?? 0; + if (rb !== ra) return rb - ra; + } + return a.label.localeCompare(b.label, undefined, { sensitivity: "base" }) || a.id.localeCompare(b.id); + }); + }, [catalog, usageRank]); const buckets = useMemo(() => bucketPresets(ranked), [ranked]); const tierList = buckets[tier]; diff --git a/gui/src/components/provider-workspace/ProviderAuthPanel.tsx b/gui/src/components/provider-workspace/ProviderAuthPanel.tsx index efa7bed39..fe26fad47 100644 --- a/gui/src/components/provider-workspace/ProviderAuthPanel.tsx +++ b/gui/src/components/provider-workspace/ProviderAuthPanel.tsx @@ -3,7 +3,7 @@ * embedding for the workspace Settings tab (WP091). Consumes WP040+WP060 * handlers via props-down; no internal auth machinery. */ -import { useState } from "react"; +import { useEffect, useState } from "react"; import { useT } from "../../i18n/shared"; import { IconLock, IconTrash } from "../../icons"; import type { WorkspaceItem } from "../../provider-workspace/catalog"; @@ -27,6 +27,7 @@ import type { CodexAccountPoolController } from "../../hooks/useCodexAccountPool import type { AccountLoadState, OAuthAccountRow, ApiKeyRow, LoginHint, ProviderAuthHandlers } from "./types"; const DOCTOR_CMD = "ocx doctor"; +const QUOTA_ENRICH_RESERVE_MS = 4_000; export default function ProviderAuthPanel({ item, apiBase, oauth, accounts = [], keys = [], accountLoadState = "ready", @@ -51,9 +52,27 @@ export default function ProviderAuthPanel({ const [addingKey, setAddingKey] = useState(false); const [newKey, setNewKey] = useState(""); const [keyBusy, setKeyBusy] = useState(false); + const [reserveQuotaSlots, setReserveQuotaSlots] = useState(false); const deviceCodeCopy = useCopyFeedback(); const doctorCopy = useCopyFeedback(); + // Soft "a=1 enrichment lands after the local account list. Reserve stacked + // bar height briefly so bars don't shove rows when WHAM returns. + useEffect(() => { + if (accounts.length === 0) { + setReserveQuotaSlots(false); + return; + } + const needsFill = accounts.some(a => a.quota == null && !a.quotaUnavailable); + if (!needsFill) { + setReserveQuotaSlots(false); + return; + } + setReserveQuotaSlots(true); + const timer = window.setTimeout(() => setReserveQuotaSlots(false), QUOTA_ENRICH_RESERVE_MS); + return () => window.clearTimeout(timer); + }, [accounts]); + const surface = providerAuthSurface({ ...item, hasApiKey: item.hasApiKey || keys.length > 0 }); const isOauth = surface === "oauth-accounts"; const isKeyAuth = surface === "api-keys"; @@ -222,7 +241,7 @@ export default function ProviderAuthPanel({ )} {showDoctor && ( - )} @@ -238,13 +257,19 @@ export default function ProviderAuthPanel({