From 59e1218060fc4726b312454b5bf9906e83b5cc78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Tue, 1 Sep 2026 03:33:48 +0100 Subject: [PATCH] a nav link carries only what it means MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pressing Layout while a band was open gave `?section=layout&band=footer`, so the row for the whole section led to one part of it. Pressing a section carried whatever the last one had left behind. Every link built a copy of the current query string and deleted the parameters known to be scoped — `media`, then `page`, and `band` would have been the third. That is a list of exceptions that has to grow every time a section gains a parameter, and it fails silently when somebody forgets: the link still works, it just goes somewhere more specific than it says. Starting from an empty query string inverts the default. A parameter survives a navigation only if the link says so, which is one decision at each link rather than a list of everything that must not leak. So a section row is `?section=x`, and a sub-item is that plus its own `band` or `page`. Nothing else rides along, including the device and the open tab — both are restored to their defaults by the panel that owns them, which is where a default belongs. Co-Authored-By: Claude Opus 5 (1M context) --- web/NavPanel.tsx | 41 ++++++++++++++++------------------------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/web/NavPanel.tsx b/web/NavPanel.tsx index 455e35f..746f8da 100644 --- a/web/NavPanel.tsx +++ b/web/NavPanel.tsx @@ -66,29 +66,20 @@ export const NavPanel = () => { // Everything else in the URL is preserved. This is one screen of a workspace, and the global // query string carries state that belongs to the shell rather than to this panel. - const search = new URLSearchParams(params); + /* + A FRESH query string, carrying only the section. + + It used to copy the current one and delete the parameters known to be scoped — `media`, then + `page`, and `band` would have been the third. That is a list of exceptions that has to grow every + time a section gains a parameter, and the failure is silent: pressing Layout while a band was open + gave `?section=layout&band=footer`, so the row for the whole section led to one part of it. + + Starting empty inverts the default. A parameter survives a navigation only if the link SAYS so, + which is one decision at each link rather than a list of everything that must not leak. + */ + const search = new URLSearchParams(); search.set('section', section.key); - /* - `media` is Media's own folder, and it goes when you leave. - - It survived every navigation, so `?section=home&media=GALLERY/CALAVERA+1` was an ordinary URL — - a parameter naming a folder in a section that is not open, carried into every link, and shared - or bookmarked that way. It is scoped to one section, so it is that section's to keep and nobody - else's to inherit. - */ - if (section.key !== 'media') search.delete('media'); - - /* - `page` is Pages' own, and pressing PAGES ITSELF clears it. - - Without this the section row and the row for whichever page was last open lead to the same - screen — the builder — and there is no way back to the list except the browser's back button. - Scoped like `media` above, and for the same reason: a parameter that belongs to one section - should not ride along into the others. - */ - search.delete('page'); - /* `record` is dead and rides along in every URL until something removes it. @@ -162,10 +153,10 @@ export const NavPanel = () => {
{row} {LAYOUT_BANDS.map((band) => { - const childSearch = new URLSearchParams(params); + // The section and this band, and nothing else — same rule as the section rows above. + const childSearch = new URLSearchParams(); childSearch.set('section', 'layout'); childSearch.set('band', band.key); - childSearch.delete('media'); const current = active === 'layout' && params.get('band') === band.key; @@ -192,10 +183,10 @@ export const NavPanel = () => {
{row} {pages.map((page) => { - const childSearch = new URLSearchParams(params); + // The section and this page, and nothing else. + const childSearch = new URLSearchParams(); childSearch.set('section', 'pages'); childSearch.set('page', pageParam(page)); - childSearch.delete('media'); const current = active === 'pages' && selectedPage === pageParam(page);