Go to file
Leopere e57e27043c
Release: 7657b99b52080f8e00a7f886b55a1b17734af935f8e4136f695cddaba13e7347
2026-02-10 14:20:23 -05:00
Resources Release: 674ca5f9d765089eacb0b61bfc546ba1146e3e8a0f5380ba4f66ad6ac2bd5ed8 2026-02-08 16:53:40 -05:00
Sources/Pommedoro bump 2026-02-10 14:03:19 -05:00
releases Release: 7657b99b52080f8e00a7f886b55a1b17734af935f8e4136f695cddaba13e7347 2026-02-10 14:20:23 -05:00
.gitignore Initial release of Pommedoro 2026-02-06 16:27:21 -05:00
CHANGELOG.md Add work log, pause/resume UX, countdown schedule, timer persistence, suggestion improvements, and ad-hoc code signing 2026-02-07 18:06:10 -05:00
LICENSE Initial release of Pommedoro 2026-02-06 16:27:21 -05:00
Makefile Skip auto-update for dev builds 2026-02-08 15:51:09 -05:00
Package.resolved Initial release of Pommedoro 2026-02-06 16:27:21 -05:00
Package.swift Initial release of Pommedoro 2026-02-06 16:27:21 -05:00
README.md Release: 674ca5f9d765089eacb0b61bfc546ba1146e3e8a0f5380ba4f66ad6ac2bd5ed8 2026-02-08 16:53:40 -05:00
build-test.sh Update README and build-test script 2026-02-08 16:25:34 -05:00
redeploy-local.sh Release: 674ca5f9d765089eacb0b61bfc546ba1146e3e8a0f5380ba4f66ad6ac2bd5ed8 2026-02-08 16:53:40 -05:00

README.md

Pommedoro

A Pomodoro timer that respects how your brain actually works.

Download the latest DMG


The Problem with Traditional Pomodoro Timers

Every Pomodoro app does the same thing: a timer counts down, an alarm fires, and a dialog box demands your attention. You dismiss it. You always dismiss it. The break never happens.

This isn't a willpower failure. It's a design failure.

When you're deep in flow — writing, coding, designing — your mind is in a groove. A sudden alarm is a context switch, and human beings are wired to reject context switches. The instinctive response to an interruption during focus is to kill the interruption, not to obey it. So you click "dismiss" and keep working, and the entire purpose of the break is defeated.

Traditional timers treat breaks as binary events: you're working, then you're not. But that's not how attention works. Attention is a continuum. You can't jump from full focus to full rest without a bridge. Transitions, not hard cuts, are the only way successful breaks will actually occur.

How Pommedoro Works

Pommedoro is built around the idea that your timer should ease you toward a break rather than ambush you with one.

Phase 1: Escalating Awareness

During the last five minutes of a work session, Pommedoro begins painting soft teal gradients along the edges of all your screens. These are gentle — barely noticeable at first. They fade in and out slowly, like breathing.

As time goes on, the blinks become more frequent and more intense:

  • 5:00 3:00 remaining: A brief flash every 60 seconds. Subtle. A whisper.
  • 3:00 1:00 remaining: Every 30 seconds. You start to notice.
  • 1:00 0:30 remaining: Every 10 seconds. The edges are becoming familiar.
  • 0:30 0:10 remaining: Every 5 seconds. Your peripheral vision has accepted what's coming.
  • 0:10 0:05 remaining: Every 2 seconds. A steady pulse.
  • Last 5 seconds: The edges hold solid. A countdown pill appears at the bottom of the screen.

The intensity of the gradient also escalates over this period — from a faint 15% opacity to a vivid 75%. Your subconscious has been processing this for five full minutes before the break screen ever appears.

Phase 2: The Break Screen

When the timer reaches zero, the screen transitions to a full teal overlay. This isn't an alert dialog you reflexively dismiss — it is your screen now. It carries a simple suggestion:

"Stretch your neck and shoulders" "Step outside for a minute of fresh air" "Unclench your jaw and relax your shoulders"

These are real, physical actions. Not platitudes. Not "take a break!" — that's meaningless. Pommedoro tells you what to do with your break, and if a suggestion doesn't resonate, you can dismiss it and it won't come back.

A 5-minute break countdown runs in the background. You can mark the suggestion as completed ("Success!"), skip it ("Next Time"), or permanently dismiss suggestions that don't suit you ("Don't Suggest").

Phase 3: Reflection

After completing a suggestion, Pommedoro asks one question:

"How did that impact your readiness for the day?"

Three options: Feeling Great, About the Same, Not Really. No journaling. No friction. Just a moment of honest self-assessment before you return to work.

Phase 4: Ready When You Are

The final screen says "Ready when you are" and offers Pause or Continue. There's no urgency. The break timer is still visible but the message is clear: you decide when to go back. The next 25-minute cycle starts when you press Continue.

Why This Works

The escalating gradient approach works because it operates on the same channel as your focus: your visual field. Rather than competing with your attention via an auditory alarm or a modal dialog, Pommedoro gradually joins your visual environment. By the time the break arrives, your brain has already been transitioning for five minutes. The break screen isn't an interruption — it's the natural conclusion of a process you've been subconsciously participating in.

This is the difference between someone tapping you on the shoulder while you're reading versus slowly turning up the lights in the room. One triggers a startle response and gets dismissed. The other lets your eyes adjust naturally.

The actionable suggestions matter too. "Take a break" is an instruction with no substance. "Do some pushups" or "Drink some water" gives your body and mind a concrete thing to switch to. The context switch succeeds because there's a real context to switch into.

Features

  • 25-minute work sessions with 5-minute escalating visual transitions
  • Edge gradient warnings that increase in frequency and intensity
  • Full-screen break overlay with actionable wellness suggestions
  • 5-minute break timer with self-assessment reflection
  • Menu bar timer with pause/resume support
  • Debug mode for testing (1-minute cycles)
  • Launch at Login via macOS LaunchAgent
  • Native macOS app — no Electron, no web views, just AppKit
  • Self-update — checks the repo over HTTPS (no login); offers an update only when the server has a newer release (SHA + timestamp)

Changelog

See CHANGELOG.md for release history and recent changes.

Install

1. Download

Go to colinknapp.com/stories/pommedoro.html or download the DMG directly:

Pommedoro.dmg

2. Install

In Finder, open your Downloads folder and double-click Pommedoro.dmg. When the disk image opens, drag Pommedoro into the Applications folder (or use the shortcut arrow if the window shows one). Then eject the disk image (right-click the "Pommedoro" volume in the Finder sidebar and choose Eject, or drag it to the Trash).

3. If macOS says "Pommedoro can't be opened" or "move to Trash"

Don't trash it. This happens because the app is distributed outside the Mac App Store.

  1. Open System SettingsPrivacy & Security.
  2. Scroll down to the message about "Pommedoro was blocked…" (or "from an unidentified developer").
  3. Click Open Anyway and confirm.

4. Open the app

Open Applications (or use Spotlight: Cmd+Space, type "Pommedoro") and double-click Pommedoro. It runs as a menu bar app — look for the timer icon in your menu bar.

Build from Source

Requires macOS 13+ and Swift 5.9+.

Command What it does
make bundle Build the .app (default target)
make install Build and install to /Applications; stamps as dev build (auto-update disabled)
make run Build and launch the app from .build/ (does not install)
make dmg Build the .app and create .build/Pommedoro.dmg
make release Build DMG and copy it + SHA256 into releases/ for publishing
make clean Remove build artifacts and the app bundle
make icons Regenerate Pommedoro.icns from Resources/boot-logo.svg
make install-agent Install LaunchAgent for Launch at Login (after make install)

For day-to-day development and releasing to users, see Development & Release below.


Development & Release

This section describes how to work on Pommedoro locally, how releases and auto-update work, and how to publish a new version.

Local development

  • Quick run (no install)
    make run — builds and opens the app from .build/Pommedoro.app. Good for quick tests.

  • Install and run from /Applications
    make install — builds, installs to /Applications/Pommedoro.app, and stamps the app as a dev build by writing dev into the update state file. The app will not prompt you to “update” to the release version; the menu item “Check for Updates” will show “Auto-update is disabled for local dev builds.”

  • After code changes
    Run make install again (or make run). There is no need to touch the update state for normal dev; the dev stamp persists until you install from a DMG or run a release flow.

  • Launch at Login (optional)
    make install-agent — installs a LaunchAgent that starts Pommedoro at login. Run after make install. Use make uninstall-agent to remove it.

How releases and auto-update work

  • Release artifacts
    A release consists of:

    • releases/Pommedoro.dmg — the disk image users install from.
    • releases/Pommedoro.dmg.sha256 — the SHA256 hash of that DMG (one line, 64 hex chars).
  • Where the app checks for updates
    The app fetches over HTTPS with no login:

    • SHA: https://git.nixc.us/colin/pommedoro/raw/branch/main/releases/Pommedoro.dmg.sha256
    • DMG: https://git.nixc.us/colin/pommedoro/raw/branch/main/releases/Pommedoro.dmg
      It uses the response Last-Modified header as the release “timestamp” so it only offers an update when the server has a newer release, not just a different one.
  • Local state (what version am I?)
    The app stores its idea of the current release in:

    • File: ~/Library/Application Support/Pommedoro/current.sha256
    • Format: two lines: SHA256 (64 hex chars) and a Unix timestamp (or 0).
    • Set when:
      • Installing from the DMG (via Install.command) — writes the DMGs SHA and, if it matches the remote, the remotes Last-Modified timestamp.
      • After a successful in-app update — the swap script writes the new SHA and timestamp.
      • After make install — writes dev so the app never nags for updates.
  • When “Update available” is shown
    Only if both are true:

    1. The release SHA on the server is different from the one in the state file.
    2. The servers Last-Modified is newer than the stored timestamp (so we dont offer “updates” to an older release).

    If the SHA in the state file matches the server, the app always reports “Up to Date,” regardless of timestamp.

Use this to build, test, and publish a release so the installed app and the server stay in sync and “Check for Updates” behaves correctly.

  1. Run the full test and push the release

    ./build-test.sh --push
    

    This will:

    • Stop any running Pommedoro, clean the build, and run make release.
    • Create the DMG and releases/Pommedoro.dmg.sha256.
    • Simulate a user install (quarantine, mount DMG, run Install.command), then verify the app runs and the local state file is stamped with the release SHA.
    • Push: git add releases/Pommedoro.dmg releases/Pommedoro.dmg.sha256, commit, and push.
    • Prove: Fetch the remote SHA and confirm it equals the first line of the local state file. If not, the script exits with failure.
  2. Result

    • The app launched by Install.command is the same build as the one now on the server.
    • “Check for Updates” in the menu should show Up to Date.
  3. Redeploy local (optional)
    To reinstall from the release DMG after pushing (e.g. you were on a dev build), run:

    ./redeploy-local.sh
    

    This stops Pommedoro, mounts releases/Pommedoro.dmg, runs Install.command, then unmounts. Your local install then matches the release you pushed.

Without --push:
./build-test.sh runs the same build and install checks but does not commit or push. Use this to verify the DMG and install flow before you push.

When to run what

Goal Command
Code and run locally without installing make run
Install to /Applications and develop (no update prompts) make install
Build a DMG only (e.g. to test the image) make release (or make dmg for .build/ only)
Test full install from DMG and state stamp ./build-test.sh
Test, push release, and prove update state ./build-test.sh --push
Reinstall from release DMG so local matches server ./redeploy-local.sh

Troubleshooting

  • “Update available” even though I just pushed / same version
    The app compares the first line of ~/Library/Application Support/Pommedoro/current.sha256 to the SHA from the server. If you installed from a DMG and then pushed that same build, run ./build-test.sh --push so the script pushes and then verifies remote SHA = local state. If the scripts proof passes but the app still nags, the app may be reading a different path (e.g. different user); the script and app both use the same state file path.

  • Im a dev; I dont want update prompts
    Use make install. It writes dev into the state file so the app never offers to update. Installing from a DMG (or updating in-app) overwrites that with a real SHA + timestamp.

  • State file location
    ~/Library/Application Support/Pommedoro/current.sha256. Two lines: SHA, then timestamp (or 0). For a dev build, the first line is the literal string dev.


Requirements

  • macOS 13+
  • Swift 5.9+
  • Apple Silicon or Intel Mac

Author

Colin Knappcolinknapp.com

License

This work is licensed under Creative Commons Attribution 4.0 International (CC BY 4.0).

You are free to share and adapt this work for any purpose, including commercially, as long as you give appropriate credit.