From 49bb1b21fbc1478f9ccf9876a72beef07be04e88 Mon Sep 17 00:00:00 2001
From: Michael Libio <malibio@gmail.com>
Date: Sun, 1 Feb 2026 17:50:36 +0100
Subject: [PATCH] Fix: New tab opens with scroll position at top (#860)

Add { preventScroll: true } to all focus() calls in the cursor
positioning system to prevent browser auto-scrolling behavior.

## Problem

When opening a new tab, the scroll position was not at the top
because element.focus() calls were causing the browser to auto-scroll
to the focused element.

## Solution

Add preventScroll option to focus() calls in:
- TextareaController.focus() - main entry point used by all controllers
- CursorPositioningService - direct textarea focus calls
- BaseNodeViewer - cursor restoration focus calls

This ensures:
- New tabs start with scroll at top (no saved scroll state)
- Tab switching preserves scroll position (restored from scroll-state store)
- Arrow navigation maintains scroll state during node-to-node movement

## Testing Notes

This change requires manual testing as unit tests use mocked DOM
that doesn't detect real browser focus/scroll behavior. Key scenarios:
- Enter key creates new node - cursor in new node
- Arrow up/down navigation between nodes
- Clicking on a node to edit
- Tab switching preserves scroll position
- New tab opens at scroll top

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---
 .../lib/design/components/base-node-viewer.svelte    |  8 ++++++--
 .../design/components/textarea-controller.svelte.ts  |  7 ++++++-
 .../src/lib/services/cursor-positioning-service.ts   | 12 +++++++++---
 3 files changed, 21 insertions(+), 6 deletions(-)

diff --git a/packages/desktop-app/src/lib/design/components/base-node-viewer.svelte b/packages/desktop-app/src/lib/design/components/base-node-viewer.svelte
index 7e268fd1d..50fd2b992 100644
--- a/packages/desktop-app/src/lib/design/components/base-node-viewer.svelte
+++ b/packages/desktop-app/src/lib/design/components/base-node-viewer.svelte
@@ -849,7 +849,9 @@
           range.collapse(true);
           selection.removeAllRanges();
           selection.addRange(range);
-          element.focus();
+          // Use preventScroll to avoid browser auto-scrolling when focusing
+          // This preserves scroll state during tab switching and cursor restoration
+          element.focus({ preventScroll: true });
           return;
         }
         currentOffset += nodeLength;
@@ -862,7 +864,9 @@
         range.collapse(true);
         selection.removeAllRanges();
         selection.addRange(range);
-        element.focus();
+        // Use preventScroll to avoid browser auto-scrolling when focusing
+        // This preserves scroll state during tab switching and cursor restoration
+        element.focus({ preventScroll: true });
       }
     } catch {
       // Silently handle cursor restoration errors
diff --git a/packages/desktop-app/src/lib/design/components/textarea-controller.svelte.ts b/packages/desktop-app/src/lib/design/components/textarea-controller.svelte.ts
index 187d4213e..40ab985f3 100644
--- a/packages/desktop-app/src/lib/design/components/textarea-controller.svelte.ts
+++ b/packages/desktop-app/src/lib/design/components/textarea-controller.svelte.ts
@@ -431,7 +431,12 @@ export class TextareaController {
     }
 
     public focus(): void {
-      this.element.focus();
+      // Use preventScroll to avoid browser auto-scrolling when focusing
+      // This is critical for:
+      // 1. New tab opening - scroll should start at top, not at first focused node
+      // 2. Tab switching - scroll position should be restored, not overridden by focus
+      // 3. Arrow navigation - maintains scroll state during node-to-node navigation
+      this.element.focus({ preventScroll: true });
 
       if (this.pendingCursorPosition !== null) {
         this.setCursorPosition(this.pendingCursorPosition);
diff --git a/packages/desktop-app/src/lib/services/cursor-positioning-service.ts b/packages/desktop-app/src/lib/services/cursor-positioning-service.ts
index d3cbeeee7..c63102862 100644
--- a/packages/desktop-app/src/lib/services/cursor-positioning-service.ts
+++ b/packages/desktop-app/src/lib/services/cursor-positioning-service.ts
@@ -96,8 +96,10 @@ export class CursorPositioningService {
       }
 
       // Focus if requested
+      // Use preventScroll to avoid browser auto-scrolling when focusing
+      // This preserves scroll state during tab switching and new tab opening
       if (focus) {
-        textarea.focus();
+        textarea.focus({ preventScroll: true });
       }
 
       // Set cursor position
@@ -132,8 +134,10 @@ export class CursorPositioningService {
       const clampedPosition = Math.max(0, Math.min(position, maxPosition));
 
       // Focus if requested
+      // Use preventScroll to avoid browser auto-scrolling when focusing
+      // This preserves scroll state during tab switching and new tab opening
       if (focus) {
-        textarea.focus();
+        textarea.focus({ preventScroll: true });
       }
 
       // Set cursor position
@@ -187,8 +191,10 @@ export class CursorPositioningService {
       absolutePosition += column;
 
       // Focus if requested
+      // Use preventScroll to avoid browser auto-scrolling when focusing
+      // This preserves scroll state during tab switching and new tab opening
       if (focus) {
-        textarea.focus();
+        textarea.focus({ preventScroll: true });
       }
 
       // Set cursor position
