@jbrowse/core 5.0.0-beta.1 → 5.0.0-beta.2

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 (115) hide show
  1. package/README.md +42 -3
  2. package/esm/BaseFeatureWidget/SequenceFeatureDetails/test_data/NCDN.d.ts +2 -2
  3. package/esm/PluginLoader.d.ts +1 -0
  4. package/esm/PluginLoader.js +52 -8
  5. package/esm/PluginManager.d.ts +70 -17
  6. package/esm/PluginManager.js +151 -20
  7. package/esm/ReExports/modules.js +3 -53
  8. package/esm/ReExports/publicUtil.d.ts +1 -1
  9. package/esm/ReExports/publicUtil.js +1 -1
  10. package/esm/ReExports/sharedModules.d.ts +96 -0
  11. package/esm/ReExports/sharedModules.js +59 -0
  12. package/esm/ReExports/uiStub.d.ts +1 -0
  13. package/esm/ReExports/uiStub.js +13 -0
  14. package/esm/ReExports/workerModules.d.ts +2 -0
  15. package/esm/ReExports/workerModules.js +10 -0
  16. package/esm/configuration/configurationSchema.d.ts +5 -38
  17. package/esm/configuration/configurationSchema.js +53 -40
  18. package/esm/configuration/promotableDefaults.js +12 -6
  19. package/esm/configuration/promotableResolve.d.ts +1 -1
  20. package/esm/configuration/promotableResolve.js +1 -1
  21. package/esm/data_adapters/BaseAdapter/BaseFeatureDataAdapter.d.ts +12 -0
  22. package/esm/data_adapters/BaseAdapter/BaseFeatureDataAdapter.js +39 -0
  23. package/esm/data_adapters/BaseAdapter/featureDensity.d.ts +29 -0
  24. package/esm/data_adapters/BaseAdapter/featureDensity.js +17 -0
  25. package/esm/data_adapters/BaseAdapter/index.d.ts +1 -0
  26. package/esm/data_adapters/BaseAdapter/index.js +1 -0
  27. package/esm/data_adapters/BaseAdapter/stats.d.ts +33 -1
  28. package/esm/data_adapters/BaseAdapter/stats.js +52 -13
  29. package/esm/data_adapters/BaseAdapter/types.d.ts +1 -0
  30. package/esm/pluggableElementTypes/DisplayType.d.ts +9 -10
  31. package/esm/pluggableElementTypes/DisplayType.js +43 -8
  32. package/esm/pluggableElementTypes/ViewType.d.ts +25 -2
  33. package/esm/pluggableElementTypes/ViewType.js +75 -2
  34. package/esm/pluggableElementTypes/extendElementType.js +8 -3
  35. package/esm/pluggableElementTypes/models/BaseDisplayModel.d.ts +41 -0
  36. package/esm/pluggableElementTypes/models/BaseDisplayModel.js +74 -1
  37. package/esm/pluggableElementTypes/models/BaseTrackModel.d.ts +21 -0
  38. package/esm/pluggableElementTypes/models/BaseTrackModel.js +37 -2
  39. package/esm/pluggableElementTypes/models/BaseViewModel.d.ts +49 -0
  40. package/esm/pluggableElementTypes/models/BaseViewModel.js +39 -0
  41. package/esm/pluggableElementTypes/models/assertDisplayContract.d.ts +10 -7
  42. package/esm/pluggableElementTypes/models/assertDisplayContract.js +23 -30
  43. package/esm/rpc/RpcRegistry.d.ts +10 -0
  44. package/esm/rpc/byteBudget.d.ts +9 -0
  45. package/esm/rpc/coreRpcMethods.d.ts +1 -0
  46. package/esm/rpc/coreRpcMethods.js +1 -0
  47. package/esm/rpc/methods/CoreGetFeatureDensity.d.ts +11 -0
  48. package/esm/rpc/methods/CoreGetFeatureDensity.js +22 -0
  49. package/esm/svg/svgReady.d.ts +1 -0
  50. package/esm/ui/CascadingMenu.js +30 -8
  51. package/esm/ui/LoadingOverlay.d.ts +14 -0
  52. package/esm/ui/LoadingOverlay.js +1 -1
  53. package/esm/ui/MenuTypes.d.ts +11 -2
  54. package/esm/ui/MenuTypes.js +3 -0
  55. package/esm/ui/PaletteContext.d.ts +11 -4
  56. package/esm/ui/PaletteContext.js +34 -5
  57. package/esm/ui/PinAdornment.js +12 -6
  58. package/esm/ui/index.d.ts +1 -1
  59. package/esm/ui/index.js +1 -1
  60. package/esm/ui/launchViewMenu.js +7 -1
  61. package/esm/ui/legendMenuItem.d.ts +5 -5
  62. package/esm/ui/legendMenuItem.js +5 -5
  63. package/esm/ui/makeSizeMenu.js +1 -1
  64. package/esm/ui/menuItems.d.ts +1 -1
  65. package/esm/ui/menuItems.js +1 -1
  66. package/esm/ui/promotableMenuItems.js +8 -6
  67. package/esm/ui/promotablePinCoverage.js +2 -1
  68. package/esm/ui/toggleMenuItems.d.ts +1 -3
  69. package/esm/util/addAndShowTrack.d.ts +1 -1
  70. package/esm/util/addAndShowTrack.js +5 -1
  71. package/esm/util/addTrackFromWidget.js +2 -2
  72. package/esm/util/cacheBudgets.d.ts +22 -19
  73. package/esm/util/cacheBudgets.js +22 -19
  74. package/esm/util/createStopTokenRotation.d.ts +1 -0
  75. package/esm/util/createStopTokenRotation.js +7 -27
  76. package/esm/util/fetchContext.d.ts +2 -2
  77. package/esm/util/fetchContext.js +2 -2
  78. package/esm/util/fetchPhases.d.ts +9 -9
  79. package/esm/util/hooks.d.ts +8 -0
  80. package/esm/util/hooks.js +27 -0
  81. package/esm/util/index.d.ts +2 -3
  82. package/esm/util/index.js +2 -3
  83. package/esm/util/installFetch.d.ts +49 -23
  84. package/esm/util/installFetch.js +49 -21
  85. package/esm/util/installInitAutorun.d.ts +8 -8
  86. package/esm/util/installInitAutorun.js +15 -15
  87. package/esm/util/lazyAfterAttach.d.ts +13 -0
  88. package/esm/util/lazyAfterAttach.js +31 -0
  89. package/esm/util/legendCandidates.d.ts +50 -0
  90. package/esm/util/legendCandidates.js +87 -0
  91. package/esm/util/mstUtils.d.ts +12 -1
  92. package/esm/util/mstUtils.js +20 -2
  93. package/esm/util/pluginStore.d.ts +1 -0
  94. package/esm/util/pluginStore.js +8 -1
  95. package/esm/util/range.d.ts +13 -9
  96. package/esm/util/range.js +17 -8
  97. package/esm/util/rxjs.d.ts +16 -0
  98. package/esm/util/rxjs.js +49 -0
  99. package/esm/util/tabix.d.ts +10 -4
  100. package/esm/util/tabix.js +11 -5
  101. package/esm/util/tracks.d.ts +48 -0
  102. package/esm/util/tracks.js +192 -66
  103. package/esm/util/types/index.d.ts +48 -5
  104. package/esm/util/types/index.js +3 -0
  105. package/esm/util/unknownSnapshotKeys.d.ts +36 -0
  106. package/esm/util/unknownSnapshotKeys.js +69 -0
  107. package/esm/util/viewStatus.d.ts +23 -0
  108. package/esm/util/viewStatus.js +17 -0
  109. package/esm/util/whenViewSettled.d.ts +17 -4
  110. package/esm/util/whenViewSettled.js +19 -6
  111. package/esm/util/withLaunchInput.d.ts +123 -0
  112. package/esm/util/withLaunchInput.js +228 -0
  113. package/package.json +12 -9
  114. package/esm/util/applyInitSettings.d.ts +0 -79
  115. package/esm/util/applyInitSettings.js +0 -91
@@ -127,11 +127,20 @@ export interface RadioMenuItem extends BaseMenuItem {
127
127
  checked: boolean;
128
128
  onClick: MenuItemClickHandler;
129
129
  }
130
- /** #menuItem subMenu | nests another `MenuItem[]`, to any depth */
130
+ /**
131
+ * #menuItem subMenu | nests another `MenuItem[]`, or a function building one, to any depth
132
+ *
133
+ * The function form is called when the submenu opens (or a walker asks for its
134
+ * rows), not when the parent menu is built — for a submenu whose rows cost a
135
+ * scan of the display's data to name. Read the field through `resolveSubMenu`
136
+ * rather than by hand, so the two forms stay one thing to the renderer, the
137
+ * pin-coverage walk and every test helper that flattens a menu.
138
+ */
131
139
  export interface SubMenuItem extends BaseMenuItem {
132
140
  type?: 'subMenu';
133
- subMenu: MenuItem[];
141
+ subMenu: MenuItem[] | (() => MenuItem[]);
134
142
  }
143
+ export declare function resolveSubMenu(item: SubMenuItem): MenuItem[];
135
144
  /**
136
145
  * #menuItem custom | renders arbitrary React content inline, e.g. a slider
137
146
  *
@@ -1,3 +1,6 @@
1
+ export function resolveSubMenu(item) {
2
+ return typeof item.subMenu === 'function' ? item.subMenu() : item.subMenu;
3
+ }
1
4
  // #endregion
2
5
  /**
3
6
  * Whether clicking a row leaves the menu up — the rule `CascadingMenu` applies,
@@ -40,8 +40,9 @@ export interface ThemeModeSession {
40
40
  * hook and the provider in one.
41
41
  *
42
42
  * How the host knows its own mode is the host's business — an attribute on
43
- * `<html>`, a `prefers-color-scheme` media query, a design-system context —
44
- * and deliberately not this hook's.
43
+ * `<html>`, a design-system context, a toggle in its own state. Pass no mode
44
+ * and JBrowse follows `prefers-color-scheme` instead, tracking OS changes; a
45
+ * host whose mode is only ever the OS preference has nothing to write.
45
46
  *
46
47
  * **Both halves are load-bearing, which is why the pairing is published as a
47
48
  * component.** The palette is what *React* draws with; the config `theme` slot
@@ -55,7 +56,7 @@ export interface ThemeModeSession {
55
56
  * the previous palette. That is a frame on a canvas that is about to be
56
57
  * redrawn anyway; it is not somewhere to add a suspense boundary.
57
58
  */
58
- export declare function useSessionPalette(session: ThemeModeSession, mode: 'light' | 'dark'): JBrowsePalette;
59
+ export declare function useSessionPalette(session: ThemeModeSession, mode?: 'light' | 'dark'): JBrowsePalette;
59
60
  /**
60
61
  * #api
61
62
  * Make JBrowse follow the host's light/dark state — the whole of it, in one
@@ -67,6 +68,12 @@ export declare function useSessionPalette(session: ThemeModeSession, mode: 'ligh
67
68
  * </SessionPaletteProvider>
68
69
  * ```
69
70
  *
71
+ * `mode` is optional. Left out, JBrowse follows `prefers-color-scheme` and
72
+ * re-themes when the OS preference changes, through the same session write an
73
+ * explicit mode takes — so a host whose dark mode *is* the OS preference mounts
74
+ * this with a session and nothing else. Pass a mode as soon as the host has a
75
+ * toggle of its own, since the media query cannot see it.
76
+ *
70
77
  * A component rather than a documented pair of calls because the pair has a
71
78
  * half that can be left out with nothing to show for it. `PaletteProvider` is
72
79
  * the name a host reaches for, and it colors the React side alone; the session
@@ -80,6 +87,6 @@ export declare function useSessionPalette(session: ThemeModeSession, mode: 'ligh
80
87
  */
81
88
  export declare function SessionPaletteProvider({ session, mode, children, }: {
82
89
  session: ThemeModeSession;
83
- mode: 'light' | 'dark';
90
+ mode?: 'light' | 'dark';
84
91
  children: ReactNode;
85
92
  }): import("react").JSX.Element;
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { createContext, use, useEffect, useMemo } from 'react';
2
+ import { createContext, use, useEffect, useMemo, useSyncExternalStore, } from 'react';
3
3
  import { defaultStyleTheme } from './styleTheme.js';
4
4
  /**
5
5
  * How a React component asks for JBrowse's design tokens.
@@ -47,6 +47,27 @@ export function useStyleTheme() {
47
47
  export function usePalette() {
48
48
  return useStyleTheme().palette;
49
49
  }
50
+ function darkSchemeQuery() {
51
+ return typeof window.matchMedia === 'function'
52
+ ? window.matchMedia('(prefers-color-scheme: dark)')
53
+ : undefined;
54
+ }
55
+ function subscribeToColorScheme(onChange) {
56
+ const media = darkSchemeQuery();
57
+ media?.addEventListener('change', onChange);
58
+ return () => {
59
+ media?.removeEventListener('change', onChange);
60
+ };
61
+ }
62
+ function readColorScheme() {
63
+ return darkSchemeQuery()?.matches ? 'dark' : 'light';
64
+ }
65
+ const alwaysLight = () => 'light';
66
+ const subscribeToNothing = () => () => { };
67
+ function useResolvedMode(mode) {
68
+ const systemMode = useSyncExternalStore(mode === undefined ? subscribeToColorScheme : subscribeToNothing, mode === undefined ? readColorScheme : alwaysLight, alwaysLight);
69
+ return mode === undefined ? systemMode : mode;
70
+ }
50
71
  /**
51
72
  * Point a session at light or dark and get back the palette it resolves to,
52
73
  * for a host that wants the palette itself — to read a color out of, or to
@@ -55,8 +76,9 @@ export function usePalette() {
55
76
  * hook and the provider in one.
56
77
  *
57
78
  * How the host knows its own mode is the host's business — an attribute on
58
- * `<html>`, a `prefers-color-scheme` media query, a design-system context —
59
- * and deliberately not this hook's.
79
+ * `<html>`, a design-system context, a toggle in its own state. Pass no mode
80
+ * and JBrowse follows `prefers-color-scheme` instead, tracking OS changes; a
81
+ * host whose mode is only ever the OS preference has nothing to write.
60
82
  *
61
83
  * **Both halves are load-bearing, which is why the pairing is published as a
62
84
  * component.** The palette is what *React* draws with; the config `theme` slot
@@ -71,9 +93,10 @@ export function usePalette() {
71
93
  * redrawn anyway; it is not somewhere to add a suspense boundary.
72
94
  */
73
95
  export function useSessionPalette(session, mode) {
96
+ const resolved = useResolvedMode(mode);
74
97
  useEffect(() => {
75
- session.setThemeMode(mode);
76
- }, [session, mode]);
98
+ session.setThemeMode(resolved);
99
+ }, [session, resolved]);
77
100
  return session.palette;
78
101
  }
79
102
  /**
@@ -87,6 +110,12 @@ export function useSessionPalette(session, mode) {
87
110
  * </SessionPaletteProvider>
88
111
  * ```
89
112
  *
113
+ * `mode` is optional. Left out, JBrowse follows `prefers-color-scheme` and
114
+ * re-themes when the OS preference changes, through the same session write an
115
+ * explicit mode takes — so a host whose dark mode *is* the OS preference mounts
116
+ * this with a session and nothing else. Pass a mode as soon as the host has a
117
+ * toggle of its own, since the media query cannot see it.
118
+ *
90
119
  * A component rather than a documented pair of calls because the pair has a
91
120
  * half that can be left out with nothing to show for it. `PaletteProvider` is
92
121
  * the name a host reaches for, and it colors the React side alone; the session
@@ -38,25 +38,31 @@ function pinPredicate(onValue) {
38
38
  }
39
39
  // The click, which is not the state the pin draws: an outline pin applies the
40
40
  // value to the open tracks, a filled one clears the default it stands for.
41
+ //
42
+ // The aria-label carries "of this type" wherever the tooltip does: it used to
43
+ // stop at "for all open tracks", stating a wider blast radius than the control
44
+ // has.
41
45
  function pinCopy(label, onValue, isDefault) {
42
46
  return isDefault
43
47
  ? {
44
48
  title: `${label} is ${pinPredicate(onValue)} for all tracks of this type (click to clear)`,
45
- ariaLabel: `clear the default for ${label}`,
49
+ ariaLabel: `clear the default for ${label} for all tracks of this type`,
46
50
  }
47
51
  : typeof onValue === 'boolean'
48
52
  ? {
49
53
  title: `Turn ${label} ${onValue ? 'on' : 'off'} for all open tracks of this type`,
50
- ariaLabel: `turn ${label} ${onValue ? 'on' : 'off'} for all open tracks`,
54
+ ariaLabel: `turn ${label} ${onValue ? 'on' : 'off'} for all open tracks of this type`,
51
55
  }
52
56
  : {
53
57
  title: `Apply ${label} to all open tracks of this type`,
54
- ariaLabel: `apply ${label} to all open tracks`,
58
+ ariaLabel: `apply ${label} to all open tracks of this type`,
55
59
  };
56
60
  }
57
- // Trailing pin for a promotable setting, rendered as a menu item's
58
- // `endAdornment` beside the value check. A ToggleButton (native button a11y + a
59
- // clear selected tint) with a pin — distinct from the value checkbox.
61
+ // Trailing pin for a promotable setting, drawn beside the value check from the
62
+ // row's `pin` declaration (`menuItemAdornment` builds this; a row never
63
+ // constructs it, which is what keeps MUI out of the eager menu-builder graph). A
64
+ // ToggleButton (native button a11y + a clear selected tint) with a pin —
65
+ // distinct from the value checkbox.
60
66
  //
61
67
  // **The click and the state are two different things.** One click writes the
62
68
  // value into every open track of this display type and raises a snackbar
package/esm/ui/index.d.ts CHANGED
@@ -70,7 +70,7 @@ export type { SlotExtensionPointName, SlotProps, WrapperProps, } from './wrapCom
70
70
  export { matchesTrackSelector } from './extensionSelectors.ts';
71
71
  export type { TrackSelector } from './extensionSelectors.ts';
72
72
  export type { BaseMenuItem, CheckboxMenuItem, ClickableMenuItem, CustomMenuItem, MenuDivider, MenuItem, MenuItemClickHandler, MenuItemsGetter, MenuSubHeader, NormalMenuItem, RadioMenuItem, SubMenuItem, } from './MenuTypes.ts';
73
- export { staysOpenOnClick } from './MenuTypes.ts';
73
+ export { resolveSubMenu, staysOpenOnClick } from './MenuTypes.ts';
74
74
  export { LAUNCH_LABEL, pushIntoSubMenu, pushLaunchViewMenuItem, } from './launchViewMenu.ts';
75
75
  export { launchTargetsMenuItem } from './launchTargetsMenuItem.ts';
76
76
  export { default as Crosshairs } from './Crosshairs.tsx';
package/esm/ui/index.js CHANGED
@@ -65,7 +65,7 @@ export { default as PluggableElements } from './PluggableElements.js';
65
65
  export { addExtensionElement } from './addExtensionElement.js';
66
66
  export { wrapComponent } from './wrapComponent.js';
67
67
  export { matchesTrackSelector } from './extensionSelectors.js';
68
- export { staysOpenOnClick } from './MenuTypes.js';
68
+ export { resolveSubMenu, staysOpenOnClick } from './MenuTypes.js';
69
69
  export { LAUNCH_LABEL, pushIntoSubMenu, pushLaunchViewMenuItem, } from './launchViewMenu.js';
70
70
  export { launchTargetsMenuItem } from './launchTargetsMenuItem.js';
71
71
  export { default as Crosshairs } from './Crosshairs.js';
@@ -10,7 +10,13 @@ export const LAUNCH_LABEL = 'Launch';
10
10
  export function pushIntoSubMenu(items, label, item) {
11
11
  const existing = items.find((i) => i.type === 'subMenu' && i.label === label);
12
12
  if (existing) {
13
- existing.subMenu.push(item);
13
+ const { subMenu } = existing;
14
+ if (typeof subMenu === 'function') {
15
+ existing.subMenu = () => [...subMenu(), item];
16
+ }
17
+ else {
18
+ subMenu.push(item);
19
+ }
14
20
  }
15
21
  else {
16
22
  items.push({ label, type: 'subMenu', subMenu: [item] });
@@ -14,11 +14,11 @@ import type { MenuItem } from './MenuTypes.ts';
14
14
  * values legitimately differ (a Hi-C color scale is off by default, a variant
15
15
  * genotype key on), and their descriptions describe genuinely different legends.
16
16
  * What they now share is being *promotable* — pass `opts.pin` and the row gains
17
- * the pin that makes the current state this display type's session-wide
18
- * default. Every display whose legend is backed by a config slot passes one,
19
- * and gets it from `LegendMixin`'s `showLegendDisplayTypeDefault` rather than
20
- * calling `makePin` itself: the slot is the per-display half, the accessors
21
- * over it are not.
17
+ * the pin that applies the current state to every open track of this display
18
+ * type, offering the display-type default as its snackbar's one action. Every
19
+ * display whose legend is backed by a config slot passes one, and gets it from
20
+ * `LegendMixin`'s `showLegendDisplayTypeDefault` rather than calling `makePin`
21
+ * itself: the slot is the per-display half, the accessors over it are not.
22
22
  *
23
23
  * `pin` is optional because two callers have no slot to promote: the Manhattan
24
24
  * plot's LD legend and `LinearBasicDisplay`'s color key are a volatile and a
@@ -14,11 +14,11 @@ import { checkboxItem } from './toggleMenuItems.js';
14
14
  * values legitimately differ (a Hi-C color scale is off by default, a variant
15
15
  * genotype key on), and their descriptions describe genuinely different legends.
16
16
  * What they now share is being *promotable* — pass `opts.pin` and the row gains
17
- * the pin that makes the current state this display type's session-wide
18
- * default. Every display whose legend is backed by a config slot passes one,
19
- * and gets it from `LegendMixin`'s `showLegendDisplayTypeDefault` rather than
20
- * calling `makePin` itself: the slot is the per-display half, the accessors
21
- * over it are not.
17
+ * the pin that applies the current state to every open track of this display
18
+ * type, offering the display-type default as its snackbar's one action. Every
19
+ * display whose legend is backed by a config slot passes one, and gets it from
20
+ * `LegendMixin`'s `showLegendDisplayTypeDefault` rather than calling `makePin`
21
+ * itself: the slot is the per-display half, the accessors over it are not.
22
22
  *
23
23
  * `pin` is optional because two callers have no slot to promote: the Manhattan
24
24
  * plot's LD legend and `LinearBasicDisplay`'s color key are a volatile and a
@@ -51,7 +51,7 @@ export function makeSizeSubMenu(opts) {
51
51
  // The same row over a **promotable** slot, which derives both halves the plain
52
52
  // form takes by hand — the reset button's enablement (`isSlotCustomized`) and
53
53
  // the "default for all tracks of this type" pin — from the one slot name. A
54
- // slider has no fixed on-value, so the pin promotes whatever the track is
54
+ // slider has no fixed on-value, so the pin carries whatever the track is
55
55
  // currently showing.
56
56
  //
57
57
  // Naming the slot once is the point: the call sites used to spell it twice, in
@@ -24,5 +24,5 @@ export { makeRadioSubMenu } from './radioSubMenu.ts';
24
24
  export { showLegendCheckboxItem } from './legendMenuItem.ts';
25
25
  export { promotableRadioItem, promotableRadioItems, promotableToggleItem, } from './promotableMenuItems.ts';
26
26
  export type { RadioOption, SettingRowOptions } from './toggleMenuItems.ts';
27
- export { staysOpenOnClick } from './MenuTypes.ts';
27
+ export { resolveSubMenu, staysOpenOnClick } from './MenuTypes.ts';
28
28
  export type { BaseMenuItem, CheckboxMenuItem, ClickableMenuItem, CustomMenuItem, MenuDivider, MenuItem, MenuItemClickHandler, MenuItemPin, MenuItemsGetter, MenuSubHeader, NormalMenuItem, RadioMenuItem, SubMenuItem, } from './MenuTypes.ts';
@@ -23,4 +23,4 @@ export { withHint } from './menuLabels.js';
23
23
  export { makeRadioSubMenu } from './radioSubMenu.js';
24
24
  export { showLegendCheckboxItem } from './legendMenuItem.js';
25
25
  export { promotableRadioItem, promotableRadioItems, promotableToggleItem, } from './promotableMenuItems.js';
26
- export { staysOpenOnClick } from './MenuTypes.js';
26
+ export { resolveSubMenu, staysOpenOnClick } from './MenuTypes.js';
@@ -10,10 +10,10 @@ import { checkboxItem, radioItem, radioItems } from './toggleMenuItems.js';
10
10
  // written that way — `checkboxItem` / `radioItems` build the row — so the
11
11
  // promotable and plain forms of a row can differ only by the pin.
12
12
  // A promotable setting as one native checkbox menu row: the value toggles the
13
- // track (inheriting native hover/sizing/keyboard), and a trailing pin
14
- // (endAdornment) applies this value to every open track of the display type,
15
- // offering the display-type default as its snackbar's one action. Always shown
16
- // so the capability is discoverable.
13
+ // track (inheriting native hover/sizing/keyboard), and a trailing pin applies
14
+ // this value to every open track of the display type, offering the display-type
15
+ // default as its snackbar's one action. Always shown so the capability is
16
+ // discoverable.
17
17
  //
18
18
  // **The row itself is `checkboxItem`'s**, so the two builders can only ever
19
19
  // differ by the pin. They used to be two literals, and had already drifted: this
@@ -61,8 +61,10 @@ export function promotableRadioItem({ label, checked, onClick, pin, ...opts }) {
61
61
  }
62
62
  // A whole promotable radio group: `radioItems` plus one pin per option. The
63
63
  // plural form of the builder above, and the one to reach for on a group whose
64
- // options differ only in their value — which is all four of them today
65
- // (`heightMode`, `displayMode`, `subfeatureLabels`, `sashimiArcsMode`).
64
+ // options differ only in their value — which is all five of them today
65
+ // (`heightMode`, `displayMode`, `showLabels`, `subfeatureLabels`,
66
+ // `sashimiArcsMode`; the two label groups reach it through canvas's
67
+ // `inlineRadioGroup`).
66
68
  //
67
69
  // **Every option in a group gets a pin, the `promotedBase` value included** —
68
70
  // once a non-base value is promoted, pinning the base back is the only per-value
@@ -23,6 +23,7 @@
23
23
  * result against a baseline.
24
24
  */
25
25
  import { promotableSlotNames } from '../configuration/promotableSlots.js';
26
+ import { resolveSubMenu } from './MenuTypes.js';
26
27
  /**
27
28
  * The registered display types whose config schema declares a promotable slot,
28
29
  * sorted — the set a pin-coverage check has to reach, answered off the
@@ -61,7 +62,7 @@ export function pinnedSlots(items) {
61
62
  found.add(item.pin.control.slot);
62
63
  }
63
64
  if ('subMenu' in item) {
64
- walk(item.subMenu);
65
+ walk(resolveSubMenu(item));
65
66
  }
66
67
  }
67
68
  };
@@ -26,11 +26,9 @@ export declare function checkboxItem(label: string, checked: boolean, onToggle:
26
26
  export declare function toggleItem(label: string, value: boolean, setValue: (value: boolean) => void, opts?: SettingRowOptions): CheckboxMenuItem;
27
27
  /** #menuBuilder radioItem | one radio setting row; the singular of `radioItems` */
28
28
  export declare function radioItem(label: string, checked: boolean, onClick: () => void, opts?: SettingRowOptions): RadioMenuItem;
29
- export interface RadioOption<T extends string> {
29
+ export interface RadioOption<T extends string> extends SettingRowOptions {
30
30
  value: T;
31
31
  label: string;
32
- helpText?: string;
33
- keepMenuOpen?: boolean;
34
32
  }
35
33
  /** #menuBuilder radioItems | a radio group, one row per option */
36
34
  export declare function radioItems<T extends string>(options: readonly RadioOption<T>[], current: T | undefined, setMode: (m: T) => void): RadioMenuItem[];
@@ -15,7 +15,7 @@ import type { SessionWithAddSessionTrack } from './types/index.ts';
15
15
  export declare function addAndShowTrack(session: SessionWithAddSessionTrack, conf: Parameters<SessionWithAddSessionTrack['addSessionTrackConf']>[0] & {
16
16
  trackId: string;
17
17
  }, view?: {
18
- showTrack: (trackId: string) => void;
18
+ launchTrack: (trackId: string) => Promise<unknown>;
19
19
  }): (import("@jbrowse/mobx-state-tree").ModelInstanceTypeProps<Record<string, any>> & {
20
20
  setSubschema(slotName: string, data: Record<string, unknown>): any;
21
21
  setSlot(slotName: string, value: unknown): void;
@@ -16,8 +16,12 @@ export function addAndShowTrack(session, conf, view) {
16
16
  // addSessionTrackConf already notified on an invalid conf; showing a trackId
17
17
  // that was never added would only add a second "Could not resolve identifier"
18
18
  // snackbar
19
+ //
20
+ // `launchTrack`, voided: the display's state model may still be a dynamic
21
+ // import away, and every caller here is a menu item or a dialog button whose
22
+ // return value is the added config rather than the opened track
19
23
  if (added) {
20
- view?.showTrack(conf.trackId);
24
+ void view?.launchTrack(conf.trackId);
21
25
  }
22
26
  return added;
23
27
  }
@@ -68,7 +68,7 @@ export function addTrackFromWidget({ model, session, conf, }) {
68
68
  session.notify(`Could not add "${conf.name ?? conf.trackId}": the view it was being added to has closed, and it uses an assembly that only that view had.`, 'warning');
69
69
  return undefined;
70
70
  }
71
- trackContainer.showTrack(conf.trackId, {}, {}, conf);
71
+ void trackContainer.launchTrack(conf.trackId, {}, {}, conf);
72
72
  finishAddTrack(model, session);
73
73
  return conf;
74
74
  }
@@ -77,7 +77,7 @@ export function addTrackFromWidget({ model, session, conf, }) {
77
77
  return undefined;
78
78
  }
79
79
  if (containerDisplaysAssembly(trackContainer, conf.assemblyNames, session.assemblyManager)) {
80
- trackContainer?.showTrack(conf.trackId);
80
+ void trackContainer?.launchTrack(conf.trackId);
81
81
  }
82
82
  else {
83
83
  const assemblies = conf.assemblyNames?.filter(a => !!a) ?? [];
@@ -1,10 +1,10 @@
1
1
  import { SharedBudget } from '@gmod/shared-read-cache';
2
2
  /**
3
- * Ceilings on what the indexed-format libraries retain, shared by every adapter
4
- * in this JS context — so one per RPC worker, plus one on the main thread,
5
- * which is the scope that actually runs out of memory.
3
+ * The ceiling on what the indexed-format libraries retain, shared by every
4
+ * adapter in this JS context — so one per RPC worker, plus one on the main
5
+ * thread, which is the scope that actually runs out of memory.
6
6
  *
7
- * ## Why these are not per file
7
+ * ## Why this is not per file
8
8
  *
9
9
  * `@gmod/bam`, `@gmod/tabix` and `@gmod/cram` each take a per-file budget,
10
10
  * sized so that a single track panning never falls off the cliff where a cache
@@ -29,24 +29,27 @@ import { SharedBudget } from '@gmod/shared-read-cache';
29
29
  * at hand their space to the one being panned, so the active track keeps a
30
30
  * whole working set however many are open.
31
31
  *
32
- * ## The numbers are not new
32
+ * ## The number is not new
33
33
  *
34
- * Both are the libraries' own per-file defaults, moved from per-file to
35
- * per-context. That makes this a strict tightening — retention can only fall —
36
- * and avoids inventing a number that would need its own justification. The
37
- * budget still has to clear one query's working set or the cliff returns, and
38
- * on that workload 512 MB shared across three tracks already cost 43 refills
39
- * where 1 GB cost 4.
34
+ * 1 GB is the libraries' own per-file default (`DEFAULT_MAX_CACHE_BYTES` in
35
+ * both `@gmod/bam` and `@gmod/cram`), moved from per-file to per-context. That
36
+ * makes this a strict tightening — retention can only fall — and avoids
37
+ * inventing a number that would need its own justification. With one file open
38
+ * the two ceilings coincide; from the second file on, this one is the one that
39
+ * binds. The budget still has to clear one query's working set or the cliff
40
+ * returns, and on that workload 512 MB shared across three tracks already cost
41
+ * 43 refills where 1 GB cost 4.
40
42
  *
41
- * ## Two budgets, because units cannot mix
43
+ * ## One budget, because every member weighs bytes
42
44
  *
43
45
  * `SharedBudget.total` is a sum over its members, so they must all weigh in the
44
- * same unit. `@gmod/bam` and `@gmod/tabix` weigh decompressed bytes;
45
- * `@gmod/cram` weighs decoded records, because a decoded record has no cheap
46
- * size. A single budget spanning them would add records to bytes and bound
47
- * neither. `sizeOf` is opaque, so nothing can catch that at runtime — hence two
48
- * budgets named for their units.
46
+ * same unit, and `sizeOf` is opaque, so nothing could catch a mismatch at
47
+ * runtime. `@gmod/bam` and `@gmod/tabix` weigh decompressed bytes, and since 14
48
+ * `@gmod/cram` weighs a decoded slice by `DecodedSlice.byteLength` — its typed
49
+ * columns exactly and its strings by estimate, within 1–8% of the measured heap
50
+ * (its ADR 0013). Before that a CRAM slice weighed its record count, which is
51
+ * why this used to be two budgets named for their units: a million records
52
+ * could not be added to bytes, and never bound long-read data either, where a
53
+ * 6 MB slice counted as 37.
49
54
  */
50
55
  export declare const decompressedBytesBudget: SharedBudget;
51
- /** See {@link decompressedBytesBudget}. Records, not bytes — CRAM only. */
52
- export declare const decodedRecordsBudget: SharedBudget;
@@ -1,10 +1,10 @@
1
1
  import { SharedBudget } from '@gmod/shared-read-cache';
2
2
  /**
3
- * Ceilings on what the indexed-format libraries retain, shared by every adapter
4
- * in this JS context — so one per RPC worker, plus one on the main thread,
5
- * which is the scope that actually runs out of memory.
3
+ * The ceiling on what the indexed-format libraries retain, shared by every
4
+ * adapter in this JS context — so one per RPC worker, plus one on the main
5
+ * thread, which is the scope that actually runs out of memory.
6
6
  *
7
- * ## Why these are not per file
7
+ * ## Why this is not per file
8
8
  *
9
9
  * `@gmod/bam`, `@gmod/tabix` and `@gmod/cram` each take a per-file budget,
10
10
  * sized so that a single track panning never falls off the cliff where a cache
@@ -29,24 +29,27 @@ import { SharedBudget } from '@gmod/shared-read-cache';
29
29
  * at hand their space to the one being panned, so the active track keeps a
30
30
  * whole working set however many are open.
31
31
  *
32
- * ## The numbers are not new
32
+ * ## The number is not new
33
33
  *
34
- * Both are the libraries' own per-file defaults, moved from per-file to
35
- * per-context. That makes this a strict tightening — retention can only fall —
36
- * and avoids inventing a number that would need its own justification. The
37
- * budget still has to clear one query's working set or the cliff returns, and
38
- * on that workload 512 MB shared across three tracks already cost 43 refills
39
- * where 1 GB cost 4.
34
+ * 1 GB is the libraries' own per-file default (`DEFAULT_MAX_CACHE_BYTES` in
35
+ * both `@gmod/bam` and `@gmod/cram`), moved from per-file to per-context. That
36
+ * makes this a strict tightening — retention can only fall — and avoids
37
+ * inventing a number that would need its own justification. With one file open
38
+ * the two ceilings coincide; from the second file on, this one is the one that
39
+ * binds. The budget still has to clear one query's working set or the cliff
40
+ * returns, and on that workload 512 MB shared across three tracks already cost
41
+ * 43 refills where 1 GB cost 4.
40
42
  *
41
- * ## Two budgets, because units cannot mix
43
+ * ## One budget, because every member weighs bytes
42
44
  *
43
45
  * `SharedBudget.total` is a sum over its members, so they must all weigh in the
44
- * same unit. `@gmod/bam` and `@gmod/tabix` weigh decompressed bytes;
45
- * `@gmod/cram` weighs decoded records, because a decoded record has no cheap
46
- * size. A single budget spanning them would add records to bytes and bound
47
- * neither. `sizeOf` is opaque, so nothing can catch that at runtime — hence two
48
- * budgets named for their units.
46
+ * same unit, and `sizeOf` is opaque, so nothing could catch a mismatch at
47
+ * runtime. `@gmod/bam` and `@gmod/tabix` weigh decompressed bytes, and since 14
48
+ * `@gmod/cram` weighs a decoded slice by `DecodedSlice.byteLength` — its typed
49
+ * columns exactly and its strings by estimate, within 1–8% of the measured heap
50
+ * (its ADR 0013). Before that a CRAM slice weighed its record count, which is
51
+ * why this used to be two budgets named for their units: a million records
52
+ * could not be added to bytes, and never bound long-read data either, where a
53
+ * 6 MB slice counted as 37.
49
54
  */
50
55
  export const decompressedBytesBudget = new SharedBudget(1024 * 2 ** 20);
51
- /** See {@link decompressedBytesBudget}. Records, not bytes — CRAM only. */
52
- export const decodedRecordsBudget = new SharedBudget(1_000_000);
@@ -110,6 +110,7 @@ export interface ActiveFetch {
110
110
  * passes a {@link createStatusChannel}, which is one volatile instead of two
111
111
  * fields and an action.
112
112
  */
113
+ export type StopTokenRotation = ReturnType<typeof createStopTokenRotation>;
113
114
  export declare function createStopTokenRotation(self: IStateTreeNode, report: StatusReporter): {
114
115
  begin(): ActiveFetch;
115
116
  cancel: () => void;
@@ -28,33 +28,6 @@ export function createStatusChannel() {
28
28
  },
29
29
  };
30
30
  }
31
- /**
32
- * Latest-wins stop-token rotation for a fetch that runs in a bare `autorun`
33
- * rather than through `FetchMixin.runFetch` (which welds the same mechanics to
34
- * `fetchGeneration`, so it's only for viewport-driven LGV fetches). Each
35
- * `begin()` aborts the prior fetch's token and returns a fresh one plus an
36
- * `isCurrent()` guard that captures this run's token — gate every post-await
37
- * write on it and a superseded or torn-down fetch can never clobber fresher
38
- * data. The guard is the return value, so a caller can't forget to compare a
39
- * token by hand.
40
- *
41
- * `end()` in the run's `finally` is the other half, and for the same reason —
42
- * see {@link ActiveFetch.end} for the two things it does together.
43
- *
44
- * Owns the token mechanics and the status channel; the caller keeps its own
45
- * loading/error/commit side-effects. Three holders: `FetchMixin` as a member
46
- * (so `cancelFetch` can reach it), `installFetch` one per installation (every
47
- * other fetch in the tree runs on that skeleton), and the synteny diagonalize
48
- * progress wrapper (`withDiagonalizeProgress`) directly.
49
- *
50
- * `report` is passed rather than read off `self`, so where the status lands is
51
- * the caller's decision and not a shape this imposes. A display passes itself —
52
- * its status fields are part of its own API, and one composing `FetchMixin`
53
- * passes the model-wide window along with them (see `StatusReporter`).
54
- * Anything with one operation to narrate and no status vocabulary of its own
55
- * passes a {@link createStatusChannel}, which is one volatile instead of two
56
- * fields and an action.
57
- */
58
31
  export function createStopTokenRotation(self, report) {
59
32
  let currentStopToken;
60
33
  // The host's window when it has one, so a display composing `FetchMixin` has
@@ -124,6 +97,13 @@ export function createStopTokenRotation(self, report) {
124
97
  statusCallback: stream.statusCallback,
125
98
  end() {
126
99
  ended = true;
100
+ // A completed fetch's token is released here, not only on supersede
101
+ // or cancel — otherwise it pins its AbortSignal controllers (and blob
102
+ // URL) until the next `begin()`, forever for a one-shot fetch that
103
+ // succeeds and never reruns. `stopStopToken` is guarded, so the
104
+ // superseded and cancelled runs that arrive here already-stopped cost
105
+ // nothing.
106
+ stopStopToken(stopToken);
127
107
  if (openStream === stream) {
128
108
  openStream = undefined;
129
109
  }
@@ -39,8 +39,8 @@ export interface FetchContext {
39
39
  }
40
40
  /**
41
41
  * Build a {@link FetchContext} over a live fetch's primitives. One
42
- * constructor, so every context — `runFetch`'s, the prerequisite skeleton's —
43
- * carries the same `callRpc` envelope.
42
+ * constructor, so every context — `FetchMixin.runFetch`'s and `runFetchOnce`'s
43
+ * alike — carries the same `callRpc` envelope.
44
44
  *
45
45
  * **`self` has to be somewhere `getRpcSessionId` can find one, which in practice
46
46
  * means a track or something under it.** `rpcSessionId` is declared by
@@ -3,8 +3,8 @@ import { createStatusFanOut } from './progress.js';
3
3
  import { getRpcHost } from './sessionServices.js';
4
4
  /**
5
5
  * Build a {@link FetchContext} over a live fetch's primitives. One
6
- * constructor, so every context — `runFetch`'s, the prerequisite skeleton's —
7
- * carries the same `callRpc` envelope.
6
+ * constructor, so every context — `FetchMixin.runFetch`'s and `runFetchOnce`'s
7
+ * alike — carries the same `callRpc` envelope.
8
8
  *
9
9
  * **`self` has to be somewhere `getRpcSessionId` can find one, which in practice
10
10
  * means a track or something under it.** `rpcSessionId` is declared by