    /* the walk's own playlist, on the real core: a row's sheet, the row
       removed and put back by Undo — the first time that stash has made
       the round trip through real SQLite — the same sheet off a real
       press, and the filter. Everything here acts on "Walk test", which
       the walk made four steps ago and deletes eight steps below, and the
       order is the one the drags above restored. */
    { do: `document.querySelectorAll('.pl-rows .trk')[0].dispatchEvent(new MouseEvent('contextmenu', { bubbles: true, cancelable: true }))`, name: "playlist-row-sheet", wait: 900 },
    { do: `__click('.halfsheet .row', 'Remove from playlist')`, name: "playlist-removed", wait: 900 },
    { do: `__click('.toast.notice .na', 'Undo')`, name: "playlist-undone", wait: 1600 },
    /* a press released without moving is still the row's sheet — on real
       touch geometry this time, not a synthesised contextmenu */
    { do: `__hold('.pl-rows .trk', 0)`, name: "playlist-drag-held", wait: 900 },
    { do: `(function () { var s = document.querySelector('.scrim'); if (s) s.click(); return true; })()`, wait: 600 },
    /* a filter that matches nothing: the rows go and the stub says so. The
       drag-refusal note hangs off the UNFILTERED list, so it is up here
       whatever the filter matched — which is what makes this readable
       against a playlist made out of whichever record the walk picked. */
    { do: `(function () { var i = document.querySelector('.plfilter input'); i.value = 'zzqq'; i.dispatchEvent(new Event('input', { bubbles: true })); })()`, name: "playlist-filter", wait: 700 },
    { do: `(function () { var i = document.querySelector('.plfilter input'); i.value = ''; i.dispatchEvent(new Event('input', { bubbles: true })); })()`, name: "playlist-filter-cleared", wait: 700 },
