Vibecode Serene
track this build4 phases, 12 steps, beginner friendly0%🪦 Serene is no longer sold; sereneapp.com now redirects to its maker Venture Harbour. The verdict below is now a post-mortem.
Serene's solo core is compact: build a local desktop utility that captures the essential focus planning workflow with keyboard-first controls and local storage. A competent builder can reach a useful personal version in one sitting, while the paid product mainly wins on polish, OS integration.
You are building a lean indie version of Serene. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Serene · indie build 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. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Shell | Tauri 2 with React and TypeScript, no network calls at all | what you work on is nobody else's business | | Time | Sessions stored as instants and recomputed from the clock on every wake | a countdown held in a timer variable is wrong after the first sleep | | Blocking | A name resolution change applied with explicit elevation, recorded before it is made | the only mechanism a desktop app can use honestly | | Safety | A watchdog that reverts on exit, on crash and at next launch | this is the part that stops the tool being dangerous | | Storage | SQLite for sessions and goals, everything local | no history leaves the machine, ever | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Builds the interface half of the application. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **A Rust toolchain and the Tauri prerequisites** · free - 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. - Verify: cargo --version prints a version and a bare Tauri app runs - [ ] **A machine or virtual machine you can crash safely** · free - 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 - [ ] **Your watchdog interval and maximum session length** · free - 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 ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - 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 If one of those is essential to you, that is the reason to keep paying for Serene, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Serene The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a focus session timer that blocks what you asked it to and always gives the machine back to replace Serene. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero One rule, and it outranks every feature in this build. Anything this app changes about the system is reverted, whatever happens to the app. A focus tool that crashes with a modified hosts file leaves someone unable to reach their bank at nine at night, and they will not remember which application did it. So: write down what you changed before you change it, keep a copy of the original file with a timestamp, revert on quit, on crash and on next launch, and print the manual undo command in the README and in the interface. The honest corollary: this is a speed bump, not a lock. Apple's parental control frameworks are documented for iOS, iPadOS, Mac Catalyst and visionOS rather than for macOS, and their entitlement needs Apple's permission before an app can ship with it, so a desktop application has no supported way to stop another application from running. Say that in the interface rather than implying a discipline the operating system will not enforce for you. ### Stack (fixed, do not substitute) - Shell: Tauri 2 with React and TypeScript, no network calls at all · what you work on is nobody else's business - Time: Sessions stored as instants and recomputed from the clock on every wake · a countdown held in a timer variable is wrong after the first sleep - Blocking: A name resolution change applied with explicit elevation, recorded before it is made · the only mechanism a desktop app can use honestly - Safety: A watchdog that reverts on exit, on crash and at next launch · this is the part that stops the tool being dangerous - Storage: SQLite for sessions and goals, everything local · no history leaves the machine, ever ### Data model (create this before Phase 1) - 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. ### Phase 1 · A timer that is right after the laptop has been shut Build: Store planned start and end as instants. Derive remaining time from the current clock on every render and on every wake notification. Handle three cases explicitly: the machine slept through the end of a session, the app was closed during a session, and the system clock moved backwards. Trap: 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. Done when: A session whose end passed while the machine slept is completed on wake with the true end time, tested by moving the clock. Closing and reopening the app during a session restores the correct remaining time. A backwards clock jump does not extend a session, and the anomaly is recorded. No countdown value is ever persisted, verified by inspection of the schema. Do not build yet: No blocking, planning or history yet. ### Phase 2 · Changing the machine, and always changing it back Build: Record the intended change and copy the original file before touching anything. Apply the name resolution change with an explicit elevation prompt that says exactly what will change. Revert on session end, on application exit and at next launch if an unreverted row exists. Run a watchdog that reverts if the main process disappears. Trap: 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. Done when: Killing the application mid-session reverts the change within the watchdog interval, tested by killing the process. An unreverted row found at launch is reverted before the interface renders, tested by writing one manually. The original file is restored byte for byte, verified by hash. The elevation prompt names the file and the change, and declining it leaves the session running without blocking rather than failing. Do not build yet: No planning, statistics or packaging yet. ### Phase 3 · One goal per session, reachable from the keyboard Build: A day plan of intentions and a session that takes one goal in plain text. Global shortcuts for start, extend and end. Every action reachable without the mouse, with visible focus and an accessible name for each control. No numbers for the day beyond what was actually planned and completed. Trap: 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. Done when: A session starts from a global shortcut and one text field, with no other required input. Every control has a keyboard path and a visible focus state, checked against a keyboard-only walkthrough in the test notes. Extending and ending a session are single actions with their own shortcuts. The day plan is optional and its absence never blocks a session. Do not build yet: No statistics, export or packaging yet. ### Phase 4 · What happens when you try to break out Build: When a blocked name is requested, show a local page naming the session goal and offering to end the session deliberately. Count interruptions by kind. Record nothing about which site, which application or which document, because none of that is needed for the count and all of it is a liability. Trap: 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. Done when: The interruptions table contains counts and kinds and no site, application or document names, asserted by a schema test. Ending a session early is a deliberate action that records the outcome rather than silently stopping. The block page is served locally with no network request. Turning off blocking mid-session reverts immediately and the session continues. Do not build yet: No export or packaging yet. ### Phase 5 · History that stays here, and an uninstall that leaves nothing behind Build: Export sessions and plans as CSV and JSON. Add an uninstall command that reverts any change, removes the block entries and reports every file it touched. Verify in the suite that the application makes no outbound network connection at all. Package for the platforms you use and document exactly which permission each prompt is asking for and why. Trap: 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. Done when: A test asserts the application opens no outbound connection in a normal session, run with the network blocked. The uninstall command reverts every applied change, removes app files and prints the list, tested from a session left running. Export produces CSV and JSON covering sessions, goals and interruption counts. Each permission prompt is documented with what is being asked and what happens if it is declined. Do not build yet: Nothing after this. ### Out of scope (and why) - Blocking applications rather than names, which a desktop app cannot do through a supported interface on macOS, and which through unsupported means is both fragile and a security decision you should not make quietly. - Anything that cannot be turned off, because a focus tool that a determined person cannot escape is a tool that traps somebody with a genuine emergency. - Screenshots, keystroke counts and application usage logs, which turn a timer into surveillance of yourself or, worse, of someone else. - Sync between devices, which would put your working hours and goals on somebody else's server for very little in return. ### README must contain - That every system change is recorded before it is made and reverted on quit, on crash and at next launch - The manual command to revert a block if all three automatic paths somehow fail - That this is a speed bump rather than a lock, and that no supported desktop interface exists to stop applications running - That session time is computed from stored instants, so sleep and restarts do not distort the record - That nothing leaves the machine, that a test enforces it, and how to run that test ===== AGENTS.md ===== # Agent instructions · Serene indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Tauri 2 with React and TypeScript, no network calls at all, Sessions stored as instants and recomputed from the clock on every wake, A name resolution change applied with explicit elevation, recorded before it is made, A watchdog that reverts on exit, on crash and at next launch, SQLite for sessions and goals, everything local. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - 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. - 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. - 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. - 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. ===== BUILD_PLAN.md ===== # Build plan · Serene 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. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A timer that is right after the laptop has been shut Session state is derived from instants, not from a running countdown. ### Steps 1. Store instants and derive the rest The clock is the source of truth; the interface only renders it. Files: `src/session/time.ts`, `src/session/time.test.ts` 2. Recompute on every wake The wake event is the one moment your assumptions are guaranteed stale. Files: `src-tauri/src/power.rs` 3. Handle a backwards clock explicitly It happens more often than you think, and it should not grant extra time. Files: `src/session/anomaly.ts` ### Done when - [ ] Sleeping through the end completes the session correctly - [ ] Reopening mid-session restores the right remaining time - [ ] A backwards clock does not extend a session - [ ] No countdown value is persisted ### Watch 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. ## Phase 2 · Changing the machine, and always changing it back Every system change is recorded before it happens and reverted by three independent paths. ### Steps 1. Record the change before making it A crash between the row and the write is recoverable; the reverse is not. Files: `src-tauri/src/blocking.rs` 2. Revert from a watchdog, at launch, and by hand Three paths, because the first two can both be killed. Files: `src-tauri/src/watchdog.rs`, `tests/revert.test.ts` 3. Degrade to no blocking when elevation is declined The timer still works; the speed bump does not. Files: `src/session/blocking.ts` ### Done when - [ ] A killed process reverts within the watchdog interval - [ ] An unreverted row at launch is reverted first - [ ] The original file is restored byte for byte - [ ] Declining elevation degrades rather than fails ### Watch 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. ## Phase 3 · One goal per session, reachable from the keyboard Starting a session takes a keystroke and a sentence. ### Steps 1. One field to start, defaults from last time Setup friction is the reason focus tools get abandoned. Files: `src/ui/StartSession.tsx` 2. Global shortcuts for the three actions Start, extend, end. Nothing else needs one. Files: `src-tauri/src/shortcuts.rs` 3. Walk it once with the mouse unplugged The fastest accessibility test there is. Files: `docs/keyboard-walkthrough.md` ### Done when - [ ] A session starts from a shortcut and one field - [ ] Every control has a keyboard path and focus state - [ ] Extend and end have their own shortcuts - [ ] The day plan is optional ### Watch 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. ## Phase 4 · What happens when you try to break out Interruptions are counted honestly without recording what you were doing. ### Steps 1. Show the goal, not a scolding The person already knows they got distracted. Files: `src/ui/BlockPage.tsx` 2. Count kinds, store nothing identifying Enforced by the schema, not by remembering. Files: `src-tauri/migrations/002_interruptions.sql` 3. Make ending early explicit A recorded decision beats a session that fizzles out. Files: `src/session/end.ts` ### Done when - [ ] No site or application names are stored - [ ] Ending early records an outcome - [ ] The block page makes no network request - [ ] Disabling blocking mid-session reverts at once ### Watch 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. ## Not in this build - Application-level blocking. - Any block that cannot be turned off. - Screenshots, keystroke counts and usage logs. - Cross-device sync. ## After v1, if you want it - 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 ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Sessions, plans and interruption counts. Never leaves the machine. DATA_DIR=~/.local/share/focus/focus.db # Required. The file that is modified. Backed up before every change. HOSTS_FILE_PATH=/etc/hosts # Required. Timestamped copies of the original file, kept until verified reverted. BACKUP_DIR=~/.local/share/focus/backups # Required. The longest a crash can leave a block applied. WATCHDOG_SECONDS=15 # Required. A ceiling on any single mistake, enforced regardless of what was entered. MAX_SESSION_MINUTES=120 # Required. Off by default, asserted by the no-network test. UPDATE_CHECKS=off
You are building a lean indie version of Serene. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Serene · indie build 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. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Shell | Tauri 2 with React and TypeScript, no network calls at all | what you work on is nobody else's business | | Time | Sessions stored as instants and recomputed from the clock on every wake | a countdown held in a timer variable is wrong after the first sleep | | Blocking | A name resolution change applied with explicit elevation, recorded before it is made | the only mechanism a desktop app can use honestly | | Safety | A watchdog that reverts on exit, on crash and at next launch | this is the part that stops the tool being dangerous | | Storage | SQLite for sessions and goals, everything local | no history leaves the machine, ever | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Builds the interface half of the application. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **A Rust toolchain and the Tauri prerequisites** · free - 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. - Verify: cargo --version prints a version and a bare Tauri app runs - [ ] **A machine or virtual machine you can crash safely** · free - 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 - [ ] **Your watchdog interval and maximum session length** · free - 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 ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - 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 If one of those is essential to you, that is the reason to keep paying for Serene, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Serene The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a focus session timer that blocks what you asked it to and always gives the machine back to replace Serene. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero One rule, and it outranks every feature in this build. Anything this app changes about the system is reverted, whatever happens to the app. A focus tool that crashes with a modified hosts file leaves someone unable to reach their bank at nine at night, and they will not remember which application did it. So: write down what you changed before you change it, keep a copy of the original file with a timestamp, revert on quit, on crash and on next launch, and print the manual undo command in the README and in the interface. The honest corollary: this is a speed bump, not a lock. Apple's parental control frameworks are documented for iOS, iPadOS, Mac Catalyst and visionOS rather than for macOS, and their entitlement needs Apple's permission before an app can ship with it, so a desktop application has no supported way to stop another application from running. Say that in the interface rather than implying a discipline the operating system will not enforce for you. ### Stack (fixed, do not substitute) - Shell: Tauri 2 with React and TypeScript, no network calls at all · what you work on is nobody else's business - Time: Sessions stored as instants and recomputed from the clock on every wake · a countdown held in a timer variable is wrong after the first sleep - Blocking: A name resolution change applied with explicit elevation, recorded before it is made · the only mechanism a desktop app can use honestly - Safety: A watchdog that reverts on exit, on crash and at next launch · this is the part that stops the tool being dangerous - Storage: SQLite for sessions and goals, everything local · no history leaves the machine, ever ### Data model (create this before Phase 1) - 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. ### Phase 1 · A timer that is right after the laptop has been shut Build: Store planned start and end as instants. Derive remaining time from the current clock on every render and on every wake notification. Handle three cases explicitly: the machine slept through the end of a session, the app was closed during a session, and the system clock moved backwards. Trap: 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. Done when: A session whose end passed while the machine slept is completed on wake with the true end time, tested by moving the clock. Closing and reopening the app during a session restores the correct remaining time. A backwards clock jump does not extend a session, and the anomaly is recorded. No countdown value is ever persisted, verified by inspection of the schema. Do not build yet: No blocking, planning or history yet. ### Phase 2 · Changing the machine, and always changing it back Build: Record the intended change and copy the original file before touching anything. Apply the name resolution change with an explicit elevation prompt that says exactly what will change. Revert on session end, on application exit and at next launch if an unreverted row exists. Run a watchdog that reverts if the main process disappears. Trap: 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. Done when: Killing the application mid-session reverts the change within the watchdog interval, tested by killing the process. An unreverted row found at launch is reverted before the interface renders, tested by writing one manually. The original file is restored byte for byte, verified by hash. The elevation prompt names the file and the change, and declining it leaves the session running without blocking rather than failing. Do not build yet: No planning, statistics or packaging yet. ### Phase 3 · One goal per session, reachable from the keyboard Build: A day plan of intentions and a session that takes one goal in plain text. Global shortcuts for start, extend and end. Every action reachable without the mouse, with visible focus and an accessible name for each control. No numbers for the day beyond what was actually planned and completed. Trap: 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. Done when: A session starts from a global shortcut and one text field, with no other required input. Every control has a keyboard path and a visible focus state, checked against a keyboard-only walkthrough in the test notes. Extending and ending a session are single actions with their own shortcuts. The day plan is optional and its absence never blocks a session. Do not build yet: No statistics, export or packaging yet. ### Phase 4 · What happens when you try to break out Build: When a blocked name is requested, show a local page naming the session goal and offering to end the session deliberately. Count interruptions by kind. Record nothing about which site, which application or which document, because none of that is needed for the count and all of it is a liability. Trap: 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. Done when: The interruptions table contains counts and kinds and no site, application or document names, asserted by a schema test. Ending a session early is a deliberate action that records the outcome rather than silently stopping. The block page is served locally with no network request. Turning off blocking mid-session reverts immediately and the session continues. Do not build yet: No export or packaging yet. ### Phase 5 · History that stays here, and an uninstall that leaves nothing behind Build: Export sessions and plans as CSV and JSON. Add an uninstall command that reverts any change, removes the block entries and reports every file it touched. Verify in the suite that the application makes no outbound network connection at all. Package for the platforms you use and document exactly which permission each prompt is asking for and why. Trap: 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. Done when: A test asserts the application opens no outbound connection in a normal session, run with the network blocked. The uninstall command reverts every applied change, removes app files and prints the list, tested from a session left running. Export produces CSV and JSON covering sessions, goals and interruption counts. Each permission prompt is documented with what is being asked and what happens if it is declined. Do not build yet: Nothing after this. ### Out of scope (and why) - Blocking applications rather than names, which a desktop app cannot do through a supported interface on macOS, and which through unsupported means is both fragile and a security decision you should not make quietly. - Anything that cannot be turned off, because a focus tool that a determined person cannot escape is a tool that traps somebody with a genuine emergency. - Screenshots, keystroke counts and application usage logs, which turn a timer into surveillance of yourself or, worse, of someone else. - Sync between devices, which would put your working hours and goals on somebody else's server for very little in return. ### README must contain - That every system change is recorded before it is made and reverted on quit, on crash and at next launch - The manual command to revert a block if all three automatic paths somehow fail - That this is a speed bump rather than a lock, and that no supported desktop interface exists to stop applications running - That session time is computed from stored instants, so sleep and restarts do not distort the record - That nothing leaves the machine, that a test enforces it, and how to run that test ===== AGENTS.md ===== # Agent instructions · Serene indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Tauri 2 with React and TypeScript, no network calls at all, Sessions stored as instants and recomputed from the clock on every wake, A name resolution change applied with explicit elevation, recorded before it is made, A watchdog that reverts on exit, on crash and at next launch, SQLite for sessions and goals, everything local. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - 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. - 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. - 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. - 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. ===== BUILD_PLAN.md ===== # Build plan · Serene 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. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A timer that is right after the laptop has been shut Session state is derived from instants, not from a running countdown. ### Steps 1. Store instants and derive the rest The clock is the source of truth; the interface only renders it. Files: `src/session/time.ts`, `src/session/time.test.ts` 2. Recompute on every wake The wake event is the one moment your assumptions are guaranteed stale. Files: `src-tauri/src/power.rs` 3. Handle a backwards clock explicitly It happens more often than you think, and it should not grant extra time. Files: `src/session/anomaly.ts` ### Done when - [ ] Sleeping through the end completes the session correctly - [ ] Reopening mid-session restores the right remaining time - [ ] A backwards clock does not extend a session - [ ] No countdown value is persisted ### Watch 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. ## Phase 2 · Changing the machine, and always changing it back Every system change is recorded before it happens and reverted by three independent paths. ### Steps 1. Record the change before making it A crash between the row and the write is recoverable; the reverse is not. Files: `src-tauri/src/blocking.rs` 2. Revert from a watchdog, at launch, and by hand Three paths, because the first two can both be killed. Files: `src-tauri/src/watchdog.rs`, `tests/revert.test.ts` 3. Degrade to no blocking when elevation is declined The timer still works; the speed bump does not. Files: `src/session/blocking.ts` ### Done when - [ ] A killed process reverts within the watchdog interval - [ ] An unreverted row at launch is reverted first - [ ] The original file is restored byte for byte - [ ] Declining elevation degrades rather than fails ### Watch 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. ## Phase 3 · One goal per session, reachable from the keyboard Starting a session takes a keystroke and a sentence. ### Steps 1. One field to start, defaults from last time Setup friction is the reason focus tools get abandoned. Files: `src/ui/StartSession.tsx` 2. Global shortcuts for the three actions Start, extend, end. Nothing else needs one. Files: `src-tauri/src/shortcuts.rs` 3. Walk it once with the mouse unplugged The fastest accessibility test there is. Files: `docs/keyboard-walkthrough.md` ### Done when - [ ] A session starts from a shortcut and one field - [ ] Every control has a keyboard path and focus state - [ ] Extend and end have their own shortcuts - [ ] The day plan is optional ### Watch 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. ## Phase 4 · What happens when you try to break out Interruptions are counted honestly without recording what you were doing. ### Steps 1. Show the goal, not a scolding The person already knows they got distracted. Files: `src/ui/BlockPage.tsx` 2. Count kinds, store nothing identifying Enforced by the schema, not by remembering. Files: `src-tauri/migrations/002_interruptions.sql` 3. Make ending early explicit A recorded decision beats a session that fizzles out. Files: `src/session/end.ts` ### Done when - [ ] No site or application names are stored - [ ] Ending early records an outcome - [ ] The block page makes no network request - [ ] Disabling blocking mid-session reverts at once ### Watch 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. ## Not in this build - Application-level blocking. - Any block that cannot be turned off. - Screenshots, keystroke counts and usage logs. - Cross-device sync. ## After v1, if you want it - 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 ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Sessions, plans and interruption counts. Never leaves the machine. DATA_DIR=~/.local/share/focus/focus.db # Required. The file that is modified. Backed up before every change. HOSTS_FILE_PATH=/etc/hosts # Required. Timestamped copies of the original file, kept until verified reverted. BACKUP_DIR=~/.local/share/focus/backups # Required. The longest a crash can leave a block applied. WATCHDOG_SECONDS=15 # Required. A ceiling on any single mistake, enforced regardless of what was entered. MAX_SESSION_MINUTES=120 # Required. Off by default, asserted by the no-network test. UPDATE_CHECKS=off
You are building a production product version of Serene. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # Serene · product brief ## Problem Serene's solo core is compact: build a local desktop utility that captures the essential focus planning workflow with keyboard-first controls and local storage. A competent builder can reach a useful personal version in one sitting, while the paid product mainly wins on polish, OS integration. ## Product outcome A focus timer that keeps honest time through sleep and restarts, applies a speed bump you consented to, reverts it by three independent paths, stores no record of what distracted you, and never opens a network connection. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - macOS, Windows, or Linux development machine - Node.js 22 and Rust toolchain - Local filesystem access and OS permissions ## Explicit non-goals for v1 - 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 ## Success criteria - Sleeping through a session end is confirmed to record the true end time - A killed process is confirmed to revert within the watchdog interval - The original file is confirmed restored byte for byte - No site or application names are confirmed to be stored - No outbound connection is confirmed during a normal session ===== BRIEF.md ===== # Build brief · Serene The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a focus session timer that blocks what you asked it to and always gives the machine back to replace Serene. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero One rule, and it outranks every feature in this build. Anything this app changes about the system is reverted, whatever happens to the app. A focus tool that crashes with a modified hosts file leaves someone unable to reach their bank at nine at night, and they will not remember which application did it. So: write down what you changed before you change it, keep a copy of the original file with a timestamp, revert on quit, on crash and on next launch, and print the manual undo command in the README and in the interface. The honest corollary: this is a speed bump, not a lock. Apple's parental control frameworks are documented for iOS, iPadOS, Mac Catalyst and visionOS rather than for macOS, and their entitlement needs Apple's permission before an app can ship with it, so a desktop application has no supported way to stop another application from running. Say that in the interface rather than implying a discipline the operating system will not enforce for you. ### Stack (fixed, do not substitute) - Shell: Tauri 2 with React and TypeScript, no network calls at all · what you work on is nobody else's business - Time: Sessions stored as instants and recomputed from the clock on every wake · a countdown held in a timer variable is wrong after the first sleep - Blocking: A name resolution change applied with explicit elevation, recorded before it is made · the only mechanism a desktop app can use honestly - Safety: A watchdog that reverts on exit, on crash and at next launch · this is the part that stops the tool being dangerous - Storage: SQLite for sessions and goals, everything local · no history leaves the machine, ever ### Data model (create this before Phase 1) - 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. ### Phase 1 · A timer that is right after the laptop has been shut Build: Store planned start and end as instants. Derive remaining time from the current clock on every render and on every wake notification. Handle three cases explicitly: the machine slept through the end of a session, the app was closed during a session, and the system clock moved backwards. Trap: 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. Done when: A session whose end passed while the machine slept is completed on wake with the true end time, tested by moving the clock. Closing and reopening the app during a session restores the correct remaining time. A backwards clock jump does not extend a session, and the anomaly is recorded. No countdown value is ever persisted, verified by inspection of the schema. Do not build yet: No blocking, planning or history yet. ### Phase 2 · Changing the machine, and always changing it back Build: Record the intended change and copy the original file before touching anything. Apply the name resolution change with an explicit elevation prompt that says exactly what will change. Revert on session end, on application exit and at next launch if an unreverted row exists. Run a watchdog that reverts if the main process disappears. Trap: 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. Done when: Killing the application mid-session reverts the change within the watchdog interval, tested by killing the process. An unreverted row found at launch is reverted before the interface renders, tested by writing one manually. The original file is restored byte for byte, verified by hash. The elevation prompt names the file and the change, and declining it leaves the session running without blocking rather than failing. Do not build yet: No planning, statistics or packaging yet. ### Phase 3 · One goal per session, reachable from the keyboard Build: A day plan of intentions and a session that takes one goal in plain text. Global shortcuts for start, extend and end. Every action reachable without the mouse, with visible focus and an accessible name for each control. No numbers for the day beyond what was actually planned and completed. Trap: 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. Done when: A session starts from a global shortcut and one text field, with no other required input. Every control has a keyboard path and a visible focus state, checked against a keyboard-only walkthrough in the test notes. Extending and ending a session are single actions with their own shortcuts. The day plan is optional and its absence never blocks a session. Do not build yet: No statistics, export or packaging yet. ### Phase 4 · What happens when you try to break out Build: When a blocked name is requested, show a local page naming the session goal and offering to end the session deliberately. Count interruptions by kind. Record nothing about which site, which application or which document, because none of that is needed for the count and all of it is a liability. Trap: 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. Done when: The interruptions table contains counts and kinds and no site, application or document names, asserted by a schema test. Ending a session early is a deliberate action that records the outcome rather than silently stopping. The block page is served locally with no network request. Turning off blocking mid-session reverts immediately and the session continues. Do not build yet: No export or packaging yet. ### Phase 5 · History that stays here, and an uninstall that leaves nothing behind Build: Export sessions and plans as CSV and JSON. Add an uninstall command that reverts any change, removes the block entries and reports every file it touched. Verify in the suite that the application makes no outbound network connection at all. Package for the platforms you use and document exactly which permission each prompt is asking for and why. Trap: 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. Done when: A test asserts the application opens no outbound connection in a normal session, run with the network blocked. The uninstall command reverts every applied change, removes app files and prints the list, tested from a session left running. Export produces CSV and JSON covering sessions, goals and interruption counts. Each permission prompt is documented with what is being asked and what happens if it is declined. Do not build yet: Nothing after this. ### Out of scope (and why) - Blocking applications rather than names, which a desktop app cannot do through a supported interface on macOS, and which through unsupported means is both fragile and a security decision you should not make quietly. - Anything that cannot be turned off, because a focus tool that a determined person cannot escape is a tool that traps somebody with a genuine emergency. - Screenshots, keystroke counts and application usage logs, which turn a timer into surveillance of yourself or, worse, of someone else. - Sync between devices, which would put your working hours and goals on somebody else's server for very little in return. ### README must contain - That every system change is recorded before it is made and reverted on quit, on crash and at next launch - The manual command to revert a block if all three automatic paths somehow fail - That this is a speed bump rather than a lock, and that no supported desktop interface exists to stop applications running - That session time is computed from stored instants, so sleep and restarts do not distort the record - That nothing leaves the machine, that a test enforces it, and how to run that test ===== ARCHITECTURE.md ===== # Architecture · Serene ## Stack | Part | Choice | Why | | --- | --- | --- | | Shell | Tauri 2 with React and TypeScript, no network calls at all | what you work on is nobody else's business | | Time | Sessions stored as instants and recomputed from the clock on every wake | a countdown held in a timer variable is wrong after the first sleep | | Blocking | A name resolution change applied with explicit elevation, recorded before it is made | the only mechanism a desktop app can use honestly | | Safety | A watchdog that reverts on exit, on crash and at next launch | this is the part that stops the tool being dangerous | | Storage | SQLite for sessions and goals, everything local | no history leaves the machine, ever | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Time | Instants, wake recomputation and clock anomalies. | Any scheduler, provided nothing persists a countdown. | | Blocking | Recorded changes and elevation. | A resolver-level mechanism instead of a file, provided the record comes first. | | Revert | Watchdog, launch check and manual command. | Not swappable for a single clean-quit handler, which is the dangerous version. | | Sessions | Goals, outcomes and interruption counts. | Any store, provided nothing identifying is recorded. | | Packaging | Uninstall, permissions and the no-network assertion. | Any packaging, provided uninstall still reports what it touched. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DATA_DIR` · required · Sessions, plans and interruption counts. Never leaves the machine. - `HOSTS_FILE_PATH` · required · The file that is modified. Backed up before every change. - `BACKUP_DIR` · required · Timestamped copies of the original file, kept until verified reverted. - `WATCHDOG_SECONDS` · required · The longest a crash can leave a block applied. - `MAX_SESSION_MINUTES` · required · A ceiling on any single mistake, enforced regardless of what was entered. - `UPDATE_CHECKS` · required · Off by default, asserted by the no-network test. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · Serene product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Tauri 2 with React and TypeScript, no network calls at all, Sessions stored as instants and recomputed from the clock on every wake, A name resolution change applied with explicit elevation, recorded before it is made, A watchdog that reverts on exit, on crash and at next launch, SQLite for sessions and goals, everything local. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - 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. - 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. - 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. - 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. - 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. ===== MILESTONES.md ===== # Delivery milestones · Serene Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · A timer that is right after the laptop has been shut Session state is derived from instants, not from a running countdown. ### Steps 1. Store instants and derive the rest The clock is the source of truth; the interface only renders it. Files: `src/session/time.ts`, `src/session/time.test.ts` 2. Recompute on every wake The wake event is the one moment your assumptions are guaranteed stale. Files: `src-tauri/src/power.rs` 3. Handle a backwards clock explicitly It happens more often than you think, and it should not grant extra time. Files: `src/session/anomaly.ts` ### Done when - [ ] Sleeping through the end completes the session correctly - [ ] Reopening mid-session restores the right remaining time - [ ] A backwards clock does not extend a session - [ ] No countdown value is persisted ### Watch 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. ## M2 · Changing the machine, and always changing it back Every system change is recorded before it happens and reverted by three independent paths. ### Steps 1. Record the change before making it A crash between the row and the write is recoverable; the reverse is not. Files: `src-tauri/src/blocking.rs` 2. Revert from a watchdog, at launch, and by hand Three paths, because the first two can both be killed. Files: `src-tauri/src/watchdog.rs`, `tests/revert.test.ts` 3. Degrade to no blocking when elevation is declined The timer still works; the speed bump does not. Files: `src/session/blocking.ts` ### Done when - [ ] A killed process reverts within the watchdog interval - [ ] An unreverted row at launch is reverted first - [ ] The original file is restored byte for byte - [ ] Declining elevation degrades rather than fails ### Watch 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. ## M3 · One goal per session, reachable from the keyboard Starting a session takes a keystroke and a sentence. ### Steps 1. One field to start, defaults from last time Setup friction is the reason focus tools get abandoned. Files: `src/ui/StartSession.tsx` 2. Global shortcuts for the three actions Start, extend, end. Nothing else needs one. Files: `src-tauri/src/shortcuts.rs` 3. Walk it once with the mouse unplugged The fastest accessibility test there is. Files: `docs/keyboard-walkthrough.md` ### Done when - [ ] A session starts from a shortcut and one field - [ ] Every control has a keyboard path and focus state - [ ] Extend and end have their own shortcuts - [ ] The day plan is optional ### Watch 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. ## M4 · What happens when you try to break out Interruptions are counted honestly without recording what you were doing. ### Steps 1. Show the goal, not a scolding The person already knows they got distracted. Files: `src/ui/BlockPage.tsx` 2. Count kinds, store nothing identifying Enforced by the schema, not by remembering. Files: `src-tauri/migrations/002_interruptions.sql` 3. Make ending early explicit A recorded decision beats a session that fizzles out. Files: `src/session/end.ts` ### Done when - [ ] No site or application names are stored - [ ] Ending early records an outcome - [ ] The block page makes no network request - [ ] Disabling blocking mid-session reverts at once ### Watch 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. ## M5 · History that stays here, and an uninstall that leaves nothing behind (production only) The data is yours, exportable, and removing the app restores the machine completely. ### Steps 1. Assert no outbound connection in the suite The claim is only worth making if a test enforces it. Files: `tests/no-network.test.ts` 2. Ship an uninstall that reports what it touched The last chance to prove the machine was handed back. Files: `src-tauri/src/uninstall.rs` 3. Document every permission prompt Including what declining it costs, which is usually less than people fear. Files: `README.md` ### Done when - [ ] No outbound connection during a normal session - [ ] Uninstall reverts changes and lists files touched - [ ] Export covers sessions, goals and interruption counts - [ ] Every permission prompt is documented ### Watch 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. ===== OPERATIONS.md ===== # Operations · Serene ## Backup The session database is small and local; copy it with the rest of your home directory. The backup that matters operationally is the copy of the original hosts file, which is taken before every change and kept until the revert is verified. ## Restore Restore the database and confirm the app starts with no unreverted applied_changes row. If one exists, the launch path should revert it before the interface renders, which is the behaviour to check after any restore. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring On a desktop tool this is the status line and the suite: whether any applied change is currently unreverted, when the watchdog last checked in, and whether the no-network test still passes. ## Incident checklist The incident is a block left applied after a crash. The recovery order is the watchdog, the launch check and then the manual command from the README, which is why the command exists in writing rather than only in code. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Sleeping through a session end is confirmed to record the true end time - [ ] A killed process is confirmed to revert within the watchdog interval - [ ] The original file is confirmed restored byte for byte - [ ] No site or application names are confirmed to be stored - [ ] No outbound connection is confirmed during a normal session ## Launch constraint Do not market omitted Serene capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Sessions, plans and interruption counts. Never leaves the machine. DATA_DIR=~/.local/share/focus/focus.db # Required. The file that is modified. Backed up before every change. HOSTS_FILE_PATH=/etc/hosts # Required. Timestamped copies of the original file, kept until verified reverted. BACKUP_DIR=~/.local/share/focus/backups # Required. The longest a crash can leave a block applied. WATCHDOG_SECONDS=15 # Required. A ceiling on any single mistake, enforced regardless of what was entered. MAX_SESSION_MINUTES=120 # Required. Off by default, asserted by the no-network test. UPDATE_CHECKS=off
# Serene · indie build 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. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Shell | Tauri 2 with React and TypeScript, no network calls at all | what you work on is nobody else's business | | Time | Sessions stored as instants and recomputed from the clock on every wake | a countdown held in a timer variable is wrong after the first sleep | | Blocking | A name resolution change applied with explicit elevation, recorded before it is made | the only mechanism a desktop app can use honestly | | Safety | A watchdog that reverts on exit, on crash and at next launch | this is the part that stops the tool being dangerous | | Storage | SQLite for sessions and goals, everything local | no history leaves the machine, ever | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Builds the interface half of the application. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **A Rust toolchain and the Tauri prerequisites** · free - 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. - Verify: cargo --version prints a version and a bare Tauri app runs - [ ] **A machine or virtual machine you can crash safely** · free - 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 - [ ] **Your watchdog interval and maximum session length** · free - 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 ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - 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 If one of those is essential to you, that is the reason to keep paying for Serene, and the README should say so rather than pretend.
# Build brief · Serene The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a focus session timer that blocks what you asked it to and always gives the machine back to replace Serene. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero One rule, and it outranks every feature in this build. Anything this app changes about the system is reverted, whatever happens to the app. A focus tool that crashes with a modified hosts file leaves someone unable to reach their bank at nine at night, and they will not remember which application did it. So: write down what you changed before you change it, keep a copy of the original file with a timestamp, revert on quit, on crash and on next launch, and print the manual undo command in the README and in the interface. The honest corollary: this is a speed bump, not a lock. Apple's parental control frameworks are documented for iOS, iPadOS, Mac Catalyst and visionOS rather than for macOS, and their entitlement needs Apple's permission before an app can ship with it, so a desktop application has no supported way to stop another application from running. Say that in the interface rather than implying a discipline the operating system will not enforce for you. ### Stack (fixed, do not substitute) - Shell: Tauri 2 with React and TypeScript, no network calls at all · what you work on is nobody else's business - Time: Sessions stored as instants and recomputed from the clock on every wake · a countdown held in a timer variable is wrong after the first sleep - Blocking: A name resolution change applied with explicit elevation, recorded before it is made · the only mechanism a desktop app can use honestly - Safety: A watchdog that reverts on exit, on crash and at next launch · this is the part that stops the tool being dangerous - Storage: SQLite for sessions and goals, everything local · no history leaves the machine, ever ### Data model (create this before Phase 1) - 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. ### Phase 1 · A timer that is right after the laptop has been shut Build: Store planned start and end as instants. Derive remaining time from the current clock on every render and on every wake notification. Handle three cases explicitly: the machine slept through the end of a session, the app was closed during a session, and the system clock moved backwards. Trap: 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. Done when: A session whose end passed while the machine slept is completed on wake with the true end time, tested by moving the clock. Closing and reopening the app during a session restores the correct remaining time. A backwards clock jump does not extend a session, and the anomaly is recorded. No countdown value is ever persisted, verified by inspection of the schema. Do not build yet: No blocking, planning or history yet. ### Phase 2 · Changing the machine, and always changing it back Build: Record the intended change and copy the original file before touching anything. Apply the name resolution change with an explicit elevation prompt that says exactly what will change. Revert on session end, on application exit and at next launch if an unreverted row exists. Run a watchdog that reverts if the main process disappears. Trap: 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. Done when: Killing the application mid-session reverts the change within the watchdog interval, tested by killing the process. An unreverted row found at launch is reverted before the interface renders, tested by writing one manually. The original file is restored byte for byte, verified by hash. The elevation prompt names the file and the change, and declining it leaves the session running without blocking rather than failing. Do not build yet: No planning, statistics or packaging yet. ### Phase 3 · One goal per session, reachable from the keyboard Build: A day plan of intentions and a session that takes one goal in plain text. Global shortcuts for start, extend and end. Every action reachable without the mouse, with visible focus and an accessible name for each control. No numbers for the day beyond what was actually planned and completed. Trap: 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. Done when: A session starts from a global shortcut and one text field, with no other required input. Every control has a keyboard path and a visible focus state, checked against a keyboard-only walkthrough in the test notes. Extending and ending a session are single actions with their own shortcuts. The day plan is optional and its absence never blocks a session. Do not build yet: No statistics, export or packaging yet. ### Phase 4 · What happens when you try to break out Build: When a blocked name is requested, show a local page naming the session goal and offering to end the session deliberately. Count interruptions by kind. Record nothing about which site, which application or which document, because none of that is needed for the count and all of it is a liability. Trap: 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. Done when: The interruptions table contains counts and kinds and no site, application or document names, asserted by a schema test. Ending a session early is a deliberate action that records the outcome rather than silently stopping. The block page is served locally with no network request. Turning off blocking mid-session reverts immediately and the session continues. Do not build yet: No export or packaging yet. ### Phase 5 · History that stays here, and an uninstall that leaves nothing behind Build: Export sessions and plans as CSV and JSON. Add an uninstall command that reverts any change, removes the block entries and reports every file it touched. Verify in the suite that the application makes no outbound network connection at all. Package for the platforms you use and document exactly which permission each prompt is asking for and why. Trap: 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. Done when: A test asserts the application opens no outbound connection in a normal session, run with the network blocked. The uninstall command reverts every applied change, removes app files and prints the list, tested from a session left running. Export produces CSV and JSON covering sessions, goals and interruption counts. Each permission prompt is documented with what is being asked and what happens if it is declined. Do not build yet: Nothing after this. ### Out of scope (and why) - Blocking applications rather than names, which a desktop app cannot do through a supported interface on macOS, and which through unsupported means is both fragile and a security decision you should not make quietly. - Anything that cannot be turned off, because a focus tool that a determined person cannot escape is a tool that traps somebody with a genuine emergency. - Screenshots, keystroke counts and application usage logs, which turn a timer into surveillance of yourself or, worse, of someone else. - Sync between devices, which would put your working hours and goals on somebody else's server for very little in return. ### README must contain - That every system change is recorded before it is made and reverted on quit, on crash and at next launch - The manual command to revert a block if all three automatic paths somehow fail - That this is a speed bump rather than a lock, and that no supported desktop interface exists to stop applications running - That session time is computed from stored instants, so sleep and restarts do not distort the record - That nothing leaves the machine, that a test enforces it, and how to run that test
# Agent instructions · Serene indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Tauri 2 with React and TypeScript, no network calls at all, Sessions stored as instants and recomputed from the clock on every wake, A name resolution change applied with explicit elevation, recorded before it is made, A watchdog that reverts on exit, on crash and at next launch, SQLite for sessions and goals, everything local. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - 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. - 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. - 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. - 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.
# Build plan · Serene 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. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A timer that is right after the laptop has been shut Session state is derived from instants, not from a running countdown. ### Steps 1. Store instants and derive the rest The clock is the source of truth; the interface only renders it. Files: `src/session/time.ts`, `src/session/time.test.ts` 2. Recompute on every wake The wake event is the one moment your assumptions are guaranteed stale. Files: `src-tauri/src/power.rs` 3. Handle a backwards clock explicitly It happens more often than you think, and it should not grant extra time. Files: `src/session/anomaly.ts` ### Done when - [ ] Sleeping through the end completes the session correctly - [ ] Reopening mid-session restores the right remaining time - [ ] A backwards clock does not extend a session - [ ] No countdown value is persisted ### Watch 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. ## Phase 2 · Changing the machine, and always changing it back Every system change is recorded before it happens and reverted by three independent paths. ### Steps 1. Record the change before making it A crash between the row and the write is recoverable; the reverse is not. Files: `src-tauri/src/blocking.rs` 2. Revert from a watchdog, at launch, and by hand Three paths, because the first two can both be killed. Files: `src-tauri/src/watchdog.rs`, `tests/revert.test.ts` 3. Degrade to no blocking when elevation is declined The timer still works; the speed bump does not. Files: `src/session/blocking.ts` ### Done when - [ ] A killed process reverts within the watchdog interval - [ ] An unreverted row at launch is reverted first - [ ] The original file is restored byte for byte - [ ] Declining elevation degrades rather than fails ### Watch 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. ## Phase 3 · One goal per session, reachable from the keyboard Starting a session takes a keystroke and a sentence. ### Steps 1. One field to start, defaults from last time Setup friction is the reason focus tools get abandoned. Files: `src/ui/StartSession.tsx` 2. Global shortcuts for the three actions Start, extend, end. Nothing else needs one. Files: `src-tauri/src/shortcuts.rs` 3. Walk it once with the mouse unplugged The fastest accessibility test there is. Files: `docs/keyboard-walkthrough.md` ### Done when - [ ] A session starts from a shortcut and one field - [ ] Every control has a keyboard path and focus state - [ ] Extend and end have their own shortcuts - [ ] The day plan is optional ### Watch 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. ## Phase 4 · What happens when you try to break out Interruptions are counted honestly without recording what you were doing. ### Steps 1. Show the goal, not a scolding The person already knows they got distracted. Files: `src/ui/BlockPage.tsx` 2. Count kinds, store nothing identifying Enforced by the schema, not by remembering. Files: `src-tauri/migrations/002_interruptions.sql` 3. Make ending early explicit A recorded decision beats a session that fizzles out. Files: `src/session/end.ts` ### Done when - [ ] No site or application names are stored - [ ] Ending early records an outcome - [ ] The block page makes no network request - [ ] Disabling blocking mid-session reverts at once ### Watch 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. ## Not in this build - Application-level blocking. - Any block that cannot be turned off. - Screenshots, keystroke counts and usage logs. - Cross-device sync. ## After v1, if you want it - 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
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Sessions, plans and interruption counts. Never leaves the machine. DATA_DIR=~/.local/share/focus/focus.db # Required. The file that is modified. Backed up before every change. HOSTS_FILE_PATH=/etc/hosts # Required. Timestamped copies of the original file, kept until verified reverted. BACKUP_DIR=~/.local/share/focus/backups # Required. The longest a crash can leave a block applied. WATCHDOG_SECONDS=15 # Required. A ceiling on any single mistake, enforced regardless of what was entered. MAX_SESSION_MINUTES=120 # Required. Off by default, asserted by the no-network test. UPDATE_CHECKS=off
# Serene · product brief ## Problem Serene's solo core is compact: build a local desktop utility that captures the essential focus planning workflow with keyboard-first controls and local storage. A competent builder can reach a useful personal version in one sitting, while the paid product mainly wins on polish, OS integration. ## Product outcome A focus timer that keeps honest time through sleep and restarts, applies a speed bump you consented to, reverts it by three independent paths, stores no record of what distracted you, and never opens a network connection. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - macOS, Windows, or Linux development machine - Node.js 22 and Rust toolchain - Local filesystem access and OS permissions ## Explicit non-goals for v1 - 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 ## Success criteria - Sleeping through a session end is confirmed to record the true end time - A killed process is confirmed to revert within the watchdog interval - The original file is confirmed restored byte for byte - No site or application names are confirmed to be stored - No outbound connection is confirmed during a normal session
# Build brief · Serene The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a focus session timer that blocks what you asked it to and always gives the machine back to replace Serene. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero One rule, and it outranks every feature in this build. Anything this app changes about the system is reverted, whatever happens to the app. A focus tool that crashes with a modified hosts file leaves someone unable to reach their bank at nine at night, and they will not remember which application did it. So: write down what you changed before you change it, keep a copy of the original file with a timestamp, revert on quit, on crash and on next launch, and print the manual undo command in the README and in the interface. The honest corollary: this is a speed bump, not a lock. Apple's parental control frameworks are documented for iOS, iPadOS, Mac Catalyst and visionOS rather than for macOS, and their entitlement needs Apple's permission before an app can ship with it, so a desktop application has no supported way to stop another application from running. Say that in the interface rather than implying a discipline the operating system will not enforce for you. ### Stack (fixed, do not substitute) - Shell: Tauri 2 with React and TypeScript, no network calls at all · what you work on is nobody else's business - Time: Sessions stored as instants and recomputed from the clock on every wake · a countdown held in a timer variable is wrong after the first sleep - Blocking: A name resolution change applied with explicit elevation, recorded before it is made · the only mechanism a desktop app can use honestly - Safety: A watchdog that reverts on exit, on crash and at next launch · this is the part that stops the tool being dangerous - Storage: SQLite for sessions and goals, everything local · no history leaves the machine, ever ### Data model (create this before Phase 1) - 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. ### Phase 1 · A timer that is right after the laptop has been shut Build: Store planned start and end as instants. Derive remaining time from the current clock on every render and on every wake notification. Handle three cases explicitly: the machine slept through the end of a session, the app was closed during a session, and the system clock moved backwards. Trap: 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. Done when: A session whose end passed while the machine slept is completed on wake with the true end time, tested by moving the clock. Closing and reopening the app during a session restores the correct remaining time. A backwards clock jump does not extend a session, and the anomaly is recorded. No countdown value is ever persisted, verified by inspection of the schema. Do not build yet: No blocking, planning or history yet. ### Phase 2 · Changing the machine, and always changing it back Build: Record the intended change and copy the original file before touching anything. Apply the name resolution change with an explicit elevation prompt that says exactly what will change. Revert on session end, on application exit and at next launch if an unreverted row exists. Run a watchdog that reverts if the main process disappears. Trap: 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. Done when: Killing the application mid-session reverts the change within the watchdog interval, tested by killing the process. An unreverted row found at launch is reverted before the interface renders, tested by writing one manually. The original file is restored byte for byte, verified by hash. The elevation prompt names the file and the change, and declining it leaves the session running without blocking rather than failing. Do not build yet: No planning, statistics or packaging yet. ### Phase 3 · One goal per session, reachable from the keyboard Build: A day plan of intentions and a session that takes one goal in plain text. Global shortcuts for start, extend and end. Every action reachable without the mouse, with visible focus and an accessible name for each control. No numbers for the day beyond what was actually planned and completed. Trap: 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. Done when: A session starts from a global shortcut and one text field, with no other required input. Every control has a keyboard path and a visible focus state, checked against a keyboard-only walkthrough in the test notes. Extending and ending a session are single actions with their own shortcuts. The day plan is optional and its absence never blocks a session. Do not build yet: No statistics, export or packaging yet. ### Phase 4 · What happens when you try to break out Build: When a blocked name is requested, show a local page naming the session goal and offering to end the session deliberately. Count interruptions by kind. Record nothing about which site, which application or which document, because none of that is needed for the count and all of it is a liability. Trap: 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. Done when: The interruptions table contains counts and kinds and no site, application or document names, asserted by a schema test. Ending a session early is a deliberate action that records the outcome rather than silently stopping. The block page is served locally with no network request. Turning off blocking mid-session reverts immediately and the session continues. Do not build yet: No export or packaging yet. ### Phase 5 · History that stays here, and an uninstall that leaves nothing behind Build: Export sessions and plans as CSV and JSON. Add an uninstall command that reverts any change, removes the block entries and reports every file it touched. Verify in the suite that the application makes no outbound network connection at all. Package for the platforms you use and document exactly which permission each prompt is asking for and why. Trap: 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. Done when: A test asserts the application opens no outbound connection in a normal session, run with the network blocked. The uninstall command reverts every applied change, removes app files and prints the list, tested from a session left running. Export produces CSV and JSON covering sessions, goals and interruption counts. Each permission prompt is documented with what is being asked and what happens if it is declined. Do not build yet: Nothing after this. ### Out of scope (and why) - Blocking applications rather than names, which a desktop app cannot do through a supported interface on macOS, and which through unsupported means is both fragile and a security decision you should not make quietly. - Anything that cannot be turned off, because a focus tool that a determined person cannot escape is a tool that traps somebody with a genuine emergency. - Screenshots, keystroke counts and application usage logs, which turn a timer into surveillance of yourself or, worse, of someone else. - Sync between devices, which would put your working hours and goals on somebody else's server for very little in return. ### README must contain - That every system change is recorded before it is made and reverted on quit, on crash and at next launch - The manual command to revert a block if all three automatic paths somehow fail - That this is a speed bump rather than a lock, and that no supported desktop interface exists to stop applications running - That session time is computed from stored instants, so sleep and restarts do not distort the record - That nothing leaves the machine, that a test enforces it, and how to run that test
# Architecture · Serene ## Stack | Part | Choice | Why | | --- | --- | --- | | Shell | Tauri 2 with React and TypeScript, no network calls at all | what you work on is nobody else's business | | Time | Sessions stored as instants and recomputed from the clock on every wake | a countdown held in a timer variable is wrong after the first sleep | | Blocking | A name resolution change applied with explicit elevation, recorded before it is made | the only mechanism a desktop app can use honestly | | Safety | A watchdog that reverts on exit, on crash and at next launch | this is the part that stops the tool being dangerous | | Storage | SQLite for sessions and goals, everything local | no history leaves the machine, ever | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Time | Instants, wake recomputation and clock anomalies. | Any scheduler, provided nothing persists a countdown. | | Blocking | Recorded changes and elevation. | A resolver-level mechanism instead of a file, provided the record comes first. | | Revert | Watchdog, launch check and manual command. | Not swappable for a single clean-quit handler, which is the dangerous version. | | Sessions | Goals, outcomes and interruption counts. | Any store, provided nothing identifying is recorded. | | Packaging | Uninstall, permissions and the no-network assertion. | Any packaging, provided uninstall still reports what it touched. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DATA_DIR` · required · Sessions, plans and interruption counts. Never leaves the machine. - `HOSTS_FILE_PATH` · required · The file that is modified. Backed up before every change. - `BACKUP_DIR` · required · Timestamped copies of the original file, kept until verified reverted. - `WATCHDOG_SECONDS` · required · The longest a crash can leave a block applied. - `MAX_SESSION_MINUTES` · required · A ceiling on any single mistake, enforced regardless of what was entered. - `UPDATE_CHECKS` · required · Off by default, asserted by the no-network test. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · Serene product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Tauri 2 with React and TypeScript, no network calls at all, Sessions stored as instants and recomputed from the clock on every wake, A name resolution change applied with explicit elevation, recorded before it is made, A watchdog that reverts on exit, on crash and at next launch, SQLite for sessions and goals, everything local. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - 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. - 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. - 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. - 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. - 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.
# Delivery milestones · Serene Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · A timer that is right after the laptop has been shut Session state is derived from instants, not from a running countdown. ### Steps 1. Store instants and derive the rest The clock is the source of truth; the interface only renders it. Files: `src/session/time.ts`, `src/session/time.test.ts` 2. Recompute on every wake The wake event is the one moment your assumptions are guaranteed stale. Files: `src-tauri/src/power.rs` 3. Handle a backwards clock explicitly It happens more often than you think, and it should not grant extra time. Files: `src/session/anomaly.ts` ### Done when - [ ] Sleeping through the end completes the session correctly - [ ] Reopening mid-session restores the right remaining time - [ ] A backwards clock does not extend a session - [ ] No countdown value is persisted ### Watch 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. ## M2 · Changing the machine, and always changing it back Every system change is recorded before it happens and reverted by three independent paths. ### Steps 1. Record the change before making it A crash between the row and the write is recoverable; the reverse is not. Files: `src-tauri/src/blocking.rs` 2. Revert from a watchdog, at launch, and by hand Three paths, because the first two can both be killed. Files: `src-tauri/src/watchdog.rs`, `tests/revert.test.ts` 3. Degrade to no blocking when elevation is declined The timer still works; the speed bump does not. Files: `src/session/blocking.ts` ### Done when - [ ] A killed process reverts within the watchdog interval - [ ] An unreverted row at launch is reverted first - [ ] The original file is restored byte for byte - [ ] Declining elevation degrades rather than fails ### Watch 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. ## M3 · One goal per session, reachable from the keyboard Starting a session takes a keystroke and a sentence. ### Steps 1. One field to start, defaults from last time Setup friction is the reason focus tools get abandoned. Files: `src/ui/StartSession.tsx` 2. Global shortcuts for the three actions Start, extend, end. Nothing else needs one. Files: `src-tauri/src/shortcuts.rs` 3. Walk it once with the mouse unplugged The fastest accessibility test there is. Files: `docs/keyboard-walkthrough.md` ### Done when - [ ] A session starts from a shortcut and one field - [ ] Every control has a keyboard path and focus state - [ ] Extend and end have their own shortcuts - [ ] The day plan is optional ### Watch 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. ## M4 · What happens when you try to break out Interruptions are counted honestly without recording what you were doing. ### Steps 1. Show the goal, not a scolding The person already knows they got distracted. Files: `src/ui/BlockPage.tsx` 2. Count kinds, store nothing identifying Enforced by the schema, not by remembering. Files: `src-tauri/migrations/002_interruptions.sql` 3. Make ending early explicit A recorded decision beats a session that fizzles out. Files: `src/session/end.ts` ### Done when - [ ] No site or application names are stored - [ ] Ending early records an outcome - [ ] The block page makes no network request - [ ] Disabling blocking mid-session reverts at once ### Watch 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. ## M5 · History that stays here, and an uninstall that leaves nothing behind (production only) The data is yours, exportable, and removing the app restores the machine completely. ### Steps 1. Assert no outbound connection in the suite The claim is only worth making if a test enforces it. Files: `tests/no-network.test.ts` 2. Ship an uninstall that reports what it touched The last chance to prove the machine was handed back. Files: `src-tauri/src/uninstall.rs` 3. Document every permission prompt Including what declining it costs, which is usually less than people fear. Files: `README.md` ### Done when - [ ] No outbound connection during a normal session - [ ] Uninstall reverts changes and lists files touched - [ ] Export covers sessions, goals and interruption counts - [ ] Every permission prompt is documented ### Watch 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.
# Operations · Serene ## Backup The session database is small and local; copy it with the rest of your home directory. The backup that matters operationally is the copy of the original hosts file, which is taken before every change and kept until the revert is verified. ## Restore Restore the database and confirm the app starts with no unreverted applied_changes row. If one exists, the launch path should revert it before the interface renders, which is the behaviour to check after any restore. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring On a desktop tool this is the status line and the suite: whether any applied change is currently unreverted, when the watchdog last checked in, and whether the no-network test still passes. ## Incident checklist The incident is a block left applied after a crash. The recovery order is the watchdog, the launch check and then the manual command from the README, which is why the command exists in writing rather than only in code. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Sleeping through a session end is confirmed to record the true end time - [ ] A killed process is confirmed to revert within the watchdog interval - [ ] The original file is confirmed restored byte for byte - [ ] No site or application names are confirmed to be stored - [ ] No outbound connection is confirmed during a normal session ## Launch constraint Do not market omitted Serene capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Sessions, plans and interruption counts. Never leaves the machine. DATA_DIR=~/.local/share/focus/focus.db # Required. The file that is modified. Backed up before every change. HOSTS_FILE_PATH=/etc/hosts # Required. Timestamped copies of the original file, kept until verified reverted. BACKUP_DIR=~/.local/share/focus/backups # Required. The longest a crash can leave a block applied. WATCHDOG_SECONDS=15 # Required. A ceiling on any single mistake, enforced regardless of what was entered. MAX_SESSION_MINUTES=120 # Required. Off by default, asserted by the no-network test. UPDATE_CHECKS=off
$ choose a build depth, inspect the files, then open the complete pack in your agent
Serene: People keep paying because a utility used dozens of times a day must be instant, predictable, and compatible with every OS update.
xpolished shortcuts and accessibility
xvendor-maintained updates
xcross-device sync
xdeep operating-system edge-case coverage
Vibecode Serene
Yes. A competent AI coding agent (Claude Code, Codex, Cursor) can build a usable personal Serene replacement in one session with the prompt on this page. It runs on your own machine or server with no subscription.
How much does Serene cost?
Nothing anymore: Serene is no longer sold; sereneapp.com now redirects to its maker Venture Harbour. Before that it ran about $4/month.
What do I lose by replacing Serene?
Honestly: polished shortcuts and accessibility; vendor-maintained updates; cross-device sync; deep operating-system edge-case coverage. Since the product is gone, these gaps are now everyone's problem.
Is there an open-source alternative to Serene?
Yes: ActivityWatch (Local-first automatic time tracking with an extensible event model.). Using prior art is also vibecoding; the prompt is for when you want it exactly your way.