Skip to content

feat(widget): free placement for the compact widget - #159

Merged
hiskudin merged 8 commits into
StackOneHQ:mainfrom
faisalreza-stackone:feat/widget-free-placement
Aug 21, 2026
Merged

feat(widget): free placement for the compact widget#159
hiskudin merged 8 commits into
StackOneHQ:mainfrom
faisalreza-stackone:feat/widget-free-placement

Conversation

@faisalreza-stackone

Copy link
Copy Markdown
Contributor

What

Adds an opt-in "free placement" mode for the compact widget (the always-on pill), which until now could only sit at one of four screen corners.

A new Snap to corners toggle in Settings → Widget drives it. It defaults to On, so existing users see no change. Turn it off and the pill stays wherever you drop it.

Behaviour

  • Snap on (default): unchanged. Releasing a drag animates the pill to the nearest corner and persists that corner.
  • Snap off: the pill stays where you drop it. Its position is saved and restored across relaunches.
  • Own display: a free-placed pill stays on the screen you left it on, not whichever screen the cursor happens to be on.
  • Dock level: the pill clamps into the screen's full frame rather than the Dock-excluded area, so it can rest down at Dock level. The top stays capped just below the menu bar, and nothing hangs off the physical screen.
  • Re-enabling snap sends the pill to the corner nearest its last free spot. Toggling snap off leaves it where it currently rests.

Settings

New Snap to corners toggle. The existing Widget corner row dims while snap is off, since it has no effect in free mode.

Config

  • STACKNUDGE_COMPACT_SNAP (true/false, default true)
  • STACKNUDGE_COMPACT_POS (x,y, the saved free origin)

STACKNUDGE_COMPACT_CORNER is unchanged and remains the source of truth while snap is on.

Scope

The pill widget only. Notification banners are macOS notifications positioned by the system, and the full expanded panel keeps its own position handling. Neither is touched.

Testing

  • Unit tests for the new geometry and persistence helpers (CompactPlacement): nearest-corner selection, clamping, screen selection, placement bounds, and position parse/format.
  • Full swift test suite passes.
  • Built and exercised in the live app: drag to free-place, Dock-level placement, snap on/off transitions, and multi-monitor placement.

🤖 Generated with Claude Code

faisalreza-stackoneand others added 7 commits August 20, 2026 16:15
In free-placement mode, clamp the pill into a placement region that runs
the full screen width and down to the physical bottom (so it can sit over
the Dock), but is capped just below the menu bar at the top. Nothing hangs
off-screen, yet the pill can rest at Dock level.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@StuBehan
StuBehan requested review from StuBehan and hiskudin and a lite review from Copilot and removed request for StuBehan and CopilotAugust 20, 2026 16:20

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

Read the full diff in context, checked out the branch, and built it locally (make build clean; CI green across all six checks).

Overall: solid. Extracting the geometry into a pure CompactPlacement enum is the right call, and the tests are genuine — degenerate-frame clamp, malformed parse, and frame-origin offset are all covered. Default-on preserves existing behaviour. .snapToCorners is correctly wired into settingsRows, applyCycle, and selectedRowRespondsToArrows, so no half-wired row. Nice catch passing snap into handleSnapModeChange as a parameter rather than re-reading it — see the first inline comment for why that mattered.

Four things worth addressing; the two marked (1) and (2) are the ones I'd want fixed before merge. Details inline.

Also: README not updated

Not anchorable to the diff, so noting it here. README.md:210 still describes the widget settings as "widget (corner, mascot picker, opacity)", and neither STACKNUDGE_COMPACT_SNAP nor STACKNUDGE_COMPACT_POS appears anywhere in the docs — the config table around README.md:248 is where the other user-facing keys live. Repo convention is to keep it current; the last panel PR updated README.md:189 for a smaller behaviour change than this one.

Comment threadpanel/Panel.swift Outdated
Comment threadpanel/CompactPlacement.swift
Comment threadpanel/Panel.swift Outdated
Comment threadpanel/Panel.swift Outdated
Comment threadpanel/CompactPlacement.swift
Comment threadTests/StackNudgePanelCoreTests/CompactPlacementTests.swift
Comment threadpanel/Settings.swift Outdated
- applyCompactLayout takes an optional `snap` override so the $compactSnap
sink (which runs during the @published willSet, when the property is
still stale) lays out with the correct value instead of the inverted one.
- parsePosition rejects non-finite values (NaN/Inf) so a hand-edited
STACKNUDGE_COMPACT_POS can't flow through clamp into setFrame.
- Snap-off seeds the free origin from the panel's own screen rather than
the cursor's, so keyboard-driven toggling on multi-monitor keeps the
widget on its display (cornerOrigin now takes an optional screen).
- Document + test the nearestCorner midX/midY tie-break; add a
placementBounds test for a display below the main one (negative origin).
- Re-align the Settings Widget rows after the new toggle.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@faisalreza-stackone

Copy link
Copy Markdown
ContributorAuthor

On the README note: intentionally leaving the docs unchanged in this PR (author's call). Flagging so it's a conscious decision rather than an oversight — the widget-settings line at README.md:210 and the two new keys (STACKNUDGE_COMPACT_SNAP, STACKNUDGE_COMPACT_POS) can follow in a separate docs pass if preferred.

@hiskudin
hiskudin merged commit ee68ceb into StackOneHQ:mainAug 21, 2026
6 checks passed
Sign up for freeto 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

@faisalreza-stackone@hiskudin