Documentation / @etherfold/browser / StateMovedAcrossTabs
Type Alias: StateMovedAcrossTabs ​
StateMovedAcrossTabs =
object
Defined in: packages/browser/src/stateMovedAcrossTabs.ts:183
THIS TAB'S END OF THE CROSS-TAB SIGNAL: what it says to the other tabs, and what it is told by them.
ONE object for both directions deliberately -- see openStateMovedAcrossTabs on why that is what keeps a tab from hearing itself.
TWO THINGS a reader can be told over it, and they are the two the port already carries (ADR-0082): the state MOVED (publish / onStateMoved), and WHERE THE FOLD IS (publishProgress / onProgress). They are not merged, because they answer different questions at different cadences; they share a channel, because a second channel would be a second lifetime and a second silence for a reader that already has this one open.
Properties ​
channelName ​
readonlychannelName:string
Defined in: packages/browser/src/stateMovedAcrossTabs.ts:185
WHICH channel this is on, as stateMovedChannelName composed it.
Methods ​
close() ​
close():
void
Defined in: packages/browser/src/stateMovedAcrossTabs.ts:315
This tab is done with the signal: stop listening and stop being able to publish.
Closing the underlying channel is what releases it, so a tab that opened one and let go of the reference without closing would go on receiving. A publication after this is DROPPED and noted rather than raised: the ordinary way it happens is a teardown releasing this before the port subscription that feeds it, which is a tab going away rather than a failure, and an app unmounting is not a place to throw.
Returns ​
void
onProgress() ​
onProgress(
listener): () =>void
Defined in: packages/browser/src/stateMovedAcrossTabs.ts:303
BE TOLD WHERE THE FOLD HAS GOT TO by the tab that is doing it. Returns the detach.
The same verb, the same value and the same meaning as IndexerPort.onProgress, because it is the same report over a second transport -- so an app binds it with the same helper it would bind a port with:
const progress = createProgressReadable(tabs); // a port works here too
// {$progress.phase === 'at-tip' ? 'live' : `syncing, ${$progress.blocksBehindTip} blocks behind`}A tab attaching part way through IS told, and that is the opposite of ​
onStateMoved one line above
Progress is a STATE rather than a thing that HAPPENED, which is the same split the port makes: its progress subscribe ANSWERS with the current value, while its notification subscribe answers nothing. So a listener here is handed WHERE THE FOLD IS as soon as this tab knows it:
- if this tab has already heard a report, that one, immediately;
- otherwise this tab ASKS the other tabs, and any tab holding a report of its own re-posts it. That ask is what stops a tab OPENED INTO A QUIET CHAIN being blank for ever: a host resting at the tip pushes nothing, so there is no next push to wait for.
It is an ASK and not a request. Nothing is awaited, nothing is retried and no tab is remembered, so a channel with no tab holding a report answers nothing at all and this listener simply waits for the next push -- best-effort, exactly like everything else here.
A REPEAT is not delivered ​
A report identical to the one this tab already holds is dropped rather than handed on, which is the rule the host itself follows on the port (it posts nothing where the report would repeat the last one). It is what makes several tabs asking at once cost one render rather than one per ask, and it is SUPPRESSION and never composition: what is held is one report, by reference, replaced wholesale.
Parameters ​
listener ​
(progress) => void
Returns ​
() => void
onStateMoved() ​
onStateMoved(
listener):StateMovedDetach
Defined in: packages/browser/src/stateMovedAcrossTabs.ts:223
BE TOLD BY ANOTHER TAB that the state moved. Returns the detach.
The same verb and the same value as IndexerPort.onStateMoved, because it is the same signal over a second transport: an app writes ONE handler and the two lines of the reader rule do not change.
let held: string | undefined;
tabs.onStateMoved((moved) => {
if (moved.coherence !== held) {held = moved.coherence; return refetchEverything();}
if (moved.kind === 'applied') for (const entity of moved.entities) refetch(entity);
});A tab that attaches part way through is told NOTHING until the next notification, exactly as on the port: a notification is a thing that HAPPENED rather than a value to render, and what a freshly attached tab does instead is READ.
Parameters ​
listener ​
Returns ​
publish() ​
publish(
moved):void
Defined in: packages/browser/src/stateMovedAcrossTabs.ts:202
TELL THE OTHER TABS what this tab's fold just did.
Handed the value a producer published and posts it unchanged, so it is wired straight onto whatever this tab holds the signal through:
const tabs = openStateMovedAcrossTabs({databaseName});
indexer.onStateMoved(tabs.publish); // this tab has a host; the others do notPOST AND FORGET. Nothing is acknowledged, nothing is retried and nothing is kept for a tab that was not listening: delivery is best-effort and the producer holds no per-client state (ADR-0083), which is what a reader's coherence token makes safe rather than merely cheap.
Parameters ​
moved ​
Returns ​
void
publishProgress() ​
publishProgress(
progress):void
Defined in: packages/browser/src/stateMovedAcrossTabs.ts:261
TELL THE OTHER TABS WHERE THIS TAB'S FOLD HAS GOT TO, so a tab that is merely reading can render "syncing, 400 blocks behind".
Handed the report a HOST made and posts it unchanged, so it is wired straight onto the push a tab with a host already receives:
const tabs = openStateMovedAcrossTabs({databaseName});
indexer.onProgress(tabs.publishProgress); // this tab has a host; the others do notThe value is the HOST'S, and this is a VIEW of it ​
Nothing here composes, rounds, re-derives or re-times: a reader tab and a hosting tab render the same words from the same numbers because they are literally the same value (HostProgress, ADR-0082's vocabulary, whose distance to the chain tip is blocksBehindTip -- named for the tip it measures against, since bare blocksBehind already means how far a NON-CANONICAL generation is behind the canonical one). A reader could not compute this for itself in any case: the sync cursor is opaque behind the storage seam (ADR-0027), so the side that knows has to say.
The CADENCE is the host's too, and no timer is introduced ​
A report is posted when one is pushed at this tab, and the host pushes when a batch has been APPLIED or the phase changed and says nothing when the report would repeat the last one. So this channel carries progress at the port's cadence rather than at the cadence of the applied blocks it otherwise carries: nothing polls, nothing beats, and a host resting at the tip is silent.
POST AND FORGET, exactly as publish is, with ONE report held: the last one THIS tab published, which is what an ask from a tab that opened later is answered with. One value whatever the number of listening tabs -- nothing is kept PER TAB, which is the property every transport in ADR-0083 rests on.
Parameters ​
progress ​
Returns ​
void