the URL says which page, not which row

`?page=about` instead of `?page=2`. A URL that names the page can be read, sent to somebody, and still
means the right thing after a database is restored from a dump with different ids — which happened
once in this project already, earlier today.

Home needs a word, because its slug is empty by design: that is the check constraint, and it is what
makes `/` fall out of joining a slug to the root. An empty string is no use in a query parameter that
has to name a page unambiguously, so the admin says `home` — and the site's API now REFUSES that slug,
which is what makes the mapping total in both directions rather than merely usual.

One reserved address costs nothing. Anybody who wanted /home wanted the front page, which they have.

One place maps a page to its URL value and back — pageParam and pageFromParam — so the nav, the detail
panel and the builder cannot disagree about what `?page=` means.

`record` is deleted on every navigation. It belonged to the per-page record panels, which are
unreachable now; nothing reads it, so it was not a bug, but every URL anybody copied carried a
parameter naming a row in a section that no longer exists.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-31 04:42:16 +01:00
co-authored by Claude Opus 5
parent b7c887a9e8
commit 5b1c5a2590
4 changed files with 38 additions and 9 deletions
+13 -4
View File
@@ -2,7 +2,7 @@ import { Link, useSearchParams } from 'react-router';
import { Lock } from 'lucide-react';
import { DEFAULT_SECTION, SECTIONS, isSectionKey, sectionOpen, type SectionKey } from './sections';
import { useReady } from './useReady';
import { usePages } from './usePages';
import { pageParam, usePages } from './usePages';
/**
* The left column: which part of the fofos site the detail panel is showing.
@@ -53,7 +53,7 @@ export const NavPanel = () => {
// 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 = Number(params.get('page')) || null;
const selectedPage = params.get('page');
const raw = params.get('section');
const active: SectionKey = isSectionKey(raw) ? raw : DEFAULT_SECTION;
const { ready, rootSet } = useReady();
@@ -89,6 +89,15 @@ export const NavPanel = () => {
*/
search.delete('page');
/*
`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" />
@@ -144,10 +153,10 @@ export const NavPanel = () => {
{pages.map((page) => {
const childSearch = new URLSearchParams(params);
childSearch.set('section', 'pages');
childSearch.set('page', String(page.id));
childSearch.set('page', pageParam(page));
childSearch.delete('media');
const current = active === 'pages' && selectedPage === page.id;
const current = active === 'pages' && selectedPage === pageParam(page);
return (
<Link