Build Timeular

KINDAreplaces $9/mosaves $108/yrback to the verdict

0%0 of 16 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

A manual time tracker built on the observation that manual tracking fails for exactly two reasons: switching takes too long and people forget to start. So the entire build is about making the switch instant and making the gaps recoverable.

estimated effort weekendthe files for this build are in the project pack

SwitchingA global hotkey to a switcher that closes on selectionGapsUntracked time detected and offered for fillingRuntimeTypeScript with a local application and SQLiteRemindersIdle and long-running detection, gentle rather than naggingExportComplete, with rounding applied at export only

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why Runs the local application.

    Get it nodejs.org, or nvm install 22. open ↗

    Verify node --version prints v22 or higher

  2. installfree

    Why Both are platform-specific, and the idle detection in particular differs in what it can see and what permission it needs.

    Get it Whatever your desktop framework provides, checking the permission requirement for idle time before designing onboarding.

    Verify A hotkey fires from another application and idle seconds can be read

  3. have readyfree, and it takes two weeks

    Why Switch friction, gap frequency and reminder tolerance are all behavioural, and two weeks is roughly how long it takes for a tracker to be abandoned if it is annoying.

    Get it Track your real work, including the days you forget entirely, since those produce the gaps the build exists to handle.

    Verify Two weeks exist including at least three days with substantial gaps

  4. decidefree

    Why Gaps are detected against working hours and exports are rounded by an agreed rule, and both are enforced in code rather than being preferences.

    Get it Write down your working hours per day and the rounding rule you have agreed with whoever receives the exports.

    Verify Both are written down and configured before the first export

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- activities(id, name, colour, project_id, hotkey, archived_at)
- entries(id, activity_id, started_at, ended_at, note, source) · source is switched, filled or edited
- gaps(id, from_at, to_at, resolved_by_entry_id, dismissed_at) · untracked time, surfaced rather than ignored
- idle_events(at, duration_seconds, resolution) · resolution is discarded, assigned or kept
- projects(id, name, client, billable, rate_minor, rate_effective_from)
- exports(id, period, rounding_rule, generated_at, path)
- An entry records whether it was created by switching, by filling a gap or by editing, because data entered three days later is not the same evidence as data entered at the time.

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
DB_PATHrequired~/.local/share/timetrack/log.dbYour working time record.
SWITCH_HOTKEYrequiredCtrl+Shift+TThe global hotkey. Choose one nothing else claims.
MAX_SWITCH_MSrequired800The threshold that fails the build. Switch cost decides whether tracking happens.
IDLE_THRESHOLD_SECONDSrequired300When idle time is offered for assignment. Too short and the tracker gets closed.
WORKING_HOURSrequired09:00-18:00Gaps are detected against these. Evenings are not missing data.
ROUNDING_RULErequirednoneApplied at export only. The stored entry keeps the actual duration.

The build, in order

  1. Switching in one keystroke

    Changing what you are tracking takes under a second.

    1. The first switch of the day is the one that decides the habit.

      Files src/switcher.ts

    2. Notes are added to the entry afterwards.

      Files src/entries.ts

    3. A number, not an impression. It is the primary requirement.

      Files src/metrics.ts

    done when · tick each as it passes
    watch out
    • A switcher requiring a mouse, a confirmation or a note before it will change activity costs several seconds each time, and at twenty switches a day people stop using it within a fortnight. Make the switch immediate and let the note be added afterwards to the entry that already exists.
  2. The time you forgot to track

    Gaps are visible and fillable rather than silently lost.

    1. Not against the clock. Evenings are not missing data.

      Files src/gaps.ts

    2. A slow fill flow is the same problem as a slow switch.

      Files src/gaps.ts

    3. Filled three days later is weaker evidence than switched at the time.

      Files src/entries.ts

    done when · tick each as it passes
    watch out
    • Silently ignoring untracked time produces a week that looks like eleven hours of work, and the person concludes the tracker is broken rather than that they forgot. Surface the gaps, make filling one quick, and let dismissal be an answer so the total is honest either way.
  3. Reminders that do not become noise

    You are nudged when it helps and left alone when it does not.

    1. Not during. Interrupting a break is the wrong moment.

      Files src/idle.ts

    2. Once. A timer left running overnight is the case being caught.

      Files src/reminders.ts

    3. Several dismissals is an answer for the day.

      Files src/reminders.ts

    done when · tick each as it passes
    watch out
    • An idle prompt appearing every time you read something for four minutes is a tracker people close by lunchtime. Set the idle threshold from observed behaviour rather than a guess, and stop asking for the day once someone has dismissed several in a row.
  4. Projects, rates and what changed when

    Billable time is computed from the rate that applied at the time.

    1. Applying the current rate backwards reprices invoiced work.

      Files src/rates.tssrc/rates.test.ts

    2. Mid-period changes are the case that gets it wrong.

      Files src/value.ts

    3. So a disagreement is about the rate rather than the arithmetic.

      Files src/report.ts

    done when · tick each as it passes
    watch out
    • Applying the current rate to historic entries silently reprices work you already invoiced, so a report run today disagrees with an invoice sent last month and nobody can reconstruct which was right. Store rates with effective dates and always use the one that applied when the work happened.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more time tracking.