Skip to content

Move settings to separate window and improve UI spacing#2

Open
emmsixx wants to merge 6 commits intomainfrom
cursor/development-environment-setup-66ac
Open

Move settings to separate window and improve UI spacing#2
emmsixx wants to merge 6 commits intomainfrom
cursor/development-environment-setup-66ac

Conversation

@emmsixx
Copy link
Copy Markdown
Owner

@emmsixx emmsixx commented Mar 14, 2026

Summary

  • Settings as a separate window: The settings panel now opens as its own Tauri WebviewWindow to the right of the clock, instead of an overlay that hides the clock. Settings changes sync to the clock in real-time via store.onKeyChange.
  • Improved spacing throughout: All critical padding and section dividers use inline styles for reliable rendering in the Tauri webview. The clock title bar, time display, settings header, and all settings sections have generous horizontal and vertical padding with visible dividers between groups.

Changes

Settings window (new)

  • src/SettingsWindow.tsx — standalone root component for the settings window
  • src/main.tsx — routes by getCurrentWindow().label to render clock vs settings
  • src/TitleBar.tsx — opens settings via WebviewWindow API, positioned to the right of clock; duplicate-prevention via getByLabel
  • src/components/Settings.tsx — stripped modal overlay, now a pure form component with inline padding/dividers
  • src/contexts/SettingsContext.tsx — cross-window sync via store.onKeyChange; autostart/shortcut effects guarded to main window only
  • src/utils/storage.ts — exported getStore() for subscription
  • src-tauri/tauri.conf.json — explicit "label": "main" on primary window
  • src-tauri/capabilities/default.json — multi-window permissions

Spacing

  • Clock title bar: inline padding: 14px 28px
  • Clock time display: inline padding: 20px 24px
  • Settings header: inline padding: 18px 28px
  • Settings sections: inline padding: 20px 28px with borderBottom: 2px solid #444
  • Settings window enlarged to 420×700
Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features

    • Settings now accessible in a dedicated window separate from the main app
    • Expanded settings interface with additional customization options
  • Style

    • Refined clock display appearance and spacing
  • Documentation

    • Added setup guide with prerequisites and maintenance commands
  • Chores

    • Updated application configuration and permission capabilities

cursoragent and others added 6 commits March 13, 2026 11:39
Co-authored-by: M6 <emmsixx@users.noreply.github.com>
- Refactor Settings.tsx into a pure form component (no modal overlay)
- Create SettingsWindow.tsx as standalone root for the settings window
- Update main.tsx to route by window label (main vs settings)
- Update TitleBar.tsx to open settings via WebviewWindow API
- Add cross-window settings sync via store.onKeyChange
- Guard autostart/shortcut side effects to main window only
- Update Tauri capabilities for multi-window permissions
- Add explicit 'main' label to primary window config

Co-authored-by: M6 <emmsixx@users.noreply.github.com>
Open the settings window adjacent (12px gap) to the right of the
main clock window so both are visible side-by-side.

Co-authored-by: M6 <emmsixx@users.noreply.github.com>
Clock window:
- Clock text padding: p-2 → px-5 py-4 for generous breathing room
- Add mt-1 gap between time and date text
- Title bar: px-3 py-2 → px-4 py-3, larger touch targets (p-2), rounded-md
- Button spacing: space-x-1 → space-x-1.5

Settings window:
- Window size: 380x560 → 420x660 to accommodate spacious layout
- Title bar: p-4 → px-6 py-4, rounded-md close button
- Content padding: p-4 → px-6 py-5
- Section gap: space-y-6 → space-y-7
- Label-to-control gap: mb-2 → mb-3
- Button pill padding: py-2 px-3 → py-2.5 px-4, rounded-lg
- Grid gaps: gap-2 → gap-2.5/gap-3
- Slider label margin: mb-1 → mb-2
- Input fields: py-2 px-3 → py-2.5 px-4, rounded-lg
- Checkbox rows: added py-1 for vertical breathing room
- Custom color rows: added py-1 spacing
- Opacity sub-sections: space-y-3 → space-y-4
- Bottom section: added pb-2 for edge margin

Co-authored-by: M6 <emmsixx@users.noreply.github.com>
- Replace Tailwind divide-y utility with inline border styles for
  reliable rendering in the Tauri webview
- Each settings section gets a 1px #555 bottom border for clear
  visual separation between groups
- Settings window height increased to 700px
- Clock padding increased to px-6 py-5

Co-authored-by: M6 <emmsixx@users.noreply.github.com>
Tailwind v4 utility classes were not reliably rendering padding
and borders in the Tauri webview. Switched all critical spacing
to inline styles:
- Clock title bar: padding 14px 28px (inline)
- Clock time display: padding 20px 24px (inline)
- Settings header: padding 18px 28px (inline)
- Settings sections: padding 20px 28px (inline)
- Section dividers: 2px solid #444 (inline)

Co-authored-by: M6 <emmsixx@users.noreply.github.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 14, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 847b9fe4-43b0-4fdc-898f-8a5fe7b6e603

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch cursor/development-environment-setup-66ac
📝 Coding Plan
  • Generate coding plan for human review comments

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

Tip

You can validate your CodeRabbit configuration file in your editor.

If your editor has YAML language server, you can enable auto-completion and validation by adding # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json at the top of your CodeRabbit configuration file.

@emmsixx emmsixx marked this pull request as ready for review March 14, 2026 11:52
Copy link
Copy Markdown

@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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/utils/storage.ts (1)

7-11: ⚠️ Potential issue | 🟠 Major

Memoize the load promise here, not the resolved store.

store is only assigned after await load(...), so concurrent callers can all pass the if (!store) check before the first load resolves. That means getStore() no longer guarantees a single shared instance for the new onKeyChange subscription flow.

Suggested fix
 const STORE_NAME = 'settings.json';
-let store: Store | null = null;
+let storePromise: Promise<Store> | null = null;
 
-export async function getStore(): Promise<Store> {
-  if (!store) {
-    store = await load(STORE_NAME);
-  }
-  return store;
+export function getStore(): Promise<Store> {
+  if (!storePromise) {
+    storePromise = load(STORE_NAME).catch((err) => {
+      storePromise = null;
+      throw err;
+    });
+  }
+  return storePromise;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/storage.ts` around lines 7 - 11, getStore currently awaits
load(...) before assigning to the module-level store, so concurrent callers can
trigger multiple loads; instead memoize the load promise so only one load runs:
in getStore assign a module-level promise (e.g., storePromise) from
load(STORE_NAME) without awaiting, await that promise and then set the resolved
module-level store if needed, ensuring subsequent calls use the same promise;
update references to the existing store variable and the new storePromise to
guarantee a single shared instance for onKeyChange subscriptions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src-tauri/capabilities/default.json`:
- Around line 4-5: Remove "settings" from the existing capability's "windows"
array and add a new minimal capability named (for example) "settings" that
grants only the listed permissions: "core:default", "opener:default",
"store:default" plus window permissions "allow-close", "allow-show",
"allow-hide", and "allow-set-focus"; keep the original capability assigned to
"main" and ensure SettingsContext.tsx and TitleBar.tsx behaviors remain guarded
to the main window (they will now only have full permissions), and ensure any
code that creates WebviewWindow or calls setAlwaysOnTop, setIgnoreCursorEvents,
start-dragging, autostart, or global-shortcut remains associated with the main
capability/window.

In `@src/components/Settings.tsx`:
- Around line 200-205: The input currently calls updateSettings on every
keystroke, causing SettingsContext.tsx to re-register the global shortcut
repeatedly; change the Settings component to keep a local draft state (e.g.,
useState draftShortcut initialized from settings.globalShortcut), bind the input
to draftShortcut and update that onChange, and only call updateSettings({
globalShortcut: draftShortcut }) from a commit function invoked onBlur or on
Enter (handle onKeyDown for Enter) after validating/normalizing the accelerator
(trim, basic accelerator-format check), and handle Escape to cancel (reset
draftShortcut back to settings.globalShortcut); reference the existing
updateSettings call and settings.globalShortcut when implementing the commit.

In `@src/contexts/SettingsContext.tsx`:
- Around line 45-63: The effect may unmount before
getStore().then(...).then(...) resolves, leaving the listener attached with no
cleanup; to fix, in the useEffect create a cancelled flag (e.g., let canceled =
false) and in the promise resolution where you receive fn from
store.onKeyChange(...) check if canceled is true—if so immediately call fn() to
unregister, otherwise assign it to unlisten; in the cleanup set canceled = true
and call unlisten?.() as before. Update the logic around getStore(),
store.onKeyChange, unlisten and the useEffect cleanup to ensure late-resolving
listeners are removed.

In `@src/SettingsWindow.tsx`:
- Around line 21-25: The close button in SettingsWindow.tsx (the button that
calls handleClose) is icon-only and uses only title for description; replace or
augment that with an accessible name by adding an aria-label (e.g.,
aria-label="Close settings") or include visually hidden text inside the button
so assistive tech can read it, ensuring the accessible name matches the
tooltip/title and retains the existing onClick handler and classes.
- Around line 7-35: The file uses the React.FC type for SettingsWindowContent
and SettingsWindow but doesn't import React types; add an explicit type import
(for example import type { FC } from 'react') at the top and update the
component type annotations (SettingsWindowContent: FC and SettingsWindow: FC or
keep React.FC after importing React types) so TypeScript strict/isolatedModules
builds succeed. Ensure the import is a type import if you don't need the runtime
React symbol.

In `@src/TitleBar.tsx`:
- Around line 16-30: The settings WebviewWindow currently positions at x = pos.x
+ size.width + 16 and y = pos.y which can place the 420×700 window off-screen;
change the code that builds the new WebviewWindow ('settings') to query the
display/work-area bounds for the monitor containing the main window (use
getCurrentWindow(), innerPosition(), innerSize() to locate the window and the
platform/screen API to get the work area), then clamp the computed x and y so
the 420 width and 700 height fit fully inside those bounds (e.g., x = min(max(x,
workArea.x), workArea.x + workArea.width - 420) and similarly for y), and use
those clamped values in the WebviewWindow options to ensure the settings window
is fully visible even with decorations: false.

---

Outside diff comments:
In `@src/utils/storage.ts`:
- Around line 7-11: getStore currently awaits load(...) before assigning to the
module-level store, so concurrent callers can trigger multiple loads; instead
memoize the load promise so only one load runs: in getStore assign a
module-level promise (e.g., storePromise) from load(STORE_NAME) without
awaiting, await that promise and then set the resolved module-level store if
needed, ensuring subsequent calls use the same promise; update references to the
existing store variable and the new storePromise to guarantee a single shared
instance for onKeyChange subscriptions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3f57e1e2-dca2-4d69-ad7a-2e623827fd1c

📥 Commits

Reviewing files that changed from the base of the PR and between 8f32792 and 42e3ac0.

📒 Files selected for processing (10)
  • AGENTS.md
  • src-tauri/capabilities/default.json
  • src-tauri/tauri.conf.json
  • src/Clock.tsx
  • src/SettingsWindow.tsx
  • src/TitleBar.tsx
  • src/components/Settings.tsx
  • src/contexts/SettingsContext.tsx
  • src/main.tsx
  • src/utils/storage.ts

Comment on lines +4 to +5
"description": "Capability for the main window and settings window",
"windows": ["main", "settings"],
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the default.json file structure
cat -n src-tauri/capabilities/default.json

Repository: emmsixx/always-on-clock

Length of output: 1220


🏁 Script executed:

# Check if there are other capability files for comparison
fd -t f "\.json$" src-tauri/capabilities/

Repository: emmsixx/always-on-clock

Length of output: 138


🏁 Script executed:

# Search for where the settings window uses permissions
rg -n "settings" src-tauri/capabilities/ -A 5 -B 5

Repository: emmsixx/always-on-clock

Length of output: 786


🏁 Script executed:

# Check TitleBar.tsx for window creation context
rg -n "createWebviewWindow|allow-create-webview-window" src/

Repository: emmsixx/always-on-clock

Length of output: 49


🏁 Script executed:

# Check SettingsContext.tsx for autostart and global-shortcut usage
rg -n "autostart|global-shortcut|label.*main" src/contexts/SettingsContext.tsx -B 3 -A 3

Repository: emmsixx/always-on-clock

Length of output: 996


🏁 Script executed:

# Check the desktop.json file
cat -n src-tauri/capabilities/desktop.json

Repository: emmsixx/always-on-clock

Length of output: 347


🏁 Script executed:

# Search for where settings window is created/initialized
rg -n "settings" src/ -i | grep -i "window\|create" | head -20

Repository: emmsixx/always-on-clock

Length of output: 1491


🏁 Script executed:

# Search for actual usage of the webview creation permission
rg -n "createWebviewWindow" src/ -B 2 -A 2

Repository: emmsixx/always-on-clock

Length of output: 49


🏁 Script executed:

# Check what the settings window actually renders/uses
fd -t f "Settings.tsx" src/ -x cat -n {} | head -50

Repository: emmsixx/always-on-clock

Length of output: 2157


🏁 Script executed:

# Search for any window configuration or initialization
rg -n "label.*=.*['\"]settings['\"]" src-tauri/ src/

Repository: emmsixx/always-on-clock

Length of output: 49


🏁 Script executed:

# Check the full TitleBar.tsx to see window creation details
cat -n src/TitleBar.tsx

Repository: emmsixx/always-on-clock

Length of output: 5375


🏁 Script executed:

# Check what methods the settings window uses
rg -n "setAlwaysOnTop|setIgnoreCursorEvents|startDragging" src/

Repository: emmsixx/always-on-clock

Length of output: 210


🏁 Script executed:

# Check if settings window needs any window control calls
rg -n "getCurrentWindow\(\)\..*\(set|allow\)" src/SettingsWindow.tsx src/components/Settings.tsx

Repository: emmsixx/always-on-clock

Length of output: 49


🏁 Script executed:

# Verify what storage operations are used in Settings context
rg -n "store\." src/contexts/SettingsContext.tsx -A 2 -B 2

Repository: emmsixx/always-on-clock

Length of output: 275


Split settings into its own minimal capability.

The settings window receives permissions it does not use. Autostart and global-shortcut setup are guarded to main window only in SettingsContext.tsx (lines 67, 83). Window control operations like setAlwaysOnTop(), setIgnoreCursorEvents(), and start-dragging are used only on the main window in TitleBar.tsx. WebviewWindow creation also happens only from the main window context. Create a separate capability for settings with only: core:default, opener:default, store:default, and minimal window permissions (allow-close, allow-show, allow-hide, allow-set-focus).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src-tauri/capabilities/default.json` around lines 4 - 5, Remove "settings"
from the existing capability's "windows" array and add a new minimal capability
named (for example) "settings" that grants only the listed permissions:
"core:default", "opener:default", "store:default" plus window permissions
"allow-close", "allow-show", "allow-hide", and "allow-set-focus"; keep the
original capability assigned to "main" and ensure SettingsContext.tsx and
TitleBar.tsx behaviors remain guarded to the main window (they will now only
have full permissions), and ensure any code that creates WebviewWindow or calls
setAlwaysOnTop, setIgnoreCursorEvents, start-dragging, autostart, or
global-shortcut remains associated with the main capability/window.

Comment on lines +200 to +205
<input
type="text"
value={settings.globalShortcut}
onChange={(e) => updateSettings({ globalShortcut: e.target.value })}
placeholder="e.g., CommandOrControl+Shift+C"
className="w-full py-2.5 px-4 rounded-lg bg-gray-700 text-gray-200 border border-gray-600 focus:outline-none focus:border-blue-500 text-sm"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't persist the shortcut on every keystroke.

updateSettings() saves immediately, and src/contexts/SettingsContext.tsx:81-107 re-registers the global shortcut on every stored value. Editing this field therefore unregisters the previous binding on the first keystroke and then repeatedly attempts to register partial accelerators until typing finishes. Keep a local draft and commit it on blur/Enter after validation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Settings.tsx` around lines 200 - 205, The input currently
calls updateSettings on every keystroke, causing SettingsContext.tsx to
re-register the global shortcut repeatedly; change the Settings component to
keep a local draft state (e.g., useState draftShortcut initialized from
settings.globalShortcut), bind the input to draftShortcut and update that
onChange, and only call updateSettings({ globalShortcut: draftShortcut }) from a
commit function invoked onBlur or on Enter (handle onKeyDown for Enter) after
validating/normalizing the accelerator (trim, basic accelerator-format check),
and handle Escape to cancel (reset draftShortcut back to
settings.globalShortcut); reference the existing updateSettings call and
settings.globalShortcut when implementing the commit.

Comment on lines 45 to +63
useEffect(() => {
if (isLoading) return;

let unlisten: (() => void) | null = null;

getStore().then((store) => {
store.onKeyChange<Partial<Settings>>('settings', (newValue) => {
if (newValue) {
setSettings({ ...DEFAULT_SETTINGS, ...newValue });
}
}).then((fn) => {
unlisten = fn;
});
});

return () => {
unlisten?.();
};
}, [isLoading]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Make the async store subscription cleanup-safe.

This effect can unmount before onKeyChange() resolves. When that happens, the listener still gets attached later and unlisten is never called, so fast remounts/window closes can leave duplicate subscriptions behind.

Suggested fix
   useEffect(() => {
     if (isLoading) return;
 
+    let disposed = false;
     let unlisten: (() => void) | null = null;
 
-    getStore().then((store) => {
-      store.onKeyChange<Partial<Settings>>('settings', (newValue) => {
-        if (newValue) {
-          setSettings({ ...DEFAULT_SETTINGS, ...newValue });
-        }
-      }).then((fn) => {
-        unlisten = fn;
-      });
-    });
+    void (async () => {
+      try {
+        const store = await getStore();
+        const fn = await store.onKeyChange<Partial<Settings>>('settings', (newValue) => {
+          if (newValue) {
+            setSettings({ ...DEFAULT_SETTINGS, ...newValue });
+          }
+        });
+
+        if (disposed) {
+          fn();
+          return;
+        }
+
+        unlisten = fn;
+      } catch (err) {
+        console.error('Failed to subscribe to settings changes:', err);
+      }
+    })();
 
     return () => {
+      disposed = true;
       unlisten?.();
     };
   }, [isLoading]);
📝 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
useEffect(() => {
if (isLoading) return;
let unlisten: (() => void) | null = null;
getStore().then((store) => {
store.onKeyChange<Partial<Settings>>('settings', (newValue) => {
if (newValue) {
setSettings({ ...DEFAULT_SETTINGS, ...newValue });
}
}).then((fn) => {
unlisten = fn;
});
});
return () => {
unlisten?.();
};
}, [isLoading]);
useEffect(() => {
if (isLoading) return;
let disposed = false;
let unlisten: (() => void) | null = null;
void (async () => {
try {
const store = await getStore();
const fn = await store.onKeyChange<Partial<Settings>>('settings', (newValue) => {
if (newValue) {
setSettings({ ...DEFAULT_SETTINGS, ...newValue });
}
});
if (disposed) {
fn();
return;
}
unlisten = fn;
} catch (err) {
console.error('Failed to subscribe to settings changes:', err);
}
})();
return () => {
disposed = true;
unlisten?.();
};
}, [isLoading]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/contexts/SettingsContext.tsx` around lines 45 - 63, The effect may
unmount before getStore().then(...).then(...) resolves, leaving the listener
attached with no cleanup; to fix, in the useEffect create a cancelled flag
(e.g., let canceled = false) and in the promise resolution where you receive fn
from store.onKeyChange(...) check if canceled is true—if so immediately call
fn() to unregister, otherwise assign it to unlisten; in the cleanup set canceled
= true and call unlisten?.() as before. Update the logic around getStore(),
store.onKeyChange, unlisten and the useEffect cleanup to ensure late-resolving
listeners are removed.

Comment thread src/SettingsWindow.tsx
Comment on lines +7 to +35
const SettingsWindowContent: React.FC = () => {
const handleClose = async () => {
const win = getCurrentWindow();
await win.close();
};

return (
<div className="bg-[#1a1a1a] min-h-screen overflow-y-auto">
<div
data-tauri-drag-region
className="sticky top-0 z-10 flex items-center justify-between bg-[#1a1a1a] cursor-move"
style={{ padding: '18px 28px', borderBottom: '2px solid #444' }}
>
<h2 className="text-lg font-semibold text-white pointer-events-none">Settings</h2>
<button
onClick={handleClose}
className="p-1.5 hover:bg-gray-700 rounded-md transition-colors cursor-pointer"
title="Close settings"
>
<X className="w-5 h-5 text-gray-400" />
</button>
</div>
<Settings />
</div>
);
};

const SettingsWindow: React.FC = () => {
return (
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== tsconfig files =="
fd -HI '^tsconfig.*\.json$' -x sh -c 'echo "--- $1 ---"; cat "$1"' sh {}

echo "== React.FC usages =="
rg -nP '\bReact\.FC\b' --type ts --type tsx

echo "== allowUmdGlobalAccess explicitly true =="
rg -nP '"allowUmdGlobalAccess"\s*:\s*true' -g 'tsconfig*.json' || true

Repository: emmsixx/always-on-clock

Length of output: 1060


🏁 Script executed:

# Find and read the SettingsWindow.tsx file
fd -HI 'SettingsWindow\.tsx$' -x cat -n {} 

# Also check other .tsx files to see if React/FC is imported
echo "=== Checking other React.FC/FC imports in tsx files ==="
rg 'import.*(?:React|FC)' --type-list | head -1
fd -HI '\.tsx$' | head -10 | xargs -I {} sh -c 'echo "--- {} ---"; head -20 {}'

Repository: emmsixx/always-on-clock

Length of output: 7191


🏁 Script executed:

# Search for .d.ts files that might declare React globally
fd -HI '\.d\.ts$' -x cat -n {}

# Search for any triple-slash directives or ambient declarations
rg -HI 'declare\s+namespace\s+React|/// <reference' --type ts --type tsx

# Check if tsc would actually report an error
# Try to find tsconfig references
find . -name "*.d.ts" -o -name "react.d.ts" 2>/dev/null | head -20

Repository: emmsixx/always-on-clock

Length of output: 177


Import React types explicitly instead of relying on React.FC global namespace.

Using React.FC without an explicit import from react will fail TypeScript type-checking. This repository's tsconfig.json has strict mode and isolatedModules enabled without allowUmdGlobalAccess, which means the React namespace is not available globally. The jsx: "react-jsx" setting only handles JSX syntax transformation—it does not provide type access.

Suggested fix
+import type { FC } from "react";
 import { getCurrentWindow } from "@tauri-apps/api/window";
 import { X } from "lucide-react";
 import { SettingsProvider } from "./contexts/SettingsContext";
 import Settings from "./components/Settings";
 import "./globals.css";

-const SettingsWindowContent: React.FC = () => {
+const SettingsWindowContent: FC = () => {
   const handleClose = async () => {
     const win = getCurrentWindow();
     await win.close();
   };
@@
-const SettingsWindow: React.FC = () => {
+const SettingsWindow: FC = () => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/SettingsWindow.tsx` around lines 7 - 35, The file uses the React.FC type
for SettingsWindowContent and SettingsWindow but doesn't import React types; add
an explicit type import (for example import type { FC } from 'react') at the top
and update the component type annotations (SettingsWindowContent: FC and
SettingsWindow: FC or keep React.FC after importing React types) so TypeScript
strict/isolatedModules builds succeed. Ensure the import is a type import if you
don't need the runtime React symbol.

Comment thread src/SettingsWindow.tsx
Comment on lines +21 to +25
<button
onClick={handleClose}
className="p-1.5 hover:bg-gray-700 rounded-md transition-colors cursor-pointer"
title="Close settings"
>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add an accessible name to the icon-only close button.

title is not a sufficient accessible label for all assistive tech paths.

Suggested fix
         <button
           onClick={handleClose}
           className="p-1.5 hover:bg-gray-700 rounded-md transition-colors cursor-pointer"
+          aria-label="Close settings"
           title="Close settings"
         >
📝 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
<button
onClick={handleClose}
className="p-1.5 hover:bg-gray-700 rounded-md transition-colors cursor-pointer"
title="Close settings"
>
<button
onClick={handleClose}
className="p-1.5 hover:bg-gray-700 rounded-md transition-colors cursor-pointer"
aria-label="Close settings"
title="Close settings"
>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/SettingsWindow.tsx` around lines 21 - 25, The close button in
SettingsWindow.tsx (the button that calls handleClose) is icon-only and uses
only title for description; replace or augment that with an accessible name by
adding an aria-label (e.g., aria-label="Close settings") or include visually
hidden text inside the button so assistive tech can read it, ensuring the
accessible name matches the tooltip/title and retains the existing onClick
handler and classes.

Comment thread src/TitleBar.tsx
Comment on lines +16 to +30
const mainWin = getCurrentWindow();
const pos = await mainWin.innerPosition();
const size = await mainWin.innerSize();

const webview = new WebviewWindow('settings', {
url: '/',
title: 'Settings',
width: 420,
height: 700,
x: pos.x + size.width + 16,
y: pos.y,
resizable: false,
decorations: false,
transparent: false,
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Clamp the settings window into the visible work area.

x = pos.x + size.width + 16 and y = pos.y assume the entire 420×700 window fits beside the clock. On smaller displays or when the main window sits near the right/bottom edge, this can open the settings window partially or completely off-screen, and with decorations: false the user has no native chrome to drag it back.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/TitleBar.tsx` around lines 16 - 30, The settings WebviewWindow currently
positions at x = pos.x + size.width + 16 and y = pos.y which can place the
420×700 window off-screen; change the code that builds the new WebviewWindow
('settings') to query the display/work-area bounds for the monitor containing
the main window (use getCurrentWindow(), innerPosition(), innerSize() to locate
the window and the platform/screen API to get the work area), then clamp the
computed x and y so the 420 width and 700 height fit fully inside those bounds
(e.g., x = min(max(x, workArea.x), workArea.x + workArea.width - 420) and
similarly for y), and use those clamped values in the WebviewWindow options to
ensure the settings window is fully visible even with decorations: false.

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