Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# AGENTS.md

## Cursor Cloud specific instructions

This is a **Tauri 2** desktop application (Rust backend + React 19/TypeScript frontend). A single command `pnpm tauri dev` starts both the Vite dev server (port 1420) and the native Tauri window.

### Prerequisites (system-level, installed once in the VM snapshot)

- **Tauri Linux system libraries**: `libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf`
- **Rust stable** (1.94+): The default rustup toolchain must be `stable`, not the older system-pinned `1.83.0`. Run `rustup default stable` if `cargo --version` shows < 1.94.
- **Node.js** (v18+) and **pnpm** (v9+): Already available via nvm.

### Key commands

| Task | Command |
|---|---|
| Install frontend deps | `pnpm install` |
| TypeScript lint/check | `pnpm exec tsc --noEmit` |
| Frontend build | `pnpm build` |
| Rust check | `cd src-tauri && cargo check` |
| Run dev (full app) | `pnpm tauri dev` |
| Production build | `pnpm tauri build` |

### Gotchas

- The first `cargo check` / `pnpm tauri dev` after a clean clone takes ~60s to compile all Rust dependencies. Subsequent builds are incremental and much faster.
- In headless/VM environments, expect `libEGL warning: DRI3 error` messages — these are benign and the app still renders correctly via software rendering.
- `pnpm tauri dev` auto-runs `pnpm dev` (Vite) as a `beforeDevCommand`; you don't need to start Vite separately.
- There are no automated test suites in this repository. Validation is done via TypeScript type checking (`tsc --noEmit`) and building the app.
8 changes: 6 additions & 2 deletions src-tauri/capabilities/default.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main"],
"description": "Capability for the main window and settings window",
"windows": ["main", "settings"],
Comment on lines +4 to +5
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.

"permissions": [
"core:default",
"opener:default",
Expand All @@ -16,6 +16,10 @@
"core:window:allow-inner-position",
"core:window:allow-show",
"core:window:allow-hide",
"core:window:allow-close",
"core:window:allow-set-focus",
"core:window:allow-center",
"core:webview:allow-create-webview-window",
"core:app:allow-default-window-icon",
"positioner:default",
"store:default",
Expand Down
1 change: 1 addition & 0 deletions src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"app": {
"windows": [
{
"label": "main",
"title": "Clock",
"decorations": false,
"transparent": true,
Expand Down
5 changes: 3 additions & 2 deletions src/Clock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,11 @@ const Clock = () => {

return (
<div
className="font-black w-fit p-2 pointer-events-none transition-all duration-200"
className="font-black w-fit pointer-events-none transition-all duration-200"
style={{
backgroundColor: bgColorWithOpacity,
fontSize,
padding: '20px 24px',
}}
>
<span
Expand All @@ -77,7 +78,7 @@ const Clock = () => {
</span>
{date && (
<span
className="drop-shadow-[0_1px_1px_rgba(0,0,0,0.8)] block text-center"
className="drop-shadow-[0_1px_1px_rgba(0,0,0,0.8)] block text-center mt-1"
style={{
color: settings.textColor,
opacity: settings.textOpacity * 0.8,
Expand Down
42 changes: 42 additions & 0 deletions src/SettingsWindow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
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 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"
>
Comment on lines +21 to +25
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.

<X className="w-5 h-5 text-gray-400" />
</button>
</div>
<Settings />
</div>
);
};

const SettingsWindow: React.FC = () => {
return (
Comment on lines +7 to +35
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.

<SettingsProvider>
<SettingsWindowContent />
</SettingsProvider>
);
};

export default SettingsWindow;
52 changes: 38 additions & 14 deletions src/TitleBar.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,41 @@
import { getCurrentWindow } from "@tauri-apps/api/window";
import { WebviewWindow } from "@tauri-apps/api/webviewWindow";
import React, { useState, useEffect, useRef } from "react";
import { TrayIcon } from '@tauri-apps/api/tray';
import { defaultWindowIcon } from "@tauri-apps/api/app";
import { Menu } from '@tauri-apps/api/menu';
import { Clock1, PinIcon, Settings as SettingsIcon } from "lucide-react";
import Settings from "./components/Settings";

interface TitleBarProps {
onSettingsOpen?: () => void;
async function openSettingsWindow() {
const existing = await WebviewWindow.getByLabel('settings');
if (existing) {
await existing.setFocus();
return;
}

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,
});
Comment on lines +16 to +30
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.


webview.once('tauri://error', (e) => {
console.error('Failed to create settings window:', e);
});
}

const TitleBar: React.FC<TitleBarProps> = () => {
const TitleBar: React.FC = () => {
const [onTop, setOnTop] = useState<boolean | null>(null);
const [settingsOpen, setSettingsOpen] = useState(false);
const trayRef = useRef<TrayIcon | null>(null);

useEffect(() => {
Expand All @@ -39,8 +62,9 @@ const TitleBar: React.FC<TitleBarProps> = () => {
{
id: 'settings',
text: 'Settings',
action: () => {
setTop(false).then(() => setSettingsOpen(true));
action: async () => {
await setTop(false);
await openSettingsWindow();
},
},
{
Expand Down Expand Up @@ -90,22 +114,23 @@ const TitleBar: React.FC<TitleBarProps> = () => {
) : onTop ? null : (
<div
data-tauri-drag-region
className="w-full cursor-move flex flex-row justify-between items-center text-white px-3 py-2 text-lg bg-[#0c0d0f] transition-all"
className="w-full cursor-move flex flex-row justify-between items-center text-white text-lg bg-[#0c0d0f] transition-all"
style={{ padding: '14px 28px' }}
>
<div className="flex flex-row items-center space-x-2 pointer-events-none">
<div className="flex flex-row items-center space-x-2.5 pointer-events-none">
<Clock1 className="w-5 h-5" />
<span className="text-sm font-medium">Clock</span>
</div>
<div className="flex flex-row items-center space-x-1">
<div className="flex flex-row items-center space-x-1.5">
<button
className="p-1.5 hover:bg-gray-700 rounded transition-colors cursor-pointer"
onClick={() => setSettingsOpen(true)}
className="p-2 hover:bg-gray-700 rounded-md transition-colors cursor-pointer"
onClick={() => openSettingsWindow()}
title="Settings"
>
<SettingsIcon className="w-4 h-4" />
</button>
<button
className="p-1.5 hover:bg-gray-700 rounded transition-colors cursor-pointer"
className="p-2 hover:bg-gray-700 rounded-md transition-colors cursor-pointer"
onClick={() => setTop(true)}
title="Pin window (always on top)"
>
Expand All @@ -114,7 +139,6 @@ const TitleBar: React.FC<TitleBarProps> = () => {
</div>
</div>
)}
<Settings isOpen={settingsOpen} onClose={() => setSettingsOpen(false)} />
</>
);
};
Expand Down
Loading
Loading