Skip to content

feat(ui): sidebar navigation redesign and global style polish#768

Open
Serokz wants to merge 6 commits into
jniebuhr:masterfrom
Serokz:pr-nav-styling
Open

feat(ui): sidebar navigation redesign and global style polish#768
Serokz wants to merge 6 commits into
jniebuhr:masterfrom
Serokz:pr-nav-styling

Conversation

@Serokz

@Serokz Serokz commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR modernizes the sidebar navigation layout and polishes global CSS aesthetics, introducing clean interactive transitions, improved color tokens, and font-loading optimizations.

What's Changed

  • Redesigned Sidebar (web/src/components/Navigation.jsx): Reworked navigation to always-mounted elements with clean slide transitions, and embedded custom SVG logo (GmLogoIcon.jsx). The link structure is kept identical to master to ensure backward compatibility and prevent dead links.
  • Global Styles (web/src/style.css, web/index.html): Polished visual variables (color tokens, focus rings, progress bar animations) and deferred Google Fonts loading in index.html to eliminate render-blocking CSS imports.
  • Component Styling (Card.jsx, Chart.jsx, etc.): Added a customizable surface prop to Card.jsx and added a ResizeObserver to charts to dynamically handle size changes (e.g. within accordion structures).

This is part of a series of independent, story-driven PRs split from the larger frontend modernization effort.

Summary by CodeRabbit

  • New Features

    • Added navigation state persistence across sessions.
    • Introduced enhanced tooltip functionality with improved styling options.
  • Improvements

    • Chart components now adapt responsively to container resizing.
    • Mobile navigation and collapse controls enhanced with improved visual feedback.
    • Responsive padding adjustments for better layout scaling.
    • Theme colors and styling refinements across light and dark modes.
  • Style

    • Expanded animations and transitions with accessibility support for reduced-motion preferences.

@cla-bot cla-bot Bot added the cla-signed label Jun 14, 2026
@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 033a6bfb-1667-4891-b440-b78f88da46b1

📥 Commits

Reviewing files that changed from the base of the PR and between 3e32ad4 and 5182a66.

📒 Files selected for processing (1)
  • web/src/components/Navigation.jsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/src/components/Navigation.jsx

📝 Walkthrough

Walkthrough

Refactors the web UI: introduces a GmLogoIcon SVG component, overhauls the Navigation sidebar to use CSS-driven collapsed/expanded transitions (replacing conditional rendering), adds localStorage persistence for nav state, adds a surface prop to Card/Section, extends Tooltip with a className prop, adds ResizeObserver to Chart, and introduces 400+ lines of CSS animations and transitions.

Changes

Web UI Visual Refresh

Layer / File(s) Summary
Theme token and font updates
web/src/style.css
DaisyUI --color-primary and --color-primary-content values updated for light and nord themes; --font-logo fallback stack widened to include system-ui and web-safe fonts.
GmLogoIcon component and Tooltip className prop
web/src/components/GmLogoIcon.jsx, web/src/components/Tooltip.jsx
New GmLogoIcon SVG component uses useId() for unique clipPath ids and accepts sizing/style props. Tooltip gains a className prop applied to the trigger span.
Card/Section component refactor
web/src/components/Card.jsx
Component renamed to Section (exported as both Section and Card), gains a surface boolean prop that switches background class, header layout simplified, body padding changed to p-5, shadow-xl removed.
Navigation sidebar overhaul and App state persistence
web/src/components/Navigation.jsx, web/src/index.jsx
MenuItem gains onHover, Tooltip wrapping, and exact-match root route detection. Backdrop and footer/collapse controls converted from conditional renders to always-mounted CSS-toggled elements. Logo area replaced with GmLogoIcon + /logo.svg. App changed to default export; navCollapsed reads/writes localStorage key gm_nav_collapsed; new open-mobile-nav event listener forces expanded state; responsive padding added.
CSS animation and transition system
web/src/style.css
Adds scrollbar-none, touch-action rule, @custom-variant hmd, mobile morph/profile-card accordion/action-dropdown/input focus/mobile search/sidebar/progressive-loading transition and animation utilities — all with prefers-reduced-motion overrides.
Chart ResizeObserver and minor styling tweaks
web/src/components/Chart.jsx, web/src/components/VisualizerUploadModal.jsx, web/src/pages/Home/ModeTab.jsx
ChartComponent adds ResizeObserver for container resize events with cleanup on unmount. VisualizerUploadModal shadow changed from shadow-xl to shadow-lg. ModeTab active state drops shadow-sm.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • jniebuhr/gaggimate#660: Modifies the same Navigation.jsx expand/collapse behavior and index.jsx nav collapsed persistence that this PR overhauls.
  • jniebuhr/gaggimate#378: Directly related — both PRs add chart.resize() calls inside Chart.jsx's resize-handling effect.
  • jniebuhr/gaggimate#557: Introduced the Floating UI Tooltip component that this PR extends with the new className prop.

Poem

🐇 Hoppity-hop through the sidebar I go,
CSS transitions make it all flow!
GmLogoIcon gleams with a clip-path unique,
ResizeObserver watches so charts won't freak.
The Card becomes Section, the nav learns to slide —
A bunny-approved polish on every side! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main changes: sidebar navigation redesign and global style polish are the primary focus across multiple components and the stylesheet.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

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

Comment thread web/index.html Fixed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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

Inline comments:
In `@web/src/index.jsx`:
- Around line 60-66: Guard the localStorage operations to prevent crashes when
storage is unavailable or contains malformed data. Wrap the
JSON.parse(localStorage.getItem('gm_nav_collapsed')) call in the useState
initialization with a try-catch block, defaulting to false if any error occurs
(storage unavailable, parse failure, etc.). Similarly, wrap the
localStorage.setItem call in the useEffect hook with a try-catch block to safely
handle any storage write errors without crashing the app. This ensures the nav
collapse state can gracefully degrade when storage operations fail.

In `@web/src/style.css`:
- Line 423: The stylelint configuration extends stylelint-config-standard-scss
which has the at-rule-no-unknown rule enabled by default, causing Tailwind v4
directives like `@theme`, `@source`, and `@custom-variant` to be flagged as unknown
at-rules and block CI builds. Update the at-rule-no-unknown rule in the
configuration to include an ignoreAtRules array that lists the Tailwind v4
at-rules (theme, source, custom-variant, apply, utility, variant), or
alternatively replace the current extended configuration with
stylelint-config-tailwindcss which provides built-in support for Tailwind v4
syntax.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e546be75-de64-49e6-b419-168c687c61d8

📥 Commits

Reviewing files that changed from the base of the PR and between 591abe7 and 9d4b69c.

📒 Files selected for processing (10)
  • web/index.html
  • web/src/components/Card.jsx
  • web/src/components/Chart.jsx
  • web/src/components/GmLogoIcon.jsx
  • web/src/components/Navigation.jsx
  • web/src/components/Tooltip.jsx
  • web/src/components/VisualizerUploadModal.jsx
  • web/src/index.jsx
  • web/src/pages/Home/ModeTab.jsx
  • web/src/style.css

Comment thread web/src/index.jsx
Comment on lines +60 to 66
const [navCollapsed, setNavCollapsed] = useState(
JSON.parse(localStorage.getItem('gm_nav_collapsed') || 'false'),
);

useEffect(() => {
const storage = globalThis.window?.localStorage;
if (!storage) return;

try {
storage.setItem(DESKTOP_NAV_COLLAPSED_STORAGE_KEY, String(navCollapsed));
} catch {
// Ignore storage write failures so the navigation still works in restricted browsers.
}
localStorage.setItem('gm_nav_collapsed', JSON.stringify(navCollapsed));
}, [navCollapsed]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard persisted nav-state reads/writes to prevent boot-time crashes.

Direct JSON.parse(localStorage.getItem(...)) and unguarded localStorage.setItem(...) can throw and take down the app when storage is unavailable or value is malformed.

Suggested fix
 export default function App() {
-  const [navCollapsed, setNavCollapsed] = useState(
-    JSON.parse(localStorage.getItem('gm_nav_collapsed') || 'false'),
-  );
+  const [navCollapsed, setNavCollapsed] = useState(() => {
+    try {
+      const raw = globalThis.window?.localStorage?.getItem('gm_nav_collapsed');
+      return raw ? JSON.parse(raw) === true : false;
+    } catch {
+      return false;
+    }
+  });

   useEffect(() => {
-    localStorage.setItem('gm_nav_collapsed', JSON.stringify(navCollapsed));
+    try {
+      globalThis.window?.localStorage?.setItem(
+        'gm_nav_collapsed',
+        JSON.stringify(navCollapsed),
+      );
+    } catch {}
   }, [navCollapsed]);
📝 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 [navCollapsed, setNavCollapsed] = useState(
JSON.parse(localStorage.getItem('gm_nav_collapsed') || 'false'),
);
useEffect(() => {
const storage = globalThis.window?.localStorage;
if (!storage) return;
try {
storage.setItem(DESKTOP_NAV_COLLAPSED_STORAGE_KEY, String(navCollapsed));
} catch {
// Ignore storage write failures so the navigation still works in restricted browsers.
}
localStorage.setItem('gm_nav_collapsed', JSON.stringify(navCollapsed));
}, [navCollapsed]);
const [navCollapsed, setNavCollapsed] = useState(() => {
try {
const raw = globalThis.window?.localStorage?.getItem('gm_nav_collapsed');
return raw ? JSON.parse(raw) === true : false;
} catch {
return false;
}
});
useEffect(() => {
try {
globalThis.window?.localStorage?.setItem(
'gm_nav_collapsed',
JSON.stringify(navCollapsed),
);
} catch {}
}, [navCollapsed]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/index.jsx` around lines 60 - 66, Guard the localStorage operations to
prevent crashes when storage is unavailable or contains malformed data. Wrap the
JSON.parse(localStorage.getItem('gm_nav_collapsed')) call in the useState
initialization with a try-catch block, defaulting to false if any error occurs
(storage unavailable, parse failure, etc.). Similarly, wrap the
localStorage.setItem call in the useEffect hook with a try-catch block to safely
handle any storage write errors without crashing the app. This ensures the nav
collapse state can gracefully degrade when storage operations fail.

Comment thread web/src/style.css

@theme {
--font-logo: 'Montserrat', sans-serif;
--font-logo: 'Montserrat', system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify Tailwind v4 at-rule usage in CSS
rg -n "`@custom-variant`|`@plugin`|`@theme`|`@source`|--font-logo" web/src/style.css

# Inspect stylelint config files/rules for unknown at-rule handling
fd -i "stylelint" -t f
fd -i ".stylelintrc" -t f
rg -n "at-rule-no-unknown|scss/at-rule-no-unknown|ignoreAtRules|custom-variant|tailwind" --iglob "*stylelint*" --iglob ".stylelintrc*"

Repository: jniebuhr/gaggimate

Length of output: 407


🏁 Script executed:

# Check package.json for stylelint and Tailwind configuration
cat package.json | head -100

# Search for all stylelint config files more broadly
find . -type f -name "*stylelint*" -o -name ".stylelintrc*" 2>/dev/null | head -20

# Check for stylelint config in common locations
test -f .stylelintrc && echo "Found .stylelintrc" || echo "No .stylelintrc"
test -f .stylelintrc.json && echo "Found .stylelintrc.json" || echo "No .stylelintrc.json"
test -f stylelint.config.js && echo "Found stylelint.config.js" || echo "No stylelint.config.js"
test -f stylelint.config.mjs && echo "Found stylelint.config.mjs" || echo "No stylelint.config.mjs"

# Check if Tailwind is installed and what version
rg '"tailwindcss"' package.json -A 1

Repository: jniebuhr/gaggimate

Length of output: 285


🏁 Script executed:

# Find package.json
fd "package.json" -t f

# Read stylelint config
cat .stylelintrc.json

# Once we find package.json, check Tailwind version
fd "package.json" -t f -x head -50 {} \;

Repository: jniebuhr/gaggimate

Length of output: 2674


🏁 Script executed:

# Check if there's a configuration for ignoreAtRules or at-rule-no-unknown in stylelint config
cat .stylelintrc.json | grep -E "at-rule|ignore|custom-variant|theme|source"

# Check if stylelint-config-standard-scss is mentioned elsewhere
rg "stylelint-config-standard-scss" -t json

# Search web/src/style.css for the actual lines to verify context
sed -n '420,430p' web/src/style.css
sed -n '467,471p' web/src/style.css

# Check if there's a tailwind.config file
fd "tailwind.config" -t f

Repository: jniebuhr/gaggimate

Length of output: 564


🏁 Script executed:

# Check tailwind.config.js for Tailwind v4 configuration
cat web/tailwind.config.js

# Check if there are actual lint errors by looking at CI config or lint output
fd ".github" -t d
fd "eslintignore|stylelintignore" -t f

# Search documentation comments or issues about Tailwind v4 at-rules
rg -i "custom-variant|`@theme`|`@source`" web/src/style.css -B 2 -A 2

Repository: jniebuhr/gaggimate

Length of output: 438


🌐 Web query:

Tailwind CSS v4 @custom-variant @theme @source stylelint configuration ignoreAtRules

💡 Result:

In Tailwind CSS v4, directives such as @theme, @source, and @custom-variant are native CSS-driven features [1][2]. To prevent Stylelint from flagging these as unknown at-rules, you can use the following approaches: Recommended Approach: Use a dedicated Tailwind-aware Stylelint configuration. Projects like stylelint-config-tailwindcss are designed for v4+ and automatically configure Stylelint to recognize Tailwind’s directives and functions [3][4][5]. These configurations integrate into Stylelint's languageOptions, ensuring that directives like @theme, @source, and @custom-variant are treated as valid syntax rather than errors [3][4]. Manual Configuration: If you prefer to configure Stylelint manually, you must add these directives to the ignoreAtRules option within the at-rule-no-unknown rule (or the equivalent SCSS rule if you are using stylelint-scss) [6][7][8]. Example configuration: { "rules": { "at-rule-no-unknown": [ true, { "ignoreAtRules": [ "theme", "source", "custom-variant", "utility", "apply", "variant" ] } ] } } Additionally, if you are using nesting in your CSS (e.g., inside @utility), you may need to add the same directives to the ignoreAtRules option for the nesting-selector-no-missing-scoping-root rule to avoid scoping errors [9]. { "rules": { "nesting-selector-no-missing-scoping-root": [ true, { "ignoreAtRules": ["utility", "custom-variant"] } ] } } Ensure that your Stylelint version is up-to-date (v16.17+ is typically recommended for full compatibility with these configurations) [3][4][5].

Citations:


Configure stylelint to recognize Tailwind v4 at-rules (@theme, @source, @custom-variant).

The .stylelintrc.json extends stylelint-config-standard-scss, which has at-rule-no-unknown enabled by default. This will flag Tailwind v4's native directives as unknown at-rules and block CI. Add an ignoreAtRules list to the at-rule-no-unknown rule:

Fix for .stylelintrc.json
{
  "extends": ["stylelint-config-standard-scss"],
  "rules": {
    "at-rule-no-unknown": [true, { "ignoreAtRules": ["theme", "source", "custom-variant", "apply", "utility", "variant"] }],
    // ... existing rules ...
  }
}

Alternatively, use stylelint-config-tailwindcss for built-in Tailwind v4 support.

🧰 Tools
🪛 Stylelint (17.12.0)

[error] 423-423: Expected "BlinkMacSystemFont" to be "blinkmacsystemfont" (value-keyword-case)

(value-keyword-case)


[error] 423-423: Expected "Roboto" to be "roboto" (value-keyword-case)

(value-keyword-case)

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

In `@web/src/style.css` at line 423, The stylelint configuration extends
stylelint-config-standard-scss which has the at-rule-no-unknown rule enabled by
default, causing Tailwind v4 directives like `@theme`, `@source`, and
`@custom-variant` to be flagged as unknown at-rules and block CI builds. Update
the at-rule-no-unknown rule in the configuration to include an ignoreAtRules
array that lists the Tailwind v4 at-rules (theme, source, custom-variant, apply,
utility, variant), or alternatively replace the current extended configuration
with stylelint-config-tailwindcss which provides built-in support for Tailwind
v4 syntax.

Source: Linters/SAST tools

Comment thread web/index.html Fixed
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
5.4% Duplication on New Code (required ≤ 4%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants