Move settings to separate window and improve UI spacing#2
Move settings to separate window and improve UI spacing#2
Conversation
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>
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
Comment 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 |
There was a problem hiding this comment.
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 | 🟠 MajorMemoize the load promise here, not the resolved store.
storeis only assigned afterawait load(...), so concurrent callers can all pass theif (!store)check before the first load resolves. That meansgetStore()no longer guarantees a single shared instance for the newonKeyChangesubscription 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
📒 Files selected for processing (10)
AGENTS.mdsrc-tauri/capabilities/default.jsonsrc-tauri/tauri.conf.jsonsrc/Clock.tsxsrc/SettingsWindow.tsxsrc/TitleBar.tsxsrc/components/Settings.tsxsrc/contexts/SettingsContext.tsxsrc/main.tsxsrc/utils/storage.ts
| "description": "Capability for the main window and settings window", | ||
| "windows": ["main", "settings"], |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's examine the default.json file structure
cat -n src-tauri/capabilities/default.jsonRepository: 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 5Repository: 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 3Repository: emmsixx/always-on-clock
Length of output: 996
🏁 Script executed:
# Check the desktop.json file
cat -n src-tauri/capabilities/desktop.jsonRepository: 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 -20Repository: 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 2Repository: 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 -50Repository: 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.tsxRepository: 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.tsxRepository: 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 2Repository: 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.
| <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" |
There was a problem hiding this comment.
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.
| 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]); |
There was a problem hiding this comment.
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.
| 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.
| 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 ( |
There was a problem hiding this comment.
🧩 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' || trueRepository: 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 -20Repository: 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.
| <button | ||
| onClick={handleClose} | ||
| className="p-1.5 hover:bg-gray-700 rounded-md transition-colors cursor-pointer" | ||
| title="Close settings" | ||
| > |
There was a problem hiding this comment.
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.
| <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.
| 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, | ||
| }); |
There was a problem hiding this comment.
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.
Summary
WebviewWindowto the right of the clock, instead of an overlay that hides the clock. Settings changes sync to the clock in real-time viastore.onKeyChange.Changes
Settings window (new)
src/SettingsWindow.tsx— standalone root component for the settings windowsrc/main.tsx— routes bygetCurrentWindow().labelto render clock vs settingssrc/TitleBar.tsx— opens settings viaWebviewWindowAPI, positioned to the right of clock; duplicate-prevention viagetByLabelsrc/components/Settings.tsx— stripped modal overlay, now a pure form component with inline padding/dividerssrc/contexts/SettingsContext.tsx— cross-window sync viastore.onKeyChange; autostart/shortcut effects guarded to main window onlysrc/utils/storage.ts— exportedgetStore()for subscriptionsrc-tauri/tauri.conf.json— explicit"label": "main"on primary windowsrc-tauri/capabilities/default.json— multi-window permissionsSpacing
padding: 14px 28pxpadding: 20px 24pxpadding: 18px 28pxpadding: 20px 28pxwithborderBottom: 2px solid #444Summary by CodeRabbit
New Features
Style
Documentation
Chores