Documentation / @etherfold/browser / StreamSeedState
Type Alias: StreamSeedState ​
StreamSeedState = {
status:"installing"; } | {at:number;events:number;from:string;reachesBackTo:number;segments:number;status:"seeded"; } | {direction?:StreamSeedDirection;reason:NotInstalledReason;status:"refused"; }
Defined in: packages/browser/src/publishedStart.ts:91
WHAT HAPPENED TO THE STREAM SEED, as a small discriminated state an app can render: SyncingState.streamSeed on the main thread, HostProgress.streamSeed across a port.
The visibility half of a-browser-app-starts-from-a-published-artifact: an app that cannot say WHY it has no seed shows an empty screen instead of an explanation, which is the outcome that spec exists to avoid (ADR-0064). So the outcome the loader returns reaches the surface an application already subscribes to, and not only the boot path's return value.
It REPORTS and it does not decide, exactly as nonCanonicalGenerations does: whether "installing", "seeded" or "refused" should dim, hide or replace what is on screen is the application's call.
What it deliberately does NOT carry ​
No byte-level progress. In the recommended single-document shape the whole install is about 1 s on a mid-range phone and ~300 ms on desktop, which a spinner covers; the variable part is the DOWNLOAD, not the install, so a progress signal belongs on the fetch as an optional loader callback if it is ever wanted (work/notes/findings/what-a-published-stream-seed-costs-to-install.md). installing and the terminal states are the whole surface, which is also why this field publishes at most twice per boot.
No inference. See StreamSeedDirection.
Union Members ​
Type Literal ​
{ status: "installing"; }
The install is running. Published before the fetch, and replaced by a terminal state on every path the loader RETURNS from -- which is every ordinary one, since a refusal is data.
The exception, stated here because this is what an app author reads: if the loader THROWS (a malformed expectedContentHash, a keeper failing mid-install, a batch declined by another writer) there is no outcome to report, so this value STANDS and the boot fails instead (init rejects on the main thread; a worker host reports phase: 'refused' with the failure). Neither of the alternatives is truthful -- clearing the field says no seed was asked for, and a synthetic terminal state needs a reason the loader's vocabulary does not have -- so the honest signal is the failure, and an app must treat it as the end of the boot rather than waiting on this field. The main thread's status phase does NOT stick: it returns to Idle, so a spinner keyed on InstallingStreamSeed (which is what the guide shows) clears.
Type Literal ​
{ at: number; events: number; from: string; reachesBackTo: number; segments: number; status: "seeded"; }
at ​
readonlyat:number
How far the installed stream REACHES: the seed's coverage end, above its last event.
events ​
readonlyevents:number
from ​
readonlyfrom:string
WHICH location served it, so an app can say where its history came from.
reachesBackTo ​
readonlyreachesBackTo:number
How far back it reaches: what the keeper recorded as the stream's startBlock.
segments ​
readonlysegments:number
How many saves it took, which is how many SEGMENTS the keeper now holds.
status ​
readonlystatus:"seeded"
A stream was installed, and the app now holds history it never fetched.
Type Literal ​
{ direction?: StreamSeedDirection; reason: NotInstalledReason; status: "refused"; }
direction? ​
readonlyoptionaldirection?:StreamSeedDirection
Present only where the reason names one. See StreamSeedDirection.
reason ​
readonlyreason:NotInstalledReason
WHY, verbatim from the loader, so an app can explain it.
status ​
readonlystatus:"refused"
No seed was installed, and the app STARTS ANYWAY.
A refusal is a NORMAL condition and never gates the boot (ADR-0064): state still comes up from a published snapshot and indexes forward from the tip, and what is lost is the stream underneath, so the generation is a leaf and a later processor-only change waits for a republished snapshot instead of being free. That is why this is its own field and not error: an app treating error as a fault would render a crash for an ordinary outcome, and acknowledgeError() does not fit an outcome nothing can acknowledge away.