Build Session

YESreplaces $3.33/mosaves $39.96/yrback to the verdict

0%0 of 17 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 local focus timer whose hard problems are measuring elapsed time correctly when the machine sleeps, never counting an abandoned session as completed, and being honest about what app blocking a normal application can and cannot do.

estimated effort one sittingthe files for this build are in the project pack

ShellTauri 2 with React and TypeScript, and a tray presenceStorageSQLite, sessions stored as intervals rather than a countdownTimeWall-clock instants for scheduling, both clocks recorded, suspends detectedInputEvery action keyboard-reachable, with a global shortcut to start and stopBlockingOpt-in, permission-gated, and honestly described as a nudge unless the platform offers more

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 Builds the interface and runs the tests.

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

    Verify node --version prints v22 or higher

  2. installfree

    Why Tauri 2 needs it, and the tray, the global shortcut and the natively scheduled notification all live there.

    Get it rustup.rs, then the platform build dependencies Tauri lists. open ↗

    Verify cargo --version prints a version and a dev build shows a tray icon

  3. have readyfree

    Why The suspend behaviour is the central correctness problem here and it cannot be tested in a unit test, only by closing a lid and opening it again.

    Get it Any laptop, or a desktop you can suspend and wake on demand.

    Verify A session started before a suspend produces a suspends row after waking

  4. have readyfree

    Why What blocking can honestly mean differs completely by operating system, and writing this feature without reading that first produces either a placebo or a request for a permission you do not need.

    Get it Read the operating system vendor's own developer documentation on the relevant mechanism and permission.

    Verify You can state in one sentence what your platform allows and what it does not

  5. decidefree

    Why Whether a session slept through counts as focus is a judgement that changes every number in the history, and it must be made before the history exists.

    Get it Write one paragraph covering suspend, forward clock change and backward clock change.

    Verify The paragraph is in the README and each case is named in a test

Data model

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

- sessions(id, intent, planned_seconds, started_at_utc, ended_at_utc, outcome, tag) · outcome is completed, stopped, abandoned or interrupted
- intervals(session_id, ordinal, kind, started_at_utc, ended_at_utc, monotonic_start_ms, monotonic_end_ms) · kind is focus, pause or suspend
- suspends(session_id, detected_at_utc, wall_gap_s, monotonic_gap_s, resolution) · when the two clocks disagree, this row records by how much
- breaks(session_id, ordinal, planned_seconds, started_at_utc, ended_at_utc, skipped)
- blocklist(id, pattern, kind, enabled) · kind names the mechanism, because what is possible differs per platform
- days(date, focus_seconds, completed_sessions, abandoned_sessions) · derived, rebuildable from intervals
- There is no remaining-seconds column anywhere. Remaining is computed from the stored instants on every tick, which is why a crash costs nothing.

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
DATA_DIRrequired~/.local/share/focusThe SQLite file with sessions, intervals and suspends.
DEFAULT_SESSION_MINUTESrequired25The planned duration for a new session. Stored per session, so changing it does not rewrite history.
DEFAULT_BREAK_MINUTESrequired5Breaks are recorded like sessions and can be skipped.
SUSPEND_THRESHOLD_SECONDSrequired60A clock divergence larger than this is treated as a suspend and resolved by the documented rule.
GLOBAL_SHORTCUTrequiredCmdOrCtrl+Shift+FStarts and stops without the window. Conflicts are the commonest setup failure.
BLOCKING_MODErequirednudgeWhatever your platform genuinely supports. The interface label is generated from this value.

The build, in order

  1. Time that survives a closed lid

    A session's end is derived from recorded instants, and a suspend is detected and recorded rather than silently absorbed.

    1. The countdown is a rendering, not a state.

      Files src-tauri/src/session.rssrc/timer/remaining.ts

    2. Their disagreement is the only way to see a suspend.

      Files src-tauri/src/clocks.rstests/suspend-detection.test.ts

    3. Does a slept twenty minutes count as focus? Decide once, in the README.

      Files docs/time-policy.md

    done when · tick each as it passes
    watch out
    • Counting down with a monotonic timer and assuming it behaves the same everywhere is wrong in a way you will only see on someone else's machine: Rust's own standard library documentation says it is not specified whether system suspends count as elapsed time, that the behaviour varies across platforms and Rust versions, and that Instant uses CLOCK_UPTIME_RAW on Darwin, CLOCK_MONOTONIC on other Unix systems and QueryPerformanceCounter on Windows (checked against the Rust documentation). Record both clocks, detect the divergence, and decide explicitly what a twenty minute sleep does to a twenty five minute session.
  2. Start, pause, stop, from the keyboard

    The session state machine is append-only and every transition has a key.

    1. The total is a query; the intervals are the truth.

      Files src/timer/intervals.ts

    2. Including the ones you think nobody uses.

      Files src/shortcuts.tstests/keyboard-complete.test.ts

    3. So the timer does not need a window to be useful.

      Files src-tauri/src/tray.rs

    done when · tick each as it passes
    watch out
    • Implementing pause by subtracting from a duration field loses the history and double-counts after a crash, because the app has no way to know whether the subtraction already happened. Append a pause interval and a resume interval, and derive focus time by summing focus intervals only.
  3. A chime that arrives on time

    The end of a session is announced when it happens, whatever the window was doing.

    1. The web view tick is decoration.

      Files src-tauri/src/notify.rs

    2. Two moments where a stale display becomes a wrong decision.

      Files src/timer/sync.ts

    3. Sleeping through the end is the normal case, not the edge case.

      Files src/timer/sync.tstests/late-resume.test.ts

    done when · tick each as it passes
    watch out
    • Driving the timer from a timer callback in the web view means the operating system throttles it when the window is not in front, so the chime for a twenty five minute session arrives several minutes late and only when you happen to click back, which is precisely the moment the timer was supposed to save you from. Schedule against the stored end instant natively and treat the interface tick as display only.
  4. History that does not flatter you

    Statistics count what happened, and an abandoned session is not a completed one.

    1. Four outcomes, and only one of them is completed.

      Files src/sessions/outcome.tstests/abandoned.test.ts

    2. And assert the rebuild matches, so the derived table cannot drift.

      Files src/stats/rebuild.ts

    3. The number that is actually useful is the honest one.

      Files src/views/history.tsx

    done when · tick each as it passes
    watch out
    • Counting any session that reached its end instant as completed rewards walking away from the machine, and within a fortnight the statistics say you focused for six hours on a day you did not, which makes the entire history worthless for the one decision it exists to support. Mark a session abandoned when the application was not running at its end, and show abandoned time separately.
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 productivity utilities.