refactor(player): the watched toggle is stamped at each answer, with no hold while a write is out

The hold changed nothing a user could see (each answer stamps the toggle right after its own echo), and it would have outranked a mark made elsewhere after the click while the write was still out.
This commit is contained in:
2026-10-25 13:56:40 +01:00
parent 0bc1024287
commit ee4a5b46f8
+14 -16
View File
@@ -163,12 +163,14 @@ const META_OWN_LINE = "w-full [&>span:first-child>[data-meta-sep]]:hidden";
// the line wraps and the meta, or the actions after it, move down instead of squeezing.
const META_MIN = "min-w-48 flex-1";
// The watched toggle's own word on an item: what it last asked for, how many of its writes are still
// on the way, and when the last of them was answered (Infinity until then).
type OwnMark = { status: VideoStatus; seq: number; pending: number; at: number };
// The watched toggle's own word on an item: what it last asked for (`seq`, its write), and when — its
// click, then the server's last answer to any of its writes.
type OwnMark = { status: VideoStatus; seq: number; at: number };
// One of the toggle's writes was answered: stamped once none is left on the way; the latest refused,
// the toggle's word is dropped and the item shows what the server holds.
// One of the toggle's writes was answered. Its answer has just come back as a session mark too (the
// write marks before it resolves), so the toggle's word is stamped after it and outranks it; a later
// mark from elsewhere still outranks the toggle. The latest write refused, the toggle's word is
// dropped and the item shows what the server holds.
function settled(marks: Record<string, OwnMark>, id: string, seq: number, ok: boolean) {
const own = marks[id];
if (!own) return marks;
@@ -177,8 +179,7 @@ function settled(marks: Record<string, OwnMark>, id: string, seq: number, ok: bo
delete rest[id];
return rest;
}
const pending = own.pending - 1;
return { ...marks, [id]: { ...own, pending, at: pending > 0 ? Infinity : Date.now() } };
return { ...marks, [id]: { ...own, at: Date.now() } };
}
export default function PlayerModal({
@@ -203,7 +204,7 @@ export default function PlayerModal({
onClose: () => void;
/** The player's own marks (its watched toggle, the auto-mark at the end), for the item it is on —
* named by `video`, which may be none of the host's (a related video played in place). Returns
* the write: the toggle holds its own word until the server has answered, and drops a refused one. */
* the write: the toggle weighs its own word at the server's answer, and drops a refused one. */
onState: (id: string, status: VideoStatus, video?: Video) => Promise<unknown>;
/** A write the related list made itself, for a host that refetches once the player closes and its
* writes have landed (the feed's player session). */
@@ -573,10 +574,10 @@ export default function PlayerModal({
// The newest word wins: this toggle's own mark, or one the session confirmed elsewhere since the
// player opened (the related row's own menu, a notice's Undo — `useVideoMarks`), else the item as
// it was opened. The toggle alone missed those, and its first click re-sent the same status.
// Both are weighed when the server answered them: the toggle's own writes come back as session
// marks too, stamped at their answer, so a click weighed at its own time lost to the late answer
// of the click before it. While any of its writes is on the way the toggle's word holds; a refused
// one is dropped, so the toggle does not keep showing what the server turned down.
// The toggle's own writes come back as session marks too, stamped at their answer, so a click
// weighed only at its own time lost to the late answer of the click before it: each answer stamps
// the toggle's word again (`settled`). A refused one is dropped, so the toggle does not keep showing
// what the server turned down.
const [marks, setMarks] = useState<Record<string, OwnMark>>({});
const ownSeq = useRef(0);
const [openedAt] = useState(() => Date.now());
@@ -593,10 +594,7 @@ export default function PlayerModal({
const id = subject.id;
const next = on ? "watched" : "new";
const seq = ++ownSeq.current;
setMarks((m) => ({
...m,
[id]: { status: next, seq, pending: (m[id]?.pending ?? 0) + 1, at: Infinity },
}));
setMarks((m) => ({ ...m, [id]: { status: next, seq, at: Date.now() } }));
Promise.resolve(onState(id, next, subject)).then(
() => setMarks((m) => settled(m, id, seq, true)),
() => setMarks((m) => settled(m, id, seq, false)),