111 of 122 top-level src/utils/ files move into 16 topic folders (audio,
cache, cover, share, server, playback, playlist, deviceSync, waveform,
mix, format, export, changelog, ui, perf, componentHelpers). True
singletons with no cluster stay at the utils/ root.
Pure file-move: a path-aware codemod rewrote 539 relative-import
specifiers across 275 files; no logic touched. The hot-path coverage
gate list (.github/frontend-hot-path-files.txt) is updated to the new
paths for the 11 gated utils files — a mechanical consequence of the
move, not a CI change. tsc is green.
Three-cut cluster pulling the last cluster of useEffect bodies + the
DnD-reorder visual feedback out of PlaylistDetail.tsx. 452 → 423 LOC
(−29).
usePlaylistRouteEffects — bundles two route-driven effects:
contextMenu reset (clears contextMenuSongId whenever playerStore's
context menu closes) and openEditMeta-from-route (consumes the
`openEditMeta` location.state flag, opens the meta modal, and
clears the flag with a navigate-replace so back-nav doesn't
re-trigger). Subscribes to playerStore directly.
useBulkPlPickerOutsideClick — the global mousedown listener that
closes the bulk-add-to-playlist picker when clicking outside the
picker wrapper. No-op when closed.
usePlaylistDnDReorder — owns dropTargetIdx state, the
container.addEventListener('psy-drop', …) wiring that calls
runPlaylistReorderDrop, and the handleRowMouseEnter
drag-over-visual helper. Subscribes to DragDropContext directly.
PlaylistDetail drops the direct runPlaylistReorderDrop import and
the contextMenuOpen store subscription — both now live in the hooks
that need them. Pure code move otherwise.