a nav link carries only what it means

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) <noreply@anthropic.com>
This commit is contained in:
2026-09-01 03:33:48 +01:00
co-authored by Claude Opus 5
parent 5cc4742119
commit 59e1218060
+16 -25
View File
@@ -66,29 +66,20 @@ export const NavPanel = () => {
// Everything else in the URL is preserved. This is one screen of a workspace, and the global // 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. // 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); 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. `record` is dead and rides along in every URL until something removes it.
@@ -162,10 +153,10 @@ export const NavPanel = () => {
<div key={section.key} className="flex flex-col"> <div key={section.key} className="flex flex-col">
{row} {row}
{LAYOUT_BANDS.map((band) => { {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('section', 'layout');
childSearch.set('band', band.key); childSearch.set('band', band.key);
childSearch.delete('media');
const current = active === 'layout' && params.get('band') === band.key; const current = active === 'layout' && params.get('band') === band.key;
@@ -192,10 +183,10 @@ export const NavPanel = () => {
<div key={section.key} className="flex flex-col"> <div key={section.key} className="flex flex-col">
{row} {row}
{pages.map((page) => { {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('section', 'pages');
childSearch.set('page', pageParam(page)); childSearch.set('page', pageParam(page));
childSearch.delete('media');
const current = active === 'pages' && selectedPage === pageParam(page); const current = active === 'pages' && selectedPage === pageParam(page);