TreeMap
무료 오픈소스 디스크 사용량 시각화 도구로, 사각형 트리맵·중복 파일 탐색·디스크 가득 예측·정리 시 파일을 휴지통으로만 이동한다
프리랜서를 위한 로컬 타임 트래커, 브라우저만으로 동작하고 모든 데이터를 컴퓨터의 SQLite 파일에 저장
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.
생산성Vue · TypeScriptGitHub @TimWriter온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기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.