Chronoscribe is a real, packaged desktop time-tracking app — not a script, not a browser tab. It's a tiny glassmorphic widget that docks itself to the top-right corner of your screen with one job: Start and Stop. Click it and it expands into a full dashboard for editing entries, browsing past weeks, and watching your Familiar breathe while it keeps count.
Everything runs locally. Your hours live in a SQLite file on your own disk — nothing is sent anywhere.
Most time trackers ask you to open an app, find a project, click into a timer view, and then start tracking. Chronoscribe's whole premise is that starting and stopping a timer should be as fast as glancing at a clock — so the entire "start tracking" interaction is one click on a widget that's already sitting in the corner of your screen, always. The dashboard exists for everything else: fixing a forgotten clock-in, trimming a session you left running overnight, or seeing how the week shaped up.
- The floating widget — an always-on-top, borderless, transparent glass pill docked to your screen's top-right corner. Click Start/Stop. Hover to reveal an expand button that opens the full dashboard.
- The Familiar — the little orb at the widget's heart. It breathes slowly and glows violet when idle; when you're tracking, it quickens, turns ember-orange, and pulses outward rings. Pure decoration, but it's the detail that makes this feel alive instead of like a stopwatch.
- The Dashboard — a full glassmorphic window (
This Week/Historytabs) for reviewing and editing your ledger. - Full CRUD on sessions — forgot to clock in? Add 3 hours manually, backdated to any time. Left it running overnight? Edit the entry down, or delete it outright.
- Custom weekly rounding — end-of-week totals round to the nearest hour
using a 15-minute threshold:
5:14rounds down to5h,5:15rounds up to6h. This is a deliberate payroll-style rule, notMath.round. - Weeks auto-archive on rollover — the live counter is always just the current week's total; past weeks close automatically and move to History.
- The Weekly Chronicle — an auto-generated recap card with a bit of flavor text summarizing the week, showing both the raw and rounded total.
- Idle Nudge — two-stage, both measured from the same idle stretch:
- 45 min idle while tracking: the widget grows a banner ("the scribe grew restless — still working?") plus a system notification. The session keeps counting; ignoring it changes nothing.
- 60 min idle (i.e. the 45-min nudge went unanswered and idle continued another 15 min): the session auto-stops, exactly as if you'd clicked Stop right then, and the banner switches to a "the scribe dozed off — stopped the clock" choice.
- Either banner offers Keep it all (leave the full idle-inclusive duration — the default, and what happens if you never respond) or Trim idle time (cut the idle stretch off the end). (Windows only for now — see Roadmap.)
- Global hotkey —
Ctrl+Shift+Kstarts/stops tracking from anywhere, even with the widget out of focus. - Launch at login — Chronoscribe registers itself to start automatically when you log in (toggleable any time from the tray menu — "Launch at Login"), so it's just there, the way a tray app should be.
- Lives entirely in the tray — no taskbar icon, no dock clutter. Quit from the tray icon's context menu.
| Layer | Choice | Why |
|---|---|---|
| Shell | Tauri v2 (Rust) | Native tray/multi-window support, ~10–15MB installer vs. Electron's 150MB+, low idle memory footprint — important for something that runs 24/7 |
| UI | React 18 + TypeScript | |
| Styling | Tailwind CSS | glassmorphism theme, dark mode, custom color tokens |
| Motion | Framer Motion | the breathing Familiar, spring transitions, layout animations |
| Database | SQLite, via tauri-plugin-sql |
local-only, zero-config, transactional |
| Icons | lucide-react |
chronoscribe/
├── src/ # React frontend (shared by both windows)
│ ├── widget/ # The floating pill
│ │ ├── Widget.tsx # Start/Stop, live clock, idle-nudge banner
│ │ ├── Familiar.tsx # the breathing/pulsing orb
│ │ └── IdleNudge.tsx # the "still working?" banner
│ ├── dashboard/ # The expanded ledger view
│ │ ├── Dashboard.tsx # tabs, layout, data loading
│ │ └── components/
│ │ ├── ChronicleCard.tsx # weekly recap card
│ │ ├── SessionList.tsx # this week's entries
│ │ ├── SessionEditor.tsx # add/edit modal (manual CRUD)
│ │ └── HistoryPanel.tsx # past weeks
│ ├── db/
│ │ ├── client.ts # shared SQLite connection
│ │ └── sessions.ts # all session/week queries + rounding hookup
│ ├── lib/
│ │ ├── rounding.ts # the 15-minute weekly rounding rule
│ │ └── week.ts # week-boundary math (Mon–Sun)
│ └── index.css # glassmorphism theme, Tailwind layers
├── src-tauri/ # Rust backend
│ ├── src/lib.rs # tray icon, windows, global shortcut,
│ │ # idle watcher, autostart, Tauri commands
│ ├── migrations/001_init.sql # SQLite schema
│ ├── capabilities/default.json # Tauri v2 permission grants
│ ├── icons/ # app/tray icons
│ └── tauri.conf.json # window configs (widget + dashboard)
├── dev-mock/ # browser-only stand-ins for the Tauri API,
│ # used only by `npm run dev:mock`
├── assets/ # logo + README screenshots
├── widget.html # entry point for the widget window
├── dashboard.html # entry point for the dashboard window
└── package.json
sessions — every tracked interval. A row with end_time IS NULL is
the one currently-running session (enforced by a partial unique index —
SQLite can't have two active sessions at once).
| column | type | notes |
|---|---|---|
id |
INTEGER | primary key |
start_time |
TEXT | ISO 8601 |
end_time |
TEXT | NULL while running |
duration_seconds |
INTEGER | NULL while running |
is_manual |
INTEGER | 0/1 — manually added vs. tracked live |
note |
TEXT | optional |
week_start |
TEXT | Monday of that week, yyyy-MM-dd (denormalized for fast weekly queries) |
created_at / updated_at |
TEXT | ISO 8601 |
weeks — one row per week, summarized and archived on rollover.
| column | type | notes |
|---|---|---|
week_start |
TEXT | primary key, yyyy-MM-dd |
week_end |
TEXT | |
total_seconds |
INTEGER | raw sum of that week's sessions |
rounded_minutes |
INTEGER | after the 15-minute rounding rule |
closed_at |
TEXT | NULL = current/live week |
Prerequisites:
- Node.js 18+
- Rust (stable, MSVC toolchain on Windows)
- The Tauri prerequisites for your OS — on Windows, that's the MSVC C++ Build Tools (with the Windows 10/11 SDK component — easy to miss, since the compiler can be present without it) and the WebView2 Runtime (preinstalled on Windows 11).
npm install
npm run tauri devThis launches the widget docked to your screen's top-right corner. Click it
to open the dashboard, or use Ctrl+Shift+K to start/stop from anywhere.
To build the production app + installer:
npm run tauri buildThe installer and standalone .exe land in
src-tauri/target/release/bundle/. Chronoscribe registers itself to launch
at login on first run (toggleable from the tray menu).
For quick visual iteration on styling/animation without the Rust toolchain,
dev-mock/ provides in-memory stand-ins for the Tauri/SQLite APIs:
npm run dev:mockThen open http://localhost:1420/widget.html or .../dashboard.html
directly in a browser. This is dev-only tooling — npm run build and
npm run tauri dev never touch it, and it never ships in the packaged app.
The tray/app icons in src-tauri/icons/ are a generated violet orb matching
the Familiar — good enough for Windows dev/build. Before shipping
cross-platform, regenerate the full set (including .icns for macOS) from a
1024×1024 master image:
npx tauri icon path/to/master-icon.png- WebView2 + OneDrive-synced project folders: if your project directory
is live-synced (OneDrive, Dropbox, etc.), set
WEBVIEW2_USER_DATA_FOLDERto a non-synced path (e.g.%LOCALAPPDATA%\chronoscribe-webview2) before runningtauri dev/tauri build. Sync clients can interfere with WebView2's runtime cache. - Dashboard window: it's declared statically in
tauri.conf.json(hidden until opened) rather than built dynamically at runtime — creating a second Tauri window on demand was unreliable on Windows (the WebView2 control could fail to attach). If you add more windows, prefer this same pattern: declare in config, toggle visibility, don'tWebviewWindowBuilder::build()post-startup.
- macOS (
CGEventSourceSecondsSinceLastEventType) / Linux (X11XScreenSaverQueryInfo) idle detection —get_idle_seconds()insrc-tauri/src/lib.rscurrently only implements Windows and stubs everything else to0(Idle Nudge silently disabled) - CSV/PDF export of the weekly Chronicle for payroll
- Sound design: soft chime on start/stop (toggleable)
- Configurable week-start day / rounding threshold / idle timeouts in a
settings panel (currently constants:
WEEK_STARTS_ONinsrc/lib/week.ts,IDLE_NUDGE_THRESHOLD_SECS/IDLE_AUTOSTOP_THRESHOLD_SECSinsrc-tauri/src/lib.rs)


