Skip to content

refactor(editor): move the song-creation flow to src/create.js (R2, step 22) - #173

Merged
byrongamatos merged 3 commits into
mainfrom
refactor/r2-step22-create
Jul 9, 2026
Merged

refactor(editor): move the song-creation flow to src/create.js (R2, step 22)#173
byrongamatos merged 3 commits into
mainfrom
refactor/r2-step22-create

Conversation

@byrongamatos

@byrongamatos byrongamatos commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Twenty-third module. src/main.js 12,738 → 10,380 — now 51% of the 21,176 lines this refactor started from.

2,380 lines: the format picker, the sloppak-create modal, the roster, the MusicBrainz match, the album-art picker, and the createState object every step reads. They travel together because they are one flow over one object.

Found a real bug — and deliberately did not fix it here

main.js contained two async function _editorDoBlankCreate definitions.

Inside the file’s IIFE that is legal: function declarations hoist, and the last one in source order silently wins. So the version added by the Create-New redesign (#45, Jul 5) never ran. The one that has actually been executing came from “restore audio-only project creation” (3f66bec, Jul 4) — and the two differ in behaviour:

running (kept) dead (removed)
artist required not checked
audio required optional — draft now, audio later
roster not validated must contain an instrument

The collision surfaced only because create.js is a module, where a duplicate declaration is a SyntaxError rather than a silent overwrite. It has been sitting in main.js since 5 July.

This PR preserves runtime behaviour exactly: the dead definition is deleted, not promoted. Restoring the redesign’s intent is a product decision — audio optional, roster validated — and belongs in its own PR. A comment in create.js records the whole story at the call site.

@ChrisBeWithYou, this is your #45 work that never took effect.

Structure

main.js keeps the load/audio pipeline (loadCDLC, loadAudio), the library rescan and the transport readouts — six new host hooks — plus the entry landing, which is screen-entry UI that merely opens two of these dialogs.

The 22 window.editor* handlers the HTML calls are exported as plain functions and re-attached by main.js. The module’s one import-time side effect — a global input listener — became an exported initCreate() called from init(). A module must do no work when it is loaded, or its tests cannot import it without a DOM.

Why a headless harness, again

verify_create.py opens the modal and types a title. That single keystroke exercises all three things that can only fail in a browser, because nothing else is listening:

check wired initCreate() commented out
every window.editor* handler is bound PASS PASS
the modal opens PASS PASS
Create starts disabled PASS PASS
typing a title enables Create PASS FAIL
clearing it disables again PASS PASS

Comment out initCreate() and all 89 unit tests still pass while the Create button never un-greys.

Verification

node --test 89/89 · pytest 248/248 · npm run lint 0 errors (6 warnings) · Codex clean — it independently confirmed which _editorDoBlankCreate was the runtime binding, that the arrow→function conversions rely on no this/arguments, and that createState’s live binding tracks reassignment while main.js legally mutates .audioUrl · all 15 headless harnesses pass.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Fixed an issue where duplicate song-creation logic could cause the wrong behavior to run, keeping the runtime behavior consistent.
  • Improvements
    • Enhanced the song-creation experience, including the format picker, create dialog, roster handling, matching, and album-art selection.
    • Improved how creation controls initialize to support more reliable app startup.
  • Chores
    • Updated the test harness to directly validate the creation gate logic.

…tep 22)

src/main.js 12,738 -> 10,380. Twenty-third module; the graph stays acyclic.
main.js is now 51% of the 21,176 lines this refactor started from.

2,380 lines: the format picker, the sloppak-create modal, the roster, the
MusicBrainz metadata match, the album-art picker, and the `createState` object
every step of the flow reads. They travel together because they ARE one flow
over one object.

main.js keeps the load/audio pipeline (loadCDLC, loadAudio), the library rescan
and the transport readouts — six new host hooks — plus the entry landing, which
is screen-entry UI that merely opens two of these dialogs.

The 22 window.editor* handlers the HTML calls are exported as plain functions
and re-attached by main.js. The module's one import-time side effect, a global
'input' listener, became an exported initCreate() called from init(): a module
must do no work when it is loaded, or its tests cannot import it without a DOM.

FOUND A REAL BUG, and did not fix it here.

main.js contained TWO `async function _editorDoBlankCreate` definitions. Inside
the IIFE that is legal: function declarations hoist, and the LAST one in source
order silently wins. So the version added by the Create-New redesign (#45, Jul 5)
never ran. The one that has been executing came from "restore audio-only project
creation" (3f66bec, Jul 4), and the two differ in behaviour:

  running (kept):  artist REQUIRED, audio REQUIRED, no roster validation
  dead (removed):  audio optional (draft-now, audio-later), roster validated

The collision only surfaced because create.js is a module, where a duplicate
declaration is a SyntaxError rather than a silent overwrite.

This PR preserves runtime behaviour exactly: the dead definition is deleted, NOT
promoted. Restoring the redesign's intent is a product decision and belongs in
its own PR. A comment in create.js records all of this at the call site.

Verified beyond the unit tests, which cannot see the wiring: verify_create.py
opens the modal and types a title. That one keystroke exercises all three things
that can only fail in a browser — the window.* re-attach, initCreate(), and
host.addGlobalListener — because nothing else is listening. Comment out
initCreate() and all 89 unit tests still pass while the Create button never
un-greys.

node --test 89/89, pytest 248/248, npm run lint 0 errors (6 warnings), Codex
clean, all 15 headless harnesses pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 9, 2026 22:58
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

💤 Files selected but had no reviewable changes (1)
  • src/create.js
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 26cd6377-1dd8-4431-9f79-2543d3fe4b30

📥 Commits

Reviewing files that changed from the base of the PR and between fc88f2a and f7c7eb5.

📒 Files selected for processing (1)
  • src/create.js

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The create-gate test now imports _createGateOpen from src/create.js, the changelog documents create-flow changes, and host exposes additional default pipeline callbacks.

Changes

Create flow updates

Layer / File(s) Summary
Create gate test migration
tests/create_gate.test.mjs, CHANGELOG.md
The test switches from dynamic CommonJS extraction to ESM imports from src/create.js; the changelog records the create-flow relocation and duplicate-definition fix.

Host callback registry

Layer / File(s) Summary
Load and audio hook defaults
src/host.js
The exported host object adds no-op callbacks for loading, audio, library rescanning, offset UI reset, time display updates, and global listener registration.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately summarizes the main refactor: moving the song-creation flow into src/create.js.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/r2-step22-create

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Around line 27-30: Correct the CHANGELOG entry to reflect that reducing
main.js from 12,738 to 10,380 lines leaves it at approximately 81.5% of its
original size, representing an approximately 18.5% reduction rather than 51%.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9cbb2783-725c-4602-95ec-90b52e8a0eb0

📥 Commits

Reviewing files that changed from the base of the PR and between 9859060 and c37e6c7.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • src/create.js
  • src/host.js
  • src/main.js
  • tests/create_gate.test.mjs

Comment thread CHANGELOG.md Outdated
CodeRabbit, on #173. The sentence said main.js is '51% of what it was', which
reads as 51% of the previous step's 12,738 and is wrong either way.

10,380 / 21,176 = 49% of the ORIGINAL, i.e. a 51% reduction from where this
refactor started. Against the previous step it is 81%. Both numbers are now
stated explicitly rather than left to the reader.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@byrongamatos

Copy link
Copy Markdown
Collaborator Author

The wording was wrong, though not by the arithmetic you did — and that ambiguity is exactly the problem, so it is fixed.

The 51% was measured against the 21,176 lines this refactor started at, not against the previous step:

  • 10,380 / 21,176 = 49% of the original, i.e. a 51% reduction
  • 10,380 / 12,738 = 81% of the previous step

Your 81.5% is the step-over-step figure, and the sentence as written ("51% of what it was") reads as though it meant that. Both numbers are now stated explicitly instead of leaving the baseline to the reader.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR continues the editor refactor by extracting the entire song-creation flow out of src/main.js into a new src/create.js module, while preserving the existing runtime behavior and keeping Node-importability for unit tests (no import-time DOM side effects).

Changes:

  • Moved the “New…” / create-modal flow (format picker, modal state, roster, MusicBrainz match, album art picker, build/import plumbing) into src/create.js, and re-attached the window.editor* handlers from main.js.
  • Expanded the host hook surface to include the load/audio pipeline and a teardown-tracked global listener registration, enabling create.js to call back into main.js without cycles.
  • Updated the create-gate unit test to directly import _createGateOpen from src/create.js (no longer brace-extracting from main.js).

Reviewed changes

Copilot reviewed 3 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/create_gate.test.mjs Switches to ESM and imports _createGateOpen directly from src/create.js.
src/main.js Imports create-flow exports, wires new host hooks, and re-attaches window.editor* handlers; calls initCreate() during init().
src/host.js Adds new host hook placeholders for create/load/audio pipeline and global listener registration.
src/create.js New module containing the extracted create flow and exported handlers/state.
CHANGELOG.md Documents the extraction and the historical duplicate _editorDoBlankCreate definition.
Comments suppressed due to low confidence (1)

tests/create_gate.test.mjs:7

  • The header comment says from-scratch draft create “needs only a title”, but _createGateOpen (and the tests below) require a title and at least one instrument in the roster (default roster contains Lead, which makes it feel title-only). This comment is likely to mislead future edits to the gate.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/create.js Outdated
Comment thread src/create.js
Comment thread src/create.js
…te UI

Copilot, on #173. Three findings, all pre-existing code this move surfaced, all
from the same commit.

- initCreate()'s comment claimed the input listener invalidates a cached upload
  URL. It does not, and never did here: that happens in the audio file/URL
  change handlers, which clear createState.audioUrl directly. Comment corrected.

- _populateCreateArrButtons and _populateStringCountButtons are UNREACHABLE. The
  first is called only by itself (a re-render on click); the second only by the
  first. Nothing outside the pair enters them, which is why lint has warned about
  the first for weeks.

- They read and write createState.initialArr. Nothing else does — the create
  payload sends createState.initialArrangement. Were this UI ever wired up, the
  arrangement the user picked would not reach the server.

Not renamed. Making dead code consistent would change the behaviour of code
nobody has decided to run, and the right field name is a product question. Both
functions arrived with the Create-New redesign (977ec65, #45) — the same commit
whose _editorDoBlankCreate never ran either, for an unrelated reason. That
redesign is half-wired. Recorded at the call site; left exactly as found.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@byrongamatos

Copy link
Copy Markdown
Collaborator Author

All three are real, all three are pre-existing, and they turn out to be the same finding.

_populateCreateArrButtons is called only by itself (a re-render on click). _populateStringCountButtons is called only by it. Nothing outside the pair ever enters them — which is why lint has been warning about the first one for weeks. So the initialArr / initialArrangement mismatch you spotted is real but currently unreachable: the create payload sends initialArrangement, and only this dead UI ever touches initialArr.

git log -S puts both functions in 977ec65 (#45, the Create-New redesign) — the same commit whose _editorDoBlankCreate never ran either, for an unrelated reason (a duplicate declaration that the IIFE silently swallowed). That redesign is half-wired.

I have not renamed the field. Making dead code self-consistent would change the behaviour of code nobody has decided to run, and which name is correct is a product question, not a refactor's. Both functions are left exactly as found, with the whole story recorded at the call site.

The initCreate() comment was simply wrong and is fixed: the cached-upload-URL invalidation it described happens in the audio file/URL change handlers, which clear createState.audioUrl directly. This listener only re-gates the Create button and dismisses the autofill note.

@byrongamatos
byrongamatos merged commit f9fbf78 into main Jul 9, 2026
4 checks passed
byrongamatos added a commit that referenced this pull request Jul 9, 2026
…nal (#174)

Typing a title enabled the Create button. Clicking it said "Artist is required."
Supplying an artist then said "Audio is required for an audio-only project",
which made the advertised draft-now-audio-later flow impossible. The roster
chips were ignored — every draft came out as a lone Lead arrangement — and the
eight extended-metadata fields the modal collects were never sent.

Root cause is the duplicate `_editorDoBlankCreate` found while extracting
src/create.js (#173): main.js defined it twice, function declarations hoist, and
the LAST one in source order silently won. So PR #45's redesign never ran, and
the older definition kept sending the server's documented BACK-COMPAT payload
(`initial_arrangement` + `init_drum_tab`) instead of the roster it asks for.

Everything else already agreed, which is what makes this unambiguous rather than
a product call:

  _createGateOpen   enables Create on a title + one instrument (and is unit-tested)
  screen.html       marks only Title "(required)"
  editorDoCreate    comments, right above the call: "only a title is required;
                    audio + artist are optional"
  create_sloppak    "title required"; "Artist is OPTIONAL for a draft"; "Draft-now,
                    audio-later: audio is OPTIONAL"; and it calls
                    initial_arrangement/init_drum_tab the "Legacy shape"

Only the handler disagreed. It now validates a title and a non-empty instrument
roster, treats artist and audio as optional, and sends `arrangements` plus the
spec-complete metadata via the same _createExtendedMeta() helper the Guitar Pro
and EOF paths already use.

Kept from the old function: the art-upload retry (art normally uploads on
selection; this covers a selection whose upload failed). Added: the roster
check re-enables the button on failure, which the redesign's version forgot.

Removed createState.initialArrangement and createState.initDrumTab — nothing
reads them now. The separate, older editorShowCreateSloppakModal dialog still
sends the legacy shape with its own drum checkbox, and the server still accepts
it; untouched.

Codex flagged one apparent regression: that the old payload defaulted
init_drum_tab to true, so a default create used to seed Drums as well. It reads
that way, but the line below the default overwrites it with a lookup for
`#editor-create-drum-tab` — an element the same redesign deleted from
screen.html. Verified against the live DOM: the element does not exist, the
expression is false, and the server appended nothing. The default roster was
['Lead'] before this change and is ['Lead'] after it.

verify_blank_create.py drives the real modal and reads the real POST body: a
title alone POSTs, no artist and no audio are sent or demanded, the roster chips
arrive as `arrangements`, no back-compat keys are sent, and the extended fields
travel. It fails on 6 of 8 checks against the pre-fix code. End to end the
server writes an audio-less draft (`stems: []`) with arrangements/lead.json,
arrangements/drums.json and drum_tab.json when Drums is chosen.

node --test 89/89, pytest 248/248, npm run lint 0 errors, all 16 headless
harnesses pass.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants