diff --git a/apps/desktop/src/settings/DesktopClientSettings.test.ts b/apps/desktop/src/settings/DesktopClientSettings.test.ts index 26f604318c1..81d33c5db70 100644 --- a/apps/desktop/src/settings/DesktopClientSettings.test.ts +++ b/apps/desktop/src/settings/DesktopClientSettings.test.ts @@ -19,6 +19,7 @@ const clientSettings: ClientSettings = { dismissedProviderUpdateNotificationKeys: [], diffIgnoreWhitespace: true, favorites: [], + fileExplorerShowDotfiles: true, headerGitActionsVisibility: "auto", headerOpenInEditorVisibility: "auto", headerProjectScriptsVisibility: "auto", @@ -33,6 +34,7 @@ const clientSettings: ClientSettings = { providerModelPreferences: {}, sidebarChatListView: "grouped", sidebarHostStatsVisible: false, + sidebarHostStatsStyle: "classic", sidebarProjectGroupingMode: "repository_path", sidebarProjectGroupingOverrides: { "environment-1:/tmp/project-a": "separate", diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index 1b737a8d22b..24415d8a912 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -210,7 +210,8 @@ import { useCopyToClipboard } from "~/hooks/useCopyToClipboard"; import { useIsMobile } from "~/hooks/useMediaQuery"; import { CommandDialogTrigger } from "./ui/command"; import { useClientSettings, useUpdateClientSettings } from "~/hooks/useSettings"; -import { useHostStats } from "~/hooks/useHostStats"; +import { useHostStatsWithHistory } from "~/hooks/useHostStats"; +import { HOST_STATS_VARIANTS } from "~/components/host-stats/variants"; import { primaryServerConfigAtom, primaryServerKeybindingsAtom } from "../state/server"; import { derivePhysicalProjectKey, @@ -3298,37 +3299,19 @@ function T3Wordmark() { ); } -// Compact "3.2/15.6 GB" style figure for the footer host-stats readout. -function formatFooterGigabytes(bytes: number): string { - const gigabytes = bytes / 1024 ** 3; - if (gigabytes >= 100) return String(Math.round(gigabytes)); - return gigabytes.toFixed(1); -} - // Ambient CPU/memory telemetry for the T3 server host, shown next to the // Settings button when the "Server load" toggle (Settings → Features) is on. +// The visual style is selectable there too; see components/host-stats/. const SidebarHostStats = memo(function SidebarHostStats() { const visible = useClientSettings((settings) => settings.sidebarHostStatsVisible); - const stats = useHostStats(visible); + const style = useClientSettings((settings) => settings.sidebarHostStatsStyle); + const { stats, history } = useHostStatsWithHistory(visible); if (!visible || stats === null) { return null; } - const cpuLabel = `${Math.round(stats.cpuPercent)}%`; - const memLabel = `${formatFooterGigabytes(stats.memUsedBytes)}/${formatFooterGigabytes(stats.memTotalBytes)}G`; - const coreLabel = stats.cpuCount === 1 ? "1 core" : `${stats.cpuCount} cores`; - const detail = `Server load — CPU ${stats.cpuPercent.toFixed(1)}% of ${coreLabel} · memory ${formatFooterGigabytes(stats.memUsedBytes)} of ${formatFooterGigabytes(stats.memTotalBytes)} GB`; - - return ( -
Visibility is stored per device. “Auto” shows a control on desktop-width screens and hides
diff --git a/apps/web/src/hooks/useHostStats.ts b/apps/web/src/hooks/useHostStats.ts
index 11ef2ee8774..50baaecb727 100644
--- a/apps/web/src/hooks/useHostStats.ts
+++ b/apps/web/src/hooks/useHostStats.ts
@@ -1,5 +1,5 @@
-import type { ServerHostStatsResult } from "@t3tools/contracts";
-import { useEffect } from "react";
+import type { ServerHostStatsResult, ServerHostStatsSnapshot } from "@t3tools/contracts";
+import { useEffect, useRef, useState } from "react";
import { usePrimaryEnvironmentId } from "../state/environments";
import { useEnvironmentQuery } from "../state/query";
@@ -37,3 +37,58 @@ export function useHostStats(enabled: boolean): ServerHostStatsResult {
return query.data;
}
+
+/** One retained host-stats sample; `at` is the client receive time (ms epoch). */
+export interface HostStatsSample {
+ readonly at: number;
+ readonly cpuPercent: number;
+ readonly memUsedBytes: number;
+ readonly memTotalBytes: number;
+}
+
+/** ~2 minutes of history at the 5s poll interval. */
+const HISTORY_LIMIT = 24;
+
+export interface HostStatsWithHistory {
+ readonly stats: ServerHostStatsSnapshot | null;
+ /** Oldest→newest rolling window of recent samples, including `stats`. */
+ readonly history: readonly HostStatsSample[];
+}
+
+// Module-level so the window survives sidebar unmounts (e.g. visiting the
+// Settings page and coming back) — only an explicit disable clears it.
+let cachedHistory: readonly HostStatsSample[] = [];
+
+/**
+ * `useHostStats` plus a client-side rolling window of recent samples so
+ * richer readouts (sparklines, trend meters) have something to draw. The
+ * history resets when the readout is disabled.
+ */
+export function useHostStatsWithHistory(enabled: boolean): HostStatsWithHistory {
+ const stats = useHostStats(enabled);
+ const [history, setHistory] = useState