Просмотр исходного кода

Fix embedded Reddit header interactions, layout, and scrolling.

Restore reliable Log In and menu clicks, keep the search bar pinned while feeds scroll, and make fullscreen use the full window with balanced top spacing.

Co-authored-by: Cursor <cursoragent@cursor.com>
AhtashamShahzad1 1 месяц назад
Родитель
Сommit
dd7ba42e47

+ 4 - 2
Reddit App/Managers/RedditLoginScriptBridge.swift

@@ -10,11 +10,13 @@ final class RedditLoginScriptBridge: NSObject, WKScriptMessageHandler {
         super.init()
         super.init()
     }
     }
 
 
-    func userContentController(
+    nonisolated func userContentController(
         _ userContentController: WKUserContentController,
         _ userContentController: WKUserContentController,
         didReceive message: WKScriptMessage
         didReceive message: WKScriptMessage
     ) {
     ) {
         guard message.name == RedditLoginBridgeStyle.messageHandlerName else { return }
         guard message.name == RedditLoginBridgeStyle.messageHandlerName else { return }
-        onLoginRequested?()
+        Task { @MainActor in
+            onLoginRequested?()
+        }
     }
     }
 }
 }

+ 1 - 1
Reddit App/Managers/RedditWebViewManager.swift

@@ -29,7 +29,7 @@ enum RedditWebViewManager {
 
 
         let chromeScript = WKUserScript(
         let chromeScript = WKUserScript(
             source: RedditHomeChromeStyle.injectionScript,
             source: RedditHomeChromeStyle.injectionScript,
-            injectionTime: .atDocumentEnd,
+            injectionTime: .atDocumentStart,
             forMainFrameOnly: true
             forMainFrameOnly: true
         )
         )
         configuration.userContentController.addUserScript(chromeScript)
         configuration.userContentController.addUserScript(chromeScript)

+ 5 - 2
Reddit App/Utilities/AppWindow.swift

@@ -6,6 +6,9 @@ enum AppWindow {
     static let minWidth: CGFloat = width
     static let minWidth: CGFloat = width
     static let minHeight: CGFloat = height
     static let minHeight: CGFloat = height
 
 
-    /// Extra top inset when the window is in macOS full screen (hidden title bar has no safe area there).
-    static let fullScreenTopInset: CGFloat = 28
+    /// Extra top inset for the sidebar when the window is in macOS full screen.
+    static let fullScreenTopInset: CGFloat = 16
+
+    /// Small top breathing room for the embedded Reddit panel.
+    static let redditTopInset: CGFloat = 30
 }
 }

+ 191 - 2
Reddit App/Utilities/RedditHomeChromeStyle.swift

@@ -1,20 +1,48 @@
 import Foundation
 import Foundation
 
 
 enum RedditHomeChromeStyle {
 enum RedditHomeChromeStyle {
-    /// Keeps Reddit header separators flush with the in-app sidebar divider.
+    /// Keeps Reddit header separators flush with the in-app sidebar divider and
+    /// fixes Log In / menu hit targets that get covered by borders or feed chrome.
     static let injectionScript = """
     static let injectionScript = """
     (function() {
     (function() {
       const STYLE_ID = 'reddora-home-chrome-style';
       const STYLE_ID = 'reddora-home-chrome-style';
-      if (document.getElementById(STYLE_ID)) return;
+      if (window.__reddoraHomeChromeInstalled) return;
+      window.__reddoraHomeChromeInstalled = true;
+
+      const HEADER_ACTION_IDS = ['login-button', 'expand-user-drawer-button'];
+      const DECORATIVE_SELECTORS = [
+        'faceplate-border',
+        'hr',
+        '[role="separator"]',
+        'shreddit-overlay-display'
+      ].join(', ');
 
 
       const style = document.createElement('style');
       const style = document.createElement('style');
       style.id = STYLE_ID;
       style.id = STYLE_ID;
       style.textContent = `
       style.textContent = `
+        html,
+        body,
+        shreddit-app {
+          margin-top: 0 !important;
+          padding-top: 0 !important;
+        }
+
         header,
         header,
         header > * {
         header > * {
           margin-left: 0 !important;
           margin-left: 0 !important;
         }
         }
 
 
+        header {
+          position: sticky !important;
+          top: 0 !important;
+          z-index: 300 !important;
+          align-items: center !important;
+          isolation: isolate !important;
+          margin-top: 0 !important;
+          padding-top: 0 !important;
+          background: var(--shreddit-content-background, #ffffff) !important;
+        }
+
         header faceplate-border,
         header faceplate-border,
         header [role="separator"],
         header [role="separator"],
         header hr {
         header hr {
@@ -22,10 +50,171 @@ enum RedditHomeChromeStyle {
           max-width: none !important;
           max-width: none !important;
           margin-left: 0 !important;
           margin-left: 0 !important;
           margin-right: 0 !important;
           margin-right: 0 !important;
+          pointer-events: none !important;
+          z-index: 0 !important;
+        }
+
+        header faceplate-search-input,
+        header #SearchDropdown,
+        header [data-testid="search-input-container"] {
+          flex: 1 1 auto !important;
+          min-width: 0 !important;
+          max-width: 100% !important;
+          overflow: hidden !important;
+          align-self: center !important;
+          pointer-events: auto !important;
+        }
+
+        /* Wrappers around header actions often extend past the visible button. */
+        faceplate-tracker:has(#login-button),
+        faceplate-tracker:has(#expand-user-drawer-button),
+        rpl-tooltip:has(#login-button),
+        rpl-tooltip:has(#expand-user-drawer-button),
+        activate-feature:has(#expand-user-drawer-button) {
+          display: contents !important;
+        }
+
+        #login-button,
+        #expand-user-drawer-button {
+          position: relative !important;
+          z-index: 301 !important;
+          pointer-events: auto !important;
+          flex-shrink: 0 !important;
+          align-self: center !important;
+          touch-action: manipulation !important;
+        }
+
+        shreddit-overlay-display {
+          pointer-events: none !important;
+        }
+
+        shreddit-overlay-display:has(
+          shreddit-signup-drawer,
+          auth-flow-modal,
+          input[type="password"]
+        ) {
+          pointer-events: auto !important;
+        }
+
+        /* Feed sort row sits below the header; keep it from overlapping action buttons. */
+        shreddit-sort-dropdown,
+        [data-testid="sort-dropdown"],
+        [data-testid="sort-posts-dropdown"],
+        shreddit-feed-sort-dropdown {
+          position: relative !important;
+          z-index: 1 !important;
         }
         }
       `;
       `;
 
 
       (document.head || document.documentElement).appendChild(style);
       (document.head || document.documentElement).appendChild(style);
+
+      function isDecorative(node) {
+        if (!(node instanceof Element)) return false;
+        return node.matches(DECORATIVE_SELECTORS)
+          || (node.closest('header') && node.matches('faceplate-border, hr, [role="separator"]'));
+      }
+
+      function isInteractive(node) {
+        if (!(node instanceof Element)) return false;
+        return node.matches(
+          'button, a, [role="button"], input, select, textarea, label, faceplate-button'
+        );
+      }
+
+      function clearBlockersForButton(button) {
+        if (!(button instanceof Element)) return;
+        const rect = button.getBoundingClientRect();
+        if (rect.width < 1 || rect.height < 1) return;
+
+        const sampleYs = [
+          rect.top + rect.height * 0.25,
+          rect.top + rect.height * 0.5,
+          rect.top + rect.height * 0.75,
+          rect.bottom - 1
+        ];
+        const x = rect.left + rect.width * 0.5;
+
+        sampleYs.forEach(function(y) {
+          const stack = document.elementsFromPoint(x, y);
+          for (let i = 0; i < stack.length; i++) {
+            const el = stack[i];
+            if (el === button || button.contains(el)) break;
+            if (isInteractive(el)) break;
+            if (isDecorative(el) || !el.closest('header')) {
+              el.style.setProperty('pointer-events', 'none', 'important');
+              el.dataset.reddoraPointerFix = '1';
+            }
+          }
+        });
+      }
+
+      function pinHeader() {
+        document.querySelectorAll('header, reddit-header-large, faceplate-header').forEach(function(node) {
+          if (!(node instanceof HTMLElement)) return;
+          node.style.setProperty('position', 'sticky', 'important');
+          node.style.setProperty('top', '0', 'important');
+          node.style.setProperty('z-index', '300', 'important');
+          node.style.setProperty(
+            'background',
+            'var(--shreddit-content-background, #ffffff)',
+            'important'
+          );
+        });
+      }
+
+      function patchHeaderHitTargets() {
+        pinHeader();
+
+        HEADER_ACTION_IDS.forEach(function(id) {
+          const button = document.getElementById(id);
+          if (!button) return;
+          clearBlockersForButton(button);
+          button.style.setProperty('position', 'relative', 'important');
+          button.style.setProperty('z-index', '301', 'important');
+        });
+
+        document.querySelectorAll('header faceplate-border, header hr, header [role="separator"]').forEach(function(el) {
+          el.style.setProperty('pointer-events', 'none', 'important');
+        });
+      }
+
+      let patchTimer = null;
+      function schedulePatch() {
+        if (patchTimer) return;
+        patchTimer = setTimeout(function() {
+          patchTimer = null;
+          patchHeaderHitTargets();
+        }, 50);
+      }
+
+      patchHeaderHitTargets();
+      [0, 100, 300, 750, 1500, 3000].forEach(function(delay) {
+        setTimeout(patchHeaderHitTargets, delay);
+      });
+
+      const observer = new MutationObserver(schedulePatch);
+      observer.observe(document.documentElement, { childList: true, subtree: true, attributes: true });
+
+      window.addEventListener('resize', schedulePatch, { passive: true });
+      window.addEventListener('scroll', schedulePatch, { passive: true });
+
+      function forwardClicksWithinButtonBounds(event) {
+        if (event.type !== 'pointerdown') return;
+
+        const menuButton = document.getElementById('expand-user-drawer-button');
+        if (!menuButton || menuButton.contains(event.target)) return;
+
+        const rect = menuButton.getBoundingClientRect();
+        const x = event.clientX;
+        const y = event.clientY;
+        if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom) return;
+
+        event.preventDefault();
+        event.stopImmediatePropagation();
+        menuButton.click();
+      }
+
+      document.addEventListener('pointerdown', forwardClicksWithinButtonBounds, true);
     })();
     })();
     """
     """
 }
 }

+ 128 - 34
Reddit App/Utilities/RedditLoginBridgeStyle.swift

@@ -11,41 +11,29 @@ enum RedditLoginBridgeStyle {
       window.__reddoraLoginBridgeInstalled = true;
       window.__reddoraLoginBridgeInstalled = true;
 
 
       const LOGIN_PATTERN = /log\\s*in/i;
       const LOGIN_PATTERN = /log\\s*in/i;
-      const HEADER_SELECTORS = 'header, [role="banner"], #header, faceplate-header, nav';
+      const HEADER_SELECTORS = 'header, [role="banner"], #header, faceplate-header, nav, shreddit-header-action';
+      const CLICKABLE_SELECTORS = 'button, a, [role="button"], faceplate-tracker, faceplate-button';
       const AUTH_MODAL_SELECTORS = [
       const AUTH_MODAL_SELECTORS = [
         'auth-flow-modal',
         'auth-flow-modal',
         '[data-testid="login-modal"]',
         '[data-testid="login-modal"]',
         '#credentials-login-popup',
         '#credentials-login-popup',
+        'shreddit-signup-drawer',
         'faceplate-modal[slot="login"]',
         'faceplate-modal[slot="login"]',
         'faceplate-modal[open]'
         'faceplate-modal[open]'
       ].join(', ');
       ].join(', ');
 
 
+      const boundLoginTriggers = new WeakSet();
+
       function nodeLabel(node) {
       function nodeLabel(node) {
         if (!(node instanceof Element)) return '';
         if (!(node instanceof Element)) return '';
         return (
         return (
           (node.textContent || '') + ' ' +
           (node.textContent || '') + ' ' +
           (node.getAttribute('aria-label') || '') + ' ' +
           (node.getAttribute('aria-label') || '') + ' ' +
-          (node.getAttribute('title') || '')
+          (node.getAttribute('title') || '') + ' ' +
+          (node.getAttribute('href') || '')
         ).trim();
         ).trim();
       }
       }
 
 
-      function isHeaderLoginTrigger(node) {
-        if (!(node instanceof Element)) return false;
-
-        const clickable = node.matches('button, a, [role="button"], faceplate-tracker, faceplate-button')
-          ? node
-          : node.closest('button, a, [role="button"], faceplate-tracker, faceplate-button');
-        if (!clickable) return false;
-
-        if (clickable.id === 'login-button') return true;
-        if (clickable.getAttribute('data-testid') === 'login-button') return true;
-
-        const label = nodeLabel(clickable);
-        if (!LOGIN_PATTERN.test(label)) return false;
-
-        return !!clickable.closest(HEADER_SELECTORS);
-      }
-
       function isLoginPage() {
       function isLoginPage() {
         const host = window.location.hostname.toLowerCase();
         const host = window.location.hostname.toLowerCase();
         return (host === 'www.reddit.com' || host === 'reddit.com' || host === 'new.reddit.com')
         return (host === 'www.reddit.com' || host === 'reddit.com' || host === 'new.reddit.com')
@@ -69,11 +57,95 @@ enum RedditLoginBridgeStyle {
         }
         }
       }
       }
 
 
+      function interceptLoginEvent(event) {
+        if (isLoginPage()) return false;
+        if (eventMatchesLoginTrigger(event)) {
+          event.preventDefault();
+          event.stopImmediatePropagation();
+          window.__reddoraCloseRedditAuthModal && window.__reddoraCloseRedditAuthModal();
+          requestInAppLogin();
+          return true;
+        }
+
+        const loginButton = document.getElementById('login-button');
+        if (loginButton && eventMatchesButtonBounds(event, loginButton)) {
+          event.preventDefault();
+          event.stopImmediatePropagation();
+          window.__reddoraCloseRedditAuthModal && window.__reddoraCloseRedditAuthModal();
+          requestInAppLogin();
+          return true;
+        }
+
+        return false;
+      }
+
+      function eventMatchesButtonBounds(event, button) {
+        if (!(button instanceof Element)) return false;
+        if (!(event instanceof MouseEvent) && event.type !== 'pointerdown' && event.type !== 'mousedown') {
+          return false;
+        }
+        if (button.contains(event.target)) return false;
+
+        const rect = button.getBoundingClientRect();
+        const x = event.clientX;
+        const y = event.clientY;
+        return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
+      }
+
+      function eventMatchesLoginTrigger(event) {
+        const path = event.composedPath ? event.composedPath() : [event.target];
+        for (let i = 0; i < path.length; i++) {
+          if (resolveLoginTrigger(path[i])) return true;
+        }
+        return false;
+      }
+
+      function resolveLoginTrigger(node) {
+        if (!(node instanceof Element)) return null;
+
+        if (node.id === 'login-button') return node;
+        if (node.getAttribute('data-testid') === 'login-button') return node;
+
+        const clickable = node.matches(CLICKABLE_SELECTORS)
+          ? node
+          : node.closest(CLICKABLE_SELECTORS);
+        if (!clickable) return null;
+
+        if (clickable.id === 'login-button') return clickable;
+        if (clickable.getAttribute('data-testid') === 'login-button') return clickable;
+
+        const href = (clickable.getAttribute('href') || '').toLowerCase();
+        if (href.includes('/login')) return clickable;
+
+        const label = nodeLabel(clickable);
+        if (!LOGIN_PATTERN.test(label)) return null;
+
+        if (clickable.closest(HEADER_SELECTORS)) return clickable;
+
+        const rect = clickable.getBoundingClientRect();
+        if (rect.top >= 0 && rect.top < 96 && rect.width > 0 && rect.height > 0) {
+          return clickable;
+        }
+
+        return null;
+      }
+
+      function isLoginTriggerElement(node) {
+        return resolveLoginTrigger(node) !== null;
+      }
+
       function isAuthModal(element) {
       function isAuthModal(element) {
         if (!(element instanceof Element)) return false;
         if (!(element instanceof Element)) return false;
-        if (element.matches('auth-flow-modal, [data-testid="login-modal"], #credentials-login-popup')) {
+        if (element.matches(
+          'auth-flow-modal, [data-testid="login-modal"], #credentials-login-popup, shreddit-signup-drawer'
+        )) {
           return true;
           return true;
         }
         }
+        if (element.matches('shreddit-overlay-display')) {
+          return !!element.querySelector(
+            'shreddit-signup-drawer, auth-flow-modal, input[type="password"]'
+          );
+        }
         if (element.matches('faceplate-modal')) {
         if (element.matches('faceplate-modal')) {
           const label = nodeLabel(element);
           const label = nodeLabel(element);
           return LOGIN_PATTERN.test(label) || !!element.querySelector('input[type="password"]');
           return LOGIN_PATTERN.test(label) || !!element.querySelector('input[type="password"]');
@@ -95,6 +167,28 @@ enum RedditLoginBridgeStyle {
         });
         });
       };
       };
 
 
+      function bindDirectLoginListeners(root) {
+        function walk(node) {
+          if (!(node instanceof Element)) return;
+
+          const trigger = resolveLoginTrigger(node);
+          if (trigger && !boundLoginTriggers.has(trigger)) {
+            boundLoginTriggers.add(trigger);
+            ['pointerdown', 'mousedown', 'click'].forEach(function(type) {
+              trigger.addEventListener(type, interceptLoginEvent, true);
+            });
+          }
+
+          if (node.shadowRoot) walk(node.shadowRoot);
+          const children = node.children;
+          for (let i = 0; i < children.length; i++) {
+            walk(children[i]);
+          }
+        }
+
+        walk(root || document.documentElement);
+      }
+
       function installAuthModalBlocker() {
       function installAuthModalBlocker() {
         if (isLoginPage()) return;
         if (isLoginPage()) return;
 
 
@@ -105,7 +199,8 @@ enum RedditLoginBridgeStyle {
           style.textContent = [
           style.textContent = [
             'auth-flow-modal,',
             'auth-flow-modal,',
             '[data-testid="login-modal"],',
             '[data-testid="login-modal"],',
-            '#credentials-login-popup {',
+            '#credentials-login-popup,',
+            'shreddit-signup-drawer {',
             '  display: none !important;',
             '  display: none !important;',
             '  pointer-events: none !important;',
             '  pointer-events: none !important;',
             '}'
             '}'
@@ -115,6 +210,8 @@ enum RedditLoginBridgeStyle {
 
 
         const observer = new MutationObserver(function() {
         const observer = new MutationObserver(function() {
           if (isLoginPage()) return;
           if (isLoginPage()) return;
+          bindDirectLoginListeners(document.documentElement);
+
           let foundAuthModal = false;
           let foundAuthModal = false;
           document.querySelectorAll(AUTH_MODAL_SELECTORS).forEach(function(element) {
           document.querySelectorAll(AUTH_MODAL_SELECTORS).forEach(function(element) {
             if (!isAuthModal(element)) return;
             if (!isAuthModal(element)) return;
@@ -129,21 +226,18 @@ enum RedditLoginBridgeStyle {
         observer.observe(document.documentElement, { childList: true, subtree: true });
         observer.observe(document.documentElement, { childList: true, subtree: true });
       }
       }
 
 
-      document.addEventListener('click', function(event) {
-        if (isLoginPage()) return;
-        const path = event.composedPath();
-        for (let i = 0; i < path.length; i++) {
-          if (!isHeaderLoginTrigger(path[i])) continue;
-
-          event.preventDefault();
-          event.stopImmediatePropagation();
-          window.__reddoraCloseRedditAuthModal();
-          requestInAppLogin();
-          return;
-        }
-      }, true);
+      ['pointerdown', 'mousedown', 'click'].forEach(function(type) {
+        document.addEventListener(type, interceptLoginEvent, true);
+      });
 
 
+      bindDirectLoginListeners(document.documentElement);
       installAuthModalBlocker();
       installAuthModalBlocker();
+
+      [250, 750, 1500, 3000].forEach(function(delay) {
+        setTimeout(function() {
+          bindDirectLoginListeners(document.documentElement);
+        }, delay);
+      });
     })();
     })();
     """
     """
 
 

+ 21 - 7
Reddit App/Views/Components/WindowFullScreenObserver.swift

@@ -88,22 +88,36 @@ final class WindowObservationView: NSView {
         super.viewDidMoveToWindow()
         super.viewDidMoveToWindow()
         onWindowChange?(window)
         onWindowChange?(window)
     }
     }
+
+    override func hitTest(_ point: NSPoint) -> NSView? {
+        nil
+    }
 }
 }
 
 
-private struct FullScreenTopInsetModifier: ViewModifier {
+private struct FullScreenLayoutModifier: ViewModifier {
     @State private var isFullScreen = false
     @State private var isFullScreen = false
 
 
     func body(content: Content) -> some View {
     func body(content: Content) -> some View {
-        content
-            .padding(.top, isFullScreen ? AppWindow.fullScreenTopInset : 0)
-            .background {
-                WindowFullScreenObserver(isFullScreen: $isFullScreen)
+        VStack(spacing: 0) {
+            if isFullScreen {
+                AppTheme.sidebarBackground
+                    .frame(height: AppWindow.fullScreenTopInset)
+                    .frame(maxWidth: .infinity)
+                    .allowsHitTesting(false)
             }
             }
+
+            content
+                .frame(maxWidth: .infinity, maxHeight: .infinity)
+        }
+        .frame(maxWidth: .infinity, maxHeight: .infinity)
+        .background {
+            WindowFullScreenObserver(isFullScreen: $isFullScreen)
+        }
     }
     }
 }
 }
 
 
 extension View {
 extension View {
-    func fullScreenTopInsetWhenNeeded() -> some View {
-        modifier(FullScreenTopInsetModifier())
+    func fullScreenLayoutWhenNeeded() -> some View {
+        modifier(FullScreenLayoutModifier())
     }
     }
 }
 }

+ 12 - 2
Reddit App/Views/FrontPageView.swift

@@ -29,7 +29,7 @@ struct FrontPageView: View {
                 mainContent
                 mainContent
                     .frame(maxWidth: .infinity, maxHeight: .infinity)
                     .frame(maxWidth: .infinity, maxHeight: .infinity)
             }
             }
-            .fullScreenTopInsetWhenNeeded()
+            .fullScreenLayoutWhenNeeded()
             .environment(\.requirePremiumAccess, {
             .environment(\.requirePremiumAccess, {
                 if subscriptions.hasPremiumAccess { return true }
                 if subscriptions.hasPremiumAccess { return true }
                 viewModel.showPaywall()
                 viewModel.showPaywall()
@@ -47,6 +47,8 @@ struct FrontPageView: View {
             }
             }
         }
         }
         .animation(.easeInOut(duration: 0.2), value: viewModel.isPaywallPresented)
         .animation(.easeInOut(duration: 0.2), value: viewModel.isPaywallPresented)
+        .frame(maxWidth: .infinity, maxHeight: .infinity)
+        .frame(minWidth: AppWindow.minWidth, minHeight: AppWindow.minHeight)
         .onChange(of: subscriptions.hasPremiumAccess) { hasPremium in
         .onChange(of: subscriptions.hasPremiumAccess) { hasPremium in
             if hasPremium {
             if hasPremium {
                 viewModel.dismissPaywall()
                 viewModel.dismissPaywall()
@@ -70,7 +72,6 @@ struct FrontPageView: View {
                 configureRedditAccessForPremiumStatus()
                 configureRedditAccessForPremiumStatus()
             }
             }
         }
         }
-        .frame(minWidth: AppWindow.minWidth, minHeight: AppWindow.minHeight)
         .background(AppTheme.background)
         .background(AppTheme.background)
         .alert("Enjoying \(AppLinks.appName)?", isPresented: Binding(
         .alert("Enjoying \(AppLinks.appName)?", isPresented: Binding(
             get: { appRating.showRatingPrompt },
             get: { appRating.showRatingPrompt },
@@ -112,6 +113,13 @@ struct FrontPageView: View {
     @ViewBuilder
     @ViewBuilder
     private var mainContent: some View {
     private var mainContent: some View {
         VStack(spacing: 0) {
         VStack(spacing: 0) {
+            if viewModel.isRedditActive {
+                AppTheme.sidebarBackground
+                    .frame(height: AppWindow.redditTopInset)
+                    .frame(maxWidth: .infinity)
+                    .allowsHitTesting(false)
+            }
+
             if viewModel.isRedditActive, viewModel.redditCanGoBack {
             if viewModel.isRedditActive, viewModel.redditCanGoBack {
                 RedditWebBackBar(
                 RedditWebBackBar(
                     canGoBack: true,
                     canGoBack: true,
@@ -145,6 +153,8 @@ struct FrontPageView: View {
             }
             }
         }
         }
         .background(viewModel.isRedditActive ? AppTheme.sidebarBackground : AppTheme.background)
         .background(viewModel.isRedditActive ? AppTheme.sidebarBackground : AppTheme.background)
+        .frame(maxWidth: .infinity, maxHeight: .infinity)
+        .ignoresSafeArea(.container, edges: viewModel.isRedditActive ? .top : [])
     }
     }
 
 
     @ViewBuilder
     @ViewBuilder

+ 22 - 10
Reddit App/Views/RedditWebView.swift

@@ -244,19 +244,27 @@ struct RedditWebView: NSViewRepresentable {
         }
         }
 
 
         private func beginCredentialLogin() {
         private func beginCredentialLogin() {
-            guard let container else { return }
+            let performLogin = { [weak self] in
+                guard let self, let container = self.container else { return }
 
 
-            if let url = container.webView.url, RedditWebAuthHelper.isCredentialLoginPage(url) {
-                return
-            }
+                if let url = container.webView.url, RedditWebAuthHelper.isCredentialLoginPage(url) {
+                    return
+                }
 
 
-            RedditOAuthPanelController.shared.dismiss()
+                RedditOAuthPanelController.shared.dismiss()
 
 
-            container.webView.evaluateJavaScript(
-                RedditLoginBridgeStyle.closeAuthModalScript,
-                completionHandler: nil
-            )
-            container.load(url: RedditWebAuthHelper.redditLoginURL)
+                container.webView.evaluateJavaScript(
+                    RedditLoginBridgeStyle.closeAuthModalScript,
+                    completionHandler: nil
+                )
+                container.load(url: RedditWebAuthHelper.redditLoginURL)
+            }
+
+            if Thread.isMainThread {
+                performLogin()
+            } else {
+                DispatchQueue.main.async(execute: performLogin)
+            }
         }
         }
 
 
         private func beginOAuthSignIn(url: URL) {
         private func beginOAuthSignIn(url: URL) {
@@ -336,6 +344,10 @@ final class RedditWebViewContainer: NSView {
         tryLoadIfReady()
         tryLoadIfReady()
     }
     }
 
 
+    override func acceptsFirstMouse(for event: NSEvent?) -> Bool {
+        true
+    }
+
     private func tryLoadIfReady() {
     private func tryLoadIfReady() {
         guard let url = pendingURL else { return }
         guard let url = pendingURL else { return }
         guard bounds.width > 1, bounds.height > 1 else { return }
         guard bounds.width > 1, bounds.height > 1 else { return }

+ 1 - 0
Reddit App/Views/SidebarView.swift

@@ -13,6 +13,7 @@ struct SidebarView: View {
             upgradeCard
             upgradeCard
         }
         }
         .frame(width: AppTheme.sidebarWidth)
         .frame(width: AppTheme.sidebarWidth)
+        .frame(maxHeight: .infinity, alignment: .top)
         .background(AppTheme.sidebarBackground)
         .background(AppTheme.sidebarBackground)
     }
     }