Skip to content

Phase 1: foundations + XPC + permission lifecycle (M0–M3) - #1

Merged
thanhhaudev merged 29 commits into
masterfrom
feature/phase-1
May 17, 2026
Merged

thanhhaudev merged 29 commits into
masterfrom
feature/phase-1

Conversation

@thanhhaudev

Copy link
Copy Markdown
Owner

Implementation of Phase 1 per docs/superpowers/plans/2026-05-17-sleev-implementation-plan.md (local-only). M0.1 scaffold; M0.2 source stubs; M0.3 XPC contract; M0.4 XPC ping + agent launchd registration (M2.3 merged); M0.5 CI. Subsequent commits will land M1–M3.

Also fix LD_RUNPATH_SEARCH_PATHS in project.yml: embedded LoginItem
executable is 5 levels deep from Sleev.app/Contents/Frameworks, not 4.
Add GitHub Actions CI on macos-14 running swiftlint --strict,
swiftformat --lint, xcodegen, xcodebuild build + test for every push
or PR targeting master/main. Fix swiftformat violations in source
files and extend .swiftformat with --exclude for internal/ examples.
- Extract SleeveGlyph helper (proportionally-scaled dots + triangle)
- StatusBarController draws custom NSImage via SleeveGlyph instead of SF Symbol chevron; isTemplate = true for system menubar theming
- OnboardingViewController: 520×420, NSVisualEffectView background, SF typography hierarchy (22pt semibold title, 13pt secondary body), accent-colored logo, primary blue Open System Settings button
- OnboardingWindowController: content rect updated to 520×420
… vibrancy

- SleeveGlyph: add naturalSize(forHeight:) helper so callers derive width
  from intrinsic aspect ratio, eliminating the negative-startX clip bug
- SleeveGlyph: add image(height:) convenience that uses naturalSize
- StatusBarController: switch to image(height:14) signature
- OnboardingViewController: use image(height:60) for logo (no clipping);
  enable body label multi-line wrapping; center vstack vertically;
  align button stack centerX; constrain vstack top >= 56pt for traffic lights;
  wire NSVisualEffectView with Auto Layout pinned to all edges, state=.active
- OnboardingWindowController: shrink window to 520×380; add .fullSizeContentView,
  transparent titlebar, hidden title, clear/non-opaque background for vibrancy
…ht buttons

- Logo shrunk from 60pt to 36pt for a less-hero appearance
- Title bumped from 22pt semibold to 26pt bold for clearer hierarchy
- Body label switched to left-alignment in a fixed 400pt column
- Button row extracted from vstack and pinned to bottom-right (24pt insets)
- Window narrowed from 520×380 to 500×340 to match tighter layout
Adds a subtle rounded-rect pill (5pt radius, 0.5pt border at 10% white
alpha, 6% white fill, 7pt × 2pt padding) around the status-bar glyph.
Uses dynamic NSColor.labelColor so the pill resolves correctly in both
light and dark menubars. Onboarding logo is unaffected (wrapped: false
default). Removes the stale image(size:) overload; extracts drawPill and
drawGlyph helpers to stay within swiftlint function_body_length limits.
- Add AXService wrapping AXIsProcessTrusted(WithOptions) for real state checks
- Add AXPermissionMonitor polling every 1 s and firing onChange on transitions
- Wire AgentService with AXService injection and uiProxy for reverse push
- Wire XPCListener to set remoteObjectInterface + uiProxy on each connection
- AgentMain composes all three and keeps monitor alive for the run loop
…oarding close button

- Agent observes com.apple.accessibility.api and exits so launchd respawns
  it; the fresh process queries AXIsProcessTrusted() without the per-process
  stale cache, fixing the onboarding-never-dismisses bug.
- AgentClient invalidationHandler schedules a reconnect instead of a no-op
  log; AgentClientObserver gains agentClientDidReconnect so the app layer
  re-evaluates AX state after the agent restarts.
- SleevApp implements applicationShouldHandleReopen so clicking the Dock icon
  re-runs evaluatePermissionAndPresentUI instead of doing nothing.
- OnboardingWindowController removes .closable from the style mask so the [X]
  button is gone; users must use Quit.
- Add OnboardingWindowControllerTests (2 tests) as a regression guard; total
  test count is now 12.
On connection accept, call pushCurrentAXState() so the UI immediately
learns the live permission state without polling. Fixes onboarding not
dismissing after AX is already granted when a fresh agent spawns.

Also emit initial state on first monitor tick as belt-and-suspenders,
and remove dead reconnect() code path that never fired under launchd
KeepAlive seamless reconnection.
…ache

AXIsProcessTrusted() is stale-cached for the entire agent process
lifetime. When the user grants Accessibility while sleev is already
running, the long-lived agent always returns undetermined. Fix: while
onboarding is visible the UI asks the agent to exit (launchd respawns
it); each fresh agent process reads the real TCC state and pushes it
via the existing connection-accept mechanism.
…ache

Replace polling-based agent restart (launchd KeepAlive throttle degrades
reliability) with a full-app self-relaunch: arm an NSWorkspace activation
observer when the user clicks Open System Settings, then relaunch via a
detached shell the moment sleev is re-activated so the new process reads
fresh AXIsProcessTrusted state. Remove the now-dead restartForFreshAXCheck
XPC method from protocol, agent, and client.
Match the standard menubar app pattern (Hidden Bar, AltTab, Rectangle):
the UI process holds the Accessibility grant and polls AXIsProcessTrusted
directly, rather than routing through a helper's reverse XPC push.

The previous helper-based push model proved unreliable when the agent
process restarted: pushes from a dying agent were dropped before XPC
could deliver them, leaving the onboarding window stuck even after the
user granted permission. UI-side polling eliminates the cross-process
complexity entirely.

- Add AccessibilityService in UI for AXIsProcessTrusted{,WithOptions}
- UI polls every 1.5s while onboarding is shown; stops on grant
- "Sleev" (not "SleevAgent") now appears in System Settings
- Remove SleevUIProtocol reverse channel, AXService, AXPermissionMonitor
- Agent simplified to just ping (preserved for Phase 2 work)
Migrate all bundle IDs, XPC mach service name, app group identifier,
OSLog subsystem, launchd plist, entitlements, tests, and Makefile from
the dev.sleev prefix to com.thanhhaudev.sleev. Also add
-allowProvisioningUpdates to local build/test targets so Xcode can
register the new bundle IDs automatically.
@thanhhaudev
thanhhaudev merged commit 6021a6b into master May 17, 2026
1 check passed
@thanhhaudev
thanhhaudev deleted the feature/phase-1 branch May 24, 2026 13:45
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