5d76e853fa84baa6c4a05880d3f3b985a5e51878
braney
Sat Aug 8 13:22:15 2026 -0700
docent: add session, loadSession and expect verbs, refs #37892
session: writes the whole cart at that step to sessions//.txt, in
the format hgSession's save-to-a-local-file button produces. No login is
involved. With sessionUrlBase: set, the run also prints the load URL, so a tour
hands back the state it built instead of only a picture of it. A file rather
than a live hgsid link, because the hgsid cart keeps changing as the tour runs.
loadSession: is the other direction: a settings file by URL, a share link, a
named session, or a local file written by an earlier session:. The load always
goes to the server named in target:, since named sessions are per-server and
every later step navigates to target: by absolute URL.
expect: is the only verb that can fail a run. It checks the rows drawn, the
image height, the tooltip now up, and the page text, then throws, so make stops
at that step rather than writing a wrong figure over a right one.
A session taken on a lifted view is short-lived: it names the quickLift hub by
its trash path, and re-pointing it at a durable copy does not work today. See
#38046. The README says so rather than working around it.
diff --git src/hg/utils/docent/README.md src/hg/utils/docent/README.md
index 2b1f3df22b2..b8a475ba67b 100644
--- src/hg/utils/docent/README.md
+++ src/hg/utils/docent/README.md
@@ -1,281 +1,403 @@
# Docent — a language for authoring guided tours of the Genome Browser
A **docent** leads a tour and explains what you are looking at. A Docent script is
that tour, written down: an ordered list of high-level verbs (`go`, `hide`,
`track`, `mouseover`, `convert`, `drag`, `shot`) that drive a real browser against a
real server.
-`docent.js` renders one script into two things at once:
+`docent.js` renders one script into three things at once:
-- a **silent mp4** of the whole tour, and
-- a named **PNG still** at every `shot:` marker.
+- a **silent mp4** of the whole tour,
+- a named **PNG still** at every `shot:` marker, and
+- a loadable **session file** at every `session:` marker.
So a published figure is literally a frame of the tour, and the two can never drift
-apart. The surface syntax is YAML — so ordinary editors highlight it and no one has to
+apart. A session file goes further: it hands back the state itself, so a reader can open
+the view the figure was taken from instead of only looking at it. The surface syntax is YAML — so ordinary editors highlight it and no one has to
learn a new parser — but the language is the verb vocabulary layered on top, not the
serialization. Scripts are named `.docent.yaml` (a bare `.docent` works too).
The verbs deliberately encode *browser mechanics* rather than selectors: `convert:`
knows that the Hide-defaults checkbox reverts when the Assembly menu reloads,
`track: {clinvar: pack}` asks trackDb which containers and checkboxes that implies, and
`mouseover:` knows that a lifted track's DOM id gains a per-run `hub__` prefix. The author writes
intent; the renderer deals with the UI.
## Run
```
PLAYWRIGHT_BROWSERS_PATH=~/pwrec/browsers NODE_PATH=~/pwrec/node_modules \
node docent.js AP1.docent.yaml
```
Needs `playwright`, `js-yaml`, and `ffmpeg`. At UCSC these live in a shared install at
`~braney/pwrec` (`pwrec/browsers` for Chromium, `pwrec/node_modules` for the modules) —
point the two variables above anywhere you have them.
Outputs, relative to the script's own directory:
- mp4 → `../.mp4` (override with `mp4:` or a second argument)
- stills → `stills//.png` (override with `stills:`)
+- sessions → `sessions//.txt` (override the parent with `sessions:`)
`docent.mk` in this directory has the make rules — include it from a project that keeps
a set of scripts and it rebuilds only the ones whose source changed. See the usage
comment at the top of that file.
## Top of file (all optional)
```yaml
target: genome-test # or rr, hgwdev, hgwbeta, hgwdev-, or a full https://.../cgi-bin
db: hg38 # source assembly
position: chr7:155.8M # starting position (tracked from then on; `go:` sets it too)
reset: true # cartReset first (clean cart + fresh quickLift hub)
size: [1000, 760] # viewport
pix: 850 # browser image width
pace: 1.2 # seconds to dwell after each step
shotHold: 2.2 # extra seconds the video pauses at a shot
trackAnim: false # skip the visible dropdown gesture on `track:` (nav only)
fast: false # true = figures only: no dwells, no cursor animation, no mp4
scale: 1 # >1 = print-resolution stills (see Print resolution)
pinMouseovers: false # record every mouseover for `pinShot:` (see below)
mp4: ../myname.mp4 # override output paths if you want
stills: stills/AP1
+sessions: sessions # PARENT of the session dirs; files land in //
+sessionUrlBase: https://hgwdev-you.gi.ucsc.edu/~you/docent/sessions # see Sessions
```
`target:` defaults to genome-test, so a script that forgets to say where it runs will
not quietly hit someone's personal sandbox.
## Print resolution
A screen still is about 120 dpi across a journal's 7-inch column — fine on a monitor,
too coarse to print. `scale:` (or `DOCENT_SCALE=k`, or `make hires`) renders **the same
tour with k times the pixels**, which is not the same thing as enlarging the stills
afterwards: a still only ever has the pixels it was drawn with, so every layer is asked
to draw more of them.
```
make hires # every scenario at 3x -> stills.hires//
make hires SCALE=2 # 2x
make hires BASES=BP1 # one scenario
DOCENT_SCALE=3 DOCENT_STILLS=stills.hires DOCENT_FAST=1 node docent.js BP1.docent.yaml
```
What k does:
- **deviceScaleFactor: k**, with the viewport left at its 1x CSS size. The page lays out
exactly as at 1x — same line breaks, same jQuery-dialog width, same tooltip placement —
and all of it rasterizes with k times the pixels.
- **`pix` × k, `textSize` stepped up with it** (hgTracks' size ladder: 6, 8, 10, 12, 14,
18, 24, 34; 3x lands exactly on 24). The server draws a genuinely wider image, and the
bigger font keeps its layout decisions proportional — tick spacing, room for labels,
how features pack into rows. A wider image with an 8px font would be a different
picture, not a bigger one.
- **`zoom: 1/k` on the image table**, handing that wider image the 1x amount of layout
space, so one image pixel lands on one device pixel. Native resolution, no resampling.
- **The tooltip font is pinned back to its 1x size.** hgTracks takes the tooltip's
font-size from the browser text size (`window.browserTextSize` → `hg/js/utils.js`
`addMouseover`), which `textSize × k` has just tripled — and then the device pixel ratio
scales the same text a second time. Left alone, a 3x still gets tooltips 3x too big: the
popups swamp the figure and the last one pinned falls off the crop.
- **Every drawn row asks for k times its height** (`