keyboard overlay fixes: step arcs, degree labels, and the 1-of-0 desync
Created by: jedmund
Summary
Three bug fixes that build on the recent Note Identifier and keyboard work.
-
d97a2c8note identifier: fix 1-of-0 desync on first exercise load. The exercise initializer wrote the prompt pool into an@Statevar and readpromptPool.countback on the very next line. On the first render that read could return the stale empty value, sostate.reset(totalPrompts: 0)was called; the firstadvance()then flippedisCompleteimmediately (anypromptIndex >= 0passed the check) and the completion card showed "1 of 0". Retry worked because by then the@Statehad materialized. Fix: compute the pool into a local first, write the@Stateoff it, and pass the local's count tostate.reset. Same shape applied inresetExercise. Belt-and-braces guard inExerciseState.advance()soisCompletecan't flip whentotalPrompts <= 0. -
567d4f8keyboard overlay: fix arc/label rendering and ForEach tuple-keypath crash. Step arcs and degree labels were drawing on top of the keys instead of above them. TheoverlayStackrelied on.alignmentGuide(.top) { $0[.bottom] + offset }to push overlays outside the keyboard's top edge; that trick didn't take effect reliably and the content landed at the ZStack's natural top (inside the key area). Replaced with an explicit.offset(y: -(height + bottomOffset)). Separately,overlayStackandStepArcOverlayiterated tuple-typed arrays ([(item: …, bottomOffset: …)],[(arc: …, tier: …)]) with keypath ids. SwiftUI's runtime generic-witness lookup for ForEach over tuple-keypath-id is fragile and crashed withEXC_BAD_ACCESSinswift_getAssociatedTypeWitnessthe moment the overlay was toggled on. IntroducedPositionedOverlayandTieredArcas properIdentifiablestructs; ForEach now uses the nativeIdentifiableoverload. -
c4ce5deplayground degree labels: fix stack-overflow from self-referential keyboardConfig. Toggling 'Degree labels' in the Playground sidebar crashed withEXC_BAD_ACCESS: Thread stack size exceeded due to excessive recursion. When the toggle was true,keyboardConfigbuilt an overlay item whose content wasdefaultDegreeLabels, anddefaultDegreeLabelsreadkeyboardConfig.pitchRangeto bound its iteration — self-reference. ExtractedpitchRangeas its own computed property derived directly from params; bothkeyboardConfiganddefaultDegreeLabelsread from it now. Bonus:DegreeLabelis nowIdentifiableandDegreeLabelOverlay's ForEach drops its keypath-id, matching the step-arc fix.
Test plan
-
Open lesson 1-1 "Same Key, Two Names" fresh (no prior progress). Exercise starts at prompt 1, all 10 prompts present, scoring card reads "X of 10". -
Lesson 1-2 "Half Steps", "Three Kinds of Half Step", "Whole Steps": arcs render above the keys, not overlapping them. -
Lesson 1-3 "Perfect Intervals": P4 / P5 / P8 arcs tiered above the keyboard (taller stack, most likely to surface clipping). -
Scale Builder: completed arcs stack above the keys as each step is answered. -
Playground Keyboard tool: toggle "Step arcs" on — no crash, arcs render above keys. Toggle off/on repeatedly. -
Playground Keyboard tool: toggle "Degree labels" on — no crash, circled degree badges above their keys. Toggle off/on repeatedly. -
Playground with both toggles on: arcs + labels stacked above the keys without overlap. -
Tap an arc — still plays the interval and shows the expanded name. Tap a degree badge — still shows the full degree name.