Skip to content

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
Sunbird-Knowlg:v1.0.3from
Rakshitha-D:learning_path_editor
Open

fix(learning-path): assessment/Unit icon colors, Curriculum gating, real UUID codes for new Levels#16
Rakshitha-D wants to merge 9 commits into
Sunbird-Knowlg:v1.0.3from
Rakshitha-D:learning_path_editor

Conversation

@Rakshitha-D

@Rakshitha-D Rakshitha-D commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Linked Prior/Outcome/Level assessment courses in the outline tree now use the same terracotta icon square as the Level/root icons, instead of the generic quiz-green.
  • The Curriculum (framework) category fields (Industry/Domain/...) no longer render until a Curriculum is actually picked — they were showing as empty, required dropdowns before any framework was selected.
  • A newly created Level's metadata.code is now a real crypto.randomUUID() instead of reusing the local temp-<random> placeholder id, which was getting persisted to the backend as-is.
  • The linked Course's Unit icon (in the Course details panel) now gets the same terracotta background as other folder-type icons, instead of a plain uncolored one.
  • Interim guard: adding a QuestionSet-only course as the only content of a first/last-positioned Level is now blocked, since that shape is indistinguishable from a genuine Prior/Outcome Assessment slot on the next reload. Scoped to the generic add flow only, so it doesn't collide with Level Exam Course — see learning_path_assessment_slot_identity_plan.md for the fuller design discussion (a persisted marker) that's still open.
  • New: Level Exam Course — 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 gate); the same ambiguous-slot guard above also applies here, so it can't recreate the pre/post ambiguity by landing as a Level's sole content. See learning_path_level_exam_plan.md.

Test plan

  • Verify assessment courses (Prior/Outcome/Level exam) show the terracotta icon in the outline tree, not green.
  • Verify Industry/Domain/etc. fields stay hidden until a Curriculum is selected, and appear correctly once one is picked.
  • Create a new Level, save, and confirm its persisted code is a UUID, not a temp- string.
  • Open a linked Course's details and confirm the Unit icon has a terracotta background.
  • Try adding a QuestionSet-only course as the sole course of an empty first or last Level — confirm it's blocked with a toast, and that adding it alongside another course (or into a middle Level) works normally.
  • On a content Level, add a Level Exam course (a question-set-only course) via the new "Add Level Exam" row — confirm it shows with a remove button, and that removing it works via the confirm dialog.
  • Confirm a second Level Exam course on the same Level is rejected, and that adding a Level Exam course as the sole content of an empty first/last Level is blocked (same guard as above).

…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.
@Rakshitha-D Rakshitha-D changed the title Learning path editor fix(learning-path): assessment/Unit icon colors, Curriculum gating, real UUID codes for new Levels Aug 13, 2026
… 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.
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.

1 participant