Archilyzer · Source

archilyzer

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

commit 8b68b4759af6fd9bb0adfdc20ee14385f213aaa1
parent f45aa9be0cd817525f831a193a5029ea5a8a36ca
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Thu,  1 Oct 2026 03:05:28 -0400

editor: ChannelForm create's On create boxes seed only from a refused submit of their own kind (review L1)

Fetch posts now (social) and Fetch playlist now / Add to top (video) are drawn
in alternate branches. After a refused submit from one branch, switching the URL
to the other kind mounted the other branch's box seeded from `values`, where its
name was absent, so it came up unticked; on main it came up at its default
(ticked). Each branch now seeds only from a refusal of its own kind, told by the
hidden `sourceKind=social` the social branch always posts. A new spec case
refuses a video submit and switches to a social URL (Fetch posts now ticked),
and the reverse.

Controlled.tsx's comment says what a controlled select resets to (review I2): its
options never get defaultSelected, so the reset selects the server-rendered
option, or the first when it mounted on the client.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Diffstat:
Meditor/app/channels/components/ChannelForm.tsx | 21++++++++++++++++++---
Meditor/app/components/forms/Controlled.tsx | 16+++++++++-------
Meditor/e2e/forms-keep-input.spec.ts | 46++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 73 insertions(+), 10 deletions(-)

diff --git a/editor/app/channels/components/ChannelForm.tsx b/editor/app/channels/components/ChannelForm.tsx @@ -43,6 +43,7 @@ import { ControlledSelect, } from "../../components/forms/Controlled"; import { + failedValues, seedChecked, seedValue, type SeedSource, @@ -138,6 +139,16 @@ export function ChannelForm({ "handling", c?.handling === "transcribe" ? "transcribe" : "youtube", ); + // Create mode's "On create" boxes are drawn per KIND of source (Fetch posts + // now for a social account; Fetch playlist now and Add to top for a video + // channel), so each kind seeds only from a refused submit of its own kind. + // A refused video submit carries no `fetchPostsNow`, and switching the URL + // to a social account afterwards must show that box at its default + // (ticked), not "absent, so unticked". The social branch always posts the + // hidden `sourceKind=social`. + const postedSocial = failedValues(state)?.sourceKind === "social"; + const socialSeed = postedSocial ? state : undefined; + const videoSeed = postedSocial ? undefined : state; // Controlled, so a surface that has to compare what is TYPED against what a // stored scan was run under can read them. (Nothing does yet; the metadata @@ -493,7 +504,11 @@ export function ChannelForm({ <input type="checkbox" name="fetchPostsNow" - defaultChecked={seedChecked(state, "fetchPostsNow", true)} + defaultChecked={seedChecked( + socialSeed, + "fetchPostsNow", + true, + )} className="mt-1" /> <span className="flex flex-col gap-0.5"> @@ -510,7 +525,7 @@ export function ChannelForm({ <input type="checkbox" name="fetchPlaylist" - defaultChecked={seedChecked(state, "fetchPlaylist", true)} + defaultChecked={seedChecked(videoSeed, "fetchPlaylist", true)} className="mt-1" /> <span className="flex flex-col gap-0.5"> @@ -527,7 +542,7 @@ export function ChannelForm({ type="checkbox" name="prioritizeDownload" defaultChecked={seedChecked( - state, + videoSeed, "prioritizeDownload", false, )} diff --git a/editor/app/components/forms/Controlled.tsx b/editor/app/components/forms/Controlled.tsx @@ -18,13 +18,15 @@ import { // back. It does not do the same for the other two kinds: // - a checkbox or radio's `defaultChecked` is set only when it MOUNTS // (react-dom `initInput`; `updateInput` sets `checked` alone); -// - a select's options' `defaultSelected` likewise (`updateOptions` is -// called with `setDefaultSelected` false on every update). -// So after ANY submit — failure or success — the reset returns the element to -// what it mounted with while React's state still says otherwise, until the -// next re-render that reaches it re-asserts the prop (the editor's -// auto-refresh, 5 s by default; never, with it off). A NAMED one submitted in -// that window posts the stale choice. +// - a controlled select's options never get `defaultSelected` +// (`updateOptions` is called with `setDefaultSelected` false at mount and +// on every update), so the reset selects the option the SERVER rendered +// `selected`, or the first option when the select mounted on the client. +// So after ANY submit — failure or success — the reset returns a checkbox or +// radio to what it mounted with, and a select to that option, while React's +// state still says otherwise, until the next re-render that reaches it +// re-asserts the prop (the editor's auto-refresh, 5 s by default; never, with +// it off). A NAMED one submitted in that window posts the stale choice. // // These put the controlled value back right after the form resets: a `reset` // listener on the element's form, deferred a microtask so it runs after the diff --git a/editor/e2e/forms-keep-input.spec.ts b/editor/e2e/forms-keep-input.spec.ts @@ -314,6 +314,52 @@ test("ChannelForm create: a refused slug keeps every field", async ({ await expect(named(form, "sleepBetweenDownloadsSeconds")).toHaveValue("9"); }); +// Create mode draws its "On create" boxes per KIND of source: Fetch posts now +// for a social account, Fetch playlist now for a video channel. A box of the +// kind that was NOT posted must come back at its default (ticked) when the URL +// switches kind after a refusal — not read as "absent from values, unticked". +test("ChannelForm create: switching the source's kind after a refusal shows the other kind's box at its default", async ({ + page, +}) => { + const BSKY_URL = "https://bsky.app/profile/someone.bsky.social"; + const VIDEO_URL = "https://www.youtube.com/@kindswitch/videos"; + await resetData("one-youtube-channel"); + await generateReport(page, "new"); + const form = page.locator("form").filter({ + has: page.getByRole("button", { name: /create channel/i }), + }); + const posts = named(form, "fetchPostsNow"); + const playlist = named(form, "fetchPlaylist"); + const refuse = async () => { + await form.getByRole("button", { name: /create channel/i }).click(); + await expect(form.getByText(/already exists/i)).toBeVisible(); + }; + + // A refused VIDEO submit, then a social URL. + await page.goto("/channels/new"); + await hydrated(form); + await named(form, "name").fill("Kind Switch"); + await named(form, "slug").fill("test-youtube"); // already exists + await named(form, "url").fill(VIDEO_URL); + await playlist.uncheck(); + await refuse(); + await expect(playlist).not.toBeChecked(); + await named(form, "url").fill(BSKY_URL); + await expect(posts).toBeChecked(); + + // The reverse: a refused SOCIAL submit, then a video URL. + await page.goto("/channels/new"); + await hydrated(form); + await named(form, "name").fill("Kind Switch"); + await named(form, "slug").fill("test-youtube"); + await named(form, "url").fill(BSKY_URL); + await posts.uncheck(); + await refuse(); + await expect(posts).not.toBeChecked(); + await named(form, "url").fill(VIDEO_URL); + await expect(playlist).toBeChecked(); +}); + test("ChannelForm edit: a refused filter keeps every field", async ({ page, }) => {