From 49bb1b21fbc1478f9ccf9876a72beef07be04e88 Mon Sep 17 00:00:00 2001 From: Michael Libio 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 --- .../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