@jbrowse/capture 5.0.0-beta.8 → 5.0.0-beta.9

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.
@@ -9,54 +9,54 @@ export interface SessionExpectations {
9
9
  * the default set alone when the id you passed does not exist at all.
10
10
  */
11
11
  trackIds?: string[];
12
+ /**
13
+ * The fewest open views that count. Default 1; 0 accepts a session that
14
+ * opened none, such as one loaded to show a dialog.
15
+ */
16
+ views?: number;
12
17
  }
13
- /**
14
- * The census the app publishes beside its phase: `AppReadyMarker` renders
15
- * `data-app-views` (a count), and `data-app-assemblies` / `data-app-tracks`
16
- * (JSON string arrays of what is open).
17
- *
18
- * One element answers the whole gate. Each view declares what it holds and
19
- * `openViews`/`openTracks` reduce over those declarations (ADR-103), so nothing
20
- * here has to know that a synteny view keeps its tracks on levels and its rows
21
- * on sub-views — the duck-typed walk of `window.JBrowseSession` that used to
22
- * live here, and drifted against the copy in `isPageBusyInPage`, is gone with
23
- * the builds that needed it.
24
- *
25
- * `scripts/readinessContract.test.ts` pins the attribute names against the
26
- * marker, since neither package may import the other.
27
- */
28
- export declare const APP_CENSUS = "[data-app-tracks]";
29
18
  export interface SessionSummary {
30
19
  views: number;
31
20
  assemblies: string[];
32
21
  trackIds: string[];
33
22
  }
34
- /** What the page currently has open, or undefined if there is no census yet. */
35
- export declare function readSessionSummary(page: Page): Promise<SessionSummary | undefined>;
23
+ /**
24
+ * The census the app publishes beside its phase: `AppReadyMarker` renders
25
+ * `data-app-views` (a count), and `data-app-assemblies` / `data-app-tracks`
26
+ * (JSON string arrays of what is open). Each view declares what it holds and
27
+ * `openViews`/`openTracks` reduce over those declarations (ADR-103), so
28
+ * nothing here has to know which property a container view keeps its children
29
+ * on. `scripts/readinessContract.test.ts` pins the attribute names against the
30
+ * marker, since neither package may import the other.
31
+ *
32
+ * Serialized into the page, so it can only call what it declares.
33
+ */
36
34
  export declare function readSessionSummaryInPage(): SessionSummary | undefined;
35
+ /**
36
+ * The message the app shows in place of itself when its config, session or
37
+ * plugins fail to load. Serialized into the page.
38
+ */
39
+ export declare function readLoadFailureInPage(): string | undefined;
37
40
  /**
38
41
  * Wait until the census exists and the assembly and tracks that were asked for
39
42
  * are actually open.
40
43
  *
41
- * Throws on timeout rather than proceeding. A config URL that 404s, a trackId
42
- * that does not exist in the config, and an assembly name that does not match
43
- * the one the config declares all fail here, which is the only place they fail
44
- * at all — each of them otherwise produces a browser that loads, paints its
45
- * chrome, and photographs beautifully with nothing in it.
44
+ * A config, session or plugin that fails to load fails here at once, with the
45
+ * app's own message. A trackId that does not exist in the config, and an
46
+ * assembly name that does not match the one the config declares, fail here on
47
+ * timeout, which is the only place they fail at all — each of them otherwise
48
+ * produces a browser that loads, paints its chrome, and photographs beautifully
49
+ * with nothing in it.
46
50
  */
47
- export declare function waitForSession(page: Page, { assembly, trackIds, timeout, }?: SessionExpectations & {
51
+ export declare function waitForSession(page: Page, { assembly, trackIds, views, timeout, }?: SessionExpectations & {
48
52
  timeout?: number;
49
53
  }): Promise<void>;
50
- /**
51
- * Displays that were still reporting unpainted at the moment of the call.
52
- *
53
- * Distinct from `unsettled`, which says a wait ran out of time. This says what
54
- * the page looked like when the shutter fired.
55
- */
56
- export declare function pendingDisplays(page: Page): Promise<string[]>;
57
- /** One unpainted display and what it says about itself. */
54
+ /** One display and what it says about itself. */
58
55
  export interface PendingDisplay {
59
- /** the display TYPE, shared by every instance of it */
56
+ /**
57
+ * the display TYPE, shared by every instance of it, or its id where a banner
58
+ * has replaced the display and no type is on the page
59
+ */
60
60
  name: string;
61
61
  /** which instance, where the display publishes `data-display-id` */
62
62
  id?: string;
@@ -68,17 +68,19 @@ export interface PendingDisplay {
68
68
  phase?: string;
69
69
  }
70
70
  /**
71
- * The same census, with each display's own account of itself.
71
+ * Displays still reporting unpainted, canceled, or failed to render, each with
72
+ * its own account of itself. The phase separates the cases a bare name runs
73
+ * together —
72
74
  *
73
- * `pendingDisplays` above answers WHICH, and that was the whole report: every
74
- * timeout read the same however it was caused. The phase separates the cases a
75
- * bare name runs together —
76
- *
77
- * `loading` still fetching. A slow page or a fetch that never returns.
78
- * `error` finished, badly. Nothing is coming; the picture is a banner.
79
- * `ready` it says it is done and reports no paint. That is the display's
80
- * bug, not the wait's, and it is the one a longer timeout will
81
- * never fix.
75
+ * `loading` still fetching. A slow page or a fetch that never returns.
76
+ * `error` finished, badly. Nothing is coming; the picture is a banner.
77
+ * `canceled` stopped by the user, and it stays stopped until Retry. It is
78
+ * in the census painted or not, since the picture is the
79
+ * Retry overlay whatever was drawn before.
80
+ * `renderError` its renderer failed; the picture is a Retry banner.
81
+ * `ready` it says it is done and reports no paint. That is the
82
+ * display's bug, not the wait's, and it is the one a longer
83
+ * timeout will never fix.
82
84
  *
83
85
  * Serialized into the page, so a test can call the real function. Read fresh
84
86
  * from the DOM at report time rather than from handles the waits held. A handle
@@ -87,6 +89,13 @@ export interface PendingDisplay {
87
89
  * timeouts into nine opaque puppeteer errors (reverted in 28c6ee6d90).
88
90
  */
89
91
  export declare function pendingDisplayStatesInPage(): PendingDisplay[];
92
+ /**
93
+ * Displays showing the "too much data" banner in place of their features: the
94
+ * region is wider than the track fetches without being asked. Not a failure,
95
+ * since that is the app working as designed, but not the picture either.
96
+ * Serialized into the page.
97
+ */
98
+ export declare function tooLargeDisplaysInPage(): PendingDisplay[];
90
99
  export declare function pendingDisplayStates(page: Page): Promise<PendingDisplay[]>;
91
100
  /** `pendingDisplayStates` as one line for an error message. */
92
101
  export declare function describePendingDisplays(pending: PendingDisplay[]): string;
@@ -1,27 +1,15 @@
1
+ import { holdTrue } from './poll.js';
1
2
  /**
2
3
  * The census the app publishes beside its phase: `AppReadyMarker` renders
3
4
  * `data-app-views` (a count), and `data-app-assemblies` / `data-app-tracks`
4
- * (JSON string arrays of what is open).
5
- *
6
- * One element answers the whole gate. Each view declares what it holds and
7
- * `openViews`/`openTracks` reduce over those declarations (ADR-103), so nothing
8
- * here has to know that a synteny view keeps its tracks on levels and its rows
9
- * on sub-views — the duck-typed walk of `window.JBrowseSession` that used to
10
- * live here, and drifted against the copy in `isPageBusyInPage`, is gone with
11
- * the builds that needed it.
12
- *
13
- * `scripts/readinessContract.test.ts` pins the attribute names against the
5
+ * (JSON string arrays of what is open). Each view declares what it holds and
6
+ * `openViews`/`openTracks` reduce over those declarations (ADR-103), so
7
+ * nothing here has to know which property a container view keeps its children
8
+ * on. `scripts/readinessContract.test.ts` pins the attribute names against the
14
9
  * marker, since neither package may import the other.
10
+ *
11
+ * Serialized into the page, so it can only call what it declares.
15
12
  */
16
- export const APP_CENSUS = '[data-app-tracks]';
17
- /** What the page currently has open, or undefined if there is no census yet. */
18
- export function readSessionSummary(page) {
19
- return page.evaluate(readSessionSummaryInPage);
20
- }
21
- // Serialized into the page, so it can only call what it declares. Exported for
22
- // its test: it reads the document and nothing else, so calling it in node
23
- // against a staged DOM exercises the very function puppeteer serializes, rather
24
- // than a copy of it that can drift.
25
13
  export function readSessionSummaryInPage() {
26
14
  const marker = document.querySelector('[data-app-tracks]');
27
15
  if (!marker) {
@@ -38,50 +26,53 @@ export function readSessionSummaryInPage() {
38
26
  return undefined;
39
27
  }
40
28
  }
29
+ /**
30
+ * The message the app shows in place of itself when its config, session or
31
+ * plugins fail to load. Serialized into the page.
32
+ */
33
+ export function readLoadFailureInPage() {
34
+ return document.querySelector('[data-app-error]')?.dataset
35
+ .appError;
36
+ }
41
37
  /**
42
38
  * Wait until the census exists and the assembly and tracks that were asked for
43
39
  * are actually open.
44
40
  *
45
- * Throws on timeout rather than proceeding. A config URL that 404s, a trackId
46
- * that does not exist in the config, and an assembly name that does not match
47
- * the one the config declares all fail here, which is the only place they fail
48
- * at all — each of them otherwise produces a browser that loads, paints its
49
- * chrome, and photographs beautifully with nothing in it.
41
+ * A config, session or plugin that fails to load fails here at once, with the
42
+ * app's own message. A trackId that does not exist in the config, and an
43
+ * assembly name that does not match the one the config declares, fail here on
44
+ * timeout, which is the only place they fail at all — each of them otherwise
45
+ * produces a browser that loads, paints its chrome, and photographs beautifully
46
+ * with nothing in it.
50
47
  */
51
- export async function waitForSession(page, { assembly, trackIds = [], timeout = 60000, } = {}) {
52
- try {
53
- // #region session-gate
54
- await page.waitForFunction((wantAssembly, wantTracks) => {
55
- const marker = document.querySelector('[data-app-tracks]');
56
- if (!marker) {
57
- return false;
58
- }
59
- try {
60
- const openViews = Number(marker.dataset.appViews) || 0;
61
- const assemblies = JSON.parse(marker.dataset.appAssemblies ?? '[]');
62
- const openTracks = JSON.parse(marker.dataset.appTracks ?? '[]');
63
- return (openViews > 0 &&
64
- (wantAssembly === null || assemblies.includes(wantAssembly)) &&
65
- wantTracks.every(id => openTracks.includes(id)));
66
- }
67
- catch {
68
- return false;
69
- }
70
- }, { timeout, polling: 250 }, assembly ?? null, trackIds);
71
- // #endregion session-gate
72
- }
73
- catch {
74
- // Best-effort: the wait may have failed because the page crashed or
75
- // navigated, and an unguarded evaluate here would replace the diagnostic
76
- // below with its own opaque error.
77
- const summary = await readSessionSummary(page).catch(() => undefined);
78
- const found = summary
79
- ? `${summary.views} view(s), assemblies [${summary.assemblies.join(', ')}], tracks [${summary.trackIds.join(', ')}]`
80
- : 'no census on the page at all — either this is not a JBrowse app, or ' +
81
- 'it predates the readiness marker, in which case point --instance at a ' +
82
- 'build that publishes one';
83
- const missing = summary
84
- ? trackIds.filter(id => !summary.trackIds.includes(id))
48
+ export async function waitForSession(page, { assembly, trackIds = [], views = 1, timeout = 60000, } = {}) {
49
+ // a page that navigates or closes under us fails the evaluate, which reads as
50
+ // nothing there yet and leaves the deadline to decide
51
+ let summary;
52
+ const reached = await holdTrue(async () => {
53
+ const failure = await page
54
+ .evaluate(readLoadFailureInPage)
55
+ .catch(() => undefined);
56
+ if (failure) {
57
+ throw new Error(`JBrowse could not load: ${failure}`);
58
+ }
59
+ const now = await page
60
+ .evaluate(readSessionSummaryInPage)
61
+ .catch(() => undefined);
62
+ summary = now;
63
+ return (!!now &&
64
+ now.views >= views &&
65
+ (assembly === undefined || now.assemblies.includes(assembly)) &&
66
+ trackIds.every(id => now.trackIds.includes(id)));
67
+ }, { holdMs: 0, timeout, pollMs: 250 });
68
+ if (!reached) {
69
+ const last = summary;
70
+ const found = last
71
+ ? `${last.views} view(s), assemblies [${last.assemblies.join(', ')}], tracks [${last.trackIds.join(', ')}]`
72
+ : 'no census on the page at all, so either this is not a JBrowse app or ' +
73
+ 'it is older than v5, the first release to publish one';
74
+ const missing = last
75
+ ? trackIds.filter(id => !last.trackIds.includes(id))
85
76
  : trackIds;
86
77
  const wanted = [
87
78
  assembly ? `assembly "${assembly}"` : undefined,
@@ -90,32 +81,25 @@ export async function waitForSession(page, { assembly, trackIds = [], timeout =
90
81
  .filter(Boolean)
91
82
  .join(' and ');
92
83
  throw new Error(`the session never reached the requested state after ${timeout}ms. ` +
93
- `Wanted ${wanted || 'an open view'}; found ${found}. ` +
94
- 'A config URL that 404s, a trackId the config does not define, or an ' +
95
- 'assembly name that does not match the config all look like this.');
84
+ `Wanted ${wanted || (views > 0 ? 'an open view' : 'a session')}; found ${found}. ` +
85
+ 'A trackId the config does not define, or an assembly name that does ' +
86
+ 'not match the config, looks like this.');
96
87
  }
97
88
  }
98
89
  /**
99
- * Displays that were still reporting unpainted at the moment of the call.
100
- *
101
- * Distinct from `unsettled`, which says a wait ran out of time. This says what
102
- * the page looked like when the shutter fired.
103
- */
104
- export async function pendingDisplays(page) {
105
- return (await pendingDisplayStates(page)).map(d => d.name);
106
- }
107
- /**
108
- * The same census, with each display's own account of itself.
90
+ * Displays still reporting unpainted, canceled, or failed to render, each with
91
+ * its own account of itself. The phase separates the cases a bare name runs
92
+ * together —
109
93
  *
110
- * `pendingDisplays` above answers WHICH, and that was the whole report: every
111
- * timeout read the same however it was caused. The phase separates the cases a
112
- * bare name runs together —
113
- *
114
- * `loading` still fetching. A slow page or a fetch that never returns.
115
- * `error` finished, badly. Nothing is coming; the picture is a banner.
116
- * `ready` it says it is done and reports no paint. That is the display's
117
- * bug, not the wait's, and it is the one a longer timeout will
118
- * never fix.
94
+ * `loading` still fetching. A slow page or a fetch that never returns.
95
+ * `error` finished, badly. Nothing is coming; the picture is a banner.
96
+ * `canceled` stopped by the user, and it stays stopped until Retry. It is
97
+ * in the census painted or not, since the picture is the
98
+ * Retry overlay whatever was drawn before.
99
+ * `renderError` its renderer failed; the picture is a Retry banner.
100
+ * `ready` it says it is done and reports no paint. That is the
101
+ * display's bug, not the wait's, and it is the one a longer
102
+ * timeout will never fix.
119
103
  *
120
104
  * Serialized into the page, so a test can call the real function. Read fresh
121
105
  * from the DOM at report time rather than from handles the waits held. A handle
@@ -125,19 +109,34 @@ export async function pendingDisplays(page) {
125
109
  */
126
110
  export function pendingDisplayStatesInPage() {
127
111
  return [
128
- ...document.querySelectorAll('[data-display-drawn="false"]'),
112
+ ...document.querySelectorAll('[data-display-drawn="false"], [data-display-phase="canceled"], [data-display-phase="renderError"]'),
129
113
  ].map(el => ({
130
- name: el.dataset.testid ?? (el.id || 'unnamed display'),
114
+ name: el.dataset.testid ?? el.dataset.displayId ?? 'unnamed display',
131
115
  id: el.dataset.displayId,
132
116
  phase: el.dataset.displayPhase,
133
117
  }));
134
118
  }
119
+ /**
120
+ * Displays showing the "too much data" banner in place of their features: the
121
+ * region is wider than the track fetches without being asked. Not a failure,
122
+ * since that is the app working as designed, but not the picture either.
123
+ * Serialized into the page.
124
+ */
125
+ export function tooLargeDisplaysInPage() {
126
+ return [
127
+ ...document.querySelectorAll('[data-display-phase="tooLarge"]'),
128
+ ].map(el => ({
129
+ name: el.dataset.displayId ?? 'unnamed display',
130
+ id: el.dataset.displayId,
131
+ phase: 'tooLarge',
132
+ }));
133
+ }
135
134
  export function pendingDisplayStates(page) {
136
135
  return page.evaluate(pendingDisplayStatesInPage);
137
136
  }
138
137
  /** `pendingDisplayStates` as one line for an error message. */
139
138
  export function describePendingDisplays(pending) {
140
139
  return pending
141
- .map(d => `${d.name}${d.id ? ` (${d.id})` : ''} is ${d.phase ?? 'in an unpublished phase'}`)
140
+ .map(d => `${d.name}${d.id && d.id !== d.name ? ` (${d.id})` : ''} is ${d.phase ?? 'in an unpublished phase'}`)
142
141
  .join('; ');
143
142
  }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * How many CSS pixels of the session lie below the window.
3
+ *
4
+ * JBrowse scrolls a column inside the app rather than the document, so the
5
+ * document is always exactly the window's height. The overflow is that column's,
6
+ * found as `overflow` in jbApi finds it, less the trailing room `ViewStack`
7
+ * leaves for scrolling the last view up, which is space rather than content.
8
+ *
9
+ * Serialized into the page, so it declares everything it uses.
10
+ */
11
+ export declare function sessionOverflowInPage(): number;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * How many CSS pixels of the session lie below the window.
3
+ *
4
+ * JBrowse scrolls a column inside the app rather than the document, so the
5
+ * document is always exactly the window's height. The overflow is that column's,
6
+ * found as `overflow` in jbApi finds it, less the trailing room `ViewStack`
7
+ * leaves for scrolling the last view up, which is space rather than content.
8
+ *
9
+ * Serialized into the page, so it declares everything it uses.
10
+ */
11
+ export function sessionOverflowInPage() {
12
+ const container = document.querySelector('[data-testid^="view-container-"]');
13
+ for (let el = container?.parentElement; el; el = el.parentElement) {
14
+ const { overflowY } = getComputedStyle(el);
15
+ if ((overflowY === 'auto' || overflowY === 'scroll') &&
16
+ el.scrollHeight > el.clientHeight) {
17
+ const room = el
18
+ .querySelector('[data-testid="view-stack-overscroll"]')
19
+ ?.getBoundingClientRect().height ?? 0;
20
+ return Math.max(0, Math.ceil(el.scrollHeight - room - el.clientHeight));
21
+ }
22
+ }
23
+ return Math.max(0, document.documentElement.scrollHeight - window.innerHeight);
24
+ }
package/esm/url.d.ts CHANGED
@@ -14,13 +14,23 @@ export interface JBrowseUrlOptions {
14
14
  loc?: string;
15
15
  /** trackIds from the config to open. */
16
16
  tracks?: string[];
17
- /** The full session spec, for anything the three fields above cannot say. */
17
+ /** A session spec, for anything the three fields above cannot say. */
18
+ spec?: object;
19
+ /**
20
+ * A session saved with "Export session...": the `{ session: {...} }`
21
+ * document, or the snapshot inside it.
22
+ */
18
23
  session?: object;
19
24
  /** Name the session carries once opened. */
20
25
  sessionName?: string;
21
26
  /** JBrowse Web deployment to load. Defaults to the public one. */
22
27
  instance?: string;
23
28
  }
29
+ /**
30
+ * A spec or a saved session says which assembly, locations and tracks to open,
31
+ * and the URL can carry one of the three ways of saying so, never two.
32
+ */
33
+ export declare function assertSessionStandsAlone({ spec, session, assembly, loc, tracks, }: JBrowseUrlOptions): void;
24
34
  /**
25
35
  * The URL that opens a JBrowse Web instance onto a described view. Useful on its
26
36
  * own: an agent can hand this to a human, or paste it into a report, without
@@ -29,11 +39,11 @@ export interface JBrowseUrlOptions {
29
39
  * Two ways to say where to go, and they are not interchangeable. `assembly` /
30
40
  * `loc` / `tracks` become the [URL parameters](https://jbrowse.org/jb2/docs/urlparams/),
31
41
  * which route `loc` through the config's text-search index, so a **gene name**
32
- * works there. `session` becomes a
42
+ * works there. `spec` becomes a
33
43
  * [session spec](https://jbrowse.org/jb2/docs/urlparams/#session-spec), which
34
44
  * can describe several views and per-display settings but whose `init.loc` is
35
- * parsed as a locstring only and throws on a gene name. Pass one or the other;
36
- * `session` wins, and the URL params are dropped rather than silently starting
37
- * the fresh session that `&loc=` otherwise forces.
45
+ * parsed as a locstring only and throws on a gene name, and `session` opens a
46
+ * saved session as it was exported. Pass one of the three: `&loc=` starts a
47
+ * fresh session, so the URL cannot carry two.
38
48
  */
39
- export declare function jbrowseUrl({ hub, config, assembly, loc, tracks, session, sessionName, instance, }: JBrowseUrlOptions): string;
49
+ export declare function jbrowseUrl({ hub, config, assembly, loc, tracks, spec, session, sessionName, instance, }: JBrowseUrlOptions): string;
package/esm/url.js CHANGED
@@ -1,8 +1,22 @@
1
1
  import { hubUrl } from './hub.js';
2
- import { sessionSpecParam } from './session.js';
2
+ import { savedSessionParam, sessionSpecParam } from './session.js';
3
3
  // The public JBrowse Web build the docs' own figure links point at. Any other
4
4
  // deployment works the same way, including a local `npx serve` of a build.
5
5
  export const PUBLIC_INSTANCE = 'https://jbrowse.org/code/jb2/latest/';
6
+ /**
7
+ * A spec or a saved session says which assembly, locations and tracks to open,
8
+ * and the URL can carry one of the three ways of saying so, never two.
9
+ */
10
+ export function assertSessionStandsAlone({ spec, session, assembly, loc, tracks, }) {
11
+ if (spec && session) {
12
+ throw new Error('pass a session spec or a saved session, not both');
13
+ }
14
+ if ((spec || session) && (assembly || loc || tracks?.length)) {
15
+ throw new Error('a session spec or saved session says which assembly, locations and ' +
16
+ 'tracks to open, so it cannot be combined with assembly, loc or tracks ' +
17
+ '(--assembly, --loc, --track): put them in the session');
18
+ }
19
+ }
6
20
  /**
7
21
  * The URL that opens a JBrowse Web instance onto a described view. Useful on its
8
22
  * own: an agent can hand this to a human, or paste it into a report, without
@@ -11,34 +25,37 @@ export const PUBLIC_INSTANCE = 'https://jbrowse.org/code/jb2/latest/';
11
25
  * Two ways to say where to go, and they are not interchangeable. `assembly` /
12
26
  * `loc` / `tracks` become the [URL parameters](https://jbrowse.org/jb2/docs/urlparams/),
13
27
  * which route `loc` through the config's text-search index, so a **gene name**
14
- * works there. `session` becomes a
28
+ * works there. `spec` becomes a
15
29
  * [session spec](https://jbrowse.org/jb2/docs/urlparams/#session-spec), which
16
30
  * can describe several views and per-display settings but whose `init.loc` is
17
- * parsed as a locstring only and throws on a gene name. Pass one or the other;
18
- * `session` wins, and the URL params are dropped rather than silently starting
19
- * the fresh session that `&loc=` otherwise forces.
31
+ * parsed as a locstring only and throws on a gene name, and `session` opens a
32
+ * saved session as it was exported. Pass one of the three: `&loc=` starts a
33
+ * fresh session, so the URL cannot carry two.
20
34
  */
21
- export function jbrowseUrl({ hub, config, assembly = hub, loc, tracks, session, sessionName, instance = PUBLIC_INSTANCE, }) {
22
- const configUrl = config ?? (hub ? hubUrl(hub) : undefined);
23
- const url = new URL(instance);
24
- // URL's own encoder, not a hand-built query string: a config URL contains
25
- // `://` and `?`, and a session spec is a whole JSON document, so appending
26
- // either raw produces a link that loads a truncated config and reports
27
- // nothing.
35
+ export function jbrowseUrl({ hub, config, assembly, loc, tracks, spec, session, sessionName, instance = PUBLIC_INSTANCE, }) {
36
+ assertSessionStandsAlone({ spec, session, assembly, loc, tracks });
37
+ // In the fragment, which never reaches a server: a whole session spec in the
38
+ // query string can exceed the request-line limit and come back HTTP 414.
39
+ const params = new URLSearchParams();
28
40
  const set = (key, value) => {
29
41
  if (value) {
30
- url.searchParams.set(key, value);
42
+ params.set(key, value);
31
43
  }
32
44
  };
33
- set('config', configUrl);
34
- if (session) {
35
- set('session', sessionSpecParam(session));
45
+ set('config', config ?? (hub ? hubUrl(hub) : undefined));
46
+ if (spec) {
47
+ set('session', sessionSpecParam(spec));
48
+ }
49
+ else if (session) {
50
+ set('session', savedSessionParam(session));
36
51
  }
37
52
  else {
38
- set('assembly', assembly);
53
+ set('assembly', assembly ?? hub);
39
54
  set('loc', loc);
40
55
  set('tracks', tracks?.join(','));
41
56
  }
42
57
  set('sessionName', sessionName);
58
+ const url = new URL(instance);
59
+ url.hash = params.toString();
43
60
  return url.href;
44
61
  }
package/esm/waits.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import type { ElementHandle, Page } from 'puppeteer';
2
- export declare const delay: (ms: number) => Promise<void>;
3
2
  export declare const LOADING_OVERLAY = "[data-testid=\"loading-overlay\"]";
4
3
  /**
5
4
  * The whole readiness contract, in one selector.
@@ -17,6 +16,14 @@ export declare const LOADING_OVERLAY = "[data-testid=\"loading-overlay\"]";
17
16
  * capture taken in it is a picture of an empty browser.
18
17
  */
19
18
  export declare const APP_READY = "[data-app-phase=\"ready\"]";
19
+ /**
20
+ * A display drawing a transition between two settled pictures: canvas's row
21
+ * morph, a multi-way lane re-aligning. The app is finished working and every
22
+ * display reads `ready`, so neither the marker nor the phase can say so; the
23
+ * display publishes it itself, and a frame taken while it reads `true` is a
24
+ * picture no settled state ever shows.
25
+ */
26
+ export declare const ANIMATING_DISPLAYS = "[data-display-animating=\"true\"]";
20
27
  /**
21
28
  * Wait until the loading overlay is gone, and with `waitForDownloads` until
22
29
  * nothing else reports itself busy either — a remote fetch can outlive the
@@ -72,11 +79,8 @@ export declare const PENDING_DISPLAYS = "[data-display-drawn=\"false\"]";
72
79
  * finished badly and is showing a banner, and `ready` is a display claiming it
73
80
  * finished without drawing — a bug in the display rather than in the wait.
74
81
  *
75
- * An EMPTY census is the other answer, and `tooLarge` / `renderError` are why it
76
- * is not good news. Those two replace the display's subtree outright, so they
77
- * publish neither attribute and appear here as nothing at all: a wait on such a
78
- * display can never resolve and can never be reported. Nothing pending means
79
- * either that, or a selector that was never a display's.
82
+ * A display showing "too much data" is not pending, and a wait on its body
83
+ * can never resolve, so the census names those too.
80
84
  */
81
85
  export declare function waitForSelectorAttributed(page: Page, selector: string, timeoutMs: number): Promise<ElementHandle>;
82
86
  /**
@@ -91,8 +95,6 @@ export declare const displayPainted: (testid: string) => string;
91
95
  * on the phase instead.
92
96
  */
93
97
  export declare const displaySettled: (testid: string) => string;
94
- /** One display, by its config's `displayId` rather than by type. */
95
- export declare const displayById: (displayId: string) => string;
96
98
  export declare function waitForDisplaysDone(page: Page, timeoutMs: number): Promise<boolean>;
97
99
  export declare function waitForDisplayPhases(page: Page, timeoutMs: number): Promise<boolean>;
98
100
  export declare function waitForViewPhases(page: Page, timeoutMs: number): Promise<import("puppeteer").JSHandle<false> | import("puppeteer").JSHandle<true>>;
@@ -109,6 +111,7 @@ export declare function waitForViewPhases(page: Page, timeoutMs: number): Promis
109
111
  * wherever it tells a user it is working
110
112
  * `data-display-phase` one display's own fetch, newer builds
111
113
  * `data-view-phase` a view still resolving its assembly, newer builds
114
+ * `data-display-animating` a display drawing a morph, newer builds
112
115
  */
113
116
  export declare const BUSY_SELECTOR: string;
114
117
  /**
@@ -141,9 +144,6 @@ export declare function isPageBusyInPage(busySelector: string): boolean;
141
144
  * "nothing is in flight right now", which is what a probe or a benchmark wants
142
145
  * between two measured actions.
143
146
  *
144
- * Polled from node rather than in-page: chrome throttles in-page timers and rAF
145
- * once the tab is not visible, which is the state a headless capture sits in.
146
- *
147
147
  * Returns false on timeout rather than throwing, like its neighbours.
148
148
  */
149
149
  export declare function waitForQuietPeriod(page: Page, { quietMs, timeout, pollMs, }?: {
@@ -165,8 +165,6 @@ export declare function waitForQuietPeriod(page: Page, { quietMs, timeout, pollM
165
165
  export declare function waitForAppReady(page: Page, { timeout }?: {
166
166
  timeout?: number;
167
167
  }): Promise<boolean>;
168
- /** Whether this build publishes the app-level readiness marker at all. */
169
- export declare function hasAppReadyMarker(page: Page): Promise<boolean>;
170
168
  /**
171
169
  * Wait out work an INTERACTION started: `ready`, and still ready a beat later.
172
170
  *
@@ -191,9 +189,8 @@ export declare function hasAppReadyMarker(page: Page): Promise<boolean>;
191
189
  * replaced. Requiring the idle to HOLD costs the hold and no more, and catches
192
190
  * the same late-starting work.
193
191
  *
194
- * Polled from Node rather than by `page.waitForSelector`, for the reason
195
- * `waitForQuietPeriod` is: chrome throttles in-page timers and rAF once the tab
196
- * is not visible, which is exactly the state a headless capture sits in.
192
+ * The hold also requires no view body to be waiting on its lazy component and
193
+ * no display to be animating, neither of which the marker can see.
197
194
  *
198
195
  * Throws on a build too old for the marker rather than falling back — see the
199
196
  * body.