4 functions, 234 lines. app.js 4,452 -> 4,218. Bodies VERBATIM.
INTERFACE WIDTH ZERO — nothing in app.js calls into this cluster. app.js needs only the names
on the window contract, so the markup's onclick= handlers resolve. That is what makes it the
cleanest slice left.
AND IT ONLY BECAME CLEAN BECAUSE THE LIBRARY CAME OUT FIRST (#896). Every dependency the modal
has is a module now: it reads six bindings out of ./library.js (loadLibrary, loadFavorites,
loadTreeView, _removeLibCardsForFilename, libView, _lastLibSelected) plus dom.js and the L
container. Before that carve, extracting this would have dragged the whole library with it.
Checked, and it matters: the modal never WRITES any of those six. An imported binding is
READ-ONLY, so a single write would have forced a setter or a state container. Every use is a
read, so plain imports suffice.
Acyclic: edit-modal -> { dom, library-state, library }, and library imports none of them back.
VERIFIED. A/B against origin/main in two browsers: the window contract, the modal actually
OPENING off a real library row, its title and year fields rendering, and the data-edit-save
wiring (rather than an inline onclick embedding the filename — the fix this cluster's harness
exists to guard). IDENTICAL, no new page errors.
node 1045, pytest 2425, ESLint 0 (no-cycle clean), host contract 2/2, Codex 0.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(library): Edit Metadata modal — editable Year + no close on drag-release
Two fixes to the Songs -> Edit Metadata modal (openEditModal/saveEditModal in
static/app.js), both reported on macOS for 0.3.0.
1) Year is now editable. A year can be set when authoring a pak but the modal
had no Year field, so it could never be changed. The backend
(POST /api/song/<f>/meta) already accepts + normalizes `year` and writes it
into the file via songmeta (survives a rescan) -- only the UI omitted it.
Add a Year input (populated from the song's current year) and include
`year` in the save POST body. Both the v3 card menu and the legacy edit
button already pass the year through, so both surfaces get the field.
2) The modal no longer closes when a click-drag is released on the backdrop.
Selecting text inside a field and releasing the mouse past the modal edge
dismissed the form without warning (the `click` event's target resolves to
the backdrop, the common ancestor) -- discarding the edit. Backdrop
dismissal now also requires the mousedown to have STARTED on the backdrop,
tracked per-modal and decided by a new pure helper
_editModalShouldClose(clickTarget, modalEl, downOnBackdrop). Cancel / X
still close on a normal click.
Tests: tests/js/edit_metadata_modal.test.js extracts the real functions from
app.js and asserts (a) openEditModal renders #edit-year, (b) saveEditModal's
meta POST body carries `year`, and (c) the backdrop-close decision table
(Cancel always closes; backdrop needs down+up on the backdrop; a drag from a
field released on the backdrop does NOT close).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QbexxfTt8q2tAn436MqGWF
* fix(library): wire Edit Metadata Save via listener, not an inline onclick
encodeURIComponent does not escape "'", so embedding the filename in the
single-quoted inline onclick="saveEditModal('…')" handler produced a
malformed handler for any song whose filename contains an apostrophe
(e.g. Bob's Song.sloppak) — clicking Save threw a syntax error and the
edit silently failed. Replace the inline onclick with a data-edit-save
hook wired in JS from the closure filename (mirrors the existing Delete
button pattern), so the filename never has to survive attribute-string
embedding. Pre-existing bug surfaced during review of this modal.
Adds a regression assertion (no inline saveEditModal onclick; Save wired
via data-edit-save).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: byrongamatos <xasiklas@gmail.com>