Skip to content

Desktop App · Windows & macOS Time Tracker

StaffVertex — Desktop Tracker

A cross-platform desktop tracker with a Rust core: records offline to an encrypted local database, syncs on reconnect, and detects clock tampering so tracked hours hold up in a payroll dispute.

  • Running timer, task picker and weekly reports. Shown with demo data.
  • Task details beside the running timer, and the sync audit log that makes every sync and integrity decision inspectable.
  • Light and dark themes, plus settings for startup behaviour and diagnostics.
On this page

The Problem

A time tracker runs all day on someone else's computer and produces the number that person gets paid against. That makes it an unusually unforgiving thing to build: everything that can go wrong on a laptop will go wrong, and every one of those failures turns directly into somebody losing money or somebody being wrongly billed.

The problems that had to be solved:

  • The network is unreliable, and the tracker cannot care. People work from cafés, on flights and through outages. A tracker that only records time while the server is reachable silently deletes real work. Recording has to be entirely local, and syncing has to be a separate, resumable concern.
  • Machines sleep, crash and get force-quit. A running timer has to survive a lid closing, an OS-update reboot, a power loss and a user ending the process from Task Manager, and recover to the truth rather than to zero or to a made-up number.
  • Idle time is a policy question, not a technical one. Organizations genuinely disagree about what should happen when someone stops typing: some count it, some discard it, some want to ask, some want to stop the clock. Getting it wrong in either direction is serious. Over-aggressive idle handling deletes real work; under-aggressive handling bills a client for an empty chair.
  • The system clock is attacker-controlled. The tracked hour is worth money, and the machine recording it belongs to the person being paid. Anyone can change their PC clock, so any integrity model that trusts the client timestamp is not an integrity model.
  • Employees are entitled to privacy and predictability. Monitoring software that feels like spyware gets uninstalled or fought. Screenshot blurring, transparent settings, an auditable local record and encryption at rest are what make it deployable at all.
  • It has to be light, on two platforms. An always-on background agent cannot behave like a second browser on the user's machine, and it has to ship to Windows and macOS from one codebase.
  • It can never break the users already on it. Desktop clients update on their own schedule, and some defer for months. Every build in the wild has to keep tracking and syncing against a server that keeps moving.

What I Built

A cross-platform desktop time tracker on Tauri 2, with a Rust core doing all the real work and a deliberately thin React UI on top. Roughly 23,500 lines of Rust and 7,900 lines of TypeScript, shipping as a Windows installer and a macOS disk image from a single codebase. It is the desktop client of the StaffVertex platform.

Architecture

The two halves talk only through Tauri's IPC. The React 19 frontend (Zustand, TanStack Query, React Router) never touches the network or the database directly: every operation goes through a typed command wrapper into a Rust handler, and the Rust side pushes state changes back as events. That keeps the trust boundary clean. The UI cannot invent a time log, it can only ask the core to record one.

The Rust core is a set of long-running background services, each spawned once at boot: a heartbeat that syncs the active timer and detects sleep/wake gaps, two independent sync engines, an activity tracker, a screenshot service, an app-usage tracker, a settings watcher, a trusted clock, a notification service and the auto-updater.

Offline-first durability

All tracking is written to a local SQLite database first, so the tracker is fully functional with no network at all. Two independent sync engines, one for time logs and one for screenshots, drain the local queue to the server with retry and backoff, so a stalled screenshot upload can never hold up time data.

  • When the database lock is contended, writes go to a pending queue that the next heartbeat flushes, so a write is never silently dropped
  • On a forced quit the app writes the stop synchronously and checkpoints the write-ahead log before exiting
  • Crash, sleep/wake and boot paths all reconcile open rows back to the truth instead of discarding them

Idle handling as policy

Idle detection runs on a low-level input listener and honours four organization policies exactly:

  • Keep: idle time counts and the timer keeps running live
  • Discard: the display freezes and the idle gap is split out on return
  • Prompt: the timer stays alive and the user chooses keep or discard when they come back, even the next day
  • Stop: the timer stops at the start of the idle period

There is no time-based auto-stop and no session-length limit, because an earlier over-aggressive pass proved that killing timers on a timeout destroys real worked hours. Worked time is never trimmed; the only time ever removed is idle that was explicitly discarded.

Time integrity against a hostile clock

The tracker assumes the local system clock is untrustworthy:

  • A trusted clock service and a clock gate decide whether the machine's time can be believed, and new timer starts are blocked while it cannot
  • If the PC clock is changed mid-session beyond a small tolerance, the session is stopped at trusted time, the work already tracked is kept and marked verified, and restarts stay blocked until the clock is fixed
  • The organization's daily maximum stops the timer in the org's own timezone, and a separate physical ceiling means a day's total can never exceed the real hours elapsed in that day
  • A sync audit log screen makes every one of these decisions inspectable instead of mysterious

The rule throughout: integrity checks tighten what can be started and what can be claimed, but they never quietly delete work that was really done.

Evidence capture

Periodic screenshots with organization-controlled frequency and optional blurring, each carrying a SHA-256 integrity hash, queued locally and uploaded straight to Cloudflare R2 through presigned URLs, so image bytes never pass through the application server. Application and website usage tracking feeds a per-minute activity classification, so managers see what the time was spent on, not just how long it was.

Security hardening

  • The whole local database is encrypted at rest with SQLCipher, with the key held in the OS keychain (Windows Credential Manager / macOS Keychain), never in plaintext on disk
  • Release builds lock the server URL at compile time, so a tampered config cannot redirect a client's data
  • Payloads are HMAC-signed, dynamic SQL is restricted to a table whitelist, and the webview runs under a strict content security policy
  • Single-instance enforcement is layered: the OS-level plugin is authoritative, with a SQLite process lock (boot id plus heartbeat, self-healing reclaim) as defence in depth, so two copies can never double-count time

Desktop platform integration

System-tray residency with close-to-tray instead of close-to-quit, a native confirmation if you quit while a timer runs, a global shortcut to toggle the timer, autostart on login, native notifications, and staffvertex:// deep links for SSO login and for starting a timer on a specific project or task straight from the web app.

Signed auto-updates that respect running timers

Signed update artifacts are served from the web API and checked every four hours and on network reconnect. I added a server-controlled silent mode: an administrator chooses silent or prompted rollout, the mode travels in the update manifest, and silent updates download in the background but only ever install when no timer is running, so an update can never interrupt or truncate tracked work.

Cross-platform reality

Shipping to macOS meant fixing a crash in an upstream input-listening crate that faulted on every keypress on macOS 15. I vendored a minimally patched copy rather than forking or dropping the dependency, keeping it byte-identical apart from the one fix so it stays reviewable.


Outcomes

  • Live in production on Windows and macOS from one codebase, currently at v8.6, with real users tracking against it every working day.
  • No lost time. The tracker keeps working through outages, flights, sleep, crashes and forced quits, and reconciles on reconnect. Network conditions cannot cost a user their hours.
  • Idle handling that stopped destroying real work. Rebuilding idle as an explicit organization policy, and removing the time-based auto-stop entirely, closed a class of bug where genuinely worked hours were being trimmed. The invariants are documented and enforced so the regression cannot creep back.
  • Hours that hold up against a manipulated clock. Clock tampering is detected, gated and audited, and the response keeps work already done while blocking further unverifiable tracking. Together with the server-side ceiling, desktop-recorded hours are defensible in a billing or payroll dispute.
  • A footprint users tolerate. A Rust core with the system webview, instead of a bundled browser runtime, keeps the installer and resident memory far below an equivalent Electron build, which matters for software that runs all day, every day.
  • Deployable in privacy-sensitive organizations. Encryption at rest, keychain-held keys, screenshot blurring and a transparent audit log turn monitoring from something employees resist into something that can actually be rolled out.
  • Backward compatibility held. Every server-side change since launch has been additive, and older desktop builds in the field still track and sync correctly. No user has been stranded by a release.
All projects

Have a project in mind?

Whether it's an MVP, a dashboard, or a rescue mission on an existing codebase — let's talk about it.

Prefer to talk first? Book a free 30-min call (opens in a new tab)