Skip to content

feat(storage): show SMART health in dashboard#1485

Open
shm11C3 wants to merge 2 commits into
developfrom
feat/storage-smart-dashboard-summary
Open

feat(storage): show SMART health in dashboard#1485
shm11C3 wants to merge 2 commits into
developfrom
feat/storage-smart-dashboard-summary

Conversation

@shm11C3
Copy link
Copy Markdown
Owner

@shm11C3 shm11C3 commented May 10, 2026

Add latest Storage SMART snapshot aggregation for the dashboard and display per-drive health status inside the existing Storage card.

Include frontend summary utilities, status icons, translations, and tests for dashboard storage health rendering.

Summary

Related Issues

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 storage device health monitoring to the dashboard with status indicators (good, warning, critical).
    • Displays latest storage health snapshots with automatic periodic refresh.
  • Localization

    • Added storage health status translations for English, Japanese, and Russian.

Review Change Stack

Add latest Storage SMART snapshot aggregation for the dashboard and display per-drive health status inside the existing Storage card.

Include frontend summary utilities, status icons, translations, and tests for dashboard storage health rendering.
Copilot AI review requested due to automatic review settings May 10, 2026 17:17
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 10, 2026

📝 Walkthrough

Walkthrough

This PR implements end-to-end storage health SMART snapshot monitoring. The core database exposes a new latest_snapshot_records() accessor; a Tauri command bridges it to the frontend with type conversions; React components fetch snapshots periodically, summarize them with health ranking logic, and display device status icons with translations.

Changes

Storage Health Dashboard Feature

Layer / File(s) Summary
Data Contracts & Types
core/src/models/hardware.rs, src-tauri/src/models/hardware.rs, src/rspc/bindings.ts
StorageHealthStatus and StorageWarningLevel enums defined; core StorageHealthSnapshotRecord added with device metadata and health fields; Tauri StorageSmartDashboardSnapshot mirrors core type for frontend use.
Core Database Accessor
core/src/infrastructure/database/storage_smart.rs
New latest_snapshot_records() function queries the most recent snapshot per device, joins device display name, orders by warning severity, parses health/warning enums and JSON warning reasons, and returns mapped records. Test extended to verify output.
Tauri Bridge & Bindings
src-tauri/src/commands/hardware.rs, src-tauri/src/models/hardware.rs, src-tauri/src/lib.rs, src/rspc/bindings.ts
New get_storage_smart_latest_snapshots command fetches core snapshots, converts via From impl, and returns dashboard-ready results; command registered in Specta builder and published to generated frontend bindings.
Summary Builder
src/features/hardware/dashboard/utils/storageHealthSummary.ts
buildStorageHealthSummary transforms snapshots into UI-ready view model: ranks by health severity, selects focus device, computes overall status, derives warnings/metrics, detects staleness. Includes helpers for metric collection, max temperature, and date calculations.
Summary Tests
src/features/hardware/dashboard/utils/storageHealthSummary.test.ts
Comprehensive test suite covering empty snapshots, good/warning/critical health states, device label fallbacks, unknown status handling, and stale data detection scenarios.
UI Components
src/features/hardware/dashboard/components/StorageHealthStatusIcon.tsx, DashboardItems.tsx
StorageHealthStatusIcon renders status-specific Phosphor icon with i18n label and accessibility attributes. StorageDataInfo fetches snapshots on mount, polls every 60 seconds, builds summary, displays device health overview with per-device status.
Dashboard Item Normalization
src/features/hardware/dashboard/hooks/useSortableDashboard.ts
Constant DEFAULT_DASHBOARD_ITEMS extracted; new useEffect synchronizes dashboard item state by filtering to known items and appending missing defaults.
Translations
src/lang/en.json, ja.json, ru.json
Added pages.dashboard.storageHealth.status keys for good, warning, critical, unknown in English, Japanese, and Russian.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • shm11C3/HardwareVisualizer#1483: Adds SMART storage models and database insert/delete operations that this PR extends with a new latest-snapshot query accessor.

Suggested labels

rust, frontend, feature, hardviz_core, hardviz_tauri

Poem

🐰 A rabbit hops through storage snapshots clear,
Health status glowing, critical or dear,
From database to dashboard bright and wise,
Device warnings blink before your eyes!
Temperature soars, but the rabbit still cheers—
One more feature shipped, no storage fears! 🌟

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 64.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description provides a summary of the change but lacks critical details: no related issues are linked, the Type of Change section is unchecked, and the Test Plan and Checklist sections are incomplete. Complete the PR template: link related issues, check the appropriate Type of Change checkbox ('New feature'), describe the test plan (manual/unit tests performed), and verify checklist items are addressed before merge.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(storage): show SMART health in dashboard' clearly and concisely summarizes the main change: adding SMART health display to the dashboard storage section.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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/storage-smart-dashboard-summary

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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 10, 2026

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 94.81% (🎯 60%) 1299 / 1370
🔵 Statements 94.42% (🎯 60%) 1373 / 1454
🔵 Functions 96.47% (🎯 60%) 328 / 340
🔵 Branches 86.46% (🎯 60%) 492 / 569
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
src/features/hardware/dashboard/hooks/useSortableDashboard.ts 96.29% 91.66% 100% 95.83%
src/features/hardware/dashboard/utils/storageHealthSummary.ts 89.28% 67.44% 100% 88.67%
Generated in workflow #3047 for commit 21d8226 by the Vitest Coverage Report Action

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds support for showing per-drive Storage SMART health on the dashboard by exposing a “latest SMART snapshot per device” backend query and rendering a concise health/status list in the existing Storage dashboard card.

Changes:

  • Backend: add a DB query to fetch the latest SMART snapshot per storage device, expose it via a new Tauri command, and add the corresponding (Specta) models/types.
  • Frontend: add summary-building utilities + tests, and render per-drive health status icons/labels in the Storage dashboard card.
  • i18n: add dashboard storage-health status translations (en/ja/ru) and fill a missing ru dashboard GPU selector label.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/rspc/bindings.ts Adds the new command/type exports for SMART dashboard snapshots (but this file is generated).
src/lang/en.json Adds storage health status strings for the dashboard.
src/lang/ja.json Adds storage health status strings for the dashboard.
src/lang/ru.json Adds storage health status strings and a missing dashboard GPU selector label.
src/features/hardware/dashboard/utils/storageHealthSummary.ts New utility to aggregate latest SMART snapshots into a dashboard-friendly view model.
src/features/hardware/dashboard/utils/storageHealthSummary.test.ts Unit tests for the storage health summary builder.
src/features/hardware/dashboard/hooks/useSortableDashboard.ts Normalizes stored dashboard item lists and introduces a default item list.
src/features/hardware/dashboard/components/StorageHealthStatusIcon.tsx New status icon component with accessible labeling and i18n integration.
src/features/hardware/dashboard/components/DashboardItems.tsx Fetches latest SMART snapshots and renders a per-drive health overview in the Storage card.
src-tauri/src/models/hardware.rs Adds app-side models/enums for SMART dashboard snapshots and conversion from core records.
src-tauri/src/lib.rs Registers the new Tauri command.
src-tauri/src/commands/hardware.rs Adds the new get_storage_smart_latest_snapshots command.
core/src/models/hardware.rs Adds a core model for a snapshot “record” shape returned by the DB layer.
core/src/infrastructure/database/storage_smart.rs Adds latest_snapshot_records() query + helpers and tests.

Comment thread src/rspc/bindings.ts
Comment on lines 83 to 90
inUseBytes: string | null,
allocBytes: string | null,
} | null, string>(__TAURI_INVOKE("get_gpu_memory_usage")),
// ## Get latest Storage SMART snapshots for the dashboard
getStorageSmartLatestSnapshots: () => typedError<StorageSmartDashboardSnapshot[], string>(__TAURI_INVOKE("get_storage_smart_latest_snapshots")),
getSettings: () => typedError<ClientSettings_Serialize, string>(__TAURI_INVOKE("get_settings")),
setLanguage: (newLanguage: string) => typedError<null, string>(__TAURI_INVOKE("set_language", { newLanguage })),
setTheme: (newTheme: Theme) => typedError<null, string>(__TAURI_INVOKE("set_theme", { newTheme })),
Comment on lines +185 to +188
const daysBetweenDateKeys = (currentDate: string, snapshotDate: string) => {
const current = Date.parse(`${currentDate}T00:00:00`);
const snapshot = Date.parse(`${snapshotDate}T00:00:00`);
if (!Number.isFinite(current) || !Number.isFinite(snapshot)) {
Comment on lines +184 to +195
///
/// ## Get latest Storage SMART snapshots for the dashboard
///
#[command]
#[specta::specta]
pub async fn get_storage_smart_latest_snapshots()
-> Result<Vec<models::hardware::StorageSmartDashboardSnapshot>, String> {
hardviz_core::infrastructure::database::storage_smart::latest_snapshot_records()
.await
.map(|records| records.into_iter().map(Into::into).collect())
.map_err(|e| format!("Failed to fetch latest storage SMART snapshots: {e}"))
}
Comment on lines +8 to +16
const DEFAULT_DASHBOARD_ITEMS: DashboardItemType[] = [
"cpu",
"gpu",
"memory",
"storage",
"network",
"process",
"motherboard",
];
Comment on lines +417 to +443
useEffect(() => {
let isMounted = true;

const loadStorageHealthDevices = async () => {
const result = await commands.getStorageSmartLatestSnapshots();
if (!isMounted) return;

if (isError(result)) {
console.error(
"Failed to fetch storage SMART dashboard snapshots",
result.error,
);
setStorageHealthDevices([]);
return;
}

setStorageHealthDevices(buildStorageHealthSummary(result.data).devices);
};

loadStorageHealthDevices();
const intervalId = window.setInterval(loadStorageHealthDevices, 60_000);

return () => {
isMounted = false;
window.clearInterval(intervalId);
};
}, []);
@github-actions
Copy link
Copy Markdown
Contributor

Rust Tauri Coverage Report

Coverage Details
Filename                                     Regions    Missed Regions     Cover   Functions  Missed Functions  Executed       Lines      Missed Lines     Cover    Branches   Missed Branches     Cover
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
_tests/commands/background_image_test.rs          39                 0   100.00%           6                 0   100.00%          21                 0   100.00%           0                 0         -
_tests/commands/settings_test.rs                 222                 0   100.00%          18                 0   100.00%         169                 0   100.00%           0                 0         -
adapters/tray.rs                                 127               127     0.00%          14                14     0.00%          87                87     0.00%           0                 0         -
adapters/window.rs                               254                69    72.83%          21                 8    61.90%         195                47    75.90%           0                 0         -
app/startup.rs                                   188                87    53.72%          10                 3    70.00%         114                58    49.12%           0                 0         -
commands/background_image.rs                      22                 7    68.18%          11                 5    54.55%          19                 7    63.16%           0                 0         -
commands/hardware.rs                              73                73     0.00%          24                24     0.00%          78                78     0.00%           0                 0         -
commands/settings.rs                             687               687     0.00%         118               118     0.00%         588               588     0.00%           0                 0         -
commands/system.rs                                21                21     0.00%          10                10     0.00%          20                20     0.00%           0                 0         -
commands/ui.rs                                    17                17     0.00%           2                 2     0.00%          13                13     0.00%           0                 0         -
commands/updater.rs                               97                97     0.00%          15                15     0.00%          66                66     0.00%           0                 0         -
enums/error.rs                                   101                10    90.10%           8                 1    87.50%          86                10    88.37%           0                 0         -
enums/hardware.rs                                184                 7    96.20%          15                 1    93.33%         111                 6    94.59%           0                 0         -
enums/settings.rs                                386                16    95.85%          23                 2    91.30%         253                10    96.05%           0                 0         -
infrastructure/database/migration.rs              89                 2    97.75%          12                 0   100.00%         148                 0   100.00%           0                 0         -
lib.rs                                           244               244     0.00%           6                 6     0.00%         159               159     0.00%           0                 0         -
lifecycle.rs                                     254               205    19.29%          31                27    12.90%         186               163    12.37%           0                 0         -
main.rs                                            3                 3     0.00%           1                 1     0.00%           3                 3     0.00%           0                 0         -
models/hardware.rs                               412               120    70.87%          34                15    55.88%         313               138    55.91%           0                 0         -
models/hardware_archive.rs                         8                 0   100.00%           2                 0   100.00%          10                 0   100.00%           0                 0         -
models/settings.rs                               311                 0   100.00%          17                 0   100.00%         278                 0   100.00%           0                 0         -
models/storage_smart.rs                            7                 0   100.00%           2                 0   100.00%           9                 0   100.00%           0                 0         -
services/background_image_service.rs             165                96    41.82%          16                10    37.50%          93                59    36.56%           0                 0         -
services/gpu_service.rs                           56                56     0.00%          11                11     0.00%          43                43     0.00%           0                 0         -
services/hardware_service.rs                      85                85     0.00%           4                 4     0.00%          51                51     0.00%           0                 0         -
services/language_service.rs                     101                 0   100.00%          18                 0   100.00%          57                 0   100.00%           0                 0         -
services/memory_service.rs                        12                12     0.00%           3                 3     0.00%           7                 7     0.00%           0                 0         -
services/motherboard_service.rs                   12                12     0.00%           3                 3     0.00%           7                 7     0.00%           0                 0         -
services/network_service.rs                       14                14     0.00%           1                 1     0.00%           8                 8     0.00%           0                 0         -
services/settings_service.rs                     480               172    64.17%          44                19    56.82%         362               154    57.46%           0                 0         -
services/system_service.rs                        22                22     0.00%           2                 2     0.00%          12                12     0.00%           0                 0         -
services/ui_service.rs                            45                45     0.00%           8                 8     0.00%          36                36     0.00%           0                 0         -
tray/surface/mod.rs                                8                 8     0.00%           2                 2     0.00%           5                 5     0.00%           0                 0         -
tray/surface/tauri_surface.rs                     80                80     0.00%           6                 6     0.00%          48                48     0.00%           0                 0         -
tray/widget.rs                                   601                 5    99.17%          51                 1    98.04%         533                 3    99.44%           0                 0         -
utils/color.rs                                    66                 1    98.48%           4                 0   100.00%          26                 0   100.00%           0                 0         -
utils/file.rs                                    224                 5    97.77%          14                 0   100.00%         144                 4    97.22%           0                 0         -
utils/formatter.rs                                55                 0   100.00%           5                 0   100.00%          39                 0   100.00%           0                 0         -
utils/logger.rs                                   71                71     0.00%           1                 1     0.00%          38                38     0.00%           0                 0         -
utils/tauri.rs                                   138                 0   100.00%          17                 0   100.00%          82                 0   100.00%           0                 0         -
workers/mod.rs                                    48                48     0.00%           2                 2     0.00%          28                28     0.00%           0                 0         -
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
TOTAL                                           6029              2524    58.14%         612               325    46.90%        4545              1956    56.96%           0                 0         -

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src-tauri/src/commands/hardware.rs`:
- Around line 189-195: The command get_storage_smart_latest_snapshots currently
calls hardviz_core::infrastructure::database directly; move that DB call into a
new service function (e.g.,
services::hardware_service::get_storage_smart_latest_snapshots) which simply
delegates to
hardviz_core::infrastructure::database::storage_smart::latest_snapshot_records()
and returns
Result<Vec<hardviz_core::models::hardware::StorageHealthSnapshotRecord>,
sqlx::Error>; then update the command get_storage_smart_latest_snapshots to call
services::hardware_service::get_storage_smart_latest_snapshots().await, map the
returned core records into the Tauri API type
(models::hardware::StorageSmartDashboardSnapshot via Into) and map errors into
the existing string error message, so the commands layer only handles boundary
formatting and the services layer owns DB access.

In `@src/features/hardware/dashboard/components/DashboardItems.tsx`:
- Around line 424-431: The error branch currently only console.errors when
isError(result) in the SMART fetch code; modify it to also show a user-facing
error via useTauriDialog().error(...) so failures are visible in the UI:
import/use the dialog hook where this code lives, call the dialog's error method
with a descriptive message and the error details (e.g., include result.error)
before or after calling setStorageHealthDevices([]), and keep the existing
console.error for debugging; update the block handling isError(result) around
the storage SMART fetch to call useTauriDialog().error(...) with the constructed
message.

In `@src/features/hardware/dashboard/utils/storageHealthSummary.ts`:
- Around line 185-191: daysBetweenDateKeys is DST-sensitive because it parses
local-midnight timestamps; switch to UTC-based parsing so midnight is canonical
and not offset by DST: replace the Date.parse(`${...}T00:00:00`) approach in
daysBetweenDateKeys with a UTC construction (e.g., parse the YYYY-MM-DD parts
and use Date.UTC(year, month-1, day) or ensure the string ends with Z) to get
UTC milliseconds for both current and snapshot, keep the same MS_PER_DAY divisor
(86_400_000) and Math.floor logic, and preserve the Number.isFinite check and
POSITIVE_INFINITY fallback.
🪄 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: d7ab6d03-5b25-4b37-a243-8ae91e6648d8

📥 Commits

Reviewing files that changed from the base of the PR and between bf65795 and 671460d.

📒 Files selected for processing (14)
  • core/src/infrastructure/database/storage_smart.rs
  • core/src/models/hardware.rs
  • src-tauri/src/commands/hardware.rs
  • src-tauri/src/lib.rs
  • src-tauri/src/models/hardware.rs
  • src/features/hardware/dashboard/components/DashboardItems.tsx
  • src/features/hardware/dashboard/components/StorageHealthStatusIcon.tsx
  • src/features/hardware/dashboard/hooks/useSortableDashboard.ts
  • src/features/hardware/dashboard/utils/storageHealthSummary.test.ts
  • src/features/hardware/dashboard/utils/storageHealthSummary.ts
  • src/lang/en.json
  • src/lang/ja.json
  • src/lang/ru.json
  • src/rspc/bindings.ts

Comment on lines +189 to +195
pub async fn get_storage_smart_latest_snapshots()
-> Result<Vec<models::hardware::StorageSmartDashboardSnapshot>, String> {
hardviz_core::infrastructure::database::storage_smart::latest_snapshot_records()
.await
.map(|records| records.into_iter().map(Into::into).collect())
.map_err(|e| format!("Failed to fetch latest storage SMART snapshots: {e}"))
}
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.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Route this command through services instead of calling Core DB directly.

Line 191 currently reaches into hardviz_core::infrastructure::database from the commands layer. Please move that call behind a service function and let the command only handle boundary formatting.

Proposed direction
 #[command]
 #[specta::specta]
 pub async fn get_storage_smart_latest_snapshots()
 -> Result<Vec<models::hardware::StorageSmartDashboardSnapshot>, String> {
-  hardviz_core::infrastructure::database::storage_smart::latest_snapshot_records()
+  crate::services::hardware_service::get_storage_smart_latest_snapshots()
     .await
     .map(|records| records.into_iter().map(Into::into).collect())
     .map_err(|e| format!("Failed to fetch latest storage SMART snapshots: {e}"))
 }
// src-tauri/src/services/hardware_service.rs
pub async fn get_storage_smart_latest_snapshots(
) -> Result<Vec<hardviz_core::models::hardware::StorageHealthSnapshotRecord>, sqlx::Error> {
  hardviz_core::infrastructure::database::storage_smart::latest_snapshot_records().await
}

As per coding guidelines, src-tauri/src/{commands,services,platform}/**/*.rs: "Add new backend commands in ... layered architecture", and src-tauri/src/commands/**/*.rs: "Commands layer should ... delegate business logic to the services layer".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src-tauri/src/commands/hardware.rs` around lines 189 - 195, The command
get_storage_smart_latest_snapshots currently calls
hardviz_core::infrastructure::database directly; move that DB call into a new
service function (e.g.,
services::hardware_service::get_storage_smart_latest_snapshots) which simply
delegates to
hardviz_core::infrastructure::database::storage_smart::latest_snapshot_records()
and returns
Result<Vec<hardviz_core::models::hardware::StorageHealthSnapshotRecord>,
sqlx::Error>; then update the command get_storage_smart_latest_snapshots to call
services::hardware_service::get_storage_smart_latest_snapshots().await, map the
returned core records into the Tauri API type
(models::hardware::StorageSmartDashboardSnapshot via Into) and map errors into
the existing string error message, so the commands layer only handles boundary
formatting and the services layer owns DB access.

Comment on lines +424 to +431
if (isError(result)) {
console.error(
"Failed to fetch storage SMART dashboard snapshots",
result.error,
);
setStorageHealthDevices([]);
return;
}
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Surface SMART fetch failures to users, not only console logs.

Line 425-428 logs the error but doesn’t notify users. The failure becomes silent in the dashboard UI.

💡 Suggested fix
+import { useTauriDialog } from "@/hooks/useTauriDialog";
...
 export const StorageDataInfo = () => {
   const { t } = useTranslation();
+  const { error: showError } = useTauriDialog();
...
       if (isError(result)) {
         console.error(
           "Failed to fetch storage SMART dashboard snapshots",
           result.error,
         );
+        showError(
+          t("error.title.error"),
+          "Failed to fetch storage health data.",
+        );
         setStorageHealthDevices([]);
         return;
       }

As per coding guidelines, "Check Result from backend commands with isError(...) helper and notify failures via useTauriDialog().error(...)".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (isError(result)) {
console.error(
"Failed to fetch storage SMART dashboard snapshots",
result.error,
);
setStorageHealthDevices([]);
return;
}
if (isError(result)) {
console.error(
"Failed to fetch storage SMART dashboard snapshots",
result.error,
);
showError(
t("error.title.error"),
"Failed to fetch storage health data.",
);
setStorageHealthDevices([]);
return;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/hardware/dashboard/components/DashboardItems.tsx` around lines
424 - 431, The error branch currently only console.errors when isError(result)
in the SMART fetch code; modify it to also show a user-facing error via
useTauriDialog().error(...) so failures are visible in the UI: import/use the
dialog hook where this code lives, call the dialog's error method with a
descriptive message and the error details (e.g., include result.error) before or
after calling setStorageHealthDevices([]), and keep the existing console.error
for debugging; update the block handling isError(result) around the storage
SMART fetch to call useTauriDialog().error(...) with the constructed message.

Comment on lines +185 to +191
const daysBetweenDateKeys = (currentDate: string, snapshotDate: string) => {
const current = Date.parse(`${currentDate}T00:00:00`);
const snapshot = Date.parse(`${snapshotDate}T00:00:00`);
if (!Number.isFinite(current) || !Number.isFinite(snapshot)) {
return Number.POSITIVE_INFINITY;
}
return Math.floor((current - snapshot) / 86_400_000);
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

# First, locate and examine the file
find . -type f -name "storageHealthSummary.ts" | head -20

Repository: shm11C3/HardwareVisualizer

Length of output: 134


🏁 Script executed:

# If file exists, show the context around lines 185-191
if [ -f "src/features/hardware/dashboard/utils/storageHealthSummary.ts" ]; then
  cat -n "src/features/hardware/dashboard/utils/storageHealthSummary.ts" | sed -n '180,200p'
fi

Repository: shm11C3/HardwareVisualizer

Length of output: 676


🏁 Script executed:

# Search for usages of daysBetweenDateKeys to understand the impact
rg "daysBetweenDateKeys" --context 5

Repository: shm11C3/HardwareVisualizer

Length of output: 2178


🏁 Script executed:

# Search for isStale usage to understand how the result is used
rg "isStale" --context 3

Repository: shm11C3/HardwareVisualizer

Length of output: 4922


🏁 Script executed:

# Find STORAGE_HEALTH_STALE_AFTER_DAYS constant
rg "STORAGE_HEALTH_STALE_AFTER_DAYS" -A 2 -B 2

Repository: shm11C3/HardwareVisualizer

Length of output: 937


🏁 Script executed:

# Check the test file for DST-related cases and understand the threshold
cat -n "src/features/hardware/dashboard/utils/storageHealthSummary.test.ts" | head -100

Repository: shm11C3/HardwareVisualizer

Length of output: 3752


🏁 Script executed:

# Verify dateKey format more thoroughly
rg "toLocalDateKey" -A 5 -B 2

Repository: shm11C3/HardwareVisualizer

Length of output: 1476


🏁 Script executed:

# Check for DST-related test cases in the test file
cat -n "src/features/hardware/dashboard/utils/storageHealthSummary.test.ts" | grep -i "dst\|stale\|day" -A 3 -B 1

Repository: shm11C3/HardwareVisualizer

Length of output: 2359


🏁 Script executed:

# Get the full test file to see all test cases
wc -l "src/features/hardware/dashboard/utils/storageHealthSummary.test.ts"

Repository: shm11C3/HardwareVisualizer

Length of output: 141


🏁 Script executed:

# View the stale-related test cases
cat -n "src/features/hardware/dashboard/utils/storageHealthSummary.test.ts" | sed -n '110,180p'

Repository: shm11C3/HardwareVisualizer

Length of output: 2435


🏁 Script executed:

# Let me create a test to demonstrate the DST issue
python3 << 'EOF'
# Simulate the current daysBetweenDateKeys behavior during DST transition
import time
from datetime import datetime, timezone

# Spring forward DST transition: March 9, 2024 at 2 AM EST -> 3 AM EDT
# Testing March 8 vs March 10

# Simulate what Date.parse would do with local timezone
# In browsers/JS, Date.parse with no timezone uses local time

# Let's calculate the millisecond difference
# March 8, 2024 00:00:00 EST (UTC-5) = 1709898000 seconds UTC
# March 10, 2024 00:00:00 EDT (UTC-4) = 1710070800 seconds UTC

march_8_utc = 1709898000
march_10_utc = 1710070800

diff_seconds = march_10_utc - march_8_utc
diff_ms = diff_seconds * 1000
hours_per_24 = 86_400_000

days_computed = diff_ms // hours_per_24

print(f"March 8 EST midnight (UTC): {march_8_utc}")
print(f"March 10 EDT midnight (UTC): {march_10_utc}")
print(f"Difference in seconds: {diff_seconds}")
print(f"Difference in hours: {diff_seconds / 3600}")
print(f"Difference in milliseconds: {diff_ms}")
print(f"Days computed (floor({diff_ms} / 86400000)): {days_computed}")
print(f"Calendar days difference: 2")
print(f"OFF BY: {2 - days_computed} day(s)")
EOF

Repository: shm11C3/HardwareVisualizer

Length of output: 335


🏁 Script executed:

# Also check for any existing DST test coverage
grep -r "DST\|daylight\|transition" "src/features/hardware/dashboard/utils/" || echo "No DST tests found"

Repository: shm11C3/HardwareVisualizer

Length of output: 89


🏁 Script executed:

# Verify the dateKey format by checking if it's used elsewhere with expected patterns
rg "date.*:.*\"[0-9]{4}-[0-9]{2}-[0-9]{2}\"" | head -20

Repository: shm11C3/HardwareVisualizer

Length of output: 268


🏁 Script executed:

# Check if there's any actual DST test or boundary case test
cat -n "src/features/hardware/dashboard/utils/storageHealthSummary.test.ts" | tail -50

Repository: shm11C3/HardwareVisualizer

Length of output: 1743


🏁 Script executed:

# Let me simulate a more accurate DST boundary case (adjacent calendar days across DST)
python3 << 'EOF'
# The key issue: March 9 EST -> March 10 EDT (same midnight times but different UTC offsets)
# Spring forward: At 2 AM EST, clocks jump to 3 AM EDT

# March 9, 2024 00:00:00 EST (UTC-5:00)
march_9_est_seconds = 1709898000  # March 9 05:00 UTC

# March 10, 2024 00:00:00 EDT (UTC-4:00) - one hour shifted
march_10_edt_seconds = 1710010800  # March 10 04:00 UTC

diff_seconds = march_10_edt_seconds - march_9_est_seconds
diff_ms = diff_seconds * 1000
hours_per_24 = 86_400_000

days_computed = diff_ms // hours_per_24

print("=== DST Boundary Case: March 9 EST to March 10 EDT ===")
print(f"March 9, 2024 00:00:00 EST (local) = {march_9_est_seconds} seconds UTC")
print(f"March 10, 2024 00:00:00 EDT (local) = {march_10_edt_seconds} seconds UTC")
print(f"Actual difference: {diff_seconds} seconds = {diff_seconds/3600} hours")
print(f"Milliseconds: {diff_ms}")
print(f"Days computed via division: floor({diff_ms} / {hours_per_24}) = {days_computed}")
print(f"Calendar days differ by: 1")
print(f"RESULT: Off by {1 - days_computed} day(s) - Returns 0 instead of 1!")

# Another case: March 10 EDT to March 12 EDT (3 calendar days spanning forward transition)
march_12_edt_seconds = 1710184800  # March 12 04:00 UTC

diff2_seconds = march_12_edt_seconds - march_9_est_seconds
diff2_ms = diff2_seconds * 1000
days2_computed = diff2_ms // hours_per_24

print("\n=== Spanning DST: March 9 EST to March 12 EDT ===")
print(f"Actual difference: {diff2_seconds} seconds = {diff2_seconds/3600} hours")
print(f"Days computed: {days2_computed}")
print(f"Calendar days differ by: 3")
print(f"RESULT: Off by {3 - days2_computed} day(s)")
EOF

Repository: shm11C3/HardwareVisualizer

Length of output: 647


🏁 Script executed:

# Verify what Date.UTC would return for comparison
python3 << 'EOF'
from datetime import datetime, timezone

# Date.UTC always interprets as UTC midnight
march_9_utc = datetime(2024, 3, 9, 0, 0, 0, tzinfo=timezone.utc).timestamp()
march_10_utc = datetime(2024, 3, 10, 0, 0, 0, tzinfo=timezone.utc).timestamp()
march_12_utc = datetime(2024, 3, 12, 0, 0, 0, tzinfo=timezone.utc).timestamp()

diff1 = (march_10_utc - march_9_utc) / 86400
diff2 = (march_12_utc - march_9_utc) / 86400

print("=== Using UTC dates (proposed fix) ===")
print(f"March 9 UTC midnight to March 10 UTC midnight: {diff1} days (floor = {int(diff1)})")
print(f"March 9 UTC midnight to March 12 UTC midnight: {diff2} days (floor = {int(diff2)})")
print("Always correct regardless of DST!")
EOF

Repository: shm11C3/HardwareVisualizer

Length of output: 279


🏁 Script executed:

# Test the actual direction: newer date minus older date across DST
python3 << 'EOF'
# The function is called as: daysBetweenDateKeys(toLocalDateKey(now), latestDate)
# So: (current - snapshot) where current is "now" and snapshot is "latestDate"

# Case 1: One calendar day across spring forward DST (Mar 9 EST -> Mar 10 EDT)
# now = March 10, latestDate = March 9
march_10_edt_utc = 1710010800  # March 10 00:00 EDT = March 10 04:00 UTC
march_9_est_utc = 1709898000   # March 9 00:00 EST = March 9 05:00 UTC

diff1 = march_10_edt_utc - march_9_est_utc
days1 = diff1 // 86400
remainder1 = diff1 % 86400

print("=== 1 Calendar Day Across Spring Forward (current=Mar10 EDT, snapshot=Mar9 EST) ===")
print(f"March 10 00:00 EDT (local) = {march_10_edt_utc} UTC seconds = March 10 04:00 UTC")
print(f"March 9 00:00 EST (local) = {march_9_est_utc} UTC seconds = March 9 05:00 UTC")
print(f"Difference: {diff1} seconds = {diff1/3600} hours")
print(f"Days computed: floor({diff1} / 86400) = {days1}")
print(f"Calendar day difference: 1")
print(f"BUG: Returns {days1} instead of 1! ❌\n")

# Case 2: Three calendar days spanning DST
march_12_edt_utc = 1710184800  # March 12 00:00 EDT = March 12 04:00 UTC

diff2 = march_12_edt_utc - march_9_est_utc
days2 = diff2 // 86400

print("=== 3 Calendar Days Spanning Spring Forward (current=Mar12 EDT, snapshot=Mar9 EST) ===")
print(f"Difference: {diff2} seconds = {diff2/3600} hours")
print(f"Days computed: floor({diff2} / 86400) = {days2}")
print(f"Calendar day difference: 3")
print(f"BUG: Returns {days2} instead of 3! ❌")
EOF

Repository: shm11C3/HardwareVisualizer

Length of output: 691


🏁 Script executed:

# Let me recalculate more precisely using actual timezone-aware datetime
python3 << 'EOF'
from datetime import datetime, timezone, timedelta, tzinfo

# Define EST and EDT timezones
class EST(tzinfo):
    def utcoffset(self, dt):
        return timedelta(hours=-5)
    def tzname(self, dt):
        return "EST"
    def dst(self, dt):
        return timedelta(0)

class EDT(tzinfo):
    def utcoffset(self, dt):
        return timedelta(hours=-4)
    def tzname(self, dt):
        return "EDT"
    def dst(self, dt):
        return timedelta(0)

# Create dates with appropriate timezones for DST transition
march_9_est = datetime(2024, 3, 9, 0, 0, 0, tzinfo=EST())
march_10_edt = datetime(2024, 3, 10, 0, 0, 0, tzinfo=EDT())

# Convert to UTC to see the actual time difference
march_9_utc = march_9_est.astimezone(timezone.utc)
march_10_utc = march_10_edt.astimezone(timezone.utc)

print(f"March 9 00:00 EST = {march_9_utc} UTC")
print(f"March 10 00:00 EDT = {march_10_utc} UTC")

diff_seconds = (march_10_utc - march_9_utc).total_seconds()
diff_hours = diff_seconds / 3600
days_floored = int(diff_seconds // 86400)

print(f"\nTime difference: {diff_seconds} seconds = {diff_hours} hours")
print(f"Days (floored): {days_floored}")
print(f"Calendar days apart: 1")
print(f"\nBUG CONFIRMED: Returns {days_floored} days instead of 1 day!")
print("\nThis means data 1 calendar day old reports as 0 days old.")
print("With STORAGE_HEALTH_STALE_AFTER_DAYS = 2, it won't be marked stale until 3+ days pass.")
EOF

Repository: shm11C3/HardwareVisualizer

Length of output: 454


Fix DST-sensitive date comparison that causes staleness misclassification.

The daysBetweenDateKeys function uses Date.parse() with local timezone, which introduces a 1-hour offset at DST transitions. During spring forward (e.g., March 9 EST to March 10 EDT), the midnight times are only 23 hours apart; dividing by 86,400ms (24 hours) returns 0 days instead of 1. This causes data 1 calendar day old to incorrectly report as 0 days old. With STORAGE_HEALTH_STALE_AFTER_DAYS = 2, storage data up to 3 calendar days old may not be marked stale.

Use UTC date parsing to eliminate timezone-dependent calculations:

Suggested fix
+const parseDateKeyUtc = (dateKey: string) => {
+  const [year, month, day] = dateKey.split("-").map(Number);
+  if (!year || !month || !day) return Number.NaN;
+  return Date.UTC(year, month - 1, day);
+};
+
 const daysBetweenDateKeys = (currentDate: string, snapshotDate: string) => {
-  const current = Date.parse(`${currentDate}T00:00:00`);
-  const snapshot = Date.parse(`${snapshotDate}T00:00:00`);
+  const current = parseDateKeyUtc(currentDate);
+  const snapshot = parseDateKeyUtc(snapshotDate);
   if (!Number.isFinite(current) || !Number.isFinite(snapshot)) {
     return Number.POSITIVE_INFINITY;
   }
   return Math.floor((current - snapshot) / 86_400_000);
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/hardware/dashboard/utils/storageHealthSummary.ts` around lines
185 - 191, daysBetweenDateKeys is DST-sensitive because it parses local-midnight
timestamps; switch to UTC-based parsing so midnight is canonical and not offset
by DST: replace the Date.parse(`${...}T00:00:00`) approach in
daysBetweenDateKeys with a UTC construction (e.g., parse the YYYY-MM-DD parts
and use Date.UTC(year, month-1, day) or ensure the string ends with Z) to get
UTC milliseconds for both current and snapshot, keep the same MS_PER_DAY divisor
(86_400_000) and Math.floor logic, and preserve the Number.isFinite check and
POSITIVE_INFINITY fallback.

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.

2 participants