Archilyzer · Source

archilyzer

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

commit 46f6086a9abdf8c31b702f893e307e1bbc88bb9a
parent d6edb1bb6cf4da337a959a2364d06c0faf231529
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Thu,  8 Oct 2026 23:32:08 -0400

umtool: a teaser being edited is not reset by the reload a save sets off

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Diffstat:
Mumtool/components/projects/StructureEditors.tsx | 26++++++++++++++++++--------
1 file changed, 18 insertions(+), 8 deletions(-)

diff --git a/umtool/components/projects/StructureEditors.tsx b/umtool/components/projects/StructureEditors.tsx @@ -1,7 +1,7 @@ "use client"; import { useRouter } from "next/navigation"; -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { announceNotesChanged, wroteNotes } from "@/components/notes/NotesProvider"; import { buttonVariants } from "@/components/ui/button"; import { MANIFEST_CHANGED, announceManifestChanged, refusalOf, timelineOp } from "./timelineApi"; @@ -99,7 +99,15 @@ function TeaserEditor({ teaser: t, busy, save }: { teaser: Teaser; busy: boolean const [fade, setFade] = useState(t.dip ? String(t.dip.fade) : ""); const [black, setBlack] = useState(t.dip ? String(t.dip.black) : ""); const [local, setLocal] = useState<string | null>(null); + // Unsaved edits win over a reload: the re-read every structural write sets + // off can land after somebody has started typing again. + const dirty = useRef(false); + const edit = <T,>(set: (v: T) => void) => (v: T) => { + dirty.current = true; + set(v); + }; useEffect(() => { + if (dirty.current) return; setLines(linesText()); setBeat(t.beat == null ? "" : String(t.beat)); setTail(t.tail ?? ""); @@ -119,19 +127,21 @@ function TeaserEditor({ teaser: t, busy, save }: { teaser: Teaser; busy: boolean return; } const dip = fade.trim() || black.trim() ? { fade: num(fade), black: num(black) } : null; - void save({ lines: parsed, beat: num(beat), tail: tail.trim() || null, tailWait: num(tailWait), dip }); + void save({ lines: parsed, beat: num(beat), tail: tail.trim() || null, tailWait: num(tailWait), dip }).then((ok) => { + if (ok) dirty.current = false; + }); }; return ( <div data-teaser-editor={t.id} className="space-y-1 rounded border border-[var(--color-line)] p-2"> <div className="micro">teaser ยท {t.id}</div> - <textarea data-testid={`teaser-lines-${t.id}`} rows={Math.min(8, Math.max(2, lines.split("\n").length))} value={lines} onChange={(e) => setLines(e.target.value)} className={`${input} w-full font-mono`} /> + <textarea data-testid={`teaser-lines-${t.id}`} rows={Math.min(8, Math.max(2, lines.split("\n").length))} value={lines} onChange={(e) => edit(setLines)(e.target.value)} className={`${input} w-full font-mono`} /> <div className="flex flex-wrap items-center gap-2 text-[11px] text-[var(--color-dim)]"> - <label>beat <input data-testid={`teaser-beat-${t.id}`} value={beat} onChange={(e) => setBeat(e.target.value)} className={`${input} w-14`} /></label> - <label>tail <input value={tail} onChange={(e) => setTail(e.target.value)} className={`${input} w-16`} /></label> - <label>tail wait <input value={tailWait} onChange={(e) => setTailWait(e.target.value)} className={`${input} w-14`} /></label> - <label>dip fade <input value={fade} onChange={(e) => setFade(e.target.value)} className={`${input} w-14`} /></label> - <label>black <input value={black} onChange={(e) => setBlack(e.target.value)} className={`${input} w-14`} /></label> + <label>beat <input data-testid={`teaser-beat-${t.id}`} value={beat} onChange={(e) => edit(setBeat)(e.target.value)} className={`${input} w-14`} /></label> + <label>tail <input value={tail} onChange={(e) => edit(setTail)(e.target.value)} className={`${input} w-16`} /></label> + <label>tail wait <input value={tailWait} onChange={(e) => edit(setTailWait)(e.target.value)} className={`${input} w-14`} /></label> + <label>dip fade <input value={fade} onChange={(e) => edit(setFade)(e.target.value)} className={`${input} w-14`} /></label> + <label>black <input value={black} onChange={(e) => edit(setBlack)(e.target.value)} className={`${input} w-14`} /></label> <button type="button" data-testid={`teaser-save-${t.id}`} disabled={busy} onClick={submit} className={buttonVariants({ variant: "primary", size: "sm" })}> Save teaser </button>