textmachine/frontend/src/mock/live.ts

71 lines
2.8 KiB
TypeScript

// The part of the fixture world that MOVES. Reads and the stream have to agree about it, otherwise
// the fixture models a server the contract forbids: one that answers a read with a revision lower
// than a frame it has already emitted. That fixture existed here, and it hid a real client defect —
// a refetch on focus rolled the live progress backwards, and the fixture could not tell us.
import { book } from './book';
const initialEta = book.progress.eta_seconds ?? null;
/**
* Seconds per unit, taken from the hand-written book's own numbers: its estimate covers the units it
* has left. A book of another size gets an estimate of its own from this — a fixture that handed a
* twelve-unit book the two-and-a-half hours of a 4276-unit one was printing a number nothing in it
* supported (found by the adversarial review).
*/
const secondsPerUnit =
(initialEta ?? 0) / Math.max(1, book.progress.draft.total - book.progress.draft.done);
export const live = {
revision: 1841,
draftDone: book.progress.draft.done,
draftTotal: book.progress.draft.total,
etaSeconds: initialEta,
/** What the estimate was when the run began; the one below is derived from it and the progress. */
etaAtStart: initialEta,
};
/**
* A run that starts from nothing — a book just uploaded and just parsed. The counters belong to the
* run and not to the fixture that was written by hand, so a world where the book arrives from the
* form has to be able to set them.
*/
export function begin(draftTotal: number): void {
live.draftDone = 0;
live.draftTotal = draftTotal;
live.etaAtStart = Math.round(draftTotal * secondsPerUnit);
live.etaSeconds = live.etaAtStart;
}
/** One burst of the run. Counters JUMP: the contract lets the server coalesce frames. */
export function advance(step: number): void {
live.revision += 3;
live.draftDone = Math.min(live.draftTotal, live.draftDone + 7 + step * 4);
// Derived from the progress rather than counted down by a constant: an estimate that falls as the
// work is done is what the field means, and it then fits a book of any size.
live.etaSeconds =
live.etaAtStart === null
? null
: Math.max(0, Math.round(live.etaAtStart * (1 - live.draftDone / live.draftTotal)));
}
/** The book as it stands right now — what a read must answer while the run is going. */
export function liveBook() {
return {
...book,
progress: {
draft: { done: live.draftDone, total: live.draftTotal },
edit: book.progress.edit,
eta_seconds: live.etaSeconds,
},
};
}
/** Tests share one module instance; a run left half-played would leak into the next test. */
export function rewind(): void {
live.revision = 1841;
live.draftDone = book.progress.draft.done;
live.draftTotal = book.progress.draft.total;
live.etaSeconds = initialEta;
live.etaAtStart = initialEta;
}