@jbrowse/core 5.0.0-beta.11 → 5.0.0-beta.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/README.md +97 -75
  2. package/esm/BaseFeatureWidget/BaseFeatureDetail/Attributes.d.ts +3 -0
  3. package/esm/BaseFeatureWidget/BaseFeatureDetail/Attributes.js +14 -1
  4. package/esm/BaseFeatureWidget/SequenceFeatureDetails/SequenceFeatureDetails.d.ts +2 -0
  5. package/esm/BaseFeatureWidget/SequenceFeatureDetails/SequenceFeatureDetails.js +5 -5
  6. package/esm/BaseFeatureWidget/SequenceFeatureDetails/SequenceFeaturePanel.js +1 -1
  7. package/esm/BaseFeatureWidget/SequenceFeatureDetails/dialogs/FeatureSequenceDialog.d.ts +3 -1
  8. package/esm/BaseFeatureWidget/SequenceFeatureDetails/dialogs/FeatureSequenceDialog.js +2 -2
  9. package/esm/BaseFeatureWidget/SequenceFeatureDetails/featureTypeUtil.d.ts +2 -1
  10. package/esm/BaseFeatureWidget/SequenceFeatureDetails/featureTypeUtil.js +7 -14
  11. package/esm/BaseFeatureWidget/SequenceFeatureDetails/seqtypes/CDNASequence.js +3 -3
  12. package/esm/BaseFeatureWidget/stateModelFactory.d.ts +7 -0
  13. package/esm/BaseFeatureWidget/stateModelFactory.js +11 -0
  14. package/esm/BaseFeatureWidget/types.d.ts +2 -0
  15. package/esm/PluginManager.d.ts +6 -3
  16. package/esm/PluginManager.js +6 -3
  17. package/esm/ReExports/coreModules.generated.js +291 -285
  18. package/esm/ReExports/coreWorkerModules.generated.js +288 -283
  19. package/esm/ReExports/list.js +7 -0
  20. package/esm/TextSearch/TextSearchManager.d.ts +3 -3
  21. package/esm/TextSearch/TextSearchManager.js +6 -13
  22. package/esm/configuration/configurationSchema.d.ts +33 -95
  23. package/esm/configuration/configurationSchema.js +50 -218
  24. package/esm/configuration/configurationSlot.d.ts +18 -64
  25. package/esm/configuration/configurationSlot.js +31 -97
  26. package/esm/configuration/formatDetailsConfigSchema.js +2 -0
  27. package/esm/configuration/getConf.d.ts +7 -36
  28. package/esm/configuration/getConf.js +7 -37
  29. package/esm/configuration/index.d.ts +3 -2
  30. package/esm/configuration/index.js +2 -1
  31. package/esm/configuration/partitionAdvanced.d.ts +12 -0
  32. package/esm/configuration/partitionAdvanced.js +42 -0
  33. package/esm/configuration/plot.d.ts +6 -0
  34. package/esm/configuration/plot.js +34 -1
  35. package/esm/configuration/readConfObject.d.ts +15 -17
  36. package/esm/configuration/readConfObject.js +19 -50
  37. package/esm/configuration/schemaTypes.js +5 -1
  38. package/esm/configuration/snapshotPreprocess.js +1 -3
  39. package/esm/configuration/types.d.ts +60 -159
  40. package/esm/pluggableElementTypes/DisplayType.d.ts +7 -0
  41. package/esm/pluggableElementTypes/DisplayType.js +7 -0
  42. package/esm/pluggableElementTypes/RpcMethodType.d.ts +7 -0
  43. package/esm/pluggableElementTypes/RpcMethodType.js +9 -0
  44. package/esm/pluggableElementTypes/addMenuItems.d.ts +2 -1
  45. package/esm/pluggableElementTypes/addMenuItems.js +2 -1
  46. package/esm/pluggableElementTypes/models/BaseDisplayModel.d.ts +3 -2
  47. package/esm/pluggableElementTypes/models/BaseDisplayModel.js +5 -5
  48. package/esm/pluggableElementTypes/models/BaseTrackModel.d.ts +2 -4
  49. package/esm/pluggableElementTypes/models/BaseTrackModel.js +2 -4
  50. package/esm/pluggableElementTypes/models/baseTrackConfig.js +3 -0
  51. package/esm/rpc/RpcRegistry.d.ts +2 -1
  52. package/esm/rpc/byteBudget.d.ts +7 -0
  53. package/esm/rpc/byteBudget.js +10 -0
  54. package/esm/rpc/coreRpcMethods.d.ts +1 -1
  55. package/esm/rpc/coreRpcMethods.js +1 -1
  56. package/esm/rpc/explainTransferError.js +3 -3
  57. package/esm/rpc/methods/CoreLoadTrackCode.d.ts +17 -0
  58. package/esm/rpc/methods/CoreLoadTrackCode.js +24 -0
  59. package/esm/ui/AppLogo.js +43 -3
  60. package/esm/ui/buildExtraTrackMenuItems.d.ts +2 -2
  61. package/esm/ui/colors.d.ts +41 -30
  62. package/esm/ui/colors.js +81 -44
  63. package/esm/ui/multiTrackMenuItems.d.ts +2 -2
  64. package/esm/util/alignedBytes.d.ts +19 -0
  65. package/esm/util/alignedBytes.js +37 -0
  66. package/esm/util/analytics.d.ts +5 -6
  67. package/esm/util/analytics.js +5 -3
  68. package/esm/util/bezierConnector.js +5 -1
  69. package/esm/util/categoricalField.d.ts +23 -3
  70. package/esm/util/categoricalField.js +36 -3
  71. package/esm/util/cellMatches.js +14 -14
  72. package/esm/util/cellsStep.d.ts +3 -5
  73. package/esm/util/cellsStep.js +31 -29
  74. package/esm/util/color-bits/parse.js +22 -11
  75. package/esm/util/colorRamp.js +6 -0
  76. package/esm/util/colorScale.d.ts +9 -2
  77. package/esm/util/colorScale.js +33 -2
  78. package/esm/util/colorSchemes.d.ts +4 -2
  79. package/esm/util/colorSchemes.js +4 -1
  80. package/esm/util/isoformRank.d.ts +39 -0
  81. package/esm/util/isoformRank.js +118 -0
  82. package/esm/util/markEncodingTypes.d.ts +3 -1
  83. package/esm/util/quantileExtent.d.ts +22 -0
  84. package/esm/util/quantileExtent.js +24 -1
  85. package/esm/util/systemColorScheme.d.ts +4 -0
  86. package/esm/util/systemColorScheme.js +21 -0
  87. package/esm/util/tabix.d.ts +1 -1
  88. package/esm/util/tabix.js +1 -1
  89. package/esm/util/temporaryAssembly.d.ts +2 -2
  90. package/esm/util/temporaryAssembly.js +2 -3
  91. package/esm/util/trackConfigDelta.d.ts +1 -1
  92. package/esm/util/tracks.d.ts +25 -15
  93. package/esm/util/tracks.js +67 -43
  94. package/esm/util/types/index.d.ts +15 -7
  95. package/esm/util/types/index.js +21 -2
  96. package/esm/util/useRowVirtualScroll.d.ts +1 -0
  97. package/package.json +7 -4
  98. package/esm/rpc/methods/CoreLoadAdapterCode.d.ts +0 -13
  99. package/esm/rpc/methods/CoreLoadAdapterCode.js +0 -16
@@ -10,22 +10,13 @@ import { preProcessSnapshotWith } from './snapshotPreprocess.js';
10
10
  function hookList(hook) {
11
11
  return hook === undefined ? [] : Array.isArray(hook) ? hook : [hook];
12
12
  }
13
- // base first, then child. Returns the single function unchanged when only one
14
- // side declares the hook, so the common case never allocates a chain.
15
13
  function chainHooks(base, child) {
16
14
  return base && child
17
15
  ? [...hookList(base), ...hookList(child)]
18
16
  : (child ?? base);
19
17
  }
20
- /**
21
- * Fold a subclass's schema definition over its `baseConfiguration`'s. New slots
22
- * are added and sub-schema entries replaced wholesale, but a slot the subclass
23
- * **redeclares merges field-by-field over the base's** — so an override states
24
- * only what actually differs and inherits the rest. (`type` comes along
25
- * regardless: it is what marks an entry as a slot rather than a nested
26
- * sub-schema, per `isSlotDefinitionEntry`.) The cases are pinned by the
27
- * "baseConfiguration slot override merge" tests.
28
- */
18
+ // A redeclared slot merges field-by-field over the base's, so an override states
19
+ // only what differs; a sub-schema or a constant is replaced whole.
29
20
  function mergeSchemaDefinition(baseDefinition, childDefinition) {
30
21
  const merged = {
31
22
  ...baseDefinition,
@@ -33,8 +24,6 @@ function mergeSchemaDefinition(baseDefinition, childDefinition) {
33
24
  };
34
25
  for (const [slot, childEntry] of Object.entries(childDefinition)) {
35
26
  const baseEntry = baseDefinition[slot];
36
- // both sides must be slot definitions: a sub-schema (or a constant) is an
37
- // opaque entry with no fields to fold, so it keeps replace semantics
38
27
  if (isSlotDefinitionEntry(baseEntry) && isSlotDefinitionEntry(childEntry)) {
39
28
  merged[slot] = { ...baseEntry, ...childEntry };
40
29
  }
@@ -58,32 +47,19 @@ function preprocessConfigurationSchemaArguments(modelName, inputSchemaDefinition
58
47
  if (typeof modelName !== 'string') {
59
48
  throw new Error('first arg must be string name of the model that this config schema goes with');
60
49
  }
61
- // if we have a base configuration schema that we are
62
- // extending, grab the slot definitions from that
63
50
  let schemaDefinition = inputSchemaDefinition;
64
51
  let options = inputOptions;
65
52
  const baseMeta = inputOptions.baseConfiguration
66
53
  ? getConfigurationSchemaMetadata(inputOptions.baseConfiguration)
67
54
  : undefined;
68
- // A base with no registry entry used to be skipped in silence, producing a
69
- // schema missing every inherited slot with nothing thrown anywhere, and
70
- // reachable without doing anything obviously wrong:
71
- // `pluginManager.pluggableConfigSchemaType(…)` hands back a `types.union`,
72
- // which carries no slot table of its own.
73
55
  if (inputOptions.baseConfiguration && !baseMeta) {
74
56
  throw new Error(`${modelName}'s baseConfiguration is not a configuration schema: it has no registered slot table, so every slot it was meant to inherit would be dropped silently. Pass the type ConfigurationSchema() returned, not a union (pluginManager.pluggableConfigSchemaType) or a plain MST model.`);
75
57
  }
76
58
  if (baseMeta) {
77
59
  schemaDefinition = mergeSchemaDefinition(baseMeta.definition, schemaDefinition);
78
- // Everything else merges as a shallow `{...base, ...child}` spread, where
79
- // the child's value replaces the base's. The four hooks, `requires` and
80
- // `retired` must not: `createBaseTrackConfig` alone declares two of the hooks, so
81
- // replace-semantics meant no track config schema could ever declare its
82
- // own without silently dropping display-stub injection and the legacy-key
83
- // migration, and a subclass stating one requirement would have dropped
84
- // every one its base stated. They compose instead, base first, and
85
- // `retired` merges per key so a subclass adds a spelling without dropping
86
- // the ones its base retired. See MergedConfigurationSchemaOptions.
60
+ // The hooks, `requires` and `retired` compose, base first, where every
61
+ // other option is replaced by the child's: a track schema's own
62
+ // preProcessSnapshot must not drop the base's display-stub injection.
87
63
  const basePreProcess = baseMeta.options.preProcessSnapshot;
88
64
  const childPreProcess = inputOptions.preProcessSnapshot;
89
65
  const requires = [
@@ -109,7 +85,6 @@ function preprocessConfigurationSchemaArguments(modelName, inputSchemaDefinition
109
85
  return { schemaDefinition, options };
110
86
  }
111
87
  function makeConfigurationSchemaModel(modelName, schemaDefinition, options) {
112
- // now assemble the MST model of the configuration schema
113
88
  const modelDefinition = {};
114
89
  if (options.explicitlyTyped) {
115
90
  modelDefinition.type = types.optional(types.literal(modelName), modelName);
@@ -118,30 +93,19 @@ function makeConfigurationSchemaModel(modelName, schemaDefinition, options) {
118
93
  if (identifier) {
119
94
  modelDefinition[identifier] = types.identifier;
120
95
  }
121
- // String/number entries in the schema definition become volatile instance
122
- // constants (read via `model.someName`). Per-slot metadata lives in the
123
- // schema registry (a WeakMap keyed by the MST type, see schemaRegistry.ts),
124
- // not on the instance.
125
96
  const volatileConstants = {};
126
- // The members `setSubschema` replaces: a single sub-schema, swapped for a
127
- // node built from the data, or a collection of them, assigned whole so MST
128
- // reconciles the entries. Collected as the loop classifies each entry.
129
97
  const subSchemaKeys = new Set();
130
98
  const collectionKeys = new Set();
131
- // The actual slots, which is a strictly smaller set than `modelDefinition`:
132
- // that also holds the sub-schema properties and the identifier, neither of
133
- // which setSlot may write. Same collect-as-you-classify as subSchemaKeys.
99
+ // narrower than `modelDefinition`, which also holds the identifier and the
100
+ // sub-schemas, neither of which setSlot may write
134
101
  const slotKeys = new Set();
135
102
  const storesNull = new Set();
136
103
  const featureFields = new Set();
137
104
  const takesNoCallback = new Map();
138
105
  for (const [slotName, slotDefinition] of Object.entries(schemaDefinition)) {
139
106
  if (isConfigurationSchemaType(slotDefinition)) {
140
- // a sub-configuration. A bare sub-schema is already stripDefault-wrapped
141
- // (so it strips when all-default); an array/map of sub-schemas gets
142
- // wrapped so an empty collection is likewise omitted from the snapshot,
143
- // unless it is wrapped in a default of its own: a display built on
144
- // another names its own default plot that way.
107
+ // an empty collection strips from the snapshot as an all-default
108
+ // sub-schema does, unless it names a default of its own
145
109
  if (isArrayType(slotDefinition) || isMapType(slotDefinition)) {
146
110
  modelDefinition[slotName] = isOptionalType(slotDefinition)
147
111
  ? slotDefinition
@@ -157,7 +121,6 @@ function makeConfigurationSchemaModel(modelName, schemaDefinition, options) {
157
121
  volatileConstants[slotName] = slotDefinition;
158
122
  }
159
123
  else if (isSlotDefinitionEntry(slotDefinition)) {
160
- // slotDefinition is narrowed to ConfigSlotDefinition here (no cast)
161
124
  slotKeys.add(slotName);
162
125
  try {
163
126
  modelDefinition[slotName] = ConfigSlot(slotDefinition);
@@ -176,8 +139,6 @@ function makeConfigurationSchemaModel(modelName, schemaDefinition, options) {
176
139
  }
177
140
  }
178
141
  else if (typeof slotDefinition === 'object') {
179
- // an object that's neither a sub-schema nor a slot is almost always a
180
- // slot definition missing its required `type` field
181
142
  throw new Error(`no type set for config slot ${modelName}.${slotName}`);
182
143
  }
183
144
  else {
@@ -188,14 +149,11 @@ function makeConfigurationSchemaModel(modelName, schemaDefinition, options) {
188
149
  checkShorthand(modelName, schemaDefinition, options.shorthand);
189
150
  let completeModel = types
190
151
  .model(`${modelName}ConfigurationSchema`, modelDefinition)
191
- // annotated so `ConfigurationSchemaType['Type']`, which has to name these
192
- // two by hand (the model's own props are a `Record<string, any>`, so
193
- // nothing derived off them keeps a signature), cannot drift from them
152
+ // annotated so `ConfigurationSchemaType['Type']`, which names these by
153
+ // hand, cannot drift from them
194
154
  .actions((self) => ({
195
- // Replace a sub-schema member whole. `data` is whatever the sub-schema's
196
- // `preProcessSnapshot` takes, a string shorthand included; for a
197
- // collection it is the whole list or map, and `null` resets it to its
198
- // default, empty unless the collection names one.
155
+ // `data` is whatever the sub-schema's preProcessSnapshot takes; a
156
+ // collection takes the whole list or map, and `null` resets it
199
157
  setSubschema(slotName, data) {
200
158
  if (collectionKeys.has(slotName)) {
201
159
  self[slotName] = data ?? undefined;
@@ -210,28 +168,12 @@ function makeConfigurationSchemaModel(modelName, schemaDefinition, options) {
210
168
  self[slotName] = newSchema;
211
169
  return newSchema;
212
170
  },
213
- // generic slot setter the config editor's slot facade routes through. A
214
- // slot is a bare value-union property, so this is a plain assignment,
215
- // save an array, which `refillArray` writes.
216
- //
217
- // **Don't weaken the membership check to a warning, and don't check
218
- // against `modelDefinition`** — that also holds the identifier and the
219
- // sub-schema properties, neither of which is a write this action is for.
220
- // Slot-name safety is a write guard (ADR-052); `setSlot`'s tests in
221
- // `configurationSchema.test.ts` pin both halves. `slotKeys` already has
222
- // base-schema slots merged in, so an inherited slot passes.
223
- // `null` resets, the same thing `undefined` does, because a JSON-borne
224
- // caller cannot spell `undefined`: a session spec, share link or agent
225
- // call can set a slot and then has no way to put it back. Omitting the
226
- // key is not that — this action is the merge path, where an absent key
227
- // means "leave it alone". A sub-schema reads `null` the same way, and
228
- // no slot stores `null` as a value.
171
+ // The name check is a write guard (ADR-052), never a warning. `null`
172
+ // resets, as `undefined` does, since JSON cannot spell `undefined`
173
+ // (ADR-146).
229
174
  setSlot(slotName, rawValue) {
230
175
  const value = rawValue ?? undefined;
231
176
  if (!slotKeys.has(slotName)) {
232
- // the sub-schema branch re-classifies off the definition rather than
233
- // carrying a second Set: it runs only on the way to a throw, and the
234
- // predicate is the same one the loop above classified with
235
177
  throw new Error(isConfigurationSchemaType(schemaDefinition[slotName])
236
178
  ? `${slotName} is a sub-schema on ${modelName}, not a config slot — replace it with setSubschema`
237
179
  : `${modelName} has no config slot "${slotName}". Valid slots: ${[
@@ -240,10 +182,7 @@ function makeConfigurationSchemaModel(modelName, schemaDefinition, options) {
240
182
  .sort()
241
183
  .join(', ')}`);
242
184
  }
243
- // MST skips its own type check in a production build, and a value the
244
- // slot's union cannot take is then dropped with no throw: the write
245
- // reported success and the slot kept its old value. `is` still runs
246
- // there, so the guard holds in both builds.
185
+ // MST skips its own type check in a production build; `is` does not
247
186
  if (!modelDefinition[slotName].is(value)) {
248
187
  const declared = schemaDefinition[slotName];
249
188
  const slotType = isSlotDefinitionEntry(declared)
@@ -263,8 +202,6 @@ function makeConfigurationSchemaModel(modelName, schemaDefinition, options) {
263
202
  if (Object.keys(volatileConstants).length) {
264
203
  completeModel = completeModel.volatile(( /* self */) => volatileConstants);
265
204
  }
266
- // one MST call per entry, base's before the subclass's — chaining is what
267
- // makes the base's members visible on `self` inside the subclass's function
268
205
  for (const hook of hookList(options.actions)) {
269
206
  completeModel = completeModel.actions(hook);
270
207
  }
@@ -287,18 +224,15 @@ function makeConfigurationSchemaModel(modelName, schemaDefinition, options) {
287
224
  const modelDefault = options.explicitlyTyped
288
225
  ? { type: modelName, ...identifierDefault }
289
226
  : identifierDefault;
290
- // stripDefault (not optional) so a nested all-default sub-schema is omitted
291
- // from its parent's snapshot: the slot props strip themselves, the sub-schema
292
- // collapses to its default, and the parent's stripDefault drops the key.
227
+ // stripDefault, not optional, so an all-default sub-schema leaves its
228
+ // parent's snapshot
293
229
  registerConfigurationSchema(completeModel, metadata);
294
230
  return types.stripDefault(completeModel, modelDefault);
295
231
  }
296
- // Assigning an array makes MST reconcile it, scanning ahead for a node to reuse
297
- // at each mismatch: seconds for a reorder of 5,000 row names. A slot array
298
- // holds scalars, so no reused node keeps anything of its own, and emptying and
299
- // refilling it takes 50 ms for the 5,000. spliceWithArray, since spreading into
300
- // `push` overflows the stack past about 120,000 names. An unchanged array is
301
- // left alone, so it fires no observer.
232
+ // Assigning an array makes MST reconcile it entry by entry, seconds for a
233
+ // reorder of 5,000 row names; refilling a scalar array takes 50 ms.
234
+ // spliceWithArray, since spreading into `push` overflows the stack past about
235
+ // 120,000 entries.
302
236
  function refillArray(held, value) {
303
237
  if (held.length !== value.length || held.some((v, i) => v !== value[i])) {
304
238
  held.clear();
@@ -309,39 +243,22 @@ export function ConfigurationSchema(modelName, inputSchemaDefinition, inputOptio
309
243
  const { schemaDefinition, options } = preprocessConfigurationSchemaArguments(modelName, inputSchemaDefinition, inputOptions);
310
244
  return makeConfigurationSchemaModel(modelName, schemaDefinition, options);
311
245
  }
312
- // The frozen -> live half of TrackConfigurationReference's `get`, shared with
313
- // hydrateTrackConfig below so there is one hydration rather than two that can
314
- // drift on which env they build with. The memo it goes through belongs to the
315
- // PluginManager instance (ADR-031): a node built with one instance's env must
316
- // never be handed to another, and `pluginManager.hydratedTrackConfig` scopes
317
- // that structurally rather than by convention.
246
+ // memoized per PluginManager, so a node never crosses to another instance's env
247
+ // (ADR-031)
318
248
  function hydrateInto(pluginManager, schemaType, frozen) {
319
249
  return pluginManager.hydratedTrackConfig(schemaType, frozen, () => schemaType.create(frozen, { pluginManager }));
320
250
  }
321
251
  /**
322
252
  * #api core/configuration
323
- * Hydrate a plain track config into a live config node, dispatching on its
324
- * `type` to find the schema. `session.tracks` holds `types.frozen` plain
325
- * objects until something references a track (ADR-031). One of those holds only
326
- * what was literally authored: a slot at its schema default is absent,
327
- * `preProcessSnapshot` has not run, and nothing that walks a live node applies
328
- * to it.
329
- *
330
- * Use it where a caller needs the resolved config and may be handed either
331
- * form. The About dialog's "Copy config" is reached from two menus, and one of
332
- * them passes a `session.tracks` entry.
253
+ * Hydrate a plain track config, such as a `session.tracks` entry, into a live
254
+ * config node, dispatching on its `type` to find the schema. A plain entry
255
+ * holds only what was authored: a slot at its default is absent and
256
+ * `preProcessSnapshot` has not run.
333
257
  *
334
- * Returns **undefined** when the config names a track type no plugin
335
- * registered, or when `create` rejects it as invalid. An un-hydrated config has
336
- * never been validated, so the dialog opening over it should not throw. Callers
337
- * fall back to using the plain object.
338
- *
339
- * Shares `TrackConfigurationReference`'s per-PluginManager cache, so hydrating
340
- * the same entry twice returns the same node, and in an admin session a track
341
- * opened later resolves to that same node. A non-admin's open track
342
- * resolves to the session's private working copy (ADR-032), and this function
343
- * returns the pristine mirror beside it. The two have the same content;
344
- * `CopyConfigEntryPoints.test.ts` tests both cases.
258
+ * Returns **undefined** when no plugin registered the type or `create` rejects
259
+ * the config, which has never been validated; callers fall back to the plain
260
+ * object. Hydrating one entry twice returns one node. A shown track resolves to
261
+ * the session's working copy instead (ADR-032), which has the same content.
345
262
  */
346
263
  export function hydrateTrackConfig(pluginManager, config) {
347
264
  const type = config.type;
@@ -349,10 +266,6 @@ export function hydrateTrackConfig(pluginManager, config) {
349
266
  return undefined;
350
267
  }
351
268
  try {
352
- // getTrackType throws on an unregistered name rather than returning
353
- // undefined, so it is inside the guard with `create` — the two failures are
354
- // the same failure (nothing here can build this config) and neither should
355
- // reach a dialog that is only trying to show it
356
269
  const { configSchema } = pluginManager.getTrackType(type);
357
270
  return hydrateInto(pluginManager, configSchema, config);
358
271
  }
@@ -361,52 +274,24 @@ export function hydrateTrackConfig(pluginManager, config) {
361
274
  return undefined;
362
275
  }
363
276
  }
364
- // A slot holding either an id string (resolved through `ref`) or a full inline
365
- // config snapshot (held as a standalone schema instance). Both reference kinds
366
- // resolve to `schemaType` instances, so MST can't auto-dispatch on the instance
367
- // side — the explicit snapshot dispatcher (string → ref, object → schema)
368
- // disambiguates. Shared by TrackConfigurationReference/DisplayConfigurationReference,
369
- // and by neither accident nor oversight NOT by the plain branch — see the note
370
- // on it in `ConfigurationReference`.
371
- //
372
- // One consequence, because both refs' `set` callbacks read as if it were not
373
- // true: assigning a config **node** here does not store a reference to it. The
374
- // dispatcher sees a non-string and picks `schemaType`, so MST tries to adopt the
375
- // node as an inline child — which throws if it already has a parent. `set` is
376
- // therefore unreachable on both refs (checked by making it throw: 231 suites
377
- // stayed green), and exists only because MST rejects a custom reference
378
- // declaring `get` without it. The plain branch, with no dispatcher, does the
379
- // opposite and stores the reference.
277
+ // An id string resolves through `ref`; an object is an inline config. So
278
+ // assigning a node here adopts it as a child rather than storing a reference,
279
+ // which makes both refs' `set` unreachable: MST only requires one beside `get`.
380
280
  function idOrSnapshotUnion(ref, schemaType) {
381
281
  return types.union({
382
282
  dispatcher: snapshot => (typeof snapshot === 'string' ? ref : schemaType),
383
283
  }, ref, schemaType);
384
284
  }
385
285
  /**
386
- * Reference to a track configuration. Snapshot output is the trackId string.
387
- *
388
- * One load-bearing complication: **`types.union(trackRef, schemaType)` accepts a
389
- * string id OR a full config.** A view that synthesizes a track nobody else can
390
- * draw — a read-vs-ref synteny band, an SV inspector row, a circular view's
391
- * added track — writes the config here rather than registering it in
392
- * `session.tracks`, and it then lives and dies with the track that holds it.
393
- * `ReadVsRef.test.tsx` and `SVInspector.test.tsx` are the canaries.
394
- *
395
- * NOTE: don't add `as SCHEMATYPE` to the return value. It narrows SnapshotIn
396
- * to just the object branch, forcing callers to wrap string ids in
397
- * `@ts-expect-error`. The inferred union SnapshotIn is `string | SnapshotIn<schema>`.
286
+ * Reference to a track configuration, snapshotted as its trackId. It also takes
287
+ * a whole config: a view's synthesized track (a read-vs-ref band, an SV
288
+ * inspector row) holds its config inline and lives and dies with it. ADR-084.
398
289
  */
399
290
  function TrackConfigurationReference(schemaType) {
400
291
  const trackRef = types.reference(schemaType, {
401
292
  get(id, parent) {
402
293
  const session = getSession(parent);
403
294
  const trackId = String(id);
404
- // Per-id lookup: subscribes only to this trackId's derivation, so
405
- // resolving one track's config doesn't re-render the others. A session
406
- // with track deltas hands back a private, per-track working copy so a
407
- // shown track's in-place quick-edits mutate that copy and never the
408
- // shared frozen base (ADR-032); any other session falls through to the
409
- // frozen hydration cache (ADR-031).
410
295
  const ret = isSessionWithEditableTrackConfig(session)
411
296
  ? session.getEditableTrackConfig(trackId, schemaType)
412
297
  : session.getTrackById(trackId);
@@ -424,52 +309,19 @@ function TrackConfigurationReference(schemaType) {
424
309
  return idOrSnapshotUnion(trackRef, schemaType);
425
310
  }
426
311
  /**
427
- * Reference to a display configuration. Looked up inside the containing track
428
- * config's `displays` array. Snapshot output is the displayId string.
429
- *
430
- * Resolution order:
431
- * 1. by displayId
432
- * 2. by `parent.type` — handles old sessions where the saved displayId
433
- * no longer matches but a display of the same type exists on the track
434
- *
435
- * Step 2 is the safety net because `baseTrackConfig.preProcessSnapshot`
436
- * already injects a stub display for every registered displayType on the
437
- * track, so a same-type lookup always succeeds at runtime for properly
438
- * loaded tracks. It is what carries a **renamed display type** across for a
439
- * catalog track: the injected stub is named for the new type, the display
440
- * model's own `preProcessSnapshot` rewrites the state model's, and the saved
441
- * `configuration` id still spells the old name, so only the type match
442
- * reconnects them. Not every pre-rename session reaches it — a track config
443
- * that itself declares the old display entry keeps that entry's `displayId`
444
- * through the alias rewrite (`{ ...d, type: canonical }`) and wins the
445
- * first-wins dedupe, so its session resolves by id. An older third step
446
- * auto-created a *detached* config when neither matched — that produced an
447
- * orphaned MST node whose edits silently didn't persist. Removed in favor of a
448
- * clear throw.
449
- *
450
- * The union's schemaType branch is symmetry with `TrackConfigurationReference`
451
- * rather than a path anything in tree takes: the two production writers,
452
- * `showTrackGeneric` and `BaseTrackModel.replaceDisplay`, both write a
453
- * displayId string, because a display config belongs in its track config's
454
- * `displays` array. It stays because it costs nothing and because dropping it
455
- * narrows `SnapshotIn` for every caller.
312
+ * Reference to a display configuration in the containing track config's
313
+ * `displays`, snapshotted as its displayId. An id that matches nothing falls
314
+ * back to the display of the parent's type, which the track config always
315
+ * holds a stub of: that is what reconnects a session saved before a display
316
+ * type was renamed.
456
317
  */
457
318
  function DisplayConfigurationReference(schemaType) {
458
319
  const displayRef = types.reference(schemaType, {
459
320
  get(id, parent) {
460
- // track.configuration is a hydrated MST node (hydrated lazily via
461
- // TrackConfigurationReference), so its displays array contains MST nodes.
462
321
  const track = getContainingTrack(parent);
463
322
  const displays = track.configuration.displays;
464
323
  const displayType = parent.type;
465
324
  let ret = displays.find(d => d.displayId === id);
466
- // Fallback: match by display type when the displayId isn't found.
467
- // baseTrackConfig.preProcessSnapshot injects a display entry for every
468
- // registered displayType for the track, so id-mismatch (e.g. an old
469
- // session with a different displayId convention) finds a same-type
470
- // entry here. The `if (displayType)` guard prevents an undefined
471
- // parent.type from silently matching a display whose `.type` is also
472
- // undefined.
473
325
  if (!ret && displayType) {
474
326
  ret = displays.find(d => d.type === displayType);
475
327
  }
@@ -486,21 +338,10 @@ function DisplayConfigurationReference(schemaType) {
486
338
  return idOrSnapshotUnion(displayRef, schemaType);
487
339
  }
488
340
  /**
489
- * Dispatch by the schema's identifier: `trackId` → track-ref (resolves through
490
- * `session.getTrackById`), `displayId` → display-ref (resolves through the
491
- * parent track's displays array), anything else → plain reference.
492
- *
493
- * Display schemas must declare `explicitIdentifier: 'displayId'` (directly or
494
- * via `baseConfiguration: baseLinearDisplayConfigSchema`, which merges its
495
- * options through `preprocessConfigurationSchemaArguments`).
496
- *
497
- * The return is annotated `IConfigurationReference<SCHEMATYPE>` (see its doc)
498
- * so `self.configuration` carries the concrete schema. The three runtime
499
- * branches produce MST reference/union types over `schemaType` that are
500
- * assignable to that annotation, so `return ref` needs no cast — and must not
501
- * get one: a prior `as SCHEMATYPE` was dropped because it narrowed `SnapshotIn`
502
- * to just the object branch and forced callers pushing string ids to
503
- * `@ts-expect-error` (see `TrackConfigurationReference`'s note).
341
+ * A reference resolved by the schema's identifier: a `trackId` through the
342
+ * session, a `displayId` through the containing track's `displays`, anything
343
+ * else by MST's own identifier lookup. No `as SCHEMATYPE` on the return: it
344
+ * would narrow `SnapshotIn` to the object branch and refuse a string id.
504
345
  */
505
346
  export function ConfigurationReference(schemaType) {
506
347
  const id = getConfigurationSchemaMetadata(schemaType)?.options.explicitIdentifier;
@@ -508,17 +349,8 @@ export function ConfigurationReference(schemaType) {
508
349
  ? TrackConfigurationReference(schemaType)
509
350
  : id === 'displayId'
510
351
  ? DisplayConfigurationReference(schemaType)
511
- : // Plain (non-track/display) ref — internet accounts, connections —
512
- // resolved by MST's own identifier lookup rather than through the
513
- // session. **Deliberately not `idOrSnapshotUnion`**, even though it is
514
- // the same two members: this branch is also handed a *live, in-tree*
515
- // config node (`initializeInternetAccount` pushes
516
- // `jbrowse.internetAccounts[i]` straight through), and an undispatched
517
- // union sends that to the reference member because
518
- // `BaseReferenceType.isAssignableFrom` defers to its target type. A
519
- // `typeof snapshot === 'string'` dispatcher sends it to the schema
520
- // member instead, and MST refuses to adopt a node that already has a
521
- // parent. `InternetAccounts.test.ts` is the canary.
352
+ : // not idOrSnapshotUnion: this branch is handed live in-tree nodes
353
+ // (an internet account), which its dispatcher would try to adopt
522
354
  types.union(types.reference(schemaType), schemaType);
523
355
  return ref;
524
356
  }
@@ -261,29 +261,15 @@ declare const slotTypes: {
261
261
  fallbackDefault: {};
262
262
  };
263
263
  };
264
- /**
265
- * The builtin table's own names — every slot type whose MST model this module
266
- * supplies, so excluding the two enum types. Exported for `SlotValueByType` in
267
- * `types.ts`, which has to name the same set and is checked against this.
268
- */
264
+ /** the types whose model this module supplies; `SlotValueByType` names the same set */
269
265
  export type BuiltinSlotTypeName = keyof typeof slotTypes;
270
266
  declare const ENUM_SLOT_TYPES: readonly ['stringEnum', 'maybeStringEnum', 'stringEnumArray'];
271
267
  /**
272
- * Every legal `type` on a slot definition: the builtin table's own names, plus
273
- * the enum types.
274
- *
275
- * Closed on purpose. A name outside this set still *works* at runtime as long
276
- * as a `model` is given — which is exactly the trap: the value round-trips
277
- * fine, and the only symptom is that everything keyed off the type name stops
278
- * recognising the slot (`slotFacade` hands the editor no `choices`, so an enum
279
- * renders as a free text box).
268
+ * Every legal `type` on a slot definition. Closed, since a name outside it
269
+ * would still load with a `model` given, and every surface keyed on the name
270
+ * would stop recognising the slot.
280
271
  */
281
272
  export type ConfigSlotType = keyof typeof slotTypes | (typeof ENUM_SLOT_TYPES)[number];
282
- /**
283
- * The same set as `MAYBE_TYPES`, at the type level, and derived from the same
284
- * property of the same table so the two cannot disagree: an entry with no
285
- * `fallbackDefault` is one whose fixed form is "unset".
286
- */
287
273
  type MaybeBuiltinSlotTypeName = {
288
274
  [K in keyof typeof slotTypes]: 'fallbackDefault' extends keyof (typeof slotTypes)[K] ? never : K;
289
275
  }[keyof typeof slotTypes];
@@ -301,31 +287,19 @@ interface ConfigSlotDefinitionCommon {
301
287
  */
302
288
  contextVariable?: string[];
303
289
  /**
304
- * hide this slot behind a "Show advanced settings" toggle in the config
305
- * editor, so common slots aren't crowded out by rarely-changed ones
290
+ * put this slot behind a toggle so common slots aren't crowded out by
291
+ * rarely-changed ones: "Show advanced settings" in the config editor, and
292
+ * the collapsed Advanced card in a track's About dialog, which also takes
293
+ * every slot with a `contextVariable`
306
294
  */
307
295
  advanced?: boolean;
308
296
  }
309
297
  /**
310
- * A slot definition, split on whether the slot's type spends `undefined` on
311
- * "unset". The type-level twin of `ConfigSlot`'s
312
- * `defaultValue === undefined && !MAYBE_TYPES.has(type)` throw, which used to be
313
- * the only statement of the rule: a `maybe*` slot has exactly one legal
314
- * `defaultValue`, so requiring the field of every slot alike made 81 slots
315
- * across the repo carry a `defaultValue: undefined` line that said nothing.
316
- *
317
- * A `maybe*` slot's only legal `defaultValue` is the sentinel itself, but the
318
- * field stays `unknown` rather than being pinned to `undefined`: the spread in
319
- * `mergeSchemaDefinition` combines a base and a child whose halves may disagree,
320
- * so pinning it forces a cast through the one place slot definitions are
321
- * combined. `ConfigSlot` throws on a concrete one instead — and has to anyway,
322
- * since no type can see what a `baseConfiguration` merged in, which is the way
323
- * this mistake actually happens.
324
- *
325
- * Stating the sentinel stays meaningful in a subclass override, where the merge
326
- * is a spread and an omitted key inherits the base's value. The same spread is
327
- * why an enum slot's `model` stays optional here though `ConfigSlot` requires
328
- * one: a subclass restating only the default inherits the vocabulary.
298
+ * A slot definition: a `maybe*` type's `defaultValue` is optional, every
299
+ * other's required. A `maybe*` default stays `unknown` rather than `undefined`
300
+ * because a subclass override is spread over its base's, which no type can see;
301
+ * `ConfigSlot` throws on a concrete one. An enum's `model` is optional for the
302
+ * same spread: an override restating only the default inherits the vocabulary.
329
303
  */
330
304
  export type ConfigSlotDefinition = (ConfigSlotDefinitionCommon & {
331
305
  /** a `maybe*` slot type, whose unset state is `undefined` */
@@ -339,33 +313,13 @@ export type ConfigSlotDefinition = (ConfigSlotDefinitionCommon & {
339
313
  defaultValue: unknown;
340
314
  });
341
315
  /**
342
- * A configuration slot is a plain MST property holding the slot's value type,
343
- * or, where the slot declares a `contextVariable`, that or a `jexl:...`
344
- * callback string. Every other slot refuses a `jexl:` string, so a callback
345
- * the reader would evaluate is one the schema says it takes. A `featureField`
346
- * holds a `jexl:` expression as its value, which the display evaluates per
347
- * feature. The value lives directly on the parent configuration model — there
348
- * is no per-slot sub-model. `types.stripDefault` omits the property from the
349
- * parent snapshot when it equals the default, so saved sessions stay minimal.
350
- * Per-slot metadata (type/description/defaultValue/contextVariable) lives in
351
- * the schema registry (a WeakMap keyed by the MST type, see
352
- * schemaRegistry.ts); jexl callbacks are evaluated on read by
353
- * `readConfObject`.
354
- *
355
- * Interning the returned type on `(type, model, defaultValue)` was prototyped
356
- * and declined: a session builds 1197 slot types for 222 distinct ones, and
357
- * removing the repeats moves neither the time nor the heap
358
- * (`config-schema-construction`, published in `reference/EAGER_BUNDLE.md`).
316
+ * The MST property a slot is: its value type, or that or a `jexl:` callback
317
+ * where the slot declares a `contextVariable` (ADR-155), stripped from the
318
+ * snapshot at its default. Interning these was measured and declined
319
+ * (`config-schema-construction` in `reference/EAGER_BUNDLE.md`).
359
320
  */
360
321
  export default function ConfigSlot(definition: ConfigSlotDefinition): import("@jbrowse/mobx-state-tree").IOptionalIType<IAnyType, [undefined]>;
361
- /**
362
- * New value when converting a fixed-value slot to a jexl callback. Already-
363
- * callback values are returned unchanged.
364
- *
365
- * A single JSON.stringify covers every type: `jexl:${42}` and
366
- * `jexl:${JSON.stringify(42)}` are identical for numbers/booleans, and the rest
367
- * need the quoting/serialization anyway.
368
- */
322
+ /** A fixed value as the `jexl:` callback that returns it. */
369
323
  export declare function toCallbackValue(value: unknown): string;
370
324
  /**
371
325
  * New value when converting a jexl callback back to a fixed value: try