LinearSyntenyDisplay
Auto-generated @jbrowse/mobx-state-tree API for the current JBrowse release —
see pluggable elements for concepts. Provided by the
linear-comparative-view plugin.
View source.
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:
{
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.
Members a composed model contributes are listed here too, so these tables are the whole surface.
Properties
| Member | Description | Defined by |
|---|---|---|
typetype: types.literal('LinearSyntenyDisplay') | LinearSyntenyDisplay | |
configurationconfiguration: ConfigurationReference(configSchema) | LinearSyntenyDisplay | |
idid: ElementId | BaseDisplay |
Volatiles
| Member | Description | Defined by |
|---|---|---|
featureDatafeatureData: undefined as SyntenyFeatureData | undefined | LinearSyntenyDisplay | |
instanceDatainstanceData: undefined as SyntenyGeometry | undefined | 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 |
hoveredInstanceIdxhoveredInstanceIdx: -1 | 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 |
clickedInstanceIdxclickedInstanceIdx: -1 | Clicked twin of hoveredInstanceIdx — the instance whose feature stays highlighted after the pointer leaves it. | LinearSyntenyDisplay |
contextMenuAnchor: undefined as ClickCoord | undefined | LinearSyntenyDisplay | |
errorerror: undefined as unknown | BaseDisplay | |
statusMessagestatusMessage: undefined as string | undefined | BaseDisplay | |
statusProgressstatusProgress: undefined as number | undefined | 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. | BaseDisplay |
fetchingfetching: false | True while an RPC fetch is in-flight. Combined with ready it distinguishes a first load (no data yet — full overlay) from a refetch (stale content still on screen — corner indicator). | SyntenyFetchStateMixin |
loadedFetchKeyloadedFetchKey: undefined as string | undefined | Fetch-input signature the currently held data was fetched for (each display builds its own currentFetchKey). Compared against the live inputs in dataCurrent to catch data gone stale after a region/zoom change — including during the pre-refetch debounce gap, where fetching is still false and would otherwise report done on content drawn against the old viewport. | SyntenyFetchStateMixin |
assembliesSwappedassembliesSwapped: false | 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. | SyntenyFetchStateMixin |
reloadCounterreloadCounter: 0 | Bumped by reload(). Read unconditionally by installComparativeFetchAutorun, so it is always in the autorun's dependency set — which is the whole point: after an error the fetch inputs are unchanged, so nothing else would ever refire the autorun and the error banner's Retry would be a button that does nothing. | SyntenyFetchStateMixin |
fetchCanceledfetchCanceled: false | True from the moment the user clicks Cancel on the loading overlay (cancelFetchByUser) until reload() clears it. Durable and blocking: installComparativeFetchAutorun gates on it, so nothing restarts the load in the meantime — not a zoom, not a region change, and no timer.That is the deliberate half. These displays sit on single RPCs that can run for minutes against a remote index, so a cancel any pan quietly undoes is not a cancel, and a retry that re-arms itself hammers the server that just failed. The way back is the overlay's Retry button and nothing else — LoadingOverlay draws it off this flag. | SyntenyFetchStateMixin |
stopActiveFetchstopActiveFetch: () => {} | Stops the in-flight RPC. installComparativeFetchAutorun hands over its stop-token rotation's cancel at install (setStopActiveFetch): the rotation lives in that skeleton's closure, one per install and beside the fetch that uses it, and this mixin holds no fetch machinery of its own (ADR-054).A cancel that cannot reach it is not a cancel. Nothing else rotates the token, so the run the user stopped watching stays isCurrent() and COMMITS its result when it lands — the plot appears, the overlay disappears, and the cancel is undone by the fetch it cancelled. Stopping the token is also the only thing that tells the worker to drop the reads still in flight, which is half of why a user clicks it.A no-op until the skeleton installs, so a display with no fetch autorun can still be asked. | SyntenyFetchStateMixin |
Getters
| Member | Description | Defined by |
|---|---|---|
parentHelperLevelDuck | The level (row gap) this display's track sits on. Found by predicate rather than by hop count — see isSyntenyLevel. | LinearSyntenyDisplay |
levelnumber | Index of the level (row gap) this display draws in: between view.views[level] and view.views[level + 1]. | LinearSyntenyDisplay |
displayKeynumber | Stable backend key under the view-shared backend. | LinearSyntenyDisplay |
heightnumber | LinearSyntenyDisplay | |
adapterConfigRecord<string, unknown> | The track's adapter config verbatim. The body and return type are now identical to BaseDisplayModel.adapterConfig — this override survives only to hold the note below where someone would go to re-add a key.Byte-identical is the point: the worker's adapter cache keys on the config object, so a key the adapter never reads still forks the cache. The two decorative keys this used to add — name, duplicating the adapter's own type, and assemblyNames, read off the display's config schema, which declares no such slot and so always answered undefined — bought a second parse of the same file: one adapter for the ribbons and another for everything reading the track plainly (LGVSyntenyDisplay, the region launch's mate discovery). Both keys were inert at the adapter, which is exactly why nothing caught it. A worker-side value that doesn't belong to the adapter goes as a sibling RPC arg, the way sequenceAdapter does. | LinearSyntenyDisplay |
numFeatsnumber | LinearSyntenyDisplay | |
offscreenMateTally{ refName: string; count: number; }[] | The contigs this level fetched alignments to but cannot draw a ribbon for, largest first, because the facing row is not displaying them. A getter rather than shipped as objects: the tally is one entry per contig, so sorting it is over a scaffold count, while the lanes it is built from are one entry per contig too. What the reader is shown, and what names the rows worth offering to add. | LinearSyntenyDisplay |
targetOffscreenMateTally{ refName: string; count: number; }[] | The same, for the row below: contigs of the QUERY assembly that the row above is not displaying, which alignments anchored on the target axis run to. Always empty without the bidirectional fetch, since those alignments are never requested. | LinearSyntenyDisplay |
culledRibbonMatesCulledRibbonMates | undefined | 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.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.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 |
cappedMeanAlignmentPxnumber | 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.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 |
presentCigarKindsnumber | 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 |
warningsComparativeWarning[] | 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 |
readyboolean | A fetch has completed (data is present, even if it mapped zero features). Not numFeats > 0 — an empty-but-finished fetch is ready, otherwise an empty result spins the loading overlay forever. | LinearSyntenyDisplay |
fetchInertboolean | Overrides SyntenyFetchStateMixin'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 |
fetchFlagsComparativeFetchFlags | LinearSyntenyDisplay | |
loadingboolean | First load: no data has arrived yet. Drives the full striped LoadingOverlay. | LinearSyntenyDisplay |
refetchingboolean | Refetch in-flight: a new fetch is running but stale ribbons are still on screen (e.g. zoom-out across a log2 bucket, region change). Drives a subtle corner indicator instead of the full overlay so the visible ribbons aren't masked on every viewport change. | LinearSyntenyDisplay |
regionSignaturestring | 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 |
currentFetchKeystring | 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. 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 |
dataCurrentboolean | True when the rendered data was fetched for the view's current inputs. Goes false the instant a region/zoom/draw-option change makes the held ribbons stale — including during the pre-refetch debounce gap where fetching is still false so refetching alone can't catch it.This is the shared freshness hook every display foundation answers, expressed the signature-compare way (as arc and dotplot do); the per-region families answer it with spatial coverage instead. | LinearSyntenyDisplay |
svgReadyboolean | Off-screen SVG export gate: "Export SVG" waits on this before drawing (see the SVG export guide). Synteny is not an LGV display — it composes only BaseDisplay with its own fetch — so it gets no mixin svgReady, but it runs the same shared computeSvgReady policy so the two can't drift. Stale-safe on both axes: dataCurrent closes the pre-refetch debounce gap (stale window before fetching flips) and !refetching covers the in-flight RPC, so an export fired right after a zoom/pan waits for fresh ribbons instead of capturing stale ones. No regionTooLarge state (synteny never gates on region size).extraTerminal is fetchInert — the states where the fetch autorun deliberately never runs — so a data-only gate can't hang the export forever on data that is never coming. fetchCanceled is terminal for the same reason: durable until Retry, and an export presses nothing. | LinearSyntenyDisplay |
displayPhaseDisplayStatusPhase | 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.DisplayStatusPhase, not DisplayPhase: the level owns the rendering backend, so this display can never be the one to report a backend failure. | LinearSyntenyDisplay |
| view | LinearSyntenyDisplay | |
computedColorsUint32Array<ArrayBuffer> | undefined | 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.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 |
paintedChromosomeOrderreadonly string[] | undefined | 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.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 |
trackIdstring | LinearSyntenyDisplay | |
trackColorstring | 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 |
colorByModeSyntenyColorBy | 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 |
effectiveColorBySyntenyColorBy | 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. | LinearSyntenyDisplay |
| renderInstanceData | 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 |
tooltipLinesstring[] | undefined | 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 |
connectedViews{…} | undefined | 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 |
bpPerPxBucketKeystring | undefined | 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 |
lodTierLodTier | The detail tier this level's fetch asks the adapter for, resolved here on the main thread so it can enter currentFetchKey.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.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. | LinearSyntenyDisplay |
fetchRegionsRegion[] | 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 target axis is not scoped — the fetch is one-dimensional (query regions in, every mate out) — it only contributes its cumBp index, so it appears in fetchRegionsKey but not here. | LinearSyntenyDisplay |
targetFetchRegionsRegion[] | The target axis's (v1) fetch window, or [] unless the view asked for the bidirectional fetch — in which case the worker queries it too, and recovers the alignments anchored there whose query end is on a contig the row above is not displaying. Empty is the signal, so the RPC argument carries nothing when the setting is off. | LinearSyntenyDisplay |
fetchRegionsKeystring | undefined | 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 |
renderParams{…} | undefined | Per-track render params consumed by the view's aggregator. The view substitutes yTop before handing this to the backend. | LinearSyntenyDisplay |
parentTrackAbstractTrackModel | BaseDisplay | |
RenderingComponentFC<…> | BaseDisplay | |
| DisplayBlurb | BaseDisplay | |
isMinimizedboolean | Returns true if the parent track is minimized. Used to skip expensive operations like autoruns when track is not visible. | BaseDisplay |
hoveredFeatureunknown | 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.Declared here because a cross-display consumer can only read a name the base declares — the same reason SyntenyFetchStateMixin.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.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. | BaseDisplay |
featureNounstring | 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".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.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. 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. | BaseDisplay |
featureWidgetType{ type: string; id: string; } | 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.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. | BaseDisplay |
Methods
| Member | Description | Defined by |
|---|---|---|
getFeature(index: number) => FeatPos | undefined | 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 |
| renderingProps | props passed to the renderer's React "Rendering" component. these are client-side only and never sent to the worker. includes displayModel and callbacks | BaseDisplay |
() => MenuItem[] | BaseDisplay |
Actions
| Member | Description | Defined by |
|---|---|---|
| setRpcData | Set both feature and instance data in one MST action so downstream autoruns (upload, render) fire once per RPC completion, not twice. 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. 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 |
setHoveredInstanceIdx(idx: number) => void | Point the hover at one GPU instance, or -1 for none. The level's setHoveredFeature is what calls this, from a pick hit. | LinearSyntenyDisplay |
setClickedInstanceIdx(idx: number) => void | LinearSyntenyDisplay | |
(anchor: ClickCoord) => void | LinearSyntenyDisplay | |
() => void | LinearSyntenyDisplay | |
setStatusMessage(status?: RpcStatus | undefined) => void | BaseDisplay | |
setError(error?: unknown) => void | BaseDisplay | |
clearHoveredFeature() => void | Overridable hook (default no-op): drop whatever hoveredFeature reports. The writing twin of that getter, and what installClearHoverOnViewportChange calls.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. | BaseDisplay |
reload() => void | base display reload does nothing, see specialized displays for details | BaseDisplay |
setFetching(arg: boolean) => void | SyntenyFetchStateMixin | |
setAssembliesSwapped(arg: boolean) => void | SyntenyFetchStateMixin | |
setStopActiveFetch(stop: () => void) => void | Install-time wiring, called once by installComparativeFetchAutorun — see stopActiveFetch for why the stop arrives from there rather than being built here. | SyntenyFetchStateMixin |
cancelFetchByUser() => void | The loading overlay's Cancel. Stops the in-flight RPC and lands in the durable fetchCanceled state, so the fetch autorun's gate holds until reload() reopens it.Same name as FetchMixin's, which is what lets one overlay set serve all three fetch families (DisplayLoadingOverlayModel names it). What has no twin here is the internal half of that split, cancelFetch — stop, clear the flag, bump a generation to retrigger — because it exists there for clearAllRpcData, and this family has nothing that resets a display behind the user's back. reload() is the only thing that reopens the gate, which is exactly the constraint: retry is a button, never an automatic re-arm.Clears fetching itself, because nothing else will: the in-flight run's finally writes it only while isCurrent(), and the stop above just closed that guard. | SyntenyFetchStateMixin |