Build Serene
YESreplaces $4/mosaves $48/yrback to the verdict
A focus timer whose hard problems are keeping time correctly across sleep and restarts, changing system state in a way that is always reverted, and being honest that a desktop app cannot truly lock anybody out of anything.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Builds the interface half of the application.
Get it nodejs.org, or nvm install 22. open ↗
Verify
node --version prints v22 or higher - installfree
Why The privileged file operation, the watchdog and the wake notifications all live on this side.
Get it rustup.rs, then the platform packages listed in the Tauri prerequisites. open ↗
Verify
cargo --version prints a version and a bare Tauri app runs - have readyfree
Why The revert paths are the whole safety argument and the only way to test them is to kill the process and pull the power while a block is applied.
Get it A virtual machine with a snapshot taken before the first test.
Verify
A forced power cut with a block applied leaves the machine reachable after reboot - decidefree
Why The watchdog interval is the longest a crash can leave a block in place, and the maximum session length is the ceiling on any single mistake.
Get it Write down both, and the manual revert command, before implementing the blocking.
Verify
Both are in settings and the command is in the README
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- sessions(id, goal, planned_start, planned_end, actual_start, actual_end, outcome, interruptions) - blocklists(id, name, entries_json, active) · the hosts or domains a session blocks - applied_changes(id, session_id, target_path, backup_path, applied_at, reverted_at, revert_method) · written before the change is made - interruptions(id, session_id, at, kind, note) · recorded only as counts and kinds, never what you were doing - day_plans(date, intentions_json) · what the day was meant to contain - settings(key, value) · including the watchdog interval and the maximum session length - The applied_changes row exists before the file is touched, so a crash between the two leaves a record that the next launch can act on.
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.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
DATA_DIR | required | ~/.local/share/focus/focus.db | Sessions, plans and interruption counts. Never leaves the machine. |
HOSTS_FILE_PATH | required | /etc/hosts | The file that is modified. Backed up before every change. |
BACKUP_DIR | required | ~/.local/share/focus/backups | Timestamped copies of the original file, kept until verified reverted. |
WATCHDOG_SECONDS | required | 15 | The longest a crash can leave a block applied. |
MAX_SESSION_MINUTES | required | 120 | A ceiling on any single mistake, enforced regardless of what was entered. |
UPDATE_CHECKS | required | off | Off by default, asserted by the no-network test. |
The build, in order
A timer that is right after the laptop has been shut
Session state is derived from instants, not from a running countdown.
The clock is the source of truth; the interface only renders it.
Files
src/session/time.tssrc/session/time.test.tsThe wake event is the one moment your assumptions are guaranteed stale.
Files
src-tauri/src/power.rsIt happens more often than you think, and it should not grant extra time.
Files
src/session/anomaly.ts
done when · tick each as it passeswatch out- Counting down with an interval in the interface produces a session that pauses while the lid is shut and then resumes as if no time passed, so a twenty five minute session ends ninety minutes later and every statistic afterwards is fiction. Compute from stored instants every time, and treat a wake as a reason to recompute rather than to resume.
Changing the machine, and always changing it back
Every system change is recorded before it happens and reverted by three independent paths.
A crash between the row and the write is recoverable; the reverse is not.
Files
src-tauri/src/blocking.rsThree paths, because the first two can both be killed.
Files
src-tauri/src/watchdog.rstests/revert.test.tsThe timer still works; the speed bump does not.
Files
src/session/blocking.ts
done when · tick each as it passeswatch out- Reverting only on a clean quit means a crash, a forced restart or a battery dying leaves the block in place indefinitely, and the person cannot reach the sites they need with no idea why. Write the applied_changes row first, revert from the watchdog, from the next launch and from a documented manual command, and treat the row rather than the app's memory as the record.
One goal per session, reachable from the keyboard
Starting a session takes a keystroke and a sentence.
Setup friction is the reason focus tools get abandoned.
Files
src/ui/StartSession.tsxStart, extend, end. Nothing else needs one.
Files
src-tauri/src/shortcuts.rsThe fastest accessibility test there is.
Files
docs/keyboard-walkthrough.md
done when · tick each as it passeswatch out- Making the session setup a multi-step form with project, tag, estimate and category means the tool gets skipped on exactly the mornings it would have helped, because the setup costs more attention than the first ten minutes of work. One field, one shortcut, and defaults that come from last time.
What happens when you try to break out
Interruptions are counted honestly without recording what you were doing.
The person already knows they got distracted.
Files
src/ui/BlockPage.tsxEnforced by the schema, not by remembering.
Files
src-tauri/migrations/002_interruptions.sqlA recorded decision beats a session that fizzles out.
Files
src/session/end.ts
done when · tick each as it passeswatch out- Logging every blocked domain to build a report of your distractions creates a browsing history file that nobody asked for, sitting unencrypted beside a focus timer, and it is the first thing a person would be upset to find. Count the interruption, name the goal, and store nothing else.
History that stays here, and an uninstall that leaves nothing behindproduct builder
The data is yours, exportable, and removing the app restores the machine completely.
The claim is only worth making if a test enforces it.
Files
tests/no-network.test.tsThe last chance to prove the machine was handed back.
Files
src-tauri/src/uninstall.rsIncluding what declining it costs, which is usually less than people fear.
Files
README.md
done when · tick each as it passeswatch out- Shipping a desktop app that quietly checks for updates gives a tool whose whole premise is privacy an outbound connection nobody consented to, and on a focus timer that connection carries the timing of your working day. If you want update checks, make them a setting that is off until someone turns it on, and assert the default in a test.
That is the whole plan for Serene. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Application-level blocking.
- Any block that cannot be turned off.
- Screenshots, keystroke counts and usage logs.
- Cross-device sync.
- polished shortcuts and accessibility
- vendor-maintained updates
- cross-device sync
- deep operating-system edge-case coverage
- A weekly review built only from sessions and outcomes already recorded
- A resolver-level blocklist as an alternative mechanism, once the revert paths have been proven
- Scheduled sessions, which need only the existing instants and the shortcut layer
Need the files? The project pack on the verdict page hands your agent the whole brief · more productivity utilities.