Skip to content

fix(widget): turning the widget on no longer squeezes the panel into a pill - #228

Merged
adefreitas merged 2 commits into
StackOneHQ:mainfrom
adefreitas:fix/compact-layout-race
Oct 30, 2026
Merged

adefreitas merged 2 commits into
StackOneHQ:mainfrom
adefreitas:fix/compact-layout-race

Conversation

@adefreitas

@adefreitas adefreitas commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Turning Widget on in Settings could leave the panel squeezed into a pill-sized window around the full panel, after which the hotkey toggled from the broken state. The two layout sinks now run on the next run-loop turn.

Changes

  • nav.$compactMode and nav.$compactExpanded sinks in PanelController call applyCompactLayout() through DispatchQueue.main.async. @Published sinks fire in willSet, and the Widget toggle sets compactMode and then compactExpanded, so the second sink laid out pill mode against the stale expanded flag.
  • Callers that set a flag and call applyCompactLayout() themselves are unaffected.
  • No unit test: the sinks live in PanelController's setup and need a live NSPanel. Verified by hand (below).

Testing

Automated: ./build.sh (no new warnings), make test-without-xcode (1447 tests, 0 failures), make typecheck-tests.

Manual, after make reload:

  1. In Settings, switch Widget off, then on again.
  2. The panel stays open at full size, with a normal frame and no pill-sized window.
  3. Press the hotkey twice. It hides, then shows the full panel.
  4. Click the pill (after collapsing it with M) and confirm it expands to the full panel.

Related issues

None.

🤖 Generated with Claude Code


Summary by cubic

Fixes the widget toggle squeezing the panel into a pill-sized window.

The Widget toggle sets compactMode and then compactExpanded, and both layout sinks ran inline in @Published's willSet, so the second sink laid out pill mode against the stale expanded flag. Both sinks now run on the next run-loop turn, once the values have landed. Callers that set a flag and call applyCompactLayout() themselves are unaffected.

Written for commit 0edc9fd. Summary will update on new commits.

View guided diff

…a pill

The Widget toggle sets compactMode and then compactExpanded, and both layout
sinks ran inline in @published's willSet. The second therefore laid out pill
mode against the stale expanded flag: a pill-sized window around the full
panel, after which the hotkey toggled from the broken state. Both sinks now
lay out on the next run-loop turn, once the values have landed. Callers that
set the flag and lay out explicitly are unaffected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@StuBehan StuBehan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The comment in handlePostUpdateStatus says applyCompactLayout already ran via the Combine sink, which isn't true now the sink waits a turn. The explicit call under it is the only layout before the mode flip, so could the comment say that?

@hiskudin hiskudin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review by Claude, from Hisku's session.

LGTM. The diagnosis is right: @Published sinks fire in willSet, so the second sink laid out against the stale flag, and deferring a turn fixes it.

Optional: .receive(on: DispatchQueue.main) before .sink is the usual Combine spelling of the same deferral. Callers that set a flag and then call applyCompactLayout() themselves now get a second, deferred pass, which looks harmless since layout is idempotent.

- Reword the handlePostUpdateStatus comment: the sinks now lay out a
  turn later, so the explicit applyCompactLayout() is the only layout
  before the mode flip.
- Defer the compact-layout sinks with .receive(on: DispatchQueue.main)
  instead of wrapping the call in DispatchQueue.main.async.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@adefreitas

Copy link
Copy Markdown
Collaborator Author

Reworded the handlePostUpdateStatus comment so it says the explicit applyCompactLayout() is the only layout before the mode flip, and switched both sinks to .receive(on: DispatchQueue.main), 215ab77. The extra deferred pass for callers that lay out themselves is intended: handlePostUpdateStatus relies on its own synchronous call for ordering, and the later pass is a no-op.

@adefreitas
adefreitas merged commit cf6d472 into StackOneHQ:main Oct 30, 2026
7 checks passed
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.

3 participants