// THE PORT. Every view-model in this directory takes its live state as an // ARGUMENT, and this file is the shape of that argument. // // Why it exists (one-core phase 3 slice 1): the editor's payload builders used // to reach for the registry, scheduler and worker-pool getters themselves. // Those are LAZY SINGLETONS — calling one CREATES it — so a // builder that constructs is a builder that cannot be called from a status // poll, cannot be unit-tested without a real pool, and quietly re-seeds state // that `/api/test/invalidate-cache` had just cleared between e2e specs. See // `editor/app/api/view/pulseView.ts` (served at /api/view/pulse; /api/pulse is a // rewrite onto it) for the incident that rule is written from. // // Injecting the readers makes the rule structural rather than remembered: a // view here CANNOT construct, because it never imports a getter. The editor // has exactly one place allowed to call all five — `editor/app/lib/liveInputs.ts` // — and each noun's shell adds its own extras on top of these. // // TYPES ONLY. This file must never grow a value export. import type { Paths } from "../lib/paths"; import type { SiteSettings } from "../lib/settings"; import type { Scheduler } from "../jobs/scheduler"; import type { WorkerPool } from "../jobs/workerPool"; import type { getRegistry } from "../jobs/registry"; // `JobRegistry` is not exported as a class, so name it through its getter the // way the pulse route already does. Narrowed to the two reads a view is allowed // to make: listing jobs and fetching one. Nothing here may register, cancel or // mutate a job. export type RegistryReader = Pick, "list" | "get">; // The pool's read half. `summary()` is the per-worker snapshot, `isPaused()` // the transcription gate, `canStopPartial(id)` whether a running worker can be // drained rather than killed. export type PoolReader = Pick< WorkerPool, "summary" | "isPaused" | "canStopPartial" >; // What a server-rendered view gets. `now` is a function and not a number so a // test can freeze the clock; `Date.now` is banned inside views by the layer // guard for exactly that reason. export type LiveInputs = { paths: Paths; settings: SiteSettings; registry: RegistryReader; scheduler: Scheduler; pool: PoolReader; now: () => number; }; // What the OBSERVER gets — the pulse poll. Every singleton is nullable because // the observer reads the globals directly and "not created yet" is a legitimate // answer meaning "nothing to report": a pulse must be able to say the system is // idle without making a system to ask. The two mtimes and the generation // counter arrive as plain numbers so nothing here stats a file, and `digest` // is injected so nothing here imports `node:crypto`. export type ObserveInputs = { registry: RegistryReader | null; scheduler: Pick | null; pool: PoolReader | null; snapshotGeneration: number; settingsMtime: number; changelogMtime: number; digest: (s: string) => string; };