# LinearSyntenyDisplay

Auto-generated @jbrowse/mobx-state-tree API for the current JBrowse release — see [pluggable elements](https://jbrowse.org/jb2/docs/developer_guide/) for concepts. Provided by the `linear-comparative-view` plugin. [View source](https://github.com/GMOD/jbrowse-components/blob/main/plugins/linear-comparative-view/src/LinearSyntenyDisplay/model.ts).

## Example usage

A complete `SyntenyTrack` config to paste into `tracks`. The adapter needs
the query (first) and target (second) assembly names, matched by the track's
`assemblyNames`:

```js
{
  type: 'SyntenyTrack',
  trackId: 'hg38_vs_mm10',
  name: 'hg38 vs mm10',
  assemblyNames: ['hg38', 'mm10'],
  adapter: {
    type: 'PAFAdapter',
    uri: 'https://example.com/hg38_vs_mm10.paf',
    queryAssembly: 'hg38',
    targetAssembly: 'mm10',
  },
  displays: [
    {
      type: 'LinearSyntenyDisplay',
      displayId: 'hg38_vs_mm10-LinearSyntenyDisplay',
    },
  ],
}
```

Pure-data model. The containing LinearSyntenyView owns the shared GPU
backend, the upload autorun (which watches every display's `instanceData`
and keys it by `displayKey`), and the render autorun. This display only
carries per-track state and the `renderParams` the view reads out.

The configuration slots for this model are documented on its [config schema page](../../config/linearsyntenydisplay).

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

## Properties

<!-- prettier-ignore -->
| Member | Description | Defined by |
| --- | --- | --- |
| <span id="property-type">**type**</span><br><code>type: types.literal('LinearSyntenyDisplay')</code> |  | LinearSyntenyDisplay |
| <span id="property-configuration">**configuration**</span><br><code>configuration: ConfigurationReference(configSchema)</code> |  | LinearSyntenyDisplay |
| <span id="property-id">**id**</span><br><code>id: ElementId</code> |  | [BaseDisplay](../basedisplay#property-id) |

## Volatiles

<!-- prettier-ignore -->
| Member | Description | Defined by |
| --- | --- | --- |
| <span id="volatile-featuredata">**featureData**</span><br><code>featureData: undefined as SyntenyFeatureData &#124; undefined</code> |  | LinearSyntenyDisplay |
| <span id="volatile-instancedata">**instanceData**</span><br><code>instanceData: undefined as SyntenyGeometry &#124; undefined</code> | Raw GPU-instance geometry produced by the RPC. The view observes this on every display and uploads it to the shared backend keyed by `displayKey`. Clearing it (undefined) triggers backend eviction. | LinearSyntenyDisplay |
| <span id="volatile-hoveredinstanceidx">**hoveredInstanceIdx**</span><br><code>hoveredInstanceIdx: -1</code> | Index into `instanceData` of the GPU instance the pointer is over, or -1. The INSTANCE, not the feature, even though the tooltip and the highlight are both about the feature: a CIGAR-detailed ribbon is a base block plus a tile per indel, and the operator under the cursor is readable from nothing else (`getCigarOpAtInstance`). `getFeature` translates to the feature. Same choice `DotplotDisplay` makes, where the stored index is `hoveredSegmentIdx`. | LinearSyntenyDisplay |
| <span id="volatile-clickedinstanceidx">**clickedInstanceIdx**</span><br><code>clickedInstanceIdx: -1</code> | Clicked twin of `hoveredInstanceIdx` — the instance whose feature stays highlighted after the pointer leaves it. | LinearSyntenyDisplay |
| <span id="volatile-contextmenuanchor">**contextMenuAnchor**</span><br><code>contextMenuAnchor: undefined as ClickCoord &#124; undefined</code> |  | LinearSyntenyDisplay |
| <span id="volatile-error">**error**</span><br><code>error: undefined as unknown</code> |  | [BaseDisplay](../basedisplay#volatile-error) |
| <span id="volatile-statusmessage">**statusMessage**</span><br><code>statusMessage: undefined as string &#124; undefined</code> |  | [BaseDisplay](../basedisplay#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. Set alongside `statusMessage` by `setStatusMessage`; a display that never shows a bar simply leaves it undefined.</span> | [BaseDisplay](../basedisplay#volatile-statusprogress) |
| <span id="volatile-assembliesswapped">**assembliesSwapped**</span><br><code>assembliesSwapped: false</code> | <span data-pagefind-ignore>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`.</span> | [ComparativeFetchMixin](../comparativefetchmixin#volatile-assembliesswapped) |
| <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-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) |
| <span id="volatile-lodtierinfo">**lodTierInfo**</span><br><code>lodTierInfo: undefined as LodTierInfo &#124; undefined</code> |  | [LodTierInfoMixin](../lodtierinfomixin#volatile-lodtierinfo) |

## Getters

<!-- prettier-ignore -->
| Member | Description | Defined by |
| --- | --- | --- |
| <span id="getter-parenthelper">**parentHelper**</span><br><code>LevelDuck</code> | The level (row gap) this display's track sits on. Found by predicate rather than by hop count — see isSyntenyLevel. | LinearSyntenyDisplay |
| <span id="getter-level">**level**</span><br><code>number</code> | Index of the level (row gap) this display draws in: between `view.views[level]` and `view.views[level + 1]`. | LinearSyntenyDisplay |
| <span id="getter-displaykey">**displayKey**</span><br><code>number</code> | Stable backend key under the view-shared backend. | LinearSyntenyDisplay |
| <span id="getter-height">**height**</span><br><code>number</code> |  | LinearSyntenyDisplay |
| <span id="getter-numfeats">**numFeats**</span><br><code>number</code> |  | LinearSyntenyDisplay |
| <span id="getter-culledribbonmates">**culledRibbonMates**</span><br><code>CulledRibbonMates &#124; undefined</code> | Every alignment this display drew geometry for, placed on both axes, so either strip can mark the ones the band is currently culling — the facing end is on a contig that row displays and has scrolled off, which no fetch-time tally can answer. ONE PER ROW, because culling is symmetric: an alignment can be undrawable with its query end off screen and its target end in plain sight, and marking it on the query axis puts it at an x the layout rejects. See `culledRibbonMates`.<br><br>LAZY BY CONSTRUCTION rather than gated on the setting: `offscreenMateStrips` reads `showOffscreenMates` before it reads this, so with the marks off nothing observes it and the pass never runs.<br><br>BOTH PERSPECTIVES ARE BUILT WHATEVER THE FETCH IS, because they are one walk — the query span is what says whether the target span is a mate at all — and whether the lower row's is DRAWN is a separate question that `laneData` answers: a row gets a strip only if the file was queried from it, or the strip stops at the fetch window's edge rather than at the data's. | LinearSyntenyDisplay |
| <span id="getter-cappedmeanalignmentpx">**cappedMeanAlignmentPx**</span><br><code>number</code> | Mean on-screen width (px, axis 0) of this display's alignment blocks with every already-wide block counted as `FADE_WIDE_BLOCK_PX`, or 0 until a fetch lands and both views connect. The fade only affects sub-pixel ribbons (perpW < 1), so a capped mean well under 1 means the view is dominated by thin ribbons — exactly what width-proportional fade declutters, and `LinearSyntenyView.fadeThinAlignments` decides 'auto' off the narrowest of these.<br><br>O(numFeats) per zoom rather than per fetch, because the cap is a px width: 4.2 ms over a million-block whole-genome PAF, where the answer is nowhere near the threshold anyway, and 0.4 ms at a hundred thousand. | LinearSyntenyDisplay |
| <span id="getter-presentcigarkinds">**presentCigarKinds**</span><br><code>number</code> | Which CIGAR indel ops are actually painted in the current geometry. The worker only emits an indel instance for an op wide enough to draw (sub-pixel indels are dropped), so a set bit means a visible-width op of that kind is on screen. The legend keys its indel chips off this rather than the coarse "file has any CIGAR" flag, so whole-genome zoom (every indel sub-pixel) shows no dead insertion/deletion swatch. | LinearSyntenyDisplay |
| <span id="getter-warnings">**warnings**</span><br><code>ComparativeWarning[]</code> | Warnings surfaced in the view header. Flags a likely reversed assembly row order, detected once at view load (only when the two assemblies have distinct chromosome names). | LinearSyntenyDisplay |
| <span id="getter-fetchlanded">**fetchLanded**</span><br><code>boolean</code> | `ComparativeFetchMixin`'s hook: a fetch has completed (data is present, even if it mapped zero features). Not `numFeats > 0` — an empty-but-finished fetch has landed, otherwise an empty result spins the loading overlay forever. | LinearSyntenyDisplay |
| <span id="getter-fetchinert">**fetchInert**</span><br><code>boolean</code> | Overrides `FetchMixin`'s default-false hook with the two states where this display's fetch autorun deliberately never runs: minimized, or a level whose two rows aren't both showing regions. A display in one of them draws nothing (`renderParams` is undefined for exactly the same pair) and has no data coming, so anything waiting on data has to treat it as terminal rather than wait forever. One getter because four places answer it — the autorun's own gate, the loading overlay, the SVG export, and (through the mixin) `displaysSettled`. | LinearSyntenyDisplay |
| <span id="getter-regionsignature">**regionSignature**</span><br><code>string</code> | Contents, order and orientation of both connected views' displayed regions — the inputs the worker's cumBp index is built from, so a change in any of them makes held features stale. Its own getter, not inlined into `currentFetchKey`: this is O(total regions) (a whole-genome view of a scaffold-heavy assembly runs to thousands), while `currentFetchKey`'s other deps flip on every pan past the buffer and every zoom bucket. Split out, MobX memoizes it against `displayedRegions` alone instead of rebuilding the whole string per zoom step. | LinearSyntenyDisplay |
| <span id="getter-viewsignature">**viewSignature**</span><br><code>string</code> | `KeyedFetchMixin`'s hook, this display's half of `currentFetchKey`: the fetch-input signature (region set/order, snapped fetch window, zoom bucket, CIGAR draw options, LOD tier) for the view's current state — the same tracked deps the fetch autorun refetches on. The mixin appends the settings and adapter axes. Reactive: flips the instant any of them changes. Before both connected views are ready it collapses to a degenerate signature (empty region sig, no fetch-window/zoom keys) that no connected fetch can produce — a real fetch requires non-empty displayedRegions — so `dataCurrent` reads false until a real fetch lands. Non-nullable so it mirrors dotplot's. | LinearSyntenyDisplay |
| <span id="getter-displayphase">**displayPhase**</span><br><code>DisplayStatusPhase</code> | The display's own mutually-exclusive state, the way every LGV display publishes one — so `AppReadyMarker` counts this display's fetch, and the app stops reporting itself ready over a ribbon that is still working. Ranked by `comparativeDisplayPhase`, off the shared canvas's `surfaceReadiness` and this display's own fetch state.<br><br>`DisplayStatusPhase`, not `DisplayPhase`: the level owns the rendering backend, so this display can never be the one to report a backend failure. | LinearSyntenyDisplay |
| <span id="getter-view">**view**</span><br><code>ParentViewDuck</code> | The LinearSyntenyView this display's level sits in. Duck-typed, and that is the load-bearing part: this getter is the last edge of the view -> level -> display -> view cycle, and naming the view's model here is what made `levels` an `IAnyModelType` and every read off a level `any`. `parentViewDuck.ts` carries the rest, including why ADR-055's interface form does not substitute on a four-node loop. | LinearSyntenyDisplay |
| <span id="getter-effectivedrawcurves">**effectiveDrawCurves**</span><br><code>boolean</code> | Whether this level draws bezier ribbons: the promotable `drawCurves` slot resolved through the display-type cascade — this track's configured value, the session-wide default the settings menu's pin writes, then `promotedBase` (straight). The view settings checkbox writes the slot too, on every level at once (`setDrawCurves`), so there is no view tier over this. | LinearSyntenyDisplay |
| <span id="getter-effectivedrawlocationmarkers">**effectiveDrawLocationMarkers**</span><br><code>boolean</code> | Whether this level continues the query row's ruler through its ribbons. Resolved exactly as `effectiveDrawCurves` above. | LinearSyntenyDisplay |
| <span id="getter-computedcolors">**computedColors**</span><br><code>Uint32Array&lt;ArrayBuffer&gt; &#124; undefined</code> | Main-thread-computed per-instance colors. Recomputes whenever colorBy, featureData, or instanceData descriptors change — this is the gpuProps half of the rpcProps/gpuProps split. colorBy changes flow through here without touching the RPC.<br><br>`drawLocationMarkers` rides the same lane, which is what keeps it out of `currentFetchKey`: the worker always emits the ticks, and a zero alpha here is what "off" means. So the toggle costs one color-lane patch (`SYNTENY_INSTANCE_CACHE`) rather than a refetch of the whole track. | LinearSyntenyDisplay |
| <span id="getter-groundcolor">**groundColor**</span><br><code>string</code> | The band this display paints into — see `bandGroundColor`. Read here as well as on the level because the location ticks are packed into the color lane, which is this display's, and the SVG export draws through `renderSvg` without a level to ask. | LinearSyntenyDisplay |
| <span id="getter-paintedchromosomeorder">**paintedChromosomeOrder**</span><br><code>readonly string[] &#124; undefined</code> | The chromosome order the chromosome-painting modes color by: the refNames of whichever of this level's two assemblies `effectiveColorBy` resolved to, in the assembly's own order. Undefined for every other mode, and while the assembly is still loading — the color function falls back to its hash there.<br><br>It has to come from the assembly rather than from the features, because a color must not change with which chromosomes happen to be in view. | LinearSyntenyDisplay |
| <span id="getter-trackid">**trackId**</span><br><code>string</code> |  | LinearSyntenyDisplay |
| <span id="getter-trackcolor">**trackColor**</span><br><code>string</code> | This track's slot in the view's palette, used by `colorBy: 'track'`. Assigned by the view, not locally: pinning a color on one track shifts which automatic slots its siblings can take. | LinearSyntenyDisplay |
| <span id="getter-colorbymode">**colorByMode**</span><br><code>SyntenyColorBy</code> | The mode this track renders with, before the per-level 'reference' remap: its own override if the user set one, else the view-wide mode. This is the user-facing answer — menus and the legend title read it, so a uniform 'reference' view reports 'reference' rather than the query/target each level resolved it to. | LinearSyntenyDisplay |
| <span id="getter-effectivecolorby">**effectiveColorBy**</span><br><code>SyntenyColorBy</code> | `colorByMode` resolved for this specific level, for the renderer. 'reference' is a stacked-view mode that colors every level by the shared anchor assembly's chromosome names; each level maps it to 'query' or 'target' depending on which of its two assemblies is the anchor, so the coloring stays consistent across levels. Every other mode passes through.<br><br>A level touching NEITHER anchor side (the C-D level of an A-B-C-D stack anchored on B) falls through to 'query': it cannot color by an assembly it does not draw, so the cross-level continuity the mode promises stops at that level, silently — the legend still reads "reference". Inherent to the mode; surfacing it is parked with the other legend work. | LinearSyntenyDisplay |
| <span id="getter-renderinstancedata">**renderInstanceData**</span><br><span class="cell-more"><button type="button" class="cell-more-trigger"><code>{ colors: Uint32Array&lt;ArrayBuffer&gt;; bp1: Float32Array&lt;ArrayBuff…</code></button><dialog class="cell-dialog"><form method="dialog"><button class="cell-dialog-close" aria-label="Close">✕</button></form><pre><code>{ colors: Uint32Array&lt;ArrayBuffer&gt;; bp1: Float32Array&lt;ArrayBufferLike&gt;; bp2: Float32Array&lt;ArrayBufferLike&gt;; ... 7 more ...; instanceCount: number; } &#124; undefined</code></pre></dialog></span> | Instance data with main-thread-computed colors substituted in. The view's upload autorun reads this, so any colorBy change re-fires upload without an RPC round-trip. | LinearSyntenyDisplay |
| <span id="getter-tooltiplines">**tooltipLines**</span><br><code>string[] &#124; undefined</code> | The hovered ribbon's tooltip, as lines, or undefined when nothing is hovered. Lines rather than an HTML string — see `getTooltipLines`, and `DotplotDisplay.tooltipLines` for the twin. | LinearSyntenyDisplay |
| <span id="getter-connectedviews">**connectedViews**</span><br><code>{…} &#124; undefined</code> | The two adjacent genome views this level draws between, or undefined until both are initialized with regions. A level draws between an adjacent pair, so both render and fetch depend only on those two views, not the whole stack. Single source of truth for that gate. | LinearSyntenyDisplay |
| <span id="getter-bpperpxbucketkey">**bpPerPxBucketKey**</span><br><code>string &#124; undefined</code> | Stable key over the log2 zoom bucket of both connected views. The fetch autorun tracks this (a computed compares its string output) instead of raw bpPerPx, so it only refetches when zoom crosses a doubling rather than on every settled zoom within a bucket. | LinearSyntenyDisplay |
| <span id="getter-lodtier">**lodTier**</span><br><code>LodTier</code> | The detail tier this level's fetch asks the adapter for, resolved here on the main thread so it can enter `currentFetchKey`.<br><br>It cannot be resolved adapter-side from `bpPerPx`: the refetch key carries only `bpPerPxBucketKey`, a log2 bucket, and the default 10000 threshold sits *inside* bucket 13 (8192..16384). Zooming across the threshold within one bucket therefore changed nothing the key could see, and the view kept drawing the coarse tier's gap-free ribbons while reporting itself current.<br><br>The zoom fed in is `min` of both axes, because CIGAR detail is worth drawing when the band is wide on EITHER axis — buildSyntenyGeometry's MIN_CIGAR_PX_WIDTH gate uses `max(widthPx0, widthPx1)` — so dropping to coarse is only safe once BOTH axes are past the threshold. Taking the query axis alone lost indel detail on a band whose query was zoomed out but whose target was zoomed in.<br><br>The tier the adapter will serve, once `lodTierInfo` has landed: a file with no coarse tier is 'fine' at any zoom, and the threshold is clamped up to the file's `--coarse` bound. | LinearSyntenyDisplay |
| <span id="getter-coarsewalkisapproximate">**coarseWalkIsApproximate**</span><br><code>boolean</code> | True while the served tier is the coarse one and the zoom is finer than the fold's `--coarse` bound, which only a pinned "Alignment blocks only" reaches. A walk through a coarse CIGAR is within that bound of the alignment's real path, and the bound is sub-pixel at or past it and visible below it, so the follow reports a placement walked there as approximate. | LinearSyntenyDisplay |
| <span id="getter-fetchregions">**fetchRegions**</span><br><code>Region[]</code> | The query axis's (v0) fetch window, and the regions the fetch actually sends: the visible content blocks expanded by the shared pan buffer and snapped outward to a buffer-sized grid, so a pan within the buffer neither refetches nor exposes an unfetched strip. The worker emits geometry for exactly this window, so the two cannot disagree. The target axis is not scoped — the fetch is one-dimensional (query regions in, every mate out) — so its window is `targetWindowRegions`. | LinearSyntenyDisplay |
| <span id="getter-targetwindowregions">**targetWindowRegions**</span><br><code>Region[]</code> | The target axis's (v1) snapped window: what the LOWER row can pan across before the fetch key rolls over, in the key and sent to the worker either way, because `buildSyntenyGeometry` emits detail for it exactly as it does for the query window. What the bidirectional setting decides is only whether the worker also QUERIES it, recovering the alignments anchored there whose query end is on a contig the row above is not displaying.<br><br>THE EMIT, not the projection: corners are stored base-relative and the `panPx` uniforms compensate a pan at draw time, which is the whole reason panning does not inherently need a refetch. And "the lower row" rather than `v1`, which the worker spells the other way round — `executeSyntenyFeaturesAndPositions` binds `v1 = queryView`. | LinearSyntenyDisplay |
| <span id="getter-fetchregionskey">**fetchRegionsKey**</span><br><code>string &#124; undefined</code> | Stable key over the *snapped* fetch window of both connected views. The fetch autorun tracks this (through `currentFetchKey`) so a scroll/zoom that moves either snapped window refetches, while a sub-buffer pan (identical snapped windows) does not — a MobX computed only notifies when its string output changes. Built from the same `fetchRegions` the worker is handed, so the key can't describe a window the fetch didn't use. | LinearSyntenyDisplay |
| <span id="getter-renderparams">**renderParams**</span><br><code>{…} &#124; undefined</code> | Per-track render params consumed by the view's aggregator. yTop is 0 here: the level's canvas is the band, and only the multiway display stacks tracks within one. | LinearSyntenyDisplay |
| <span id="getter-parenttrack">**parentTrack**</span><br><code>AbstractTrackModel</code> |  | [BaseDisplay](../basedisplay#getter-parenttrack) |
| <span id="getter-renderingcomponent">**RenderingComponent**</span><br><code>FC&lt;…&gt;</code> |  | [BaseDisplay](../basedisplay#getter-renderingcomponent) |
| <span id="getter-displayblurb">**DisplayBlurb**</span><br><span class="cell-more"><button type="button" class="cell-more-trigger"><code>FC&lt;{ model: ModelInstanceTypeProps&lt;{ id: IOptionalIType&lt;ISimple…</code></button><dialog class="cell-dialog"><form method="dialog"><button class="cell-dialog-close" aria-label="Close">✕</button></form><pre><code>FC&lt;{ model: ModelInstanceTypeProps&lt;{ id: IOptionalIType&lt;ISimpleType&lt;string&gt;, [undefined]&gt;; type: ISimpleType&lt;string&gt;; }&gt; &amp; { ...; } &amp; { ...; } &amp; IStateTreeNode&lt;...&gt;; }&gt; &#124; null</code></pre></dialog></span> |  | [BaseDisplay](../basedisplay#getter-displayblurb) |
| <span id="getter-adapterconfig">**adapterConfig**</span><br><code>Record&lt;string, unknown&gt;</code> |  | [BaseDisplay](../basedisplay#getter-adapterconfig) |
| <span id="getter-isminimized">**isMinimized**</span><br><code>boolean</code> | <span data-pagefind-ignore>Returns true if the parent track is minimized. Used to skip expensive operations like autoruns when track is not visible.</span> | [BaseDisplay](../basedisplay#getter-isminimized) |
| <span id="getter-hoveredfeature">**hoveredFeature**</span><br><code>unknown</code> | <span data-pagefind-ignore>Overridable hook (default `undefined`): what the pointer is currently over, for readers **outside** the display. `LinearGenomeViewContainer` publishes it to `session.hovered`, the view-wide "what is the user pointing at" channel a plugin can subscribe to.<br><br>Declared here because a cross-display consumer can only read a name the base declares — the same reason `FetchMixin.fetchInert` is a hook rather than a getter each display invents. The container used to read `featureUnderMouse`, which only the wiggle, alignments and Manhattan families spelled that way — canvas said `hoveredFeature`, variants `hoveredGenotype` — so the channel carried a hover from a third of the display types and nothing said which. It also asked only `displays[0]` of each track.<br><br>`unknown` because the payload genuinely differs — a read, a wiggle bin, a SNP, a genotype cell — and `session.hovered` is typed to match ("can be anything; code that wants to deal with this should examine it"). Narrow it in the override.</span> | [BaseDisplay](../basedisplay#getter-hoveredfeature) |
| <span id="getter-featurenoun">**featureNoun**</span><br><code>string</code> | <span data-pagefind-ignore>Overridable hook (default `'feature'`): the SINGULAR word for one of the things this display draws, as a menu row or a chip says it — "Hide this read", "Showing 3 variants".<br><br>Declared here for the same reason as `hoveredFeature` above: it is read across the display boundary, by chrome that has no idea which display it is drawing for (`SoloSelectionChip`, alignments' group-label overlay), and a name only the base declares is a name every such consumer can rely on. Two displays declared it independently and one of those declarations WAS this default.<br><br>**A control keeps the generic word; content takes this one.** "Variant height" reads as a different setting from "Feature height" when it is the same one, so the shared menus stay on "feature" however the display answers here, and the noun varies where it names what the user is looking at — "Showing 3 variants", "Hide this read". A display drawing something the generic word already fits is right to leave this alone.<br><br>Distinct from the per-hit noun a context menu takes off the clicked item's own `type` ("mRNA", "gene"); that names one annotation, this names what the track holds. The hit noun falls back to this.</span> | [BaseDisplay](../basedisplay#getter-featurenoun) |
| <span id="getter-featurewidgettype">**featureWidgetType**</span><br><code>{ type: string; id: string; }</code> | <span data-pagefind-ignore>Overridable hook: which widget `openFeatureWidget` opens for one of this display's features. The default is the generic one, which is what a display drawing plain features wants and what the canvas base spelled out by hand.<br><br>An override is a display whose features have a vocabulary of their own — a read, a variant, a synteny block — and the `id` is deliberately part of it: two displays naming one id share the drawer panel, which is the behaviour when the two are showing the same kind of thing.</span> | [BaseDisplay](../basedisplay#getter-featurewidgettype) |
| <span id="getter-hasdrawable">**hasDrawable**</span><br><code>boolean</code> | <span data-pagefind-ignore>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.</span> | [ComparativeFetchMixin](../comparativefetchmixin#getter-hasdrawable) |
| <span id="getter-loading">**loading**</span><br><code>boolean</code> | <span data-pagefind-ignore>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.</span> | [ComparativeFetchMixin](../comparativefetchmixin#getter-loading) |
| <span id="getter-refetching">**refetching**</span><br><code>boolean</code> | <span data-pagefind-ignore>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.</span> | [ComparativeFetchMixin](../comparativefetchmixin#getter-refetching) |
| <span id="getter-svgready">**svgReady**</span><br><code>boolean</code> | <span data-pagefind-ignore>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`).</span> | [ComparativeFetchMixin](../comparativefetchmixin#getter-svgready) |
| <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-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) |

## Methods

<!-- prettier-ignore -->
| Member | Description | Defined by |
| --- | --- | --- |
| <span id="method-getfeature">**getFeature**</span><br><code>(index: number) =&gt; FeatPos &#124; undefined</code> | The parent feature under an INSTANCE index (what the pick engine and the hover/click state carry). Without instanceData the two spaces coincide. Deliberately not `instanceFeatureIdx[index] ?? index`: an out-of-range instance index reads `undefined` there, and falling back to the raw index would silently return a different feature rather than nothing. | LinearSyntenyDisplay |
| <span id="method-renderingprops">**renderingProps**</span><br><span class="cell-more"><button type="button" class="cell-more-trigger"><code>() =&gt; { displayModel: ModelInstanceTypeProps&lt;{ id: IOptionalITy…</code></button><dialog class="cell-dialog"><form method="dialog"><button class="cell-dialog-close" aria-label="Close">✕</button></form><pre><code>() =&gt; { displayModel: ModelInstanceTypeProps&lt;{ id: IOptionalIType&lt;…&gt;; type: ISimpleType&lt;string&gt;; }&gt; &amp; { ...; } &amp; { ...; } &amp; { ...; } &amp; IStateTreeNode&lt;...&gt;; }</code></pre></dialog></span> | <span data-pagefind-ignore>props passed to the renderer's React "Rendering" component. these are client-side only and never sent to the worker. includes displayModel and callbacks</span> | [BaseDisplay](../basedisplay#method-renderingprops) |
| <span id="method-trackmenuitems">**trackMenuItems**</span><br><code>() =&gt; MenuItem[]</code> |  | [BaseDisplay](../basedisplay#method-trackmenuitems) |

## Actions

<!-- prettier-ignore -->
| Member | Description | Defined by |
| --- | --- | --- |
| <span id="action-setrpcdata">**setRpcData**</span><br><span class="cell-more"><button type="button" class="cell-more-trigger"><code>(featureData: SyntenyFeatureData &#124; undefined, instanceData: Syn…</code></button><dialog class="cell-dialog"><form method="dialog"><button class="cell-dialog-close" aria-label="Close">✕</button></form><pre><code>(featureData: SyntenyFeatureData &#124; undefined, instanceData: SyntenyGeometry &#124; undefined) =&gt; void</code></pre></dialog></span> | Set both feature and instance data in one MST action so downstream autoruns (upload, render) fire once per RPC completion, not twice.<br><br>The hover/click indices address the OUTGOING instanceData, so they are meaningless against the incoming arrays and must be dropped here — a surviving index either highlights an unrelated ribbon (still in range) or writes NaN into the clickedFeatureId uniform (out of range). A refetch is a zoom/pan/mode change, after which the pointer is no longer over whatever it was hovering anyway.<br><br>An open context menu goes with them, for the same reason one step further along. It does not hold an index — it holds a resolved feature and the window a panel was showing — but both describe the fetch that has just been replaced, and its items act on them ASYNCHRONOUSLY, after a click. Feature ids are not comparable across a tiered PIF's two tiers, so a menu that outlived a tier flip would ask the worker to resolve an id the new tier does not have, and get back the same `undefined` a CIGAR-less block gives — which is what `moveMatchingPanel` then reports it as. | LinearSyntenyDisplay |
| <span id="action-sethoveredinstanceidx">**setHoveredInstanceIdx**</span><br><code>(idx: number) =&gt; void</code> | Point the hover at one GPU instance, or -1 for none. The level's `setHoveredFeature` is what calls this, from a pick hit. | LinearSyntenyDisplay |
| <span id="action-setclickedinstanceidx">**setClickedInstanceIdx**</span><br><code>(idx: number) =&gt; void</code> |  | LinearSyntenyDisplay |
| <span id="action-opencontextmenu">**openContextMenu**</span><br><code>(anchor: ClickCoord) =&gt; void</code> |  | LinearSyntenyDisplay |
| <span id="action-closecontextmenu">**closeContextMenu**</span><br><code>() =&gt; void</code> |  | LinearSyntenyDisplay |
| <span id="action-setstatusmessage">**setStatusMessage**</span><br><code>(status?: RpcStatus &#124; undefined) =&gt; void</code> |  | [BaseDisplay](../basedisplay#action-setstatusmessage) |
| <span id="action-seterror">**setError**</span><br><code>(error?: unknown) =&gt; void</code> |  | [BaseDisplay](../basedisplay#action-seterror) |
| <span id="action-clearhoveredfeature">**clearHoveredFeature**</span><br><code>() =&gt; void</code> | <span data-pagefind-ignore>Overridable hook (default no-op): drop whatever `hoveredFeature` reports. The writing twin of that getter, and what `installClearHoverOnViewportChange` calls.<br><br>A display that STORES its hover owes an override; one that derives it from the live pointer (MAF, Hi-C, LD) owes nothing, and the default costs it nothing. Declared here so the clear can be installed for every display rather than remembered per display — forgetting it is the failure ARCHITECTURE.md's stored-hover section is about, and it used to be six closures at six call sites, which is six chances to omit one.</span> | [BaseDisplay](../basedisplay#action-clearhoveredfeature) |
| <span id="action-reload">**reload**</span><br><code>() =&gt; void</code> | <span data-pagefind-ignore>base display reload does nothing, see specialized displays for details</span> | [BaseDisplay](../basedisplay#action-reload) |
| <span id="action-applydisplaysettings">**applyDisplaySettings**</span><br><span class="cell-more"><button type="button" class="cell-more-trigger"><code>(settings: Record&lt;…&gt;, options?: { allowSetters?: boolean &#124; unde…</code></button><dialog class="cell-dialog"><form method="dialog"><button class="cell-dialog-close" aria-label="Close">✕</button></form><pre><code>(settings: Record&lt;…&gt;, options?: { allowSetters?: boolean &#124; undefined; } &#124; undefined) =&gt; { applied: string[]; unapplied: string[]; failed: { key: string; error: string; }[]; }</code></pre></dialog></span> | <span data-pagefind-ignore>Apply a bag of display settings to the LIVE display, and report what landed. Each key runs through the display config schema's `preProcessSnapshot` (shorthand expansions, legacy-key migrations — the same lowering a session spec's inline track keys get in `showTrackGeneric`), then writes the matching config slot. Keys that are not slots come back in `unapplied` rather than vanishing: the settings vocabulary's historical failure mode is the silently dropped key.<br><br>`allowSetters` additionally routes a non-slot key to a conventionally named single-argument `set<Key>` action. Opt-in, never the default: the declarative surfaces (session specs, share links, embeds) feed this whole bags of untyped JSON, and a blanket fallback would let them reach internal setters (`setError`, `setScrollTop`, ...) and call multi-argument setters with one argument. A caller that wants a specific action can also simply call it.<br><br>A key whose write THREW is reported separately, in `failed` — it is the only one of the three that means the caller got something wrong, and the only one worth a notification. `unapplied` is not: at the `showTrackGeneric` call site it also collects keys that surface consumed itself (`type`) and MST display props the display snapshot already applied (`resolution`), so treating it as "dropped" would report a correct call as broken.<br><br>Per-key errors do not abort the rest of the bag — a caller mid-`showTrack` has already pushed the track, and one rejected value must not strand a half-configured track.</span> | [BaseDisplay](../basedisplay#action-applydisplaysettings) |
| <span id="action-setassembliesswapped">**setAssembliesSwapped**</span><br><code>(arg: boolean) =&gt; void</code> |  | [ComparativeFetchMixin](../comparativefetchmixin#action-setassembliesswapped) |
| <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-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) |
| <span id="action-setlodtierinfo">**setLodTierInfo**</span><br><code>(info: LodTierInfo &#124; undefined) =&gt; void</code> |  | [LodTierInfoMixin](../lodtierinfomixin#action-setlodtierinfo) |

