Repository navigation
Conversation
Settings > App > "Show the countdown in the menu bar" (showMenuBarCountdown,
on by default and for a config.json without the key). Off, the status item
is the eye alone: open while a session starts or runs, closed otherwise,
with no countdown and no hold-to-end ring. While a session runs the
right-click menu then leads with the time left ("2h 14m remaining", read
when the menu opens) and offers End Session above Settings, standing in
for the ring. Clicking the eye still opens the extend pills. VoiceOver
reads the time left on the eye.
Checked on a real Mac (macOS 27.0.1, built-in display plus a Studio
Display): the eye opens and closes with the session, the menu shows the
time left, and End Session ends the session.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
macOS 27 delivers a status item click at the middle of the item, with the release already sent while the button is still down. So a press on the end ring landed on the countdown and a hold never lasted. Read the pointer and button state ourselves, and draw the ring and pills in the label colour so they show on a light menu bar.
…d closes Only an event at the middle of the item's window is moved to the pointer, so an accurate click that sat in the queue keeps its place. The hold now checks that the button is still down when the ring closes, so a release just before the deadline can't still end the session. Tests for both.
On macOS 27 a click on the status item also reaches the global mouse monitor, which collapsed the open pills before a pill could take the click. Ignore global clicks that land on the item's own window.
Removes a build warning about reading the host view's frame off the main actor.
… time Adds a Countdown setting. The menu bar shows either the time left, as before, or the clock time the session ends at. The end time changes only with the session, so nothing redraws the status item each second, and the right-click menu gains a Time left line. A change in Settings reaches a running session at once. The time left stays the default, and a config from an earlier build keeps working.
A style change while a start or an extension waits on the helper left the projected countdown in the old style, and a pending start kept its 1 Hz tick after End time was picked. The controller now observes the style, and a pending extension keeps a projection like a pending start, so both redraw in the new style and the tick follows it. Two spec lines this change had left stale are corrected, and the pending tick test ends each session it starts.
…he deadline cancels the hold On macOS 26 an event at the middle of the item is a real click, so the local monitor now exists on macOS 27 and later only. When the ring closes it also checks how far the pointer moved since the hold began, so a slide made after the last poll still cancels. Tests cover both, and the release checklist gets a macOS 26 line.
The countdown redrew every second for the whole session, also with every display asleep, where nobody can see it. The spec's event table already says the redraw costs one wake per second while active and visible, but only a closed lid stopped it. With the display set to sleep after a few idle minutes, that is most of a session spent away from the Mac. A DisplaySleepObserver, alive as long as the app, passes NSWorkspace's screensDidSleep and screensDidWake to the session manager. While the displays sleep the 1 Hz timer is off. A wake redraws at once and starts it again, unless the lid keeps it stopped. A lid open starts it as before, without waiting for the wake: opening the lid wakes the displays.
… sleep The observer only heard of later changes, and the state started as awake. An app opened again while the displays slept, by a relaunch over SSH or after a force quit, resumed its session with the countdown redrawing until the next display event. The observer now reads the displays once when it starts, every online one asleep (CGDisplayIsAsleep), and reports that before the launch reconcile, so a resumed session starts with the redraw off.
Lands #84 at aad9e76, by cpugenius. Conflicts with #74, resolved here: both added an End item to the right-click menu. The menu keeps one, #74's, shown whenever a session runs and titled "End session". #84's duplicate menuEndSession and its offersEnd parameter are gone; with the countdown off the menu still leads with #84's time-left line. #84's test that expected no End item with the countdown on now expects End session, and #84's prose says "End session" to match. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd time Lands #132 at cab1050, by ChemaAlfonso. Conflicts with #84, resolved here: both added a time-left row to the right-click menu, so the merged menu had two. It keeps one, at the top as #84 placed it, with #132's text ("Time left: 2h 14m") and #132's active-session guard. The controller supplies it while a session runs and the bar does not show the time left: the countdown is off, or it shows the end time. #84's tests now expect that text, and #132's menu test expects the End session item #74 adds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lands #137 at 4a9bdb1, by salvcy. Conflicts with #132, resolved here: both added a reason for the 1 Hz countdown redraw not to run. armCountdownTimer keeps both guards, so it arms nothing while the displays sleep or while the bar shows the end time. SessionManager keeps both entry points, countdownStyleChanged and displaysAsleepChanged. The release checklist and the spec keep both entries, and the spec's power table names both reasons. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DisplaySleepObserver's isolated deinit crashed the test process on macOS 26.2 (Xcode 26.4.1): the Swift runtime's swift_task_deinitOnExecutorImpl freed a pointer it never allocated, and malloc aborted. DisplaySleepTests' testTheObserverReportsTheStateAtLaunchThenWorkspaceSleepAndWake hit it on #137's own head as well as here. Hosted CI runs newer macOS, where it passes. The deinit is now nonisolated. It reads the observer tokens, which init sets once, and NotificationCenter.removeObserver is safe from any thread. The app keeps the observer for its whole life, so only tests free it today, but the app supports macOS 26.0 and later. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With #132's end time on the bar there is no 1 Hz redraw, so a display wake arms none, but #137's wake handler still logged "display awake: countdown redraw restarted". The release checklist reads that line. Log it only when the timer is armed, and test that a wake arms nothing with the end time and that a switch to the time left while the displays sleep waits for their wake. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2cb4928000
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
With the countdown off, VoiceOver reads the time left on the eye. With the end time chosen nothing ticks, so that time left went stale: a four hour session still read "Time left: 4h" half an hour in. The eye now reads "Ends at" and the end time then, which needs no redraw. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The status item runs its own 1 Hz tick for the projected countdown while a start is in flight. #137 stopped the manager's redraw while every display sleeps but not this one. The manager's displaysAsleep is now observable; the status item stops its tick while it is true and redraws the projection when a display wakes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The end time countdown has no timer, so after a time zone change it kept the old zone's clock time, and an end tomorrow kept its weekday past midnight. A TimeChangeObserver now formats the countdown again on the system time zone, system clock, calendar day and locale notifications. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
VM run on macOS 27.0.1 at ce8ba6fThis is supporting evidence from a macOS 27.0.1 VM. It does not replace the owner's real-Mac report, which is still to come. The head moved from One check failed. A click on a pill (Minutes or Days) closes the pills about half a second later, and the clicked pill never takes focus. A bisect puts the start at the #143 merge. The other checks passed. Setup. macOS 27.0.1 (26A434), Xcode 27.0 (27A266a), Swift 6.4. Evidence: run-145.log, time zone and typing rerun, app switch rerun, insomnia.log, build, install, and the scripts. The bar text in run-145.log comes from fast OCR, which misreads some digits (5:38 as 6:38), so the screenshots are the record for what the bar showed. The log blocks below are excerpts: some lines are left out and long menu item lists are cut.
Fail: a click on a pill closes the pillsSteps from idle: click the eye and wait 1.5 s (the pills open, width 34 to 228), click the Minutes pill at x 1207, then type 7. The same again with the Days pill at x 1080. Then start a 4 h session, click the countdown, click the Minutes pill and type 7. The script samples the width about 0.1, 0.6 and 1.5 s after the pill click. At The same script on other commits, each built and installed in the same VM from a fresh config.json:
An untested guess at the cause: #143's What it means in use: Tab moves between pills and typing works (the #143 section below), so a duration can still be entered from the keyboard. Clicking a pill closes them. Hold-to-endA 4 h session with the countdown on and Time left. The ring (the end control) is at x 1238.
A click on the countdown at x 1190 opened the pills (width 120 to 228) and Esc closed them. A click on the eye did the same. Slide-off shots: 300 ms down, 500 ms down, 500 ms left. End session in the menuDuring a 4 h session, right-click the item and click End session (app pid 2963): Menu left open past the end: a 60 s session ending at 01:35:45Z. The menu opened at 01:35:26 and stayed open while the timer ended the session. At 01:36:00, 15 s after the end, the script clicked End session in that still-open menu. Nothing was logged, sleep stayed restored, and the app kept running with the same pid. The menu opened again afterwards has no End session.
Menu contents
Countdown setting off and on mid-sessionDuring a 4 h session, Settings > App > Show the countdown in the menu bar, pressed through accessibility: Off, the bar shows only the open eye at the idle width of 34, and a click on the eye still opens the pills. On again, the countdown is back. A session started with
End timeA 4 h session ending 05:38:37Z. Settings > Countdown changed from Time left to End time mid-session. The bar shows run-145.log shows End time after a time zone changeA 4 h session started with
The full bar in New York time shows the clock at Fri 23. Oct 22:12. One thing to note: the same-day form has no leading zero ( The G part in run-145.log is a first attempt that stopped at the password prompt, so the time zone never changed there. The rerun log above replaces it. Typing into the pills (#143)TextEdit is frontmost with Width 108 is the running width for this 30 min session. The digits went to the pills (Hours 2, Minutes 3), and no key reached TextEdit while the pills were open. After Esc or Enter the next key went back to TextEdit. Esc after typing into an extend did not extend. Activating Finder with the pills open closed them, and a 3 typed in Finder did not reopen them. In the app switch rerun, a click in another app's window with the pills open closed them and the next key went to that app. That click landed on a Finder window covering TextEdit, so the app was Finder.
The F part in run-145.log is a first attempt where TextEdit's AppleEvents timed out, so its text reads are void. The rerun log above replaces it. Displays asleepTime left, a 4 h session, then The force quit was End time, a 4 h session started with After the wake the bar showed Not run
🤖 Generated with Claude Code |
On macOS 27 a press on the status item deactivates Insomnia while the button is still down. The catcher panel from #143 then resigns key, and its focus-lost handler collapsed the pills about 1 ms after the press, before the pill's tap (on release) could focus it. So a pill click closed the pills, and #93's fix for the same click was lost in the merge. Focus lost with the pointer on the item now waits for the release: a pill takes focus back through focus(_:), and if the catcher is still not key 0.2 s after the release the pills close, so typing never goes to another app while they show. Focus lost anywhere else collapses at once, as before. Checked in a macOS 27.0.1 VM: Minutes and Days clicks keep the pills open, typed digits land in them and start or extend the session, and a click elsewhere, Cmd-Tab, Escape, an eye click and a drag off a pill still close them. The hold-to-end checks still pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two review findings on 54f3c7e. A release followed within 0.2 s by a second press held past that deadline could still collapse the pills under the second press: invalidating the poll timer did not cancel the delayed block it had queued. The wait is now one timer stepping RefocusWait, which restarts its grace on every press and stops once the catcher is key again, with nothing queued behind it. A new focus loss replaces the watch. A pill click calls takeFocus() again, and on macOS 27 the press has made another app frontmost by then (Finder in the VM run), which could replace the app the user was in as the one focus returns to. The panel now picks its return target on the first takeFocus() only. PillRefocusTests covers the overlapping press, the refocus, the grace and the return target; the last fails with the first-call guard removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
VM rerun on macOS 27.0.1 at 732de03: pill clicks passThis rerun is at head Root cause. On macOS 27, a press on the status item deactivates Insomnia while the button is still down, and the app that was in front before becomes frontmost again. The key catcher panel from #143 then loses key status, and its Setup. The same macOS 27.0.1 (26A434) VM and Xcode 27.0 as before.
X11 also ran against a build of Frontmost app, read with
After an eye click, focus reached TextEdit in X4, X14 and X15. In the logged runs on Evidence: pill-click-732de03.log (P1, P2), fixcheck-732de03.log (X1 to X10), cases-732de03.log (X11 to X15), run-145-A-F.log, x11-first-fix.log, insomnia.log, build, install, build and install summary, scripts. Limits
🤖 Generated with Claude Code |
Codex round 2 on 732de03: since 54f3c7e, a lost focus with the pointer on the status item waits out the press instead of collapsing at once, so testLosingKeyboardFocusClosesThePillsAndDropsTheKeyCatcher depended on where the CI machine's pointer rested. The controller now reads the pointer through pointerLocation (NSEvent.mouseLocation by default), and the test pins it off the item. The waiting branch is covered by PillRefocusTests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>






















On macOS 27 you can end a session again: hold the × beside the countdown, or right-click the eye and choose End session. The menu bar also gets two new options, the eye without a countdown and the end time instead of the time left, and it stops redrawing while the displays sleep. Clicking the Minutes or Days pill on macOS 27 no longer closes the pills.
This lands six contributor pull requests together. Each one is merged here at its exact head commit, unchanged, so GitHub marks it merged when this one merges and its author keeps the credit. Their changes overlap in the same files, so merging them one by one would have left each later one with conflicts. The conflicts are resolved in the merge commits below, and the maintainer fixes come after them.
Lands #93 by @hasin023 at e537b3a: hold-to-end works on macOS 27 and shows on a light menu bar.
Lands #74 by @ethduke at 43f7523: End session in the right-click menu.
Lands #143 by @ilya-the-frog at b6ee899: the duration fields take the keyboard focus.
Lands #84 by @cpugenius at aad9e76: an option to show the eye without the countdown.
Lands #132 by @ChemaAlfonso at cab1050: the countdown can show the end time.
Lands #137 by @salvcy at 4a9bdb1: no countdown redraw while the displays sleep.
Fixes #60
Fixes #112
Fixes #131
To the six authors: please don't push to your branches now. A new commit on your branch isn't in this pull request, so your pull request would stay open instead of showing as merged.
Conflicts resolved
menuEndSessionfunctions and didn't compile. The menu keeps Add an explicit End session action to the menu #74's item and handler: shown whenever a session runs, titled "End session" like the hold control's label. Menu bar: an option to show the eye without the countdown #84'soffersEndparameter is gone. With the countdown on, Menu bar: an option to show the eye without the countdown #84's test now expects End session in the menu too.armCountdownTimerkeeps both guards.Maintainer fixes
isolated deinitaborts on macOS 26.2. DisplaySleepTests'testTheObserverReportsTheStateAtLaunchThenWorkspaceSleepAndWakecrashed the test process on macOS 26.2 with Xcode 26.4.1. It crashed on menu bar: stop the countdown redraw while the displays sleep #137's own head too. The Swift runtime'sswift_task_deinitOnExecutorImplfreed a pointer it never allocated, and malloc aborted. Hosted CI runs newer macOS, where it passes. The deinit is now nonisolated. The app keeps the observer until it quits, so only tests free it today, but the app supports macOS 26.0 and later.displaysAsleepis now observable; the status item stops its tick while the displays sleep and redraws on wake.TimeChangeObserverformats it again on those four system notifications.Not in this
Tests
Full suite on macOS 26.2 (Xcode 26.4.1) at 2cb4928: 1,244 tests, 0 failures. The three fixes after it pass their focused classes (DisplaySleepTests, MenuBarCountdownOptionTests, TimeChangeTests, StatusMenuWarningTests: 27 tests), and each new test fails without its fix. The pill fixes pass PillRefocusTests and GlobalClickFilterTests (7 tests); the return-target test fails with its fix removed. Greptile's P1 at 54f3c7e, that the new focus path had no test, is answered by these. UIStatusTests, UIStartupTests and KeychainStoreTests are skipped on the maintainer's Mac; hosted CI runs them.
Codex review
At 2cb4928 (gpt-6.1-sol, xhigh, read-only): a P2, the eye's stale VoiceOver time left, fixed in 1e05994; a P3, the pending start's tick during display sleep, fixed in 5b18542. Greptile and the Codex GitHub reviewer also found the stale end time after a time zone change, fixed in ce8ba6f.
At ce8ba6f: two P3s, not changed.
displaysAsleepbefore its redraw restarts (menu bar: stop the countdown redraw while the displays sleep #137's design: opening the lid wakes the displays). If the displays went to sleep during the lid-open restore, the 1 Hz redraw would run until the next display event. That restore takes seconds, and display sleep needs at least a minute idle, so the window is near zero; changing it needs lid hardware to verify.At 54f3c7e: two P2s, fixed in 732de03. A release followed within 0.2 s by a second press held past the first deadline could close the pills under the second press, and a pill click could make Finder the app focus returns to.
At 732de03: a P2 and a P3. The P2, an eye click leaving focus in Finder, didn't reproduce: in the VM, after every eye click that closed the pills or committed a duration, TextEdit was in front within 0.1 s and took the next key. Not changed. The P3, a UI test that depended on where the pointer rested, is fixed in 85576e5.
At 85576e5: clean.
Evidence
The owner's report comes from their own Mac at the final head, on macOS 26 and again on macOS 27. A macOS 27.0.1 VM run comes first: at ce8ba6f, which found the pill click, and at 732de03, where it and every other case pass. Items from "Real-Mac checks before a release" in docs/releasing.md this can affect:
🤖 Generated with Claude Code


Confidence Score: 5/5
The changes since the previous review appear safe to merge.Summary
The PR combines session-ending controls, keyboard-focus fixes, countdown options, and fewer redraws while displays sleep.
Reviews (5) · Last reviewed commit: "Pills test: pin the pointer off the item..." · Reviewed by Greptile