@jbrowse/app-core 5.0.0-beta.4 → 5.0.0-beta.8
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.
- package/esm/JbApi/jbApi.d.ts +1461 -55
- package/esm/JbApi/jbApi.js +84 -15
- package/esm/ui/App/AppReadyMarker.js +21 -5
- package/package.json +3 -3
package/esm/JbApi/jbApi.js
CHANGED
|
@@ -63,25 +63,76 @@ function displayState(track) {
|
|
|
63
63
|
? { reason: display.regionTooLargeReason }
|
|
64
64
|
: {}),
|
|
65
65
|
...(display.error ? { error: String(display.error) } : {}),
|
|
66
|
+
...(display.renderError
|
|
67
|
+
? { renderError: String(display.renderError) }
|
|
68
|
+
: {}),
|
|
66
69
|
};
|
|
67
70
|
}
|
|
68
71
|
catch (e) {
|
|
69
72
|
return { display: display.type, error: String(e) };
|
|
70
73
|
}
|
|
71
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* The drawer and any modal, for the same reason `offscreen` is reported: both
|
|
77
|
+
* are on top of the views in a screenshot that otherwise looks right.
|
|
78
|
+
*
|
|
79
|
+
* The drawer takes a column off every view rather than covering them, so a
|
|
80
|
+
* figure framed with the track selector open is narrower than the one asked
|
|
81
|
+
* for — which every filmed take worked around by telling the agent in its
|
|
82
|
+
* system prompt to close it. A modal covers the app outright.
|
|
83
|
+
*/
|
|
84
|
+
function sessionChrome(session) {
|
|
85
|
+
const s = session;
|
|
86
|
+
const drawer = s.drawerVisible && s.visibleWidget
|
|
87
|
+
? {
|
|
88
|
+
drawer: {
|
|
89
|
+
widget: s.visibleWidget.type,
|
|
90
|
+
...(typeof s.drawerWidth === 'number'
|
|
91
|
+
? { width: s.drawerWidth }
|
|
92
|
+
: {}),
|
|
93
|
+
note: 'the drawer takes this width off every view; session.hideAllWidgets() closes it',
|
|
94
|
+
},
|
|
95
|
+
}
|
|
96
|
+
: {};
|
|
97
|
+
const dialog = s.DialogComponent
|
|
98
|
+
? {
|
|
99
|
+
dialog: {
|
|
100
|
+
note: 'a modal dialog is open over the app, so a screenshot is of the dialog',
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
: {};
|
|
104
|
+
return { ...drawer, ...dialog };
|
|
105
|
+
}
|
|
72
106
|
export function sessionOf(pluginManager) {
|
|
73
107
|
return pluginManager?.rootModel?.session;
|
|
74
108
|
}
|
|
75
109
|
// A view whose init failed (its assembly was never found) paints the error in
|
|
76
110
|
// place of a genome and raises no toast, and stays uninitialized: without
|
|
77
111
|
// this, a spec with "volvix" for "volvox" settled false with nothing said.
|
|
112
|
+
/**
|
|
113
|
+
* Why a view is not finished, or nothing if it is.
|
|
114
|
+
*
|
|
115
|
+
* The three terms are AppReadyMarker's, and they have to be: the marker decides
|
|
116
|
+
* whether the settle ends, this decides what it says when it doesn't, and a
|
|
117
|
+
* term in one and not the other is a settle that answers `false` with an empty
|
|
118
|
+
* `notReady`. `launching` is the one that was missing — `initialized` goes true
|
|
119
|
+
* the moment a linear view's regions land, while the same apply pass still has
|
|
120
|
+
* the spec's tracks to attach, so a spec load timing out there reported no
|
|
121
|
+
* reason at all.
|
|
122
|
+
*/
|
|
78
123
|
function viewState(v) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
124
|
+
if (v.error !== undefined) {
|
|
125
|
+
return { error: String(v.error) };
|
|
126
|
+
}
|
|
127
|
+
if (v.initialized === false) {
|
|
128
|
+
return { phase: 'initializing' };
|
|
129
|
+
}
|
|
130
|
+
return v.pendingLaunch === undefined
|
|
131
|
+
? {}
|
|
132
|
+
: {
|
|
133
|
+
phase: 'launching',
|
|
134
|
+
reason: 'the view is still applying what it was launched with (navigation, tracks)',
|
|
135
|
+
};
|
|
85
136
|
}
|
|
86
137
|
function viewSummary(view) {
|
|
87
138
|
const v = viewSelf(view);
|
|
@@ -115,6 +166,7 @@ function sessionSummary(session) {
|
|
|
115
166
|
return {
|
|
116
167
|
name: session.name,
|
|
117
168
|
assemblyNames: session.assemblyNames,
|
|
169
|
+
...sessionChrome(session),
|
|
118
170
|
views: session.views.map(v => viewSummary(v)),
|
|
119
171
|
};
|
|
120
172
|
}
|
|
@@ -312,6 +364,7 @@ root = document) {
|
|
|
312
364
|
...(messages.length ? { notifications: messages } : {}),
|
|
313
365
|
...(notReady.length ? { notReady } : {}),
|
|
314
366
|
...(offscreen ? { offscreen } : {}),
|
|
367
|
+
...(session ? sessionChrome(session) : {}),
|
|
315
368
|
};
|
|
316
369
|
}
|
|
317
370
|
// A session taller than the window scrolls, and a screenshot of the viewport
|
|
@@ -471,7 +524,7 @@ function shownTrackModel(session, trackId, viewId) {
|
|
|
471
524
|
// it was
|
|
472
525
|
if (!track) {
|
|
473
526
|
throw new Error(session.getTrackById(trackId)
|
|
474
|
-
? `"${trackId}" is not shown in ${viewId === undefined ? 'any open view' : `view ${viewId}`} — view.
|
|
527
|
+
? `"${trackId}" is not shown in ${viewId === undefined ? 'any open view' : `view ${viewId}`} — await view.launchTrack("${trackId}") first; jb.sessionSummary() lists what each view shows`
|
|
475
528
|
: `No track with trackId "${trackId}" — jb.listTracks() shows what is available`);
|
|
476
529
|
}
|
|
477
530
|
return track;
|
|
@@ -715,16 +768,16 @@ const JB_HELP = `jb drives this JBrowse app programmatically (window.jb in a bro
|
|
|
715
768
|
|
|
716
769
|
Orient first: jb.sessionSummary(). Introspect, never guess: jb.listTracks(search?, limit?) answers { total, tracks } with the trackIds; jb.describeSlots(jb.trackModel('someTrackId').activeDisplay.configuration) for the settings keys a display accepts — an unknown settings key is not an error, it lands in applyDisplaySettings' "unapplied" list, so read the report; jb.inspect('views.0') for a live node's getters, actions and modelType.
|
|
717
770
|
|
|
718
|
-
The model is mobx-state-tree: mutate only through actions (raw assignment throws), and write display settings with track.applyDisplaySettings(settings). Build views declaratively with jb.loadSessionSpec({ views: [{ type: 'LinearGenomeView', assembly, loc, tracks: [...] }] }); arrange the views already open into panels with session.layoutViews({ direction: 'horizontal', children: [{ views: [viewId] }, ...] }) — leaves name view ids or indexes into session.views; add data with jb.addTrack({ location }) (an absolute path or a URL); read data with await jb.getFeatures({ trackId, loc?, assembly?, byteLimit? }) (or jb.getFeatures(trackId, loc?, opts?)), which renames refNames ("chr1" vs "1") so the file answers and reads on the worker the track's display uses — raw adapter code must call jb.renameRegionsIfNeeded itself. After changing anything, await jb.waitReady(ms) and read its notifications and notReady lists before trusting the screen.
|
|
771
|
+
The model is mobx-state-tree: mutate only through actions (raw assignment throws), and write display settings with track.applyDisplaySettings(settings). Build views declaratively with jb.loadSessionSpec({ views: [{ type: 'LinearGenomeView', assembly, loc, tracks: [...] }] }); arrange the views already open into panels with session.layoutViews({ direction: 'horizontal', children: [{ views: [viewId] }, ...] }) — leaves name view ids or indexes into session.views; add data with jb.addTrack({ location }) (an absolute path or a URL); read data with await jb.getFeatures({ trackId, loc?, assembly?, viewId?, regions?, byteLimit? }) (or jb.getFeatures(trackId, loc?, opts?)), which renames refNames ("chr1" vs "1") so the file answers and reads on the worker the track's display uses — raw adapter code must call jb.renameRegionsIfNeeded itself. After changing anything, await jb.waitReady(ms) and read its notifications and notReady lists before trusting the screen.
|
|
719
772
|
|
|
720
|
-
Views nest and several can be open. jb.view(viewId?) is the open view, and jb.view(), jb.trackModel(trackId), jb.visibleRegions() and jb.
|
|
773
|
+
Views nest and several can be open. jb.view(viewId?) is the open view, and jb.view(), jb.trackModel(trackId), jb.visibleRegions(), jb.addTrack and jb.getFeatures over a visible region throw naming the candidates rather than picking one when more than one view could answer — pass viewId (from jb.sessionSummary()) to say which.
|
|
721
774
|
|
|
722
775
|
Full guide: https://jbrowse.org/jb2/docs/agents_live_model (JBrowse Desktop serves the same guide offline through its MCP docs tool — Help menu, "Connect an AI agent...").`;
|
|
723
776
|
// The helper library an agent drives the app through. Built from the plugin
|
|
724
777
|
// manager alone, so one of these serves a whole app rather than one session —
|
|
725
778
|
// which is what lets jbrowse-web hand the same object to every caller for the
|
|
726
779
|
// life of a plugin manager.
|
|
727
|
-
export function createJbApi(pluginManager) {
|
|
780
|
+
export function createJbApi(pluginManager, options = {}) {
|
|
728
781
|
// Resolved per call, never captured: jb.loadSessionSpec REPLACES the session,
|
|
729
782
|
// and a helper bound to the old one keeps answering from a detached tree —
|
|
730
783
|
// which reads as stale data rather than throwing, so the agent is told about
|
|
@@ -737,6 +790,18 @@ export function createJbApi(pluginManager) {
|
|
|
737
790
|
}
|
|
738
791
|
return current;
|
|
739
792
|
};
|
|
793
|
+
// Every settle an agent can reach goes through here, so a notification
|
|
794
|
+
// cannot be consumed by one of these helpers without the caller hearing
|
|
795
|
+
// about it.
|
|
796
|
+
// a declaration, not a generic arrow: `<T>(x: T) => …` in a .ts file is a
|
|
797
|
+
// JSX tag to babel, which is what jest parses these with
|
|
798
|
+
function reported(settle) {
|
|
799
|
+
const messages = settle.notifications;
|
|
800
|
+
if (Array.isArray(messages) && messages.length > 0) {
|
|
801
|
+
options.onNotifications?.(messages);
|
|
802
|
+
}
|
|
803
|
+
return settle;
|
|
804
|
+
}
|
|
740
805
|
const jb = {
|
|
741
806
|
help: JB_HELP,
|
|
742
807
|
get session() {
|
|
@@ -760,15 +825,15 @@ export function createJbApi(pluginManager) {
|
|
|
760
825
|
stopStopToken,
|
|
761
826
|
// a default because an omitted number made the deadline NaN, and a view
|
|
762
827
|
// that never readied then held the call open to the relay's own timeout
|
|
763
|
-
waitReady: (timeoutMs = 30_000) => waitReady(timeoutMs, live()),
|
|
828
|
+
waitReady: async (timeoutMs = 30_000) => reported(await waitReady(timeoutMs, live())),
|
|
764
829
|
sessionSummary: () => sessionSummary(live()),
|
|
765
830
|
inspect: (path, maxInspectBytes) => inspectSession(live(), { path, maxBytes: maxInspectBytes }),
|
|
766
831
|
listTracks: (search, limit) => listTracks(live(), search, limit),
|
|
767
832
|
view: (viewId) => viewById(live(), viewId),
|
|
768
833
|
trackModel: (trackId, viewId) => shownTrackModel(live(), trackId, viewId),
|
|
769
834
|
visibleRegions: (viewId) => visibleRegionsOf(live(), viewId),
|
|
770
|
-
loadSessionSpec: (spec, settleMs) => loadSpec(pluginManager, { spec, settleMs }),
|
|
771
|
-
addTrack: (opts) => addTrack(pluginManager, live(), opts),
|
|
835
|
+
loadSessionSpec: async (spec, settleMs) => reported(await loadSpec(pluginManager, { spec, settleMs })),
|
|
836
|
+
addTrack: (opts) => addTrack(pluginManager, live(), opts).then(reported),
|
|
772
837
|
// Two of four filmed takes wrote jb.getFeatures('trackId', loc) and lost a
|
|
773
838
|
// turn to "No track with trackId undefined", and a third wrote
|
|
774
839
|
// jb.getFeatures('trackId', loc, { assembly }) and had the options
|
|
@@ -831,12 +896,16 @@ async function addTrack(pluginManager, session, args) {
|
|
|
831
896
|
if (args.show === false) {
|
|
832
897
|
return summary;
|
|
833
898
|
}
|
|
834
|
-
const view = pickView(session, args, '
|
|
899
|
+
const view = pickView(session, args, 'launchTrack', {
|
|
835
900
|
assembly,
|
|
836
901
|
trackType: conf.type,
|
|
837
902
|
pluginManager,
|
|
838
903
|
});
|
|
839
|
-
|
|
904
|
+
// awaited: a display state model is lazy in every plugin here, and the
|
|
905
|
+
// synchronous showTrack answers before the chunk lands — so the settle below
|
|
906
|
+
// would run against a session that does not hold the track yet, and report
|
|
907
|
+
// ready over it
|
|
908
|
+
await viewSelf(view).launchTrack(conf.trackId);
|
|
840
909
|
const shown = { ...summary, shownInView: view.id };
|
|
841
910
|
// 0 skips the settle, so several adds can share one jb.waitReady
|
|
842
911
|
const settleMs = typeof args.settleMs === 'number' ? args.settleMs : ADD_TRACK_SETTLE_MS;
|
|
@@ -4,14 +4,30 @@ import { observer } from 'mobx-react';
|
|
|
4
4
|
function trackLoading(track) {
|
|
5
5
|
return track.displays.some(d => d.displayPhase === 'loading');
|
|
6
6
|
}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
/**
|
|
8
|
+
* A view whose init failed (its assembly was never found) stays uninitialized
|
|
9
|
+
* for good and paints its error, so like a display's terminal phase it is
|
|
10
|
+
* finished, not pending: without this the whole app read `loading` for as long
|
|
11
|
+
* as that view was open, and every settle answered false with no reason.
|
|
12
|
+
*
|
|
13
|
+
* `pendingLaunch` is the third term and the one that was missing. `initialized`
|
|
14
|
+
* goes true mid-launch — LGV's flips the moment displayedRegions land, while
|
|
15
|
+
* the same apply pass still has the spec's tracks to attach — and `showLoading`
|
|
16
|
+
* goes false at the same moment, deliberately, so the reader sees the
|
|
17
|
+
* navigated view rather than a spinner while its tracks arrive. That left a
|
|
18
|
+
* window, exactly as wide as attaching the tracks, in which the marker said
|
|
19
|
+
* `ready` over a session missing everything it had been asked for.
|
|
20
|
+
* `whenViewSettled` states the same rule for the callers that await a single
|
|
21
|
+
* view, and says why in more detail. Not shared as one predicate: that one
|
|
22
|
+
* requires `initialized`, while absent here means not loading, and an adapter
|
|
23
|
+
* between the two null rules would read as agreement it does not have.
|
|
24
|
+
*/
|
|
11
25
|
function viewLoading(view) {
|
|
12
26
|
const flags = view;
|
|
13
27
|
return (flags.error === undefined &&
|
|
14
|
-
(flags.showLoading === true ||
|
|
28
|
+
(flags.showLoading === true ||
|
|
29
|
+
flags.initialized === false ||
|
|
30
|
+
flags.pendingLaunch !== undefined));
|
|
15
31
|
}
|
|
16
32
|
/**
|
|
17
33
|
* One element that says whether the whole app has finished, and what is open.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jbrowse/app-core",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "JBrowse 2 code shared between the 'full featured' apps e.g. jbrowse-web and jbrowse-desktop",
|
|
6
6
|
"keywords": [
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"@mui/material": "^9.4.0",
|
|
30
30
|
"mobx": "^7.0.3",
|
|
31
31
|
"mobx-react": "^10.0.2",
|
|
32
|
-
"@jbrowse/core": "^5.0.0-beta.
|
|
33
|
-
"@jbrowse/
|
|
32
|
+
"@jbrowse/product-core": "^5.0.0-beta.8",
|
|
33
|
+
"@jbrowse/core": "^5.0.0-beta.8"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"react": ">=19.0.0",
|