Skip to content

Documentation / @etherfold/browser / openStateMovedAcrossTabs

Function: openStateMovedAcrossTabs() ​

openStateMovedAcrossTabs(storage?): StateMovedAcrossTabs

Defined in: packages/browser/src/stateMovedAcrossTabs.ts:401

OPEN THIS TAB'S END OF THE CROSS-TAB SIGNAL: the state-moved signal (ADR-0083) over a BroadcastChannel, scoped to one store.

ts
// every tab, whether or not it is the one indexing
const tabs = openStateMovedAcrossTabs({databaseName: 'my-app-state'});
tabs.onStateMoved(() => rerenderFromTheStore());
const progress = createProgressReadable(tabs); // "syncing, 400 blocks behind"

// and, in a tab that holds a host, forward what that host tells it
indexer.onStateMoved(tabs.publish);
indexer.onProgress(tabs.publishProgress);

ONE CHANNEL, TWO THINGS A READER CAN BE TOLD ​

They are the same two the PORT carries and they are kept apart for the same reason (ADR-0082): where the fold has got to is a STATE a progress bar renders, and the notification says WHAT MOVED so a cache invalidates narrowly. What they SHARE is the channel, because a reader tab needs one ear open and not two -- a second BroadcastChannel would be a second lifetime, a second name to scope against the app next door, and a second thing that can be silent for one question. The port's own progress push is untouched by any of this: a tab holding a port is told over it, and this is for the tab that holds none.

It is an ADAPTER and not a second semantics ​

What is posted is the value the fold published, or the report the HOST made, and a tab that receives either does exactly what a tab receiving it over a port does. That is the claim ADR-0083 makes about every transport (MessagePort, BroadcastChannel, a server's stream): the same notion, delivered over whichever one a deployment has, so an app that later points at a remote indexer keeps its handler. Nothing here composes, coalesces, filters or re-orders, nothing here produces a notification of its own, and nothing here computes a progress figure -- a reader could not, since the sync cursor is opaque behind the storage seam (ADR-0027), which is precisely why the side that knows has to say.

What it is FOR: a second window that is not a stale window ​

A tab that is not doing the indexing has no host to ask and no fold to subscribe to, so without this it either polls an interval it invented or shows state that stopped moving. Told by the tab that IS indexing, it re-reads the same store through the reader handle it already holds.

WHICH tab indexes is not this, and is deliberately absent ​

There is no election here, no lease, no heartbeat and no "who is publishing" question, and nothing on the wire names the publisher. Electing one indexing tab is one-tab-indexes-and-the-others-read, a rung above this, and half of it built here would be a half the spec then had to adopt or unpick. Until it exists, EVERY indexing tab publishes and EVERY tab listens, which is correct if noisy: a second publisher is a second reason to re-read, and re-reading twice is what re-reading once already was. The writer token is what makes that safe rather than this adapter -- only one tab's fold can be writing the store at all (ADR-0075).

A tab does not hear ITSELF, and that is why this is one object ​

BroadcastChannel never delivers a message back to the object that posted it, so a tab that publishes and listens through THIS one is not told what it just said. That matters in the ordinary shape: a tab hosting its own worker gets every notification over its port already, and hearing the same one again off the channel would be an avoidable duplicate. Holding one channel per tab settles it structurally, with nothing identifying the publisher and nothing to de-duplicate against -- which is the ONE thing a publisher identity would have bought, and it is not worth being half an election for.

Two ends in ONE tab (two separate calls) DO hear each other, which is right: a tab may legitimately hold two, and what it must never do is silently drop a notification because something in the same document published it.

Best-effort, and nothing kept for anybody ​

Nothing is buffered, replayed or acknowledged, and the producer holds nothing per receiving tab -- the property every transport in ADR-0083 rests on. A tab that missed a notification converges on the next one, because the coherence token it carries is one the reader has not seen and says invalidate everything. The limit that follows is stated rather than engineered around: a lost notification on a chain that then goes quiet leaves a reader stale until the next block moves.

Parameters ​

storage? ​

CrossTabStateStorage = {}

Returns ​

StateMovedAcrossTabs