@jbrowse/product-core 4.3.0 → 5.0.0-beta.1

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 (133) hide show
  1. package/esm/RootModel/BaseRootModel.d.ts +629 -304
  2. package/esm/RootModel/BaseRootModel.js +138 -14
  3. package/esm/RootModel/HierarchicalConfig.d.ts +57 -21
  4. package/esm/RootModel/HierarchicalConfig.js +36 -0
  5. package/esm/RootModel/InternetAccounts.d.ts +18 -1
  6. package/esm/RootModel/InternetAccounts.js +120 -22
  7. package/esm/RootModel/PreferencesConfig.d.ts +53 -0
  8. package/esm/RootModel/PreferencesConfig.js +61 -0
  9. package/esm/RootModel/createConfigModel.d.ts +232 -0
  10. package/esm/RootModel/createConfigModel.js +56 -0
  11. package/esm/RootModel/index.d.ts +10 -5
  12. package/esm/RootModel/index.js +10 -5
  13. package/esm/RootModel/menuItems.d.ts +24 -0
  14. package/esm/RootModel/menuItems.js +143 -0
  15. package/esm/Session/BaseSession.d.ts +310 -11
  16. package/esm/Session/BaseSession.js +448 -14
  17. package/esm/Session/Connections.d.ts +114 -8
  18. package/esm/Session/Connections.js +195 -74
  19. package/esm/Session/DrawerWidgets.d.ts +115 -12
  20. package/esm/Session/DrawerWidgets.js +161 -42
  21. package/esm/Session/MultipleViews.d.ts +1266 -1128
  22. package/esm/Session/MultipleViews.js +357 -66
  23. package/esm/Session/Preferences.d.ts +1665 -0
  24. package/esm/Session/Preferences.js +54 -0
  25. package/esm/Session/ReferenceManagement.d.ts +28 -3
  26. package/esm/Session/ReferenceManagement.js +56 -41
  27. package/esm/Session/SessionTracks.d.ts +1275 -1138
  28. package/esm/Session/SessionTracks.js +532 -35
  29. package/esm/Session/Themes.d.ts +87 -2
  30. package/esm/Session/Themes.js +156 -23
  31. package/esm/Session/TrackMenu.d.ts +112 -0
  32. package/esm/Session/TrackMenu.js +200 -0
  33. package/esm/Session/TrackMenuSessionMixin.d.ts +27 -0
  34. package/esm/Session/TrackMenuSessionMixin.js +41 -0
  35. package/esm/Session/Tracks.d.ts +1169 -1117
  36. package/esm/Session/Tracks.js +160 -20
  37. package/esm/Session/assertSessionModel.d.ts +21 -0
  38. package/esm/Session/assertSessionModel.js +1 -0
  39. package/esm/Session/finalizeSession.d.ts +16 -0
  40. package/esm/Session/finalizeSession.js +29 -0
  41. package/esm/Session/index.d.ts +22 -10
  42. package/esm/Session/index.js +13 -10
  43. package/esm/Session/shareableSnapshot.d.ts +77 -0
  44. package/esm/Session/shareableSnapshot.js +196 -0
  45. package/esm/Session/temporaryAssemblyTracks.d.ts +23 -0
  46. package/esm/Session/temporaryAssemblyTracks.js +38 -0
  47. package/esm/assertExtends.d.ts +55 -0
  48. package/esm/assertExtends.js +1 -0
  49. package/esm/controllerTracks.d.ts +56 -0
  50. package/esm/controllerTracks.js +77 -0
  51. package/esm/destroyViewState.d.ts +21 -0
  52. package/esm/destroyViewState.js +29 -0
  53. package/esm/index.d.ts +31 -4
  54. package/esm/index.js +31 -4
  55. package/esm/loadPlugins.d.ts +33 -0
  56. package/esm/loadPlugins.js +38 -0
  57. package/esm/localFiles.d.ts +74 -0
  58. package/esm/localFiles.js +156 -0
  59. package/esm/observeSession.d.ts +56 -0
  60. package/esm/observeSession.js +91 -0
  61. package/esm/pluginInput.d.ts +32 -0
  62. package/esm/pluginInput.js +19 -0
  63. package/esm/pruneUnbuildableNodes.d.ts +109 -0
  64. package/esm/pruneUnbuildableNodes.js +568 -0
  65. package/esm/resolveAssemblies.d.ts +44 -0
  66. package/esm/resolveAssemblies.js +55 -0
  67. package/esm/rpcWorker.d.ts +26 -0
  68. package/esm/rpcWorker.js +96 -15
  69. package/esm/scheduleDetachedDestroy.d.ts +18 -0
  70. package/esm/scheduleDetachedDestroy.js +24 -0
  71. package/esm/sessionMigrations/index.d.ts +53 -0
  72. package/esm/sessionMigrations/index.js +432 -0
  73. package/esm/sessionUrl.d.ts +41 -0
  74. package/esm/sessionUrl.js +53 -0
  75. package/esm/sessionUtils.d.ts +69 -0
  76. package/esm/sessionUtils.js +613 -0
  77. package/esm/siblingCast.d.ts +14 -0
  78. package/esm/siblingCast.js +16 -0
  79. package/esm/snapshotUtils.d.ts +8 -0
  80. package/esm/snapshotUtils.js +12 -0
  81. package/esm/ui/AboutDialog.d.ts +3 -6
  82. package/esm/ui/AboutDialog.js +4 -4
  83. package/esm/ui/AboutDialogContents.d.ts +2 -6
  84. package/esm/ui/AboutDialogContents.js +34 -13
  85. package/esm/ui/AssemblyInfoPanel.d.ts +5 -0
  86. package/esm/ui/AssemblyInfoPanel.js +69 -0
  87. package/esm/ui/DisplayDefaultsSection.d.ts +36 -0
  88. package/esm/ui/DisplayDefaultsSection.js +65 -0
  89. package/esm/ui/Drawer.d.ts +21 -0
  90. package/esm/ui/Drawer.js +60 -0
  91. package/esm/ui/DrawerControls.d.ts +5 -0
  92. package/esm/ui/DrawerControls.js +28 -0
  93. package/esm/ui/DrawerHeader.d.ts +6 -0
  94. package/esm/ui/DrawerHeader.js +36 -0
  95. package/esm/ui/DrawerWidget.d.ts +5 -0
  96. package/esm/ui/DrawerWidget.js +12 -0
  97. package/esm/ui/DrawerWidgetSelector.d.ts +5 -0
  98. package/esm/ui/DrawerWidgetSelector.js +40 -0
  99. package/esm/ui/FileInfoPanel.d.ts +3 -6
  100. package/esm/ui/FileInfoPanel.js +35 -26
  101. package/esm/ui/HeaderButtons.d.ts +7 -1
  102. package/esm/ui/HeaderButtons.js +23 -18
  103. package/esm/ui/ModalWidget.d.ts +12 -0
  104. package/esm/ui/ModalWidget.js +33 -0
  105. package/esm/ui/ModalWidgetAppBar.d.ts +8 -0
  106. package/esm/ui/ModalWidgetAppBar.js +9 -0
  107. package/esm/ui/PreferencesDialog.d.ts +56 -0
  108. package/esm/ui/PreferencesDialog.js +145 -0
  109. package/esm/ui/PreferencesResetDialog.d.ts +8 -0
  110. package/esm/ui/PreferencesResetDialog.js +19 -0
  111. package/esm/ui/RefNameAliasesDialog.d.ts +9 -0
  112. package/esm/ui/RefNameAliasesDialog.js +65 -0
  113. package/esm/ui/RefNameInfoDialog.d.ts +3 -6
  114. package/esm/ui/RefNameInfoDialog.js +57 -63
  115. package/esm/ui/WidgetBody.d.ts +18 -0
  116. package/esm/ui/WidgetBody.js +22 -0
  117. package/esm/ui/WidgetHeading.d.ts +7 -0
  118. package/esm/ui/WidgetHeading.js +7 -0
  119. package/esm/ui/drawerLayout.d.ts +18 -0
  120. package/esm/ui/drawerLayout.js +22 -0
  121. package/esm/ui/index.d.ts +13 -1
  122. package/esm/ui/index.js +12 -1
  123. package/esm/ui/util.d.ts +37 -4
  124. package/esm/ui/util.js +35 -38
  125. package/esm/useEngineLifecycle.d.ts +13 -0
  126. package/esm/useEngineLifecycle.js +22 -0
  127. package/package.json +11 -15
  128. package/esm/RootModel/FormatAbout.d.ts +0 -12
  129. package/esm/RootModel/FormatAbout.js +0 -15
  130. package/esm/RootModel/FormatDetails.d.ts +0 -24
  131. package/esm/RootModel/FormatDetails.js +0 -27
  132. package/esm/Session/DialogQueue.d.ts +0 -17
  133. package/esm/Session/DialogQueue.js +0 -31
@@ -1,49 +1,189 @@
1
1
  import { types } from '@jbrowse/mobx-state-tree';
2
- import { BaseSessionModel, isBaseSession } from "./BaseSession.js";
3
- import { ReferenceManagementSessionMixin } from "./ReferenceManagement.js";
2
+ import { computed } from 'mobx';
3
+ import { BaseSessionModel, isBaseSession } from './BaseSession.js';
4
+ import { isSessionWithConnections } from './Connections.js';
5
+ import { ReferenceManagementSessionMixin } from './ReferenceManagement.js';
6
+ import { assertTrackConfOutlivesItsAssemblies } from './temporaryAssemblyTracks.js';
7
+ /**
8
+ * #stateModel TracksManagerSessionMixin
9
+ */
4
10
  export function TracksManagerSessionMixin(pluginManager) {
5
11
  return types
6
12
  .compose('TracksManagerSessionMixin', BaseSessionModel(pluginManager), ReferenceManagementSessionMixin(pluginManager))
7
13
  .views(self => ({
14
+ /**
15
+ * #getter
16
+ */
8
17
  get tracks() {
9
18
  return self.jbrowse.tracks;
10
19
  },
11
- get assemblies() {
12
- return self.jbrowse.assemblies;
13
- },
14
- getTracksById() {
20
+ }))
21
+ .views(self => {
22
+ // One index trackId → config for all tracks, assembly sequences, and
23
+ // connection tracks. Frozen jbrowse.tracks entries stay plain objects
24
+ // here; hydration to MST nodes happens lazily in TrackConfigurationReference
25
+ // on first access. Cached, so the N per-id lookups below share one rebuild
26
+ // per change rather than each re-scanning.
27
+ const tracksByIdRecord = computed(() => {
15
28
  const temporaryAssemblies = 'temporaryAssemblies' in self
16
29
  ? self.temporaryAssemblies
17
30
  : [];
18
- const connectionInstances = 'connectionInstances' in self
19
- ? self.connectionInstances
20
- : [];
31
+ // both come from the connections mixin, which not every session
32
+ // composes. connectionInstances is annotated because its element type
33
+ // is a runtime-pluggable stateModel, so the guard can't infer it.
34
+ const connectionInstances = isSessionWithConnections(self) ? self.connectionInstances : [];
35
+ const connectionTrackConfigs = isSessionWithConnections(self)
36
+ ? self.connectionTrackConfigs
37
+ : {};
21
38
  return Object.fromEntries([
22
- ...this.tracks.map(t => [t.trackId, t]),
23
- ...this.assemblies.map(a => [a.sequence.trackId, a.sequence]),
39
+ ...self.tracks.map(t => [t.trackId, t]),
40
+ // assembly sequence tracks, so they resolve by trackId
41
+ ...self.assemblies.map(a => [a.sequence.trackId, a.sequence]),
24
42
  ...temporaryAssemblies.map(a => [a.sequence.trackId, a.sequence]),
25
43
  ...connectionInstances.flatMap(c => c.tracks.map(t => [t.trackId, t])),
44
+ // Persisted configs of opened connection tracks. Placed last so they
45
+ // win over the live connection instance: identity-stable across
46
+ // reload, and resolves even when the connection isn't re-established.
47
+ ...Object.entries(connectionTrackConfigs).map(([trackId, e]) => [
48
+ trackId,
49
+ e.config,
50
+ ]),
26
51
  ]);
27
- },
28
- }))
52
+ });
53
+ // Per-id computed cache backing getTrackById. Resolving one track's config
54
+ // subscribes only to that id's computed, so editing track A leaves track
55
+ // B's observers untouched: an unedited id's entry keeps its object identity
56
+ // across a sibling edit, so its computed re-evaluates equal and MobX
57
+ // short-circuits — B never wakes. Derived on read (no reconcile autorun),
58
+ // so it is never stale mid-action: session hydration and add-and-show
59
+ // resolve straight through it. Not evicted — bounded by the distinct ids
60
+ // resolved this session, and holds no authoritative state.
61
+ const trackByIdComputeds = new Map();
62
+ return {
63
+ /**
64
+ * #method
65
+ * Config for one trackId — a track, assembly sequence, or connection
66
+ * track — or undefined. Per-id reactive: every display resolves its
67
+ * config through this (via TrackConfigurationReference) and subscribes
68
+ * only to its own id, so one track's settings edit doesn't re-render the
69
+ * others.
70
+ */
71
+ getTrackById(id) {
72
+ let c = trackByIdComputeds.get(id);
73
+ if (!c) {
74
+ c = computed(() => tracksByIdRecord.get()[id]);
75
+ trackByIdComputeds.set(id, c);
76
+ }
77
+ return c.get();
78
+ },
79
+ /**
80
+ * #method
81
+ * Every track config the session can resolve, keyed by trackId. Prefer
82
+ * the per-id reactive `getTrackById(id)`: reading this whole map
83
+ * subscribes the caller to *every* track, so an edit to any one of them
84
+ * wakes it — the reason internal display config resolution moved off
85
+ * it. Kept for plugins that look up ids in a non-reactive context.
86
+ *
87
+ * @deprecated
88
+ */
89
+ getTracksById() {
90
+ return tracksByIdRecord.get();
91
+ },
92
+ };
93
+ })
29
94
  .actions(self => ({
95
+ /**
96
+ * #action
97
+ * Add a track config to *this session*.
98
+ *
99
+ * This mixin's session has no separate session-track store, so the
100
+ * destination is the jbrowse config — which in the products that compose
101
+ * it (desktop) is the single user's own file rather than something a
102
+ * server hands other visitors, so the two scopes are the same place.
103
+ * Defined here anyway so that every session has it: a feature standing a
104
+ * track up on the user's behalf can then call one action everywhere
105
+ * instead of asking which mixin it landed on.
106
+ * `SessionTracksManagerSessionMixin` overrides it with the real
107
+ * session-scoped store.
108
+ */
109
+ addSessionTrackConf(trackConf) {
110
+ assertTrackConfOutlivesItsAssemblies(self, trackConf, 'jbrowse.tracks');
111
+ return self.jbrowse.addTrackConf(trackConf);
112
+ },
113
+ /**
114
+ * #action
115
+ * Add a track config wherever *this user's* catalog edits belong — the
116
+ * "Add track" workflows, where an admin adding a track means to add it
117
+ * for the whole site. `SessionTracksManagerSessionMixin` overrides it to
118
+ * send a non-admin's to the session instead; here there is only the one
119
+ * destination.
120
+ *
121
+ * Anything that is not an Add-track workflow wants
122
+ * `addSessionTrackConf`: a track a feature stands up on the user's behalf
123
+ * — a search result, a computed consensus, a reconstruction's labels — is
124
+ * not a catalog entry, and publishing one writes it into the config.json
125
+ * every visitor is served, once per click.
126
+ */
127
+ publishTrackConf(trackConf) {
128
+ assertTrackConfOutlivesItsAssemblies(self, trackConf, 'jbrowse.tracks');
129
+ return self.jbrowse.addTrackConf(trackConf);
130
+ },
131
+ /**
132
+ * #action
133
+ * Deprecated alias of `addSessionTrackConf`. Call that, or
134
+ * `publishTrackConf`, which say which destination they mean.
135
+ *
136
+ * @deprecated
137
+ *
138
+ * Kept because a prebuilt plugin bundle reaches this by name at runtime
139
+ * and cannot be recompiled — `jbrowse-plugin-protein3d` calls it, and a
140
+ * member lookup that stops resolving throws nothing at all
141
+ * (`pluginFacingSessionApi.test.ts` is the guard). It now means the
142
+ * session, which is the destination every such caller wanted: a track a
143
+ * plugin stands up for one user is not a catalog entry. Nothing in tree
144
+ * may call it — `no-restricted-syntax` says so.
145
+ *
146
+ * Spelled out rather than delegating through `this`, for the reason
147
+ * `SessionTracks.addToSession` is a plain closure: an action whose return
148
+ * type is inferred from a sibling's makes the pair circular.
149
+ */
30
150
  addTrackConf(trackConf) {
151
+ assertTrackConfOutlivesItsAssemblies(self, trackConf, 'jbrowse.tracks');
31
152
  return self.jbrowse.addTrackConf(trackConf);
32
153
  },
33
- deleteTrackConf(trackConf) {
34
- const callbacksToDereferenceTrack = [];
35
- const dereferenceTypeCount = {};
36
- const referring = self.getReferring(trackConf);
37
- self.removeReferring(referring, trackConf, callbacksToDereferenceTrack, dereferenceTypeCount);
38
- for (const cb of callbacksToDereferenceTrack) {
39
- cb();
154
+ /**
155
+ * #action
156
+ * Persist edited track config back to the in-memory jbrowse config. The
157
+ * session-tracks mixin overrides this so a non-admin's edits become a
158
+ * shareable session-track override instead.
159
+ */
160
+ updateTrackConfiguration(trackConf) {
161
+ // an opened connection track lives in connectionTrackConfigs, not
162
+ // jbrowse.tracks; persist its edit there (jbrowse.updateTrackConf would
163
+ // no-op, since the track isn't in the config). Desktop uses this base
164
+ // mixin, so without this branch a connection-track edit is lost on
165
+ // reload.
166
+ if (isSessionWithConnections(self) &&
167
+ trackConf.trackId in self.connectionTrackConfigs) {
168
+ self.updateConnectionTrackConfig(trackConf);
40
169
  }
170
+ else {
171
+ self.jbrowse.updateTrackConf(trackConf);
172
+ }
173
+ },
174
+ /**
175
+ * #action
176
+ */
177
+ deleteTrackConf(trackConf) {
178
+ const { trackId } = trackConf;
179
+ self.dereferenceTrack(trackId, self.getReferring(trackId));
41
180
  if (self.adminMode) {
42
181
  return self.jbrowse.deleteTrackConf(trackConf);
43
182
  }
44
183
  },
45
184
  }));
46
185
  }
186
+ /** Type guard for SessionWithTracks */
47
187
  export function isSessionWithTracks(thing) {
48
188
  return isBaseSession(thing) && 'tracks' in thing;
49
189
  }
@@ -0,0 +1,21 @@
1
+ import type { AssertExtends } from '../assertExtends.ts';
2
+ import type { AbstractSessionModel } from '@jbrowse/core/util/types';
3
+ /**
4
+ * Compile-time assertion that a concrete session-model instance type satisfies
5
+ * `AbstractSessionModel`. Reference it with a product's session instance type
6
+ * to force the check; there is no runtime component, so a missing member
7
+ * surfaces as a type error at the reference site:
8
+ *
9
+ * ```ts
10
+ * type _Check = AssertSessionModel<Instance<WebSessionModelType>>
11
+ * ```
12
+ *
13
+ * Every product's session model pairs this with one `AssertExtends` per
14
+ * capability contract it relies on, because this assertion alone is not enough:
15
+ * `AbstractSessionModel` marks those capabilities **optional**, so it cannot
16
+ * catch a member drifting out of sync with the `SessionWith*` interface plugins
17
+ * actually narrow to. Keep them as one assertion per contract rather than
18
+ * collapsing them into a list — the whole value is the compile error naming the
19
+ * contract that broke.
20
+ */
21
+ export type AssertSessionModel<T extends AbstractSessionModel> = AssertExtends<T, AbstractSessionModel>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,16 @@
1
+ import type PluginManager from '@jbrowse/core/PluginManager';
2
+ import type { IAnyModelType, SnapshotIn, SnapshotOut } from '@jbrowse/mobx-state-tree';
3
+ /**
4
+ * Apply the `Core-extendSession` extension point and drop the legacy
5
+ * object-shaped `connectionInstances` from incoming snapshots (the schema
6
+ * changed from an object to an array, so old snapshots must be filtered out,
7
+ * xref https://github.com/GMOD/jbrowse-components/issues/1903).
8
+ *
9
+ * Shared by the web session (`finalizeWebSession`) and the desktop session
10
+ * factory so the extension point + migration live in one place.
11
+ */
12
+ export declare function finalizeSession<T extends IAnyModelType>(pluginManager: PluginManager, sessionModel: T): import("@jbrowse/mobx-state-tree").ISnapshotProcessor<T, Omit<SnapshotIn<T> & {
13
+ connectionInstances?: unknown;
14
+ }, "connectionInstances">, Omit<SnapshotOut<T> & {
15
+ connectionInstances?: unknown;
16
+ }, "connectionInstances">>;
@@ -0,0 +1,29 @@
1
+ import { types } from '@jbrowse/mobx-state-tree';
2
+ /**
3
+ * Apply the `Core-extendSession` extension point and drop the legacy
4
+ * object-shaped `connectionInstances` from incoming snapshots (the schema
5
+ * changed from an object to an array, so old snapshots must be filtered out,
6
+ * xref https://github.com/GMOD/jbrowse-components/issues/1903).
7
+ *
8
+ * Shared by the web session (`finalizeWebSession`) and the desktop session
9
+ * factory so the extension point + migration live in one place.
10
+ */
11
+ export function finalizeSession(pluginManager, sessionModel) {
12
+ const extendedSessionModel = pluginManager.evaluateExtensionPoint(
13
+ /** #extensionPoint Core-extendSession | sync | Extend the session model with extra state or actions */
14
+ 'Core-extendSession', sessionModel);
15
+ return types.snapshotProcessor(extendedSessionModel, {
16
+ preProcessor(snapshot) {
17
+ const { connectionInstances, ...rest } = snapshot;
18
+ return !Array.isArray(connectionInstances) ? rest : snapshot;
19
+ },
20
+ postProcessor(snapshot) {
21
+ // connectionInstances are runtime-only: they hold the whole fetched hub
22
+ // (thousands of tracks), so persisting them bloats the session. Open
23
+ // connection tracks survive reload via `connectionTrackConfigs` instead,
24
+ // which resolves synchronously without re-establishing the connection.
25
+ const { connectionInstances: _connectionInstances, ...rest } = snapshot;
26
+ return rest;
27
+ },
28
+ });
29
+ }
@@ -1,10 +1,22 @@
1
- export * from './ReferenceManagement.ts';
2
- export * from './Connections.ts';
3
- export * from './DrawerWidgets.ts';
4
- export * from './DialogQueue.ts';
5
- export * from './Themes.ts';
6
- export * from './Tracks.ts';
7
- export * from './MultipleViews.ts';
8
- export * from './BaseSession.ts';
9
- export * from './SessionTracks.ts';
10
- export * from './BaseSession.ts';
1
+ export { ReferenceManagementSessionMixin, isSessionWithReferenceManagement, } from './ReferenceManagement.ts';
2
+ export type { ReferringNode, SessionWithReferenceManagement, SessionWithReferenceManagementType, } from './ReferenceManagement.ts';
3
+ export { ConnectionManagementSessionMixin, isSessionWithConnections, } from './Connections.ts';
4
+ export type { ConnectionTrackConfigEntry, SessionWithConnections, SessionWithConnectionsType, } from './Connections.ts';
5
+ export { DrawerWidgetSessionMixin, isSessionWithDrawerWidgets, } from './DrawerWidgets.ts';
6
+ export type { SessionWithDrawerWidgets, SessionWithDrawerWidgetsType, } from './DrawerWidgets.ts';
7
+ export { ThemeManagerSessionMixin, isSessionWithThemes } from './Themes.ts';
8
+ export type { SessionWithThemes, SessionWithThemesType } from './Themes.ts';
9
+ export { TracksManagerSessionMixin, isSessionWithTracks } from './Tracks.ts';
10
+ export type { SessionWithTracks, SessionWithTracksType } from './Tracks.ts';
11
+ export { MultipleViewsSessionMixin, isSessionWithMultipleViews, } from './MultipleViews.ts';
12
+ export type { SessionWithMultipleViews, SessionWithMultipleViewsType, } from './MultipleViews.ts';
13
+ export { PreferencesSessionMixin } from './Preferences.ts';
14
+ export { BaseSessionModel, isBaseSession, isSession } from './BaseSession.ts';
15
+ export type { BaseSession, BaseSessionType } from './BaseSession.ts';
16
+ export { SessionTracksManagerSessionMixin } from './SessionTracks.ts';
17
+ export type { EditableTrackConfig, PlainTrackConfig, SessionWithSessionTracks, SessionWithSessionTracksType, } from './SessionTracks.ts';
18
+ export { bakePromotedDefaultsIntoSnapshot, bakeSessionCascades, getShareableSessionSnapshot, } from './shareableSnapshot.ts';
19
+ export { TrackMenuItemsSessionMixin, aboutTrackMenuItem, copyTrackSnapshot, pluginExtraTrackItems, trackActionItems, trackActionMenuItems, trackListMenuItems, } from './TrackMenu.ts';
20
+ export { TrackMenuSessionMixin } from './TrackMenuSessionMixin.ts';
21
+ export { finalizeSession } from './finalizeSession.ts';
22
+ export type { AssertSessionModel } from './assertSessionModel.ts';
@@ -1,10 +1,13 @@
1
- export * from "./ReferenceManagement.js";
2
- export * from "./Connections.js";
3
- export * from "./DrawerWidgets.js";
4
- export * from "./DialogQueue.js";
5
- export * from "./Themes.js";
6
- export * from "./Tracks.js";
7
- export * from "./MultipleViews.js";
8
- export * from "./BaseSession.js";
9
- export * from "./SessionTracks.js";
10
- export * from "./BaseSession.js";
1
+ export { ReferenceManagementSessionMixin, isSessionWithReferenceManagement, } from './ReferenceManagement.js';
2
+ export { ConnectionManagementSessionMixin, isSessionWithConnections, } from './Connections.js';
3
+ export { DrawerWidgetSessionMixin, isSessionWithDrawerWidgets, } from './DrawerWidgets.js';
4
+ export { ThemeManagerSessionMixin, isSessionWithThemes } from './Themes.js';
5
+ export { TracksManagerSessionMixin, isSessionWithTracks } from './Tracks.js';
6
+ export { MultipleViewsSessionMixin, isSessionWithMultipleViews, } from './MultipleViews.js';
7
+ export { PreferencesSessionMixin } from './Preferences.js';
8
+ export { BaseSessionModel, isBaseSession, isSession } from './BaseSession.js';
9
+ export { SessionTracksManagerSessionMixin } from './SessionTracks.js';
10
+ export { bakePromotedDefaultsIntoSnapshot, bakeSessionCascades, getShareableSessionSnapshot, } from './shareableSnapshot.js';
11
+ export { TrackMenuItemsSessionMixin, aboutTrackMenuItem, copyTrackSnapshot, pluginExtraTrackItems, trackActionItems, trackActionMenuItems, trackListMenuItems, } from './TrackMenu.js';
12
+ export { TrackMenuSessionMixin } from './TrackMenuSessionMixin.js';
13
+ export { finalizeSession } from './finalizeSession.js';
@@ -0,0 +1,77 @@
1
+ import type { AbstractSessionModel } from '@jbrowse/core/util';
2
+ /**
3
+ * The session snapshot to hand to anyone else — a share link, an exported
4
+ * `session.json`, a desktop→web export. Snapshotting and baking are one call so
5
+ * the pair can't be split: a bare `getSnapshot(session)` is never a correct
6
+ * outgoing snapshot (see `bakeSessionCascades`), and three of the four
7
+ * boundaries were spelling the two steps out identically.
8
+ *
9
+ * The fourth (desktop `ExportToWebDialog`) bakes a *transformed* snapshot from
10
+ * `planWebExport`, so it calls `bakeSessionCascades` directly.
11
+ */
12
+ export declare function getShareableSessionSnapshot(session: AbstractSessionModel): Record<string, unknown>;
13
+ /**
14
+ * Everything an outgoing snapshot has to resolve because the live session
15
+ * resolves it at read time against state that is staying behind.
16
+ *
17
+ * Two such cascades, and they have nothing to do with each other — promoted
18
+ * display-type defaults are per-track rendering, workspaces intent is the
19
+ * session's layout mode. What they share is only this: both live partly in the
20
+ * sender's own browser, so a raw `getSnapshot` records neither, and the
21
+ * recipient silently resolves their own.
22
+ *
23
+ * They get one entry point rather than a call each because the failure of a
24
+ * boundary that performs half of them is invisible at that boundary — the
25
+ * export succeeds, and what is missing only shows up on someone else's screen.
26
+ * Adding a third cascade should mean editing this function, not auditing four
27
+ * call sites.
28
+ */
29
+ export declare function bakeSessionCascades(session: AbstractSessionModel, snapshot: Record<string, unknown>): Record<string, unknown>;
30
+ /**
31
+ * `getComputedStyle` for the promotable-default cascade.
32
+ *
33
+ * A track that "follows" a session-wide promoted display-type default holds no
34
+ * value of its own — its rendered value is resolved at read time from the
35
+ * promoted display-type defaults in `preferencesOverrides`, which are personal,
36
+ * localStorage-backed, and deliberately never serialized into a shared session.
37
+ * So a raw `getSnapshot(session)` records a track as at-default even when the
38
+ * sender is looking at a promoted value, and the recipient (who lacks the sender's
39
+ * preferences) sees something different. This flattens the live cascade into a
40
+ * self-contained snapshot:
41
+ *
42
+ * Every slot an open display *inherits* from a promoted default (i.e.
43
+ * `getDisplayTypeDefaultChanges` — non-customized, differs from base) is baked
44
+ * into that track's config layer: its own config for a user-added or connection
45
+ * track, else a `trackConfigDeltas` entry against the admin base (see
46
+ * `ownTrackConfig`). So the concrete value travels with the document, and
47
+ * because it lands in the track's config the recipient reads it as *customized*
48
+ * — a customized value is the top of the cascade, so their own promoted defaults
49
+ * never get consulted for it.
50
+ *
51
+ * **What this deliberately does not cover**, and the reason there is no flag
52
+ * here: a slot the sender was viewing at its *base* value. Nothing is baked for
53
+ * it (the value equals base, and `stripDefault` drops it from the snapshot
54
+ * regardless), so a recipient who has promoted something else resolves it from
55
+ * their own cascade and sees their value. There is no value that can express "I
56
+ * deliberately saw the default" — at-base and unset are byte-identical once
57
+ * stripped. That case used to be covered by stamping a per-display
58
+ * `ignorePromotedDefaults` on every open display, which cost a second
59
+ * shape-aware walk that had to stay in step with `openPromotableDisplays` by
60
+ * hand, and permanently detached received tracks from the recipient's own pins
61
+ * until they clicked "use this default" on each one. A promoted default is
62
+ * personal and local — the same as the theme a session is viewed in, which this
63
+ * function has never baked either.
64
+ *
65
+ * The live session is untouched; a modified deep copy of `snapshot` is returned.
66
+ * Tracks the sender never opened carry no display state to resolve, so they're
67
+ * left to pick up the recipient's defaults when opened — matching "export the
68
+ * actual state of the (open) tracks".
69
+ *
70
+ * Reach is `openPromotableDisplays` — literally the same walk the cascade's own
71
+ * "apply to open tracks" uses, so the two can't drift. It recurses into a
72
+ * composite view's `views` array (breakpoint-split, the linear-comparative /
73
+ * synteny family), which holds child views rather than tracks of its own —
74
+ * `LGVSyntenyDisplay` is only reachable that way, and before the recursion a
75
+ * shared session containing one rendered differently for the recipient.
76
+ */
77
+ export declare function bakePromotedDefaultsIntoSnapshot(session: AbstractSessionModel, snapshot: Record<string, unknown>): Record<string, unknown>;
@@ -0,0 +1,196 @@
1
+ import { getConf, getDisplayTypeDefaultChanges, } from '@jbrowse/core/configuration';
2
+ import { getContainingTrack, mergeTrackConfig, openPromotableDisplays, } from '@jbrowse/core/util';
3
+ import { getSnapshot } from '@jbrowse/mobx-state-tree';
4
+ import { asArray, isRecord } from '../snapshotUtils.js';
5
+ /**
6
+ * The session snapshot to hand to anyone else — a share link, an exported
7
+ * `session.json`, a desktop→web export. Snapshotting and baking are one call so
8
+ * the pair can't be split: a bare `getSnapshot(session)` is never a correct
9
+ * outgoing snapshot (see `bakeSessionCascades`), and three of the four
10
+ * boundaries were spelling the two steps out identically.
11
+ *
12
+ * The fourth (desktop `ExportToWebDialog`) bakes a *transformed* snapshot from
13
+ * `planWebExport`, so it calls `bakeSessionCascades` directly.
14
+ */
15
+ export function getShareableSessionSnapshot(session) {
16
+ return bakeSessionCascades(session, getSnapshot(session));
17
+ }
18
+ /**
19
+ * Everything an outgoing snapshot has to resolve because the live session
20
+ * resolves it at read time against state that is staying behind.
21
+ *
22
+ * Two such cascades, and they have nothing to do with each other — promoted
23
+ * display-type defaults are per-track rendering, workspaces intent is the
24
+ * session's layout mode. What they share is only this: both live partly in the
25
+ * sender's own browser, so a raw `getSnapshot` records neither, and the
26
+ * recipient silently resolves their own.
27
+ *
28
+ * They get one entry point rather than a call each because the failure of a
29
+ * boundary that performs half of them is invisible at that boundary — the
30
+ * export succeeds, and what is missing only shows up on someone else's screen.
31
+ * Adding a third cascade should mean editing this function, not auditing four
32
+ * call sites.
33
+ */
34
+ export function bakeSessionCascades(session, snapshot) {
35
+ const snap = bakePromotedDefaultsIntoSnapshot(session, snapshot);
36
+ bakeWorkspacesIntent(session, snap);
37
+ return snap;
38
+ }
39
+ /**
40
+ * The workspaces cascade: session property -> user preference -> admin
41
+ * `configuration.preferences.useWorkspaces`, of which only the first is in the
42
+ * snapshot.
43
+ *
44
+ * So a sender whose workspace is on because *their admin turned it on* exports
45
+ * a fully arranged `layout` with no statement that it is a workspace at all,
46
+ * and a recipient resolving their own cascade to false renders the classic
47
+ * stack: the arrangement arrives intact and invisible, with nothing to say it
48
+ * was dropped.
49
+ *
50
+ * Only the on case is baked. At `false` there is no arrangement to carry, so
51
+ * stamping it would be a sender with nothing to say overriding a recipient who
52
+ * prefers workspaces — the same line the promoted-default bake draws at a slot
53
+ * the sender was viewing at its base value.
54
+ *
55
+ * The paths that build a layout through an explicit user intent already set the
56
+ * property themselves (`setUseWorkspacesPreference`, ViewMenu's move-view-out,
57
+ * a spec `layout`), which is exactly what hides this: every arrangement made by
58
+ * a *drag* — split a cell, drag a tab into it, open a new tab — goes through
59
+ * `WorkspaceLayoutMixin` alone, which owns the tree and knows nothing about the
60
+ * preference.
61
+ *
62
+ * `effectiveUseWorkspaces` is read behind an `in` guard because
63
+ * `AbstractSessionModel` cannot name it (it lives on MultipleViewsSessionMixin,
64
+ * one layer up), so renaming that getter silently stops the bake rather than
65
+ * failing to compile — the hazard app-core/src/WorkspaceLayout/CLAUDE.md describes for
66
+ * `setPendingMove`. jbrowse-web's `sessionModel/exportLayout.test.ts` is the
67
+ * canary, since the bake needs a real session to have a cascade at all.
68
+ */
69
+ function bakeWorkspacesIntent(session, snap) {
70
+ if (snap.useWorkspaces === undefined &&
71
+ 'effectiveUseWorkspaces' in session &&
72
+ session.effectiveUseWorkspaces ===
73
+ true) {
74
+ snap.useWorkspaces = true;
75
+ }
76
+ }
77
+ /**
78
+ * `getComputedStyle` for the promotable-default cascade.
79
+ *
80
+ * A track that "follows" a session-wide promoted display-type default holds no
81
+ * value of its own — its rendered value is resolved at read time from the
82
+ * promoted display-type defaults in `preferencesOverrides`, which are personal,
83
+ * localStorage-backed, and deliberately never serialized into a shared session.
84
+ * So a raw `getSnapshot(session)` records a track as at-default even when the
85
+ * sender is looking at a promoted value, and the recipient (who lacks the sender's
86
+ * preferences) sees something different. This flattens the live cascade into a
87
+ * self-contained snapshot:
88
+ *
89
+ * Every slot an open display *inherits* from a promoted default (i.e.
90
+ * `getDisplayTypeDefaultChanges` — non-customized, differs from base) is baked
91
+ * into that track's config layer: its own config for a user-added or connection
92
+ * track, else a `trackConfigDeltas` entry against the admin base (see
93
+ * `ownTrackConfig`). So the concrete value travels with the document, and
94
+ * because it lands in the track's config the recipient reads it as *customized*
95
+ * — a customized value is the top of the cascade, so their own promoted defaults
96
+ * never get consulted for it.
97
+ *
98
+ * **What this deliberately does not cover**, and the reason there is no flag
99
+ * here: a slot the sender was viewing at its *base* value. Nothing is baked for
100
+ * it (the value equals base, and `stripDefault` drops it from the snapshot
101
+ * regardless), so a recipient who has promoted something else resolves it from
102
+ * their own cascade and sees their value. There is no value that can express "I
103
+ * deliberately saw the default" — at-base and unset are byte-identical once
104
+ * stripped. That case used to be covered by stamping a per-display
105
+ * `ignorePromotedDefaults` on every open display, which cost a second
106
+ * shape-aware walk that had to stay in step with `openPromotableDisplays` by
107
+ * hand, and permanently detached received tracks from the recipient's own pins
108
+ * until they clicked "use this default" on each one. A promoted default is
109
+ * personal and local — the same as the theme a session is viewed in, which this
110
+ * function has never baked either.
111
+ *
112
+ * The live session is untouched; a modified deep copy of `snapshot` is returned.
113
+ * Tracks the sender never opened carry no display state to resolve, so they're
114
+ * left to pick up the recipient's defaults when opened — matching "export the
115
+ * actual state of the (open) tracks".
116
+ *
117
+ * Reach is `openPromotableDisplays` — literally the same walk the cascade's own
118
+ * "apply to open tracks" uses, so the two can't drift. It recurses into a
119
+ * composite view's `views` array (breakpoint-split, the linear-comparative /
120
+ * synteny family), which holds child views rather than tracks of its own —
121
+ * `LGVSyntenyDisplay` is only reachable that way, and before the recursion a
122
+ * shared session containing one rendered differently for the recipient.
123
+ */
124
+ export function bakePromotedDefaultsIntoSnapshot(session, snapshot) {
125
+ const snap = structuredClone(snapshot);
126
+ // `openPromotableDisplays` is the one walk that decides reach — shared with
127
+ // the cascade's own "apply to open tracks", so the set this bakes and the set
128
+ // that acts on a promoted default can't drift
129
+ for (const display of openPromotableDisplays(session)) {
130
+ const changes = getDisplayTypeDefaultChanges(display);
131
+ if (changes.length > 0) {
132
+ bakeValues(snap, {
133
+ trackId: getConf(getContainingTrack(display), 'trackId'),
134
+ displayId: getConf(display, 'displayId'),
135
+ displayType: display.type,
136
+ values: Object.fromEntries(changes.map(c => [c.path[0], c.to])),
137
+ });
138
+ }
139
+ }
140
+ return snap;
141
+ }
142
+ // The track's own full config in the snapshot, when it has one: a user-added
143
+ // track's `sessionTracks` entry, or an opened connection track's persisted
144
+ // `connectionTrackConfigs` config. Everything else is an admin-base track,
145
+ // which carries only a delta.
146
+ //
147
+ // The connection case is not optional. `trackConfigDeltas` is merged over
148
+ // `jbrowse.tracks` alone, so a delta written for a connection track resolves
149
+ // nowhere — the recipient would render the base value, which is the exact
150
+ // failure the bake exists to prevent. `updateTrackConfiguration` splits on the
151
+ // same line.
152
+ function ownTrackConfig(snap, trackId) {
153
+ const sessionTrack = asArray(snap.sessionTracks).find(t => isRecord(t) && t.trackId === trackId);
154
+ const connectionEntry = isRecord(snap.connectionTrackConfigs)
155
+ ? snap.connectionTrackConfigs[trackId]
156
+ : undefined;
157
+ return isRecord(sessionTrack)
158
+ ? sessionTrack
159
+ : isRecord(connectionEntry) && isRecord(connectionEntry.config)
160
+ ? connectionEntry.config
161
+ : undefined;
162
+ }
163
+ // Bake one display's inherited values into the config layer: into the matching
164
+ // display of the track's own config when it has one, else merged as a
165
+ // `trackConfigDeltas` entry against the admin base.
166
+ function bakeValues(snap, bake) {
167
+ const partialDisplay = {
168
+ type: bake.displayType,
169
+ displayId: bake.displayId,
170
+ ...bake.values,
171
+ };
172
+ const ownConfig = ownTrackConfig(snap, bake.trackId);
173
+ if (ownConfig) {
174
+ const displays = asArray(ownConfig.displays);
175
+ const target = displays.find(d => isRecord(d) && d.displayId === bake.displayId);
176
+ if (isRecord(target)) {
177
+ Object.assign(target, bake.values);
178
+ }
179
+ else {
180
+ ownConfig.displays = [...displays, partialDisplay];
181
+ }
182
+ }
183
+ else {
184
+ const deltas = isRecord(snap.trackConfigDeltas)
185
+ ? { ...snap.trackConfigDeltas }
186
+ : {};
187
+ const existing = isRecord(deltas[bake.trackId])
188
+ ? deltas[bake.trackId]
189
+ : { trackId: bake.trackId };
190
+ deltas[bake.trackId] = mergeTrackConfig(existing, {
191
+ trackId: bake.trackId,
192
+ displays: [partialDisplay],
193
+ });
194
+ snap.trackConfigDeltas = deltas;
195
+ }
196
+ }
@@ -0,0 +1,23 @@
1
+ import type { AnyConfigurationModel } from '@jbrowse/core/configuration';
2
+ /**
3
+ * Dev-only check that a track config being written into a session or config list
4
+ * is one something can still draw tomorrow. No-op in production.
5
+ *
6
+ * A config naming nothing but a temporary assembly is dead the moment its view
7
+ * closes, and no list outside that view has anyone to sweep it — which is the
8
+ * whole of ADR-084. A track only one view can draw carries its config on the
9
+ * track instead (`showTrackGeneric`'s `inlineConf`), so reaching a session list
10
+ * with one is the mistake, not the cleanup afterwards.
11
+ *
12
+ * **This reports a write, not a click.** The two flows a user can drive into it
13
+ * are answered before they get here — the track menu greys its copy actions, and
14
+ * `addTrackFromWidget` opens the track inline instead of adding it — so what
15
+ * survives to report is a caller that had `inlineConf` available and did not
16
+ * take it. That is the reading the message is written for.
17
+ *
18
+ * `console.error` and never `throw`, matching `assertDisplayContract`: these
19
+ * writes happen inside launchers and menu handlers where an exception is caught
20
+ * and reported as a failed track, which would hide the violation. The jest gate
21
+ * (`config/jest/contractGate.js`) is what listens.
22
+ */
23
+ export declare function assertTrackConfOutlivesItsAssemblies(session: unknown, trackConf: AnyConfigurationModel | Record<string, unknown>, destination: string): void;