fix(practice): draw the first Practice problem at the unlocked level (#21) - #25
Merged
Conversation
…21) useUnlockedLevel() resolved meta.unlockedLevel into a ref asynchronously, but useDrill/useGuided seed problem #1 synchronously in their useState initializer — when the ref is still 0. Now that level tests write meta.unlockedLevel, a user who unlocked a wider range got a level-0 first problem every session. - useUnlockedLevel() now also returns the resolved `level` + a `loaded` flag (alongside the live `ref` for imperative next-problem reads). - useDrill/useGuided keep the lazy seed for first paint and add a once-guarded effect that regenerates problem #1 when the level resolves — only if it differs from the seed (skips 0, so default users see no swap) and only while the first problem is still untouched. StrictMode-safe (draw outside the updater). - useSpeedrun only consumes the ref (its first problem is generated in the user-triggered start(), after the read resolves) — left unchanged. Tests: new useDrill/useGuided specs assert the redraw at the unlocked level and the no-swap guard at level 0; useUnlockedLevel test updated for the new shape. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Pull two copy-pasted effects out of the practice runners into shared hooks: - useFirstProblemAtLevel: redraw the lazily-seeded problem #1 at the unlocked level once it resolves (was duplicated in useDrill/useGuided). - useAttemptsRef: load the attempt history into a ref on mount (was duplicated in useDrill/useGuided/useSpeedrun). Behavior unchanged; removes the per-hook duplication. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #21.
First issue off the post-Levels roadmap — picked first because it's the only live bug and it directly protects the just-shipped Levels feature.
The bug
useUnlockedLevel()resolvedmeta.unlockedLevelinto a ref asynchronously, butuseDrill/useGuidedseed problem #1 synchronously in theiruseStateinitializer — when the ref is still0. Now that level tests writemeta.unlockedLevel, a user who unlocked a wider range got a level-0 first problem every session (only problem #2 onward respected the unlocked level). Latent until #13 shipped; live now.The fix
useUnlockedLevel()now returns{ ref, level, loaded }— the livereffor imperative next-problem reads, plus the resolvedlevel+ aloadedflag.useDrill/useGuidedkeep the lazy seed for first paint and add a once-guarded effect that regenerates problem fix: address baseline code-review findings #1 when the level resolves — only if it differs from the seed (skipslevel === 0, so default users never see a swap) and only while the first problem is still untouched. The draw happens outside the state updater (StrictMode-safe).useSpeedrunonly consumes theref(its first problem is generated in the user-triggeredstart(), after the read resolves) — left unchanged.Tests
useDrill/useGuidedspecs assert the redraw at the unlocked level and the no-swap guard at level 0 — strengthened to assert call count + problem-object identity (a weaker first cut false-passed; an adversarial review caught it and a mutation test now confirms removing thelevel === 0guard makes the no-swap test fail).useUnlockedLeveltest updated for the new{ ref, level, loaded }shape.Verification
--max-warnings 0✓ · 290 tests ✓ · build ✓🤖 Generated with Claude Code