Repository navigation
Conversation
#96 was written before main added the End session menu item (#74), the pointer checks on the end ring (#93) and the unanimated countdown (1cd9d3a), so the merge does not compile on its own. - CompactStatusMenu and performMenuAction handle .endSession. The menu item ends the way each style's hold control does: Pills through endNow, Compact and Minimal through endPresentedSession. It still does nothing once the session is over. - SessionRunningControls takes the button and pointer readers and hands them to HoldToEndButton, so the Pills ring keeps #93's checks. - The shared countdown text drops #96's numericText transition and its Motion.tick spring, which main removed: each animated frame re-renders the whole status item. It uses Motion.countdownTick like main's. - Test call sites of StatusMenu.menu pass the endSession selector.
On macOS 26.2 the Swift runtime frees a pointer it never allocated while it runs an isolated deinit, and the process aborts. Main already avoids it in DisplaySleepObserver and TimeChangeObserver. The merged test run aborted the same way in MinimalIconMouseView's deinit, after CompactSizingTests released a Compact panel with its native end ring. Both deinits are now plain ones: the hold task and the event monitors are nonisolated(unsafe), since cancelling a task and removing a monitor are safe from any thread. The panel is ordered out on the main actor.
Main draws the hold-to-end ring in the label colour (d8844ce), because a white ring vanishes on a light menu bar. #96 added a foreground parameter that defaults to white, so the Pills ring went white again and MenuBarAppearanceTests failed. HoldToEndButton and SessionRunningControls now default to .primary. Compact already passed .primary.
#96 called the inline style "Default" and stored it as "standard", and its decoder threw on a menuBarMode value it did not know, which made the whole config.json unreadable. A newer build that adds a style (Popover is planned) would do that to anyone who goes back a version. - The case is now .pills, stored as "pills", and Settings labels it Pills. #96 never shipped, so nothing stored "standard" outside its branch; that value now reads as Pills like any other unknown one. - A menuBarMode key with a value this build does not know reads as Pills and keeps the rest of the config, as main already does for the default field and the countdown style. No key keeps #96's migration from the old compactMode flag. - Code comments say Pills where they said the default mode. MinimalConfigTests covers unknown values ("standard", "popover", an empty string and a number) and the stored name. The unknown-value test fails with #96's decoder.
#147 made Settings one page grouped by what happens, with every menu bar setting in General. #96 added a Menu bar section of its own above the lid group. The picker is now the first row of General, labelled Menu bar, with one line under it on what the chosen style does: - Pills: duration pills and the countdown beside the eye. - Compact: only the eye. A click opens the pills in a panel. - Minimal: a ring for the time left. Click adds, hold ends. With Minimal chosen, the Default session length row is #96's free-text field (every Minimal click adds that length, so any length can be typed) in place of #147's preset menu. Both edit defaultPreset. #96's per-style footer is gone; the line under the picker replaces it. SettingsTests renders the window and checks the picker sits after the battery floors and before the default length, that Minimal swaps the preset menu for the field, and that each style has a name and one short line. All three fail on the layout as merged.
The Pills style focuses the field chosen in Settings each time it opens, but #96's Compact panel always reset its draft to hours. An untouched draft now opens on the Default field when the panel opens, when the countdown is clicked to add time, and after a start, an extension or an end goes through. A draft with digits in it keeps its own focus.
#96's panel became key without activating Insomnia. #143 found that the Pills' key window then leaves the digits going to the app in front, and fixed that by activating on open and yielding on close. The Compact panel now does the same. Opening activates Insomnia and remembers the app the user was in. Escape, a second click on the eye and a mode change give focus back to it. Switching apps or opening Settings leaves focus where it went. An outside click that brings no app forward gives it back. The right-click menu gives it back after the menu closes, like the Pills. The panel keeps its non-activating style. Activation is what routes the keyboard here, and a regular panel cannot become key in the test process, which the responder test relies on.
#93 made the end ring read the button and the pointer when it closes. On macOS 27 the release and the drags arrive from a 60 Hz poll, so a release or a slide just before the deadline may not have been delivered yet. #96's native press view, which Minimal's eye and the Compact panel's ring use, ended the session on its own timer without that check. It now makes the same check, with readers passed down from the status view so tests can stand in for the hardware. A hold that fails it ends nothing, and its late release adds no time. #93 also drops a held-back release when the next press arrives. The native view kept the earlier press claimed and swallowed the next click, so a new press now starts clean. #96's hold test now states that the button is down, since the hold reads it.
#96 redrew the arc whenever the countdown text changed. With #132's end-time style that text stays fixed for the whole session, so the arc never drained. With the time-left style it redrew every second, and each redraw ran a 0.25 s animation, the per-second status item cost that main removed from the countdown. The arc now reads the session's start and end on its own timer. The timer fires about every hundredth of the session, from 2 s up to 60 s; the 300 degree arc is about 46 pt long, so a second of an hour moves it far less than a pixel. A start, an extension or an end still redraws at once. Time running out redraws in one step; only added time animates. As #137 does for the countdown, the timer runs only in Minimal with a session while a display is awake. A wake redraws the arc at once and restarts the timer. Two of #96's tests drove the arc through the countdown; they now fire the arc's timer instead.
The spec still called the default "Default mode" and described #96's Minimal and Compact as #96 shipped them. It now names the styles Pills, Compact and Minimal, notes that Popover is planned and that an unknown menuBarMode reads as Pills, and records what changed here: Compact opens on the default field, takes focus and gives it back, and changes its countdown in one step; Minimal's arc runs on its own slow timer from the session, stops while the displays sleep, and its hold checks the button and the pointer when it completes; Compact and Minimal list the time left in the menu. docs/releasing.md now has real-Mac checks for each style as it behaves now: start, pending cancel, extend, end by hold on macOS 26 and 27, focus, display sleep, the menu's time left and a style change during a session. The checks the old section had for injected waits, panel geometry, Reduce Motion and the shared menus are kept.
The menu bar is planned to have four styles, and Popover is not built yet. The section now says Popover adds its own checks when #118 lands, so its absence does not read as an oversight.
With the countdown set to End time, the bar shows the clock time the session ends at. #96's shared countdown labelled it "Time remaining" for VoiceOver in Pills and the Compact panel, and Minimal's tooltip and VoiceOver value said "Keeping awake, 17:30 remaining". main's countdown used the text itself as the label, and the eye already says "Ends at" for the end-time style. SessionRunningControls takes the countdown style. Its label is "Ends at" for the end-time style and "Time remaining" otherwise, and Minimal says "Keeping awake, ends at 17:30". Both come from static functions that the new test pins for each style.
testSlotsArriveOnOpenAndLeaveWithTheLastPill slept fixed times before checking that the slots had left and the width was written once. #96's StatusItemWidthMotion writes the native width a settle (about 187 ms) after the layout's report, which took most of that margin, so a loaded runner could fail correct behavior. The test now waits, up to 2 s each, for the slots to leave and for the write, then checks the same things as before: the fold has ended, the width target, exactly one write, and no write while the pills fold. It passed 5 of 5 alone.
#96 ended the session when a release was handled 0.6 s or more after the press and the hold task had not run yet, so that a real hold the main thread delayed still ended. The same rule ended the session on a click: a stall delays the click's release as much as the hold task, and on macOS 27 the release comes from #93's poll, stamped when the poll runs. The Compact panel's ring goes through the same press view. Now only the hold task's check, which reads the button and the pointer, ends a session, as on the Pills ring. A late release ends nothing and adds no time; a delayed real hold needs a second hold. The state machine test that expected an end now expects nothing, and a new test drives the view with a click handled after the hold time. Both fail with the old rule. The spec says so.
74022336 made a Minimal or Compact-ring press whose release arrives before the hold check end nothing and add no time. The release checks still said every 0.6 s hold ends the session and every Minimal click adds time, so a busy-main-thread press that correctly did nothing would read as a failure.
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. |
MacBook run on macOS 26.2 at c81856d: Pills, Compact and MinimalThe three styles work on a real Mac. Holds, quick clicks, slides, the Compact panel's focus handling, style changes during a session and the fallback for an unknown style all behave as the description says. One check fails, and main has the same fault: Accessibility cannot read the status item's label or value in any style (details below). The head is now Setup. The maintainer's MacBook Pro (M5), macOS 26.2 (25C56), on AC power at 100%, with a dark menu bar and the built-in display only.
Accessibility findingThe status item exposes no name or value to Accessibility. Read through the Accessibility API, Minimal shows this idle, running and with End time, and Pills on wave1-base ( So VoiceOver most likely reads an unnamed status menu. It can't reach the time left from #84, the label "Insomnia", #96's help text or Minimal's End session action. VoiceOver itself was not turned on. The likely cause: the SwiftUI view that carries Pills: idle, the pills with 2 typed, running, a hold partway, after the end. Compact: the panel open, typed, running, a hold on its ring partway, after the end. Minimal, enlarged four times: idle, the full arc after the first click, a few minutes later, after a second click, with End time, after the hold. The right-click menu in Minimal and Compact during a session: The unknown style reads as Pills, with the rest of the settings kept: Logs: app, run, Accessibility reads idle, running, End time and wave1-base. The other pictures are in the folder. Not covered here: a light menu bar and macOS 27 input (the VM run follows), Reduce Motion, display sleep and a menu bar manager. The arc's timer steps were not timed. In the style-change step the Menu bar pop-up was driven through Accessibility, to keep the Compact panel open. Each step ran once, except the quick click in Minimal, which ran four times. For about 5 s after each start, the bar shows a dim countdown and no ring while the app arms its backstop and runs |
…e read SwiftUI on macOS 27 draws a form's pickers itself. The window has no NSPopUpButton, and the hosting view offers no Accessibility children in the test process, so popUpTitles found nothing and both tests failed on the hosted macOS 27 runner. They now skip there when no pop-up exists and still run, and must pass, on macOS 26.
VM run on macOS 27.0.1 at 751fc19: the three styles on a light menu barEach style draws and works on a light menu bar in a macOS 27.0.1 VM. The build came from
Pills: idle, running, the pills open, a hold partway, after the end. Compact: idle, running, the panel open, the panel during a session, a hold on its ring, after the end. The panel crops take in Control Center's icon and the clock to the right of the eye. Minimal: idle, the arc at the start, about three quarters, about half, right after a click added 4 h, during a hold, after the end. Settings, and a launch with an unknown stored style: Not covered: Minimal draws no progress during a hold, so the "halfway" frame looks like every frame from the press to the end. Display sleep, Reduce Motion and a menu bar manager were not tried. Each step ran once. |
An end from the Pills' hold or the menu's End session sets the controller's end flag but not the panel's isBusy. The manager clears the session before it restores sleep, so a switch to Compact in that wait opened the panel on live pills. They took digits, and Return did nothing, because compactSubmit refuses while an end is pending. Before the session cleared, the panel's Ending... countdown could still be clicked, which opened the pills to add time. The controller now copies its end flag into CompactMenuModel.isEnding. The model's isWaiting decides whether the countdown can be clicked and whether the pills show, so the panel shows Ending... with its countdown and ring disabled until sleep is restored, then the pills. An end from the panel's own ring already set isBusy and looks as before. Setting isBusy in endNow instead would tie the two flags together: a start's completion clears isBusy, and endNow's completion reads it to keep Minimal busy. A new test ends a session from the Pills' hold, the Pills' menu and the panel's ring with the sleep restore held, switches to Compact, and checks the countdown, the keys and the panel's width during the wait and after it. Without the copied flag both Pills ends fail it. The spec says so.
The comment said the countdown observation updated the arc and that no menu bar timer ran with the lid shut. 5726b8b moved the arc to the session's start and end on its own slow timer, which stops only while every display sleeps. A closed lid alone does not stop it, since the lid pauses the manager's countdown but does not mark the displays asleep. The comment now names what recomputes the value: a session change, a style change, a wake and that timer.
The panel took its Ending state from the controller's end flag, which only the controller's own ends set. The timer, the battery and thermal floors, --session end and a reconcile end the session in the manager, so a switch to Compact while sleep was restored showed live pills. Both controller ends also dropped the flag as soon as the manager returned, even when a restore command outlived its timeout and left the end to a retry. The pills came back while sleep restoration went on, and Return failed, because the manager refuses a start while an end is pending. SessionManager now publishes isEnding, true while any end runs or is owed (pendingEnd, now observed), and endIsWaiting, true when the end owed changed nothing and the session is still active: a busy lock, an unreadable state.json or a command holding the lock. The controller observes both and combines isEnding with its own flag into the panel's isEnding, which compactSubmit and compactEnd now check. In the waiting case the panel shows Ending... with its countdown disabled, since time added then would be ended by the retry or refused, and keeps the ring enabled to ask for the end again. Pills and Minimal still read only the controller's flag. Three tests cover a timer end with the restore held, an end whose restore command outlives its timeout from the Pills' hold, the panel's ring and the timer, and a busy lock and an unreadable journal from the timer and the ring. With the controller copying only its own flag, as before, they fail 14, 30 and 16 times. The spec and the release checks say so.
When disablesleep 0 fails but the recovery agent is confirmed armed, the end returns incomplete with the agent armed, the manager drops the end it owed, and the panel shows its pills while the agent restores sleep. The spec and the panel model's comment said Ending… lasts until sleep is restored. They now name that handoff too, as the manager's comment does.
|
The full suite at |
MacBook run on macOS 26.2 at ae56a95: the Compact panel during an endThe Compact panel now shows Ending… with its countdown and ring disabled for an end it did not start, both from the timer and from Setup. The maintainer's MacBook Pro (M5), macOS 26.2 (25C56), on AC power at 100%, with a dark menu bar and the built-in display only. insomnia.log stamps whole seconds, so during each end three things ran side by side: a picture of the status item and its panel every 100 to 135 ms, a read of insomnia.log every 10 ms that noted when each line appeared, and an Accessibility read of the panel's controls every 100 ms.
The timer end, frame by frame:
A full hold on the panel's ring: Logs: run, app, the log watch for the timer end and Not covered: an end that waits (a busy recovery lock, an unreadable state.json or a restore that outlives its timeout), so the long Ending… and the ring that stays enabled to ask again were not seen; a floor or reconcile end; the keys check above; a light menu bar and Reduce Motion. The macOS 27 test skip changes tests only. Each step ran once. |












































The menu bar gets two more styles, chosen in Settings > General > Menu bar. Pills, the bar as it is today, stays the default.
This lands #96 by @Zinoo00 at 35d50f2. The commit is merged here unchanged, so GitHub marks #96 merged when this one merges and its author keeps the credit. #96 was written before #93, #132, #137, #143 and #147 reached main and conflicts with them in six files, so it can't merge as it is.
To @Zinoo00: please don't push to your branch now. A new commit on it isn't in this pull request, so #96 would stay open instead of showing as merged.
The base is
integration/wave1-base: main with #146 and #147 (which carry #145) merged, as main will be once they land. When #147 merges, this pull request's base changes to main and nothing else changes.Conflicts resolved
git show --remerge-diff c7d6f1eshows each one.foregroundparameter stays, with main's 0.25 track opacity. Menu bar: hold-to-end works on macOS 27 and shows on a light bar #93's check of the real button and pointer when the ring closes stays, and Menu bar: Add Compact and Minimal menu bar modes + UX fixes #96'sisEnabledguard joins it.applyInputstays. TheStartProjectioncomment keeps main's wording, which covers extensions (Settings: choose whether the countdown shows the time left or the end time #132).manager.updateConfig. Menu bar: Add Compact and Minimal menu bar modes + UX fixes #96's older preset list and its localbindandsavehelpers go, since Settings: land #104 #147 replaced them.takeFocusbefore Menu bar: Add Compact and Minimal menu bar modes + UX fixes #96'sapplyInput. The pending start keeps Settings: choose whether the countdown shows the time left or the end time #132's style. The right-click menu keeps Menu bar: Add Compact and Minimal menu bar modes + UX fixes #96's split and Compact close, Fix keyboard focus when entering session duration #143's focus return, and the End session item from Add an explicit End session action to the menu #74 and Menu bar: an option to show the eye without the countdown #84.SessionRunningControlsreplaces the inline countdown and ring and gets Menu bar: hold-to-end works on macOS 27 and shows on a light bar #93's button and pointer readers. The eye keeps Menu bar: an option to show the eye without the countdown #84's time left for VoiceOver and adds Menu bar: Add Compact and Minimal menu bar modes + UX fixes #96's button trait and help text.Maintainer commits
124cc50: builds the merge against main: End session in Menu bar: Add Compact and Minimal menu bar modes + UX fixes #96's menu, Menu bar: hold-to-end works on macOS 27 and shows on a light bar #93's readers for the Pills ring, and main's unanimated countdown in place of Menu bar: Add Compact and Minimal menu bar modes + UX fixes #96's rolling digits (animating the status item cost about 46% of a core).8f1ddb9: plain deinits in place ofisolated deinitin Minimal's press view and the Compact panel, the macOS 26.2 runtime fault main already avoids.8e53e55: the end ring defaults to the label colour again, so it shows on a light menu bar.d6dc37b: the default style is named Pills and stored as"pills". A missing or unknown stored value reads as Pills and keeps the rest of config.json; Menu bar: Add Compact and Minimal menu bar modes + UX fixes #96's decoder threw on one, which made the whole file unreadable.0283156: the style picker is the first row of General, with one line on what the chosen style does.fba6d63: Compact's panel opens on the Default length from Settings.8f4b200: the Compact panel takes keyboard focus when it opens and gives it back to the app you were in when it closes, as Fix keyboard focus when entering session duration #143 does for the pills.1b7ad21: Minimal's hold and the Compact panel's ring check that the button is still down and the pointer stayed before they end a session, as Menu bar: hold-to-end works on macOS 27 and shows on a light bar #93 does for the Pills ring. A new press clears a press whose release never arrived.5726b8b: Minimal's arc reads the session's start and end on its own slow timer (every hundredth of the session, 2 to 60 s), which stops while the displays sleep. It drains in one step; only added time animates.ea3532b: a test that the Compact panel's countdown changes in one step.be954e2: the right-click menu shows the time left in Compact and Minimal, which show no countdown in the bar.a2562a5,99a9eff: docs/spec.md and docs/releasing.md describe the three styles and their real-Mac checks.934c5a8: with the end-time countdown, VoiceOver and Minimal's status say "ends at 14:30", not "14:30 remaining".a635fe2: the slots test waits for the slots to leave and the width write instead of fixed times.969d24d: in Minimal and on the Compact ring, a release that arrives 0.6 s or more after the press, before the hold check has run, ends nothing and adds no time. Menu bar: Add Compact and Minimal menu bar modes + UX fixes #96 ended the session there, which on macOS 27 let a click whose release arrived late end a session.751fc19: the release checks say so.9f0f89a: the two Settings tests that read the menus' order skip on macOS 27, where SwiftUI draws a form's pickers without anNSPopUpButtonto read. They still run on macOS 26.89bbe09,6e6ebbf: the Compact panel reads the session manager's own end state, so every end shows as Ending… with the countdown and ring disabled and no keys taken, until sleep is restored: one from the Pills before a switch to Compact, the timer, a battery or thermal floor,--session end, a reconcile, and a restore command that outlives its timeout. When an end changed nothing and the session goes on (a busy lock, an unreadable state.json), the ring stays enabled to ask for the end again. Before, a switch to Compact during a restore showed live pills whose Return did nothing.d3ce52c,ae56a95: a comment on what redraws Minimal's arc, and the spec says an end handed to the recovery agent also ends Ending….Each behavior change has a test that fails without it.
Left for the owner
The safer or existing behavior is in place for each.
969d24d). Menu bar: Add Compact and Minimal menu bar modes + UX fixes #96's rule, where it ended the session, is the other choice.compactModekey stays, though no release wrote that key.Follow-up, not in this pull request: Pills, as on main, and Minimal still read only the menu bar's own end flag. During an end the app started itself, a click opens the pills or sends a Minimal start, which the app queues behind the end or refuses with "still being ended". A small pull request can give them the same end state as Compact.
Tests
The full suite at this head,
ae56a95: 1522 tests, 0 failures, on macOS 26.2 on the maintainer's MacBook. Each new test fails without its fix. The release build with warnings as errors is clean. Hosted CI runs on macOS 26 and 27.Review
Clean at
ae56a95. The earlier rounds: round 1 found the VoiceOver wording, the slots test's fixed waits and the late release; round 2 found the release checks still described the old rule; round 3 was clean at c81856d (the same tree as751fc19).9f0f89a, the macOS 27 skip, had its own review with no findings. Greptile's two findings at9f0f89a(the Compact panel during a restore, a stale comment) were fixed in89bbe09andd3ce52c; the next review found that fix missed ends the app starts itself and a restore command that outlives its timeout (6e6ebbf), then one spec sentence (ae56a95).Evidence
ae56a95: the Compact panel shows Ending… for a timer end and for--session end, then the pills; Pills, Compact and Minimal as before.c81856dand a macOS 27 VM at751fc19with a light menu bar, both the same tree as before the end-state fix: each style idle, running, the Compact panel, Minimal's clicks and hold, style changes during a session, and an unknown stored style reading as Pills. Accessibility can't read the status item's label in any style, on main too; that fix belongs in its own pull request.

Confidence Score: 5/5
The changes appear safe to merge, with no new actionable issues found.Summary
Adds Compact and Minimal menu bar styles while keeping Pills as the default and retaining the same status item.
No new actionable issues found. macOS tests and visual checks were not run in this Linux environment.
Reviews (4) · Last reviewed commit: "Say that an end handed to the recovery a..." · Reviewed by Greptile