# ComparativeFetchMixin

Auto-generated @jbrowse/mobx-state-tree API for the current JBrowse release — see [pluggable elements](https://jbrowse.org/jb2/docs/developer_guide/) for concepts. Built into JBrowse core. [View source](https://github.com/GMOD/jbrowse-components/blob/main/packages/synteny-core/src/ComparativeFetchMixin.ts).

The fetch foundation of the two comparative displays (LinearSyntenyDisplay,
DotplotDisplay): `KeyedFetchMixin` — `FetchMixin`'s rotation, loading flag,
error, status, durable cancel and retry, plus the `currentFetchKey` /
`loadedFetchKey` freshness pair the LGV global family runs on — and, on top,
the two-way loading answer a shared canvas wants — `loading` off the
`fetchLanded` hook, `refetching` off `isLoading` — and the one-shot
reversed-assembly flag.

Until 2026-09 this was `SyntenyFetchStateMixin`, a second spelling of every
`FetchMixin` member the overlay reads — `fetching` for `isLoading`, its own
`reloadCounter` / `fetchCanceled` / `reload` / `cancelFetchByUser`, a stop
handed back from the installer because the rotation lived there — kept apart
on the grounds ADR-054 gives and ADR-105 retires. `error` is what kept the
four flags below out of that mixin: it is a `BaseDisplay` volatile the flags
read, and declaring a second one to make them type-check is the ADR-041
hazard. `FetchMixin` is the one declaration site that already wins that
compose, so composing it is what lets the flags be getters here.

`loading` and `refetching` are different questions, and the difference
decides whether the user gets a full overlay or a corner chip
(`ComparativeFetchStatus`). `displayPhase` stays per display — it reads the
shared canvas's `surfaceReadiness`, which each view publishes differently —
through `comparativeDisplayPhase`.

Members a composed model contributes are listed here too, so these tables are the whole surface.

## Volatiles

<!-- prettier-ignore -->
| Member | Description | Defined by |
| --- | --- | --- |
| <span id="volatile-assembliesswapped">**assembliesSwapped**</span><br><code>assembliesSwapped: false</code> | Set once at view load by a refName-comparison check, independent of the per-render fetch, so it never re-fires or misfires on zoom. Surfaces through each display's `warnings`. | ComparativeFetchMixin |
| <span id="volatile-loadedfetchkey">**loadedFetchKey**</span><br><code>loadedFetchKey: undefined as string &#124; undefined</code> | <span data-pagefind-ignore>`currentFetchKey` as it stood when the held data was committed — the loaded half of the freshness compare. Written only by `commitFetchResult`, so a display cannot stamp data it did not fetch, and cleared by `reload` for the overlay's sake rather than the refetch's (the skeleton's reload epoch is what overrides its gate). The data itself stays display-owned: arc keeps stale arcs on screen under the loading overlay, HiC the stale matrix, synteny the stale ribbons.</span> | [KeyedFetchMixin](../keyedfetchmixin#volatile-loadedfetchkey) |
| <span id="volatile-activestoptoken">**activeStopToken**</span><br><code>activeStopToken: undefined as StopToken &#124; undefined</code> | <span data-pagefind-ignore>stop token of the in-flight fetch, or undefined when idle</span> | [FetchMixin](../fetchmixin#volatile-activestoptoken) |
| <span id="volatile-fetchgeneration">**fetchGeneration**</span><br><code>fetchGeneration: 0</code> | <span data-pagefind-ignore>bumps at every fetch end; autoruns read it to re-evaluate, and it doubles as the staleness epoch inside runFetch</span> | [FetchMixin](../fetchmixin#volatile-fetchgeneration) |
| <span id="volatile-reloadcounter">**reloadCounter**</span><br><code>reloadCounter: 0</code> | <span data-pagefind-ignore>Bumped by `reload()` and read unconditionally by the fetch autoruns, so a user retry re-runs the body even where nothing else moved — after an error every other fetch input is unchanged. It is also the half that survives a `reload()` override that forgets to invalidate, which is the dead Retry button `makeRetryContractCheck` reports. Declared here because this is the one mixin every fetch foundation composes, the same argument that put `fetchInert` below; the comparative family carried its own until ADR-105.</span> | [FetchMixin](../fetchmixin#volatile-reloadcounter) |
| <span id="volatile-statuswindow">**statusWindow**</span><br><code>statusWindow: createStatusWindow(writeStatus(self))</code> | <span data-pagefind-ignore>This display's status field, and the only thing that writes it: one throttle window, one slot per concurrent operation, so N parallel per-region fetches thin to one stream between them rather than N and a second operation cannot end the first one's label (ADR-081). Lent whole to `createStopTokenRotation` by a display that also runs a bare-autorun fetch — see `StatusReporter`.</span> | [FetchMixin](../fetchmixin#volatile-statuswindow) |
| <span id="volatile-error">**error**</span><br><code>error: undefined as unknown</code> | <span data-pagefind-ignore>last non-abort fetch error, or undefined</span> | [FetchMixin](../fetchmixin#volatile-error) |
| <span id="volatile-statusmessage">**statusMessage**</span><br><code>statusMessage: undefined as string &#124; undefined</code> | <span data-pagefind-ignore>work-in-progress status string</span> | [FetchMixin](../fetchmixin#volatile-statusmessage) |
| <span id="volatile-statusprogress">**statusProgress**</span><br><code>statusProgress: undefined as number &#124; undefined</code> | <span data-pagefind-ignore>determinate progress fraction [0,1] for the current status, or undefined when the in-flight phase is indeterminate</span> | [FetchMixin](../fetchmixin#volatile-statusprogress) |
| <span id="volatile-fetchcanceled">**fetchCanceled**</span><br><code>fetchCanceled: false</code> | <span data-pagefind-ignore>true after the user explicitly cancels a load (the loading overlay's cancel button → `cancelFetchByUser`). A durable, blocking state — unlike `cancelFetch`, it does not retrigger the fetch autoruns — so the load stays stopped until the user retries (`reload`) or the viewport changes. Any new fetch clears it (`runFetch` resets it at the start).</span> | [FetchMixin](../fetchmixin#volatile-fetchcanceled) |
| <span id="volatile-fetchrotation">**fetchRotation**</span><br><span class="cell-more"><button type="button" class="cell-more-trigger"><code>fetchRotation: createStopTokenRotation(self, { statusWindow: se…</code></button><dialog class="cell-dialog"><form method="dialog"><button class="cell-dialog-close" aria-label="Close">✕</button></form><pre><code>fetchRotation: createStopTokenRotation(self, {&#10;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;statusWindow: self.statusWindow,&#10;&#160;&#160;&#160;&#160;&#160;&#160;})</code></pre></dialog></span> | <span data-pagefind-ignore>**The latest-wins machine this mixin is a wrapper around**, and not a second one: `createStopTokenRotation` owns token rotation, the `isCurrent` guard, the status slot and the supersede-versus-end rule (ADR-080, ADR-081), for every fetch in the codebase that has one. `runFetch` adds the observable bookkeeping a display needs on top — `isLoading`, `error`, `fetchGeneration`, `fetchCanceled` — and nothing else.<br><br>It was two implementations of that machine until 2026-08-20, which is how they came to disagree about whether a completed fetch releases its token. A display's *primary* fetch is this wrapper; a second concurrent fetch on the same node holds a rotation of its own, which is why the primitive is the thing that exists and this is the thing built on it (ADR-054 §1, the one section ADR-105 keeps).<br><br>It is lent this display's `statusWindow`, so the fetch takes a slot on the one field rather than opening a second window over it — the whole point of `StatusReporter`.</span> | [FetchMixin](../fetchmixin#volatile-fetchrotation) |

## Getters

<!-- prettier-ignore -->
| Member | Description | Defined by |
| --- | --- | --- |
| <span id="getter-fetchlanded">**fetchLanded**</span><br><code>boolean</code> | Overridable hook (default false): a fetch has completed and data is present, even if it mapped zero features. Not a feature-count test — an empty-but-finished fetch has landed, or an empty plot spins its overlay forever. Each display answers off its own payload field, which survives a `reload()` where `loadedFetchKey` does not: that is what keeps a retry on the corner chip rather than the full overlay.<br><br>Default false is the strict answer — a display that forgets the override shows its first-load overlay forever (diagnosable) rather than reporting done over nothing (silently wrong). | ComparativeFetchMixin |
| <span id="getter-hasdrawable">**hasDrawable**</span><br><code>boolean</code> | Overridable hook: the display holds something an SVG export can draw. Defaults to `fetchLanded`; the dotplot answers with its instance geometry rather than its `geometry` computed, because `svgReady` is polled outside any reactive context and a `geometry` read there recolors every segment per poll. | ComparativeFetchMixin |
| <span id="getter-loading">**loading**</span><br><code>boolean</code> | First load, nothing on screen yet: drives the full striped overlay. Deliberately not `&& isLoading`, which would blink the overlay off during the pre-fetch debounce gap. Excludes `error` so error UI and loading UI never show at once, and `fetchInert` so a display that will never fetch rests instead of spinning on data that is not coming. | ComparativeFetchMixin |
| <span id="getter-refetching">**refetching**</span><br><code>boolean</code> | A fetch is running over a stale plot still on screen (zoom, reorder, pan past the buffer): drives a corner indicator rather than the full overlay, so a viewport change does not mask what is drawn. | ComparativeFetchMixin |
| <span id="getter-svgready">**svgReady**</span><br><code>boolean</code> | Off-screen SVG export gate, the shared `computeSvgReady` policy every display runs. Neither comparative display has a `regionTooLarge` state (LOD gates the fetch, not region size). `fetchInert` is the extra terminal, so an export cannot hang on data the autorun will never fetch, and `fetchCanceled` is terminal for the same reason: durable until Retry, and an export presses nothing. The data half waits out an in-flight same-key retry (`!refetching`) and a stale plot (`dataCurrent`). | ComparativeFetchMixin |
| <span id="getter-viewsignature">**viewSignature**</span><br><code>string &#124; undefined</code> | <span data-pagefind-ignore>Overridable hook, the one freshness input a display supplies: the signature of what the current *view* calls for — its block set (`blockKeySignature`) plus any view-derived fetch tier, like HiC's binsize; both comparative views' region sets, zoom buckets and LOD tier. `undefined` means "not computable yet" (view unmeasured, a prerequisite header still in flight) and holds the fetch off.<br><br>Settings and the adapter are deliberately not the display's half: `currentFetchKey` below appends `rpcPropsCacheKey` and `adapterConfigKey`, so a field added to `rpcProps()` or a track re-pointed in the config editor invalidates held data structurally. HiC hand-folded one settings term in and would have silently missed the second; the comparative family folded the adapter in at its installer and compared without it at its export gate.<br><br>Default `undefined`, so a display that forgets the override never fetches and never exports — hung is diagnosable, stale ships wrong pixels.</span> | [KeyedFetchMixin](../keyedfetchmixin#getter-viewsignature) |
| <span id="getter-datasuperseded">**dataSuperseded**</span><br><code>boolean</code> | <span data-pagefind-ignore>Overridable hook (default false): the held data answers the key, but this display knows it is not what the screen will settle on — a dependent fetch of its own is still out, or a fetch input it writes itself has moved. The same hook `MultiRegionDisplayMixin` declares, for the same reason: the key compare is structurally blind to anything the display fetches outside its primary fetch, and an export sampling `svgReady` in that window paints the half-filled frame.<br><br>A term of `dataCurrent` and NOT of the skeleton's freshness gate, so it holds the export and never re-runs the primary fetch. It fails hung, not stale: a value that latches true parks `awaitSvgReady` on its backstop, so state only what a later commit is guaranteed to clear.</span> | [KeyedFetchMixin](../keyedfetchmixin#getter-datasuperseded) |
| <span id="getter-currentfetchkey">**currentFetchKey**</span><br><code>string &#124; undefined</code> | <span data-pagefind-ignore>Key of the fetch the current view, settings and adapter call for — the display's `viewSignature` plus the serialized `rpcProps()` axis plus the adapter config. The fetch skeleton's freshness key: captured at issue, compared against the stamp above, and written to it at commit.</span> | [KeyedFetchMixin](../keyedfetchmixin#getter-currentfetchkey) |
| <span id="getter-datacurrent">**dataCurrent**</span><br><code>boolean</code> | <span data-pagefind-ignore>The shared freshness answer every foundation gives: data has been committed (`loadedFetchKey` is only ever written beside it), it was fetched for the current view and settings, and the display is not about to supersede it itself. A pan inside the loaded blocks stays current; a block entering, a tier step, a settings change or a `reload()` moves one side of the compare. **What the fetch autorun gates on is the same compare inside `installFetch`**, not this getter — the skeleton owns it so a reload can override it. This one is for the readers outside the fetch, the export gate above all. The per-region twin is `isCacheValid`: what decides a refetch, and deliberately not the whole freshness answer.</span> | [KeyedFetchMixin](../keyedfetchmixin#getter-datacurrent) |
| <span id="getter-isloading">**isLoading**</span><br><code>boolean</code> | <span data-pagefind-ignore>true while a fetch is active</span> | [FetchMixin](../fetchmixin#getter-isloading) |
| <span id="getter-isloadingorcanceled">**isLoadingOrCanceled**</span><br><code>boolean</code> | <span data-pagefind-ignore>`isLoading` widened to cover a user-canceled load. **This, not `isLoading`, is what a `displayPhase` loading term wants.** `cancelFetchByUser` clears the stop token synchronously, so `isLoading` goes false the instant the user clicks Cancel — and the loading overlay that unmounts on it is carrying the Retry button, which is the only way back: the state is deliberately durable, so no autorun restarts the fetch on its own. A bare `isLoading` therefore reads as `ready` over a display that is stopped, empty and offering nothing.<br><br>Arc read `isLoading` directly and had exactly that hole. It is a getter here so no family has to remember the second term.</span> | [FetchMixin](../fetchmixin#getter-isloadingorcanceled) |
| <span id="getter-fetchinert">**fetchInert**</span><br><code>boolean</code> | <span data-pagefind-ignore>Overridable hook (default false): the states where this display deliberately never fetches, so it holds no data and none is coming. Sequence sets it past base resolution ("Zoom in to see sequence"); LD sets it with the triangle toggled off.<br><br>**One hook, three readers**, and that is the whole point — a display that grows such a state has one thing to say rather than three, and the reader it would have forgotten is always the one outside itself:<br><br>- the loading scrim (`computeLoadingTerm`), which otherwise parks over the placeholder, permanently once a cancel has been clicked; - the SVG export (`computeSvgReady`'s `extraTerminal`), whose `awaitSvgReady` is an unbounded `when`, so one such display hangs the whole view's export; - the retry contract check (`makeRetryContractCheck`), which would otherwise report a dead Retry on a display correctly declining to load anything.<br><br>It was three hooks — `loadingSuppressed`, `svgReadyExtraTerminal` on each of the two foundations, and `fetchInert` on the comparative family, which had already collapsed them. Both LGV displays that override it returned one expression for all three, and one of the three was hard-coded `false` on the global family for a while, which is how LD came to be able to express only half its own state. One declaration for all three fetch families since the comparative one composed this mixin (ADR-105), so the retry check reads one field everywhere. ADR-082.<br><br>A hook rather than a `displayPhase` override, because overriding the getter means restating the whole loading condition — which is how sequence came to hold a verbatim copy of the other terms, one `git blame` away from silently missing the next one added.<br><br>It lives **here** because this is the one mixin all three display foundations compose. Same argument, one level down, that put `rendersCanvas` on `RenderLifecycleMixin` beside `canvasDrawn`.</span> | [FetchMixin](../fetchmixin#getter-fetchinert) |
| <span id="getter-awaitingprerequisite">**awaitingPrerequisite**</span><br><code>boolean</code> | <span data-pagefind-ignore>Overridable hook (default false), read only by the retry contract check (`makeRetryContractCheck`): "this run declined because a prerequisite fetch in another autorun has not landed, and its arrival wakes this one again". It **defers** the retry verdict to that later run rather than waiving it, so a display cannot spend its retry on a decline it called preliminary.<br><br>Two displays say it, one per fetch foundation, which is why it lives beside `fetchInert` rather than on either: HiC's contacts fetch declines until `CoreGetInfo` lands, and `MultiSampleVariantBaseModel`'s `fetchNeeded` declines until `sourcesBase` does. Both have a `reload()` that wakes the prerequisite's autorun as well as their own.<br><br>**It has to be strictly narrower than the gate it explains.** One that restates the gate's negation makes every decline a deferred one, so no run is ever judged and the display has silently opted out — an exemption by another name. HiC is in that shape deliberately, because its gate and its prerequisite are one condition; what covers its retry instead is `LinearHicDisplay/infoFetchFailure.test.ts`.<br><br>Not for a display deliberately not fetching at all — that is `fetchInert` above, which the loading scrim and the export read too.</span> | [FetchMixin](../fetchmixin#getter-awaitingprerequisite) |
| <span id="getter-awaitingdependentdata">**awaitingDependentData**</span><br><code>boolean</code> | <span data-pagefind-ignore>Overridable hook (default false), read by `computeLoadingTerm`: a load this display depends on beyond its primary fetch has not landed for the first time, so the frame the primary fetch calls current is still missing something. Multi-way synteny says it until its lane genes and lane links first arrive, so an export or a capture never lands between the ortholog fetch and the gene models that fill the lanes.<br><br>A hook rather than a `displayPhase` override, for the reason `fetchInert` is one: that display carried the override, restating the foundation's two arguments verbatim to append one term, which is the shape that silently misses the next term added.<br><br>Not `dataSuperseded`, which holds the export through every later refetch too: a display saying this wants the scrim on the first landing only, since later lane fetches redraw over lanes already on screen.</span> | [FetchMixin](../fetchmixin#getter-awaitingdependentdata) |
| <span id="getter-rpcpropscachekey">**rpcPropsCacheKey**</span><br><code>string</code> | <span data-pagefind-ignore>The RPC cache key both fetch foundations invalidate on: this display's `rpcProps()` payload serialized to a string. `serializeRpcProps` owns the why, including the silently-dead-axis corollary.<br><br>Here, beside the two hooks above, for the same reason they are: it describes the display, and every foundation composes this mixin. The per-region family watches it from `SettingsInvalidate` and the global one from its fetch autorun's trigger list — one getter and one name, so the two cannot come to invalidate on different axes. The global side built its own local `computed` over the same function until 2026-08, which was the same value under a second spelling.</span> | [FetchMixin](../fetchmixin#getter-rpcpropscachekey) |
| <span id="getter-adapterconfigkey">**adapterConfigKey**</span><br><code>string</code> | <span data-pagefind-ignore>The adapter axis of the same key, watched by the same two readers as `rpcPropsCacheKey`: `SettingsInvalidate` per-region and the global family's `currentFetchKey`. A track re-pointed in the config editor is a different fetch, and until 2026-09 only the comparative family said so.</span> | [FetchMixin](../fetchmixin#getter-adapterconfigkey) |

## Actions

<!-- prettier-ignore -->
| Member | Description | Defined by |
| --- | --- | --- |
| <span id="action-setassembliesswapped">**setAssembliesSwapped**</span><br><code>(arg: boolean) =&gt; void</code> |  | ComparativeFetchMixin |
| <span id="action-commitfetchresult">**commitFetchResult**</span><br><code>(commit: () =&gt; void, key: string) =&gt; void</code> | <span data-pagefind-ignore>The commit half of a keyed fetch: run the display's own store in the same transaction as the key stamp, so no observer can see fresh data under a stale key or the reverse. Being the only writer of `loadedFetchKey` is what makes `dataCurrent` derivable — a display cannot commit without stamping.</span> | [KeyedFetchMixin](../keyedfetchmixin#action-commitfetchresult) |
| <span id="action-reload">**reload**</span><br><code>() =&gt; void</code> | <span data-pagefind-ignore>`FetchMixin.reload` (error, cancel, counter — the shared skeleton's reload epoch is what makes that bump override the freshness gate, even against a fetch that commits mid-reload, so nothing here has to remember to invalidate for the retry's sake) plus this layer's one addition, for the export gate rather than the refetch: dropping the loaded key sends `dataCurrent` false, so an export started after the click waits for the refetch instead of capturing what the retry is about to replace. The data itself survives, staying on screen under that overlay. A subclass whose reload needs extra teardown can override and chain.</span> | [KeyedFetchMixin](../keyedfetchmixin#action-reload) |
| <span id="action-seterror">**setError**</span><br><code>(error?: unknown) =&gt; void</code> |  | [FetchMixin](../fetchmixin#action-seterror) |
| <span id="action-setstatusmessage">**setStatusMessage**</span><br><code>(status?: RpcStatus &#124; undefined) =&gt; void</code> | <span data-pagefind-ignore>Unthrottled: a display writing a phase label by hand must see every write land. The high-frequency RPC stream is thinned one level up, by the streams `self.statusWindow` hands out — and aggregated across them, which a write by hand is not.</span> | [FetchMixin](../fetchmixin#action-setstatusmessage) |
| <span id="action-stopactivefetch">**stopActiveFetch**</span><br><code>() =&gt; void</code> | <span data-pagefind-ignore>Abort the in-flight fetch (if any) and retire its slot. The shared preamble of both cancel paths; the difference between them is only what they do to `fetchCanceled` / `fetchGeneration` afterward.</span> | [FetchMixin](../fetchmixin#action-stopactivefetch) |
| <span id="action-openstatusstream">**openStatusStream**</span><br><code>(isCurrent: () =&gt; boolean) =&gt; StatusStream</code> | <span data-pagefind-ignore>Open one operation's slot on the display's status field: an RPC `statusCallback` throttled through the display-wide window and guarded so a callback that fires after the node is torn down (RPCs resolve their status stream asynchronously) is a safe no-op, plus the `clear` that retires the slot when the operation ends.<br><br>**Every operation on the display opens one**, and the two come back together because an operation that never retires goes on voting for a phase that is over. The viewport fetch (`runFetch`), the clustering run and a lent `createStopTokenRotation` are three of them on one field; before ADR-081 each blanked the field outright and the last one to finish decided what the other two were still saying.<br><br>`isCurrent` is required and has no "node is alive" default, because alive is not the interesting question: a *superseded* fetch is on a live node, and its late status repainting the overlay of the fetch that replaced it is the failure this guards. `runFetch` passes `!isStale()`, which is what every display gets for free through `ctx.statusCallback`; a caller outside a fetch (the clustering autorun) passes its own run's flag. Defaulting to `isAlive` made the loose answer the easy one and five displays took it.<br><br>`runFetch`'s own slot is opened by the rotation; this is for an operation outside any fetch, the tree sidebar's clustering run.</span> | [FetchMixin](../fetchmixin#action-openstatusstream) |
| <span id="action-cancelfetch">**cancelFetch**</span><br><code>() =&gt; void</code> | <span data-pagefind-ignore>cancel any in-flight fetch and bump fetchGeneration (always bumps, so callers can retrigger fetch autoruns even when nothing was in flight). This is the *internal* reset `clearAllRpcData` runs — it clears any user-cancel flag so the retrigger actually re-fetches.</span> | [FetchMixin](../fetchmixin#action-cancelfetch) |
| <span id="action-cancelfetchbyuser">**cancelFetchByUser**</span><br><code>() =&gt; void</code> | <span data-pagefind-ignore>User-initiated cancel from the loading overlay. Stops the in-flight fetch and lands in a durable `fetchCanceled` state. Unlike `cancelFetch`, it does NOT bump fetchGeneration — so the fetch autoruns don't immediately restart the load. The user retries via `reload` (the overlay's retry button), or it clears on the next viewport change.</span> | [FetchMixin](../fetchmixin#action-cancelfetchbyuser) |
| <span id="action-beforedestroy">**beforeDestroy**</span><br><code>() =&gt; void</code> | <span data-pagefind-ignore>Release an in-flight fetch's stop token on teardown. Without this, a display destroyed mid-fetch (track/view closed while loading) never signals the worker to abort the now-useless work, and its in-flight HTTP reads keep downloading. MST auto-chains lifecycle hooks, so a composing display can still define its own beforeDestroy.</span> | [FetchMixin](../fetchmixin#action-beforedestroy) |
| <span id="action-beginfetch">**beginFetch**</span><br><code>(stopToken: StopToken) =&gt; void</code> | <span data-pagefind-ignore>The `onBegin` half of a fetch's bookkeeping: publish the in-flight token (`isLoading`) and clear the durable user-cancel — a load starting is the single clear point that covers every retrigger path (reload, viewport change, settings invalidate). An action of its own for the same reason `endFetch` is: `installFetch`'s lifecycle callbacks run outside any MST flow this mixin owns.</span> | [FetchMixin](../fetchmixin#action-beginfetch) |
| <span id="action-endfetch">**endFetch**</span><br><code>(current: boolean) =&gt; void</code> | <span data-pagefind-ignore>The `finally` half of `runFetch`'s bookkeeping, an action of its own because `runFetchOnce`'s `finally` resumes on a microtask the flow does not own — a direct volatile write there is outside the action context, which is the one thing hoisting the sequence into a shared function costs. The stale branch is a superseded fetch, which must not clear the loading flag the run that replaced it just set. The stop token itself is released by the rotation's own `end()`, one layer down.</span> | [FetchMixin](../fetchmixin#action-endfetch) |
| <span id="action-runfetch">**runFetch**</span><br><code>(work: (ctx: FetchContext) =&gt; Promise&lt;void&gt;) =&gt; Promise&lt;void&gt;</code> | <span data-pagefind-ignore>Run a cancel-safe fetch (cancels any prior). The work callback gets a FetchContext with a stopToken to forward to the RPC and an isStale() check to short-circuit commits once the user has moved on.<br><br>**The MST-flow wrapper over the shared `runFetchOnce` sequence**, and only the wrapper: the begin/clear/run/commit/error/end order, and the rules that keep a superseded run from writing back, are the same function every other fetch in the tree runs. What this adds is the observable bookkeeping a display needs — `isLoading` through `activeStopToken`, `fetchGeneration`, the user-cancel clear — and the flow itself, which is an action, so `work`'s synchronous prefix runs untracked wherever a fetch autorun calls this.</span> | [FetchMixin](../fetchmixin#action-runfetch) |

