sections are paths, not a query string
The admin declares its routes in the manifest, so /fofos-admin/layout/header and /fofos-admin/pages/home are real URLs. One reader takes the two segments after the mount; links carry no query string at all, so nothing leaks between sections. What stays in the query is how you are looking — device, tab, view — rather than at what. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+19
-46
@@ -1,6 +1,7 @@
|
||||
import { Link, useSearchParams } from 'react-router';
|
||||
import { Link } from 'react-router';
|
||||
import { Lock } from 'lucide-react';
|
||||
import { DEFAULT_SECTION, LAYOUT_BANDS, SECTIONS, isSectionKey, sectionOpen, type SectionKey } from './sections';
|
||||
import { LAYOUT_BANDS, SECTIONS, sectionOpen } from './sections';
|
||||
import { href, useNav } from './routes';
|
||||
import { useReady } from './useReady';
|
||||
import { pageParam, usePages } from './usePages';
|
||||
|
||||
@@ -49,13 +50,10 @@ const rowClass = (active: boolean) =>
|
||||
}`;
|
||||
|
||||
export const NavPanel = () => {
|
||||
const [params] = useSearchParams();
|
||||
// Announced by PagesPanel rather than fetched here — see usePages. Null until it has spoken, which
|
||||
// is why nothing is drawn instead of an empty list.
|
||||
const { pages } = usePages();
|
||||
const selectedPage = params.get('page');
|
||||
const raw = params.get('section');
|
||||
const active: SectionKey = isSectionKey(raw) ? raw : DEFAULT_SECTION;
|
||||
const { section: active, item } = useNav();
|
||||
const { ready, rootSet } = useReady();
|
||||
|
||||
const renderRow = (section: (typeof SECTIONS)[number]) => {
|
||||
@@ -64,31 +62,6 @@ export const NavPanel = () => {
|
||||
// connection, Administration wants nothing because it is the way out of both.
|
||||
const locked = !sectionOpen(section, { ready, rootSet });
|
||||
|
||||
// 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.
|
||||
/*
|
||||
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);
|
||||
|
||||
/*
|
||||
`record` is dead and rides along in every URL until something removes it.
|
||||
|
||||
It belonged to the per-page record panels — Gallery, Drops, Collabs — which are unreachable now.
|
||||
Nothing reads it, so it is not a bug, but every link anybody copies carries a parameter naming a
|
||||
row in a section that no longer exists.
|
||||
*/
|
||||
search.delete('record');
|
||||
|
||||
const body = (
|
||||
<>
|
||||
<Icon className="mt-0.5 size-4 shrink-0 text-muted-foreground" />
|
||||
@@ -118,7 +91,17 @@ export const NavPanel = () => {
|
||||
const row = (
|
||||
<Link
|
||||
key={section.key}
|
||||
to={{ search: `?${search.toString()}` }}
|
||||
/*
|
||||
A PATH, and no query string at all.
|
||||
|
||||
It used to build a fresh `URLSearchParams` per link, because everything about where you were
|
||||
lived in the query and a copied one leaked — pressing Layout while a band was open gave
|
||||
`?section=layout&band=footer`, so the row for a whole section led to one part of it.
|
||||
|
||||
A path cannot leak: the URL says where you are, and `href` drops the query because what is
|
||||
left in it — how you are looking, not at what — belongs to the screen you are leaving.
|
||||
*/
|
||||
to={href(section.key)}
|
||||
replace
|
||||
className={rowClass(active === section.key)}
|
||||
>
|
||||
@@ -153,17 +136,12 @@ export const NavPanel = () => {
|
||||
<div key={section.key} className="flex flex-col">
|
||||
{row}
|
||||
{LAYOUT_BANDS.map((band) => {
|
||||
// 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);
|
||||
|
||||
const current = active === 'layout' && params.get('band') === band.key;
|
||||
const current = active === 'layout' && item === band.key;
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={band.key}
|
||||
to={{ search: `?${childSearch.toString()}` }}
|
||||
to={href('layout', band.key)}
|
||||
replace
|
||||
className={`flex items-baseline gap-2 border-b border-border py-1.5 pr-3 pl-9 text-left transition-colors ${
|
||||
current ? 'bg-muted' : 'hover:bg-muted/50'
|
||||
@@ -183,17 +161,12 @@ export const NavPanel = () => {
|
||||
<div key={section.key} className="flex flex-col">
|
||||
{row}
|
||||
{pages.map((page) => {
|
||||
// The section and this page, and nothing else.
|
||||
const childSearch = new URLSearchParams();
|
||||
childSearch.set('section', 'pages');
|
||||
childSearch.set('page', pageParam(page));
|
||||
|
||||
const current = active === 'pages' && selectedPage === pageParam(page);
|
||||
const current = active === 'pages' && item === pageParam(page);
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={page.id}
|
||||
to={{ search: `?${childSearch.toString()}` }}
|
||||
to={href('pages', pageParam(page))}
|
||||
replace
|
||||
className={`flex items-baseline gap-2 border-b border-border py-1.5 pr-3 pl-9 text-left transition-colors ${
|
||||
current ? 'bg-muted' : 'hover:bg-muted/50'
|
||||
|
||||
Reference in New Issue
Block a user