Skip to content

feat(editor): suggest-position — author fretted parts in the piano roll (P6/VA.3) - #143

Merged
byrongamatos merged 2 commits into
mainfrom
feat/editor-suggest-position-p6
Jul 9, 2026
Merged

feat(editor): suggest-position — author fretted parts in the piano roll (P6/VA.3)#143
byrongamatos merged 2 commits into
mainfrom
feat/editor-suggest-position-p6

Conversation

@ChrisBeWithYou

@ChrisBeWithYou ChrisBeWithYou commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

What & why

A fretted part shown in the piano roll was read-only — adding a note there
would force a silent string/fret guess, so it was refused. This is the
suggest-position write path (design V4/VA.3): the machine enumerates the
playable positions and either writes the unambiguous one (marked suggested)
or asks. It never silently decides — a confidently-wrong tab is worse than an
honest gap.

How

  • Resolver (@pure:suggest-position): _suggestPositionPure resolves a
    sounding pitch → {string, fret} — anchor-window first, then nearest
    previous hand, then lowest fret; refuses out-of-range / string-occupied /
    outside-anchor-window / open-vs-fretted.
  • Add: double-click / EOF right-click in the fretted roll → _rollAddByPitch.
    Unambiguous → _commitAddResolved writes a note marked suggested (dimmed +
    dashed); ambiguous → _rollConfirmPosition popover lists the free candidates
    and writes a confirmed note. New notes land at the grid length
    (_defaultAddSustain), not sustain:0.
  • Move: dragging a fretted note vertically re-pitches it through the
    resolver (_rollDragPitchMove), keeping the hand where it was and holding
    on a refusal or a technique-locked fret (slide/bend/harmonic). Commits a
    suggested-marked MoveNoteCmd; a re-pitched-nothing drag explains why.
  • Marks are a module WeakSet<note>, never a note field — a field leaks
    on solo notes (serialized by reference) and vanishes on chord members
    (rebuilt via an explicit field mapper). The suggestResolved commands are the
    sanctioned carve-out that passes the read-only-roll edit lock; a deliberate
    position move (MoveToStringCmd) or Accept confirms (drops the mark; undo
    re-marks). A status nudge shows how many positions are still unconfirmed.

Tests

suggest_position.test.js (17, resolver) + suggest_position_wiring.test.js
(9, the write path + wire purity through the real reconstructChords) +
suggest_position_move.test.js (7, drag re-pitch). All fail on main. Full JS
suite green.

⚠ Stacked PR

Branches off #142 (fix/editor-group-move-snap) — the vertical re-pitch drag
reuses that PR's rigid group time-delta (_groupTimeDeltaPure + primaryOrigTime),
so this is a real dependency. Base is set to that branch; GitHub retargets to
main when #142 merges. Review #142 first.

Summary by CodeRabbit

  • New Features

    • Added “suggest-position” placement for fretted-roll notes: click/double-click/right-click inserts by resolving the sounding pitch, offers a confirmation popover for ambiguous choices, and includes an Accept position action.
    • Suggested notes have distinct visual styling and a positions unresolved status indicator.
    • “Suggest-position” marks now persist across save/reload (scoped to the current arrangement) until accepted.
  • Bug Fixes

    • Fixed vertical re-pitching during chord edits to avoid string double-booking.
    • Improved locking/read-only behavior so accepted suggestions apply correctly without blocking other roll edits.

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a8a1dd98-f49b-473d-946e-8d8690e5da17

📥 Commits

Reviewing files that changed from the base of the PR and between 1723383 and eb91f5d.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • screen.js
  • tests/suggest_position.test.js
  • tests/suggest_position_move.test.js
  • tests/suggest_position_persist.test.js
  • tests/suggest_position_wiring.test.js

📝 Walkthrough

Walkthrough

This PR adds suggest-position editing for fretted parts in the piano roll, including resolver-backed add and drag flows, provisional suggested-position marking, persistence across save/reload and arrangement changes, confirm/accept actions, and a chord re-pitch fix that avoids double-booking strings.

Changes

Suggest-position feature

Layer / File(s) Summary
Suggested-note rendering
screen.js
Adds suggested-note detection and changes note rendering in the piano-roll and fretted-roll views to dim and dash provisional positions.
Lock carve-outs and move tracking
screen.js
Extends read-only gating for resolver-backed writes and adds suggested-mark snapshot, exec, rollback, and re-mark handling in move commands.
Suggest-position resolver
screen.js
Adds candidate enumeration, anchor-window and occupancy checks, refusal reasons, unambiguous position selection, and the resolver-backed add flow with persistence helpers and accept support.
Fretted-roll entry-point wiring
screen.js
Routes fretted-roll add and right-click interactions through the resolver, keeps delete locked, and adds the context-menu accept-position action.
Suggested-mark save and restore
screen.js
Reattaches suggested marks after reconstruction, persists and restores them during save flows, migrates save-as keys, updates the unresolved-position status count, and flushes/restores marks on arrangement switches.
Resolver unit tests
tests/suggest_position.test.js
Adds tests for position enumeration, resolution tiers, refusal cases, edge behavior, and anchor selection.
Drag and move tests
tests/suggest_position_move.test.js
Adds tests for lock detection, drag repick behavior, occupancy handling, sequential chord re-pitching, and suggested-mark round-tripping.
Persistence tests
tests/suggest_position_persist.test.js
Adds tests for serialization, arrangement scoping, defensive restore parsing, reload survival, re-attach matching, and no-resurrection after clear.
Wiring and wire-purity tests
tests/suggest_position_wiring.test.js
Adds end-to-end tests for add, confirm, undo, redo, accept, and reconstruction paths while checking that suggested state does not leak into saved note fields.
Changelog entries
CHANGELOG.md
Updates the unreleased changelog with the sequential re-pitch fix, suggested-mark persistence fix, and the new suggest-position feature entry.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant FrettedRollUI
  participant SuggestPositionResolver
  participant History
  participant SuggestedMarkStore

  User->>FrettedRollUI: click or drag pitch
  FrettedRollUI->>SuggestPositionResolver: resolve SOUNDING pitch to {string,fret}
  SuggestPositionResolver->>SuggestPositionResolver: enumerate candidates and apply constraints
  alt unambiguous
    SuggestPositionResolver->>History: execute resolver-backed add/move
    History->>SuggestedMarkStore: mark note suggested
  else ambiguous
    SuggestPositionResolver->>FrettedRollUI: open confirmation popover
    User->>FrettedRollUI: pick candidate
    FrettedRollUI->>History: execute confirmed add/move
  end
  User->>FrettedRollUI: accept position
  FrettedRollUI->>History: execute AcceptPositionsCmd
  History->>SuggestedMarkStore: clear suggested mark
  User->>FrettedRollUI: save or switch arrangement
  FrettedRollUI->>SuggestedMarkStore: save and restore suggested marks
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately summarizes the main change: adding suggest-position support for fretted parts in the piano roll.
✨ 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 feat/editor-suggest-position-p6

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

@byrongamatos

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@byrongamatos
byrongamatos changed the base branch from fix/editor-group-move-snap to main July 9, 2026 07:46
ChrisBeWithYou and others added 2 commits July 9, 2026 09:47
…ll (P6/VA.3)

A fretted part shown in the piano roll was read-only: adding a note would
force a silent string/fret guess, so it was refused. This is the
suggest-position write path (design V4/VA.3) — the machine enumerates the
playable positions and either writes the unambiguous one (marked suggested)
or asks; it never silently decides ("a confidently-wrong tab is worse than
an honest gap").

- @pure:suggest-position: _suggestPositionPure resolves a sounding pitch to a
  {string, fret} — anchor-window first, then nearest previous hand, then
  lowest fret; refuses out-of-range / string-occupied / outside-anchor-window
  / open-vs-fretted.
- Add path: double-click / EOF right-click in the fretted roll ->
  _rollAddByPitch. Unambiguous -> _commitAddResolved writes a note marked
  suggested (dimmed + dashed); ambiguous -> _rollConfirmPosition popover lists
  the free candidates and writes a CONFIRMED note. New notes land at the grid
  length (_defaultAddSustain), not sustain:0.
- Move path: dragging a fretted note vertically re-pitches it through the
  resolver (_rollDragPitchMove), keeping the hand where it was and HOLDING on
  a refusal or a technique-locked fret (slide/bend/harmonic). Commits a
  suggested-marked MoveNoteCmd; a repitched-nothing drag explains why.
- Marks are a module WeakSet<note>, NOT a note field: a field LEAKS on solo
  notes (serialized by reference) and VANISHES on chord members (rebuilt by an
  explicit field mapper). The suggestResolved commands are the sanctioned
  carve-out that passes the read-only-roll edit lock; a deliberate position
  move (MoveToStringCmd) or Accept CONFIRMS (drops the mark; undo re-marks).

Tests: tests/suggest_position.test.js (17, resolver) +
suggest_position_wiring.test.js (9, the write path + wire purity through the
real reconstructChords) + suggest_position_move.test.js (7, drag re-pitch).
All fail on main. Full JS suite green.

Stacked on the group-move PR (fix/editor-group-move-snap): the vertical
re-pitch drag reuses the rigid group time-delta, so this branches off it.
Rebase onto main once it merges.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013JgxKh99UAeQqmhzSc73tv
Signed-off-by: ChrisBeWithYou <chris@rifflarr.local>
…marks (review)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@byrongamatos
byrongamatos force-pushed the feat/editor-suggest-position-p6 branch from 1723383 to eb91f5d Compare July 9, 2026 07:48
@byrongamatos
byrongamatos merged commit af2288b into main Jul 9, 2026
2 of 3 checks passed
@byrongamatos
byrongamatos deleted the feat/editor-suggest-position-p6 branch July 9, 2026 07:48

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
screen.js (1)

4954-4992: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Refusal-hint status message is immediately overwritten by the trailing updateStatus() call.

setStatus('Couldn’t repitch here…') (Line 4960) runs, but onMouseUp unconditionally falls through to draw(); updateStatus(); (Lines 4990-4992), and updateStatus() itself unconditionally ends with setStatus('Ready') (Line 9266) — clobbering the hint before the user ever sees it. Compare with _commitAddResolved and _execAcceptPositions, which both correctly call updateStatus() before their final meaningful setStatus(...) — that's the established working pattern here, and this one call site has it backwards.

🐛 Proposed fix: defer the hint until after the trailing updateStatus()
         if (rollFretted && (!cmd.markSuggestedIdx || !cmd.markSuggestedIdx.length)
             && Math.abs(y - S.drag.startY) >= PIANO_LANE_H) {
-            setStatus('Couldn’t repitch here — hand position or a locked technique blocks it. Try String view or add an anchor.');
+            S._pendingRollHint = 'Couldn’t repitch here — hand position or a locked technique blocks it. Try String view or add an anchor.';
         }
     }
 
     if (S.drag.type === 'select') {
         ...
     }
 
     S.drag = null;
     draw();
     updateStatus();
+    if (S._pendingRollHint) { setStatus(S._pendingRollHint); S._pendingRollHint = null; }
 }
🤖 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 `@screen.js` around lines 4954 - 4992, The refusal-hint in onMouseUp is being
overwritten by the unconditional updateStatus() path. Move the meaningful
setStatus('Couldn’t repitch here…') so it runs after the final
draw()/updateStatus() sequence, or otherwise prevent updateStatus() from
resetting it here; use the existing _commitAddResolved and _execAcceptPositions
flow as the pattern for preserving the last status message.
🧹 Nitpick comments (2)
tests/suggest_position_persist.test.js (1)

89-193: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the remaining persistence edge cases

  • Cover Save-As migration of editorSuggested:* keys.
  • Add a duplicate-identity case so _applySuggestedMarksPure’s greedy 1:1 claim is exercised.
🤖 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 `@tests/suggest_position_persist.test.js` around lines 89 - 193, The
suggested-mark persistence tests are missing coverage for Save-As key migration
and duplicate-identity matching behavior. Extend the existing persistence test
suite around makeEnv, _saveSuggestedMarks, _restoreSuggestedMarks, and
_applySuggestedMarksPure to verify that editorSuggested:* data is moved to the
new song key on Save-As without leaving stale source keys behind, and add a
duplicate-note scenario with identical string/fret/time proximity so the greedy
1:1 re-attachment logic only marks one matching note.
tests/suggest_position_move.test.js (1)

90-206: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Consider adding a case for a mixed locked+movable chord drag.

Good coverage of position-locking, holds, multi-note occupancy exclusion, and the sequential same-string chord fix — but none of these mix a technique-locked note with a movable sibling in the same drag that resolves onto the locked note's string. That's exactly the scenario flagged in screen.js (Lines 3816-3862) where a held note never joins claimed, so a sibling could silently double-book its string. Worth a test like the "SAME string lands DISTINCT" one (Lines 160-186) but with one member carrying slide_to/bend/harmonic instead of both being freely resolvable.

Happy to draft this test if useful.

🤖 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 `@tests/suggest_position_move.test.js` around lines 90 - 206, Add a test in
suggest_position_move.test.js for a mixed chord drag where one note is
position-locked and a sibling is movable, and the movable note resolves onto the
locked note’s string. Use the existing helpers like makeMoveEnv, startDrag, and
_rollDragPitchMove to verify the locked note stays on its original string/fret
while the sibling is repicked to a different string instead of silently
double-booking; reference the same occupancy behavior covered by the sequential
chord test and the _positionLocked/_rollDragPitchMove paths.
🤖 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 `@screen.js`:
- Around line 3776-3862: The drag resolver in _rollDragPitchMove still lets
mixed chords reuse strings because held notes are excluded from the drag-local
occupancy and _occupiedStringsAt only checks a single time point. Seed the local
`claimed`/occupancy set with technique-locked or zero-pitch movers too, so free
siblings cannot repick those strings, and extend the non-moving occupancy check
to reject any note whose full time span overlaps the moving note’s span, not
just notes containing the mover’s onset. Use _rollDragPitchMove and
_occupiedStringsAt as the main touchpoints.

---

Outside diff comments:
In `@screen.js`:
- Around line 4954-4992: The refusal-hint in onMouseUp is being overwritten by
the unconditional updateStatus() path. Move the meaningful setStatus('Couldn’t
repitch here…') so it runs after the final draw()/updateStatus() sequence, or
otherwise prevent updateStatus() from resetting it here; use the existing
_commitAddResolved and _execAcceptPositions flow as the pattern for preserving
the last status message.

---

Nitpick comments:
In `@tests/suggest_position_move.test.js`:
- Around line 90-206: Add a test in suggest_position_move.test.js for a mixed
chord drag where one note is position-locked and a sibling is movable, and the
movable note resolves onto the locked note’s string. Use the existing helpers
like makeMoveEnv, startDrag, and _rollDragPitchMove to verify the locked note
stays on its original string/fret while the sibling is repicked to a different
string instead of silently double-booking; reference the same occupancy behavior
covered by the sequential chord test and the _positionLocked/_rollDragPitchMove
paths.

In `@tests/suggest_position_persist.test.js`:
- Around line 89-193: The suggested-mark persistence tests are missing coverage
for Save-As key migration and duplicate-identity matching behavior. Extend the
existing persistence test suite around makeEnv, _saveSuggestedMarks,
_restoreSuggestedMarks, and _applySuggestedMarksPure to verify that
editorSuggested:* data is moved to the new song key on Save-As without leaving
stale source keys behind, and add a duplicate-note scenario with identical
string/fret/time proximity so the greedy 1:1 re-attachment logic only marks one
matching note.
🪄 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: 2a4d826c-9c2d-4dcc-95ac-cc40f9d00155

📥 Commits

Reviewing files that changed from the base of the PR and between 5887b59 and 1723383.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • screen.js
  • tests/suggest_position.test.js
  • tests/suggest_position_move.test.js
  • tests/suggest_position_persist.test.js
  • tests/suggest_position_wiring.test.js

Comment thread screen.js
Comment on lines +3776 to +3862
// Strings sounding across `time` — a string can't play two notes at the same
// instant. A note occupies its string from n.time to n.time+sustain (a
// zero-sustain note occupies only its onset). `except` skips notes being edited
// from their own occupancy check: a single index, a Set of indices, or -1/null
// for none (so a multi-note drag can exclude its whole moving set).
function _occupiedStringsAt(arr, time, except) {
const skip = except instanceof Set ? except
: (typeof except === 'number' && except >= 0 ? new Set([except]) : null);
const occ = new Set();
const nn = (arr && Array.isArray(arr.notes)) ? arr.notes : [];
for (let i = 0; i < nn.length; i++) {
if (skip && skip.has(i)) continue;
const n = nn[i];
if (!n || typeof n.time !== 'number') continue;
const end = n.time + (n.sustain || 0);
if (time >= n.time - 1e-6 && time <= end + 1e-6) occ.add(n.string);
}
return occ;
}

// A note whose FRET is coupled to a technique, so a roll pitch-move can't repick
// its string/fret without corrupting the technique's meaning (V5): a slide
// targets a fret, a bend/harmonic anchors to one. Such notes REFUSE the roll
// pitch-move (edit them in the fretted views). Pitch-portable techniques
// (palm mute, vibrato, accent…) ride along and don't lock.
function _positionLocked(n) {
const t = (n && n.techniques) || {};
return t.slide_to >= 0 || t.slide_unpitch_to >= 0
|| t.bend > 0 || (Array.isArray(t.bend_values) && t.bend_values.length > 0)
|| !!t.harmonic || !!t.harmonic_pinch;
}

// Live pitch-MOVE by drag in the fretted roll (VA.3): `dy` is a SOUNDING-pitch
// delta; the resolver repicks {string,fret} at the new pitch, biased to keep the
// hand where it was (prevNote = the note's original fret). `snappedDt` is the
// shared group time delta (already snapped + clamped ≥ 0 by _groupTimeDeltaPure)
// and always applies (a pitch-domain edit). A position-locked note, or a pitch
// the resolver refuses (ambiguous / out of the hand window / occupied), HOLDS at
// its last resolvable position — the note visibly sticks rather than the machine
// guessing. The drop (onMouseUp) commits the net change as a suggested MoveNoteCmd.
function _rollDragPitchMove(nn, snappedDt, dy) {
const arr = S.arrangements[S.currentArr];
const ctx = _rollPitchCtx();
if (!arr || !ctx) return;
const dMidi = -Math.round(dy / PIANO_LANE_H);
const moving = new Set(S.drag.indices);
// Time always applies (a pitch-domain edit); gather the actual pitch-movers.
const movers = [];
for (let i = 0; i < S.drag.indices.length; i++) {
const ni = S.drag.indices[i];
const n = nn[ni];
if (!n) continue;
n.time = S.drag.origTimes[i] + snappedDt;
if (dMidi === 0 || _positionLocked(n)) continue; // no pitch move / technique-locked ⇒ hold
const origPitch = _soundingPitchPure(
ctx.openMidi, ctx.tuning, ctx.capo, S.drag.origStrings[i], S.drag.origFrets[i]);
if (origPitch === null) continue;
movers.push({ n, target: origPitch + dMidi, prevFret: S.drag.origFrets[i] });
}
// Resolve SEQUENTIALLY, in ascending target sounding-pitch (ties keep drag
// order — Array.sort is stable). Each resolved member's chosen string joins
// the occupancy the later members see, so two members of a vertically-dragged
// chord can't independently pick the SAME string: at save reconstructChords
// does frets[n.string] = n.fret, and a shared string would drop a member
// (the chord template silently loses a note). A member the resolver refuses
// HOLDS its old position and contributes NO occupancy (unchanged behaviour).
movers.sort((a, b) => a.target - b.target);
const claimed = []; // {time, end, string} chosen by already-resolved siblings this drag
for (const mv of movers) {
const n = mv.n;
const occ = _occupiedStringsAt(arr, n.time, moving);
// Full interval overlap (not just onset containment): movers are ordered
// by pitch, not time, so a later-resolved member can start BEFORE an
// already-claimed one yet sustain through it — both directions collide.
const nEnd = n.time + (n.sustain || 0);
for (const c of claimed) {
if (n.time <= c.end + 1e-6 && nEnd >= c.time - 1e-6) occ.add(c.string);
}
const res = _suggestPositionPure(
mv.target, n.time, { fret: mv.prevFret }, _rollAnchorList(arr), occ, ctx);
if (res.resolved) {
n.string = res.resolved.string;
n.fret = res.resolved.fret;
claimed.push({ time: n.time, end: n.time + (n.sustain || 0), string: res.resolved.string });
}
}
}

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 | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -e
echo "=== locate relevant symbols ==="
rg -n "_occupiedStringsAt|_positionLocked|_rollDragPitchMove|reconstructChords|claimed.push|_suggestPositionPure|_rollPitchCtx|S.drag.indices" screen.js

echo
echo "=== inspect surrounding lines around drag code ==="
sed -n '3760,3885p' screen.js

echo
echo "=== inspect chord reconstruction/save logic ==="
rg -n "reconstructChords|frets\\[n\\.string\\] = n\\.fret|chord" screen.js
sed -n '7200,7345p' screen.js

Repository: got-feedBack/feedBack-plugin-editor

Length of output: 41141


🏁 Script executed:

#!/bin/bash
set -e
python3 - <<'PY'
from pathlib import Path
p = Path('screen.js')
text = p.read_text()
for needle in ['function _occupiedStringsAt', 'function _positionLocked', 'function _rollDragPitchMove', 'frets[n.string] = n.fret']:
    idx = text.find(needle)
    print(f'--- {needle} @ {idx}')
    if idx != -1:
        start = text.rfind('\n', 0, idx-200)
        end = text.find('\n\n', idx)
        print(text[idx: end if end!=-1 else idx+800])
PY

Repository: got-feedBack/feedBack-plugin-editor

Length of output: 7443


🏁 Script executed:

#!/bin/bash
set -e
sed -n '3600,3668p' screen.js

Repository: got-feedBack/feedBack-plugin-editor

Length of output: 4259


screen.js:3824-3859 — Drag resolution still allows duplicate strings in mixed chords

  • Technique-locked or zero-pitch members are skipped before claimed is seeded, so a free sibling in the same drag can still repick the held note’s string. Seed the drag-local occupancy with held notes too.
  • _occupiedStringsAt() only checks the mover’s onset against existing spans. A static note that starts during the mover’s sustain on the same string can still slip through; this path needs interval-overlap checks for non-moving notes as well.
🤖 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 `@screen.js` around lines 3776 - 3862, The drag resolver in _rollDragPitchMove
still lets mixed chords reuse strings because held notes are excluded from the
drag-local occupancy and _occupiedStringsAt only checks a single time point.
Seed the local `claimed`/occupancy set with technique-locked or zero-pitch
movers too, so free siblings cannot repick those strings, and extend the
non-moving occupancy check to reject any note whose full time span overlaps the
moving note’s span, not just notes containing the mover’s onset. Use
_rollDragPitchMove and _occupiedStringsAt as the main touchpoints.

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