// Round thirty-seven: dragged into place. The phone stood in for the // desktop's drag with Move up / Move down / Move to the top on a row's // sheet, which is the accessible path and stays — but a phone should drag // BETTER than a desktop does, not worse. So: one gesture module for both // lists, press and hold a row and it lifts, the rows below make room, let // go and it lands. The hard parts are all about the finger being the thing // that scrolls as well: a 320ms hold that does not fight the page's own // scroll (touch-action cannot be flipped mid-gesture, so a non-passive // touchmove refuses it instead), an edge pull measured in the scroller's // CONTENT space, a drop that maps to an index the mutations already // understand — moveItem and moveInQueue each grew a numeric target rather // than a second path to the backend — and a drag refused outright while // the filter is on, because a filtered list's visible order is not the // stored order. Release without moving and the row's sheet opens exactly // as it always did, which is what keeps the queue's sheet reachable at // all. No Rust, no Kotlin, no new command and no new mock command. const fs = require('fs'); const root = process.env.SS_ROOT || 'F:/Coding/SoulStream/'; const scratch = 'C:/Users/Kyle/AppData/Local/Temp/claude/F--Coding-SoulStream/5488514a-11e8-482a-b0a0-20cb511ee110/scratchpad/'; const ins = (name) => fs.readFileSync(scratch + name, 'utf8').replace(/\s+$/, ''); let p, s; const load = (file) => { p = file; s = fs.readFileSync(root + p, 'utf8'); }; const save = () => fs.writeFileSync(root + p, s); const once = (a) => { const i = s.indexOf(a); if (i < 0) throw new Error('missing in ' + p + ': ' + a.slice(0, 90)); if (s.indexOf(a, i + 1) >= 0) throw new Error('ambiguous in ' + p + ': ' + a.slice(0, 90)); return i; }; const rep = (a, b) => { once(a); s = s.replace(a, () => b); }; const NL = String.fromCharCode(10); /* The shell.css block is an APPEND with no anchor, so a second run would write it twice where `once` cannot see it coming and would only throw further down, leaving half a tree. Refuse one here, before anything is touched: the gesture module is the file this round creates and nothing else in the repo makes it. */ if (fs.existsSync(root + 'src/mobile/reorder.js')) { throw new Error('src/mobile/reorder.js already exists — round 37 has already run against ' + root); } // ── the gesture, once, for both lists ───────────────────────────────── fs.writeFileSync(root + 'src/mobile/reorder.js', ins('ins-r37-reorder.txt') + NL); // ── the mutations widen; they are not replaced ──────────────────────── load('src/mobile/playlists.svelte.js'); rep(` /** the last removal: { playlistId, rows: [{ item, position }], label } */`, ` /** true while a row is in hand on the playlist page: the page's own * re-read waits for the drop rather than replacing the rows under it */ dragging: false, /** the last removal: { playlistId, rows: [{ item, position }], label } */`); rep(`/* ── reorder: a row up, down or to the top; the complete id order goes in one call, as the desktop's drop does ── */`, `/* ── reorder: a row up, down, to the top, or to whatever index a drag dropped it at; the complete id order goes in one call either way, as the desktop's drop does ── */`); rep(` const to = where === "top" ? 0 : where === "up" ? from - 1 : from + 1;`, ` const to = typeof where === "number" ? Math.max(0, Math.min(ids.length - 1, where)) : where === "top" ? 0 : where === "up" ? from - 1 : from + 1;`); save(); load('src/mobile/player.svelte.js'); rep(`/** An upcoming row one place up or down, never past the current track. */`, `/** An upcoming row one place up or down — or to an absolute index, which * is what a drop on the queue face hands it — never past the current * track. The guards were already the right guards for either. */`); rep(` const j = i + (dir === "up" ? -1 : 1);`, ` const j = typeof dir === "number" ? dir : i + (dir === "up" ? -1 : 1);`); save(); // ── the playlist page ───────────────────────────────────────────────── load('src/mobile/screens/Playlist.svelte'); /* a sibling import rather than a rewritten one: the util.js line is the exact class of line two rounds of an earlier wave collided on */ rep(` import { mmss, longDur, stamp } from "../util.js";`, ` import { mmss, longDur, stamp } from "../util.js"; import { reorder } from "../reorder.js";`); rep(`rowClick } from "../playlists.svelte.js";`, `rowClick, moveItem } from "../playlists.svelte.js";`); rep(` function tap(item) { if (!rowClick(pl, items, item)) sheet(item); }`, ` function tap(item) { if (!rowClick(pl, items, item)) sheet(item); }` + NL + ins('ins-r37-pl-glue.txt')); rep(` void again;`, ` void again; /* a refetch that lands mid-drag would yank the rows out from under the gesture; the desktop defers one the same way (skippedWhileDrag). Reading the flag is what makes this effect re-run by itself the moment the drag has finished. */ if (pls.dragging) return;`); rep(` {#if q}{/if} {/if}`, ` {#if q}{/if} {#if q && items.length > 1}