Skip to content

refactor(editor): move the Strings (tuning) editor to src/strings.js (R2, step 37) - #189

Merged
byrongamatos merged 1 commit into
mainfrom
refactor/r2-step37-strings
Jul 10, 2026
Merged

refactor(editor): move the Strings (tuning) editor to src/strings.js (R2, step 37)#189
byrongamatos merged 1 commit into
mainfrom
refactor/r2-step37-strings

Conversation

@byrongamatos

@byrongamatos byrongamatos commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

What

Step 37 of the R2 module-extraction refactor: move the Strings (tuning) editor out of src/main.js into a new native ES module src/strings.js.

Add/remove strings on the active fretted arrangement, and edit per-string tuning offsets — all undoable.

How the seam is drawn

Function-level cut — the parts-view and the draw() reassignment that share the "Strings" banner stay in main.js:

  • src/strings.js: 5 window.editor* handlers (re-attached by main.js) + the @pure:string-tuning block (with SetStringTuningCmd) kept export-free for the test slice. draw/updateStatus route through host; no new hooks, 0 back-exports.
  • Removed 2 dead main.js imports (AddStringCmd, RemoveStringCmd).
  • tests/strings_modal.test.mjs reworked: the handlers are export const now (sliced with the export prefix stripped) and reach draw/updateStatus through an injected host stub.

main.js drops ~267 lines. 38 modules.

Verification

  • 90/90 JS suites pass; ESLint gate clean (0 errors); strict no-undef clean on strings.js.
  • Codex preflight: clean.
  • New headless harness (verify_strings.py) drives the modal: 5 handlers bound, editorShowStringsModal renders the string rows, editorAddString('low') adds one (row count +1, proving the undoable command + re-render), editorRemoveString('low') removes it, editorHideStringsModal closes — zero page errors. Negative-checked by dropping a re-attach.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a dedicated Strings tuning editor for adjusting active strings and per-string tuning.
    • Added controls to add or remove strings, with safeguards for unsupported configurations and strings containing notes.
    • Added undoable tuning and string-count changes with refreshed editor status and visuals.
  • Bug Fixes

    • Improved validation and input handling for tuning values and string operations.

…(R2, step 38)

Extract the strings-tuning editor (add/remove strings on the active fretted
arrangement, edit per-string tuning offsets, all undoable) out of the src/main.js
monolith into a new native ES module. Function-level cut: the parts-view and the
draw() reassignment that share the "Strings" banner stay in main.js.

- src/strings.js: 5 window.editor* handlers (re-attached by main.js) + the
  @pure:string-tuning block (with SetStringTuningCmd) kept export-free for the
  test slice. draw/updateStatus route through host; no new hooks, 0 back-exports.
- Removed 2 dead main.js imports (AddStringCmd, RemoveStringCmd).
- tests/strings_modal.test.mjs reworked: handlers are export const now (sliced
  with the export prefix stripped) and reach draw/updateStatus through an injected
  host stub.

main.js drops ~267 lines. 38 modules.

Verified: 90/90 JS suites pass, ESLint gate clean (0 errors), strict no-undef
clean on strings.js, Codex preflight clean. New headless harness drives the modal:
5 handlers bound, editorShowStringsModal renders the string rows, editorAddString
adds one (row count +1, proving the undoable command + re-render), editorRemoveString
removes it, editorHideStringsModal closes — zero page errors. Negative-checked.

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

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The Strings tuning editor moves from main.js into strings.js, exposing the same window handlers. Tests update their extraction harness and call the migrated handlers directly.

Changes

Strings editor extraction

Layer / File(s) Summary
Strings editor module implementation
src/strings.js
Adds standalone string-range helpers, tuning normalization, modal rendering, undoable tuning edits, and exported string editor handlers.
Main-module handler wiring
src/main.js
Imports the strings handlers, removes obsolete command imports, and preserves the existing window API assignments.
Handler extraction and regression updates
tests/strings_modal.test.mjs
Updates source extraction, handler evaluation, rendering stubs, and regression calls for the new module exports.

Estimated code review effort: 4 (Complex) | ~45 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 accurately summarizes the main change: extracting the Strings editor into src/strings.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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/r2-step37-strings

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: 2

🤖 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 `@src/strings.js`:
- Around line 99-106: Update _notesOnString() to also inspect
arr.chord_templates and count notes within each template whose string matches
idx, alongside standalone notes and chord notes; ensure RemoveStringCmd’s checks
use this count so removal is blocked when a template-only fingering would be
discarded.
- Around line 71-89: SetStringTuningCmd must restore the original tuning array
shape during rollback, not just the prior value. Capture the original tuning
array existence and length in the constructor, then update rollback to restore
that length or remove the tuning property when it was originally absent; keep
exec behavior unchanged. Use _set and rollback as the implementation points.
🪄 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: 826ca259-007e-4805-a99f-671c4e14dadb

📥 Commits

Reviewing files that changed from the base of the PR and between 6ad9b63 and 4a18f1e.

📒 Files selected for processing (3)
  • src/main.js
  • src/strings.js
  • tests/strings_modal.test.mjs

Comment thread src/strings.js
Comment on lines +71 to +89
class SetStringTuningCmd {
constructor(arrIdx, stringIdx, newOffset) {
this.arrIdx = arrIdx;
this.stringIdx = stringIdx;
this.newOffset = _stringTuningClampPure(newOffset);
const arr = S.arrangements[arrIdx];
const t = (arr && arr.tuning) || [];
this.oldOffset = Number.isFinite(Number(t[stringIdx])) ? Number(t[stringIdx]) : 0;
}
_arr() { return S.arrangements[this.arrIdx]; }
_set(v) {
const arr = this._arr();
if (!arr) return;
if (!Array.isArray(arr.tuning)) arr.tuning = [];
while (arr.tuning.length <= this.stringIdx) arr.tuning.push(0);
arr.tuning[this.stringIdx] = v;
}
exec() { this._set(this.newOffset); }
rollback() { this._set(this.oldOffset); }

@coderabbitai coderabbitai Bot Jul 10, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Restore the original tuning shape on undo.

When editing a missing tuning slot, _set() extends the array, but rollback only writes oldOffset; undo therefore leaves extra zero entries and changes serialized state.

Proposed fix
     constructor(arrIdx, stringIdx, newOffset) {
         this.arrIdx = arrIdx;
         this.stringIdx = stringIdx;
         this.newOffset = _stringTuningClampPure(newOffset);
         const arr = S.arrangements[arrIdx];
-        const t = (arr && arr.tuning) || [];
+        const t = Array.isArray(arr?.tuning) ? arr.tuning : [];
+        this.oldLength = t.length;
+        this.hadOldOffset = stringIdx < t.length;
+        this.oldRawOffset = t[stringIdx];
         this.oldOffset = Number.isFinite(Number(t[stringIdx])) ? Number(t[stringIdx]) : 0;
     }
@@
-    rollback() { this._set(this.oldOffset); }
+    rollback() {
+        const arr = this._arr();
+        if (!arr) return;
+        if (!Array.isArray(arr.tuning)) arr.tuning = [];
+        if (this.hadOldOffset) arr.tuning[this.stringIdx] = this.oldRawOffset;
+        arr.tuning.length = this.oldLength;
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class SetStringTuningCmd {
constructor(arrIdx, stringIdx, newOffset) {
this.arrIdx = arrIdx;
this.stringIdx = stringIdx;
this.newOffset = _stringTuningClampPure(newOffset);
const arr = S.arrangements[arrIdx];
const t = (arr && arr.tuning) || [];
this.oldOffset = Number.isFinite(Number(t[stringIdx])) ? Number(t[stringIdx]) : 0;
}
_arr() { return S.arrangements[this.arrIdx]; }
_set(v) {
const arr = this._arr();
if (!arr) return;
if (!Array.isArray(arr.tuning)) arr.tuning = [];
while (arr.tuning.length <= this.stringIdx) arr.tuning.push(0);
arr.tuning[this.stringIdx] = v;
}
exec() { this._set(this.newOffset); }
rollback() { this._set(this.oldOffset); }
class SetStringTuningCmd {
constructor(arrIdx, stringIdx, newOffset) {
this.arrIdx = arrIdx;
this.stringIdx = stringIdx;
this.newOffset = _stringTuningClampPure(newOffset);
const arr = S.arrangements[arrIdx];
const t = Array.isArray(arr?.tuning) ? arr.tuning : [];
this.oldLength = t.length;
this.hadOldOffset = stringIdx < t.length;
this.oldRawOffset = t[stringIdx];
this.oldOffset = Number.isFinite(Number(t[stringIdx])) ? Number(t[stringIdx]) : 0;
}
_arr() { return S.arrangements[this.arrIdx]; }
_set(v) {
const arr = this._arr();
if (!arr) return;
if (!Array.isArray(arr.tuning)) arr.tuning = [];
while (arr.tuning.length <= this.stringIdx) arr.tuning.push(0);
arr.tuning[this.stringIdx] = v;
}
exec() { this._set(this.newOffset); }
rollback() {
const arr = this._arr();
if (!arr) return;
if (!Array.isArray(arr.tuning)) arr.tuning = [];
if (this.hadOldOffset) arr.tuning[this.stringIdx] = this.oldRawOffset;
arr.tuning.length = this.oldLength;
}
🤖 Prompt for 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.

In `@src/strings.js` around lines 71 - 89, SetStringTuningCmd must restore the
original tuning array shape during rollback, not just the prior value. Capture
the original tuning array existence and length in the constructor, then update
rollback to restore that length or remove the tuning property when it was
originally absent; keep exec behavior unchanged. Use _set and rollback as the
implementation points.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Valid, but pre-existing — this is a verbatim move (16/16 tests confirm identical behavior), and it only triggers when editing a tuning slot past the array end, which does not happen for a normal full-length tuning. Deferring to a strings-editor hardening follow-up rather than changing command semantics on a move PR.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@byrongamatos, that's a reasonable call — deferring behavioral/edge-case hardening out of a pure move PR keeps the diff clean and the "16/16 identical behavior" verification is convincing. Agreed this shouldn't block the move.

Would you like me to open a follow-up issue to track this for a future strings-editor hardening pass? If so, just confirm and I'll file it referencing this PR and comment.


✏️ Learnings added
Learnt from: byrongamatos
Repo: got-feedBack/feedBack-plugin-editor PR: 189
File: src/strings.js:71-89
Timestamp: 2026-07-10T09:10:22.127Z
Learning: In `byrongamatos/got-feedBack/feedBack-plugin-editor`, PRs that move code verbatim between files (module extraction refactors) are treated as move-only PRs — pre-existing bugs surfaced in the moved code (e.g., `SetStringTuningCmd.rollback()` in `src/strings.js` not restoring the original tuning array length/shape when editing a tuning slot past the array end) should be deferred to a separate hardening follow-up rather than fixed inline, provided behavior-preservation tests confirm the move is verbatim.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment thread src/strings.js
Comment on lines +99 to +106
function _notesOnString(arr, idx) {
let count = 0;
for (const n of arr.notes || []) if (n.string === idx) count += 1;
for (const ch of arr.chords || []) {
for (const cn of ch.notes || []) if (cn.string === idx) count += 1;
}
return count;
}

@coderabbitai coderabbitai Bot Jul 10, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Block removal when a chord template uses the target string.

_notesOnString() ignores chord_templates, although RemoveStringCmd deletes their fret/finger column. A template-only fingering can therefore be silently discarded.

Proposed fix
-function _notesOnString(arr, idx) {
+function _contentOnString(arr, idx) {
     let count = 0;
     for (const n of arr.notes || []) if (n.string === idx) count += 1;
     for (const ch of arr.chords || []) {
         for (const cn of ch.notes || []) if (cn.string === idx) count += 1;
     }
+    for (const ct of arr.chord_templates || []) {
+        if (Array.isArray(ct.frets)
+            && idx < ct.frets.length
+            && ct.frets[idx] !== -1) count += 1;
+    }
     return count;
 }
@@
-    const blockers = removableIdx >= 0 ? _notesOnString(arr, removableIdx) : 0;
+    const blockers = removableIdx >= 0 ? _contentOnString(arr, removableIdx) : 0;
@@
-            warn.textContent = `${blockers} note${blockers === 1 ? '' : 's'} on the ${removePos} string — delete or move them before removing.`;
+            warn.textContent = `${blockers} chart item${blockers === 1 ? '' : 's'} on the ${removePos} string — delete or move them before removing.`;
@@
-    if (_notesOnString(arr, targetIdx) > 0) return;
+    if (_contentOnString(arr, targetIdx) > 0) return;

Also applies to: 185-200, 251-252

🤖 Prompt for 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.

In `@src/strings.js` around lines 99 - 106, Update _notesOnString() to also
inspect arr.chord_templates and count notes within each template whose string
matches idx, alongside standalone notes and chord notes; ensure
RemoveStringCmd’s checks use this count so removal is blocked when a
template-only fingering would be discarded.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Valid pre-existing issue (unchanged by this move). Fixing it right means deciding removal semantics when a chord template references the string + adding test coverage — that deserves its own PR, not a rider on a verbatim move. Flagging as a follow-up.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@byrongamatos, that's a reasonable call — scoping this as a verbatim move and deferring the removal-semantics decision (plus test coverage) to a dedicated PR makes sense rather than bundling it here.

Would you like me to open a follow-up GitHub issue to track this (referencing this PR and comment)? Just let me know and I'll file it.

@byrongamatos
byrongamatos merged commit b31737c into main Jul 10, 2026
4 checks passed
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.

1 participant