Archilyzer · Source

archilyzer

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

commit bbc5b7d2199a131476a5f20a1df9382cf9edce71
parent a65bf532578cceba940bdeebeab1a08d19b395bb
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Sat, 19 Sep 2026 02:41:21 -0400

clip bench: a preference is written when you choose it, not when the page mounts

The persist effect ran on the FIRST render too, when the state is still the
default -- so opening a clip wrote `auto: false` over what the browser
remembered, and StrictMode's second mount then read back the value the first
mount had just clobbered. The setting survived the page you set it on and died
on the next clip, which is the worst possible shape for a bug like this: it
looks like the option works.

Persisting only where somebody actually chose something cannot do that. The
loader stays an effect; every mutation goes through choosePlayback(), whose
write is idempotent because StrictMode invokes an updater twice.

Also settles the auto-audition gate, verified in a browser rather than argued
about: with the preference restored, walking onto a clip plays it (paused
false, currentTime moving) as soon as metadata lands.

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

Diffstat:
Mumtool/components/projects/ClipBench.tsx | 60+++++++++++++++++++++++++++++++++++++++++++++++-------------
1 file changed, 47 insertions(+), 13 deletions(-)

diff --git a/umtool/components/projects/ClipBench.tsx b/umtool/components/projects/ClipBench.tsx @@ -307,12 +307,32 @@ export default function ClipBench({ data }: { data: ClipBenchData }) { // ---- playback preferences ------------------------------------------------ useEffect(() => setPlayback(readPlayback()), []); + /** + * Change a preference AND remember it. Never an effect on `playback`. + * + * That was the bug: an effect that persisted state on every change also ran + * on the first render, when the state is still the DEFAULT -- so mounting + * the bench wrote `auto: false` over what the browser remembered, and in + * StrictMode's second mount the loader read back the value the first mount + * had just clobbered. The setting survived the page you changed it on and + * died on the next clip, which is the worst possible shape for a bug like + * this. Writing only where somebody actually chose something cannot do that. + * (The write inside the updater is idempotent, which is what StrictMode's + * double-invoke requires of it.) + */ + const choosePlayback = useCallback((fn: (pb: Playback) => Playback) => { + setPlayback((pb) => { + const next = fn(pb); + try { + localStorage.setItem(PLAYBACK_KEY, JSON.stringify(next)); + } catch { + /* a private window refuses to store; the session still works */ + } + return next; + }); + }, []); + useEffect(() => { - try { - localStorage.setItem(PLAYBACK_KEY, JSON.stringify(playback)); - } catch { - /* a private window refuses to store; the session still works */ - } // BOTH players. Listening to the cut at 1.5x and then to the rendered // segment at 1x is two different clips as far as your ear is concerned. // @@ -553,7 +573,7 @@ export default function ClipBench({ data }: { data: ClipBenchData }) { which === "from" ? "start" : "end", ); const bumpRate = (by: number) => - setPlayback((pb) => { + choosePlayback((pb) => { const i = RATES.indexOf(pb.rate as (typeof RATES)[number]); const next = RATES[Math.min(RATES.length - 1, Math.max(0, (i < 0 ? 1 : i) + by))]; return { ...pb, rate: next }; @@ -609,7 +629,7 @@ export default function ClipBench({ data }: { data: ClipBenchData }) { break; case "a": case "A": - setPlayback((pb) => ({ ...pb, auto: !pb.auto })); + choosePlayback((pb) => ({ ...pb, auto: !pb.auto })); break; default: return; @@ -630,6 +650,7 @@ export default function ClipBench({ data }: { data: ClipBenchData }) { data.next, confirmClip, rejectClip, + choosePlayback, ]); const refresh = useCallback(async (): Promise<ClipBenchData | null> => { @@ -793,6 +814,7 @@ export default function ClipBench({ data }: { data: ClipBenchData }) { // How far the cached file reaches past the clip on its wider side, which is // the pad the LAST fetch effectively bought. The next one asks for 20 more. const reach = cached ? Math.max(clip.start - cached.from, cached.to - clip.end) : 0; + const peekOutside = peek.some((c) => c.start < view.from - 0.02 || c.end > view.to + 0.02); const nextPad = Math.min(data.maxPad, Math.round(reach) + 20); const atMaxPad = reach >= data.maxPad - 0.5; @@ -953,7 +975,9 @@ export default function ClipBench({ data }: { data: ClipBenchData }) { data-playback-rate="" aria-label="playback speed" value={playback.rate} - onChange={(e) => setPlayback((pb) => ({ ...pb, rate: Number(e.target.value) }))} + onChange={(e) => + choosePlayback((pb) => ({ ...pb, rate: Number(e.target.value) })) + } className="rounded border border-[var(--color-line)] bg-[var(--color-panel-2)] px-1 py-0.5 text-[11px]" > {RATES.map((r) => ( @@ -970,7 +994,7 @@ export default function ClipBench({ data }: { data: ClipBenchData }) { variant: playback.auto ? "primary" : "outline", size: "sm", })} - onClick={() => setPlayback((pb) => ({ ...pb, auto: !pb.auto }))} + onClick={() => choosePlayback((pb) => ({ ...pb, auto: !pb.auto }))} > auto-audition {playback.auto ? "on" : "off"} </button> @@ -1003,11 +1027,21 @@ export default function ClipBench({ data }: { data: ClipBenchData }) { }} /> - {(atStartEdge || atEndEdge) && ( + {/* Offered at the edge of the cache, as always -- and also whenever + the rail has something to read past it. Having just read the next + sentence is exactly when you want the seconds it is in, and + dragging to the edge first to be allowed to ask is a ritual. */} + {(atStartEdge || atEndEdge || peekOutside) && ( <div className="flex flex-wrap items-center gap-2 text-[12px]"> - <span className="text-[var(--color-dirty)]"> - that is the edge of what is cached{cached ? ` (${cached.name})` : ""} - </span> + {atStartEdge || atEndEdge ? ( + <span className="text-[var(--color-dirty)]"> + that is the edge of what is cached{cached ? ` (${cached.name})` : ""} + </span> + ) : ( + <span className="text-[var(--color-dim)]"> + cached to ±{Math.floor(reach)} s around this clip + </span> + )} {atMaxPad ? ( <span data-fetch-at-max="" className="text-[var(--color-dim)]"> the cached window is at the maximum (±{data.maxPad} s)