TreeMap
A free, open-source disk-space visualizer. Squarified treemap, duplicate hunting, disk-full forecasting, and cleanup that only ever moves files to the Trash.
Local-first time tracker for freelancers — runs entirely in the browser, stores everything in one SQLite file on your computer. No backend, no login, no cloud.
ProductivityVue · TypeScriptGitHub @TimWriterOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Loading the full README. The saved excerpt is shown below.
A local, single-user time tracker for freelancers — a Toggl Track replacement that runs entirely in the browser. There is no backend, no login, and no cloud database: every client, project entry, and timer lives in a single SQLite file on your own computer.
Open the live demo — runs entirely in your browser, nothing to install. Click "Create new database file" and pick anywhere on disk to try it with real data (or throw the file away afterwards).Read the full README ↗A local, single-user time tracker for freelancers — a Toggl Track replacement that runs entirely in the browser. There is no backend, no login, and no cloud database: every client, project entry, and timer lives in a single SQLite file on your own computer.
Open the live demo — runs entirely in your browser, nothing to install. Click "Create new database file" and pick anywhere on disk to try it with real data (or throw the file away afterwards).
Prefer not to use a hosted page at all? Download hourglass.html and open it directly — no server, no Node, no install. It's a single file, rebuilt automatically from main on every push (see Offline, no-server build below for how that works).
Built with Nuxt, Nuxt UI v4, sql.js (SQLite compiled to WebAssembly) and the browser's File System Access API.
The visual style (Outfit font, purple accent, dark floating nav bar, pastel calendar entries, generous rounded corners) is modeled after the moodboard in reference/. The whole radius scale is driven by the single --ui-radius token in app/assets/css/main.css, and the primary color is a custom purple scale defined there too. Component-level style tweaks (e.g. neutral subtle-variant buttons/badges rendering as flat zinc-100/800 instead of Nuxt UI's default bg-elevated) live in app/app.config.ts's ui overrides, not scattered class props on individual components.
[email protected] via packageManager / Corepack).pnpm install
pnpm dev
Open the printed http://localhost:3000 URL in Chrome or Edge.
pnpm build
pnpm preview
The app is fully client-side (ssr: false), so the production build is a static SPA — it can be hosted on any static file host.
pnpm build:offline
This produces a single self-contained file at .output/offline/hourglass.html that you can open directly from disk (double-click it, or drag it into a browser) — no server, no Node, nothing to host. It's a normal nuxt generate output, but scripts/pack-offline.mjs then inlines the JS bundle, CSS, fonts, logo, and the sql.js WebAssembly binary directly into that one HTML file as data: URIs, and routing uses hash mode (#/track instead of /track). This is necessary because Chrome refuses to load ES module <script src> tags or make any fetch() request for a local file from a file:// page, and history-mode routing can't resolve real sub-paths without a server.
Re-run pnpm build:offline any time the app changes — it's not kept in sync automatically.
On first launch you'll see a "Connect your database file" screen:
data/ folder inside this project, or anywhere else you like) and a filename such as hourglass.sqlite. A new, empty database with the right schema is created there..sqlite file created earlier by this app.The app remembers a reference to that file (via IndexedDB) so it reconnects automatically the next time you open the page — Chrome only needs to re-ask for permission if you restart the browser, in which case a "Reconnect database" button appears (browsers require a real click to re-grant file access, so this can't happen fully automatically).
Every change (starting/stopping a timer, editing an entry, adding a client, …) is written back to that .sqlite file after a short debounce, and also flushed immediately when you switch tabs or close the page. A small badge in the top-right corner shows Saved / Saving… / Save failed at all times.
Because the data lives in one ordinary file, you can back it up, sync it with your own tools (e.g. a synced folder), or open it with any SQLite browser to inspect it directly.
DateRangeButton, shared with Overview) that opens a Nuxt UI calendar plus a "This week" shortcut; picking any day jumps to the week containing it, and the button's label reflects whichever week is showing. Entries can be created by clicking and dragging on empty time, moved by dragging, resized from either edge (snapping granularity is configurable in Settings), and edited or deleted via a click — the edit modal's time fields are precise to the second. A timer left running is automatically stopped and flagged for review after a configurable number of hours (default 24, can be turned off). If the tab is backgrounded (or the computer sleeps) for longer than a configurable idle threshold (default 10 minutes) while a timer is running, coming back prompts you to keep the gap as billable time or discard it and continue tracking from now.DateRangeButton component is used here in true range mode: This week / This month / Last month presets, or an arbitrary two-click custom range from the calendar — picking a preset (or a custom range that happens to match one) replaces whatever was showing, and the button's label switches between the preset name and the formatted date range accordingly. Billing is calculated live from time entries; nothing is marked as "invoiced" or persisted separately.meta table, so it travels with the database rather than living in browser storage — switching files means the new file's own settings apply, not the ones you just set.app/
composables/ useDatabase (sql.js + file persistence), useClients, useTimeEntries,
useSettings, useIdleDetection, useNow
components/ SetupScreen, WeekCalendar, TimerBar, ClientFormModal, EntryEditModal,
IdleDetectionModal, DateRangeButton, ...
pages/ track.vue, clients.vue, overview.vue, settings.vue
utils/ format.ts, week.ts, stats.ts, date.ts
scripts/
pack-offline.mjs packs `nuxt generate`'s output into the single-file offline build
^4.11.1 already installed, so the app was built against the v4 component API (the v3-era patterns referenced in the original brief no longer apply — e.g. UPopover/UModal use v-model:open and named slots rather than the old v-model + default-slot style).<input type="time">/<input type="date"> fields used elsewhere (entry start/end, billing range) are rendered by the browser according to its own locale and can't be overridden from JS in a cross-browser way.rate_snapshot meaningful after reassigning an entry to a different client while still allowing a manual override.app/router.options.ts) rather than history mode, so URLs look like /#/track instead of /track even when hosted normally. This was needed to support the offline single-file build (see above), where there's no server to resolve a real /track sub-path, and hash mode works identically either way.