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:
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>