Tauri Clocks — Fully vibe coded with Claude. Every line of code was generated by AI. My role was limited to guiding the process and making decisions.
A lightweight, cross-platform (Windows / macOS / Linux) desktop clock application inspired by GNOME Clocks, built with Tauri v2 and Svelte 5 (Runes).
- ⏱️ Stopwatch — high-precision, backend-driven (Rust monotonic clock) ✅
- ⏲️ Timer — countdown with looping alarm sound and blinking ring on finish ✅
- 🎨 Light/dark theme — manual toggle, OS preference as default, persisted choice ✅
- 🪟 GNOME-style window — undecorated, rounded corners, custom header bar ✅
- 🚀 Minimal RAM/CPU footprint and small executable size
All time-critical logic lives in the Rust backend:
- Timing threads use
std::time::Instant(monotonic clock), immune to system clock changes. - Shared state is managed through
tauri::Stateguarded bystd::sync::Mutex+Condvar. - The backend pushes elapsed time to the frontend via Tauri events (
emit) at a rate matched to what each view renders: 10 Hz for the stopwatch, 5 Hz for the timer — and not at all while the window is hidden — keeping webview CPU minimal. - The frontend never uses
setInterval/setTimeoutto measure time — it only reacts to backend events and calls Tauri commands (invoke) for user actions. - Emitter threads sleep on a condition variable while idle → 0% CPU when unused.
┌─────────────────────────┐ events (emit 100–200 ms) ┌──────────────────────┐
│ Rust backend │ ─────────────────────────────────▶ │ Svelte 5 frontend │
│ (threads, Instant, │ │ (Runes: $state, │
│ Mutex/Condvar state) │ ◀───────────────────────────────── │ $derived, $effect) │
└─────────────────────────┘ commands (start/pause/reset) └──────────────────────┘
tauri-clocks/
├── README.md
├── BUILD.md # Build & dependency installation guide
├── AUDIO.md # How to replace the embedded alarm sound
├── PROMPT.md # Full specification prompt for this project
├── audio.ogg.base64 # Alarm sound (Ogg Vorbis, base64-encoded)
├── package.json # Frontend dependencies & scripts (step 2)
├── vite.config.ts # Vite + Svelte plugin config (step 2)
├── svelte.config.js # Svelte 5 compiler options (step 2)
├── tsconfig.json # TypeScript config (step 2)
├── index.html # Vite entry HTML (step 2)
├── dist/ # Built frontend (generated; placeholder for now)
├── src/ # ───────── Frontend (Svelte 5, Runes only) ─────────
│ ├── main.ts # Applies the theme, mounts the Svelte app
│ ├── App.svelte # Rounded window frame, header bar, view switching
│ ├── assets/
│ │ └── dark-light.png # Theme toggle icon (rendered via CSS mask)
│ ├── styles/
│ │ └── app.css # GNOME-like design tokens, light/dark themes
│ └── lib/
│ ├── types.ts # Shared TS types mirroring Rust event payloads
│ ├── stores/ # Runes-based state modules (.svelte.ts)
│ │ ├── stopwatch.svelte.ts
│ │ ├── timer.svelte.ts
│ │ └── theme.svelte.ts # Light/dark theme manager (persisted)
│ └── components/
│ ├── Stopwatch.svelte
│ ├── Timer.svelte # Duration picker + countdown progress ring
│ └── shared/
│ ├── TabBar.svelte
│ ├── TimeDisplay.svelte
│ ├── ControlButton.svelte
│ ├── ThemeToggle.svelte
│ └── WindowControls.svelte
└── src-tauri/ # ───────── Backend (Rust + Tauri v2) ─────────
├── Cargo.toml # Crate manifest + size/speed release profile
├── build.rs # Tauri build script
├── tauri.conf.json # Tauri v2 app configuration
├── capabilities/
│ └── default.json # Permission capabilities for the main window
├── icons/ # App icons (replace with `tauri icon`)
└── src/
├── main.rs # Desktop entry point (delegates to lib.rs)
├── lib.rs # Builder setup, state registration, command handler
├── stopwatch.rs # High-precision stopwatch ✅
└── timer.rs # Countdown timer + native notification ✅
npm install # install frontend dependencies
npm run tauri dev # run in development mode
npm run tauri build # build optimized release bundlesSee BUILD.md for full instructions: per-platform dependency installation, artifact locations, and troubleshooting.
To replace the timer alarm sound, see AUDIO.md — the audio is
embedded at compile time from audio.ogg.base64.
| Kind | Name | Direction | Payload |
|---|---|---|---|
| Event | stopwatch://tick |
Rust → JS | { elapsed_ms: number, running: boolean } |
| Command | start_stopwatch |
JS → Rust | — |
| Command | pause_stopwatch |
JS → Rust | — |
| Command | reset_stopwatch |
JS → Rust | — |
| Command | get_stopwatch_state |
JS → Rust | returns { elapsed_ms, running } |
| Kind | Name | Direction | Payload |
|---|---|---|---|
| Event | timer://tick |
Rust → JS | { remaining_ms, duration_ms, running, finished } |
| Command | start_timer |
JS → Rust | { durationMs: number } |
| Command | pause_timer |
JS → Rust | — |
| Command | resume_timer |
JS → Rust | — |
| Command | reset_timer |
JS → Rust | — |
| Command | get_timer_state |
JS → Rust | returns { remaining_ms, duration_ms, running, finished } |
When the countdown reaches zero, the backend sets the finished flag in the
tick payload; the frontend reacts by looping the embedded alarm sound and
blinking the progress ring until the user presses Reset.
MIT