Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

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:
Meditor/app/channels/components/ChannelForm.tsx | 3++-
Meditor/app/channels/components/ChannelFormClient.tsx | 33++++++++++++++++-----------------
Meditor/app/channels/components/ChannelVolumeBar.tsx | 12++++++------
Meditor/app/channels/components/SiteMembershipsSection.tsx | 51++++++++++++++++++++++++++-------------------------
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.