feat: UI overhaul — native window, official logo, larger tabs#21
Merged
feat: UI overhaul — native window, official logo, larger tabs#21
Conversation
- Settings: NSPanel → NSWindow with standard close/minimize/zoom buttons and resizable behavior. Removed screenSaver level and floating panel. - Menu bar: use official template icon (menubar-icon.png) instead of SF Symbol shield. Pulsing animation preserved for loading state. - Dropdown: use official 3D logo (VPNBypass.png) in title header. - Settings titlebar: use official logo instead of SF Symbol. - Tab bar: larger font (13pt), rounded rectangle shape, more top padding to clear titlebar traffic lights. - Makefile: copy menubar-icon PNGs to app bundle Resources.
- showWindow() now deminiaturizes and reuses existing window instead of discarding it when !isVisible (miniaturized windows aren't visible) - Added NSWindowDelegate with windowWillClose to nil the reference only when the user actually closes the window (red X) - Removed .resizable from styleMask since the SwiftUI layout is fixed at 580x620 — set contentMinSize/contentMaxSize to match
Root cause: after a Homebrew upgrade, the installed helper binary stays at the old version. Route application starts 0.5s after launch, but helper version check was scheduled 2s later. XPC calls to the old helper with new method signatures hang forever (no error handler, no timeout), leaving the app stuck at "Setting Up" indefinitely. Changes: - Add ensureHelperReady() preflight that MUST complete before any route application. Verifies helper files exist, connects with timeout to check version, auto-updates if mismatched, and retries. - Replace isHelperInstalled boolean with HelperState enum: missing, checking, installing, outdated, ready, failed. - All XPC RPCs now use remoteObjectProxyWithErrorHandler (not bare remoteObjectProxy) so protocol mismatches fire the error handler instead of silently hanging. - All XPC RPCs have a timeout (10s base + 0.1s per route for batches). On timeout, the XPC connection is dropped and an error is returned. - Startup ordering: VPNBypassApp awaits ensureHelperReady() before detectAndApplyRoutesAsync(), eliminating the race. - NSApp.activate before admin prompt so the authorization dialog appears on top. - Settings UI shows proper state: "Update Required", "Checking...", "Error: ..." with contextual action buttons (Install/Update/Retry).
…very 1. Replace broken withTaskTimeout (task group + CheckedContinuation — cancellation doesn't resume stuck continuations) with OnceGate + DispatchQueue.asyncAfter hard deadline. The gate guarantees exactly- once delivery: whichever fires first (XPC reply or timer) wins, the other is silently dropped. No cooperative cancellation needed. 2. Make helper preflight an authoritative gate: if ensureHelperReady() returns false, startup now returns early — detectAndApplyRoutesAsync() is never called. No more helperless fallback presenting false state. 3. Settings Install/Update/Retry button now calls ensureHelperReady() (full preflight with version verify) instead of bare installHelper(). On success, if VPN is connected but no routes exist (startup was gated), automatically triggers route application.
1. Remove helperless fallback from addRoute/removeRoute — when helper isn't ready, fail immediately instead of silently falling back to direct /sbin/route commands that require sudo and bypass the state model. This closes the back door that let periodic refresh and network monitor re-enter the unsupported helperless path. 2. Gate auto-apply on VPN connect: checkVPNStatus() now also checks HelperManager.shared.isHelperInstalled before triggering applyAllRoutes(), preventing the 30-second periodic refresh from bypassing the startup gate. 3. Settings recovery now starts startDNSRefreshTimer() after detectAndApplyRoutesAsync(), restoring the full lifecycle that was skipped when startup returned early due to helper failure.
- Add helper-ready guards to VPN interface reroute and Tailscale
profile reroute code paths
- Replace all `else { for ... addRoute/removeRoute }` fallback loops
with early-return or log-and-skip in: applyAllRoutesInternal,
removeAllRoutes, applyRoutesFromCache, backgroundDNSRefresh,
performDNSRefresh stale cleanup
- Remove entire AppleScript hosts file fallback from modifyHostsFile
- Flatten orphan/add-failed cleanup branches (no conditional needed
when helper is the only path)
- Helper readiness is now authoritative at every route-mutating entry
point, not just at startup
- Mark OnceGate as @unchecked Sendable (thread-safe via NSLock) - Constrain T: Sendable so fallback values can cross concurrency boundaries - Use `sending T` parameter in complete() for safe value transfer - Make withXPCDeadline @mainactor to match all callers and avoid closure isolation boundary crossings Only remaining warning is Apple SDK: SMAppService.register() declared async but compiler sees no async ops (unfixable without SDK change).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
NSPanel(utility window) to standardNSWindowwith close/minimize/zoom buttons, resizable, and no aggressive floating behaviorshield.checkered) with actual app logo throughout — menu bar uses template icon, dropdown and titlebar use the 3D rendered logoChanges
NSPanel+.utilityWindow+ screenSaver levelNSWindow+ miniaturizable + resizableshield.fill/shield.checkeredmenubar-icon.pngtemplate imageshield.checkeredVPNBypass.png(official logo)Test plan