fix(learning-path): assessment/Unit icon colors, Curriculum gating, real UUID codes for new Levels - #16
Open
Rakshitha-D wants to merge 9 commits into
Open
Conversation
…n, not quiz-green A linked Prior/Outcome/Level assessment course resolves to the 'quiz' content type (getCtStyle's isAssessmentCourse check) and rendered with the generic quiz green square in the Outline Tree — same color as any standalone QuestionSet elsewhere in the app, unrelated to the Level structure it's actually part of. Now uses the same terracotta .folderIcon square as the Level/root icons, matching the rest of the Learning Path's own iconography.
…m is picked adaptLpCurriculumFields synthesized Industry/Domain/... category fields from fw.organisationFramework?.categories, which can already be resolved (e.g. a channel default used to prime term lookups) even though the Curriculum (framework) selector itself has no value yet — so an author saw empty, required category dropdowns before choosing a Curriculum at all, with nothing for them to cascade from. Now gates on the Curriculum selector's own currentValue: no category fields render until it's actually set. Updated the existing Curriculum tests to select a framework explicitly (they'd relied on the old gate-free behavior) and added a test for the hidden-until-selected state itself.
…emp- placeholder
addNode() reused the tree's own local placeholder id ('temp-' + random)
as metadata.code — a value that gets persisted to the backend as-is,
so a newly created Level's saved code literally read
'temp-a7l6ma5rb0q' instead of looking like any other content's code
(a plain UUID). The 'temp-' prefixed id itself stays unchanged (isNew
detection throughout useSaveHierarchy/useSaveQuestion/etc. depends on
identifier.startsWith('temp-')) — only metadata.code, the value that
actually reaches the backend, now gets its own crypto.randomUUID().
Added a vitest setup file polyfilling crypto for Vitest's node test
environment, which doesn't expose it the same way a real browser
does — surfaced by tree.store's own tests now exercising addNode's
crypto.randomUUID() call for the first time. Application code already
called crypto.randomUUID() elsewhere (src/api/asset.ts) untested;
same underlying gap, just not previously hit by a test.
.unitIcon in CourseDetailsPanel (a linked Course's Unit rows) had no color — plain gray background/icon, unlike every other folder-type icon in the app. Now uses the same --sbx-primary/--sbx-white pair as the outline tree's Level/root icons (.folderIcon), matching the design.
… content when it's first or last No persisted marker distinguishes a genuine Prior/Outcome Assessment slot from a Level that coincidentally ends up shaped like one (exactly one course, and that course entirely QuestionSets) — that decision is still open (see learning_path_assessment_slot_identity_plan.md). This adds an interim, no-new-metadata gate at the one place it can be enforced without guessing at intent: the generic 'add a course to a Level' flow. wouldBecomeAmbiguousSlot(rootLevels, levelNode) flags an EMPTY Level at index 0 or the last index — adding a single QuestionSet-only course there next would leave it indistinguishable from a real slot on the next reload. Scoped narrowly so it can't collide with the (unbuilt) Level Exam Course feature: a Level Exam Course paired with any other content in the same Level never triggers this, since a second course means children.length !== 1 regardless of shape. The dedicated Prior/Outcome picker (handleFillAssessmentSlot) is a separate flow already and is unaffected. LibraryDock.tsx's handleAdd is now async to run the course's own shape-check (getAssessmentCourseInfo) before letting an ambiguous add through; blocks with a toast rather than warning, matching how the rest of this validation surface (emptyLevel, courseMissingSkillTag, slot-filled errors) already blocks rather than warns.
Each content Level (not the pinned Prior/Outcome slots) can now
optionally have one Level Exam Course — a linked course whose only
content is a question set, used to determine the learner's outcome
for that specific Level. Optional: no required badge, no publish/
send-for-review gate. Per learning_path_level_exam_plan.md:
- ui.store.ts: activeLevelExamTarget (a Level id, or null), mutually
clearing with activeAssessmentSlot so only one 'add target mode' is
ever armed at a time.
- lpStructure.ts: getLevelExamCourse(level) — the at-most-one
assessment-flagged course directly on a content Level, unlike
isAssessmentLevel doesn't require it to be the Level's only child.
- useLevelExam.ts (new): course/filled/courseName/isActiveTarget +
armExam/deleteExam, scoped to one Level instead of root's pinned
pre/post positions. deleteExam confirms via the in-app ConfirmDialog
(openModal('confirmDelete', ...)), matching how Prior/Outcome
deletion already works — not window.confirm as originally drafted
in the plan, which predated that change.
- LevelExamItem.tsx (new): dashed 'Add Level Exam' row when empty;
icon + course name + meta + a single direct remove button when
filled — one action, not two, since a Level Exam course is a plain
leaf with no detail page of its own.
- UnitContentList.tsx: renders LevelExamItem for every content Level.
- LibraryDock.tsx: handleFillLevelExam mirrors handleFillAssessmentSlot
(question-set-only check via getAssessmentCourseInfo, one-per-Level
cap already enforced by canAddCourseToLevel) — plus an addition
beyond the original plan: it also runs the same
wouldBecomeAmbiguousSlot check the generic add-flow gate uses, so a
Level Exam course can't recreate the pre/post ambiguity by landing
as the sole content of an empty first/last Level. A Level Exam
paired with other content in the same Level never hits this either
way.
- useLibraryTargetLabel.ts: 'Add Level Exam course for {level}' hint
while armed.
- i18n: new keys across en/ar/fr/pt.
- lpStructure.test.ts: getLevelExamCourse coverage (mixed children,
none/no-level, and the not-solo-child distinction from
isAssessmentLevel).
Adding a Level Exam course (or Prior/Outcome) and a regular course
both go through the same single Library sidebar, with no visible
distinction beyond a small header hint — easy to lose track of which
mode you're in, especially once armed with no easy way back out.
- AssessmentSlotItem.tsx / LevelExamItem.tsx: the dashed 'Add...' row
now shows a persistent active/pressed style (border+background,
reusing .addRow's own :hover treatment) while its slot/exam target
is the one currently armed, not just on hover.
- LibraryDock.tsx: the header hint becomes a visible chip with a
dismiss (X) button while a slot/exam target is armed, instead of
plain text with no way to back out short of navigating away from
the Level. The generic 'Add to {level}'/'Open a level to add' hints
(not armed) are unaffected — still plain text.
- LibraryDock.tsx: while armed, the visible course list is narrowed to
QuestionSet-only courses (getAssessmentCourseInfo, client-side —
composite search can't filter on a course's expanded-leaf shape),
instead of showing every course and letting most picks bounce off
the 'isn't a question-set-only course' toast. Uses the existing
assessmentCourseCache, so repeat views cost nothing; 'Load More'
still paginates the underlying unfiltered search.
- i18n: cancelTargetAriaLabel across en/ar/fr/pt.
- The header chip is now the whole dismiss target — clicking it again unarms (matching the source row's own toggle below), instead of a separate nested X button. - AssessmentSlotItem.tsx / useLevelExam.ts's armExam: clicking the 'Add Prior/Outcome/Level Exam' row again while it's already the armed target now unarms it too, rather than being a one-way action with no way back except the header chip. - UnitContentList.tsx: added an explicit 'Add Course to this level' row before 'Add Level Exam', mirroring it visually. Regular course adds previously had no button of their own here — they just happened whenever nothing was armed — which gave no obvious way back to plain browsing once Level Exam mode was armed. Clicking it clears any armed slot/exam target; it has no 'on' state of its own to arm, so it's highlighted whenever neither Level Exam nor Prior/Outcome is armed, making the two options read as a clear either/or. - i18n: addCourseToLevelButton across en/ar/fr/pt.
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.
Summary
metadata.codeis now a realcrypto.randomUUID()instead of reusing the localtemp-<random>placeholder id, which was getting persisted to the backend as-is.learning_path_assessment_slot_identity_plan.mdfor the fuller design discussion (a persisted marker) that's still open.learning_path_level_exam_plan.md.Test plan
codeis a UUID, not atemp-string.