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.
翻譯準備中
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 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).檢視完整 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.