fix(macos): improve onboarding discovery

This commit is contained in:
Peter Steinberger
2026-01-11 03:50:59 +01:00
parent 9d802abd9a
commit 89291c384b
7 changed files with 90 additions and 28 deletions

View File

@@ -33,9 +33,16 @@ struct GeneralSettings: View {
Button {
DebugActions.restartOnboarding()
} label: {
Text("Complete onboarding to finish setup")
HStack(spacing: 8) {
Label("Complete onboarding to finish setup", systemImage: "arrow.counterclockwise")
.font(.callout.weight(.semibold))
.foregroundColor(.accentColor)
.foregroundStyle(Color.accentColor)
Spacer(minLength: 0)
Image(systemName: "chevron.right")
.font(.caption.weight(.semibold))
.foregroundStyle(.tertiary)
}
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.padding(.bottom, 2)

View File

@@ -456,13 +456,24 @@ final class MenuSessionsInjector: NSObject, NSMenuDelegate {
private func makeMessageItem(text: String, symbolName: String, width: CGFloat, maxLines: Int? = 2) -> NSMenuItem {
let view = AnyView(
Label(text, systemImage: symbolName)
HStack(alignment: .top, spacing: 8) {
Image(systemName: symbolName)
.font(.caption)
.foregroundStyle(.secondary)
.frame(width: 14, alignment: .leading)
.padding(.top, 1)
Text(text)
.font(.caption)
.foregroundStyle(.secondary)
.multilineTextAlignment(.leading)
.lineLimit(maxLines)
.truncationMode(.tail)
.fixedSize(horizontal: false, vertical: true)
.layoutPriority(1)
Spacer(minLength: 0)
}
.padding(.leading, 18)
.padding(.trailing, 12)
.padding(.vertical, 6)

View File

@@ -166,7 +166,8 @@ struct OnboardingView: View {
state: AppState = AppStateStore.shared,
permissionMonitor: PermissionMonitor = .shared,
discoveryModel: GatewayDiscoveryModel = GatewayDiscoveryModel(
localDisplayName: InstanceIdentity.displayName))
localDisplayName: InstanceIdentity.displayName,
filterLocalGateways: false))
{
self.state = state
self.permissionMonitor = permissionMonitor

View File

@@ -178,17 +178,15 @@ extension OnboardingView {
padding: CGFloat = 16,
@ViewBuilder _ content: () -> some View) -> some View
{
VStack(alignment: .leading, spacing: spacing) {
let shape = RoundedRectangle(cornerRadius: 16, style: .continuous)
return VStack(alignment: .leading, spacing: spacing) {
content()
}
.padding(padding)
.frame(maxWidth: .infinity, alignment: .leading)
.background(
RoundedRectangle(cornerRadius: 16, style: .continuous)
.fill(.ultraThinMaterial)
.overlay(
RoundedRectangle(cornerRadius: 16, style: .continuous)
.strokeBorder(Color.white.opacity(0.10), lineWidth: 1)))
.background(Color.clear)
.clipShape(shape)
.overlay(shape.strokeBorder(Color.white.opacity(0.10), lineWidth: 1))
}
func featureRow(title: String, subtitle: String, systemImage: String) -> some View {

View File

@@ -33,7 +33,7 @@ extension OnboardingView {
if shouldMonitor, !self.monitoringDiscovery {
self.monitoringDiscovery = true
Task { @MainActor in
try? await Task.sleep(nanoseconds: 550_000_000)
try? await Task.sleep(nanoseconds: 150_000_000)
guard self.monitoringDiscovery else { return }
self.gatewayDiscovery.start()
await self.refreshLocalGatewayProbe()

View File

@@ -292,6 +292,7 @@ final class LocationPermissionRequester: NSObject, CLLocationManagerDelegate {
static let shared = LocationPermissionRequester()
private let manager = CLLocationManager()
private var continuation: CheckedContinuation<CLAuthorizationStatus, Never>?
private var timeoutTask: Task<Void, Never>?
override init() {
super.init()
@@ -306,6 +307,16 @@ final class LocationPermissionRequester: NSObject, CLLocationManagerDelegate {
return await withCheckedContinuation { cont in
self.continuation = cont
self.timeoutTask?.cancel()
self.timeoutTask = Task { [weak self] in
try? await Task.sleep(nanoseconds: 3_000_000_000)
await MainActor.run { [weak self] in
guard let self else { return }
guard self.continuation != nil else { return }
LocationPermissionHelper.openSettings()
self.finish(status: self.manager.authorizationStatus)
}
}
if always {
self.manager.requestAlwaysAuthorization()
} else {
@@ -317,13 +328,43 @@ final class LocationPermissionRequester: NSObject, CLLocationManagerDelegate {
}
}
private func finish(status: CLAuthorizationStatus) {
self.timeoutTask?.cancel()
self.timeoutTask = nil
guard let cont = self.continuation else { return }
self.continuation = nil
cont.resume(returning: status)
}
// nonisolated for Swift 6 strict concurrency compatibility
nonisolated func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
let status = manager.authorizationStatus
Task { @MainActor in
guard let cont = self.continuation else { return }
self.continuation = nil
cont.resume(returning: status)
self.finish(status: status)
}
}
// Legacy callback (still used on some macOS versions / configurations).
nonisolated func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
Task { @MainActor in
self.finish(status: status)
}
}
nonisolated func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
let status = manager.authorizationStatus
Task { @MainActor in
if status == .denied || status == .restricted {
LocationPermissionHelper.openSettings()
}
self.finish(status: status)
}
}
nonisolated func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let status = manager.authorizationStatus
Task { @MainActor in
self.finish(status: status)
}
}
}

View File

@@ -20,7 +20,7 @@ App bundle layout:
- `Clawdbot.app/Contents/Resources/Relay/dist/`
- Compiled CLI/gateway payload from `pnpm exec tsc`
- `Clawdbot.app/Contents/Resources/Relay/node_modules/`
- Production dependencies staged via `pnpm deploy --prod --no-optional --legacy`
- Production dependencies staged via `pnpm deploy --prod --legacy` (includes optional native addons)
- `Clawdbot.app/Contents/Resources/Relay/clawdbot`
- Wrapper script that execs the bundled Node + dist entrypoint
- `Clawdbot.app/Contents/Resources/Relay/package.json`
@@ -47,7 +47,7 @@ Packaging script:
It builds:
- TS: `pnpm exec tsc`
- Swift app + helper: `swift build …`
- Relay payload: `pnpm deploy --prod --no-optional --legacy` + copy `dist/`
- Relay payload: `pnpm deploy --prod --legacy` + copy `dist/`
- Node runtime: downloads the latest Node release (override via `NODE_VERSION`)
Important knobs:
@@ -90,6 +90,10 @@ Node uses JIT. The bundled runtime is signed with:
This is applied by `scripts/codesign-mac-app.sh`.
Note: because the relay runs under hardened runtime, any bundled `*.node` native
addons must be signed with the same Team ID as the relay `node` binary.
`scripts/codesign-mac-app.sh` re-signs `Contents/Resources/Relay/**/*.node` for this.
## Image processing
To avoid shipping native `sharp` addons inside the bundle, the gateway defaults