feat(frontend): redesign atoms and event listener for multi-GPU (#1299)#1343
Conversation
Replace single-GPU atoms with per-GPU map atoms keyed by gpuId and add derived read-only atoms for backward compatibility so existing chart components continue working without changes.
📝 WalkthroughWalkthroughReworks GPU state from single-GPU atoms to per-GPU record-based atoms, adds Changes
Sequence Diagram(s)(omitted) Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Coverage Report
File Coverage
|
||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Pull request overview
Refactors the frontend hardware monitoring state to support multi-GPU updates by introducing per-GPU, gpuId-keyed atoms and updating the hardware monitor event listener accordingly, while keeping existing chart components working via derived “backward compatible” atoms.
Changes:
- Added multi-GPU map atoms (
gpuUsageHistoriesAtom,gpuUsageSourcesAtom,gpuDedicatedMemoryKbMapAtom) and aselectedGpuIdAtom. - Implemented derived atoms (
graphicUsageHistoryAtom,gpuUsageSourceAtom,gpuDedicatedMemoryKbAtom) to preserve existing single-GPU reads. - Updated
useHardwareEventListener(and tests) to consumepayload.gpus[]and populate the new per-GPU atoms.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/features/hardware/store/chart.ts | Introduces per-GPU map atoms + selected GPU atom, and adds derived atoms for backward compatibility. |
| src/features/hardware/hooks/useHardwareEventListener.ts | Updates event handling to process gpus[] and write into per-GPU atoms + auto-selects an initial GPU. |
| src/features/hardware/hooks/useHardwareEventListener.test.ts | Adds multi-GPU behavior tests and updates mocks to use gpus[] payloads. |
Comments suppressed due to low confidence (1)
src/features/hardware/hooks/useHardwareEventListener.ts:54
- The event payload type is re-declared inline here, duplicating the generated
HardwareMonitorUpdate/GpuMonitorDatashapes from@/rspc/bindings. This can silently drift if the backend payload changes again; prefer typing the callback as{ payload: HardwareMonitorUpdate }(or importing the exact event type) to keep the listener in sync with bindings.
(event: {
payload: {
cpuUsage: number;
memoryUsage: number;
gpus: {
gpuId: string;
gpuName: string;
gpuUsage: number | null;
gpuTemperature: number | null;
gpuSource: string;
gpuDedicatedMemoryUsageKb: number | null;
gpuCoolerLevel: number | null;
}[];
processorsUsage: number[];
};
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
src/features/hardware/hooks/useHardwareEventListener.test.ts (1)
509-532: Exercise the non-first selected-GPU path explicitly.This only verifies the fallback/auto-select behavior:
selectedGpuIdAtomis never seeded to a non-first id before readinggraphicUsageHistoryAtom. A regression in the actual selected-id branch would still pass. Add one case that preselects GPU 2 and asserts the derived history follows it.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/hardware/hooks/useHardwareEventListener.test.ts` around lines 509 - 532, Add a test that preselects a non-first GPU id before reading graphicUsageHistoryAtom: in useHardwareEventListener.test.ts render the hook (calling useHardwareEventListener) and set selectedGpuIdAtom to "nvapi:1" (via useAtom(selectedGpuIdAtom) and act) before emitting the payload with makePayload/makeGpu, then emit the event and assert that graphicUsageHistoryAtom (read after emit) contains the selected GPU's usage (99) instead of the fallback first GPU; reference useHardwareEventListener, selectedGpuIdAtom, graphicUsageHistoryAtom, renderHook, emit, makePayload, makeGpu, and Provider to locate where to add this case.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/features/hardware/hooks/useHardwareEventListener.ts`:
- Around line 61-112: The per-GPU atoms are updated inconsistently:
setGpuHistories and setGpuMemoryMap keep old keys via "{ ...prev }" while
setGpuSources is rebuilt from current payload and selectedGpuIdAtom isn't
revalidated, causing stale data when a GPU disappears. Fix by computing the
current GPU id set from gpus, prune prev entries to only those ids when updating
gpuUsageHistoriesAtom (setGpuHistories) and gpuDedicatedMemoryKbMapAtom
(setGpuMemoryMap), build gpuUsageSourcesAtom (setGpuSources) from the same
current id set, and then revalidate selectedGpuIdAtom via setSelectedGpuId to
null or to the first available id if the previous selected id is no longer
present so derived charts in src/features/hardware/store/chart.ts never
reference missing GPUs.
In `@src/features/hardware/store/chart.ts`:
- Around line 29-56: Add derived atoms for temperature and fan speed mirroring
the existing backward-compatibility pattern: create gpuTempAtom and
gpuFanSpeedAtom (or equivalents) that read selectedGpuIdAtom and fall back to
the first entry in the corresponding maps (like
gpuUsageHistoriesAtom/gpuUsageSourcesAtom/gpuDedicatedMemoryKbMapAtom). Update
any consumers (e.g., DashboardItems.tsx logic that currently reads
hardwareInfo.gpus[0].name) to use these new atoms so temp and fan follow the
selected GPU the same way usage/source/memory do.
- Around line 11-12: The atoms gpuUsageHistoriesAtom and graphicUsageHistoryAtom
are typed as Record<string, number[]> but padHistory populates arrays with nulls
via .fill(null); update their types to Record<string, (number | null)[]> (or
Record<string, NullableNumberArray>) so the stored arrays accept null samples,
and adjust any related function signatures (e.g., padHistory's return type and
any consumers that assume pure number[]) to use (number | null)[] so TypeScript
no longer errors under strict mode.
---
Nitpick comments:
In `@src/features/hardware/hooks/useHardwareEventListener.test.ts`:
- Around line 509-532: Add a test that preselects a non-first GPU id before
reading graphicUsageHistoryAtom: in useHardwareEventListener.test.ts render the
hook (calling useHardwareEventListener) and set selectedGpuIdAtom to "nvapi:1"
(via useAtom(selectedGpuIdAtom) and act) before emitting the payload with
makePayload/makeGpu, then emit the event and assert that graphicUsageHistoryAtom
(read after emit) contains the selected GPU's usage (99) instead of the fallback
first GPU; reference useHardwareEventListener, selectedGpuIdAtom,
graphicUsageHistoryAtom, renderHook, emit, makePayload, makeGpu, and Provider to
locate where to add this case.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: e0923eeb-2e8e-4dae-99e4-d285a5d82d4b
📒 Files selected for processing (3)
src/features/hardware/hooks/useHardwareEventListener.test.tssrc/features/hardware/hooks/useHardwareEventListener.tssrc/features/hardware/store/chart.ts
Use type predicate filters to narrow nullable fields, removing unnecessary gpuName != null guards (gpuName is always string) and the corresponding `as number` casts.
Add gpuTempMapAtom and gpuFanSpeedMapAtom keyed by gpuId, making temp/fan data consistent with the other per-GPU atoms. Derive gpuTempAtom (read-write for TemperatureUnitSelect clear) and gpuFanSpeedAtom (read-only) as backward-compat NameValues atoms.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (3)
src/features/hardware/store/chart.ts (2)
11-12:⚠️ Potential issue | 🟠 MajorWiden the GPU history atoms to nullable samples.
These histories still advertise
number[], but the updater pads them withnullsamples. That makesgraphicUsageHistoryAtomlie about its shape and hides gap values from downstream chart consumers.Suggested fix
+export type GpuUsageHistory = Array<number | null>; + /** Per-GPU usage histories keyed by gpuId */ -export const gpuUsageHistoriesAtom = atom<Record<string, number[]>>({}); +export const gpuUsageHistoriesAtom = atom<Record<string, GpuUsageHistory>>({}); /** Resolves to the selected (or first) GPU's usage history */ -export const graphicUsageHistoryAtom = atom<number[]>((get) => { +export const graphicUsageHistoryAtom = atom<GpuUsageHistory>((get) => { const selected = get(selectedGpuIdAtom); const histories = get(gpuUsageHistoriesAtom); if (selected && histories[selected]) return histories[selected];#!/bin/bash # Verify that history arrays are padded with nulls while the atoms are typed as number[]. rg -n -C2 'fill\(null\)|padHistory|gpuUsageHistoriesAtom|graphicUsageHistoryAtom' \ src/features/hardware/store/chart.ts \ src/features/hardware/hooks/useHardwareEventListener.tsAlso applies to: 53-59
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/hardware/store/chart.ts` around lines 11 - 12, The GPU history atoms advertise non-nullable arrays but the updater pads histories with nulls; update the atom types to accept nullable samples (use Record<string, (number | null)[]> for gpuUsageHistoriesAtom and any related atoms such as graphicUsageHistoryAtom and the atoms declared around the same block) so the type matches runtime data, and update any exported types or selectors that reference these atoms to the widened (number | null)[] shape.
37-48:⚠️ Potential issue | 🟠 MajorTemp/fan compatibility still does not follow the selected GPU.
This store now has selected-GPU compatibility for usage, source, and memory, but
gpuTempAtom/gpuFanSpeedAtomstill only expose all-GPU aggregates.src/features/hardware/dashboard/components/DashboardItems.tsx:78-106resolves those metrics againsthardwareInfo.gpus[0].name, so switching GPUs can update some fields while temp/fan stay pinned to the primary adapter.src/features/hardware/hooks/useHardwareEventListener.ts (1)
61-128:⚠️ Potential issue | 🟠 MajorKeep missing-GPU handling consistent across all per-GPU atoms.
setGpuHistoriesandsetGpuMemoryMapretain old keys via{ ...prev }, but source/temp/fan are rebuilt from only the current payload andselectedGpuIdAtomis never revalidated. When a selected GPU drops out for one event,src/features/hardware/store/chart.ts:53-76can return stale history/memory for that GPU while source/temp/fan silently switch to another GPU ornull.Please pick one policy and apply it everywhere: either prune missing IDs from every per-GPU atom and reselect/clear
selectedGpuIdAtom, or keep last-known values consistently for every metric.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/hardware/hooks/useHardwareEventListener.ts` around lines 61 - 128, The per-GPU atoms are inconsistent: some preserve old keys ({ ...prev }) while others are rebuilt, causing stale data and a selected GPU mismatch. Choose the "prune missing IDs" policy: for setGpuHistories use only current gpus (map gpus -> padHistory([...existingHistoryForGpu? maybe use prev[gpuId] if you want to preserve history for GPUs still present], gpu.gpuUsage)) instead of spreading all prev keys; change setGpuMemoryMap to build from gpus only (no { ...prev }); change setGpuTempMap, setGpuSources, setGpuFanSpeedMap to continue being built solely from current gpus; and finally revalidate selectedGpuId via setSelectedGpuId so if the previous selected id is not in the current gpus list you set it to gpus[0].gpuId or null. Reference symbols: setGpuHistories, padHistory, setGpuMemoryMap, setGpuTempMap, setGpuSources, setGpuFanSpeedMap, setSelectedGpuId, selectedGpuIdAtom.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/features/hardware/hooks/useHardwareEventListener.ts`:
- Around line 61-75: In useHardwareEventListener's setGpuHistories update (the
gpus.reduce block), when a gpu.gpuUsage is null you should still append a gap
sample (null) to that GPU's history instead of skipping it; locate the reducer
where acc[gpu.gpuId] is built and change the branch so it always does
acc[gpu.gpuId] = padHistory([...(acc[gpu.gpuId] ?? []), gpu.gpuUsage ===
undefined ? null : gpu.gpuUsage]) (or equivalent) so histories keep time
alignment while reusing the existing padHistory/gpuId logic.
---
Duplicate comments:
In `@src/features/hardware/hooks/useHardwareEventListener.ts`:
- Around line 61-128: The per-GPU atoms are inconsistent: some preserve old keys
({ ...prev }) while others are rebuilt, causing stale data and a selected GPU
mismatch. Choose the "prune missing IDs" policy: for setGpuHistories use only
current gpus (map gpus -> padHistory([...existingHistoryForGpu? maybe use
prev[gpuId] if you want to preserve history for GPUs still present],
gpu.gpuUsage)) instead of spreading all prev keys; change setGpuMemoryMap to
build from gpus only (no { ...prev }); change setGpuTempMap, setGpuSources,
setGpuFanSpeedMap to continue being built solely from current gpus; and finally
revalidate selectedGpuId via setSelectedGpuId so if the previous selected id is
not in the current gpus list you set it to gpus[0].gpuId or null. Reference
symbols: setGpuHistories, padHistory, setGpuMemoryMap, setGpuTempMap,
setGpuSources, setGpuFanSpeedMap, setSelectedGpuId, selectedGpuIdAtom.
In `@src/features/hardware/store/chart.ts`:
- Around line 11-12: The GPU history atoms advertise non-nullable arrays but the
updater pads histories with nulls; update the atom types to accept nullable
samples (use Record<string, (number | null)[]> for gpuUsageHistoriesAtom and any
related atoms such as graphicUsageHistoryAtom and the atoms declared around the
same block) so the type matches runtime data, and update any exported types or
selectors that reference these atoms to the widened (number | null)[] shape.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: 3bc48b6e-cffc-4d61-b55b-1f7fe8c2e2a5
📒 Files selected for processing (3)
src/features/hardware/hooks/useHardwareEventListener.test.tssrc/features/hardware/hooks/useHardwareEventListener.tssrc/features/hardware/store/chart.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/features/hardware/hooks/useHardwareEventListener.test.ts
Summary
Replace single-GPU atoms with per-GPU map atoms keyed by gpuId and add derived read-only atoms for backward compatibility so existing chart components continue working without changes.
Related Issues
Close #1299
Type of Change
fix/branch)feat/branch)refactor/branch)docs/branch)chore/branch)Screenshots / Videos
Test Plan
Checklist
npm run lint && npm run format/cargo tauri-lint && cargo tauri-fmt)npm test/cargo tauri-test)Summary by CodeRabbit
New Features
Tests