mac: the join page's "Open in the app" lands — elevenmessenger://add pre-fills Add-from-link (#1173)
The join page has always fired elevenmessenger://add?url=<link> and the Mac dropped it (onOpenURL accepted only the home-session sign-in callback). Route it: validate with parseJoinLink, stash on MacAppState, and PRE-FILL the existing Add-from-link sheet — the sheet is the confirming click (a URL arriving from outside must never join silently), and SpaceClaim already handles all three link shapes natively, add-device included. The fragment secret rides encoded inside the scheme URL's query value and never reaches any server. First slice of web↔app handoff; the web-side "Open in the Eleven app" for signed-in members (the "Continue on web" regret path) comes next.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
3 files changed,
+41
−2
ios/LChatMac/AddFromLinkSheet.swift+15 −0ios/LChatMac/LChatMacApp.swift+21 −2ios/LChatMac/RootView.swift+5 −0
diff --git a/ios/LChatMac/AddFromLinkSheet.swift b/ios/LChatMac/AddFromLinkSheet.swift| index fa4a39b..be2597f 100644 |
| --- a/ios/LChatMac/AddFromLinkSheet.swift |
| +++ b/ios/LChatMac/AddFromLinkSheet.swift |
| @@ -14,6 +14,15 @@ struct AddFromLinkSheet: View { |
| @State private var busy = false |
| @State private var error: String? |
| + /// The browser handoff's pre-fill (onOpenURL stashes the validated link; |
| + /// see LChatMacApp): consumed once, so a later manual open starts blank. |
| + private func consumePendingLink() { |
| + if let pending = app.pendingAddLink { |
| + link = pending |
| + app.pendingAddLink = nil |
| + } |
| + } |
| + |
| var body: some View { |
| VStack(alignment: .leading, spacing: 12) { |
| Text("Add a space from a link") |
| @@ -52,6 +61,12 @@ struct AddFromLinkSheet: View { |
| } |
| .padding(20) |
| .frame(width: 440) |
| + .onAppear(perform: consumePendingLink) |
| + // A handoff can land while the sheet is already up (a second click |
| + // in the browser) — take the newest link in place. |
| + .onChange(of: app.pendingAddLink) { _, pending in |
| + if pending != nil, !busy { consumePendingLink() } |
| + } |
| } |
| private func add() { |
diff --git a/ios/LChatMac/LChatMacApp.swift b/ios/LChatMac/LChatMacApp.swift| index b769346..d032fbf 100644 |
| --- a/ios/LChatMac/LChatMacApp.swift |
| +++ b/ios/LChatMac/LChatMacApp.swift |
| @@ -51,10 +51,24 @@ struct LChatMacApp: App { |
| .onAppear { appDelegate.appState = app } |
| .onOpenURL { url in |
| // The browser sign-in handoff (elevenmessenger:// |
| - // home-session#…) — the ONLY link the Mac app accepts now: |
| - // joining happens on the web, entry here is by signing in. |
| + // home-session#…): claimed first, always. |
| if MacHomeSignIn.handleCallback(url, into: app) { |
| NSApp.activate(ignoringOtherApps: true) |
| + return |
| + } |
| + // The join page's "Open in the app" (issue #1089's |
| + // handoff cousin): elevenmessenger://add?url=<join or |
| + // add-device link, fragment riding encoded inside the |
| + // query value — the secret never reaches any server>. |
| + // Validated here, then PRE-FILLS the Add-from-link sheet |
| + // rather than joining — one confirming click, with the |
| + // link visible, for any URL that arrived from outside. |
| + if url.host == "add", |
| + let link = URLComponents(url: url, resolvingAgainstBaseURL: false)? |
| + .queryItems?.first(where: { $0.name == "url" })?.value, |
| + parseJoinLink(link) != nil { |
| + app.pendingAddLink = link |
| + NSApp.activate(ignoringOtherApps: true) |
| } |
| } |
| } |
| @@ -247,6 +261,11 @@ final class MacAppState: ObservableObject { |
| @Published var createSpaceTick = 0 |
| // Spaces → Add from link…: RootView owns the paste sheet. |
| @Published var addFromLinkTick = 0 |
| + // A join/add-device link handed over by the browser (the join page's |
| + // "Open in the app" → elevenmessenger://add?url=…). Pre-fills the |
| + // Add-from-link sheet — the sheet IS the confirming click; a URL arriving |
| + // from outside the app must never join silently. Consumed on sheet appear. |
| + @Published var pendingAddLink: String? |
| func requestCreateSpace() { createSpaceTick += 1 } |
| func requestAddFromLink() { addFromLinkTick += 1 } |
diff --git a/ios/LChatMac/RootView.swift b/ios/LChatMac/RootView.swift| index 3dc8fbc..dc7722b 100644 |
| --- a/ios/LChatMac/RootView.swift |
| +++ b/ios/LChatMac/RootView.swift |
| @@ -98,6 +98,11 @@ struct RootView: View { |
| .onChange(of: app.createSpaceTick) { _, _ in createModel = makeCreateModel() } |
| // Spaces ▸ Add from link… — the native paste-a-link claim. |
| .onChange(of: app.addFromLinkTick) { _, _ in showAddFromLink = true } |
| + // …and the browser handoff pre-fills the same sheet (onOpenURL's |
| + // elevenmessenger://add — the sheet is the confirming click). |
| + .onChange(of: app.pendingAddLink) { _, link in |
| + if link != nil { showAddFromLink = true } |
| + } |
| .sheet(isPresented: $showAddFromLink) { AddFromLinkSheet() } |
| // ⌘K quick-switch palette, drawn over the whole window (above the web feed). |
| .overlay { if app.showPalette { CommandPalette() } } |