From 88b68ee9747fbd030b429b059e0ea3d492f5a9b2 Mon Sep 17 00:00:00 2001 From: "snake.tech" <9079973+snaketech-tu@users.noreply.github.com> Date: Tue, 10 Nov 2026 07:59:34 +0000 Subject: [PATCH 1/3] Open every page at the top All pages scroll in the same element, so a new page kept the scroll position of the last one: Preferences opened from the Logs page started at its bottom. Reset the scroll when the page or the connection changes, and test it. --- CHANGELOG.md | 5 +++++ e2e/tests/prefs.spec.ts | 13 ++++++++++++- ui/src/App.svelte | 12 +++++++++++- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b59712f..b7f1459 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ Release notes for each version. The section of a version becomes the text of its GitHub release, and Stackline shows it in the update dialog. +## Unreleased + +### Fixed +- **Pages open at the top.** Every page scrolls in the same area, so a page opened where the last one was scrolled to: Preferences, opened from the Logs page, started at its bottom. Each page, and each connection you switch to, now starts at the top + ## 0.6.0 Stackline now works like Kubri: lists you can drive with the keyboard, an inspector beside the list, and a log workspace that keeps running while you look around. Read-only is now a switch per connection, and with it off Stackline can restart, stop, start and scale services. diff --git a/e2e/tests/prefs.spec.ts b/e2e/tests/prefs.spec.ts index 5892bb0..3d16b09 100644 --- a/e2e/tests/prefs.spec.ts +++ b/e2e/tests/prefs.spec.ts @@ -1,5 +1,5 @@ import { expect, test, type Page } from '@playwright/test' -import { open } from './helpers' +import { nav, open } from './helpers' const storeLine = (page: Page) => page.locator('section.panel', { has: page.getByRole('heading', { name: 'Local store' }) }).locator('.set .l').first() const entries = async (page: Page) => Number(((await storeLine(page).textContent()) ?? '').match(/([\d,.]+) entries/)?.[1].replace(/[,.]/g, '') ?? -1) @@ -35,3 +35,14 @@ test('after clearing the store, logs are collected again', async ({ page }) => { await open(page, 'logs') await expect(page.locator('.stream .row').first()).toBeVisible() }) + +test('Preferences opens at the top, not where the last page was scrolled to', async ({ page }) => { + await open(page, 'logs') + const view = page.locator('main.view') + // The log list makes the page long; scroll it well down. + await expect.poll(() => view.evaluate((el) => el.scrollHeight - el.clientHeight)).toBeGreaterThan(300) + await view.evaluate((el) => (el.scrollTop = el.scrollHeight)) + await nav(page, 'Preferences') + await expect(page.getByRole('heading', { name: 'Appearance' })).toBeVisible() + expect(await view.evaluate((el) => el.scrollTop)).toBe(0) +}) diff --git a/ui/src/App.svelte b/ui/src/App.svelte index c771d82..f61e7c8 100644 --- a/ui/src/App.svelte +++ b/ui/src/App.svelte @@ -106,6 +106,16 @@ } /** The note about a reset settings file was read. */ let configSeen = $state(false) + + // Every page scrolls in the same element, so a new page would open at the + // place the last one was scrolled to (Preferences after the Logs page opened + // at its bottom). Each page and each connection starts at the top. + let viewEl = $state() + $effect(() => { + void app.view + void app.activeId + if (viewEl) viewEl.scrollTop = 0 + }) @@ -144,7 +154,7 @@
-
+
{#if app.meta?.configWarning && !configSeen}