Skip to content

Conversation

@wilkie
Copy link
Contributor

@wilkie wilkie commented Oct 25, 2025

Does the following:

  • For sublevels, it is still 'Continue' when it would go back to the parent
  • Replaces 'Continue' with 'Continue to Level X' (unless it is a sublevel and would return back up)
  • Applies styling to truncate any overflowing text with ... since the text is significantly longer
  • Replaces 'Finish' with 'Finish Lesson'
  • Unless it is the last level of the only lesson in the unit, then it still says 'Finish'
  • Replaces 'Submit' with 'Submit Work'
  • Has a variant prop that can restore the old text and behavior which is used on the Hour of AI Guide elements.
  • Makes the background behind the navigation button the primary color for the theme
After
Has a next level
image
Dark Theme
image
Last level in a lesson
image
Last sublevel that goes to parent level
image
Last level in a single lesson unit
image
Guides are always a 'small' variant which uses the old simple text
image
image

Also done: "Submit" becomes "Submit Work" but I do not know about a lab2 assessment level to illustrate it.

Width Constraints

Sometimes the button is just too small for the full 'Continue to Level X' text, so it might collapse with ...:

2025-12-12.14-37-24.mp4

Notes

This updates the progress redux selectors since we need to know the level number to render the 'Continue to Level X' text and there's no easy way to get the level data. The existing code was only to support getting the level id for the next level for navigation and to know if there's a next level. So this adds the getParentLevel and getNextLevel convenience functions and refactors a bit when those functions could be used internally.

Links

PR Creation Checklist:

  • Tests provide adequate coverage
  • Privacy impacts have been documented
  • Security impacts have been documented
  • Code is well-commented
  • New features are translatable or updates will not break translations
  • Relevant documentation has been added or updated
  • User impact is well-understood and desirable
  • Follow-up work items (including potential tech debt) are tracked and linked

@wilkie wilkie force-pushed the labs/update-continue-button-text branch from 06982bf to 384924b Compare October 26, 2025 19:48
@wilkie wilkie marked this pull request as ready for review October 26, 2025 20:28
@wilkie wilkie force-pushed the labs/update-continue-button-text branch 2 times, most recently from e1f255d to 36bff36 Compare October 30, 2025 20:35
@wilkie wilkie force-pushed the labs/update-continue-button-text branch from 36bff36 to bc8e5ca Compare November 13, 2025 21:47
@wilkie wilkie requested a review from a team November 17, 2025 19:19
Copy link
Contributor

@fisher-alice fisher-alice left a comment

Choose a reason for hiding this comment

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

Nice! Just left some questions, one about about shared UI with GuideInstructions. Thanks for taking this on!

"submitAnswer": "Submit answer",
"submitAssessment": "Submit your assessment",
"submitSurvey": "Submit your survey",
"submitWork": "Submit Work",
Copy link
Contributor

Choose a reason for hiding this comment

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

Just curious - do we still need to add loc strings?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I do so here because this is a global element that is visible in legacy applications... though, it is still unlikely to get a translation. We might go in and manually add a manual translation for a few languages, however. For instance, a spanish translation we manually add seems appropriate for this. This is follow-up work.

state => state.lab.validationState?.satisfied
);
// Get the level number of the next level (in the progression)
const continueToLevel = useAppSelector(
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: could name something like nextLevelNumber and currentLevel as currentLevelNumber (below).

*
* Bubble choice levels return the parent level in the case where the navigation
* type is not 'NEXT_LEVEL', since that is where the progression would take them
* when they press 'continue' in that case.
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you add in the doc what happens when type is NEXT_LEVEL and not PARENT? And to confirm my understanding, would the level after the parent level be returned? If so, would 'Continue to Level X' be displayed?

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, what happens if the bubble_choice level is last in the progression with NEXT_LEVEL navigation type? Would 'Finish Lesson' be displayed?

Copy link
Contributor

Choose a reason for hiding this comment

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

Tagging @sanchitmalhotra126 who added navigation_type in #69083 - it seems like if the sublevel navigation type is NEXT_LEVEL, then the level after parent level would be returned, and if the parent level was the last in a progression, undefined would be returned?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yep correct! If getNextLevel returns undefined, in either case, we will show the "Finish" text rather than Continue.

Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: could update to include both navigation types:
* Bubble choice levels return the parent level in the case where the navigation type is 'PARENT' and not 'NEXT_LEVEL'

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's technically true, however, it's a default behavior where NEXT_LEVEL indicates breaking the default behavior. It's documenting the guard and not the else statement: Here's what to do to influence the default behavior for sublevels... which usually go to the parent level when you are done. I don't want to over-document in case anything is updated where the documentation could go out of sync... though to be honest a good way to deal with that is a test and we have none for these. The proper thing to do would be to add some tests to ensure we capture the documented behavior. I will do this.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks Wilkie for your thoughtful response - this makes sense!

"continueBeyondHourOfCode": "Continue Beyond an Hour of Code",
"continueLesson": "Continue lesson",
"continueToActivity": "Continue to activity",
"continueToLevel": "Continue to Level {level}",
Copy link
Contributor

Choose a reason for hiding this comment

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

This change also impacts the Hour of AI Guide UI since the NavigationArea is shared between resource panel and GuideInstructions. Maybe add a prop when we want to add more descriptive text?

Image

Copy link
Contributor

Choose a reason for hiding this comment

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

And do you mind checking levels in music lab? They share the navigation area, but their navigation button is not pinned to the bottom as it is in other lab2 labs. It appears once the required actions are taken by user. Thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good find. Yes, these buttons are problematic when localized too since being too long is difficult to accommodate.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Resolved this by using a resize observer. If the button is too small for whatever reason, it will render the simpler text we were using before.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Please see the video in the PR description.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for adding the variant prop!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated to just truncate the text with text-overflow so it shows ... (although another update just used the added variant prop to force Guide components to use the old text)

"findYourSchool": "Find your school or provide details if it's not listed.",
"finish": "Finish",
"finishCreatingSections": "Finish creating sections",
"finishLesson": "Finish Lesson",
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar impact on Guide UI - probably need to check if folks want 'Finish Lesson' instead of 'Finish' on last level of Hour of AI.

Copy link
Contributor

Choose a reason for hiding this comment

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

Screenshot 2025-11-18 at 8 58 02 AM

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, so if there is only one lesson? That's an interesting question.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Resolved this by investigating the number of lessons in the script. If there's only one, then we always just say 'Finish' instead of 'Finish Lesson'.

Copy link
Contributor

Choose a reason for hiding this comment

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

Nice!

@fisher-alice fisher-alice requested a review from a team November 18, 2025 16:12
@wilkie wilkie force-pushed the labs/update-continue-button-text branch from 48962cc to 5e52153 Compare December 10, 2025 10:12
@wilkie wilkie requested review from a team as code owners December 10, 2025 10:12
@wilkie wilkie changed the base branch from staging to staging-next December 10, 2025 10:16
@wilkie wilkie force-pushed the labs/update-continue-button-text branch from f91e344 to a858e22 Compare December 10, 2025 10:17
@github-actions
Copy link

github-actions bot commented Dec 10, 2025

🖼️ Storybook Visual Comparison Report

✅ No Storybook eyes differences detected!


.navigationFooter {
padding: 10px;
background-color: var(--background-neutral-secondary);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is part of this ticket to update the background color to have it stand out against feedback messages in the instructions panel... so that the button doesn't look like it is relating to any feedback or messaging that might appear directly above it.

useEffect(() => {
const container = buttonContainerRef.current;

let resizeObserver: ResizeObserver | undefined;
Copy link
Contributor

Choose a reason for hiding this comment

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

When I load a level, the full button text is shown, but when I go to next level, shortened version if shown. But full text is shown if I reload level.

I think that we're missing dependency on level id?

Screen.Recording.2025-12-11.at.3.38.40.PM.mov

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The element gets unmounted and hidden for a moment as the level switches leading it to have a 0 width for a frame. However, it doesn't trigger the event again when it is revealed. It's a little haphazard and mysterious, though it is fixed if you ignore 0 width... I've elected to abandon it and just do normal text truncation since the main issue was with Guide components and we just force those to render the old text anyway.

Copy link
Contributor

@fisher-alice fisher-alice left a comment

Choose a reason for hiding this comment

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

Thanks so much for iterating on this! We discussed a couple possible solutions to the resizing issue on Slack so will wait to see what you decide on for that.

styleAsBubble?: boolean;
/** Optional on continue/finish callback. */
onContinue?: () => void;
/** Whether or not to keep the text 'simple' */
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: could rename to something like buttonTextVariant.

Copy link
Contributor

Choose a reason for hiding this comment

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

+1, or something like simpleButtonText?: boolean? variant feels a bit generic here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I dislike boolean properties! What if you want to add a third option? You'd have to rewrite the whole property. I learned this lesson from a W3C CSS editor when he asked me why I thought there were no boolean properties in CSS: because you don't want to be stuck with it. Doesn't 100% apply here of course... we can change them, but it's on my mind a whole dang lot.

variant is a conventional React property for influencing the appearance where it doesn't necessarily affect the behavior of a component. It felt right to use it when it is a property that decides a certain type of rendering behavior. It is meant to be generic. It's possible we might want a third option at some point.

Copy link
Contributor

Choose a reason for hiding this comment

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

That's fair! I do still think a more specific property name would be helpful though as NavigationArea refers to more than just the navigation button.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks Wilkie for sharing your thoughts! I agree with Sanchit that a more specific property name would be helpful, like buttonTextSize or buttonTextVariant since NavigationArea is used in InstructionsV2 and the resource panel which both contain a long list of props, some of which get passed to NavigationArea.
But I'm good with your keeping variant if you feel strongly about it. Thanks for the discussion!

"continueBeyondHourOfCode": "Continue Beyond an Hour of Code",
"continueLesson": "Continue lesson",
"continueToActivity": "Continue to activity",
"continueToLevel": "Continue to Level {level}",
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for adding the variant prop!

*
* Bubble choice levels return the parent level in the case where the navigation
* type is not 'NEXT_LEVEL', since that is where the progression would take them
* when they press 'continue' in that case.
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: could update to include both navigation types:
* Bubble choice levels return the parent level in the case where the navigation type is 'PARENT' and not 'NEXT_LEVEL'

const smallText = hasNextLevel
? commonI18n.continue()
: lessonCount > 1
? commonI18n.finishLesson()
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder for the 'small' variant if we just want to always display 'Finish'? @dju90 curious for your thoughts here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep. Makes sense. Done.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry, what's the alternative?

Copy link
Contributor

Choose a reason for hiding this comment

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

I was suggesting that If the button text variant is 'small' (as it is in the GuideInstructions for the Mix and Move progresion), the text will always be 'Finish', even if there is more than one lesson in the course unit.
The alternative would be that even if the button text variant is 'small', that 'Finish lesson' would be displayed if there is more than one lesson.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh I see. Okay yes, having just "Finish" always for the small variant sounds good to me.

"findYourSchool": "Find your school or provide details if it's not listed.",
"finish": "Finish",
"finishCreatingSections": "Finish creating sections",
"finishLesson": "Finish Lesson",
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice!

Ensures small variant uses 'Finish' instead of 'Finish Lesson'.
@alex-m-brown alex-m-brown changed the base branch from staging-next to staging December 15, 2025 15:43
Copy link
Contributor

@fisher-alice fisher-alice left a comment

Choose a reason for hiding this comment

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

🎉 - thanks for addressing my earlier questions/comments - looks great!
Just left a non-blocking suggestion on updating NavigationArea prop name. Thanks Wilkie!

);
}

return;
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: remove?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's not a nit. That's just wrong!!

Copy link
Contributor

Choose a reason for hiding this comment

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

Just curious - Is it wrong or is it not necessary/redundant?

styleAsBubble?: boolean;
/** Optional on continue/finish callback. */
onContinue?: () => void;
/** Whether or not to keep the text 'simple' */
Copy link
Contributor

Choose a reason for hiding this comment

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

+1, or something like simpleButtonText?: boolean? variant feels a bit generic here

Comment on lines 86 to 93
const nextLevelNumber = useAppSelector(
state => getNextLevel(state)?.levelNumber
);
// Get the current main level (parent if it is a sublevel)
const currentLevel = useAppSelector(
state =>
getParentLevel(state)?.levelNumber || getCurrentLevel(state)?.levelNumber
);
Copy link
Contributor

Choose a reason for hiding this comment

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

Optional: could move the logic for computing the button text into one selector instead of pulling out these two values separately

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I generally dislike adding logic to the redux selectors, but I'm happy to do this.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah I suggest this just because then technically the component will re-render only when the actual text value changes, rather than when either of these values change.

@dju90
Copy link
Contributor

dju90 commented Dec 15, 2025

Screenshots look good to me! Thank you @wilkie and @fisher-alice

state.progress.currentLessonId,
currentLevel.parentLevelId
);
currentLevelIndex = parentLevel.levelNumber - 1;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

What's confusing is why this is levelNumber - 1 to get to the level after the parent level. I'd expect just levelNumber.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But it is because the levelNumber is 1 based and we need to convert it to a zero-based index.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Although if it has an unplugged activity, all this logic already breaks down unless the level data specifically has title || position in such a way that it provides the right 0-based index. hm.

Copy link
Contributor

Choose a reason for hiding this comment

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

One of my favorite unplugged lessons is Binary Bracelets. Looking at the script, I see
position is a Script Level property that gets assigned tolevelNumber on the frontend I think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed. I love that we promote unplugged and more offline content.

Here's the logic (processedLevel()) that decides the levelNumber... indeed it is decided on the frontend and is an interpretation of the data sent from the backend. So, the level data contains a levelNumber that is undefined if unplugged and otherwise is the title value (falling back to the position if title is false-y (0 or undefined))

levelNumber:
level.kind === LevelKind.unplugged
? undefined
: level.title || level.position,

So if you follow the logic through, when it constructs the level array that it is using to dictate the level progression, it does this by calling levelsByLesson() which maps each level via levelWithProgress():

lessons.map(lesson =>
lesson.levels.map(level => {
let statusLevel = levelWithProgress(
{levelResults, unitProgress, levelPairing, currentLevelId},
level,
lesson.lockable
);
return statusLevel;
})
);

Which always expands the level data via that aforementioned processedLevel method:

const levelWithProgress = (
{levelResults, unitProgress, levelPairing = {}, currentLevelId},
level,
isLockable,
parentLevelId
) => {
const normalizedLevel = processedLevel(level, parentLevelId);

So, if you have level data (like the test data being used in progressReduxTest.js) that is [{kind: 'unplugged', title: 'Unplugged'}, {title: 1}, {title: 2}] you'll get a levels array that is: [{levelNumber: undefined}, {levelNumber: 1}, {levelNumber: 2}] which then breaks the logic because if you are on the second level which has levelNumber equal to 1, it will decide that the current level index is 0 (not 1) and advance to level "index" 1, which is the level it is already on.

I don't know why this isn't a problem elsewhere. Probably because the test data is not very representative? It says that the test data is real data, though. But I would assume that the title is somehow accurate to level "index" in real-life situations.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for the walk-through! So levelNumber can sometimes NOT be a number and sometimes a string like for the bracelet level!
Screenshot 2025-12-18 at 2 44 21 PM

And yes, it does seem like for most levels, title is the same as level 'index'.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, it would be the same as the level index + 1, but yes. In the test data, it would render in the progress bubbles as (Unplugged Activity) (1) (2) which would break the expected behavior. This is a tangent of the original intention of this task... but to add tests, I needed to fix it to work for the test case. Overall, it breaking in the test case makes me nervous regardless. Let's see if I can prove that this never actually happens in the wild.

Copy link
Contributor Author

@wilkie wilkie Dec 19, 2025

Choose a reason for hiding this comment

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

In practice, title is not a real field. It is computed relative to position:

title: level_display_text,

def level_display_text
if level.unplugged?
I18n.t('unplugged_activity')
elsif lesson.unplugged_lesson?
position - 1
else
position
end
end

It does seem like a lesson with an unplugged activity as level 1 would break the progression logic for the continue button on any level in that lesson... if the subsequent levels have their positions be 2, 3, 4, etc? title is not a very good representation of levelNumber in this case... or the logic to produce the levels array should ignore the unplugged levels (where levelNumber === undefined).

Searching for this:

[development] dashboard > ls=Lesson.all.filter{|lesson| lesson.unplugged_lesson?}
[development] dashboard > ls_of_interest=ls.filter{|lesson| lesson.script_levels.second&.level_display_text == 1}
[development] dashboard > ls_of_interest.map(&:script).map(&:name).uniq
=> ["AlgebraB", "algebra", "course1", "course2", "course3", "k5concepts"]

So all deprecated courses and k5concepts which doesn't use the progression logic.

It just so happens we used course3 as the test example for the progress redux when it's the rare example of a course that breaks the progress logic. Neat.

Copy link
Contributor

@fisher-alice fisher-alice Jan 5, 2026

Choose a reason for hiding this comment

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

TIL the difference between an unplugged level and an external level displayed as an unplugged activity. Also learned more about levelNumber and title and see that the Unplugged Activity bubble label is actually set by whether level.isUnplugged is true or not in BubbleFactory.jsx (not by levelNumber).

Nice investigation! I see that lesson.unplugged_lesson? only returns true if the first level (or oldest active level) is a true unplugged level:

def unplugged_lesson?
script_level = get_script_level_by_id
return false if script_level.blank?
script_level.oldest_active_level.unplugged?
end

So it makes sense that the title or level_display_text in modern courses including those with unplugged activities are assigned position (and not position - 1).
Here’s an example of a modern course with an unplugged activity as the first level and the second level as bubble 2. ​​https://studio.code.org/courses/coursea-2025/units/1/lessons/11/levels/1
Screenshot 2026-01-05 at 4 56 25 PM
Screenshot 2026-01-05 at 4 56 33 PM

As you found, only deprecated courses uses the position - 1 for title. You can see the second level has bubble 1 in the screenshot the deprecated course 3 lesson 1 progression.

Screenshot 2026-01-05 at 4 40 04 PM Screenshot 2026-01-05 at 4 40 13 PM

I wonder if maybe in a follow-up we should replace the test data in progressReduxTest with more modern course data ('coursea' instead of 'course3')?

Learned a ton - thanks Wilkie!


const currentLevelIndex = levels.findIndex(
level => level.levelNumber === currentLevelNumber
);
Copy link
Contributor Author

@wilkie wilkie Dec 17, 2025

Choose a reason for hiding this comment

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

The logic to come up with the index into the levels array was updated since when I wrote the unit tests, the test data actually doesn't work with the old logic. The test data has an unplugged activity at the beginning of the lesson (which is actually somewhat typical) and this gets index 0 with the lesson after it having title: 1, which made it levelNumber: 1. The old logic always assumed that levelNumber was 1 greater than that level's index into the levels array (so it believed it would be entry 0, but it's not... it's entry 1). The old logic had the test lesson's level 1 going to itself as the next level in a loop.

This logic still works for the cases that fit that assumption but now also work for other bizarre cases where levels without numbers might appear. Although I considered finding currentLevelNumber + 1, this would not handle level progressions where the levelNumbers weren't perfectly ordered and wouldn't match existing behavior. Old and new behaviors will still go to an unnumbered level if that still gets placed into the levels array via our lesson processing logic.

});
});

describe('nextLevelId', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: add unit test for when on the last level of a progression.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's not a nit! That's an actual omission.

});
});

describe('getParentLevel', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

thank you for adding these unit tests! 🎉

);
currentLevelIndex = parentLevel.levelNumber - 1;
// So, we just consider the 'current' level the current parent
currentLevelNumber = parentLevel.levelNumber;
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice update!

@wilkie wilkie merged commit a0c878f into staging Jan 8, 2026
6 checks passed
@wilkie wilkie deleted the labs/update-continue-button-text branch January 8, 2026 18:23
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.

4 participants