fix(channel): the channel page's address names the channel and its own view, not the page under it

Opening a channel kept every key of the page under it (`?q=music&sort=oldest&chan=…`, a sort the
channel did not show), and the channel's own tab, sort and content types lived in its state alone, so a
reload or a bookmark lost them. Now the channel's address is `channelUrl`: the shell's keys and `chan`,
shared by `openChannel` and the channel link's href. The page writes its view (`chtab`, `chsort`,
`chtypes`, lib/channelPageUrlState) and reopens it; these are shell keys, cleared by a page switch and
by closing it in place. While a channel stands over a page, no module store writes its keys (`urlPage`);
Back finds them on the page's own entry. A link written by an older build opens without the page's keys.
This commit is contained in:
2026-10-26 00:19:02 +01:00
parent 9bd7304bb9
commit 91d36b3357
17 changed files with 565 additions and 46 deletions
+19 -2
View File
@@ -5,6 +5,7 @@
// retired. Only non-default values are emitted so URLs stay clean. NOTE: the browser TAB title stays
// the short module/channel name (NavigationProvider owns document.title) — the query never leaks into it.
import { TAG_CATEGORIES, type FeedFilters } from "./api";
import { CHANNEL_PAGE_KEYS } from "./channelPageUrlState";
export const FEED_KEYS = [
"q",
@@ -31,12 +32,13 @@ export const FEED_KEYS = [
] as const;
const KEYS = FEED_KEYS;
// The keys the shell itself reads, whatever page is on screen: the page, the channel page (it opens
// over any page), the welcome card's mode and the one-time entry links. No module may declare one of
// The keys the shell itself reads, whatever page is on screen: the page, the channel page and its view
// (it opens over any page, lib/channelPageUrlState), the welcome card's mode and the one-time entry links. No module may declare one of
// these (moduleUrl.contract.test), and a page switch never clears them.
export const SHELL_KEYS = [
"page",
"chan",
...CHANNEL_PAGE_KEYS,
"m",
"link",
"oauth",
@@ -228,6 +230,21 @@ export function urlWithParams(updates: Record<string, string | null>): string {
return hrefHere(params);
}
/** The channel page's address, opened over the page on screen: the shell's own keys (the page, an
* entry link not yet read) and the channel — none of the page's keys, and none of another channel's
* view (the channel page writes its own, lib/channelPageUrlState). What `openChannel` pushes, and what a
* channel link points at, so a new tab opens the same view. */
export function channelUrl(id: string): string {
const here = new URLSearchParams(window.location.search);
const viewKeys: readonly string[] = CHANNEL_PAGE_KEYS;
const params = new URLSearchParams();
for (const k of SHELL_KEYS) {
const v = k === "chan" ? id : here.get(k);
if (v != null && !viewKeys.includes(k)) params.set(k, v);
}
return hrefHere(params);
}
/** This page's address with its query replaced by `query` and its fragment by `hash` — what every
* address-bar writer hands to `history`. Built on the URL itself, never by gluing `pathname` in front:
* a path opened as `//evil.com/x` would read as a link to ANOTHER host, `history.replaceState` refuses