Skip to content

Daisy UI Migration#355

Merged
jniebuhr merged 6 commits intomasterfrom
feature/daisy-ui
Aug 13, 2025
Merged

Daisy UI Migration#355
jniebuhr merged 6 commits intomasterfrom
feature/daisy-ui

Conversation

@jniebuhr
Copy link
Owner

@jniebuhr jniebuhr commented Aug 13, 2025

By @ManfredRichthofen

Summary by CodeRabbit

  • New Features

    • Theme selection with persistence; configurable dashboard layout; Plugins settings (HomeKit, Boiler Refill, Smart Grind, Home Assistant).
  • UI/UX

    • App-wide DaisyUI/card-based styling; updated Header/Footer/404/Spinner; revamped Home, Profiles, Settings, Shot History, charts, and many components for responsive, accessible layouts.
  • Bug Fixes

    • Corrected decimal parsing in shot history.
  • Chores

    • Added DaisyUI and Tailwind-aware Prettier plugin; removed legacy dashboard page; updated formatting and package dependencies.

ManfredRichthofen and others added 5 commits July 31, 2025 22:01
* Initial Daisy Ui transition and 4 themes to start

* Update Ui

* Fix SonarCloud issues

* Remove Computed Single line Chart Register

* Use JSX syntax

* Apply Bunny Fixes

* Use card component

* Use Card

* Fix active and hover states

* Format and Style Nav Bar Better

* Move profiles over(need to fix spacing between lables)

* Fix Mobile Layout

* Fix mobile layout

* Listen to the bunny

* Update style.css

* Convert Scale and Autotune

* Delete dashboard.html

* I like the titles on the left better
* Migrate Settings to DaisyUi and removed unused error handling

* Fix eslint error var to const

* Fix eslint

* Remove Unused container-fluid and create context. Run Format. Migrate Process Info to daisy

* Change to aria label

* Use Daisy UI tool tips

* Remove Unused Styles

* Use Semantic Colors

* Add Theme Manager to settings

Right now this is a separate card but can be changed whenever

* Standardize spacing and Padding as well as component usage

* Fix Profile pages and use consistent styling

* Improve Dark style ?

* add aria

* Fix Save

* Consistent Styles. Use 10/5 Grid Layout

* Fix Button Spacing

* Prevent race condition

* Fix button Position and use 5/10 layout on home page

* fix card class again

* javascript:void(0)

* simple bp

* add theme urls

* add safelist

* fix profile cards

* FMT and When saving profile rout back to profile list

* Clarify Settings Card

* Fix Chart Height

* This needs a bit of testing but should fix card duplication

* Add Switching the layout of the home page

* Format and Fix Lint errors

* Fix some code reviews

* Update index.jsx

* Split Settings Page up a bit

* Clean up Inputs

* Fix Radix

---------

Co-authored-by: Jochen Niebuhr <kontakt@ju-hh.de>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 13, 2025

Walkthrough

Replaces controller->deactivate() with controller->clear() on WebSocket req:change-mode; introduces DaisyUI and theme/dashboard managers; large UI refactors to Card-based layouts across many web pages; removes legacy dashboard.html; initializes theme before render and persists dashboard layout.

Changes

Cohort / File(s) Summary
Backend mode-change handling
src/display/plugins/WebUIPlugin.cpp
WebSocket req:change-mode handler now calls controller->clear() instead of controller->deactivate() before setMode(mode).
Formatting & deps
web/.prettierrc, web/package.json
Prettier config updated (printWidth 100, trailingComma all, jsxSingleQuote, Tailwind plugin); added daisyui and prettier-plugin-tailwindcss; bumped Prettier version.
Global styling & themes
web/src/style.css, web/index.html
Added DaisyUI theme definitions (light, dark, coffee, nord), removed legacy utility classes; changed body background class to bg-base-100.
Theme & dashboard utilities
web/src/utils/themeManager.js, web/src/utils/dashboardManager.js, web/src/utils/uuid.js
New theme manager (get/set/apply/init/handle), new dashboard layout persistence (get/set, enum), minor formatting tweaks in UUID util.
App bootstrap
web/src/index.jsx
Calls initializeTheme() before render; reorganized top-level layout and className usage; routes preserved.
Core components
web/src/components/Card.jsx, web/src/components/Header.jsx, web/src/components/Footer.jsx, web/src/components/Navigation.jsx, web/src/components/Spinner.jsx, web/src/components/OverviewChart.jsx, web/src/components/ProcessInfo.jsx
Card API refactor and grid helpers; Header uses location for active state; Footer/Navigation markup/styling updates; Spinner moves to CSS-based spinner; OverviewChart lifecycle/responsive improvements; ProcessInfo uses native progress element.
Home / Process controls
web/src/pages/Home/index.jsx, web/src/pages/Home/ProcessControls.jsx, web/src/components/OverviewChart.jsx
Home reworked to dashboard-layout driven grid with Card wrappers; ProcessControls expanded with mode tabs, richer controls, and PropTypes; chart registration consolidated.
Settings & plugins
web/src/pages/Settings/index.jsx, web/src/pages/Settings/PluginCard.jsx
Settings refactored into multiple Cards, theme/dashboard controls added; new PluginCard component with toggles and plugin configs.
Profiles (list & edit)
web/src/pages/ProfileList/index.jsx, web/src/pages/ProfileList/*, web/src/pages/ProfileEdit/*
ProfileList migrated to Card-based layout, accessibility improvements, import/export modernized; ProfileEdit split into Card selections and richer StandardProfileForm with phase model changes and redirect to /profiles after save.
Scales
web/src/pages/Scales/index.jsx
Card-wrapped UI, scan/connect flows, periodic refresh every 10s, isScanning state, enhanced empty/loading/error states.
OTA & Autotune
web/src/pages/OTA/index.jsx, web/src/pages/Autotune/index.jsx
OTA and Autotune moved to Card-based layouts; event listener cleanup added; form/button actions refactored; improved idle/active/result UIs.
Shot History
web/src/pages/ShotHistory/*
Layout and action controls modernized (buttons/tooltips); parseInt calls now specify radix 10; minor chart and formatting tweaks.
Not Found / Removed file
web/src/pages/_404.jsx, web/dashboard.html
NotFound simplified to centered layout with primary button; deleted legacy web/dashboard.html.
Minor / formatting-only
web/src/services/ApiService.js, web/src/pages/ProfileList/ExtendedContent.jsx, various small files
Mostly stylistic/arrow-syntax changes, Chart.js registration consolidated, small accessibility/className fixes.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Web UI
  participant Server as WebUIPlugin
  participant Controller

  Client->>Server: WS tp: "req:change-mode", mode
  Server->>Controller: clear()
  Server->>Controller: setMode(mode)
  Controller-->>Server: ack
  Server-->>Client: res:change-mode (ok)
Loading
sequenceDiagram
  participant Browser
  participant ThemeMgr as ThemeManager
  participant DOM

  Browser->>ThemeMgr: initializeTheme()
  ThemeMgr->>ThemeMgr: getStoredTheme()
  ThemeMgr->>DOM: document.documentElement[data-theme]=theme
Loading
sequenceDiagram
  participant Page as Home Page
  participant DashMgr as DashboardManager
  participant Storage as localStorage

  Page->>DashMgr: getDashboardLayout()
  DashMgr->>Storage: read "dashboardLayout"
  Storage-->>DashMgr: value
  DashMgr-->>Page: layout or default
  Page->>DashMgr: setDashboardLayout(layout)
  DashMgr->>Storage: write "dashboardLayout"
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45–60 minutes

Possibly related PRs

  • feat: Add headless version #335: Modifies the same src/display/plugins/WebUIPlugin.cpp path and the "req:change-mode" controller state handling, closely related to this backend change.

Poem

I thump my paws on a tidy log,
New themes and cards spring from the fog.
Modes now clear with a single cheer,
Charts stretch wide and dashboards steer.
I nibble code and hop away—daisy blooms today! 🐇🌼

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/daisy-ui

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
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: 24

🔭 Outside diff range comments (11)
src/display/plugins/WebUIPlugin.cpp (1)

159-161: Fix format string in ESP_LOGI (invalid placeholder).

The format string uses "%" without a specifier; this is undefined/incorrect and may garble logs.

Apply:

-                        ESP_LOGI("WebUIPlugin", "Received request: %", (char *)data);
+                        ESP_LOGI("WebUIPlugin", "Received request: %s", (char *)data);
web/src/pages/ShotHistory/HistoryChart.jsx (1)

114-117: Destroy Chart instance on unmount to avoid leaks.

The Chart is created but never destroyed. Add a cleanup to prevent memory leaks and canvas context warnings when navigating.

Example:

useEffect(() => {
  const ct = new Chart(ref.current, chartData);
  setChart(ct);
  return () => {
    try { ct.destroy(); } catch {}
  };
}, [ref]);
web/src/services/ApiService.js (2)

122-141: Clear timeout on resolve to avoid dangling timers

If the response arrives before the 30s timeout, the timer still fires later. Clear it upon resolve to prevent unnecessary work and potential confusion in debugging.

-    return new Promise((resolve, reject) => {
+    return new Promise((resolve, reject) => {
       // Create a listener for the response with matching rid
-      const listenerId = this.on(returnType, response => {
+      let timeoutId;
+      const listenerId = this.on(returnType, response => {
         if (response.rid === rid) {
           // Clean up the listener
           this.off(returnType, listenerId);
+          if (timeoutId) clearTimeout(timeoutId);
           resolve(response);
         }
       });
 
       // Send the request
       this.send(message);
 
       // Optional: Add timeout
-      setTimeout(() => {
+      timeoutId = setTimeout(() => {
         this.off(returnType, listenerId);
         reject(new Error(`Request ${data.tp} timed out`));
       }, 30000); // 30 second timeout
     });

119-121: Validate request type and avoid brittle substring usage

Guard against invalid tp values and prefer slice for clarity.

-    const returnType = `res:${data.tp.substring(4)}`;
+    if (!data?.tp?.startsWith('req:')) {
+      throw new Error(`request(): expected data.tp to start with "req:". Got "${data?.tp}"`);
+    }
+    const returnType = `res:${data.tp.slice(4)}`;
web/src/pages/ProfileList/ExtendedContent.jsx (1)

104-107: Destroy Chart instance on unmount to prevent memory leaks

Chart.js instances should be cleaned up; otherwise re-mounts or route changes can leak canvases and handlers.

   useEffect(() => {
     const ct = new Chart(ref.current, config);
+    return () => ct.destroy();
   }, [ref]);
web/src/pages/ProfileEdit/StandardProfileForm.jsx (2)

173-190: Potential data loss when resetting volumetric target

When onVolumetricTargetChange receives a value of 0, it sets targets to null, which removes all targets including non-volumetric ones that might exist. This could lead to unintended data loss.

  const onVolumetricTargetChange = value => {
    if (value === 0) {
+     // Remove only the volumetric target, preserve other targets
+     const nonVolumetricTargets = (phase.targets || []).filter(t => t.type !== 'volumetric');
      onChange({
        ...phase,
-       targets: null,
+       targets: nonVolumetricTargets.length > 0 ? nonVolumetricTargets : null,
      });
      return;
    }
+   // Add or update volumetric target while preserving other targets
+   const nonVolumetricTargets = (phase.targets || []).filter(t => t.type !== 'volumetric');
    onChange({
      ...phase,
-     targets: [
+     targets: [
+       ...nonVolumetricTargets,
        {
          type: 'volumetric',
          value: value,
        },
      ],
    });
  };

196-199: Add defensive checks for non-numeric pump values

The code assumes phase.pump is either a number or an object with pressure and flow properties, but doesn't handle potential undefined or malformed values safely.

  const pumpPower = isNumber(phase.pump) ? phase.pump : 100;
-  const pressure = !isNumber(phase.pump) ? phase.pump.pressure : 0;
-  const flow = !isNumber(phase.pump) ? phase.pump.flow : 0;
+  const pressure = !isNumber(phase.pump) && phase.pump ? (phase.pump.pressure || 0) : 0;
+  const flow = !isNumber(phase.pump) && phase.pump ? (phase.pump.flow || 0) : 0;
  const mode = isNumber(phase.pump) ? (phase.pump === 0 ? 'off' : 'power') : phase.pump.target;
web/src/pages/ProfileList/index.jsx (1)

308-321: Add error handling for file upload

The file upload handler lacks error handling for JSON parsing and API requests, which could leave the UI in an inconsistent state.

   const onUpload = function (evt) {
     if (evt.target.files.length) {
       const file = evt.target.files[0];
       const reader = new FileReader();
       reader.onload = async e => {
-        const result = e.target.result;
-        if (typeof result === 'string') {
-          let profiles = JSON.parse(result);
-          if (!Array.isArray(profiles)) {
-            profiles = [profiles];
-          }
-          for (const p of profiles) {
-            await apiService.request({ tp: 'req:profiles:save', profile: p });
-          }
-          await loadProfiles();
-        }
+        try {
+          const result = e.target.result;
+          if (typeof result === 'string') {
+            let profiles = JSON.parse(result);
+            if (!Array.isArray(profiles)) {
+              profiles = [profiles];
+            }
+            setLoading(true);
+            for (const p of profiles) {
+              await apiService.request({ tp: 'req:profiles:save', profile: p });
+            }
+            await loadProfiles();
+          }
+        } catch (error) {
+          console.error('Failed to import profiles:', error);
+          setLoading(false);
+          // Consider adding user-facing error notification here
+        }
       };
+      reader.onerror = () => {
+        console.error('Failed to read file');
+        // Consider adding user-facing error notification here
+      };
       reader.readAsText(file);
     }
   };
web/src/pages/Settings/index.jsx (3)

191-505: Critical: Remove duplicate form sections causing JSX parsing errors

There are two complete duplicate sections of the settings form (lines 191-505 and 506-1093), which is causing the pipeline build failures. The second section starting at line 506 contains the actual form element and should be kept, while the first section should be removed.

Remove the entire duplicate section from lines 191-505. This duplicate content is causing JSX parsing errors as reported in the pipeline failures.

-      </div>
-      <Card xs={12} lg={6} title="Temperature settings">
-        <div>
-          <label htmlFor="targetSteamTemp" className="block font-medium text-gray-700 dark:text-gray-400">
-            Default Steam Temperature (°C)
-          </label>
-          ... [lines 192-505 should be removed]
-        </div>
-
       <form key='settings' ref={formRef} method='post' action='/api/settings' onSubmit={onSubmit}>

Also applies to: 506-1093


50-98: Simplify onChange handler with a more maintainable approach

The onChange function has repetitive boolean toggle logic that could be simplified using a Set or array of toggle fields.

  const onChange = key => {
    return e => {
      let value = e.currentTarget.value;
-     if (key === 'homekit') {
-       value = !formData.homekit;
-     }
-     if (key === 'boilerFillActive') {
-       value = !formData.boilerFillActive;
-     }
-     if (key === 'smartGrindActive') {
-       value = !formData.smartGrindActive;
-     }
-     if (key === 'smartGrindToggle') {
-       value = !formData.smartGrindToggle;
-     }
-     if (key === 'homeAssistant') {
-       value = !formData.homeAssistant;
-     }
-     if (key === 'momentaryButtons') {
-       value = !formData.momentaryButtons;
-     }
-     if (key === 'delayAdjust') {
-       value = !formData.delayAdjust;
-     }
-     if (key === 'clock24hFormat') {
-       value = !formData.clock24hFormat;
-     }
+     const toggleFields = [
+       'homekit', 'boilerFillActive', 'smartGrindActive', 'smartGrindToggle',
+       'homeAssistant', 'momentaryButtons', 'delayAdjust', 'clock24hFormat'
+     ];
+     
+     if (toggleFields.includes(key)) {
+       value = !formData[key];
+     }
+     
      if (key === 'standbyDisplayEnabled') {
        value = !formData.standbyDisplayEnabled;
        // Set standby brightness to 0 when toggle is off
        const newFormData = {
          ...formData,
          [key]: value,
        };
        if (!value) {
          newFormData.standbyBrightness = 0;
        }
        setFormData(newFormData);
        return;
      }
      if (key === 'dashboardLayout') {
        setDashboardLayout(value);
      }
      setFormData({
        ...formData,
        [key]: value,
      });
    };
  };

144-154: Add error handling for file upload

Similar to ProfileList, the settings upload handler lacks error handling for JSON parsing.

   const onUpload = function (evt) {
     if (evt.target.files.length) {
       const file = evt.target.files[0];
       const reader = new FileReader();
       reader.onload = async e => {
-        const data = JSON.parse(e.target.result);
-        setFormData(data);
+        try {
+          const data = JSON.parse(e.target.result);
+          setFormData(data);
+        } catch (error) {
+          console.error('Failed to parse settings file:', error);
+          // Consider adding user-facing error notification
+        }
       };
+      reader.onerror = () => {
+        console.error('Failed to read settings file');
+        // Consider adding user-facing error notification
+      };
       reader.readAsText(file);
     }
   };
🧹 Nitpick comments (32)
web/src/components/Navigation.jsx (4)

5-5: Prefer DaisyUI "btn-ghost" for base state; drop manual hover/text classes

This simplifies styling, aligns with DaisyUI conventions, and avoids hard-coding theme tokens that DaisyUI already manages.

Apply this diff:

-    'btn btn-md justify-start gap-3 w-full text-base-content hover:text-base-content hover:bg-base-content/10 bg-transparent border-none px-2';
+    'btn btn-ghost btn-md justify-start gap-3 w-full px-2';

21-21: Nit: Add an accessible label to nav

Providing an aria-label makes the nav landmark discoverable to assistive tech.

Example:

-    <nav className='hidden lg:col-span-2 lg:block'>
+    <nav className='hidden lg:col-span-2 lg:block' aria-label='Primary'>

23-23: Use DaisyUI "divider" instead of


for consistent theming

This aligns with the DaisyUI migration and removes reliance on height hacks.

Apply this diff:

-      <hr className='h-5 border-0' />
+      <div className='divider my-1'></div>

(Repeat for each separator)

Also applies to: 28-28, 34-34


22-37: Reduce duplication by mapping an items config to MenuItem

This makes the menu easier to maintain and reorder, and centralizes icon/link definitions.

Example refactor (outside selected lines):

const sections = [
  {
    items: [
      { label: 'Dashboard', link: '/', icon: 'fa-solid fa-house' },
    ],
  },
  {
    items: [
      { label: 'Profiles', link: '/profiles', icon: 'fa-solid fa-list' },
      { label: 'Shot History', link: '/history', icon: 'fa-solid fa-clock-rotate-left' },
    ],
  },
  {
    items: [
      { label: 'PID Autotune', link: '/pidtune', icon: 'fa-solid fa-temperature-half' },
      { label: 'Bluetooth Scales', link: '/scales', icon: 'fa-brands fa-bluetooth-b' },
      { label: 'Settings', link: '/settings', icon: 'fa-solid fa-gear' },
    ],
  },
  {
    items: [
      { label: 'System & Updates', link: '/ota', icon: 'fa-solid fa-arrows-rotate' },
    ],
  },
];

export function Navigation() {
  return (
    <nav className="hidden lg:col-span-2 lg:block" aria-label="Primary">
      {sections.map((section, idx) => (
        <div key={idx} className="space-y-1.5">
          {section.items.map(item => (
            <MenuItem
              key={item.link}
              label={item.label}
              link={item.link}
              iconClass={item.icon}
            />
          ))}
          <div className="divider my-1"></div>
        </div>
      ))}
    </nav>
  );
}
web/src/components/Spinner.jsx (1)

10-14: Expose className/label for theming and i18n (optional)

Allow callers to pass text color (e.g., text-primary) and localized labels without changing internals.

Here’s a minimal enhancement (outside the selected range) for consideration:

export function Spinner({ size = 8, label = 'Loading', className = '', ...rest }) {
  const sizeMap = { 4: 'loading-xs', 8: 'loading-md', 12: 'loading-lg', 16: 'loading-lg' };
  const sizeClass = sizeMap[size] || 'loading-md';
  return (
    <span
      className={`loading loading-spinner ${sizeClass} ${className}`}
      role="status"
      aria-label={label}
      style={!sizeMap[size] && Number.isFinite(size) ? { width: size, height: size } : undefined}
      {...rest}
    />
  );
}

If you’d like, I can update call sites to pass a localized label and optional className.

web/package.json (1)

29-31: Ensure Prettier loads the Tailwind plugin in all environments.

prettier-plugin-tailwindcss works with Prettier 3.x, but discovery can fail in certain setups (monorepos, non-standard config resolution). If not already in your .prettierrc, explicitly declare the plugin to avoid class-ordering silently not applying.

Optionally add to .prettierrc:

{
  "plugins": ["prettier-plugin-tailwindcss"]
}
web/index.html (1)

26-26: Prevent theme flash on first paint by seeding data-theme before app boot.

bg-base-100 relies on DaisyUI theme tokens. Until initializeTheme runs, users may briefly see the wrong theme. Seed data-theme early in so CSS variables are correct on first paint.

Add a tiny inline script in :

<script>
  try {
    const stored = localStorage.getItem('theme');
    const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
    document.documentElement.setAttribute('data-theme', stored || (prefersDark ? 'dark' : 'light'));
  } catch (e) {
    // no-op
  }
</script>
web/src/services/ApiService.js (2)

82-85: Add jitter to exponential backoff to prevent synchronized reconnect storms

Formatting looks good. Add jitter so multiple clients don’t reconnect simultaneously after outages.

-    const delay = Math.min(
-      this.baseReconnectDelay * Math.pow(2, this.reconnectAttempts),
-      this.maxReconnectDelay,
-    );
+    const base = this.baseReconnectDelay * (2 ** this.reconnectAttempts);
+    const jitter = Math.random() * base * 0.3; // 30% jitter
+    const delay = Math.min(base + jitter, this.maxReconnectDelay);

5-10: Use slice instead of deprecated substr in randomId

substr is legacy and discouraged. slice is the modern, well-supported alternative.

   return Math.random()
     .toString(36)
     .replace(/[^a-z]+/g, '')
-    .substr(2, 10);
+    .slice(2, 12);
web/src/pages/ProfileList/ExtendedContent.jsx (1)

63-63: Remove unused variable

The local variable ‘labels’ is unused.

-  const labels = [0];
web/src/pages/ProfileList/ProfileAddCard.jsx (1)

6-13: Make the whole card body clickable for better UX

Expand the anchor to fill the card body so the entire card area is a hit target.

       <a
         href='/profiles/new'
-        className='text-base-content hover:text-primary flex cursor-pointer flex-col items-center justify-center gap-2 p-4 transition-colors'
+        className='text-base-content hover:text-primary flex cursor-pointer flex-col items-center justify-center gap-2 p-4 transition-colors block h-full w-full'
       >
web/src/components/ProcessInfo.jsx (1)

35-36: Optional: add accessible labelling to progress

Screen readers generally handle , but adding an aria-label and valuetext can improve UX.

-      <progress className='progress progress-primary w-full' value={progress} max='100' />
+      <progress
+        className='progress progress-primary w-full'
+        value={progress}
+        max='100'
+        aria-label={`Process progress ${progress}%`}
+      />
web/src/utils/uuid.js (1)

2-4: Prefer native crypto.randomUUID with a safe fallback

Modern browsers expose crypto.randomUUID(), which is simpler and more robust. Fallback to your current implementation (and finally to Math.random) when unavailable.

Apply this diff:

 export default function uuidv4() {
-  return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, c =>
-    (+c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (+c / 4)))).toString(16),
-  );
+  const cryptoObj = globalThis.crypto || (typeof window !== 'undefined' && window.crypto);
+  if (cryptoObj?.randomUUID) return cryptoObj.randomUUID();
+  if (cryptoObj?.getRandomValues) {
+    // RFC4122 v4-like fallback
+    return '10000000-1000-4000-8000-100000000000'.replace(
+      /[018]/g,
+      c => (+c ^ (cryptoObj.getRandomValues(new Uint8Array(1))[0] & (15 >> (+c / 4)))).toString(16),
+    );
+  }
+  // Least-preferred non-crypto fallback
+  return ([1e7]+-1e3+-4e3+-8e3+-1e11)
+    .replace(/[018]/g, c => (c ^ (Math.random() * 16 >> (c / 4))).toString(16));
 }
web/src/pages/_404.jsx (1)

3-8: Use semantic

and SPA routing to improve accessibility and UX

  • Wrap the content in a semantic main landmark.
  • If you use a client-side router elsewhere, prefer its Link component to avoid a full page reload.

Apply this diff for semantics; and consider swapping with your router’s Link if applicable:

-    <div className='flex min-h-screen flex-col items-center justify-center'>
+    <main className='flex min-h-screen flex-col items-center justify-center' role='main' aria-labelledby='page-title'>
-      <h1 className='text-base-content mb-4 text-6xl font-bold'>404</h1>
+      <h1 id='page-title' className='text-base-content mb-4 text-6xl font-bold'>404</h1>
       <p className='text-base-content/70 mb-8 text-xl'>Page not found</p>
-      <a href='/' className='btn btn-primary'>
+      <a href='/' className='btn btn-primary'>
         Go Home
       </a>
-    </div>
+    </main>

If you’re using a router (e.g., react-router, preact-router), confirm and I can provide a precise Link replacement.

web/src/style.css (1)

7-11: Silence Biome “unknown property” for DaisyUI theme metadata keys

Properties like name, default, and prefersdark inside @plugin "daisyui/theme" blocks are DaisyUI-specific, not standard CSS, hence Biome flags them. Either ignore the rule for this file or just for these blocks.

Apply this diff to ignore the rule file-wide (simplest and future-proof), keeping other linters unaffected:

+/* biome-ignore-file lint/correctness/noUnknownProperty: DaisyUI theme plugin uses non-standard keys (name, default, prefersdark, color-scheme) */
 @import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&display=swap');
 @import 'tailwindcss';
 @plugin 'daisyui';

Alternatively, add targeted ignores before each block:

-@plugin "daisyui/theme" {
+/* biome-ignore lint/correctness/noUnknownProperty: DaisyUI theme metadata */
+@plugin "daisyui/theme" {
   name: 'light';
   default: true;
   prefersdark: false;
   color-scheme: 'light';

Repeat for the dark, coffee, and nord blocks.

Also applies to: 43-47, 79-83, 115-119

web/src/components/Card.jsx (1)

13-26: Minor: extract getGridClasses outside component or memoize for stability

Not critical, but if Card re-renders frequently, consider memoizing grid class computation or extracting a pure helper to avoid recreating the function each render.

Option:

-export default function Card({
+function getGridClassesFrom({ xs, sm, md, lg, xl }) {
+  const breakpoints = [
+    { value: xs, prefix: '' },
+    { value: sm, prefix: 'sm:' },
+    { value: md, prefix: 'md:' },
+    { value: lg, prefix: 'lg:' },
+    { value: xl, prefix: 'xl:' },
+  ];
+  return breakpoints
+    .filter(bp => bp.value && bp.value >= 1 && bp.value <= 12)
+    .map(bp => `${bp.prefix}col-span-${bp.value}`)
+    .join(' ');
+}
+
+export default function Card({
   xs,
   sm,
   md,
   lg,
   xl,
@@
-}) {
-  const getGridClasses = () => {
-    const breakpoints = [
-      { value: xs, prefix: '' },
-      { value: sm, prefix: 'sm:' },
-      { value: md, prefix: 'md:' },
-      { value: lg, prefix: 'lg:' },
-      { value: xl, prefix: 'xl:' },
-    ];
-
-    return breakpoints
-      .filter(bp => bp.value && bp.value >= 1 && bp.value <= 12)
-      .map(bp => `${bp.prefix}col-span-${bp.value}`)
-      .join(' ');
-  };
+}) {
@@
-  const gridClasses = getGridClasses();
+  const gridClasses = getGridClassesFrom({ xs, sm, md, lg, xl });
web/src/pages/Settings/PluginCard.jsx (3)

108-109: Incorrect placeholder for IP address field

The placeholder "0" is not appropriate for an IP address field. Consider using a valid IP format.

 className='input input-bordered w-full'
-placeholder='0'
+placeholder='192.168.1.100'
 value={formData.smartGrindIp}

167-169: Incorrect placeholder for IP address field

The placeholder "0" is not appropriate for an IP address field.

 className='input input-bordered w-full'
-placeholder='0'
+placeholder='192.168.1.100'
 value={formData.haIP}

182-183: Incorrect placeholder for port field

The placeholder "0" might mislead users. Consider using a common MQTT port.

 className='input input-bordered w-full'
-placeholder='0'
+placeholder='1883'
 value={formData.haPort}
web/src/components/OverviewChart.jsx (1)

115-119: Consider memoizing the tick callback function

The callback function creates new Date instances on every tick render. Consider memoizing or optimizing for better performance.

You could extract this to a utility function or memoize it to avoid repeated Date instantiations:

const createTickCallback = () => {
  return (value, index, ticks) => {
    const now = Date.now();
    const diff = Math.ceil((now - value) / 1000);
    return `-${diff}s`;
  };
};
web/src/utils/themeManager.js (1)

14-26: Consider separating validation from side effects

The setStoredTheme function both validates and applies the theme. Consider separating these concerns for better testability.

Consider splitting the validation and application:

 export function setStoredTheme(theme) {
   try {
     if (AVAILABLE_THEMES.includes(theme)) {
       localStorage.setItem(THEME_STORAGE_KEY, theme);
-      applyTheme(theme);
       return true;
     }
     return false;
   } catch (error) {
     console.warn('Failed to set stored theme:', error);
     return false;
   }
 }

+export function setAndApplyTheme(theme) {
+  const success = setStoredTheme(theme);
+  if (success) {
+    applyTheme(theme);
+  }
+  return success;
+}

Then update handleThemeChange to use setAndApplyTheme.

web/src/pages/ShotHistory/HistoryCard.jsx (1)

50-55: Render volume when it’s 0 (falsy) by checking for null/undefined instead

If a shot’s volume is 0g, it won’t render. Prefer a null/undefined check.

Apply this diff:

-        {shot.volume && (
+        {shot.volume != null && (
           <div className='flex flex-row items-center gap-2'>
             <span className='fa fa-scale-balanced'></span>
             {shot.volume}g
           </div>
         )}
web/src/pages/Home/index.jsx (1)

65-76: Nit: prefer boolean prop shorthand

Minor readability tweak.

Apply this diff:

-            <Card sm={10} lg={6} title='Temperature & Pressure Chart' fullHeight={true}>
+            <Card sm={10} lg={6} title='Temperature & Pressure Chart' fullHeight>
               <OverviewChart />
             </Card>

Repeat for the second chart card below.

Also applies to: 71-73

web/src/pages/ProfileEdit/ProfileTypeSelection.jsx (1)

22-24: Grammar nit: tighten phrasing

“Supports creating of profiles...” reads awkwardly.

Apply this diff:

-          <span className='text-base-content/70 text-center text-sm'>
-            Supports creating of profiles with different brew phases and targets.
-          </span>
+          <span className='text-base-content/70 text-center text-sm'>
+            Supports creating profiles with different brew phases and targets.
+          </span>
web/src/pages/Autotune/index.jsx (1)

131-149: UX: guard against accidental double starts (minor)

You already hide the Start button when active, but adding a defensive early-return in onStart improves robustness.

For example:

const onStart = useCallback(() => {
  if (active) return;
  apiService.send({ tp: 'req:autotune-start', time, samples });
  setActive(true);
}, [active, time, samples, apiService]);
web/src/pages/ShotHistory/index.jsx (1)

72-72: Unnecessary wrapper function for onDelete prop

The arrow function wrapper id => onDelete(id) is redundant since onDelete already has the same signature.

-          <HistoryCard shot={item} key={idx} onDelete={id => onDelete(id)} />
+          <HistoryCard shot={item} key={idx} onDelete={onDelete} />
web/src/pages/Scales/index.jsx (1)

91-95: Consider using the Spinner component for consistency

The code uses loading loading-spinner loading-lg directly instead of the Spinner component that's available and used elsewhere in the codebase.

+import { Spinner } from '../../components/Spinner.jsx';
           {isLoading || isInfoLoading ? (
             <div className='flex items-center justify-center py-12'>
-              <span className='loading loading-spinner loading-lg' />
+              <Spinner size={12} />
               <span className='text-base-content/70 ml-3'>Loading scales...</span>
             </div>
web/src/pages/Home/ProcessControls.jsx (2)

225-240: Accessibility: Icon-only buttons need proper aria-labels

The Time/Weight toggle buttons display icons with text, but the icon elements should have proper ARIA attributes for screen readers.

             <button
               className={`flex-1 rounded-full px-3 py-2 text-sm transition-all duration-200 ${brewTarget === 0 ? 'bg-primary text-primary-content font-medium' : 'text-base-content/60 hover:text-base-content'}`}
               onClick={() => changeTarget(0)}
+              aria-label="Select time-based brewing"
             >
               <i className='fa-solid fa-clock' />
               <span className='ml-1'>Time</span>
             </button>
             <button
               className={`flex-1 rounded-full px-3 py-2 text-sm transition-all duration-200 ${brewTarget === 1 ? 'bg-primary text-primary-content font-medium' : 'text-base-content/60 hover:text-base-content'}`}
               onClick={() => changeTarget(1)}
+              aria-label="Select weight-based brewing"
             >
               <i className='fa-solid fa-weight-scale' />
               <span className='ml-1'>Weight</span>
             </button>

243-244: Icon-only control button needs aria-label

The main control button only has an icon without accessible text for screen readers.

-          <button className='btn btn-circle btn-lg btn-primary' onClick={handleButtonClick}>
+          <button 
+            className='btn btn-circle btn-lg btn-primary' 
+            onClick={handleButtonClick}
+            aria-label={active ? 'Pause' : finished ? 'Clear' : 'Start'}
+          >
             <i className={`text-2xl ${getButtonIcon()}`} />
           </button>
web/src/pages/ProfileEdit/StandardProfileForm.jsx (1)

271-272: Extract reusable label components

The Volumetric Target and Pressure/Flow labels contain conditional text that could be extracted into a more readable format.

Consider extracting the conditional label logic:

const getFieldLabel = (mode, fieldType) => {
  if (fieldType === 'pressure') {
    return mode === 'pressure' ? 'Pressure Target' : 'Pressure Limit';
  }
  if (fieldType === 'flow') {
    return mode === 'flow' ? 'Flow Target' : 'Flow Limit';
  }
  return '';
};

Then use it in the labels:

-              Pressure {mode === 'pressure' ? 'Target' : 'Limit'}
+              {getFieldLabel(mode, 'pressure')}

Also applies to: 398-400, 421-422

web/src/pages/ProfileList/index.jsx (2)

213-213: Simplify favoriteCount calculation

The current implementation uses map followed by reduce, which is less efficient than a single reduce operation.

-  const favoriteCount = profiles.map(p => (p.favorite ? 1 : 0)).reduce((a, b) => a + b, 0);
+  const favoriteCount = profiles.reduce((count, p) => count + (p.favorite ? 1 : 0), 0);

286-294: Improve data handling for export functionality

The export function creates a new object for each profile just to delete properties. Consider using destructuring for cleaner code.

   const onExport = useCallback(() => {
-    const exportedProfiles = profiles.map(p => {
-      const ep = {
-        ...p,
-      };
-      delete ep.id;
-      delete ep.selected;
-      delete ep.favorite;
-      return ep;
-    });
+    const exportedProfiles = profiles.map(({ id, selected, favorite, ...profile }) => profile);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4a0785d and 63c4201.

⛔ Files ignored due to path filters (1)
  • web/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (36)
  • src/display/plugins/WebUIPlugin.cpp (1 hunks)
  • web/.prettierrc (1 hunks)
  • web/dashboard.html (0 hunks)
  • web/index.html (1 hunks)
  • web/package.json (2 hunks)
  • web/src/components/Card.jsx (1 hunks)
  • web/src/components/Footer.jsx (1 hunks)
  • web/src/components/Header.jsx (1 hunks)
  • web/src/components/Navigation.jsx (1 hunks)
  • web/src/components/OverviewChart.jsx (6 hunks)
  • web/src/components/ProcessInfo.jsx (1 hunks)
  • web/src/components/Spinner.jsx (1 hunks)
  • web/src/index.jsx (3 hunks)
  • web/src/pages/Autotune/index.jsx (3 hunks)
  • web/src/pages/Home/ProcessControls.jsx (2 hunks)
  • web/src/pages/Home/index.jsx (1 hunks)
  • web/src/pages/OTA/index.jsx (6 hunks)
  • web/src/pages/ProfileEdit/ProfileTypeSelection.jsx (1 hunks)
  • web/src/pages/ProfileEdit/StandardProfileForm.jsx (5 hunks)
  • web/src/pages/ProfileEdit/index.jsx (1 hunks)
  • web/src/pages/ProfileList/ExtendedContent.jsx (4 hunks)
  • web/src/pages/ProfileList/ProfileAddCard.jsx (1 hunks)
  • web/src/pages/ProfileList/index.jsx (8 hunks)
  • web/src/pages/Scales/index.jsx (2 hunks)
  • web/src/pages/Settings/PluginCard.jsx (1 hunks)
  • web/src/pages/Settings/index.jsx (10 hunks)
  • web/src/pages/ShotHistory/HistoryCard.jsx (1 hunks)
  • web/src/pages/ShotHistory/HistoryChart.jsx (3 hunks)
  • web/src/pages/ShotHistory/index.jsx (1 hunks)
  • web/src/pages/ShotHistory/utils.js (1 hunks)
  • web/src/pages/_404.jsx (1 hunks)
  • web/src/services/ApiService.js (2 hunks)
  • web/src/style.css (1 hunks)
  • web/src/utils/dashboardManager.js (1 hunks)
  • web/src/utils/themeManager.js (1 hunks)
  • web/src/utils/uuid.js (1 hunks)
🔥 Files not summarized due to errors (1)
  • web/src/components/Navigation.jsx: Error: Server error: no LLM provider could handle the message
💤 Files with no reviewable changes (1)
  • web/dashboard.html
🧰 Additional context used
🧬 Code Graph Analysis (20)
web/src/pages/ShotHistory/HistoryChart.jsx (2)
web/src/components/OverviewChart.jsx (1)
  • ref (133-133)
web/src/pages/ProfileList/ExtendedContent.jsx (1)
  • ref (101-101)
web/src/pages/Settings/PluginCard.jsx (2)
web/src/pages/OTA/index.jsx (1)
  • formData (10-10)
web/src/pages/Settings/index.jsx (2)
  • formData (17-17)
  • onChange (50-98)
web/src/pages/ProfileList/ProfileAddCard.jsx (1)
web/src/components/Card.jsx (1)
  • Card (1-45)
web/src/pages/ShotHistory/HistoryCard.jsx (4)
web/src/components/Card.jsx (1)
  • Card (1-45)
web/src/pages/ProfileList/index.jsx (2)
  • onExport (285-302)
  • onDelete (232-239)
web/src/pages/Settings/index.jsx (1)
  • onExport (134-142)
web/src/pages/ShotHistory/index.jsx (1)
  • onDelete (47-54)
web/src/pages/ProfileEdit/ProfileTypeSelection.jsx (2)
web/src/components/Card.jsx (1)
  • Card (1-45)
web/src/pages/ProfileList/index.jsx (1)
  • onSelect (241-248)
web/src/components/Card.jsx (2)
web/src/components/Navigation.jsx (1)
  • className (4-5)
web/src/components/Header.jsx (1)
  • className (6-7)
web/src/index.jsx (1)
web/src/utils/themeManager.js (1)
  • initializeTheme (42-45)
web/src/pages/Scales/index.jsx (1)
web/src/components/Card.jsx (1)
  • Card (1-45)
web/src/pages/ProfileEdit/index.jsx (3)
web/src/components/Spinner.jsx (1)
  • Spinner (1-16)
web/src/pages/ProfileEdit/ProfileTypeSelection.jsx (1)
  • ProfileTypeSelection (3-46)
web/src/pages/ProfileEdit/StandardProfileForm.jsx (1)
  • StandardProfileForm (5-163)
web/src/components/OverviewChart.jsx (2)
web/src/pages/ShotHistory/HistoryChart.jsx (3)
  • ref (112-112)
  • chartData (113-113)
  • chart (111-111)
web/src/services/ApiService.js (2)
  • machine (193-207)
  • machine (193-207)
web/src/components/ProcessInfo.jsx (2)
web/src/pages/OTA/index.jsx (1)
  • progress (12-12)
web/src/pages/Home/ProcessControls.jsx (1)
  • progress (19-19)
web/src/pages/Autotune/index.jsx (12)
web/src/index.jsx (1)
  • apiService (23-23)
web/src/pages/OTA/index.jsx (1)
  • apiService (7-7)
web/src/pages/Home/index.jsx (1)
  • apiService (23-23)
web/src/pages/Home/ProcessControls.jsx (3)
  • apiService (72-72)
  • active (18-18)
  • active (70-70)
web/src/pages/ProfileEdit/index.jsx (1)
  • apiService (13-13)
web/src/pages/ProfileList/index.jsx (1)
  • apiService (210-210)
web/src/pages/ShotHistory/index.jsx (1)
  • apiService (32-32)
web/src/services/ApiService.js (2)
  • ApiServiceContext (191-191)
  • ApiServiceContext (191-191)
web/src/pages/ProfileList/ExtendedContent.jsx (2)
  • time (64-64)
  • time (70-70)
web/src/components/Card.jsx (1)
  • Card (1-45)
web/src/components/OverviewChart.jsx (1)
  • OverviewChart (131-203)
web/src/components/Spinner.jsx (1)
  • Spinner (1-16)
web/src/pages/ShotHistory/index.jsx (4)
web/src/pages/ProfileList/index.jsx (3)
  • onDelete (232-239)
  • apiService (210-210)
  • loading (212-212)
web/src/pages/Home/index.jsx (1)
  • apiService (23-23)
web/src/components/Spinner.jsx (1)
  • Spinner (1-16)
web/src/pages/ShotHistory/HistoryCard.jsx (1)
  • HistoryCard (5-62)
web/src/pages/Home/index.jsx (6)
web/src/utils/dashboardManager.js (4)
  • setDashboardLayout (16-37)
  • setDashboardLayout (16-37)
  • getDashboardLayout (3-14)
  • getDashboardLayout (3-14)
web/src/index.jsx (1)
  • apiService (23-23)
web/src/pages/Home/ProcessControls.jsx (2)
  • apiService (72-72)
  • ProcessControls (65-250)
web/src/services/ApiService.js (4)
  • ApiServiceContext (191-191)
  • ApiServiceContext (191-191)
  • machine (193-207)
  • machine (193-207)
web/src/components/Card.jsx (1)
  • Card (1-45)
web/src/components/OverviewChart.jsx (1)
  • OverviewChart (131-203)
web/src/pages/Home/ProcessControls.jsx (4)
web/src/components/ProcessInfo.jsx (3)
  • processInfo (4-4)
  • progress (23-26)
  • target (20-21)
web/src/index.jsx (1)
  • apiService (23-23)
web/src/pages/Home/index.jsx (3)
  • apiService (23-23)
  • mode (50-50)
  • changeMode (41-49)
web/src/services/ApiService.js (2)
  • ApiServiceContext (191-191)
  • ApiServiceContext (191-191)
web/src/pages/Settings/index.jsx (7)
web/src/services/ApiService.js (2)
  • machine (193-207)
  • machine (193-207)
web/src/utils/themeManager.js (2)
  • getStoredTheme (4-12)
  • handleThemeChange (48-51)
web/src/utils/dashboardManager.js (2)
  • setDashboardLayout (16-37)
  • setDashboardLayout (16-37)
web/src/components/Spinner.jsx (1)
  • Spinner (1-16)
web/src/components/Card.jsx (1)
  • Card (1-45)
web/src/config/zones.js (2)
  • timezones (1-463)
  • timezones (1-463)
web/src/pages/Settings/PluginCard.jsx (1)
  • PluginCard (3-222)
web/src/pages/OTA/index.jsx (10)
web/src/index.jsx (1)
  • apiService (23-23)
web/src/pages/Autotune/index.jsx (1)
  • apiService (8-8)
web/src/pages/Home/index.jsx (1)
  • apiService (23-23)
web/src/pages/Home/ProcessControls.jsx (1)
  • apiService (72-72)
web/src/pages/ProfileEdit/index.jsx (1)
  • apiService (13-13)
web/src/pages/ProfileList/index.jsx (1)
  • apiService (210-210)
web/src/pages/ShotHistory/index.jsx (1)
  • apiService (32-32)
web/src/pages/Settings/index.jsx (1)
  • submitting (15-15)
web/src/components/Spinner.jsx (1)
  • Spinner (1-16)
web/src/components/Card.jsx (1)
  • Card (1-45)
web/src/pages/ProfileList/index.jsx (6)
web/src/pages/ProfileList/ExtendedContent.jsx (3)
  • data (69-69)
  • i (72-72)
  • ExtendedContent (100-112)
web/src/components/Card.jsx (1)
  • Card (1-45)
web/src/index.jsx (1)
  • apiService (23-23)
web/src/pages/Home/index.jsx (1)
  • apiService (23-23)
web/src/pages/Settings/index.jsx (1)
  • onExport (134-142)
web/src/components/Spinner.jsx (1)
  • Spinner (1-16)
web/src/pages/ProfileEdit/StandardProfileForm.jsx (3)
web/src/pages/ProfileEdit/index.jsx (3)
  • data (18-18)
  • onSave (65-74)
  • pressureAvailable (10-10)
web/src/components/Card.jsx (1)
  • Card (1-45)
web/src/components/Spinner.jsx (1)
  • Spinner (1-16)
web/src/components/Navigation.jsx (1)
web/src/components/Header.jsx (1)
  • useLocation (5-5)
🪛 GitHub Actions: Deploy PR Preview
web/src/pages/Settings/index.jsx

[error] 1094-1094: Step: 'vite build' failed. Transform failed: Unexpected closing fragment tag does not match opening 'Card' tag.


[error] 1097-1097: Step: 'vite build' failed. Transform failed: Unexpected end of file before a closing fragment tag.

🪛 Biome (2.1.2)
web/src/style.css

[error] 7-7: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 8-8: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 9-9: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 43-43: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 44-44: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 45-45: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 79-79: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 80-80: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 81-81: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 115-115: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 116-116: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 117-117: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test
  • GitHub Check: test
🔇 Additional comments (26)
web/src/components/Spinner.jsx (1)

2-6: Keep loading-xl — it's a valid DaisyUI spinner size (do not apply the suggested diff)

DaisyUI supports loading-xs, loading-sm, loading-md, loading-lg, and loading-xl (current docs). So the original replacement is unnecessary unless your project uses an older DaisyUI version.

  • File: web/src/components/Spinner.jsx — sizeMap entry 16: 'loading-xl' — leave as-is.
  • If the class has no effect locally, check your daisyui version in package.json/lockfile and upgrade (older releases only went up to loading-lg).

Likely an incorrect or invalid review comment.

web/package.json (1)

16-16: daisyUI v5 is compatible with Tailwind v4 — verify project config

Short: daisyUI v5.0.x supports Tailwind CSS v4 and the new CSS @plugin API, but you must register daisyUI in your main CSS (not via JS) and use Tailwind’s Vite plugin (@tailwindcss/vite). Please check/adjust the following.

  • Files to verify/modify:
    • web/package.json — daisyUI version (OK if ^5.0.50):
          "daisyui": "^5.0.50",
      
    • main CSS entry (e.g., app.css / index.css): ensure you use
      @import "tailwindcss";
      @plugin "daisyui";
      
      (Move any daisyUI options into the @plugin "daisyui" { ... } block if needed.)
    • tailwind.config.js: remove require('daisyui') from the JS plugins array (plugin JS registration is deprecated for v5).
    • vite.config.{js,ts}: ensure you use the Vite plugin:
      import tailwind from "@tailwindcss/vite";
      export default defineConfig({ plugins: [tailwind()] });
    • PostCSS config: audit/remove legacy plugins (postcss-import, autoprefixer, postcss-nesting) as recommended by Tailwind v4 migration.

If you’d like, I can scan the repo to confirm these files and show exact lines to change.

src/display/plugins/WebUIPlugin.cpp (1)

179-185: Ignore: clear() does not imply deactivate() — the current sequence is intentional

Short explanation: I inspected the implementations: deactivate() stops the active process (moves currentProcess → lastProcess and emits end events) while clear() deletes lastProcess (and emits brew:clear). clear() does not stop an active process, so calling deactivate(); clear(); before setMode(...) is correct.

Relevant locations:

  • src/display/plugins/WebUIPlugin.cpp — ws handler for "req:change-mode": calls controller->deactivate(); controller->clear(); controller->setMode(mode);
  • src/display/core/Controller.cpp — deactivate() (stops currentProcess, sets lastProcess) and clear() (deletes lastProcess and triggers brew:clear)

Action: No change required. Optional: if you want an atomic operation, add a Controller::switchMode(int) / switchMode helper that encapsulates deactivate + clear + setMode.

Likely an incorrect or invalid review comment.

web/src/pages/ShotHistory/utils.js (2)

9-9: Good: parseInt radix specified.

Specifying base 10 removes environment-dependent parsing. Change looks correct.


17-17: Good: t sample parsed with explicit radix.

Consistent with the header timestamp fix. LGTM.

web/src/pages/ShotHistory/HistoryChart.jsx (3)

83-86: Formatting-only change is fine.

Arrow callback parens removal doesn't affect behavior. Safe.


94-97: Formatting-only change is fine.

Same here; safe and consistent with Prettier rules.


128-128: Formatting-only JSX quote change is fine.

No functional impact. LGTM.

web/.prettierrc (1)

2-16: Prettier v3 + Tailwind plugin configuration looks consistent

The config aligns with the code changes (single quotes in JSX, trailing commas, arrowParens: avoid) and the Tailwind plugin usage. No issues from a tooling perspective.

web/src/pages/ProfileList/ExtendedContent.jsx (1)

11-13: Stylistic arrow fn and className tweaks: LGTM

The changes are purely stylistic and match the updated Prettier rules. No behavioral impact.

Also applies to: 23-25, 64-66, 108-110

web/src/pages/ProfileList/ProfileAddCard.jsx (2)

3-14: Card wrapper migration and simplified markup: LGTM

Good move to consistent Card usage; semantics and accessibility look fine (icon marked aria-hidden, visible text provided).


6-13: Verify SPA navigation needs

If you’re using a client-side router, will cause a full page reload. Consider using the router’s Link component for in-app navigation.

Would you like me to refactor this to use your router’s Link (if present) and handle active states?

web/src/components/ProcessInfo.jsx (1)

20-27: Progress calculation and markup swap: LGTM

Calculation guards for zero targets and the replacement are sound and improve semantics.

Also applies to: 29-36

web/src/style.css (1)

153-153: Confirm Tailwind v4 @source usage is intentional

@source inline(...) is Tailwind v4’s content hint. Ensure your build uses Tailwind v4 and that your tooling (PostCSS/CLI) processes this file so the generated col-span utilities are retained in production builds.

If needed, I can help verify tailwind and daisyUI versions in package.json and your build pipeline.

web/src/pages/Settings/PluginCard.jsx (1)

208-209: Consider using a password manager-friendly name attribute

The name attribute "haPassword" is close but could be more standardized for password managers.

 id='haPassword'
-name='haPassword'
+name='haPassword'
 type='password'

Actually, upon review, the name attribute is fine as-is for this context.

web/src/components/Header.jsx (2)

5-12: Clean path comparison implementation

The path-based active state detection using useLocation is well implemented. The conditional className assignment provides clear visual feedback for the current route.


78-80: Correct use of camelCase attributes

Good correction from fillRule and clipRule to use camelCase as required in JSX.

web/src/components/OverviewChart.jsx (2)

135-149: Well-structured chart lifecycle management

The chart creation and cleanup logic is properly implemented with the destroy call in the cleanup function, preventing memory leaks.


162-196: Comprehensive responsive behavior implementation

The resize event handler effectively adjusts font sizes and tick limits based on screen size. The use of chart.update('none') for performance optimization is a good practice.

web/src/components/Footer.jsx (1)

1-36: Consistent styling updates aligned with DaisyUI theme

The Footer component correctly adopts the new DaisyUI theme tokens (e.g., border-base-content/10, text-error) and maintains proper accessibility with rel="noreferrer" on external links.

web/src/utils/themeManager.js (2)

4-12: Robust theme retrieval with proper validation

The getStoredTheme function correctly validates against AVAILABLE_THEMES and provides appropriate fallback handling with error logging.


42-45: Clean initialization function

The initializeTheme function provides a clean entry point for theme initialization on app load.

web/src/pages/Home/index.jsx (1)

25-39: Storage event only fires across documents (e.g., other tabs), not within the same tab

If layout is changed in the same tab, this listener won’t fire. Ensure the setter also updates local component state or use a shared signal/event bus.

Do you update dashboardLayout state in the same tab when calling setDashboardLayout? If not, I can propose a small shared store or context to keep all views in sync without relying on the storage event.

web/src/pages/Autotune/index.jsx (1)

86-105: Verify API contract: input ranges and labels no longer match sent parameters

UI labels imply “Tuning Goal” [0–100] and “Window Size” [1–10], yet you’re sending { time, samples } as-is to req:autotune-start. If the backend expects seconds for time, this is a breaking change.

Please confirm:

  • Does req:autotune-start expect time in seconds or a 0–100 goal?
  • Does samples align with “Window Size” 1–10?

If the API still expects seconds/samples, consider either mapping UI inputs to expected units or renaming state and payload fields. I can generate a patch once you confirm the intended contract.

Also applies to: 107-124, 14-21

web/src/index.jsx (1)

63-65: LGTM! Theme initialization before render prevents flash of unstyled content

Good practice to initialize the theme before rendering to ensure consistent styling from the start.

web/src/pages/ProfileList/index.jsx (1)

21-30: LGTM! Clean Chart.js registration

The consolidated Chart.register call is cleaner and follows Chart.js best practices.

Comment on lines +32 to +41
className={`card bg-base-100 shadow-xl ${gridClasses} ${fullHeight ? 'h-full' : ''} ${className}`}
role={role}
>
{title && (
<div className='card-header px-4 pt-4'>
<h2 className='card-title text-lg sm:text-xl'>{title}</h2>
</div>
)}
<div className={`card-body flex flex-col gap-2 p-4 ${fullHeight ? 'flex-1' : ''}`}>
{children}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Align Card markup with DaisyUI conventions; remove non-standard card-header

DaisyUI expects card-title inside card-body. card-header is not a documented class and may not style as intended.

Apply this diff to move the title into card-body and avoid redundant padding:

-    <div
-      className={`card bg-base-100 shadow-xl ${gridClasses} ${fullHeight ? 'h-full' : ''} ${className}`}
-      role={role}
-    >
-      {title && (
-        <div className='card-header px-4 pt-4'>
-          <h2 className='card-title text-lg sm:text-xl'>{title}</h2>
-        </div>
-      )}
-      <div className={`card-body flex flex-col gap-2 p-4 ${fullHeight ? 'flex-1' : ''}`}>
-        {children}
-      </div>
+    <div
+      className={`card bg-base-100 shadow-xl ${gridClasses} ${fullHeight ? 'h-full' : ''} ${className}`}
+      role={role}
+    >
+      <div className={`card-body flex flex-col gap-2 ${fullHeight ? 'flex-1' : ''}`}>
+        {title && <h2 className='card-title text-lg sm:text-xl'>{title}</h2>}
+        {children}
+      </div>
📝 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
className={`card bg-base-100 shadow-xl ${gridClasses} ${fullHeight ? 'h-full' : ''} ${className}`}
role={role}
>
{title && (
<div className='card-header px-4 pt-4'>
<h2 className='card-title text-lg sm:text-xl'>{title}</h2>
</div>
)}
<div className={`card-body flex flex-col gap-2 p-4 ${fullHeight ? 'flex-1' : ''}`}>
{children}
<div
className={`card bg-base-100 shadow-xl ${gridClasses} ${fullHeight ? 'h-full' : ''} ${className}`}
role={role}
>
<div className={`card-body flex flex-col gap-2 ${fullHeight ? 'flex-1' : ''}`}>
{title && <h2 className='card-title text-lg sm:text-xl'>{title}</h2>}
{children}
</div>
🤖 Prompt for AI Agents
In web/src/components/Card.jsx around lines 32 to 41, the component uses a
non-standard card-header wrapper which conflicts with DaisyUI conventions; move
the title markup into the card-body and remove the extra header wrapper and its
padding. Specifically, remove the <div className='card-header px-4 pt-4'> block
and place the <h2 className='card-title text-lg sm:text-xl'>{title}</h2>
directly at the top of the card-body container, adjusting the card-body
padding/classes to avoid redundant padding when title is present so the title
uses DaisyUI's card-title inside card-body.

Comment on lines +8 to +9
className =
'btn btn-md justify-start gap-3 w-full bg-primary text-primary-content hover:bg-primary hover:text-primary-content px-2';
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Active state should handle nested routes; use DaisyUI "btn-primary" for active

Exact path equality fails to mark parent sections active for nested routes (e.g., /settings/plugins). Also, let DaisyUI handle active colors via btn-primary instead of manual bg/text classes.

Apply this diff:

-    className =
-      'btn btn-md justify-start gap-3 w-full bg-primary text-primary-content hover:bg-primary hover:text-primary-content px-2';
+    className =
+      'btn btn-primary btn-md justify-start gap-3 w-full px-2';

And extend the condition to include nested paths:

-  if (props.active || path === props.link) {
+  if (
+    props.active ||
+    path === props.link ||
+    (props.link !== '/' && path.startsWith(props.link))
+  ) {

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In web/src/components/Navigation.jsx around lines 8-9, the current className
uses hardcoded bg/text hover classes and exact path equality which fails to mark
parent items active for nested routes; replace the manual
bg-primary/text-primary-content classes with DaisyUI's "btn-primary" for the
active state and update the active-condition to match nested routes (e.g., use
location.pathname.startsWith(item.path) or NavLink's isActive callback) so
parent sections (like /settings) are active when child routes
(/settings/plugins) are visited; ensure the class string toggles "btn btn-md
justify-start gap-3 w-full px-2" plus "btn-primary" only when the nested-match
condition is true.

Comment on lines 13 to 14
<i className={props.iconClass || ''} />
<span>{props.label}</span>
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Treat icons as decorative for a11y

Mark icons as aria-hidden so screen readers don’t announce them redundantly (the label text already conveys meaning).

Apply this diff:

-      <i className={props.iconClass || ''} />
+      <i className={props.iconClass || ''} aria-hidden="true" />

Additionally, consider setting aria-current="page" on the anchor when active. Example (outside the changed lines, for illustration):

// inside MenuItem return:
<a
  href={props.link}
  className={className}
  aria-current={
    props.active ||
    path === props.link ||
    (props.link !== '/' && path.startsWith(props.link))
      ? 'page'
      : undefined
  }
>
🤖 Prompt for AI Agents
In web/src/components/Navigation.jsx around lines 13-14, the icon element is not
marked decorative; add aria-hidden="true" to the <i> element so screen readers
ignore it (keep props.iconClass fallback), and ensure accessibility by setting
aria-current="page" on the anchor when the item is active (compute active using
props.active or current path matching props.link and set aria-current to 'page'
only when active, otherwise leave it undefined).

Comment on lines +22 to 37
<MenuItem label='Dashboard' link='/' iconClass='fa fa-home' />
<hr className='h-5 border-0' />
<div className='space-y-1.5'>
<MenuItem label='Profiles' link='/profiles' iconClass='fa fa-list' />
<MenuItem label='Shot History' link='/history' iconClass='fa fa-timeline' />
</div>
<hr className="h-5 border-0" />
<div class="space-y-1.5">
<MenuItem label="PID Autotune" link="/pidtune" iconClass="fa fa-temperature-half" />
<MenuItem label="Bluetooth Scales" link="/scales" iconClass="fa-brands fa-bluetooth-b" />
<MenuItem label="Settings" link="/settings" iconClass="fa fa-cog" />
<hr className='h-5 border-0' />
<div className='space-y-1.5'>
<MenuItem label='PID Autotune' link='/pidtune' iconClass='fa fa-temperature-half' />
<MenuItem label='Bluetooth Scales' link='/scales' iconClass='fa-brands fa-bluetooth-b' />
<MenuItem label='Settings' link='/settings' iconClass='fa fa-cog' />
</div>
<hr class="h-5 border-0" />
<div class="space-y-1.5">
<MenuItem label="System & Updates" link="/ota" iconClass="fa fa-rotate" />
<hr className='h-5 border-0' />
<div className='space-y-1.5'>
<MenuItem label='System & Updates' link='/ota' iconClass='fa fa-rotate' />
</div>
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Standardize Font Awesome version/prefixes; some icon names may be invalid

The usage mixes FA4/5/6 conventions: 'fa', 'fa-brands', and icon names like 'fa-timeline' and 'fa-rotate' which may not exist (or be Pro-only) depending on the FA version. This can lead to missing icons.

Verification script to determine FA version and usages:

If using FA6, consider normalizing to 'fa-solid'/'fa-regular'/'fa-brands' and valid icon names. Example mappings:

  • Dashboard: fa-solid fa-house
  • Shot History: fa-solid fa-clock-rotate-left (instead of fa-timeline)
  • System & Updates: fa-solid fa-arrows-rotate (instead of fa-rotate)
  • Settings: fa-solid fa-gear (fa-cog alias may still work)

Optionally, introduce a small Icon component to enforce consistent prefixes:

function Icon({ name, style = 'solid' }) {
  const prefix = style === 'brands' ? 'fa-brands' : style === 'regular' ? 'fa-regular' : 'fa-solid';
  return <i className={`${prefix} ${name}`} aria-hidden="true" />;
}

🏁 Script executed:

#!/bin/bash
# Find Font Awesome references and determine version
rg -n "fontawesome|fortawesome|fa[- ]" -A 2
fd package.json | xargs -I{} sh -c 'echo "== {} =="; rg -n "@fortawesome|fontawesome" {}'
# Check CSS includes in HTML
fd -e html | xargs -I{} sh -c 'echo "== {} =="; rg -n "fontawesome|fortawesome|fa-" {} -A 2'

Length of output: 36709


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== root package.json (first 200 lines) ==="
if [ -f package.json ]; then sed -n '1,200p' package.json; else echo "no root package.json"; fi
echo

echo "=== web/package.json (full) ==="
if [ -f web/package.json ]; then cat web/package.json; else echo "no web/package.json"; fi
echo

echo "=== rg search for Font Awesome / kit / prefixes across repo ==="
rg -n "@fortawesome|fontawesome|font-awesome|kit.fontawesome|use.fontawesome|cdnjs.cloudflare.com/ajax/libs/font-awesome|maxcdn.bootstrapcdn.com/font-awesome|fontawesome-free|fa-brands|fa-solid|fa-regular|fab|fas|far|fal|\\bfa\\b" -S || true
echo

echo "=== HTML files that include Font Awesome/kit references (matches) ==="
rg -n "kit.fontawesome|use.fontawesome|cdnjs.cloudflare.com/ajax/libs/font-awesome|maxcdn.bootstrapcdn.com/font-awesome|fontawesome.com" -S || true
echo

echo "=== CSS files importing fontawesome (matches) ==="
rg -n "@import .*font-awesome|@import .*fontawesome|@import .*fortawesome" -S || true

Length of output: 268485


Action: standardize Font Awesome usage to the FA6 prefixes and valid icon names

Short summary — web/index.html loads Font Awesome 6.7.2 (CDN) but the codebase mixes "fa", "fa-solid", and "fa-brands" and uses names that may be missing or Pro-only (e.g. fa-timeline, fa-rotate). That will produce missing icons at runtime.

Files I found that need attention (non-exhaustive)

  • web/index.html (includes https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css)
  • web/src/components/Navigation.jsx (lines ~22–37)
  • web/src/components/Header.jsx (menu icons)
  • web/src/pages/Home/ProcessControls.jsx (mixed prefixes)
  • web/src/pages/ShotHistory/HistoryCard.jsx, web/src/pages/Settings/index.jsx, web/src/pages/ProfileList/, web/src/pages/ProfileEdit/ and many other files (rg found many "fa" occurrences)

Suggested replacements (example for Navigation.jsx — update to FA6-style prefixes and known free icons):

<MenuItem label='Dashboard' link='/' iconClass='fa-solid fa-house' />
<hr className='h-5 border-0' />
<div className='space-y-1.5'>
  <MenuItem label='Profiles' link='/profiles' iconClass='fa-solid fa-list' />
  <MenuItem label='Shot History' link='/history' iconClass='fa-solid fa-clock' />
</div>
<hr className='h-5 border-0' />
<div className='space-y-1.5'>
  <MenuItem label='PID Autotune' link='/pidtune' iconClass='fa-solid fa-temperature-half' />
  <MenuItem label='Bluetooth Scales' link='/scales' iconClass='fa-brands fa-bluetooth-b' />
  <MenuItem label='Settings' link='/settings' iconClass='fa-solid fa-gear' />
</div>
<hr className='h-5 border-0' />
<div className='space-y-1.5'>
  <MenuItem label='System & Updates' link='/ota' iconClass='fa-solid fa-arrows-rotate' />
</div>

Notes / next steps (short)

  • Verify each icon name against the Font Awesome 6 free catalogue (some names in the repo may be Pro-only or renamed). Replace invalid names with FA6 equivalents (or swap to acceptable free icons).
  • Normalize prefixes across the repo to fa-solid / fa-regular / fa-brands (or keep a single style if you prefer).
  • Optionally add a small Icon wrapper to centralize prefix logic and avoid repeating incorrect classes:
function Icon({ name, style = 'solid' }) {
  const prefix = style === 'brands' ? 'fa-brands' : style === 'regular' ? 'fa-regular' : 'fa-solid';
  return <i className={`${prefix} ${name}`} aria-hidden="true" />;
}

I verified the FA6 CDN reference and many mixed usages with ripgrep; please update the files above and run the app to confirm there are no missing glyphs.

🤖 Prompt for AI Agents
In web/src/components/Navigation.jsx around lines 22 to 37, the menu uses
mixed/legacy Font Awesome classes and some icon names that are missing or
Pro-only (e.g. "fa", "fa-timeline", "fa-rotate"); update each MenuItem to use
FA6 free prefixes and valid icon names (use fa-solid / fa-regular / fa-brands as
appropriate), replacing invalid names with FA6 free equivalents (examples: use
fa-solid fa-house for Dashboard, fa-solid fa-clock for Shot History, fa-solid
fa-gear for Settings, fa-solid fa-arrows-rotate for System & Updates, keep
fa-brands fa-bluetooth-b for Bluetooth), then scan and normalize other files
(Header.jsx, ProcessControls.jsx, HistoryCard.jsx, ProfileList/Edit, Settings,
etc.) to the same FA6 prefixes and verify every icon name against the Font
Awesome 6 free catalog; optionally add a small shared Icon wrapper to centralize
prefix logic to avoid future inconsistencies.

Comment on lines +10 to +14
<span
className={`loading loading-spinner ${sizeMap[size] || 'loading-md'}`}
role='status'
aria-label='Loading'
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Preserve numeric size behavior: add inline-style fallback for arbitrary numbers

Today, unknown numeric sizes silently fall back to 'md', which changes visuals relative to a pixel-based SVG. Add an inline width/height fallback so numeric values still work as before.

Apply this diff:

       className={`loading loading-spinner ${sizeMap[size] || 'loading-md'}`}
       role='status'
-      aria-label='Loading'
+      aria-label='Loading'
+      style={!sizeMap[size] && Number.isFinite(size) ? { width: size, height: size } : undefined}

Optionally verify existing usages and size values to assess impact:


🏁 Script executed:

#!/bin/bash
# List all Spinner usages
rg -n "<Spinner" -S

echo
echo "Spinner usages with explicit numeric sizes (by value):"
# Extract numeric sizes passed as JSX expressions: size={N}
rg -n --pcre2 "<Spinner[^>]*size=\{([0-9]+)\}" -or '$1' | sort -n | uniq -c

Length of output: 1315


Preserve numeric size behavior: add inline-style fallback for arbitrary numbers

Unknown numeric sizes currently fall back to 'loading-md', changing visuals vs. pixel-based numeric sizes. Add an inline width/height fallback so numeric props render as before.

Files to update / check:

  • Change: web/src/components/Spinner.jsx (the span at lines ~10-14)
  • Affected usages with numeric sizes (will be preserved by this change):
    • web/src/pages/OTA/index.jsx:62 —
    • web/src/pages/OTA/index.jsx:70 —
    • web/src/pages/Autotune/index.jsx:48 —
    • web/src/pages/ProfileEdit/index.jsx:79 —
    • web/src/pages/ProfileEdit/StandardProfileForm.jsx:157 — {saving && }
    • web/src/pages/Settings/index.jsx:159 —
    • web/src/pages/Settings/index.jsx:1079 — {submitting && }
    • web/src/pages/ShotHistory/index.jsx:59 —
    • web/src/pages/ProfileList/index.jsx:333 —

Apply this diff:

       className={`loading loading-spinner ${sizeMap[size] || 'loading-md'}`}
       role='status'
-      aria-label='Loading'
+      aria-label='Loading'
+      style={!sizeMap[size] && Number.isFinite(size) ? { width: size, height: size } : undefined}

Notes: this preserves numeric (number-typed) size props. If you also pass numeric sizes as strings (e.g., "8"), consider parsing or handling those separately.

📝 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
<span
className={`loading loading-spinner ${sizeMap[size] || 'loading-md'}`}
role='status'
aria-label='Loading'
/>
<span
className={`loading loading-spinner ${sizeMap[size] || 'loading-md'}`}
role='status'
aria-label='Loading'
style={!sizeMap[size] && Number.isFinite(size) ? { width: size, height: size } : undefined}
/>
🤖 Prompt for AI Agents
In web/src/components/Spinner.jsx around lines 10-14, the span currently only
uses className fallback 'loading-md' which changes visuals for numeric sizes;
detect if the incoming size prop is a number and, when so, add an inline style
object setting width and height to `${size}px` (merge with any existing style
prop), while keeping the existing className expression (sizeMap[size] ||
'loading-md') for non-numeric or mapped sizes so numeric-typed sizes render as
before.

Comment on lines +41 to 45
useEffect(() => {
if (connected.value) {
await loadHistory();
loadHistory();
}
}, [connected.value]);
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Async function removed from useEffect could impact error handling

The change from async to synchronous removes the ability to catch errors from loadHistory(). Consider wrapping the call in a try-catch or handling errors within loadHistory() itself.


🏁 Script executed:

#!/bin/bash
# Check if loadHistory has internal error handling
ast-grep --pattern $'const loadHistory = async () => {
  $$$
}'

Length of output: 532


Handle errors from loadHistory invoked in useEffect

loadHistory (web/src/pages/ShotHistory/index.jsx — lines ~35–40) performs an async request but has no try/catch. Calling it from useEffect (lines ~41–45) without awaiting or catching can produce unhandled promise rejections. Add error handling either inside loadHistory or around the call in useEffect.

Files / locations to update:

  • web/src/pages/ShotHistory/index.jsx
    • loadHistory implementation (around lines 35–40)
    • useEffect invoking loadHistory (around lines 41–45)

Suggested fixes (pick one):

Option A — handle errors inside loadHistory:

const loadHistory = async () => {
  try {
    const response = await apiService.request({ tp: 'req:history:list' });
    const history = response.history.map(parseHistoryData).reverse();
    setHistory(history);
  } catch (err) {
    console.error('Failed to load history', err);
    // optionally set error state / show toast
  } finally {
    setLoading(false);
  }
};

Option B — catch around the call in useEffect:

useEffect(() => {
  if (connected.value) {
    (async () => {
      try {
        await loadHistory();
      } catch (err) {
        console.error('Failed to load history', err);
      }
    })();
  }
}, [connected.value]);
🤖 Prompt for AI Agents
In web/src/pages/ShotHistory/index.jsx around lines 35–45, the async loadHistory
call can produce unhandled promise rejections; update either the loadHistory
implementation (around lines ~35–40) to wrap its await logic in
try/catch/finally and handle/log errors and ensure loading state is cleared, or
update the useEffect (lines ~41–45) to call loadHistory inside an
immediately-invoked async function that awaits loadHistory and catches/logs
errors; pick one approach and keep error handling consistent (log error and
optionally set an error state or show a toast).

Comment on lines +8 to +14
try {
return localStorage.getItem(DASHBOARD_LAYOUT_KEY) || 'process-first';
} catch (error) {
console.warn('getDashboardLayout: localStorage access failed:', error);
return 'process-first';
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Bug: getDashboardLayout returns JSON string (with quotes) rather than the actual value

setDashboardLayout JSON-stringifies the layout, but getDashboardLayout returns the raw stored string without JSON.parse. Consumers will see '"process-first"' instead of 'process-first'.

Apply this diff to parse safely and fall back correctly:

   try {
-    return localStorage.getItem(DASHBOARD_LAYOUT_KEY) || 'process-first';
+    const raw = localStorage.getItem(DASHBOARD_LAYOUT_KEY);
+    if (!raw) return 'process-first';
+    try {
+      const parsed = JSON.parse(raw);
+      return typeof parsed === 'string' ? parsed : 'process-first';
+    } catch {
+      // Support legacy/plain storage without JSON
+      return raw;
+    }
   } catch (error) {
     console.warn('getDashboardLayout: localStorage access failed:', error);
     return 'process-first';
   }
📝 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
try {
return localStorage.getItem(DASHBOARD_LAYOUT_KEY) || 'process-first';
} catch (error) {
console.warn('getDashboardLayout: localStorage access failed:', error);
return 'process-first';
}
};
try {
const raw = localStorage.getItem(DASHBOARD_LAYOUT_KEY);
if (!raw) return 'process-first';
try {
const parsed = JSON.parse(raw);
return typeof parsed === 'string' ? parsed : 'process-first';
} catch {
// Support legacy/plain storage without JSON
return raw;
}
} catch (error) {
console.warn('getDashboardLayout: localStorage access failed:', error);
return 'process-first';
}
};
🤖 Prompt for AI Agents
In web/src/utils/dashboardManager.js around lines 8 to 14, getDashboardLayout
currently returns the raw stored string (possibly JSON-stringified with quotes)
causing consumers to receive values like '"process-first"'; update the function
to read the localStorage value, JSON.parse it inside a try/catch (handling
non-JSON strings by falling back to the raw value if parse fails), and return
the parsed value or the default 'process-first' on error, while preserving the
existing console.warn fallback behavior.

Comment on lines +16 to +21
export const setDashboardLayout = layout => {
if (layout === null || layout === undefined) {
console.error('setDashboardLayout: Layout cannot be null or undefined');
return false;
}

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

SSR/unsupported environment guard missing in setDashboardLayout

In non-browser contexts, referencing localStorage can throw. Mirror the guard you used in getDashboardLayout.

Apply this diff:

 export const setDashboardLayout = layout => {
+  if (typeof window === 'undefined' || !window.localStorage) {
+    console.warn('setDashboardLayout: localStorage not available; ignoring.');
+    return false;
+  }
   if (layout === null || layout === undefined) {
     console.error('setDashboardLayout: Layout cannot be null or undefined');
     return false;
   }
📝 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
export const setDashboardLayout = layout => {
if (layout === null || layout === undefined) {
console.error('setDashboardLayout: Layout cannot be null or undefined');
return false;
}
export const setDashboardLayout = layout => {
if (typeof window === 'undefined' || !window.localStorage) {
console.warn('setDashboardLayout: localStorage not available; ignoring.');
return false;
}
if (layout === null || layout === undefined) {
console.error('setDashboardLayout: Layout cannot be null or undefined');
return false;
}
🤖 Prompt for AI Agents
In web/src/utils/dashboardManager.js around lines 16 to 21, setDashboardLayout
currently accesses localStorage without checking for a browser/SSR environment;
add the same guard used in getDashboardLayout (e.g. verify typeof window !==
'undefined' and window.localStorage exists) before using localStorage, and if
the guard fails return false and avoid any localStorage calls or errors.

Comment on lines +22 to +37
let serializedLayout;
try {
serializedLayout = JSON.stringify(layout);
} catch (error) {
console.error('setDashboardLayout: Layout is not JSON-serializable:', error);
return false;
}

try {
localStorage.setItem(DASHBOARD_LAYOUT_KEY, serializedLayout);
return true;
} catch (error) {
console.error('setDashboardLayout: Failed to store layout in localStorage:', error);
return false;
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Validate input against allowed layouts to prevent bad state

Constrain layout to DASHBOARD_LAYOUTS values; this prevents storing arbitrary/invalid values and makes downstream comparisons reliable.

Apply this diff:

 export const setDashboardLayout = layout => {
@@
-  let serializedLayout;
+  // Validate against known layouts
+  if (typeof layout !== 'string' || !Object.values(DASHBOARD_LAYOUTS).includes(layout)) {
+    console.error('setDashboardLayout: Invalid layout value', layout);
+    return false;
+  }
+
+  let serializedLayout;
   try {
     serializedLayout = JSON.stringify(layout);
   } catch (error) {
     console.error('setDashboardLayout: Layout is not JSON-serializable:', error);
     return false;
   }

Note: Referencing DASHBOARD_LAYOUTS here is safe at runtime since module initialization completes before any function is called.

Also applies to: 39-42

🤖 Prompt for AI Agents
In web/src/utils/dashboardManager.js around lines 22 to 37 (and also apply same
change at lines 39 to 42), the function currently serializes and stores any
layout value; restrict accepted layouts to the predefined DASHBOARD_LAYOUTS to
avoid persisting invalid state. Before JSON.stringify, check that layout is one
of the allowed values (e.g., DASHBOARD_LAYOUTS.includes(layout) or equivalent),
log a clear error and return false if it is not allowed, and only then proceed
to serialize and write to localStorage; apply the same validation check in the
other code path at lines 39–42.

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
E Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@jniebuhr jniebuhr merged commit 0954e99 into master Aug 13, 2025
5 of 7 checks passed
@jniebuhr jniebuhr deleted the feature/daisy-ui branch August 13, 2025 20:38
Copy link
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: 2

♻️ Duplicate comments (4)
web/src/components/Navigation.jsx (1)

4-10: Use DaisyUI btn-primary/btn-ghost and support nested routes; add aria-current

Use DaisyUI’s button variants instead of manual bg/text classes, and mark parent items active for nested paths (e.g., /settings/plugins). Also expose active state to ATs via aria-current.

-  let className =
-    'btn btn-md justify-start gap-3 w-full text-base-content hover:text-base-content hover:bg-base-content/10 bg-transparent border-none px-2';
-  const { path } = useLocation();
-  if (props.active || path === props.link) {
-    className =
-      'btn btn-md justify-start gap-3 w-full bg-primary text-primary-content hover:bg-primary hover:text-primary-content px-2';
-  }
+  const { path } = useLocation();
+  const isActive =
+    props.active ||
+    path === props.link ||
+    (props.link !== '/' && path.startsWith(props.link));
+  const className = `btn btn-md justify-start gap-3 w-full px-2 ${isActive ? 'btn-primary' : 'btn-ghost'}`;
@@
-    <a href={props.link} className={className}>
+    <a href={props.link} className={className} aria-current={isActive ? 'page' : undefined}>

Also applies to: 12-12

web/src/pages/Settings/index.jsx (1)

433-437: Fix deprecated selected attribute usage.

The selected attribute on <option> elements is deprecated in React. The timezone select should be controlled via the value prop.

Apply this fix to make it a controlled component:

              <select
                id='timezone'
                name='timezone'
                className='select select-bordered w-full'
+               value={formData.timezone || ''}
                onChange={onChange('timezone')}
              >
                {timezones.map(timezone => (
-                 <option key={timezone} value={timezone} selected={formData.timezone === timezone}>
+                 <option key={timezone} value={timezone}>
                    {timezone}
                  </option>
                ))}
              </select>
web/src/pages/Home/index.jsx (2)

17-17: Use the centralized storage key to avoid drift

Import the layout storage key constant to keep the event listener aligned with the manager.

Apply this diff:

-import { getDashboardLayout } from '../../utils/dashboardManager.js';
+import { getDashboardLayout, DASHBOARD_LAYOUT_KEY } from '../../utils/dashboardManager.js';

25-39: Parse storage event values and use the shared key; current code will set state to a quoted JSON string

The storage event handler has two issues:

  1. It uses a hardcoded string 'dashboardLayout' instead of the imported constant
  2. It doesn't parse the JSON-serialized value from localStorage, which will cause state to be set to a quoted string like '"process-first"' instead of 'process-first'

Apply this diff to fix both issues:

   useEffect(() => {
     setDashboardLayout(getDashboardLayout());
 
     const handleStorageChange = e => {
-      if (e.key === 'dashboardLayout') {
-        setDashboardLayout(e.newValue || 'process-first');
+      if (e.key === DASHBOARD_LAYOUT_KEY) {
+        try {
+          const layout = e.newValue ? JSON.parse(e.newValue) : 'process-first';
+          setDashboardLayout(layout);
+        } catch {
+          // Fallback for non-JSON values
+          setDashboardLayout('process-first');
+        }
       }
     };

Additionally, update getDashboardLayout in dashboardManager.js to parse the stored value:

export const getDashboardLayout = () => {
  if (typeof window === 'undefined' || !window.localStorage) {
    return 'process-first';
  }

  try {
    const raw = localStorage.getItem(DASHBOARD_LAYOUT_KEY);
    return raw ? JSON.parse(raw) : 'process-first';
  } catch (error) {
    console.warn('getDashboardLayout: Failed to parse layout:', error);
    return 'process-first';
  }
};
🧹 Nitpick comments (6)
web/src/components/Navigation.jsx (2)

13-15: Decorative icon a11y looks good; optionally render icon conditionally

Good call adding aria-hidden on the icon. Optionally, avoid rendering an empty when iconClass is falsy.

-      <i className={props.iconClass || ''} aria-hidden='true' />
+      {props.iconClass && <i className={props.iconClass} aria-hidden='true' />}

21-21: Label the navigation landmark

Add an aria-label so screen readers can identify this as the primary navigation.

-    <nav className='hidden lg:col-span-2 lg:block'>
+    <nav className='hidden lg:col-span-2 lg:block' aria-label='Primary navigation'>
web/src/pages/Settings/index.jsx (2)

16-16: Consider removing unnecessary state mutation.

The gen state is now a constant and doesn't seem to be used for triggering re-fetches. If it's not needed for cache invalidation, consider removing it entirely.

-const [gen] = useState(0);

And update the query key:

-const { isLoading, data: fetchedSettings } = useQuery(`settings/${gen}`, async () => {
+const { isLoading, data: fetchedSettings } = useQuery('settings', async () => {

131-131: Minor: Updated dependency array includes unused formRef.

The formRef in the dependency array is unnecessary since refs don't trigger re-renders when their current value changes.

-[setFormData, formRef, formData],
+[setFormData, formData],
web/src/components/OverviewChart.jsx (2)

131-149: Consider adding error handling for Chart.js instantiation.

The chart creation could fail if the canvas element is corrupted or if Chart.js encounters an initialization error. Consider wrapping the chart creation in a try-catch block.

Apply this diff to add error handling:

   useEffect(() => {
     if (!ref.current) return;
 
-    const chartData = getChartData(machine.value.history);
-    const newChart = new Chart(ref.current, chartData);
-    setChart(newChart);
+    try {
+      const chartData = getChartData(machine.value.history);
+      const newChart = new Chart(ref.current, chartData);
+      setChart(newChart);
 
-    // Cleanup function to destroy chart on unmount
-    return () => {
-      if (newChart) {
-        newChart.destroy();
-      }
-    };
+      // Cleanup function to destroy chart on unmount
+      return () => {
+        if (newChart) {
+          newChart.destroy();
+        }
+      };
+    } catch (error) {
+      console.error('Failed to initialize chart:', error);
+    }
   }, []); // Empty dependency array - only run on mount

161-196: Consider throttling resize events for better performance.

While using 'none' mode for updates helps, frequent resize events could still impact performance. Consider throttling the resize handler.

Add a throttle utility at the top of the file:

const throttle = (func, delay) => {
  let timeout;
  let lastRan;
  return (...args) => {
    if (!lastRan) {
      func(...args);
      lastRan = Date.now();
    } else {
      clearTimeout(timeout);
      timeout = setTimeout(() => {
        if ((Date.now() - lastRan) >= delay) {
          func(...args);
          lastRan = Date.now();
        }
      }, delay - (Date.now() - lastRan));
    }
  };
};

Then apply this diff to throttle the resize handler:

-    const handleResize = () => {
+    const handleResize = throttle(() => {
       const isSmallScreen = window.innerWidth < 640;
 
       // Update legend font size
       chart.options.plugins.legend.labels.font.size = isSmallScreen ? 10 : 12;
 
       // Update title font size
       chart.options.plugins.title.font.size = isSmallScreen ? 14 : 16;
 
       // Update axis font sizes
       chart.options.scales.y.ticks.font.size = isSmallScreen ? 10 : 12;
       chart.options.scales.y1.ticks.font.size = isSmallScreen ? 10 : 12;
       chart.options.scales.x.ticks.font.size = isSmallScreen ? 10 : 12;
 
       // Update maxTicksLimit for x-axis
       chart.options.scales.x.ticks.maxTicksLimit = isSmallScreen ? 5 : 10;
 
       // Update the chart to apply changes
       chart.update('none'); // Use 'none' mode for better performance
-    };
+    }, 250);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 63c4201 and 08c2e5b.

📒 Files selected for processing (8)
  • web/src/components/Navigation.jsx (1 hunks)
  • web/src/components/OverviewChart.jsx (6 hunks)
  • web/src/pages/Home/ProcessControls.jsx (2 hunks)
  • web/src/pages/Home/index.jsx (1 hunks)
  • web/src/pages/ProfileEdit/StandardProfileForm.jsx (5 hunks)
  • web/src/pages/Settings/index.jsx (6 hunks)
  • web/src/pages/ShotHistory/HistoryChart.jsx (3 hunks)
  • web/src/pages/ShotHistory/index.jsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • web/src/pages/ShotHistory/HistoryChart.jsx
  • web/src/pages/ShotHistory/index.jsx
  • web/src/pages/ProfileEdit/StandardProfileForm.jsx
🧰 Additional context used
🧬 Code Graph Analysis (5)
web/src/components/Navigation.jsx (1)
web/src/components/Header.jsx (2)
  • useLocation (5-5)
  • className (6-7)
web/src/components/OverviewChart.jsx (2)
web/src/pages/ShotHistory/HistoryChart.jsx (3)
  • ref (111-111)
  • chartData (112-112)
  • chart (110-110)
web/src/services/ApiService.js (2)
  • machine (193-207)
  • machine (193-207)
web/src/pages/Home/ProcessControls.jsx (6)
web/src/components/ProcessInfo.jsx (1)
  • processInfo (4-4)
web/src/pages/Home/index.jsx (3)
  • apiService (23-23)
  • mode (50-50)
  • changeMode (41-49)
web/src/pages/ShotHistory/index.jsx (1)
  • apiService (32-32)
web/src/index.jsx (1)
  • apiService (23-23)
web/src/pages/ProfileList/index.jsx (1)
  • apiService (210-210)
web/src/services/ApiService.js (2)
  • ApiServiceContext (191-191)
  • ApiServiceContext (191-191)
web/src/pages/Settings/index.jsx (5)
web/src/utils/themeManager.js (2)
  • getStoredTheme (4-12)
  • handleThemeChange (48-51)
web/src/utils/dashboardManager.js (2)
  • setDashboardLayout (16-37)
  • setDashboardLayout (16-37)
web/src/components/Spinner.jsx (1)
  • Spinner (1-16)
web/src/config/zones.js (2)
  • timezones (1-463)
  • timezones (1-463)
web/src/pages/Settings/PluginCard.jsx (1)
  • PluginCard (3-222)
web/src/pages/Home/index.jsx (6)
web/src/utils/dashboardManager.js (4)
  • setDashboardLayout (16-37)
  • setDashboardLayout (16-37)
  • getDashboardLayout (3-14)
  • getDashboardLayout (3-14)
web/src/pages/Home/ProcessControls.jsx (2)
  • apiService (72-72)
  • ProcessControls (65-252)
web/src/index.jsx (1)
  • apiService (23-23)
web/src/services/ApiService.js (4)
  • ApiServiceContext (191-191)
  • ApiServiceContext (191-191)
  • machine (193-207)
  • machine (193-207)
web/src/components/Card.jsx (1)
  • Card (1-45)
web/src/components/OverviewChart.jsx (1)
  • OverviewChart (131-203)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: deploy
  • GitHub Check: test
  • GitHub Check: test
🔇 Additional comments (30)
web/src/pages/Settings/index.jsx (17)

8-10: LGTM! Clean integration of theme and dashboard management.

The imports properly utilize the theme and dashboard utility modules with clean, focused function imports.


18-18: LGTM! Theme state management properly initialized.

The currentTheme state correctly tracks the current theme selection and is properly initialized from storage.


37-37: Good default value handling for dashboard layout.

The fallback to 'process-first' ensures consistent behavior when the setting is not yet configured.


45-48: LGTM! Proper theme initialization from storage.

The useEffect correctly initializes the theme state from localStorage using the theme manager utility.


50-98: Enhanced change handler with dashboard layout support.

The updated onChange function properly handles the new dashboard layout control by calling setDashboardLayout() to persist changes.


135-142: LGTM! Improved export implementation with template literals.

The updated export function uses modern string interpolation and maintains the same functionality while being more readable.


148-148: LGTM! Modern arrow function syntax.

The upload handler now uses cleaner arrow function syntax while maintaining the same functionality.


158-161: LGTM! Updated loading state with proper styling.

The loading spinner now uses DaisyUI classes with proper flex layout and centered positioning.


165-190: LGTM! Clean header layout with improved button styling.

The new header layout uses proper flex positioning and DaisyUI button styling. The import/export functionality is preserved with better visual hierarchy.


192-329: LGTM! Well-structured card-based layout for core settings.

The Temperature settings and User preferences cards are well-organized with proper form controls and DaisyUI styling. The grid layout provides good visual separation.


331-375: LGTM! Excellent theme and dashboard layout controls.

The Web Settings card properly integrates theme management with controlled select components. The dashboard layout control correctly calls setDashboardLayout() for persistence.


377-456: LGTM! Comprehensive system preferences card.

The System preferences card includes all necessary network and time settings with proper form controls and validation attributes.


458-548: LGTM! Detailed machine configuration settings.

The Machine settings card provides comprehensive hardware configuration options with appropriate input types, placeholders, and validation constraints.


550-633: LGTM! Well-organized display settings with proper state management.

The Display settings card correctly handles the standby display toggle logic and properly disables the brightness input when standby display is disabled.


635-762: LGTM! Conditional LED control settings with proper layout.

The Sunrise Settings card is properly conditionally rendered based on LED control capability and uses a well-structured grid layout for color controls.


764-766: LGTM! Clean plugin integration.

The Plugins card properly integrates the PluginCard component with the existing form data and change handlers.


769-793: LGTM! Improved footer layout with proper button styling.

The footer section uses proper flex layout, DaisyUI styling, and maintains all the necessary form submission functionality including the restart option.

web/src/components/OverviewChart.jsx (3)

5-129: LGTM!

The getChartData function properly configures Chart.js with responsive settings and appropriate scales. The responsive font sizing and dynamic tick formatting are well-implemented.


151-159: LGTM!

The chart update logic correctly handles data changes and guards against null chart instances.


198-203: LGTM!

The responsive container with flex layout and min-height constraints ensures proper chart rendering across different screen sizes.

web/src/pages/Home/ProcessControls.jsx (8)

1-14: LGTM!

Clean imports and utility functions are well-structured.


16-63: LGTM!

The BrewProgress component has good visual hierarchy and responsive design with appropriate text sizing for different screen sizes.


105-123: LGTM!

The button click handlers and icon logic are clear and follow a logical state progression.


125-147: LGTM!

The tab-based mode selector with responsive styling provides a clean UI for mode switching.


178-223: LGTM!

The conditional rendering logic for expanded and collapsed states is well-organized and provides appropriate user feedback for each mode.


225-252: LGTM!

The brew target selector and action button implementation is clean and follows consistent styling patterns.


254-260: LGTM!

PropTypes validation is properly defined for all required props.


149-176: Add null checks for pressure values to prevent runtime errors.

The pressure display uses optional chaining for toFixed(0) but still attempts to call the method on potentially undefined values, which could cause runtime errors.

Apply this diff to add proper null checks:

           <span className='text-base-content'>
-            {status.value.currentPressure?.toFixed(0) || 0} /{' '}
-            {status.value.targetPressure?.toFixed(0) || 0} bar
+            {status.value.currentPressure != null ? status.value.currentPressure.toFixed(0) : 0} /{' '}
+            {status.value.targetPressure != null ? status.value.targetPressure.toFixed(0) : 0} bar
           </span>

Likely an incorrect or invalid review comment.

web/src/pages/Home/index.jsx (2)

41-50: LGTM!

The mode change callback is properly memoized and correctly uses the ApiServiceContext.


52-89: LGTM!

The responsive grid layout with conditional rendering based on dashboard layout preference is well-implemented. The Card components are properly configured with appropriate responsive breakpoints.

Comment on lines +22 to +36
<MenuItem label='Dashboard' link='/' iconClass='fa fa-home' />
<hr className='h-5 border-0' />
<div className='space-y-1.5'>
<MenuItem label='Profiles' link='/profiles' iconClass='fa fa-list' />
<MenuItem label='Shot History' link='/history' iconClass='fa fa-timeline' />
</div>
<hr className="h-5 border-0" />
<div class="space-y-1.5">
<MenuItem label="PID Autotune" link="/pidtune" iconClass="fa fa-temperature-half" />
<MenuItem label="Bluetooth Scales" link="/scales" iconClass="fa-brands fa-bluetooth-b" />
<MenuItem label="Settings" link="/settings" iconClass="fa fa-cog" />
<hr className='h-5 border-0' />
<div className='space-y-1.5'>
<MenuItem label='PID Autotune' link='/pidtune' iconClass='fa fa-temperature-half' />
<MenuItem label='Bluetooth Scales' link='/scales' iconClass='fa-brands fa-bluetooth-b' />
<MenuItem label='Settings' link='/settings' iconClass='fa fa-cog' />
</div>
<hr class="h-5 border-0" />
<div class="space-y-1.5">
<MenuItem label="System & Updates" link="/ota" iconClass="fa fa-rotate" />
<hr className='h-5 border-0' />
<div className='space-y-1.5'>
<MenuItem label='System & Updates' link='/ota' iconClass='fa fa-rotate' />
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Normalize to Font Awesome 6 prefixes and valid free icon names

The menu mixes legacy FA4-style classes (fa fa-*) and uses names that don’t exist in FA6 free (e.g., fa-timeline, fa-rotate). Align with FA6 free prefixes to avoid missing glyphs.

-      <MenuItem label='Dashboard' link='/' iconClass='fa fa-home' />
+      <MenuItem label='Dashboard' link='/' iconClass='fa-solid fa-house' />
@@
-        <MenuItem label='Profiles' link='/profiles' iconClass='fa fa-list' />
-        <MenuItem label='Shot History' link='/history' iconClass='fa fa-timeline' />
+        <MenuItem label='Profiles' link='/profiles' iconClass='fa-solid fa-list' />
+        <MenuItem label='Shot History' link='/history' iconClass='fa-solid fa-clock-rotate-left' />
@@
-        <MenuItem label='PID Autotune' link='/pidtune' iconClass='fa fa-temperature-half' />
+        <MenuItem label='PID Autotune' link='/pidtune' iconClass='fa-solid fa-temperature-half' />
         <MenuItem label='Bluetooth Scales' link='/scales' iconClass='fa-brands fa-bluetooth-b' />
-        <MenuItem label='Settings' link='/settings' iconClass='fa fa-cog' />
+        <MenuItem label='Settings' link='/settings' iconClass='fa-solid fa-gear' />
@@
-        <MenuItem label='System & Updates' link='/ota' iconClass='fa fa-rotate' />
+        <MenuItem label='System & Updates' link='/ota' iconClass='fa-solid fa-arrows-rotate' />

Run this to find remaining legacy usages across the repo and ensure consistency with FA6:


🏁 Script executed:

#!/bin/bash
# Find legacy FA4-style usages and potential invalid icon names
set -euo pipefail
echo "Legacy FA4 'fa ' prefix (excluding fa-brands/fa-solid/fa-regular):"
rg -n "class(Name)?=.[^'\"]*\\bfa\\s(?!brands|solid|regular)\\b" -S || true

echo
echo "Potentially invalid FA icon names seen in code (timeline|rotate without arrows):"
rg -n "fa-((timeline)|(rotate)(?!-))" -S || true

echo
echo "All FA class usages for manual inspection:"
rg -n "class(Name)?=.[^'\"]*fa(-brands|-solid|-regular)?\\sfa-[a-z0-9-]+" -S || true

Length of output: 4107


Normalize Font Awesome 6 usage (replace legacy "fa" classes and invalid icon names) — action required

Ripgrep shows many legacy "fa" usages across the codebase. Replace plain "fa" (FA4) with the appropriate FA6 prefix (fa-solid / fa-regular / fa-brands) and verify each icon name exists in Font Awesome Free 6.

Files/lines that need attention (from repo scan):

  • web/src/components/Header.jsx:50, 61
  • web/src/pages/ProfileEdit/StandardProfileForm.jsx:118, 137, 243
  • web/src/pages/ProfileEdit/ProfileTypeSelection.jsx:20, 36
  • web/src/pages/ShotHistory/HistoryCard.jsx:31, 40, 47, 52
  • web/src/pages/Settings/index.jsx:174, 181
  • web/src/pages/ProfileList/ProfileAddCard.jsx:10
  • web/src/pages/Home/ProcessControls.jsx:151, 159, 173, 232, 239
  • web/src/pages/ProfileList/index.jsx:123, 130, 137, 144, 151, 186, 348, 356

Suggested replacements for the Navigation snippet (update iconClass to FA6 prefixes / valid names):

-      <MenuItem label='Dashboard' link='/' iconClass='fa fa-home' />
+      <MenuItem label='Dashboard' link='/' iconClass='fa-solid fa-house' />
@@
-        <MenuItem label='Profiles' link='/profiles' iconClass='fa fa-list' />
-        <MenuItem label='Shot History' link='/history' iconClass='fa fa-timeline' />
+        <MenuItem label='Profiles' link='/profiles' iconClass='fa-solid fa-list' />
+        <MenuItem label='Shot History' link='/history' iconClass='fa-solid fa-clock-rotate-left' />
@@
-        <MenuItem label='PID Autotune' link='/pidtune' iconClass='fa fa-temperature-half' />
+        <MenuItem label='PID Autotune' link='/pidtune' iconClass='fa-solid fa-temperature-half' />
         <MenuItem label='Bluetooth Scales' link='/scales' iconClass='fa-brands fa-bluetooth-b' />
-        <MenuItem label='Settings' link='/settings' iconClass='fa fa-cog' />
+        <MenuItem label='Settings' link='/settings' iconClass='fa-solid fa-gear' />
@@
-        <MenuItem label='System & Updates' link='/ota' iconClass='fa fa-rotate' />
+        <MenuItem label='System & Updates' link='/ota' iconClass='fa-solid fa-arrows-rotate' />

Run this improved scan locally to find remaining legacy usages and to extract unique fa-* tokens for manual verification:

#!/bin/bash
set -euo pipefail
echo "All lines containing 'fa' in class/className:"
rg -n "class(Name)?=.[^'\"\n]*\bfa\b" -S || true

echo
echo "Lines that likely still use legacy FA4 (exclude fa-solid/fa-regular/fa-brands):"
rg -n "class(Name)?=.[^'\"\n]*\bfa\b" -S | rg -v "fa-(brands|solid|regular)" || true

echo
echo "Unique fa-* tokens found (inspect these against FA6 Free):"
rg -n "class(Name)?=.[^'\"\n]*\bfa\b" -S | sed -E 's/^[^:]+:[0-9]+://' | grep -oE "fa-[a-z0-9-]+" | sort -u || true

Notes:

  • Confirm each replacement icon exists in Font Awesome Free 6 before committing (some FA4 names were renamed).
  • Keep fa-brands usage for brand icons (GitHub, Discord) and use fa-solid/fa-regular for UI glyphs.

Comment on lines +65 to +75
const ProcessControls = props => {
// brew is true when mode equals 1 (Brew mode), false otherwise
const { brew, mode, changeMode } = props;
const brewTarget = status.value.brewTarget;
const processInfo = status.value.process;
const active = !!processInfo?.a;
const finished = !!processInfo && !active;
const apiService = useContext(ApiServiceContext);

// Determine if we should show expanded view
const shouldExpand = brew && (active || finished || (brew && !active && !finished));
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Validate the shouldExpand logic for all mode transitions.

The shouldExpand logic on line 75 seems to have redundant conditions. When brew is true and neither active nor finished, the condition brew && !active && !finished is already satisfied, making the entire expression simplify to just brew.


🏁 Script executed:

#!/bin/bash
# Check if there are any other usages of shouldExpand that might explain the logic
rg -A 5 "shouldExpand"

Length of output: 1490


Simplify shouldExpand — the expression is a tautology and should be reduced to brew.

The current expression always evaluates to brew (the inner clause is true for all combinations of active/finished when brew is true). Ripgrep shows shouldExpand is only used in web/src/pages/Home/ProcessControls.jsx.

Files to change:

  • web/src/pages/Home/ProcessControls.jsx — replace the declaration

Suggested replacement:

- const shouldExpand = brew && (active || finished || (brew && !active && !finished));
+ const shouldExpand = !!brew;

Optional: while not required, you can also replace other explicit ... && brew && ... checks with shouldExpand && ... for clarity.

📝 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
const ProcessControls = props => {
// brew is true when mode equals 1 (Brew mode), false otherwise
const { brew, mode, changeMode } = props;
const brewTarget = status.value.brewTarget;
const processInfo = status.value.process;
const active = !!processInfo?.a;
const finished = !!processInfo && !active;
const apiService = useContext(ApiServiceContext);
// Determine if we should show expanded view
const shouldExpand = brew && (active || finished || (brew && !active && !finished));
const ProcessControls = props => {
// brew is true when mode equals 1 (Brew mode), false otherwise
const { brew, mode, changeMode } = props;
const brewTarget = status.value.brewTarget;
const processInfo = status.value.process;
const active = !!processInfo?.a;
const finished = !!processInfo && !active;
const apiService = useContext(ApiServiceContext);
// Determine if we should show expanded view
const shouldExpand = !!brew;
🤖 Prompt for AI Agents
In web/src/pages/Home/ProcessControls.jsx around lines 65 to 75, the
shouldExpand expression is a tautology; replace the current complex expression
with a simple boolean that equals brew (i.e., set shouldExpand = brew). Update
any nearby uses that redundantly check brew when they can use shouldExpand for
clarity, but the primary fix is to replace the existing declaration with a
single-line assignment of shouldExpand to brew.

silverfoxy pushed a commit to silverfoxy/gaggimate that referenced this pull request Aug 15, 2025
* Add Daisy Ui (jniebuhr#339)

* Initial Daisy Ui transition and 4 themes to start

* Update Ui

* Fix SonarCloud issues

* Remove Computed Single line Chart Register

* Use JSX syntax

* Apply Bunny Fixes

* Use card component

* Use Card

* Fix active and hover states

* Format and Style Nav Bar Better

* Move profiles over(need to fix spacing between lables)

* Fix Mobile Layout

* Fix mobile layout

* Listen to the bunny

* Update style.css

* Convert Scale and Autotune

* Delete dashboard.html

* I like the titles on the left better

* fix: Fix some bugs in the UI

* feat: Change overview chart readability

* Dasiy Ui (jniebuhr#347)

* Migrate Settings to DaisyUi and removed unused error handling

* Fix eslint error var to const

* Fix eslint

* Remove Unused container-fluid and create context. Run Format. Migrate Process Info to daisy

* Change to aria label

* Use Daisy UI tool tips

* Remove Unused Styles

* Use Semantic Colors

* Add Theme Manager to settings

Right now this is a separate card but can be changed whenever

* Standardize spacing and Padding as well as component usage

* Fix Profile pages and use consistent styling

* Improve Dark style ?

* add aria

* Fix Save

* Consistent Styles. Use 10/5 Grid Layout

* Fix Button Spacing

* Prevent race condition

* Fix button Position and use 5/10 layout on home page

* fix card class again

* javascript:void(0)

* simple bp

* add theme urls

* add safelist

* fix profile cards

* FMT and When saving profile rout back to profile list

* Clarify Settings Card

* Fix Chart Height

* This needs a bit of testing but should fix card duplication

* Add Switching the layout of the home page

* Format and Fix Lint errors

* Fix some code reviews

* Update index.jsx

* Split Settings Page up a bit

* Clean up Inputs

* Fix Radix

---------

Co-authored-by: Jochen Niebuhr <kontakt@ju-hh.de>

* feat: Add landscape dashboard layout, fix profile edit

---------

Co-authored-by: DreadPirateRoberts <46958197+ManfredRichthofen@users.noreply.github.com>
@coderabbitai coderabbitai bot mentioned this pull request Aug 17, 2025
@jniebuhr jniebuhr added this to the v1.6.0 milestone Aug 17, 2025
mikld86 pushed a commit to mikld86/GMTiredandForked that referenced this pull request Sep 3, 2025
* Add Daisy Ui (jniebuhr#339)

* Initial Daisy Ui transition and 4 themes to start

* Update Ui

* Fix SonarCloud issues

* Remove Computed Single line Chart Register

* Use JSX syntax

* Apply Bunny Fixes

* Use card component

* Use Card

* Fix active and hover states

* Format and Style Nav Bar Better

* Move profiles over(need to fix spacing between lables)

* Fix Mobile Layout

* Fix mobile layout

* Listen to the bunny

* Update style.css

* Convert Scale and Autotune

* Delete dashboard.html

* I like the titles on the left better

* fix: Fix some bugs in the UI

* feat: Change overview chart readability

* Dasiy Ui (jniebuhr#347)

* Migrate Settings to DaisyUi and removed unused error handling

* Fix eslint error var to const

* Fix eslint

* Remove Unused container-fluid and create context. Run Format. Migrate Process Info to daisy

* Change to aria label

* Use Daisy UI tool tips

* Remove Unused Styles

* Use Semantic Colors

* Add Theme Manager to settings

Right now this is a separate card but can be changed whenever

* Standardize spacing and Padding as well as component usage

* Fix Profile pages and use consistent styling

* Improve Dark style ?

* add aria

* Fix Save

* Consistent Styles. Use 10/5 Grid Layout

* Fix Button Spacing

* Prevent race condition

* Fix button Position and use 5/10 layout on home page

* fix card class again

* javascript:void(0)

* simple bp

* add theme urls

* add safelist

* fix profile cards

* FMT and When saving profile rout back to profile list

* Clarify Settings Card

* Fix Chart Height

* This needs a bit of testing but should fix card duplication

* Add Switching the layout of the home page

* Format and Fix Lint errors

* Fix some code reviews

* Update index.jsx

* Split Settings Page up a bit

* Clean up Inputs

* Fix Radix

---------

Co-authored-by: Jochen Niebuhr <kontakt@ju-hh.de>

* feat: Add landscape dashboard layout, fix profile edit

---------

Co-authored-by: DreadPirateRoberts <46958197+ManfredRichthofen@users.noreply.github.com>
KT-0001 pushed a commit to KT-0001/gaggimate that referenced this pull request Nov 30, 2025
* Add Daisy Ui (jniebuhr#339)

* Initial Daisy Ui transition and 4 themes to start

* Update Ui

* Fix SonarCloud issues

* Remove Computed Single line Chart Register

* Use JSX syntax

* Apply Bunny Fixes

* Use card component

* Use Card

* Fix active and hover states

* Format and Style Nav Bar Better

* Move profiles over(need to fix spacing between lables)

* Fix Mobile Layout

* Fix mobile layout

* Listen to the bunny

* Update style.css

* Convert Scale and Autotune

* Delete dashboard.html

* I like the titles on the left better

* fix: Fix some bugs in the UI

* feat: Change overview chart readability

* Dasiy Ui (jniebuhr#347)

* Migrate Settings to DaisyUi and removed unused error handling

* Fix eslint error var to const

* Fix eslint

* Remove Unused container-fluid and create context. Run Format. Migrate Process Info to daisy

* Change to aria label

* Use Daisy UI tool tips

* Remove Unused Styles

* Use Semantic Colors

* Add Theme Manager to settings

Right now this is a separate card but can be changed whenever

* Standardize spacing and Padding as well as component usage

* Fix Profile pages and use consistent styling

* Improve Dark style ?

* add aria

* Fix Save

* Consistent Styles. Use 10/5 Grid Layout

* Fix Button Spacing

* Prevent race condition

* Fix button Position and use 5/10 layout on home page

* fix card class again

* javascript:void(0)

* simple bp

* add theme urls

* add safelist

* fix profile cards

* FMT and When saving profile rout back to profile list

* Clarify Settings Card

* Fix Chart Height

* This needs a bit of testing but should fix card duplication

* Add Switching the layout of the home page

* Format and Fix Lint errors

* Fix some code reviews

* Update index.jsx

* Split Settings Page up a bit

* Clean up Inputs

* Fix Radix

---------

Co-authored-by: Jochen Niebuhr <kontakt@ju-hh.de>

* feat: Add landscape dashboard layout, fix profile edit

---------

Co-authored-by: DreadPirateRoberts <46958197+ManfredRichthofen@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants