Skip to content

Menu bar styles: land #96 (Compact and Minimal) - #152

Open
krishhgg wants to merge 28 commits into
integration/wave1-basefrom
integration/menu-bar-styles
Open

krishhgg wants to merge 28 commits into
integration/wave1-basefrom
integration/menu-bar-styles

Conversation

@krishhgg

@krishhgg krishhgg commented Oct 27, 2026 •

Copy link
Copy Markdown
Owner

The menu bar gets two more styles, chosen in Settings > General > Menu bar. Pills, the bar as it is today, stays the default.

  • Compact shows only the eye. A click opens the pills in a panel under it, with the countdown and the end ring.
  • Minimal shows the eye with an arc for the time left. A click adds the default length, and a 0.6 s hold ends the session.

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 c7d6f1e shows each one.

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 of isolated deinit in 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 an NSPopUpButton to 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.

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 as 751fc19). 9f0f89a, the macOS 27 skip, had its own review with no findings. Greptile's two findings at 9f0f89a (the Compact panel during a restore, a stale comment) were fixed in 89bbe09 and d3ce52c; 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

  • MacBook, macOS 26.2, at ae56a95: the Compact panel shows Ending… for a timer end and for --session end, then the pills; Pills, Compact and Minimal as before.
  • MacBook, macOS 26.2, at c81856d and a macOS 27 VM at 751fc19 with 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.

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

  • Settings switch the menu bar between Pills, Compact, and Minimal.
  • Compact opens a panel under the eye for session controls.
  • Minimal starts sessions with a click and shows time left in an arc.
  • Pills unfold smoothly while the menu bar keeps the eye in place.

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

Zinoo00 and others added 23 commits October 12, 2026 19:46
#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 compile fixes after the merge dropped #96's rolling digits and its
0.18 s spring, since each animated frame re-renders the status item. The
base test covers the countdown in the menu bar. This covers the same
controls in the Compact panel; with #96's transition put back, both
tests fail.
#84 and #132 put the time left in the right-click menu whenever a session
runs and the bar does not show it. Compact and Minimal never show a
countdown in the bar, so the same rule now covers them. The Pills style
is unchanged.
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.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 27, 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-27T10:42:03.763077Z 751fc19 PR opened
🔒 Security Review ✅ Completed 2026-10-27T10:43:56.003680Z 751fc19 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.

Comment thread Sources/Insomnia/UI/MenuBarModel.swift Resolved
@krishhgg

Copy link
Copy Markdown
Owner Author

MacBook run on macOS 26.2 at c81856d: Pills, Compact and Minimal

The 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 751fc19, which has the same tree as c81856d: only commit messages changed (git rev-parse c81856d^{tree} 751fc19^{tree} prints e8207d27f8e2 twice).

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. c81856d installed from a clean checkout with scripts/install.sh, starting from default settings. install.sh exited 0 and /etc/sudoers.d/insomnia held exactly the four expected pmset lines. The run was unattended, so install.sh's own password prompt was not exercised. The first launch wrote menuBarMode: "pills". Each style was picked in Settings > General > Menu bar.

Step Result
Pills The picker line reads "Duration pills and the countdown beside the eye." Idle, the pills, a 2 h session (the eye, the countdown and the ring, 120 pt), a hold and the end look and log the same as on wave1-base
Compact, panel "Only the eye. A click opens the pills in a panel." A click opens the panel 2 pt under the bar with its left edge under the eye, on Hours (the Default field), focused
Compact, start 2 in Hours, then Return, starts a 2 h session. The panel stays open with the countdown and the ring; the bar keeps the eye alone
Compact, ring A 0.3 s hold fills the ring about halfway and runs back, and a quick click does nothing: no log line, the same end time. A 0.65 s hold shows "Ending…" and ends the session; the panel shows the pills again
Compact, focus With an empty TextEdit document in front, a click on the eye brings Insomnia forward and a typed 3 lands in the panel, not the document. Esc closes the panel and TextEdit is in front again, and a typed k reaches the document. A second click on the eye closes the panel the same way, and the typed draft survives a close
Minimal, Default "A ring for the time left. Click adds, hold ends." Default session length becomes a text field; 30m and Return save defaultPreset: 1800
Minimal, clicks One click starts a 30 minute session with the full arc around a smaller eye. A second click logs "session extended by 1800s". Three more quick clicks each add 30 minutes, and a release at 0.4 s counts as a click. None ends the session
Minimal, slides A 16 pt slide released at about 0.26 s and slides of 16 and 14 pt held past 1 s log nothing and leave the end time alone
Minimal, hold A 0.65 s hold ends the session with no "session extended" line. With Countdown set to End time the arc still shows
Right-click menu In Compact and Minimal during a session it starts with "Time left: 1h 59m" or "Time left: 2h 55m", then the status lines, End session, Settings… and Quit Insomnia
Style changes During one 2 h session: Pills, Compact, Minimal, Pills. The end time stays the same and no session line is logged. Insomnia has one menu bar item at every step, 120, 34, 34 and 120 pt wide. Picking Minimal with the Compact panel open closes the panel
Unknown style With the app quit, menuBarMode set to "bogus". After a launch the idle eye opens the pills, Settings shows Pills, the other settings are unchanged (Default session length 30 minutes), and the launch leaves config.json as it was
Accessibility Fail, also on main. See below

Accessibility finding

The 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 (401b6cb, main with #146 and #147 merged) shows the same. The item is an AXMenuBarItem with subrole AXMenuExtra and role description "status menu". Its AXTitle is empty, it has no AXDescription, AXValue or AXHelp, and it has no children. A hit test at the eye, from the app and from the whole system, returns that same item. Its only action is AXPress.

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 .accessibilityLabel, the value and the actions sits in an NSHostingView added as a subview of the status bar button, and the button gets an empty title (StatusItemController.swift lines 363 to 364 here, 125 to 127 on main). AppKit presents the button to Accessibility as a single element, so the subview is never reached. Main has the same setup, so this isn't a regression in this pull request. The fix belongs in its own pull request against main.

Settings, General: Menu bar Pills, Duration pills and the countdown beside the eye.

Menu bar Compact: Only the eye. A click opens the pills in a panel.

Menu bar Minimal: A ring for the time left. Click adds, hold ends. Default session length as a text field, 30m

Pills: idle, the pills with 2 typed, running, a hold partway, after the end.

Pills idle: the eye alone

The pills open with 2 in Hours

Pills running: the eye, 1:59:34 and the end ring

A hold on the end ring, partway

After the hold: the eye alone

Compact: the panel open, typed, running, a hold on its ring partway, after the end.

Compact: a click on the eye opens the pills in a panel under it, on Hours

The panel with 2 in Hours

The panel during the session: 1:59:52 and the end ring, with the eye alone in the bar

A 0.3 s hold on the panel's ring, partway

After a full hold the panel shows the pills again

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.

Six Minimal frames, enlarged: the idle eye, the full arc, a slightly shorter arc, the arc grown back, the arc with End time, the idle eye after the hold

The right-click menu in Minimal and Compact during a session:

Minimal's menu: Time left 2h 55m, the status lines, End session, Settings and Quit Insomnia

Compact's menu: Time left 1h 59m and the same rows

The unknown style reads as Pills, with the rest of the settings kept:

After a launch with menuBarMode bogus: Menu bar Pills, Default session length 30 minutes

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 pmset. Every build in this run shows the same gap, wave1-base included.

…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.
@krishhgg

Copy link
Copy Markdown
Owner Author

VM run on macOS 27.0.1 at 751fc19: the three styles on a light menu bar

Each style draws and works on a light menu bar in a macOS 27.0.1 VM. The build came from 751fc19 with its own scripts/install.sh, starting from default settings. 9f0f89a, after it, changes only tests.

Step Result
Pills Idle, a 2 h session (1:59:38 and the end ring), the pills open, a hold on the ring about halfway, and the eye alone after the hold. The log shows "session end (user)" and "sleep restored"
Compact The eye alone, idle and running. A click opens the panel on Hours; 2 and Return start a 2 h session, and the panel shrinks to the countdown and the ring. A hold on the panel's ring ends the session, and the panel shows the pills again
Minimal The arc drains during a session. A click adds the default length ("session extended by 14400s") and the arc grows back. A 0.6 s hold ends the session; the eye dims at the press and closes after the end
Settings The picker shows Pills, Compact and Minimal, each with its line
Unknown style With "menuBarMode": "zzz" and "defaultPreset": 7200, General shows Pills and a 2 hour default. The app leaves "zzz" in config.json, as the spec says

Pills: idle, running, the pills open, a hold partway, after the end.

Pills idle on a light menu bar: the closed eye

Pills running: the eye, 1:59:38 and the end ring

The Days, Hours and Minutes pills open during the session

A hold on the end ring, the ring about half drawn

After the hold: the closed eye

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.

Compact idle: the closed eye

Compact running: the open eye alone

The panel open while idle: Days, Hours and Minutes, Hours focused

The panel during a session: 1:59:46 and the end ring

A hold on the panel's ring, about half drawn

After the hold: the panel shows the pills again

A click on the eye opens the panel; Hours, 2 and Return start a 2 hour session

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.

Minimal idle: the closed eye

The full arc just after a start

The arc about three quarters full

The arc about half full

The arc almost full again after a click added 4 hours

During a hold: the eye and arc dimmed

After the hold: the closed eye

A hold from the press to the session's end: the eye dims, then closes

Settings, and a launch with an unknown stored style:

Menu bar Pills: Duration pills and the countdown beside the eye.

Menu bar Compact: Only the eye. A click opens the pills in a panel.

Menu bar Minimal: A ring for the time left. Click adds, hold ends.

After a launch with menuBarMode zzz and defaultPreset 7200: Menu bar Pills, Default session length 2 hours

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.
@krishhgg

Copy link
Copy Markdown
Owner Author

The full suite at ae56a95, this head, passes on macOS 26.2 on the maintainer's MacBook: 1522 tests, 0 failures, in 34 minutes.

@krishhgg

Copy link
Copy Markdown
Owner Author

MacBook run on macOS 26.2 at ae56a95: the Compact panel during an end

The Compact panel now shows Ending… with its countdown and ring disabled for an end it did not start, both from the timer and from Insomnia --session end. Then it shows the pills, sleep is restored, and a typed length with Return starts a new session. Pills, Compact and Minimal still behave as they did at c81856d. Nothing failed.

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. ae56a95 installed from a clean checkout with scripts/install.sh, starting from default settings. install.sh exited 0 and /etc/sudoers.d/insomnia held exactly the four expected pmset lines. The run was unattended, so install.sh's own password prompt was not exercised.

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.

Step Result
Timer end A 60 s session from --session start 60, with the panel open. The frame at 12:17:22.100Z shows Ending… in grey with a grey ring, and Accessibility at 22.105Z reads "Time remaining: Ending…" and End session, both disabled. "session end (timer)" appeared at 22.116Z and "sleep restored" at 22.140Z. The pills show from 22.349Z, and the panel stays open
After it SleepDisabled 0 and session.json gone. 2 and Return start a 2 h session from the panel
--session end Sent at 12:19:16.713Z; it exits 0 and prints "no session is running". "session end (user)" appeared at 16.730Z and "sleep restored" at 16.754Z. The frame at 16.797Z shows Ending… in grey, and the pills show from 17.041Z. Accessibility's polls fell on either side of Ending… this time, so only the picture shows it
Keys during an end Skipped. Ending… lasted about 24 ms between "session end" and "sleep restored" in both ends, far too short to type into. testThePanelShowsEveryEndAsEndingUntilSleepIsRestored presses 4 during each end and checks that no digit lands
Pills Idle eye, the pills on Hours, 2 and Return start a 2 h session (eye, countdown and ring, 120 pt), a 1.5 s hold ends it. Same as at c81856d
Compact A quick click on the ring logs nothing and keeps the end time. A 0.65 s hold shows Ending… with the ring full (12:20:55.060Z), then the pills from 55.312Z; "session end (user)" and "sleep restored" are 13 ms apart
Minimal Default session length is a text field; 30m saves defaultPreset: 1800. One click starts a 30 minute session with the full arc, and a 0.65 s hold ends it with no "session extended" line
End state No session, SleepDisabled 0, Low Power Mode as before the run, no process left stopped

The timer end, frame by frame:

Eleven frames from 12:17:21.481Z to 22.720Z: the panel at 00:01, then Ending… in grey with a grey ring at 22.100Z, a crossfade, then the pills

--session end, frame by frame:

Ten frames from 12:19:16.310Z to 17.412Z: the panel at 1:59:41, Ending… in grey at 16.797Z, a crossfade, then the pills

A full hold on the panel's ring:

Twelve frames: the ring fills over half a second, Ending… with the full ring at 12:20:55.060Z, then the pills

Settings, Menu bar Compact: Only the eye. A click opens the pills in a panel.

Menu bar Minimal, Default session length as a text field, 30m

Logs: run, app, the log watch for the timer end and --session end, the Accessibility reads for the timer end and --session end, and install. The other pictures are in the folder.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants