Skip to content

feat(frontend): redesign atoms and event listener for multi-GPU (#1299)#1343

Merged
shm11C3 merged 3 commits into
developfrom
feat/frontend-multi-gpu-atoms-1299
Apr 11, 2026
Merged

feat(frontend): redesign atoms and event listener for multi-GPU (#1299)#1343
shm11C3 merged 3 commits into
developfrom
feat/frontend-multi-gpu-atoms-1299

Conversation

@shm11C3

@shm11C3 shm11C3 commented Apr 11, 2026

Copy link
Copy Markdown
Owner

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

  • Bug fix (fix/ branch)
  • New feature (feat/ branch)
  • Refactoring (refactor/ branch)
  • Documentation (docs/ branch)
  • Dependencies update
  • Other (chore/ branch)

Screenshots / Videos

Test Plan

  • Manual testing
  • Unit tests

Checklist

  • Self-reviewed the code
  • Linting and formatting pass (npm run lint && npm run format / cargo tauri-lint && cargo tauri-fmt)
  • Tests pass (npm test / cargo tauri-test)
  • No new warnings or errors

Summary by CodeRabbit

  • New Features

    • Added multi-GPU support: per‑GPU usage histories, memory, temperature, fan speed, and usage sources; aggregated temperature and fan-speed views.
    • Automatic GPU selection now defaults to the first available GPU when none is selected.
  • Tests

    • Added multi‑GPU tests verifying per‑GPU histories, selection behavior, and correct aggregation and mapping of incoming GPU metrics.

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.
Copilot AI review requested due to automatic review settings April 11, 2026 21:23
@coderabbitai

coderabbitai Bot commented Apr 11, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Reworks GPU state from single-GPU atoms to per-GPU record-based atoms, adds selectedGpuIdAtom, and updates the hardware event listener to process event.payload.gpus[]. Single-GPU atoms are converted to derived atoms that resolve to the selected or first GPU.

Changes

Cohort / File(s) Summary
Atom Store Redesign
src/features/hardware/store/chart.ts
Added per-GPU map atoms (gpuUsageHistoriesAtom, gpuUsageSourcesAtom, gpuDedicatedMemoryKbMapAtom, gpuTempMapAtom, gpuFanSpeedMapAtom) and selectedGpuIdAtom. Converted graphicUsageHistoryAtom, gpuUsageSourceAtom, and gpuDedicatedMemoryKbAtom into derived atoms that resolve by selectedGpuIdAtom or first available GPU. Updated gpuTempAtom/gpuFanSpeedAtom to derive from per-GPU maps.
Event Listener Updates
src/features/hardware/hooks/useHardwareEventListener.ts
Changed listener to iterate event.payload.gpus[], append per-gpuId usage into gpuUsageHistoriesAtom, populate per-GPU maps for temp/fan/source/dedicated memory, and set selectedGpuIdAtom to first GPU ID when null.
Test Coverage
src/features/hardware/hooks/useHardwareEventListener.test.ts
Expanded tests with a "multi-GPU support" group asserting separate per-gpuId usage histories, auto-selection of first GPU ID, selected GPU history reflected by derived atoms, aggregated temp/fan outputs, and per-GPU metadata maps updated from payload.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • Issue #1296 — Both are part of the frontend multi-GPU redesign and change event payload handling to gpus[].
  • Issue #1345 — Related to per-GPU map atoms and lifecycle/pruning concerns for GPU keyed state managed by the event listener.

Possibly related PRs

  • PR #1332 — Closely related frontend/backend coordination for replacing flat GPU fields with a gpus[] payload and adapting the event listener and atoms to per-GPU state.

Poem

🐰
Multi-GPUs hop into view,
Per-id maps track each one true,
The first gets picked when none is known,
Temperatures and fans all shown,
Hooray — the dashboard’s garden has grown!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(frontend): redesign atoms and event listener for multi-GPU' accurately describes the main change—migrating frontend state from single-GPU to multi-GPU atoms and updating the event listener accordingly.
Description check ✅ Passed The PR description provides a clear summary, links the related issue (#1299), identifies this as a bug fix, and confirms that manual testing and unit tests were completed.
Linked Issues check ✅ Passed The PR fully implements all coding requirements from issue #1299: per-GPU atoms with map structures, derived backward-compatibility atoms, updated event listener processing for gpus[] arrays, and updated tests.
Out of Scope Changes check ✅ Passed All changes are scoped to the three files specified in issue #1299 and directly implement the multi-GPU redesign with no unrelated modifications.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/frontend-multi-gpu-atoms-1299

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Apr 11, 2026

Copy link
Copy Markdown
Contributor

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 99.03% (🎯 60%) 1025 / 1035
🔵 Statements 98.81% (🎯 60%) 1088 / 1101
🔵 Functions 98.47% (🎯 60%) 259 / 263
🔵 Branches 93.75% (🎯 60%) 345 / 368
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
src/features/hardware/hooks/useHardwareEventListener.ts 100% 100% 100% 100%
src/features/hardware/store/chart.ts 97.29% 90.9% 83.33% 97.05%
Generated in workflow #2662 for commit b71bcf5 by the Vitest Coverage Report Action

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 a selectedGpuIdAtom.
  • Implemented derived atoms (graphicUsageHistoryAtom, gpuUsageSourceAtom, gpuDedicatedMemoryKbAtom) to preserve existing single-GPU reads.
  • Updated useHardwareEventListener (and tests) to consume payload.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 / GpuMonitorData shapes 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[];
      };

Comment thread src/features/hardware/store/chart.ts
Comment thread src/features/hardware/store/chart.ts
Comment thread src/features/hardware/hooks/useHardwareEventListener.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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: selectedGpuIdAtom is never seeded to a non-first id before reading graphicUsageHistoryAtom. 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

📥 Commits

Reviewing files that changed from the base of the PR and between 8510c83 and 4886ef5.

📒 Files selected for processing (3)
  • src/features/hardware/hooks/useHardwareEventListener.test.ts
  • src/features/hardware/hooks/useHardwareEventListener.ts
  • src/features/hardware/store/chart.ts

Comment thread src/features/hardware/hooks/useHardwareEventListener.ts
Comment thread src/features/hardware/store/chart.ts
Comment thread src/features/hardware/store/chart.ts
shm11C3 added 2 commits April 12, 2026 06:37
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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
src/features/hardware/store/chart.ts (2)

11-12: ⚠️ Potential issue | 🟠 Major

Widen the GPU history atoms to nullable samples.

These histories still advertise number[], but the updater pads them with null samples. That makes graphicUsageHistoryAtom lie 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.ts

Also 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 | 🟠 Major

Temp/fan compatibility still does not follow the selected GPU.

This store now has selected-GPU compatibility for usage, source, and memory, but gpuTempAtom/gpuFanSpeedAtom still only expose all-GPU aggregates. src/features/hardware/dashboard/components/DashboardItems.tsx:78-106 resolves those metrics against hardwareInfo.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 | 🟠 Major

Keep missing-GPU handling consistent across all per-GPU atoms.

setGpuHistories and setGpuMemoryMap retain old keys via { ...prev }, but source/temp/fan are rebuilt from only the current payload and selectedGpuIdAtom is never revalidated. When a selected GPU drops out for one event, src/features/hardware/store/chart.ts:53-76 can return stale history/memory for that GPU while source/temp/fan silently switch to another GPU or null.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 4886ef5 and b71bcf5.

📒 Files selected for processing (3)
  • src/features/hardware/hooks/useHardwareEventListener.test.ts
  • src/features/hardware/hooks/useHardwareEventListener.ts
  • src/features/hardware/store/chart.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/features/hardware/hooks/useHardwareEventListener.test.ts

Comment thread src/features/hardware/hooks/useHardwareEventListener.ts
@shm11C3 shm11C3 merged commit 686a44b into develop Apr 11, 2026
22 checks passed
@shm11C3 shm11C3 deleted the feat/frontend-multi-gpu-atoms-1299 branch April 11, 2026 22:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Frontend] Redesign atoms and event listener for multi-GPU

2 participants