Time tracking in Timify is designed to stay out of your way. A single button press starts a timer against the active project; another press stops it. Behind the scenes, Timify recordsDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/Aking16/timify/llms.txt
Use this file to discover all available pages before exploring further.
startTime and endTime as Unix timestamps, calculates duration in seconds, and immediately reflects the running entry as a live, ticking clock in the UI. All data is stored locally in SQLite so everything works completely offline.
Time entry data shape
Each row in thetime_entries table holds the following fields.
UUID primary key, auto-generated with
crypto.randomUUID() on insert.Foreign key referencing the
user table. Deleting a user cascades and removes all their time entries.Foreign key referencing the
projects table. Set to null if the associated project is deleted (onDelete: "set null").Short label for the entry. Must be between 4 and 32 characters when edited. New entries default to a placeholder title.
Longer description. Must be at least 4 characters when edited.
When the timer was started. Defaults to the current Unix time via
unixepoch(). Used as the reference point for all duration calculations.When the timer was stopped.
null while the entry is still running.Elapsed time in seconds, stored as a calculated field when the timer stops:
Math.floor((endTime - startTime) / 1000). null while running.true while the timer is active. Defaults to true on insert. Set to false when stopped.Whether this entry should count toward billing. Defaults to
false.Per-entry rate override. When set, this takes precedence over the parent project’s
hourlyRate. Optional — null means “use project rate”.Creation timestamp, set automatically by SQLite.
Last-updated timestamp, refreshed on every write via Drizzle’s
$onUpdateFn.Starting a timer
Select an active project
A project must be selected before a timer can start. If none is active, Timify redirects you to
/app/projects. The active project is stored in localStorage under the key "active-project".Press Start Timer
Click the Start Timer button in the sidebar or the bottom navigation bar. This calls the
createTimeEntry server action with the current projectId.Existing timers are stopped automatically
Before inserting the new entry,
createTimeEntry queries all running entries for your user (isRunning = true) and stops each one by computing its duration and setting isRunning = false, endTime = now.Only one timer can be running at a time per user. Starting a second timer automatically stops all currently running entries before creating the new one.
Real-time duration display
While a timer is running, the duration shown in the entry card ticks every second. This is powered by theuseRealtimeDuration hook.
Running entry
The hook fires
calculateDuration every 1 000 ms using ahooks/useInterval, displaying elapsed time as H:MM:SS or MM:SS.Stopped entry
The interval is paused (
delay = undefined) and the formatted staticDuration from the database is displayed instead.Duration formatting
TheformatDuration(seconds) utility converts raw seconds to a human-readable string:
Stopping a timer
Click the Stop button on the running entry card. This callsstopTimeEntry with the entry’s id.
isRunning: false, endTime: now, and duration to the database in a single db.update call and revalidates the get-time-entries cache tag.
Editing a time entry
Open the entry’s edit panel by clicking the edit (pencil) icon on an entry card. TheeditTimeEntry server action accepts the following fields.
Validation rules for editTimeEntry
| Field | Rule |
|---|---|
id | Required · existing entry UUID |
title | Required · 4–32 characters |
description | Required · minimum 4 characters |
startTime | Required · ISO date string, converted to Date |
endTime | Optional · ISO date string, converted to Date |
duration is recalculated from the new startTime and endTime:
Deleting a time entry
CalldeleteTimeEntry(id) from the entry card’s action menu. The action verifies your session, runs db.delete(timeEntries).where(eq(timeEntries.id, id)), and revalidates the entry list. This action is permanent — there is no undo.
Billable entries and hourly rate override
Each entry has abillable boolean (default false) and an optional hourlyRate (default null). When hourlyRate is set on an entry it overrides the parent project’s rate for any billing calculations in reports. Toggle the Billable switch in the entry edit panel to mark work as billable.