Skip to content

feat: compact pill widget with mascots, ring gauge, drag-snap#65

Merged
hiskudin merged 4 commits into
mainfrom
feat/compact-mode-and-animations
Jun 1, 2026
Merged

feat: compact pill widget with mascots, ring gauge, drag-snap#65
hiskudin merged 4 commits into
mainfrom
feat/compact-mode-and-animations

Conversation

@hiskudin

@hiskudin hiskudin commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator

Adds a pinned glance widget that lives at a screen corner above all spaces. Always-on (no toggle), expand to full panel via button / double-click / hotkey / M, collapse back via Esc / M / hotkey. Built up over several iterations of design polish and bug fixes.

What's in the pill

  • Concentric quota ring: outer = 7d, inner = 5h, angular gradient stroke (cyan → yellow → orange → red), inner glow pulses on busy, orbital spinner dot while polling, animated sweep on value changes, digital % in the center. Outer soft halo + bigger ring (42×42, 4pt stroke) for prominence.
  • 5-hour reset countdown next to the gauge.
  • Animated mascot with four choices via Settings → Widget → Mascot: Robot, Cat (single-path silhouette with attached ears), Sentinel (HAL-style scanning lens), Ghost (floaty bob, wavy bottom). Each has per-state expressions for idle / watching / busy / alert / happy plus a blink cycle.
  • Headline that adapts:
    • Busy session → project name + token count.
    • Recent event + queue=1 → event title + live relative time.
    • Recent event + queue>1 → event title + ×N.
    • Idle sessions → most-recent session name.
    • Nothing → "watching" placeholder.
  • Session count badge with busy/idle color before the expand button.

Polish & behavior

  • Drag to reposition + snap — drag the pill anywhere via isMovableByWindowBackground, on mouse-up it animates to the nearest screen corner. Uses an explicit .leftMouseUp NSEvent monitor so pausing mid-drag doesn't false-trigger a snap.
  • Animation pausing during drag — three nested TimelineViews were competing with AppKit's drag handler on the main thread. Track drag state via the mouse-event monitor and render static variants of the pill background / gauge / mascot during a drag.
  • Border glow tracks quota urgency — cyan under 75%, amber 75–90%, red 90%+ with progressively faster pulse rate.
  • New-event ripple — soft urgency-colored capsule expands and fades outward on event arrival.
  • Always-on: Compact widget toggle was removed (its only role was gating the other rows). The pill is the new default UX. Settings rows shift accordingly.

Bug fixes that emerged from the work

  • contentMinSize re-applied after lock/unlock / monitor disconnect inflated the panel to ~1100×500 while SwiftUI still rendered the pill content inside it. Lower contentMinSize to the widget size while compact; restore the 560×260 floor on exit.
  • Panel size persistence corrupted by widget sizedidResize was saving the compact size as the user's panel size. Skip persistence while in the widget state.
  • Drag-snap loop between original and target corner — nav.compactCorner = corner fires Combine → applyCompactLayout → setFrame → didMoveNotification, which scheduled another snap. Tight ignoringProgrammaticMove flag around each programmatic setFrame swallows those move events.
  • Cat mascot looked like a blob — ears were floating triangles off the head; redesigned as a single-path CatHeadShape with attached ears, inner ear hints, a small nose, and tighter whiskers.

UI consistency

  • Footer key labels uppercased: r → R, p → P, n → N, esc → Esc across Events / Sessions / Usage / Phrases / Settings / Updater / Bootstrap.

Settings additions

Widget
  Widget corner (cycle, TL/TR/BL/BR)
  Mascot        (cycle, Robot/Cat/Sentinel/Ghost)

Plus a new M shortcut from Events/Sessions/Usage tabs that collapses to the pill; and M from the pill (when it has key focus via a click) that expands to the full panel.

Test plan

  • Compact pill visible across all spaces, including fullscreen apps.
  • Drag and release — snaps to nearest corner cleanly, no loop, no false triggers when pausing mid-drag.
  • Lock/unlock and disconnect external monitor — pill stays at compact size.
  • Border glow shifts color and pulse rate with 5h quota.
  • Ring chart fills correctly when busy session pushes 5h usage up.
  • Mascot reflects state (busy session → focused slits, recent stop → smile, recent permission → alert).
  • All four mascots render and animate; switching in Settings updates immediately.
  • M expands from pill (with focus) / collapses from full panel.
  • Single event headline shows live age ticker; multi-event headline shows ×N and doesn't wrap.

🤖 Generated with Claude Code

hiskudin and others added 4 commits June 1, 2026 10:48
Adds an opt-in glance widget that lives pinned to a screen corner above
all spaces. Toggle via Settings -> Widget -> Compact widget, or press
"M" from Events/Sessions/Usage tabs. Expand button on the pill (or
double-click) exits compact mode persistently; hotkey toggles
expand/collapse while in compact mode.

Layout: concentric quota ring on the left (5h inner / 7d outer with
gradient stroke + pulsing inner glow + orbital polling spinner), reset
countdown, a tiny vector bot mascot whose expression reflects state
(idle / watching / busy / alert / happy), dynamic headline showing
project name + token count or last event, session count badge, expand
button.

Polish: border glow shifts color and pulse rate with quota urgency
(cyan -> orange -> red). Ripple capsule animation on every new event.
Drag-to-reposition via window-background drag with snap-to-nearest-
corner on release. Snap uses a NSEvent .leftMouseUp local monitor for
an unambiguous end-of-drag signal so pausing mid-drag doesn't false-
trigger a snap.

Other changes:
- Hotkey/escape/hide funnel through hidePanel which collapses to the
  pill instead of hiding outright while compact mode is on.
- Frame-size persistence skips the compact widget size so the saved
  full-panel size sticks across mode flips.
- Settings layout shifts Sound/Voice/Usage/Actions down by 2 rows to
  fit the new Widget section.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three nested TimelineViews running at 0.05s each (pill background pulse
+ glow, quota gauge pulse + spinner, mascot blink) competed with
AppKit's window-background drag handler on the main thread, producing
visible lag while dragging. Track drag state via the existing
.leftMouseDown/.leftMouseUp event monitor and render static variants
of all three subviews while compactDragging is true. Animations
resume on mouse-up.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…MinSize fix

- Quota gauge 32x32 -> 42x42, ring stroke 2.8 -> 4pt, center number
  9 -> 14pt semibold, punchier cyan, brighter inner glow, soft outer
  halo behind the rings. Pill height 46 -> 56 to fit.
- Bot mascot 18x16 -> 26x24, head/eyes/antenna/mouth scaled
  proportionally so the character is readable at a glance.
- Headline adapts: shows pending count when the queue has >1 event,
  otherwise the live relative timestamp of the latest event. Single
  events still show 'X sec ago' for recency; backlog shows 'N pending'.
- Drop the panel's contentMinSize to the compact widget size while
  compact, restore the original 560x260 floor on exit. AppKit was
  re-applying the original minimum after system events (lock/unlock,
  screen reconfiguration), inflating the panel to ~1100x500 while
  SwiftUI still rendered the compact pill content inside it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…fix overflow

- Add MascotKind enum (robot, cat, eye, ghost) selectable via new
  Settings -> Widget -> Mascot row. Each mascot ships its own SwiftUI
  drawing with per-state expressions (idle/watching/busy/alert/happy)
  and animation logic; the cat uses a single-path CatHeadShape so the
  ears feel attached to the face.
- Compact mode is always-on now. Drop the Compact widget toggle from
  Settings (its only role was gating the other widget rows). Both the
  expand button and double-click expand to the full panel temporarily;
  M / Esc / hotkey collapse back. Settings rows shift down by 1.
- M from the pill (when it has key focus) expands to the full panel,
  mirroring the M-to-collapse footer shortcut shown across the tabs.
- Uppercase footer-hint key labels across all tabs (r -> R, p -> P,
  n -> N, esc -> Esc) for visual consistency with the existing M, S
  hints.
- Fix headline overflow when the queue has >1 event: 'N pending' was
  wrapping at the space. Switch to 'xN' format with .lineLimit(1) +
  .fixedSize().

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@hiskudin hiskudin merged commit 55af219 into main Jun 1, 2026
5 checks passed
@hiskudin hiskudin deleted the feat/compact-mode-and-animations branch June 1, 2026 15:50
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.

1 participant