6af92621b7dba437be69c05f3ff49079ba637383
braney
  Fri Sep 4 11:43:39 2026 -0700
docent: let convert: take a bare string, like every other verb

convert: hs1 left the argument as a String, so o.to was undefined and o.search
picked up String.prototype.search. The run then reported

convert: "function search() { [native code] }" matched nothing

which says nothing about what is wrong with the script. A bare string is now
the target assembly, matching hub:, addCustomTrack:, loadSession: and the rest.
quicklift: is still never implied, so convert: hs1 is a plain coordinate
convert; the README row says so now.

Found while writing the first of the regression tests for #38252, fourteen of
which open with convert:.

refs #37892

diff --git src/hg/utils/docent/docent.js src/hg/utils/docent/docent.js
index c8604e635d1..faa72a30d3b 100755
--- src/hg/utils/docent/docent.js
+++ src/hg/utils/docent/docent.js
@@ -1395,31 +1395,36 @@
   async function resolveTarget(to) {
     if (/^GC[AF]_/i.test(to)) return to;
     const matches = await page.$$eval('#hglft_toDbSelect option', (os, q) => {
       const norm = t => t.toLowerCase().replace(/[^a-z0-9]+/g, ' ');
       const toks = norm(q).trim().split(' ').filter(Boolean);
       return os.filter(o => toks.every(tk => norm(o.text).includes(tk))).map(o => ({ v: o.value, t: o.text }));
     }, String(to));
     if (!matches.length) throw new Error('convert target not found in Assembly dropdown: ' + to);
     if (matches.length > 1) {
       console.warn(`WARNING: "${to}" matches ${matches.length} assemblies; using the first. Use an exact accession to disambiguate:`);
       matches.forEach(m => console.warn(`    ${m.v}  ${m.t}`));
     }
     return matches[0].v;
   }
   async function convert(o) {
-    o = o || {};
+    // A bare string is the target assembly, the way every other verb takes its one
+    // obvious argument. Without this `convert: hs1` left o as the String, so o.to was
+    // undefined and o.search picked up String.prototype.search -- the run then reported
+    // `convert: "function search() { [native code] }" matched nothing`, which tells the
+    // reader nothing about what is wrong with their script.
+    o = (typeof o === 'string') ? { to: o } : (o || {});
     // `shot:` here can name up to three moments of the Convert page, none of which any
     // other verb can reach (after Submit the tour is already on the results page):
     //   shot: convert_filled                 -- just before Submit (the common one)
     //   shot: {opened: a, filled: b, result: c}
     //     opened  the page as it comes up, nothing chosen yet
     //     filled  target searched for, QuickLift/Hide-defaults set -- ready to Submit
     //     result  the conversion result page (the coordinate link `open: lift` clicks)
     const shots = (o.shot == null) ? {}
                 : (typeof o.shot === 'string' ? { filled: o.shot } : o.shot);
     for (const k of Object.keys(shots))
       if (!['opened', 'filled', 'result'].includes(k))
         console.warn(`convert shot: unknown moment "${k}" (use opened, filled or result)`);
     try {
       await glideTo('#view'); await page.hover('#view'); await dwell(900);
       await clickGlide('a#convertMenuLink'); await page.waitForSelector('#hglft_toDbSelect', { timeout: 8000 });