From 806fd072c70a3e7dc7ae4722ad180251e3b98eeb Mon Sep 17 00:00:00 2001 From: Sharon Stratsianis Date: Tue, 10 Nov 2026 18:33:48 +1100 Subject: [PATCH 1/2] Fix source pane resize - cancel save disappear Prompt: Source pane footer disappears on resize investigate and fix Co-authored-by: Claude Opus 5.5 --- src/mainPage/index.ts | 5 ++++- src/outline/manager.js | 2 ++ test/unit/mainPage/index.test.ts | 25 +++++++++++++++++++++++++ test/unit/outline/manager.test.ts | 31 +++++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/mainPage/index.ts b/src/mainPage/index.ts index 595b848e..7efed58e 100644 --- a/src/mainPage/index.ts +++ b/src/mainPage/index.ts @@ -101,7 +101,10 @@ export async function refreshUI (outliner: OutlineManager) { // Folder explorers adapt through CSS. Recreating one from the page's // history entry discards its internal resource/pane selection on resize. const themeUnchanged = currentSignature.split('|')[1] === previousSignature.split('|')[1] - if (envChanged && themeUnchanged && document.querySelector('#MainContent folder-pane-view')) { + const preserveExplorer = document.querySelector( + '#MainContent folder-pane-view, #MainContent [data-preserve-pane-on-resize="true"]' + ) + if (envChanged && themeUnchanged && preserveExplorer) { if (activeExplorerRefreshPending) return activeExplorerRefreshPending = true await new Promise(resolve => requestAnimationFrame(() => resolve())) diff --git a/src/outline/manager.js b/src/outline/manager.js index e6b05f23..2a9e500c 100644 --- a/src/outline/manager.js +++ b/src/outline/manager.js @@ -313,6 +313,8 @@ export default function (context) { const currentPane = provider.querySelector(':scope > .paneDiv') if (!currentPane) continue + if (currentPane.matches('[data-preserve-pane-on-resize="true"]') || + currentPane.querySelector('[data-preserve-pane-on-resize="true"]')) continue // Re-rendering the folder shell recreates its view and loses the resource // selected inside it. Nested providers can refresh their active resource // pane without replacing the shell. diff --git a/test/unit/mainPage/index.test.ts b/test/unit/mainPage/index.test.ts index 3f90881c..c6855192 100644 --- a/test/unit/mainPage/index.test.ts +++ b/test/unit/mainPage/index.test.ts @@ -330,6 +330,31 @@ describe('browser navigation without a reload', () => { expect(GotoSubject).toHaveBeenCalledOnce() }) + it('refreshes explorers instead of replacing the page during an active edit', async () => { + const GotoSubject = await setupLoaded() + const main = document.createElement('main') + main.id = 'MainContent' + main.innerHTML = '
' + document.body.append(main) + const outliner = getOutliner() + const refresh = vi.fn() + outliner.refreshActiveExplorerPanes = refresh + outliner.__lastRenderEnvSignature = 'desktop|light|pointer|1280|800' + outliner.context = { + session: { store }, + environment: { ...environment, layout: 'mobile', viewport: { width: 320, height: 800 } } + } + + await refreshUI(outliner) + + expect(refresh).toHaveBeenCalledOnce() + expect(GotoSubject).not.toHaveBeenCalled() + + outliner.context.environment = { ...outliner.context.environment, theme: 'dark' } + await refreshUI(outliner) + expect(GotoSubject).toHaveBeenCalledOnce() + }) + it('falls back to popstate when the Navigation API is unavailable', async () => { Reflect.deleteProperty(window, 'navigation') vi.resetModules() diff --git a/test/unit/outline/manager.test.ts b/test/unit/outline/manager.test.ts index 5c5ae13f..93e022f0 100644 --- a/test/unit/outline/manager.test.ts +++ b/test/unit/outline/manager.test.ts @@ -451,6 +451,37 @@ describe('manager', () => { expect(provider.pane).toBe(pane) }) + it.each([false, true])('preserves an editing pane and refreshes unrelated panes (nested marker: %s)', async nested => { + const pane = { ...MockPane, render: vi.fn(MockPane.render) } + const manager = new OutlineManager({ + dom: document, + session: { paneRegistry: { list: [pane], byName: findPane }, store }, + environment: { layout: 'desktop' } + }) + const editing = manager.propertyTable(container, undefined, pane, {}) + const unrelated = manager.propertyTable(sym(`${window.location.origin}/other.ttl`), undefined, pane, {}) + document.body.append(editing, unrelated) + await vi.waitFor(() => expect(unrelated.querySelector('.paneDiv')).not.toBeNull()) + const current = editing.querySelector('.paneDiv')! + const other = unrelated.querySelector('.paneDiv') + const marker = nested ? current.appendChild(document.createElement('div')) : current + marker.setAttribute('data-preserve-pane-on-resize', 'true') + const draft = current.appendChild(document.createElement('textarea')) + draft.value = 'unsaved draft' + pane.render.mockClear() + + manager.refreshActiveExplorerPanes() + + expect(editing.querySelector('.paneDiv')).toBe(current) + expect(draft.value).toBe('unsaved draft') + expect(unrelated.querySelector('.paneDiv')).not.toBe(other) + expect(pane.render).toHaveBeenCalledOnce() + + marker.removeAttribute('data-preserve-pane-on-resize') + manager.refreshActiveExplorerPanes() + expect(editing.querySelector('.paneDiv')).not.toBe(current) + }) + it('does not re-render the folder shell while refreshing a nested resource pane', async () => { const folderPane = { ...MockPane, name: 'folder', render: vi.fn(MockPane.render) } const subject = sym(`${window.location.origin}/storage/`) From c1d5f96ab05094d65f5c30e7a7d1b09bd5b109af Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 10 Nov 2026 07:38:51 +0000 Subject: [PATCH 2/2] chore: update solidos dependencies (dev: solid-logic@6.0.1-6 solid-ui@5.0.2-9 pane-registry@5.0.2-3 folder-pane@4.0.1-2 profile-pane@4.1.0-0 source-pane@4.0.1-0) (latest: rdflib@2.4.1 activitystreams-pane@2.0.0 chat-pane@4.0.0 contacts-pane@4.0.0 issue-pane@4.0.0 meeting-pane@4.0.0) --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 85217a34..0cb32258 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5461,9 +5461,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.478", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.478.tgz", - "integrity": "sha512-pftG+ma5Lm8O6/p45bhYT6StBN5ziQymaRzmCZwFl8DwWHKPPD93FdOmP1aqftcepolQYvrcyTLa17kE8Yf+jg==", + "version": "1.5.479", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.479.tgz", + "integrity": "sha512-uAJdJQ7vHa4TvPfzt1TOqoRgHmav5gwmqHMPLxBH6m8Cbj5R9/z/rygTOQiZ8pe5g0ZAEogga1bP6HoKcOzx/Q==", "dev": true, "license": "ISC" }, @@ -8256,9 +8256,9 @@ } }, "node_modules/lucide": { - "version": "1.70.0", - "resolved": "https://registry.npmjs.org/lucide/-/lucide-1.70.0.tgz", - "integrity": "sha512-LxPqUjt8e5PT7JuvmnKc8ECAqgkbnLG3hIi/FV2JpqWdPWLbLCoI5P3jje/Lq84uNU2kCpe2webgyFFJJ9zPAA==", + "version": "1.71.0", + "resolved": "https://registry.npmjs.org/lucide/-/lucide-1.71.0.tgz", + "integrity": "sha512-n6KHs0byHCG6fAQKBb+UhZtrUZM6v0ithDErPte6PkWkSijH0tPQoETnMobubUSj4okhAMeE6xUc/5S8LndrRg==", "license": "ISC" }, "node_modules/lz-string": {