Skip to content

Fix issue #350: deterministic notation stacking order#351

Merged
saxjax merged 10 commits into
masterfrom
issue-350-order-of-notations-fix
Apr 22, 2026
Merged

Fix issue #350: deterministic notation stacking order#351
saxjax merged 10 commits into
masterfrom
issue-350-order-of-notations-fix

Conversation

@saxjax
Copy link
Copy Markdown
Collaborator

@saxjax saxjax commented Apr 21, 2026

Summary

Fixes #350 — the notation layers on each key no longer appear in random/reversed order after menu interactions or session loads. The rendered stack now always matches the canonical Notation-menu order, top-to-bottom: Chord extensions → Scale Steps → Relative → Romance → German → English (Chord extensions on top, English at bottom).

Approach (state-boundary normalization)

The earlier attempt (now reverted) patched the generic ListCheckbox component, but discovery showed the real ownership was WholeApp.state.notation plus the session-restore path that bypassed any menu-layer normalization. This PR takes a state-layer approach instead:

  • New pure helper src/Model/normalizeNotationOrder.js — idempotent, never mutates, preserves unknown values defensively.
  • WholeApp.handleChangeNotation normalizes the selected notation array before setState.
  • WholeApp.openSavedSession normalizes result.notation before setState, so legacy/out-of-order session documents can no longer carry a scrambled array into live state.
  • New WholeApp.componentDidUpdate safety net re-normalizes state.notation after any other writer (direct setState, future writers), making canonical order a true invariant of component state rather than a convention.

This is documented as a new invariant in .github/skills/flows/settings-propagation.md.

Tests

New integration tests at src/integration/notation-order.test.js cover both writer paths:

  1. Menu writer path — calls handleChangeNotation with a scrambled array and asserts the rendered .noteWrapper--* DOM order is canonical.
  2. Direct state-write path (session-restore) — sets state.notation directly to a legacy out-of-order array (simulating openSavedSession) and asserts canonical DOM order.

Both tests fail on the baseline and pass with the fix.

Test status:

  • Target tests: 2/2 pass
  • Adjacent WholeApp integration suites (scale-visualization, menu-staff-integration, state-flow): 34/34 still pass
  • Full suite: 301/306 pass. The 5 failing tests (MusicScale relative-syllable logic, CustomVideoPlayer integration) were verified to fail identically on origin/master — pre-existing, unrelated to this fix.

Commit structure

  • chore(dal-a) — DAL-A project intelligence and lint script (unrelated scaffolding included in the same task)
  • docs(task) — task-tracking file rewrite
  • Revert "Fix deterministic notation stacking order for issue #350" — undo of the prior approach (fb80ceb)
  • test(issue-350) — failing integration tests (red baseline)
  • fix(issue-350) — helper + writer normalization + lifecycle safety net
  • docs(flows) — record the new invariant in the flow doc

Risk

  • No changes to notation data generation (MusicScale.js), render components (Keyboard.js, ColorKey.js), or the ListCheckbox public API.
  • Backward compatible with existing Firestore session documents — legacy arrays are silently canonicalized on load.
  • componentDidUpdate safety net triggers a second render only when an un-normalized write occurs; common menu/restore paths eagerly normalize and render correctly on the first frame.

Closes #350

Copilot AI review requested due to automatic review settings April 21, 2026 19:25
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes unstable notation stacking (issue #350) by making ListCheckbox emit selected values in a deterministic, canonical order so keyboard notation layers render consistently.

Changes:

  • Updated ListCheckbox to emit selected values in a stable order derived from the options list (while retaining non-menu selections present in initOptions).
  • Added prop-sync behavior so local checkbox state rebuilds when options/initOptions change.
  • Added regression tests covering toggle-order stability and initOptions prop updates.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
src/components/form/ListCheckbox.js Implements canonical-order emission and prop-sync state rebuilding.
src/test/ListCheckbox.order.test.js Adds regression tests for deterministic emission and prop syncing.
.github/copilot/tasks/issue-350-order-of-notations/task.md Documents investigation, plan, and validation for issue #350.

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

Comment thread src/components/form/ListCheckbox.js
saxjax added 8 commits April 21, 2026 21:50
- .github/copilot/: project-context, constraints, dependencies, copilot-instructions
- .github/skills/flows/: 6 flow docs + hub (app-bootstrap, scale-change-pipeline,
  note-playback, session-share, video-overlay, settings-propagation)
- .github/hooks/: constraint-guard, dangerous-command-guard,
  flow-verification-reminder, constraint-forwarding
- package.json: add "lint" script (eslint src --ext .js,.jsx) and eslint
  devDependency aligned with existing lockfile chain
- tasks/dal-a-project-setup/: completed setup task record
Rewrite task.md to reflect decision to revert the prior fix commit fb80ceb
and replan using state-boundary normalization in WholeApp. Documents
slice-by-slice roadmap and confirmed visual stacking direction.
This reverts commit fb80ceb.

Reason: the prior fix edited the generic ListCheckbox component rather
than the real ordering owner (WholeApp state + Keyboard/ColorKey render
pipeline) and missed the session-restore path. Replanning per task file
with Option 1 (state-boundary normalization in WholeApp).

Conflict resolution: kept the current HEAD version of the issue-350
task.md (it was rewritten on this branch with the new roadmap after
fb80ceb was authored).
…order

Two focused integration tests lock the visual contract that notation
layers render in the canonical Notation-menu order (Chord extensions on
top, English at bottom) regardless of the order in which the notation
array is written into WholeApp state.

Test 1 (menu writer path): calls instance.handleChangeNotation with a
scrambled array and asserts canonical DOM order.

Test 2 (session-restore path): simulates openSavedSession by directly
setting state.notation to a legacy out-of-order array and asserts
canonical DOM order.

Both fail today because WholeApp writes the notation array verbatim into
state and Keyboard/ColorKey render in that literal order. Slice 3 will
add normalizeNotationOrder at every writer seam to make these pass.
…writer

Add src/Model/normalizeNotationOrder.js exporting a pure, idempotent
helper that re-orders any notation array to the canonical Notation-menu
order ("Colors" first, then Chord extensions, Scale Steps, Relative,
Romance, German, English). Unknown values are preserved at the end for
forward-compat and malformed session payloads.

Wire the helper into WholeApp with defense-in-depth (Approach 3):
- handleChangeNotation now normalizes the selected notation array
  emitted by the Notation menu's ListCheckbox callback.
- openSavedSession now normalizes result.notation before setState, so
  legacy or out-of-order session documents cannot carry a scrambled
  array into live state.
- componentDidUpdate acts as a safety net: any writer that reaches
  state.notation (direct setState, future writers) is re-normalized,
  making canonical order a true invariant of WholeApp state rather than
  a convention.

Verification: both tests in src/__integration__/notation-order.test.js
now pass, and adjacent WholeApp integration suites (scale-visualization,
menu-staff-integration, state-flow) still pass (34/34).
…opagation

Fix #350 made canonical Notation-menu order a true invariant of
WholeApp state.notation. Record the enforcement points (handleChangeNotation,
openSavedSession, componentDidUpdate) and the normalizeNotationOrder
helper in the settings-propagation flow doc so future work knows this
invariant exists and where it is enforced.
Copy link
Copy Markdown

Copilot AI left a comment

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 fixes issue #350 by enforcing a deterministic, canonical stacking order for notation label rows rendered on keys, ensuring the UI matches the Notation menu’s intended ordering even after menu interactions and session restores. It also adds DAL-A-related repo documentation/hook scaffolding and a standalone lint script.

Changes:

  • Introduce normalizeNotationOrder and apply it at WholeApp notation writer boundaries (menu + session restore) plus a componentDidUpdate safety net to enforce a state invariant.
  • Add integration coverage to lock the DOM/visual stacking order regardless of input array ordering.
  • Add DAL-A scaffolding: flow docs, Copilot project docs/constraints, hook scripts, and a lint npm script/devDependency.

Reviewed changes

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

Show a summary per file
File Description
src/integration/notation-order.test.js Adds integration tests asserting canonical DOM order for notation rows via menu writer and direct state writes.
src/WholeApp.js Normalizes notation on write (menu + session restore) and enforces canonical order via componentDidUpdate.
src/Model/normalizeNotationOrder.js New pure helper to reorder notation arrays into canonical order while preserving unknown values.
package.json Adds standalone lint script and adds eslint devDependency.
.github/skills/flows/video-overlay.md Adds runtime flow doc for the video overlay path.
.github/skills/flows/settings-propagation.md Adds flow doc and documents the new state.notation canonical-order invariant.
.github/skills/flows/session-share.md Adds runtime flow doc for session sharing/restoration.
.github/skills/flows/scale-change-pipeline.md Adds runtime flow doc for scale selection → keyboard rebuild.
.github/skills/flows/note-playback.md Adds runtime flow doc for keyboard input → audio playback.
.github/skills/flows/app-bootstrap.md Adds runtime flow doc for app startup and session hydration.
.github/skills/flows/SKILL.md Adds a catalog/index for the flow docs.
.github/hooks/scripts/flow-verification-reminder.py Adds hook script to remind updating flow docs when edited files are referenced by a flow.
.github/hooks/scripts/dangerous-command-guard.py Adds hook script to flag potentially destructive terminal commands.
.github/hooks/scripts/constraint-guard.py Adds hook script enforcing DAL-A constraints on edits to sensitive paths.
.github/hooks/scripts/constraint-forwarding.py Adds hook script to forward constraint summaries to subagents.
.github/hooks/hooks.json Wires up the hook scripts for PreToolUse/PostToolUse/SubagentStart.
.github/copilot/tasks/issue-350-order-of-notations/task.md Adds task tracking doc for the issue #350 work and decisions.
.github/copilot/tasks/dal-a-project-setup/task.md Adds task tracking doc for DAL-A setup work.
.github/copilot/project-context.md Adds Copilot-oriented project context (stack, structure, conventions).
.github/copilot/dependencies.md Adds Copilot-oriented dependency inventory.
.github/copilot/copilot-instructions.md Adds repo-local Copilot usage instructions and references.
.github/copilot/constraints.md Adds DAL-A constraints (protected paths, security boundaries, fragile areas, etc.).

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

Comment thread .github/copilot/tasks/issue-350-order-of-notations/task.md Outdated
Comment thread src/Model/normalizeNotationOrder.js Outdated
Comment thread src/Model/normalizeNotationOrder.js Outdated
Comment thread .github/hooks/scripts/dangerous-command-guard.py Outdated
Comment thread .github/copilot/dependencies.md Outdated
- normalizeNotationOrder.js: rephrase idempotency contract as content
  equality (function returns a new array instance, so === is not
  guaranteed). Correct the "Colors" comment: the case "Colors" branch
  in Keyboard is a no-op; entry is preserved for backward compatibility.
- task.md: consolidate the two conflicting visual-direction rows in the
  Decisions table into a single canonical row (DOM order = canonical
  list, Chord extensions on top, English at bottom).
- dependencies.md: align eslint version with package.json (^8.3.0).
- dangerous-command-guard.py: fix `rm -rf .` regex so it matches `.`
  followed by /, whitespace, or end-of-string (previously required a
  trailing whitespace and missed the common end-of-line form).
@saxjax saxjax merged commit 4d96b88 into master Apr 22, 2026
21 checks passed
@saxjax saxjax deleted the issue-350-order-of-notations-fix branch April 22, 2026 20:47
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.

order of notations

2 participants