Skip to content

feat: UI overhaul — native window, official logo, larger tabs#21

Merged
GeiserX merged 8 commits intomainfrom
feat/ui-overhaul
Mar 31, 2026
Merged

feat: UI overhaul — native window, official logo, larger tabs#21
GeiserX merged 8 commits intomainfrom
feat/ui-overhaul

Conversation

@GeiserX
Copy link
Copy Markdown
Owner

@GeiserX GeiserX commented Mar 31, 2026

Summary

  • Settings window: Converted from NSPanel (utility window) to standard NSWindow with close/minimize/zoom buttons, resizable, and no aggressive floating behavior
  • Official logo: Replaced SF Symbol (shield.checkered) with actual app logo throughout — menu bar uses template icon, dropdown and titlebar use the 3D rendered logo
  • Tab bar: Larger font (13pt), rounded rectangle shape, proper spacing below titlebar traffic lights

Changes

Area Before After
Settings window NSPanel + .utilityWindow + screenSaver level NSWindow + miniaturizable + resizable
Menu bar icon SF Symbol shield.fill / shield.checkered menubar-icon.png template image
Dropdown header SF Symbol shield.checkered VPNBypass.png (official logo)
Titlebar SF Symbol 13pt Official logo 18px
Tab items 12pt capsule pills 13pt rounded rectangles
Makefile 3 assets copied 5 assets copied (+ menubar-icon PNGs)

Test plan

  • Settings window opens with standard close/minimize/zoom buttons
  • Settings window can be minimized to Dock and restored
  • Settings window can be resized (min 500x480)
  • Menu bar shows the official shield+arrow icon (not SF Symbol)
  • Dropdown shows the 3D logo in the title header
  • Tab bar items are visibly larger and easier to click
  • Loading pulse animation still works on menu bar icon

GeiserX added 8 commits March 31, 2026 11:40
- 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).
@GeiserX GeiserX merged commit edd8773 into main Mar 31, 2026
5 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.

1 participant