Skip to content

Conversation

@yujonglee
Copy link
Contributor

@yujonglee yujonglee commented Nov 29, 2025

Summary

On Linux, window decorations are removed (decorations(false) in plugins/windows/src/window/v1.rs), leaving no way to close, minimize, or maximize windows. This PR adds macOS-style traffic light buttons for Linux users.

Changes:

  • usePlatform hook for platform detection using @tauri-apps/plugin-os
  • TrafficLights component with close (red), minimize (yellow), maximize (green) buttons
  • Integration into both Settings and Main window headers, only rendered on Linux
  • Rust: Added .minimizable(true) and .maximizable(true) to Settings window builder (was missing, which prevented minimize/maximize from working)

Updates since last revision

  • Added traffic lights to Main window in addition to Settings window
  • Fixed Settings window Rust config: Added missing .minimizable(true) and .maximizable(true) flags to enable those window operations

Testing performed on Linux:

  • Main window: All three buttons (close, minimize, maximize) work correctly
  • Settings window: Buttons are visible but clicks may not be registering properly - needs verification

Review & Testing Checklist for Human

  • Test Settings window traffic lights: I observed the buttons were visible but clicks weren't responding during my testing. Please verify if this is a real issue or testing artifact.
  • Test Main window traffic lights: Verify all three buttons work (close, minimize, toggleMaximize). These worked in my testing.
  • Verify macOS unchanged: Confirm native traffic lights still appear on macOS and the custom buttons don't render.
  • Consider Windows: Windows also has decorations(false) - decide if these buttons should show there too (currently Linux-only).

Recommended test plan:

  1. Build and run on Linux
  2. Test Main window: click each traffic light button
  3. Open Settings window and test each button there
  4. Check devtools console for any permission errors

Notes

The Settings window traffic lights are rendered inside a sidebar Group component with a nested data-tauri-drag-region div. If clicks aren't registering, this may be a hit-testing issue with the drag region. The Main window uses a simpler structure that works correctly.

Link to Devin run: https://app.devin.ai/sessions/6d971ace7142469990f8bf522babf7d1
Requested by: yujonglee (@yujonglee)

On Linux, window decorations are removed, leaving no way to close/minimize/maximize windows. This adds macOS-style traffic light buttons to the settings window header for Linux users.

Changes:
- Add usePlatform hook for platform detection using @tauri-apps/plugin-os
- Add TrafficLights component with close/minimize/maximize buttons
- Integrate TrafficLights into settings layout for Linux only

Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
@devin-ai-integration
Copy link
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@netlify
Copy link

netlify bot commented Nov 29, 2025

Deploy Preview for hyprnote ready!

Name Link
🔨 Latest commit 60428c2
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote/deploys/692a8239a680ec00087f589e
😎 Deploy Preview https://deploy-preview-1988--hyprnote.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 29, 2025

📝 Walkthrough

Walkthrough

Adds a platform detection hook and a Linux-only TrafficLights React component, integrates it into desktop header and settings layout, and enables minimizable/maximizable flags for the Settings window in the native window builder.

Changes

Cohort / File(s) Summary
Platform detection utilities
apps/desktop/src/hooks/usePlatform.ts
New hook re-exporting Platform from @tauri-apps/plugin-os and adding usePlatform(), useIsLinux(), useIsMacos(), useIsWindows() helpers.
Window control component
apps/desktop/src/components/window/traffic-lights.tsx
New React TSX component TrafficLights rendering close/minimize/maximize buttons and calling Tauri window methods; accepts optional className.
Header / settings integration
apps/desktop/src/components/main/body/index.tsx, apps/desktop/src/routes/app/settings/_layout.tsx
Import and use useIsLinux; conditionally render TrafficLights and apply Linux-specific padding/structure in header and settings drag region.
Window flags (native plugin)
plugins/windows/src/window/v1.rs
Settings window builder updated to enable minimizable(true) and maximizable(true) alongside existing flags.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Pay attention to correct Tauri API usage in TrafficLights (getCurrentWebviewWindow, minimize/maximize/toggle, close).
  • Verify usePlatform hook accurately maps and returns Platform across environments and that boolean helpers cover expected values.
  • Confirm conditional rendering preserves drag-region behavior and styling on non-Linux platforms.
  • Check native window flags in v1.rs don't conflict with other window creation logic or platform-specific expectations.

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding macOS-style traffic light buttons specifically for Linux, which directly addresses the core problem of missing window controls on Linux.
Description check ✅ Passed The pull request description clearly explains the problem (no window controls on Linux with decorations removed) and details all changes made, including the usePlatform hook, TrafficLights component, and integration into both Settings and Main window headers.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch devin/1764389371-linux-traffic-lights

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

@netlify
Copy link

netlify bot commented Nov 29, 2025

Deploy Preview for hyprnote-storybook ready!

Name Link
🔨 Latest commit 60428c2
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote-storybook/deploys/692a8239ac576a00083742c2
😎 Deploy Preview https://deploy-preview-1988--hyprnote-storybook.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

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: 0

🧹 Nitpick comments (1)
apps/desktop/src/components/window/traffic-lights.tsx (1)

5-10: Consider caching the window instance.

getCurrentWebviewWindow() is called on every render. If this function isn't internally memoized, consider extracting it with useMemo:

+import { useMemo } from "react";
 import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";

 import { cn } from "@hypr/utils";

 export function TrafficLights({ className }: { className?: string }) {
-  const win = getCurrentWebviewWindow();
+  const win = useMemo(() => getCurrentWebviewWindow(), []);

   const onClose = () => win.close();
   const onMinimize = () => win.minimize();
   const onMaximize = () => win.toggleMaximize();

This prevents unnecessary calls if the Tauri API doesn't cache the result internally.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f7c8d66 and 7da127c.

📒 Files selected for processing (3)
  • apps/desktop/src/components/window/traffic-lights.tsx (1 hunks)
  • apps/desktop/src/hooks/usePlatform.ts (1 hunks)
  • apps/desktop/src/routes/app/settings/_layout.tsx (3 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Avoid creating a bunch of types/interfaces if they are not shared. Especially for function props, just inline them instead.
Never do manual state management for form/mutation. Use useForm (from tanstack-form) and useQuery/useMutation (from tanstack-query) instead for 99% of cases. Avoid patterns like setError.
If there are many classNames with conditional logic, use cn (import from @hypr/utils). It is similar to clsx. Always pass an array and split by logical grouping.
Use motion/react instead of framer-motion.

Files:

  • apps/desktop/src/routes/app/settings/_layout.tsx
  • apps/desktop/src/components/window/traffic-lights.tsx
  • apps/desktop/src/hooks/usePlatform.ts
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.ts: Agent implementations should use TypeScript and follow the established architectural patterns defined in the agent framework
Agent communication should use defined message protocols and interfaces

Files:

  • apps/desktop/src/hooks/usePlatform.ts
🧠 Learnings (2)
📚 Learning: 2025-11-24T16:32:19.706Z
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T16:32:19.706Z
Learning: Applies to **/*.{ts,tsx} : If there are many classNames with conditional logic, use `cn` (import from `hypr/utils`), similar to `clsx`. Always pass an array and split by logical grouping.

Applied to files:

  • apps/desktop/src/routes/app/settings/_layout.tsx
📚 Learning: 2025-11-24T16:32:23.055Z
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T16:32:23.055Z
Learning: Applies to **/*.{ts,tsx} : If there are many classNames with conditional logic, use `cn` (import from `hypr/utils`). It is similar to `clsx`. Always pass an array and split by logical grouping.

Applied to files:

  • apps/desktop/src/routes/app/settings/_layout.tsx
🧬 Code graph analysis (2)
apps/desktop/src/routes/app/settings/_layout.tsx (2)
apps/desktop/src/hooks/usePlatform.ts (1)
  • useIsLinux (9-11)
apps/desktop/src/components/window/traffic-lights.tsx (1)
  • TrafficLights (5-34)
apps/desktop/src/components/window/traffic-lights.tsx (1)
packages/utils/src/cn.ts (1)
  • cn (20-22)
⏰ 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). (10)
  • GitHub Check: Redirect rules - hyprnote-storybook
  • GitHub Check: Header rules - hyprnote-storybook
  • GitHub Check: Pages changed - hyprnote-storybook
  • GitHub Check: Redirect rules - hyprnote
  • GitHub Check: Header rules - hyprnote
  • GitHub Check: Pages changed - hyprnote
  • GitHub Check: ci (linux, depot-ubuntu-22.04-8)
  • GitHub Check: ci (macos, depot-macos-14)
  • GitHub Check: ci (linux, depot-ubuntu-24.04-8)
  • GitHub Check: fmt
🔇 Additional comments (4)
apps/desktop/src/components/window/traffic-lights.tsx (1)

13-32: LGTM: Clean implementation of traffic light buttons.

The component correctly:

  • Uses inline props per guidelines (not shared)
  • Applies cn with an array for className composition
  • Sets data-tauri-drag-region="false" to prevent drag interference
  • Uses proper button types and semantic HTML

The macOS-style colors (#ff5f57 for close, #ffbd2e for minimize, #28c840 for maximize) accurately replicate the native appearance.

apps/desktop/src/routes/app/settings/_layout.tsx (2)

26-27: LGTM: Appropriate imports for platform-specific UI.

The imports are correctly scoped to enable Linux-specific traffic light rendering.

Note: Ensure the critical issue in apps/desktop/src/hooks/usePlatform.ts (invalid React hooks) is addressed first.


190-194: LGTM: Correct conditional rendering for Linux traffic lights.

The implementation properly:

  • Wraps the traffic lights in a drag-region container with consistent height (h-9)
  • Only renders <TrafficLights /> on Linux where native window controls aren't available
  • Maintains proper spacing with px-3

This approach makes sense since macOS already has native traffic light buttons, so they're only needed on Linux.

The logic depends on the platform detection hooks being fixed (see critical issue in apps/desktop/src/hooks/usePlatform.ts).

apps/desktop/src/hooks/usePlatform.ts (1)

5-19: These are not valid React hooks; cache the platform detection.

These functions use the "use" prefix but don't call any React hooks internally, violating React's Rules of Hooks. Additionally, platform() is invoked on every render, which is inefficient since the platform never changes at runtime.

Recommended solution: Cache the platform at module level since it's constant:

-export function usePlatform(): Platform {
-  return platform();
-}
-
-export function useIsLinux(): boolean {
-  return usePlatform() === "linux";
-}
-
-export function useIsMacos(): boolean {
-  return usePlatform() === "macos";
-}
-
-export function useIsWindows(): boolean {
-  return usePlatform() === "windows";
-}
+// Cache at module level since platform never changes at runtime
+const currentPlatform = platform();
+
+export function usePlatform(): Platform {
+  return currentPlatform;
+}
+
+export function useIsLinux(): boolean {
+  return currentPlatform === "linux";
+}
+
+export function useIsMacos(): boolean {
+  return currentPlatform === "macos";
+}
+
+export function useIsWindows(): boolean {
+  return currentPlatform === "windows";
+}

Alternative solution: If you need these to be true React hooks (e.g., for future reactivity), use useMemo:

import { useMemo } from "react";

export function usePlatform(): Platform {
  return useMemo(() => platform(), []);
}

export function useIsLinux(): boolean {
  const p = usePlatform();
  return useMemo(() => p === "linux", [p]);
}
// ... similar for other helpers
⛔ Skipped due to learnings
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T16:32:23.055Z
Learning: Applies to **/*.{ts,tsx} : Avoid creating a bunch of types/interfaces if they are not shared. Especially for function props, just inline them instead.
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T16:32:19.706Z
Learning: Applies to **/*.{ts,tsx} : Avoid creating a bunch of types/interfaces if they are not shared. Especially for function props. Just inline them.

…hts to main window

Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
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: 0

🧹 Nitpick comments (1)
apps/desktop/src/components/main/body/index.tsx (1)

16-24: Linux header integration looks good; consider tightening padding logic and verifying target window

The Linux‑specific header adjustments are wired up cleanly: using useIsLinux() to gate <TrafficLights /> and keeping the drag region handling via data-tauri-drag-region plus data-tauri-drag-region="false" on the buttons is a solid approach. Use of cn([...]) also matches the project guidelines. Based on learnings, …

Two small follow‑ups:

  1. Avoid conflicting padding utilities on Linux

Right now the header classes combine:

className={cn([
  "w-full h-9 flex items-center",
  !leftsidebar.expanded && "pl-[72px]",
  isLinux && "pl-3",
])}

On Linux with a collapsed sidebar both pl-[72px] and pl-3 will be present, which makes the effective padding a bit opaque and potentially order‑dependent. You can make the intent explicit by choosing one pl-* based on conditions, e.g.:

 function Header({ tabs }: { tabs: Tab[] }) {
   const { leftsidebar } = useShell();
   const isLinux = useIsLinux();
+  const headerPadding =
+    isLinux ? "pl-3" : !leftsidebar.expanded ? "pl-[72px]" : undefined;

   …
   <div
     data-tauri-drag-region
     className={cn([
       "w-full h-9 flex items-center",
-      !leftsidebar.expanded && "pl-[72px]",
-      isLinux && "pl-3",
+      headerPadding,
     ])}
   >

Adjust the exact values as needed for your layout.

  1. Confirm this Body header is intended to show TrafficLights

Body lives under components/main/body, so it’s presumably used by the main app window. The PR text emphasizes adding traffic lights for the Settings window specifically, while other undecorated windows might be updated later. Please double‑check that this header is only used on windows where the Linux traffic lights should appear; if not, you may want to gate it further or rely on the Settings‑specific layout instead.

Also applies to: 61-103

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7da127c and 60428c2.

📒 Files selected for processing (2)
  • apps/desktop/src/components/main/body/index.tsx (3 hunks)
  • plugins/windows/src/window/v1.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Avoid creating a bunch of types/interfaces if they are not shared. Especially for function props, just inline them instead.
Never do manual state management for form/mutation. Use useForm (from tanstack-form) and useQuery/useMutation (from tanstack-query) instead for 99% of cases. Avoid patterns like setError.
If there are many classNames with conditional logic, use cn (import from @hypr/utils). It is similar to clsx. Always pass an array and split by logical grouping.
Use motion/react instead of framer-motion.

Files:

  • apps/desktop/src/components/main/body/index.tsx
🧠 Learnings (2)
📚 Learning: 2025-11-24T16:32:19.706Z
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T16:32:19.706Z
Learning: Applies to **/*.{ts,tsx} : If there are many classNames with conditional logic, use `cn` (import from `hypr/utils`), similar to `clsx`. Always pass an array and split by logical grouping.

Applied to files:

  • apps/desktop/src/components/main/body/index.tsx
📚 Learning: 2025-11-24T16:32:23.055Z
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T16:32:23.055Z
Learning: Applies to **/*.{ts,tsx} : If there are many classNames with conditional logic, use `cn` (import from `hypr/utils`). It is similar to `clsx`. Always pass an array and split by logical grouping.

Applied to files:

  • apps/desktop/src/components/main/body/index.tsx
🧬 Code graph analysis (1)
apps/desktop/src/components/main/body/index.tsx (2)
apps/desktop/src/hooks/usePlatform.ts (1)
  • useIsLinux (9-11)
apps/desktop/src/components/window/traffic-lights.tsx (1)
  • TrafficLights (5-34)
⏰ 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). (7)
  • GitHub Check: Redirect rules - hyprnote
  • GitHub Check: Header rules - hyprnote
  • GitHub Check: Pages changed - hyprnote
  • GitHub Check: ci (linux, depot-ubuntu-24.04-8)
  • GitHub Check: ci (macos, depot-macos-14)
  • GitHub Check: ci (linux, depot-ubuntu-22.04-8)
  • GitHub Check: fmt
🔇 Additional comments (1)
plugins/windows/src/window/v1.rs (1)

131-138: Enabling minimize/maximize for Settings looks consistent and necessary

Making the Settings window minimizable and maximizable aligns it with the Main window and is required for the Linux TrafficLights minimize/maximize actions to work as expected. I don’t see issues with this change; just ensure the new controls are manually exercised on Linux as you noted in the PR.

@yujonglee yujonglee merged commit 753bb61 into main Nov 29, 2025
15 checks passed
@yujonglee yujonglee deleted the devin/1764389371-linux-traffic-lights branch November 29, 2025 06:25
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