commit b963c6e8a15bb1e872f83cf6b674a8d9f1607fdf
parent d70e34ef60c382e5ae0a3e20caf74b9e18f1b101
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Wed, 30 Sep 2026 00:56:39 -0400
editor: a new channel starts checked on the active site, from the provider; the stale ?site= comments (review L5)
- ChannelFormClient resolves the create-mode site as the picker does off a
site's pages: a `?site=` link's, else the stored selection
(`resolveActiveSiteFrom([?site=, useSiteScope().stored], siteIds)`), on its
first render. It read only `window.location.search`, in a mount effect, so
/channels/new reached from the editor's own links never pre-checked a site.
- SiteMembershipsSection takes that site into its INITIAL state instead of an
effect: the server renders the box checked (no unchecked first paint), and a
later refresh, from the pulse or a pick in another tab, cannot re-check a box
the user cleared (the effect re-ran whenever `sites` came back as a new
array).
- The comments in ChannelFormClient, ChannelForm, SiteMembershipsSection and
ChannelVolumeBar no longer say the picker mirrors the scope into `?site=`.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
4 files changed, 50 insertions(+), 49 deletions(-)
diff --git a/editor/app/channels/components/ChannelForm.tsx b/editor/app/channels/components/ChannelForm.tsx
@@ -96,7 +96,8 @@ type Props = {
sites: SiteMembershipOption[];
// Edit mode: the channel's current site memberships.
initialMemberships?: InitialMembership[];
- // Create mode: the active site (from ?site=) to pre-check.
+ // Create mode: the active site, which starts checked (ChannelFormClient: a
+ // `?site=` link's, else the stored selection).
activeSiteId?: string | null;
};
diff --git a/editor/app/channels/components/ChannelFormClient.tsx b/editor/app/channels/components/ChannelFormClient.tsx
@@ -1,10 +1,12 @@
"use client";
-import { useActionState, useEffect, useState } from "react";
+import { useActionState } from "react";
+import { useSearchParams } from "next/navigation";
import { ChannelForm } from "./ChannelForm";
import type { ChannelConfig } from "yt-dlp-transcript-common/lib/channelConfig";
import type { ActionResult } from "../actions";
-import { ALL_SITES } from "../../lib/activeSite";
+import { resolveActiveSiteFrom } from "../../lib/activeSite";
+import { useSiteScope } from "../../components/SiteScopeProvider";
import type {
InitialMembership,
SiteMembershipOption,
@@ -32,21 +34,18 @@ export function ChannelFormClient({
action,
undefined,
);
- // The active site is mirrored into the `?site=` URL param by SiteScopeSelect.
- // Read it here (avoiding useSearchParams so this form needn't be wrapped in a
- // Suspense boundary) so a newly created channel pre-checks that site in the
- // Sites membership section. Only meaningful in create mode — an existing
- // channel's memberships come from initialMemberships.
- const [activeSite, setActiveSite] = useState<string | null>(null);
- useEffect(() => {
- try {
- setActiveSite(new URLSearchParams(window.location.search).get("site"));
- } catch {
- /* ignore */
- }
- }, []);
- const activeSiteId =
- !initial && activeSite && activeSite !== ALL_SITES ? activeSite : null;
+ // A new channel starts checked on the active site, as the picker resolves it
+ // off a site's pages: a `?site=` link's, else the stored selection (the
+ // cookie, through SiteScopeProvider). Both are known on the server and on the
+ // first client render, so the box is checked in the first paint. Create mode
+ // only: an existing channel's memberships come from initialMemberships.
+ // (useSearchParams needs no Suspense here: both pages that render this form
+ // are dynamic.)
+ const { stored, siteIds } = useSiteScope();
+ const linkSite = useSearchParams().get("site");
+ const activeSiteId = initial
+ ? null
+ : resolveActiveSiteFrom([linkSite, stored], siteIds).siteId;
return (
<ChannelForm
action={formAction}
diff --git a/editor/app/channels/components/ChannelVolumeBar.tsx b/editor/app/channels/components/ChannelVolumeBar.tsx
@@ -21,10 +21,9 @@ import { LOCATION_FILTER_PARAM } from "yt-dlp-transcript-common/views/storage";
// per-row focus sentence made before the focus bar took it. So it lives here,
// one chip per volume, above the rack.
//
-// THE CHIP IS THE FILTER, and it is a LINK. `?location=` is a URL param beside
-// `?site=` for the reason that one is: the page is a server component, the
-// filter changes what the server sends, and a link is shareable — /storage
-// links straight to a filtered list. Client state would also lose the race with
+// THE CHIP IS THE FILTER, and it is a LINK. `?location=` is a URL param because
+// the page is a server component, the filter changes what the server sends,
+// and a link is shareable — /storage links straight to a filtered list. Client state would also lose the race with
// the global AutoRefresh's router.refresh(), which is why the sort is the one
// thing here that stays local.
@@ -65,8 +64,9 @@ export function ChannelVolumeBar({
const pathname = usePathname();
const params = useSearchParams();
// THE SITE SCOPE SURVIVES THE VOLUME FILTER. They are two independent
- // questions ("whose channels" and "which disk") and a chip that silently
- // dropped ?site= would answer one by discarding the other.
+ // questions ("whose channels" and "which disk"). The stored scope is a cookie
+ // and not in the URL; a `?site=` link's scope is, and a chip that silently
+ // dropped it would answer one question by discarding the other.
const href = (id: string | null): string => {
const next = new URLSearchParams(params.toString());
if (id === null) next.delete(LOCATION_FILTER_PARAM);
diff --git a/editor/app/channels/components/SiteMembershipsSection.tsx b/editor/app/channels/components/SiteMembershipsSection.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useEffect, useState } from "react";
+import { useState } from "react";
// The channel form's per-site membership picker: every configured site with a
// checkbox (member or not) plus a group dropdown, including a "+ New group…"
@@ -28,8 +28,9 @@ type Props = {
sites: SiteMembershipOption[];
// Edit mode: the channel's current memberships (pre-checked).
initialMemberships?: InitialMembership[];
- // Create mode: the active site from `?site=` to pre-check. Arrives via a
- // mount effect in ChannelFormClient, before any user interaction.
+ // Create mode: the active site, which starts checked. ChannelFormClient
+ // resolves it on its first render (a `?site=` link's, else the stored
+ // selection), so it is part of the initial state below.
activeSiteId?: string | null;
};
@@ -39,28 +40,28 @@ export function SiteMembershipsSection({
activeSiteId,
}: Props) {
// Presence in the map = checked. groupId "" = the site's default group.
- const [selected, setSelected] = useState<Map<string, Row>>(
- () =>
- new Map(
- (initialMemberships ?? []).map((m) => [
- m.siteId,
- { groupId: m.groupId ?? "", newGroupName: "" },
- ]),
- ),
- );
-
- // Create-mode pre-check of the active site (mirrors the old hidden
- // `activeSite` field's behavior). Fires before user interaction, so no
- // clobber guard beyond "already checked" is needed.
- useEffect(() => {
- if (!activeSiteId || !sites.some((s) => s.siteId === activeSiteId)) return;
- setSelected((prev) => {
- if (prev.has(activeSiteId)) return prev;
- const next = new Map(prev);
- next.set(activeSiteId, { groupId: "", newGroupName: "" });
- return next;
- });
- }, [activeSiteId, sites]);
+ //
+ // Create mode starts with the active site checked (mirrors the old hidden
+ // `activeSite` field's behavior). It is INITIAL state, not an effect: the
+ // server renders the box checked, so there is no unchecked first paint, and a
+ // later refresh (the pulse, or a site picked in another tab) cannot re-check a
+ // box the user has cleared.
+ const [selected, setSelected] = useState<Map<string, Row>>(() => {
+ const rows = new Map(
+ (initialMemberships ?? []).map((m) => [
+ m.siteId,
+ { groupId: m.groupId ?? "", newGroupName: "" },
+ ]),
+ );
+ if (
+ activeSiteId &&
+ !rows.has(activeSiteId) &&
+ sites.some((s) => s.siteId === activeSiteId)
+ ) {
+ rows.set(activeSiteId, { groupId: "", newGroupName: "" });
+ }
+ return rows;
+ });
if (sites.length === 0) {
// No hidden field at all: the actions skip membership reconciliation.