Skip to content

Menu controls: land #93, #74, #143, #84, #132 and #137 - #145

Open
krishhgg wants to merge 28 commits into
mainfrom
integration/menu-controls
Open

krishhgg wants to merge 28 commits into
mainfrom
integration/menu-controls

Conversation

@krishhgg

@krishhgg krishhgg commented Oct 24, 2026 •

Copy link
Copy Markdown
Owner

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

Maintainer fixes

  • 5022269, menu bar: stop the countdown redraw while the displays sleep #137's isolated deinit aborts on macOS 26.2. DisplaySleepTests' testTheObserverReportsTheStateAtLaunchThenWorkspaceSleepAndWake crashed 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's swift_task_deinitOnExecutorImpl freed 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.
  • 2cb4928, the wake log line. With the end time there's no redraw, but menu bar: stop the countdown redraw while the displays sleep #137's wake handler still logged "display awake: countdown redraw restarted", a line the release checklist reads. It's logged only when the timer starts now. A new test covers the end time's wake and a switch to the time left while the displays sleep. It fails with either guard removed.
  • 1e05994, the eye's VoiceOver value with the end time. With the countdown hidden, VoiceOver reads the time left on the eye. With the end time chosen nothing ticks, so that value went stale. The eye now reads "Ends at" and the end time then.
  • 5b18542, the pending start's tick during display sleep. The status item runs its own 1 Hz tick for the projected countdown while a start is in flight, and menu bar: stop the countdown redraw while the displays sleep #137 didn't stop it. displaysAsleep is now observable; the status item stops its tick while the displays sleep and redraws on wake.
  • ce8ba6f, the end time after a time zone, clock, day or locale change. Nothing ticks the end time, so it kept the old zone's clock time, and an end tomorrow kept its weekday past midnight. A TimeChangeObserver formats it again on those four system notifications.
  • 54f3c7e, 732de03 and 85576e5, a pill click closed the pills on macOS 27. A VM run at ce8ba6f found it. On macOS 27 a press on the status item deactivates Insomnia while the button is still down. Fix keyboard focus when entering session duration #143's typing panel then lost key status and closed the pills about 1 ms into the press, before the pill's click landed, which undid Menu bar: hold-to-end works on macOS 27 and shows on a light bar #93's fix for the same click. Now a lost focus with the pointer on the item waits for the release, and the pills close only if nothing has taken focus back 0.2 s after it. 732de03 restarts that wait on every press and keeps the app focus goes back to as the one the user was in. 85576e5 lets a UI test pin the pointer, so the CI machine's pointer can't change its result.

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.

  • Lid open clears displaysAsleep before 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.
  • A time zone change or midnight during a pending start or extension leaves the projected countdown stale until the manager confirms, which takes seconds; the confirmed text is then current.

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:

  • hold-to-end: a short tap does nothing, a full hold ends the session, sliding off at the last moment cancels it, and the pills and countdown still take their own clicks;
  • End session in the right-click menu, during a session and after the timer ends with the menu open;
  • clicking the Minutes and Days pills, and typing a duration after clicking the eye, Escape and Enter, and switching apps while the fields are open;
  • "Show the countdown in the menu bar" off and on, mid-session;
  • Countdown set to End time: the bar, the Time left line and CPU near zero;
  • displays asleep: the log lines, CPU time while asleep, and the countdown on wake.

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence 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.

  • The right-click menu can end a session and show its time left.
  • The menu bar can show when a session will end.
  • Countdown redraws stop when the display cannot show them.
  • Menu-bar presses can end a session without swallowing other clicks.

Reviews (5) · Last reviewed commit: "Pills test: pin the pointer off the item..." · Reviewed by Greptile

ethduke and others added 22 commits October 11, 2026 11:24
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 #93 at e537b3a, by hasin023.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lands #74 at 43f7523, by ethduke.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lands #143 at b6ee899, by ilya-the-frog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-24T00:58:43.297244Z 2cb4928 PR opened
🔒 Security Review ✅ Completed 2026-10-24T00:59:12.886059Z 2cb4928 PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread Sources/Insomnia/Core/SessionManager.swift
Comment thread Sources/Insomnia/Core/SessionManager.swift
Comment thread Sources/Insomnia/UI/StatusRootView.swift Outdated
krishhgg and others added 3 commits October 23, 2026 18:12
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>
@krishhgg

Copy link
Copy Markdown
Owner Author

VM run on macOS 27.0.1 at ce8ba6f

This 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 2cb4928 to ce8ba6f before the run, so everything below is ce8ba6f. 2cb4928 was not tested on its own.

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. integration/menu-controls at ce8ba6f01adfe3ef3f4368320afc3a586e42a72d with a clean tree (git status --porcelain printed nothing). build-app.sh exited 0 and ad hoc signed the app, CDHash a4e80b55f0b22cfacd9d57833a16cedd31522837. install.sh --allow-unverified-origin --app exited 0 with ==> Installed, version 0.1.0 (1). Region de_DE, time zone GMT, screen 1440x900. Each part starts the app from a fresh config.json without the new keys, unless a key is named. Clicks, holds, drags and keys are posted CGEvents, not a real mouse or trackpad. Status item widths in the logs: idle 34, running with Time left 120, End time 99, pills open 228.

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.

Check Result
Hold-to-end: short tap, full hold, slide-off Pass
Clicks on the countdown and the eye Pass
Click on a pill Fail
End session in the menu during a session Pass
End session in a menu left open past the timer end Pass
Menu contents: one End session, Time left row only when needed Pass
Countdown setting off and on mid-session Pass
End time in the bar, CPU near zero Pass
End time after a time zone change Pass
Typing into the pills (#143): Escape, Enter, switching apps Pass
Displays asleep Pass

Fail: a click on a pill closes the pills

Steps 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 ce8ba6f the pills close within 0.6 s in all three cases, and the 7 neither starts nor extends a session. The app logs nothing about it.

[02:15:34Z] P1 idle, eye clicked: item x 1022 width 228; clicking the Minutes pill at x 1207
[02:15:37Z] P1 after the click: +1: width 228 +2: width 34 +3: width 34
[02:15:37Z] P1 typed 7: item width 34; log since the eye click:
[02:15:41Z] P1 idle, eye clicked: item x 1022 width 228; clicking the Days pill at x 1080
[02:15:43Z] P1 after the click: +1: width 228 +2: width 34 +3: width 34
[02:15:44Z] P1 typed 7: item width 34; log since the eye click:
[02:15:58Z] P2 running, countdown clicked: item x 1022 width 228; clicking the Minutes pill at x 1207
[02:16:00Z] P2 after the click: +1: width 228 +2: width 120 +3: width 120
[02:16:01Z] P2 typed 7: item width 120; log since the countdown click:
    2026-10-24T02:15:58Z [info] insomnia: hotspot join skipped: no hotspotSSID configured

Pill clicks at ce8ba6f: the pills close after a click on Minutes or Days

The same script on other commits, each built and installed in the same VM from a fresh config.json:

Commit What it is After a click on a pill
e242782 main the pills close (sheet, log). main does not have #93, the fix for clicks on macOS 27
e537b3a #93's head the pills stay open and the clicked pill takes focus (sheet, log)
091e413 this branch after the #93 and #74 merges the pills stay open (sheet, log)
25e6f15 this branch after the #143 merge the pills close (sheet, log)
ce8ba6f head the pills close

An untested guess at the cause: #143's KeyCatcherPanel calls onFocusLost from resignKey, and installMonitors sets it to collapse(returnFocus: false) (StatusItemController.swift:841). If a click on the status item's window takes key status from the panel, that collapses the pills. #93's isClickOnItem check only guards the global mouse monitor.

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-end

A 4 h session with the countdown on and Time left. The ring (the end control) is at x 1238.

Step Result
100 ms tap on the ring no session end line, session.json present, SleepDisabled 1
Press 300 ms, slide down off the item to y 90, release 0.9 s later the session keeps running
Press 500 ms, slide down off the item, release 0.9 s later the session keeps running
Press 500 ms, slide left onto the countdown at x 1198, release 0.9 s later the session keeps running
Hold 1.5 s, twice the session ends while the button is still down
[01:33:59Z] A5 hold 1: 0.9 s after the press, button still down: session.json absent; session end lines 1
[01:33:59Z] A5 hold 1: after the release: session.json absent; SleepDisabled 0
    2026-10-24T01:33:55Z [info] insomnia: session end (user)
    2026-10-24T01:33:55Z [info] insomnia: sleep restored
    2026-10-24T01:33:55Z [info] insomnia: app nap restored for 20 app(s)
[01:34:13Z] A5 hold 2: 0.9 s after the press, button still down: session.json absent; session end lines 1
    2026-10-24T01:34:09Z [info] insomnia: session end (user)
Running During the hold After the hold

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 menu

During a 4 h session, right-click the item and click End session (app pid 2963):

[01:34:37Z] B1 End session items: 1; Time left rows: 0
    2026-10-24T01:34:37Z [info] insomnia: session end (user)
    2026-10-24T01:34:37Z [info] insomnia: sleep restored
    2026-10-24T01:34:37Z [info] insomnia: app nap restored for 20 app(s)
    2026-10-24T01:34:37Z [info] insomnia: notify: Session ended - Ended by you. Sleep is back to normal.
[01:34:42Z] session.json absent; SleepDisabled 0; agent runs 10, last exit code 0
[01:34:42Z] B1 app pid after: 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.

    2026-10-24T01:35:46Z [info] insomnia: session end (timer)
    2026-10-24T01:35:46Z [info] insomnia: sleep restored
    2026-10-24T01:35:46Z [info] insomnia: app nap restored for 20 app(s)
    2026-10-24T01:35:46Z [info] insomnia: notify: Session ended - Time is up. Sleep is back to normal.
[01:36:00Z] B2 click End session in the menu left open:
[01:36:05Z] B2 log after that click:
[01:36:05Z] session.json absent; SleepDisabled 0; agent runs 12, last exit code 0
[01:36:05Z] B2 app pid after: 2963
[01:36:08Z] B3 menu reopened after the end End session items: 0; Time left rows: 0
Before the end Still open 15 s after the end Reopened

Menu contents

Countdown in the bar Countdown style First row of the menu End session items
on Time left no Time left row (B1, C2) 1
off Time left Time left: 3h 59m (C1, C3) 1
on End time Time left: 3h 59m (D2, G2) 1
off End time Time left: 3h 58m (D2b) 1
no session no Time left row (B3) 0

The menu with the countdown off: Time left first, one End session

Countdown setting off and on mid-session

During a 4 h session, Settings > App > Show the countdown in the menu bar, pressed through accessibility:

[01:37:02Z] C1 press 'Show the countdown in the menu bar': ok; config showMenuBarCountdown false
[01:37:06Z] C1 countdown off mid-session: item width 34
[01:37:11Z] C1 click on the eye with the countdown off: item width 228
[01:37:43Z] C2 press 'Show the countdown in the menu bar': ok; config showMenuBarCountdown true
[01:37:46Z] C2 countdown back on mid-session: item width 120

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 {"showMenuBarCountdown": false} already in config.json also ran at width 34, and End session in the menu ended it (session end (user), sleep restored), leaving the eye closed.

On Off mid-session On again Started off, after End session

End time

A 4 h session ending 05:38:37Z. Settings > Countdown changed from Time left to End time mid-session. The bar shows 5:38 at width 99. CPU sampled with ps -o %cpu every 0.5 s, 40 samples, starting 10 s after the change, plus the process CPU time before and after:

[01:39:14Z] D0 Time left, 10 s after the start, 40 samples: CPU mean 0.4%, min 0.0%, max 1.3%, 40 samples
[01:41:16Z] D3 End time, 10 s after the switch, 40 samples: CPU mean 0.0%, min 0.0%, max 0.0%, 40 samples; CPU time 0:53.44 -> 0:53.44

The bar with End time: 5:38

run-145.log shows config countdownStyle "(absent)" on the D1 line because the script read config.json in the same instant as the pick. A separate check read it 2 s after the pick and found "endTime", then "remaining" after picking Time left again (settings-countdown-save.log).

End time after a time zone change

A 4 h session started with {"countdownStyle": "endTime"}, ending 06:12:04Z. The bar read 6:12. In System Settings > General > Date & Time, the script typed New York as the closest city, pressed Return, typed the password into the prompt and pressed Return on Unlock. Screenshots of the item started just before that last Return. The first frame that differs from the first one came 0.81 s after it and reads Sat 02:12, which is 06:12 GMT in New York time. Back to GMT with systemsetup -settimezone GMT, the item changed 0.28 s later.

[02:12:28Z] G1 Unlock pressed (Return) in the prompt for the Time zone city change to New York
[02:12:34Z] G1 time zone now: Time Zone: America/New_York; date: Fri Oct 23 22:12:34 EDT 2026
[02:12:34Z] G1 status item: frame 15, 0.81 s after the change
[02:12:58Z] G2 after the change, 40 samples: CPU mean 0.0%, min 0.0%, max 0.3%, 40 samples; CPU time 0:00.37 -> 0:00.37
[02:13:03Z] G3 time zone set back with systemsetup -settimezone GMT
[02:13:08Z] G3 status item: frame 12, 0.28 s after the change
Before, GMT New York Back to GMT

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 (6:12) and the next-day form has one (Sat 02:12), while the menu bar clock in this region reads 02:15.

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 abc typed in it (TextEdit capitalizes it to Abc). The script reads TextEdit's text through accessibility.

[02:13:34Z] F1 clicked the eye: item width 228; front TextEdit
[02:13:35Z] F1 typed 2, Tab, 35, Backspace: TextEdit text 'Abc'
[02:13:37Z] F1 Esc: item width 34; front TextEdit
[02:13:37Z] F1 typed d after Esc: TextEdit text 'Abcd'
[02:13:46Z] F2 Tab, 30, Enter: 2026-10-24T02:13:45Z [info] insomnia: session started until 2026-10-24T02:43:40Z (1800s requested)
[02:13:47Z] F2 after Enter: front TextEdit; TextEdit text 'Abcd'
[02:13:48Z] F2 typed e: TextEdit text 'Abcde'
[02:13:50Z] F3 clicked the countdown, typed 1: TextEdit text 'Abcde'
[02:13:52Z] F3 Esc: item width 108; front TextEdit; extend lines 0
[02:13:53Z] F3 typed f: TextEdit text 'Abcdef'
[02:13:55Z] F4 pills open again: item width 228; front TextEdit
[02:13:57Z] F4 switched to Finder with the pills open: item width 108; front Finder
[02:13:57Z] F4 typed 3 in Finder: item width 108

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.

2, Tab, 35, Backspace Tab, 30 After switching to 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 asleep

Time left, a 4 h session, then pmset displaysleepnow. A CGDisplayIsAsleep check confirmed the display was asleep.

[01:42:39Z] E1 remaining, 5 s after pmset displaysleepnow: displays asleep (1 display(s)); log:
    2026-10-24T01:42:34Z [info] insomnia: displays asleep: countdown redraw stopped
[01:43:49Z] E1 remaining, 60 s untouched: CPU time 0:00.57 -> 0:00.57; displays asleep (1 display(s)); CPU mean 0.0%, min 0.0%, max 0.0%, 20 samples
[01:43:59Z] E2 force-quit and opened again while asleep: pid 5896; displays asleep (1 display(s)); log:
    2026-10-24T01:43:57Z [info] insomnia: reconcile: session valid until 2026-10-24T05:42:25Z
[01:44:29Z] E2 30 s later: CPU time 0:00.19 -> 0:00.20; displays asleep (1 display(s))
[01:44:33Z] E3 remaining, after a wake (caffeinate -u): displays awake (1 display(s)); log:
    2026-10-24T01:44:29Z [info] insomnia: display awake: countdown redraw restarted

The force quit was kill -9. After the wake the bar counted down again (shot).

End time, a 4 h session started with {"countdownStyle": "endTime"}, the same steps without the force quit. The log has neither a redraw stopped nor a restarted line, and CPU time stayed flat:

[01:45:10Z] E1 endTime, 5 s after pmset displaysleepnow: displays asleep (1 display(s)); log:
[01:46:20Z] E1 endTime, 60 s untouched: CPU time 0:00.41 -> 0:00.41; displays asleep (1 display(s)); CPU mean 0.0%, min 0.0%, max 0.1%, 20 samples
[01:46:25Z] E3 endTime, after a wake (caffeinate -u): displays awake (1 display(s)); log:
[01:46:25Z] E3 endTime: lines since the wake that say restarted: 0

After the wake the bar showed 5:44 (shot).

Not run

  • The eye's VoiceOver value with the countdown hidden and End time (1e05994). The status item exposes no accessibility children in this VM.
  • The pending start's countdown tick during display sleep (5b18542). Starting a session needs a click, and the click wakes the displays.
  • End time after a clock, day or locale change. Only the time zone change ran.
  • A real mouse or trackpad. All input was posted CGEvents.
  • A light menu bar.
  • Notification banners. Notifications are off for the app in this VM, so only the notify: log lines show.
  • Lid close, an external display, and macOS 26.
  • Keyboard focus in the Settings window.

🤖 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>
Comment thread Sources/Insomnia/UI/StatusItemController.swift
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>
@krishhgg

Copy link
Copy Markdown
Owner Author

VM rerun on macOS 27.0.1 at 732de03: pill clicks pass

This rerun is at head 732de03. It supersedes the pill click failure in my earlier comment, which was at ce8ba6f. Everything below passed. It is supporting evidence from a VM and does not replace the owner's real-Mac report.

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 onFocusLost handler collapsed the pills at once, before the pill's tap could fire on mouse-up. Temporary logging on ce8ba6f showed onFocusLost was the only path that collapsed them. 732de03 waits out a press on the item instead. A pill takes focus back when the button comes up, and the pills close if nothing has taken focus 0.2 s after the release.

Setup. The same macOS 27.0.1 (26A434) VM and Xcode 27.0 as before. integration/menu-controls at 732de031ff81c62d56a7250b92061f5ef5b7edae with a clean tree. build-app.sh exited 0, CDHash af2486571e4f0c3d017fcd1f2a53803162407601, and install.sh --allow-unverified-origin --app exited 0. For the X cases, TextEdit is frontmost with abc typed in it, and the script reads its text through accessibility. Clicks and keys are posted CGEvents. Item widths: idle 34, pills open 228, running 120.

Case Steps What happened Result
P1 Idle: eye, then the Minutes pill, type 7 Pills stay open: width 228 at about 0.1, 0.6 and 1.5 s and after the 7 Pass
P1 Idle: eye, then the Days pill, type 7 Pills stay open Pass
P2 Running: countdown, then Minutes, type 7 Pills stay open Pass
X1 Eye, Minutes, 7, Enter Session of 420 s, next key in TextEdit Pass
X2 Eye, Days, 1, Enter Session of 86400 s, next key in TextEdit Pass
X3 Running: countdown, Minutes, 7, Enter Extended by 420 s, next key in TextEdit Pass
X4 Eye click with the pills open Pills close, next key in TextEdit Pass
X5 Press on Minutes, drag off below the bar, release Pills close after the release, next key in TextEdit Pass
X6 Click on the item's right edge, past Minutes Pills close, next key in TextEdit Pass
X7 Click in TextEdit's window Pills close, next key in TextEdit Pass
X8 Cmd-Tab Pills close, Finder in front Pass
X9 Escape Pills close, next key in TextEdit Pass
X10 Enter with nothing typed Default session of 14400 s, next key in TextEdit Pass
X11 Click Minutes, press Days 108 ms after that release and hold it 508 ms, type 7, Enter Pills stay open, 7 in Days, session of 604800 s, next key in TextEdit Pass
X12 Eye, Minutes, 7, Enter Session of 420 s, TextEdit in front after Enter, next key in TextEdit Pass
X13 Eye, Minutes, 7, Escape No session, TextEdit in front after Escape, next key in TextEdit Pass
X14 Eye opens the pills, eye closes them, a key 1 s later Key in TextEdit Pass
X15 Eye, Minutes, 7, then the eye commits Session of 420 s, next key in TextEdit Pass
A Hold-to-end: a short tap, three slide-offs, two 1.5 s holds, plus clicks on the countdown and the eye The tap and slide-offs leave the session running, both holds end it, the clicks open the pills. Same lines as at ce8ba6f Pass
F1 to F4 Typing into the pills (#143): Escape, Enter, Escape after an extend, switching to Finder Same lines as at ce8ba6f Pass
F5 Pills open, then a click in TextEdit's window Pills close, the key goes to the clicked app. As in the app switch rerun at ce8ba6f, a Finder window covers TextEdit there, so that app is Finder Pass

X11 also ran against a build of 54f3c7e's code, the first version of the fix. There the pills folded during the Days hold, closed after the release, and the 7 went to TextEdit. That is the race 732de03 fixes.

Frontmost app, read with lsappinfo around the press, key or release that ends each case:

Case Before About 0.1 s after About 1 s after
X4, eye closes the pills Insomnia TextEdit TextEdit
X11, Days release, pills stay open Insomnia Insomnia Insomnia
X12, Enter Insomnia TextEdit TextEdit
X13, Escape Insomnia TextEdit TextEdit
X14, eye closes the pills Insomnia TextEdit TextEdit
X15, eye commits Insomnia TextEdit TextEdit

After an eye click, focus reached TextEdit in X4, X14 and X15. In the logged runs on ce8ba6f, Finder was in front before the eye click, and the press on the item made Finder frontmost again. So in these cases focus reaching TextEdit does not depend on KeyCatcherPanel.returnFocus.

Pill clicks at 732de03: the pills stay open after a click on Minutes or Days

X11 at 732de03 and with the first fix: overlapping presses on Minutes and Days

The typed digit in the clicked pill in X1, X2, X3, X12, X13 and X15

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

  • macOS 26 is not covered. The lab has no macOS 26 VM, and the owner's Mac runs 26.2.
  • After a press on the item that no handler takes, such as a drag off a pill or a click on the item's edge, keys typed within 0.2 s of the release can reach the app in front before the pills close.
  • X11 ran with one timing: the second press 108 ms after the first release, held 508 ms.
  • Each case ran once, with posted CGEvents rather than a real mouse or trackpad.

🤖 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

7 participants