mac: keep file-drop registration alive across navigations (+ surface alert()) (#105)
WKWebView clobbers the view's registered drag types on every page navigation, so the registerForDraggedTypes([.fileURL]) from makeNSView / updateNSView silently vanished after the page loaded: the visible chat accepted no drops at all until the next chat switch happened to re-run updateNSView. Fix by re-asserting registration in didFinish and keeping every stacked webview registered permanently — cross-chat routing is already guaranteed by handleDroppedFiles targeting app.activeWebView. Also implement runJavaScriptAlertPanelWithMessage: chat.js reports attachment failures with alert(), which macOS WKWebView swallows without this hook, making real errors indistinguishable from the dead-drop bug.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 file changed,
+24
−9
ios/LChatMac/ChatFeedWebView.swift+24 −9
diff --git a/ios/LChatMac/ChatFeedWebView.swift b/ios/LChatMac/ChatFeedWebView.swift| index 1a30819..903ad63 100644 |
| --- a/ios/LChatMac/ChatFeedWebView.swift |
| +++ b/ios/LChatMac/ChatFeedWebView.swift |
| @@ -76,15 +76,14 @@ struct ChatFeedWebView: NSViewRepresentable { |
| func updateNSView(_ web: WKWebView, context: Context) { |
| // Track the selected instance's webview so a dropped (or picked) file is always |
| // injected into the feed you're viewing — never a hidden, stacked instance's |
| - // webview (which is how drops leaked to a different chat). Keeping drop |
| - // registration on the selected webview only is a first line; the routing in |
| - // handleDroppedFiles is the guarantee. |
| - if isActive { |
| - app.activeWebView = web |
| - web.registerForDraggedTypes([.fileURL]) |
| - } else { |
| - web.unregisterDraggedTypes() |
| - } |
| + // webview (which is how drops leaked to a different chat). That routing in |
| + // handleDroppedFiles is the sole guarantee: every webview stays registered for |
| + // file drags, because WKWebView wipes the view's registered drag types on each |
| + // navigation. Keeping only the selected one registered left dead states where |
| + // no view accepted the drop at all — it silently did nothing until the next |
| + // chat switch re-registered the visible webview. |
| + if isActive { app.activeWebView = web } |
| + web.registerForDraggedTypes([.fileURL]) |
| } |
| // Runs on every page load. (1) Window drag: start a native window-drag when the |
| @@ -251,6 +250,9 @@ struct ChatFeedWebView: NSViewRepresentable { |
| // After the origin loads, if this origin has no stored session, claim the |
| // invite once via the join link. Guarded so we never loop or re-claim. |
| func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { |
| + // WKWebView clobbers the view's registered drag types during navigation, |
| + // so re-assert file-drop registration after every page load. |
| + webView.registerForDraggedTypes([.fileURL]) |
| guard !didAttemptJoin, let joinURL = URL(string: chat.url) else { return } |
| webView.evaluateJavaScript("localStorage.getItem('lchat_session')") { [weak self] result, _ in |
| guard let self, !self.didAttemptJoin else { return } |
| @@ -262,6 +264,19 @@ struct ChatFeedWebView: NSViewRepresentable { |
| } |
| } |
| + // chat.js reports failures (e.g. "couldn't add that file") with alert(); |
| + // macOS WKWebView drops those silently unless the host shows the panel, so |
| + // real errors looked like nothing happening. |
| + func webView(_ webView: WKWebView, |
| + runJavaScriptAlertPanelWithMessage message: String, |
| + initiatedByFrame frame: WKFrameInfo, |
| + completionHandler: @escaping () -> Void) { |
| + let alert = NSAlert() |
| + alert.messageText = message |
| + alert.runModal() |
| + completionHandler() |
| + } |
| + |
| // macOS WKWebView needs this hook or the composer's "+" file picker does |
| // nothing (unlike iOS). |
| func webView(_ webView: WKWebView, |