@jbrowse/capture 5.0.0-beta.3 → 5.0.0-beta.7

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/README.md CHANGED
@@ -19,14 +19,10 @@ config, builds a session, resolves an assembly, fetches each track, and then
19
19
  draws to a canvas — and a screenshot taken at any point before the last step is
20
20
  a picture of an empty browser that looks like a successful run.
21
21
 
22
- Which signals exist at all depends on the build. Checked on 2026-09-05, the
23
- default `--instance` — `jbrowse.org/code/jb2/latest`, the released build every
24
- genomes.jbrowse.org link opens — publishes the loading overlay
25
- (`[data-testid="loading-overlay"]`) and none of `data-app-phase`,
26
- `data-view-phase`, `data-display-phase`, `data-display-drawn` or `data-busy`.
27
- Every one of those is a **negative** signal — no overlay, no display in its
28
- loading phase, no unpainted canvas — so all of them pass on a page whose
29
- JavaScript has not started yet. Measured against that instance:
22
+ Nearly every signal a browser publishes is **negative** — no loading overlay, no
23
+ display in its loading phase, no unpainted canvas — and an absence is equally
24
+ true of a page whose JavaScript has not started. Measured against a released
25
+ build with two remote tracks:
30
26
 
31
27
  - `networkidle2` resolves at ~350ms
32
28
  - the session appears at ~880ms
@@ -36,23 +32,30 @@ JavaScript has not started yet. Measured against that instance:
36
32
  A wait chain built from those signals alone finishes in under a second and
37
33
  reports success.
38
34
 
39
- So this package puts a **positive gate** in front of them, read off the live MST
40
- session model that jbrowse-web publishes as `window.JBrowseSession`: the session
41
- exists, its views are initialized, and the assembly and trackIds you asked for
42
- are the ones actually open. A config URL that 404s, a trackId the config does
43
- not define, and an assembly name that does not match all fail there, loudly.
44
-
45
- What runs after that gate depends on which build answered it:
46
-
47
- - **One that publishes `data-app-phase`** — `jbrowse.org/code/jb2/main`, or a
48
- local build of this repo, reached with `--instance` — needs one selector and
49
- no chain: `[data-app-phase="ready"]` appears when no view is resolving an
50
- assembly and no display is fetching, and `data-display-drawn` then says paint
51
- happened.
52
- - **Every other build, the default included**, has the overlay and the status
53
- text the session model carries per display. There the app has to be _seen
54
- busy, then quiet for 2s_ — an absence alone is satisfied before the first
55
- fetch is even set up — then given a fixed 1.5s for the paint nothing reports.
35
+ So this package waits on the two **positive** signals instead, and nothing else
36
+ decides the answer:
37
+
38
+ 1. **The census.** `AppReadyMarker` publishes what is open as `data-app-views`,
39
+ `data-app-assemblies` and `data-app-tracks`, and the gate reads that one
40
+ element: at least one view, your assembly among them, every trackId you named
41
+ actually open. A config URL that 404s, a trackId the config does not define
42
+ and an assembly name that does not match the config all fail here, loudly,
43
+ and this is the only place they fail at all.
44
+ 2. **`[data-app-phase="ready"]`, held.** The session renders it when no view is
45
+ resolving an assembly and no display is fetching. It has to hold for a beat,
46
+ not merely be true once: a display drops to `ready` in the gap between one
47
+ fetch finishing and the debounced next one starting.
48
+
49
+ Then one negative gate, which is meaningful only after those two and answers
50
+ what they do not — the marker is about WORK, and a display whose fetch failed is
51
+ not working, so it reads `ready` over an error banner. `data-display-drawn` is
52
+ the stricter question, and the census of what is still unpainted goes in the
53
+ error.
54
+
55
+ **A build that publishes no `[data-app-phase]` is an error, not a slower path.**
56
+ The fallback chain that used to run there was those negative signals plus a
57
+ seen-busy-then-quiet heuristic to compensate for them, and it existed for builds
58
+ older than the marker. Point `--instance` at a build that has it.
56
59
 
57
60
  ## Library
58
61
 
@@ -60,7 +63,7 @@ What runs after that gate depends on which build answered it:
60
63
  import { captureJBrowse, openJBrowse } from '@jbrowse/capture'
61
64
 
62
65
  // one call: launch, wait, shoot, close
63
- const { pending, paintContract } = await captureJBrowse({
66
+ const { pending, unsettled } = await captureJBrowse({
64
67
  hub: 'hg38',
65
68
  loc: 'BRCA1',
66
69
  tracks: ['hg38-ncbiRefSeqCurated'],
@@ -85,9 +88,8 @@ Two waits, depending on what you did:
85
88
  that is loading starts out `loading` and the transition into `ready` is it
86
89
  finishing; a page you just clicked is already `ready` and stays that way until
87
90
  the click's work registers, so waiting for `ready` there returns on the
88
- pre-click frame. `waitForAppSettled` requires it to hold, and on a build
89
- without the marker falls back to the quiet period rather than passing
90
- instantly.
91
+ pre-click frame. `waitForAppSettled` requires it to hold, and throws on a
92
+ build with no marker rather than falling back to a wait that cannot fail.
91
93
 
92
94
  ## Timeouts and unsettled waits
93
95
 
@@ -111,20 +113,16 @@ still tells you what did not settle.
111
113
 
112
114
  ## Reading the result
113
115
 
114
- Four fields on a successful capture:
116
+ Two fields on a successful capture:
115
117
 
116
118
  - **`unsettled`** — stages that hit their timeout. Empty unless you asked to
117
119
  proceed anyway.
118
- - **`appMarker`** — which of the two paths above ran. False on the default
119
- instance, and on anything else that predates `data-app-phase`.
120
- - **`pending`** — displays still reporting unpainted when the shutter fired. A
121
- display can return to pending after its stage passed, so this is a separate
122
- question from `unsettled`.
123
- - **`paintContract`** — whether this JBrowse build publishes the per-display
124
- paint attributes at all. It is false on the default instance, so `pending: []`
125
- there means "cannot tell", not "all done", and the CLI says so. A page with no
126
- tracks open reports true — there is nothing to measure, which is not the same
127
- as being unable to.
120
+ - **`pending`** — displays still reporting unpainted when the shutter fired.
121
+ Read after `settle`, not before it, so the frame it describes is the frame
122
+ that was captured; a display that finished during the settle is not in it, and
123
+ no longer fails the run either. Whatever is left lands in `unsettled` too,
124
+ carrying each display's own phase — `loading` is a slow fetch, `error` a
125
+ banner, `ready` a display claiming it finished without drawing.
128
126
 
129
127
  ## CLI
130
128
 
package/esm/args.d.ts CHANGED
@@ -18,13 +18,23 @@ export interface ParsedArgs {
18
18
  verbose: boolean;
19
19
  help: boolean;
20
20
  allowUnsettled: boolean;
21
+ /** What `list` reads its hub and filter from. Empty for every other form. */
22
+ positionals: string[];
21
23
  }
22
24
  /**
23
25
  * Parse `jb2capture` flags. Split from the binary so the accepted shapes are
24
26
  * unit-testable without launching a browser.
25
27
  *
26
- * Unknown flags throw rather than being ignored: a mistyped `--tracks` on a tool
27
- * whose whole job is to produce a plausible-looking image would otherwise be
28
- * reported by nothing at all.
28
+ * `node:util`'s parser in strict mode, which rejects an unknown flag rather
29
+ * than ignoring it: a mistyped `--tracks` on a tool whose whole job is to
30
+ * produce a plausible-looking image would otherwise be reported by nothing at
31
+ * all. It also rejects `--fullPage=false`, which used to set the flag true.
32
+ *
33
+ * `allowPositionals` is for `list`, the one form that takes bare words. Left
34
+ * off, a stray `foo.png` is an error rather than a silently ignored argument —
35
+ * and node's unknown-flag message stays free of the advice about `--` that only
36
+ * applies to a command with positionals.
29
37
  */
30
- export declare function parseArgs(argv: string[]): ParsedArgs;
38
+ export declare function parseArgs(argv: string[], { allowPositionals }?: {
39
+ allowPositionals?: boolean;
40
+ }): ParsedArgs;
package/esm/args.js CHANGED
@@ -1,94 +1,88 @@
1
- const NUMERIC = new Set(['width', 'height', 'scale', 'timeout', 'settle']);
2
- const POSITIVE = new Set(['width', 'height', 'scale', 'timeout']);
3
- const NON_NEGATIVE = new Set(['settle']);
4
- const FLAGS = new Set([
5
- 'fullPage',
6
- 'headed',
7
- 'verbose',
8
- 'help',
9
- 'allowUnsettled',
10
- ]);
11
- const REPEATABLE = new Set(['track']);
12
- const STRINGS = new Set([
13
- 'hub',
14
- 'config',
15
- 'assembly',
16
- 'loc',
17
- 'session',
18
- 'sessionName',
19
- 'instance',
20
- 'out',
21
- ]);
1
+ import { parseArgs as parseNodeArgs } from 'node:util';
22
2
  // `--fullPage`, not `--full-page`: the flags match the option names in the
23
3
  // library API one for one, so a script and a command line say the same thing.
24
4
  // Aliases are the two abbreviations that are hard not to type.
25
- const ALIASES = { o: 'out', h: 'help' };
26
- function known(name) {
27
- return (NUMERIC.has(name) ||
28
- FLAGS.has(name) ||
29
- REPEATABLE.has(name) ||
30
- STRINGS.has(name));
5
+ const OPTIONS = {
6
+ hub: { type: 'string' },
7
+ config: { type: 'string' },
8
+ assembly: { type: 'string' },
9
+ loc: { type: 'string' },
10
+ session: { type: 'string' },
11
+ sessionName: { type: 'string' },
12
+ instance: { type: 'string' },
13
+ out: { type: 'string', short: 'o' },
14
+ track: { type: 'string', multiple: true, default: [] },
15
+ width: { type: 'string' },
16
+ height: { type: 'string' },
17
+ scale: { type: 'string' },
18
+ timeout: { type: 'string' },
19
+ settle: { type: 'string' },
20
+ fullPage: { type: 'boolean', default: false },
21
+ headed: { type: 'boolean', default: false },
22
+ verbose: { type: 'boolean', default: false },
23
+ help: { type: 'boolean', short: 'h', default: false },
24
+ allowUnsettled: { type: 'boolean', default: false },
25
+ };
26
+ function finite(name, raw) {
27
+ if (raw === undefined) {
28
+ return undefined;
29
+ }
30
+ const n = Number(raw);
31
+ if (!Number.isFinite(n)) {
32
+ throw new Error(`--${name} needs a number, got "${raw}"`);
33
+ }
34
+ return n;
35
+ }
36
+ // node:util parses every value as a string, and these two checks are the ones
37
+ // it cannot make. Without them they fail much later and elsewhere: a zero size
38
+ // inside puppeteer, naming neither the flag nor the value, and `--timeout 0` as
39
+ // no timeout at all there while the node-polled waits read it as expired.
40
+ function positive(name, raw) {
41
+ const n = finite(name, raw);
42
+ if (n !== undefined && n <= 0) {
43
+ throw new Error(`--${name} needs a positive number, got "${raw}"`);
44
+ }
45
+ return n;
46
+ }
47
+ function milliseconds(name, raw) {
48
+ const n = finite(name, raw);
49
+ if (n !== undefined && n < 0) {
50
+ throw new Error(`--${name} needs a number of milliseconds that is zero or more, got "${raw}"`);
51
+ }
52
+ return n;
31
53
  }
32
54
  /**
33
55
  * Parse `jb2capture` flags. Split from the binary so the accepted shapes are
34
56
  * unit-testable without launching a browser.
35
57
  *
36
- * Unknown flags throw rather than being ignored: a mistyped `--tracks` on a tool
37
- * whose whole job is to produce a plausible-looking image would otherwise be
38
- * reported by nothing at all.
58
+ * `node:util`'s parser in strict mode, which rejects an unknown flag rather
59
+ * than ignoring it: a mistyped `--tracks` on a tool whose whole job is to
60
+ * produce a plausible-looking image would otherwise be reported by nothing at
61
+ * all. It also rejects `--fullPage=false`, which used to set the flag true.
62
+ *
63
+ * `allowPositionals` is for `list`, the one form that takes bare words. Left
64
+ * off, a stray `foo.png` is an error rather than a silently ignored argument —
65
+ * and node's unknown-flag message stays free of the advice about `--` that only
66
+ * applies to a command with positionals.
39
67
  */
40
- export function parseArgs(argv) {
41
- const out = { tracks: [] };
42
- for (const flag of FLAGS) {
43
- out[flag] = false;
44
- }
45
- for (let i = 0; i < argv.length; i++) {
46
- const arg = argv[i];
47
- if (!arg.startsWith('-')) {
48
- throw new Error(`unexpected argument "${arg}"`);
49
- }
50
- const bare = arg.replace(/^--?/, '');
51
- const eq = bare.indexOf('=');
52
- const rawName = eq === -1 ? bare : bare.slice(0, eq);
53
- const name = ALIASES[rawName] ?? rawName;
54
- if (!known(name)) {
55
- throw new Error(`unknown flag "${arg}"`);
56
- }
57
- if (FLAGS.has(name)) {
58
- // `--fullPage=false` used to set the flag true, silently.
59
- if (eq !== -1) {
60
- throw new Error(`--${name} is a flag and takes no value; omit it to leave it off`);
61
- }
62
- out[name] = true;
63
- continue;
64
- }
65
- const value = eq === -1 ? argv[++i] : bare.slice(eq + 1);
66
- if (value === undefined) {
67
- throw new Error(`--${name} needs a value`);
68
- }
69
- if (REPEATABLE.has(name)) {
70
- ;
71
- out.tracks.push(value);
72
- }
73
- else if (NUMERIC.has(name)) {
74
- const n = Number(value);
75
- if (!Number.isFinite(n)) {
76
- throw new Error(`--${name} needs a number, got "${value}"`);
77
- }
78
- // Otherwise these fail much later and elsewhere: a zero size inside
79
- // puppeteer, naming neither the flag nor the value, and `--timeout 0` as
80
- // no timeout at all there while the node-polled waits read it as expired.
81
- if (POSITIVE.has(name) && n <= 0) {
82
- throw new Error(`--${name} needs a positive number, got "${value}"`);
83
- }
84
- if (NON_NEGATIVE.has(name) && n < 0) {
85
- throw new Error(`--${name} needs a number of milliseconds that is zero or more, got "${value}"`);
86
- }
87
- out[name] = n;
88
- }
89
- else {
90
- out[name] = value;
91
- }
92
- }
93
- return out;
68
+ export function parseArgs(argv, { allowPositionals = false } = {}) {
69
+ const { values, positionals } = parseNodeArgs({
70
+ args: argv,
71
+ options: OPTIONS,
72
+ allowPositionals,
73
+ });
74
+ const { track, width, height, scale, timeout, settle, ...rest } = values;
75
+ return {
76
+ ...rest,
77
+ // Copied: with no `--track`, node hands back the `default: []` array off
78
+ // OPTIONS itself, so every call shares one instance and a caller that
79
+ // pushes to it edits the module constant.
80
+ tracks: [...track],
81
+ width: positive('width', width),
82
+ height: positive('height', height),
83
+ scale: positive('scale', scale),
84
+ timeout: positive('timeout', timeout),
85
+ settle: milliseconds('settle', settle),
86
+ positionals,
87
+ };
94
88
  }
package/esm/bin.js CHANGED
@@ -3,7 +3,6 @@ import { readFileSync } from 'node:fs';
3
3
  import { parseArgs } from './args.js';
4
4
  import { captureJBrowse } from './capture.js';
5
5
  import { listHubTracks } from './hub.js';
6
- import { PAINT_CONTRACT_NOTE } from './sessionGate.js';
7
6
  import { jbrowseUrl } from './url.js';
8
7
  const HELP = `jb2capture — screenshot a live JBrowse 2 view, once it has finished drawing
9
8
 
@@ -37,7 +36,7 @@ THE IMAGE
37
36
 
38
37
  WAITING
39
38
  --timeout <ms> budget per wait stage (default 60000)
40
- --settle <ms> extra pause after everything reports drawn (default 0)
39
+ --settle <ms> extra pause before the census and the shot (default 0)
41
40
  --allowUnsettled write the image anyway when a stage times out
42
41
 
43
42
  Every stage is waited on for you: the session holding the assembly and tracks
@@ -85,8 +84,8 @@ function urlOptions(args) {
85
84
  instance: args.instance,
86
85
  };
87
86
  }
88
- async function runList(rest) {
89
- const [hub, filter] = rest;
87
+ async function runList(positionals) {
88
+ const [hub, filter] = positionals;
90
89
  if (!hub) {
91
90
  throw new Error('list needs an assembly, e.g. `jb2capture list hg38`. ' +
92
91
  'Browse them at https://genomes.jbrowse.org.');
@@ -105,24 +104,30 @@ async function runList(rest) {
105
104
  async function main() {
106
105
  const argv = process.argv.slice(2);
107
106
  const [first, ...rest] = argv;
108
- if (first === 'list') {
109
- await runList(rest);
110
- return;
111
- }
112
- const isUrlOnly = first === 'url';
113
- const args = parseArgs(isUrlOnly ? rest : argv);
107
+ // `list` is the one form that takes bare words, so it is the one that allows
108
+ // positionals. Routing it through the parser at all is what makes a flag
109
+ // after it an error: `list hg38 --foo` used to filter the track list on the
110
+ // string "--foo" and report that nothing matched.
111
+ const isSubcommand = first === 'list' || first === 'url';
112
+ const args = parseArgs(isSubcommand ? rest : argv, {
113
+ allowPositionals: first === 'list',
114
+ });
114
115
  if (args.help || argv.length === 0) {
115
116
  console.log(HELP);
116
117
  return;
117
118
  }
118
- if (isUrlOnly) {
119
+ if (first === 'list') {
120
+ await runList(args.positionals);
121
+ return;
122
+ }
123
+ if (first === 'url') {
119
124
  console.log(jbrowseUrl(urlOptions(args)));
120
125
  return;
121
126
  }
122
127
  if (!args.out) {
123
128
  throw new Error('--out <file.png> is required (or use `jb2capture url`)');
124
129
  }
125
- const { url, pending, paintContract, unsettled } = await captureJBrowse({
130
+ const { url, pending, unsettled } = await captureJBrowse({
126
131
  ...urlOptions(args),
127
132
  out: args.out,
128
133
  width: args.width,
@@ -152,9 +157,6 @@ async function main() {
152
157
  console.error(`warning: ${pending.length} display(s) had not finished drawing at capture time ` +
153
158
  `(${pending.join(', ')}). Raise --timeout, or --settle for a slow remote file.`);
154
159
  }
155
- else if (!paintContract) {
156
- console.error(`note: ${PAINT_CONTRACT_NOTE}`);
157
- }
158
160
  }
159
161
  try {
160
162
  await main();
package/esm/capture.js CHANGED
@@ -6,6 +6,10 @@ import { waitForJBrowseReady } from './ready.js';
6
6
  import { assemblyFromSession, trackIdsFromSession } from './session.js';
7
7
  import { jbrowseUrl } from './url.js';
8
8
  import { waitForAppSettled } from './waits.js';
9
+ // Puppeteer's own `goto` default is 30s, half of what a caller passing nothing
10
+ // is told each wait stage gets. Defaulted here so the navigation, the chain and
11
+ // the fullPage re-settle all run on one budget.
12
+ const DEFAULT_TIMEOUT = 60000;
9
13
  /**
10
14
  * Launch a browser, navigate to a JBrowse session, and wait until it has
11
15
  * rendered. The caller owns the returned browser and must close it.
@@ -19,11 +23,7 @@ import { waitForAppSettled } from './waits.js';
19
23
  * than producing an image of an empty browser.
20
24
  */
21
25
  export async function openJBrowse(options = {}) {
22
- const { width = 1400, height = 900, deviceScaleFactor = 2, headless = true, executablePath = findChromeExecutable(), args = [], onConsole,
23
- // Defaulted here as well as in waitForJBrowseReady, so the navigation gets
24
- // the same budget as the wait stages — puppeteer's own goto default is 30s,
25
- // half of what a caller passing nothing was told each stage would get.
26
- timeout = 60000, trackIds,
26
+ const { width = 1400, height = 900, deviceScaleFactor = 2, headless = true, executablePath = findChromeExecutable(), args = [], onConsole, timeout = DEFAULT_TIMEOUT, trackIds,
27
27
  // `assembly` is deliberately NOT pulled out here: it is both a URL option
28
28
  // (which assembly to open) and the session gate's expectation (which
29
29
  // assembly must end up open), and destructuring it for the second use would
@@ -102,8 +102,22 @@ export async function captureJBrowse(options = {}) {
102
102
  if (viewport && pageHeight > viewport.height) {
103
103
  await page.setViewport({ ...viewport, height: pageHeight });
104
104
  // The resize invalidates the raster and can start work (a display that
105
- // grew gained rows to draw), so the frame has to settle again.
106
- await waitForAppSettled(page, { timeout: openOptions.timeout });
105
+ // grew gained rows to draw), so the frame has to settle again — on the
106
+ // same budget the chain used, and with its outcome reported. Dropping
107
+ // the outcome made this the one stage that could give up and still
108
+ // report `unsettled: []`, over the frame most likely to be half-drawn.
109
+ const settled = await waitForAppSettled(page, {
110
+ timeout: openOptions.timeout ?? DEFAULT_TIMEOUT,
111
+ });
112
+ if (!settled) {
113
+ report.unsettled.push('the app never re-settled after the resize');
114
+ if (!openOptions.allowUnsettled) {
115
+ throw new Error(`gave up waiting after ${openOptions.timeout ?? DEFAULT_TIMEOUT}ms: ` +
116
+ 'the app never re-settled after the fullPage resize. Raise the ' +
117
+ 'timeout, or pass allowUnsettled (--allowUnsettled) to capture ' +
118
+ 'the frame as it stands.');
119
+ }
120
+ }
107
121
  }
108
122
  }
109
123
  const image = await page.screenshot({ path: out });
package/esm/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { captureJBrowse, openJBrowse } from './capture.ts';
2
2
  export { waitForJBrowseReady } from './ready.ts';
3
- export { APP_CENSUS, PAINT_CONTRACT_NOTE, describePendingDisplays, hasPaintContract, pendingDisplayStates, pendingDisplayStatesInPage, pendingDisplays, readInstrumentation, readSessionSummary, waitForSession, } from './sessionGate.ts';
3
+ export { APP_CENSUS, describePendingDisplays, pendingDisplayStates, pendingDisplayStatesInPage, pendingDisplays, readSessionSummary, waitForSession, } from './sessionGate.ts';
4
4
  export { PUBLIC_INSTANCE, jbrowseUrl } from './url.ts';
5
5
  export { fetchHubConfig, hubUrl, listHubTracks } from './hub.ts';
6
6
  export { assemblyFromSession, encodeSessionSpec, sessionSpecParam, sessionSpecQuery, trackIdsFromSession, } from './session.ts';
@@ -8,5 +8,5 @@ export { APP_READY, BUSY_SELECTOR, LOADING_OVERLAY, PENDING_DISPLAYS, delay, des
8
8
  export { BASE_CHROME_ARGS, SANDBOX_CHROME_ARGS, findChromeExecutable, isBrowserConsoleNoise, } from './browser.ts';
9
9
  export type { CaptureOptions, CaptureResult, OpenOptions, OpenResult, } from './capture.ts';
10
10
  export type { ReadyOptions, ReadyReport } from './ready.ts';
11
- export type { Instrumentation, PendingDisplay, SessionExpectations, SessionSummary, } from './sessionGate.ts';
11
+ export type { PendingDisplay, SessionExpectations, SessionSummary, } from './sessionGate.ts';
12
12
  export type { JBrowseUrlOptions } from './url.ts';
package/esm/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export { captureJBrowse, openJBrowse } from './capture.js';
2
2
  export { waitForJBrowseReady } from './ready.js';
3
- export { APP_CENSUS, PAINT_CONTRACT_NOTE, describePendingDisplays, hasPaintContract, pendingDisplayStates, pendingDisplayStatesInPage, pendingDisplays, readInstrumentation, readSessionSummary, waitForSession, } from './sessionGate.js';
3
+ export { APP_CENSUS, describePendingDisplays, pendingDisplayStates, pendingDisplayStatesInPage, pendingDisplays, readSessionSummary, waitForSession, } from './sessionGate.js';
4
4
  export { PUBLIC_INSTANCE, jbrowseUrl } from './url.js';
5
5
  export { fetchHubConfig, hubUrl, listHubTracks } from './hub.js';
6
6
  export { assemblyFromSession, encodeSessionSpec, sessionSpecParam, sessionSpecQuery, trackIdsFromSession, } from './session.js';
package/esm/ready.d.ts CHANGED
@@ -1,26 +1,14 @@
1
- import type { Instrumentation, PendingDisplay, SessionExpectations } from './sessionGate.ts';
1
+ import type { PendingDisplay, SessionExpectations } from './sessionGate.ts';
2
2
  import type { Page } from 'puppeteer';
3
3
  export interface ReadyOptions extends SessionExpectations {
4
4
  /** Budget for each individual wait stage. */
5
5
  timeout?: number;
6
6
  /**
7
- * Also wait out the app's own busy reports — adapter "Downloading…" status
8
- * text, which can outlive the loading overlay when a track streams a large
9
- * remote file, and any "Loading…/Rendering…" label still on screen. One
10
- * predicate answers both. Only consulted on a build without the app-ready
11
- * marker; the marker's `ready` already means no display is fetching.
7
+ * Extra pause after every stage reports done, for animations and tooltips —
8
+ * and for a display finishing its first paint, since the census that decides
9
+ * whether the run failed is taken after this rather than before it.
12
10
  */
13
- waitForDownloads?: boolean;
14
- /** Extra settle after everything reports done, for animations and tooltips. */
15
11
  settleMs?: number;
16
- /**
17
- * Skip the session gate. Only for a page that is not jbrowse-web and so
18
- * publishes no `window.JBrowseSession` — an embedded component, say. You lose
19
- * the only check that the data you asked for is the data on screen, and the
20
- * marker and instrumentation reads then race the boot, so the chain leans on
21
- * the seen-busy-then-quiet gate instead of the attributes.
22
- */
23
- expectSession?: boolean;
24
12
  /**
25
13
  * Return the report instead of throwing when a wait stage times out. For
26
14
  * capturing a page that is deliberately mid-load, or accepting a known-slow
@@ -37,35 +25,25 @@ export interface ReadyReport {
37
25
  * painting. Empty whenever `pending` is.
38
26
  */
39
27
  pendingStates: PendingDisplay[];
40
- /**
41
- * Whether every display's paint state was actually measurable — false when
42
- * tracks are open but the build publishes no `data-display-drawn`. With this
43
- * false, an empty `pending` means "cannot tell", not "all done".
44
- */
45
- paintContract: boolean;
46
28
  /** Wait stages that hit their timeout instead of being satisfied. */
47
29
  unsettled: string[];
48
- /**
49
- * Which readiness attributes the build published, read once the session was
50
- * up. Every `false` here is a wait that could not fail rather than one that
51
- * passed, and the chain compensates for it — see LEGACY_QUIET_MS.
52
- */
53
- instrumentation: Instrumentation;
54
- /**
55
- * Whether the page published `[data-app-phase]`, the one positive readiness
56
- * selector. True means the wait was that selector and nothing else; false
57
- * means the build predates it and the fallback chain ran.
58
- */
59
- appMarker: boolean;
60
30
  }
61
31
  /**
62
32
  * Wait until a JBrowse page has finished loading AND finished drawing.
63
33
  *
64
- * One positive gate, then the negative ones. The order is the whole point: the
65
- * DOM waits all pass on a page that has not started yet, so without the session
66
- * gate in front the chain returns in under a second (see sessionGate.ts). After
67
- * it, each stage is only meaningful once the previous has passed — a view still
68
- * resolving its assembly has mounted no displays, and a display that has mounted
69
- * but not fetched has nothing to be drawn yet.
34
+ * Two gates, both positive, which is the property that matters — every other
35
+ * readiness signal the app publishes is an ABSENCE, and an absence is equally
36
+ * true of an app that has not started.
37
+ *
38
+ * 1. The census says the assembly and trackIds you asked for are open.
39
+ * 2. `[data-app-phase="ready"]` HOLDS: no view is resolving an assembly and no
40
+ * display is fetching, and still not a beat later. A display drops to
41
+ * `ready` in the gap between one fetch finishing and the debounced next one
42
+ * starting, so a single read takes that gap for the end.
43
+ *
44
+ * Then one negative gate that is only meaningful after them, and answers the
45
+ * question the marker does not: the marker is about WORK, and a display whose
46
+ * fetch failed is not working — it reads `ready` over an error banner.
47
+ * `data-display-drawn` is the stricter gate.
70
48
  */
71
- export declare function waitForJBrowseReady(page: Page, { timeout, waitForDownloads, settleMs, expectSession, assembly, trackIds, allowUnsettled, }?: ReadyOptions): Promise<ReadyReport>;
49
+ export declare function waitForJBrowseReady(page: Page, { timeout, settleMs, assembly, trackIds, allowUnsettled, }?: ReadyOptions): Promise<ReadyReport>;