eleven / ocho Public

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>
Paul Campbell 8ea5052b42913db3d1b46bb3ab7a32917618a948 parent 01953ca
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,