@forwardimpact/pathway 0.21.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/README.md +3 -3
  2. package/bin/fit-pathway.js +22 -22
  3. package/package.json +4 -3
  4. package/src/commands/agent.js +14 -10
  5. package/src/commands/behaviour.js +11 -1
  6. package/src/commands/build.js +11 -2
  7. package/src/commands/command-factory.js +4 -2
  8. package/src/commands/dev.js +9 -2
  9. package/src/commands/discipline.js +19 -2
  10. package/src/commands/driver.js +11 -1
  11. package/src/commands/index.js +1 -1
  12. package/src/commands/init.js +1 -1
  13. package/src/commands/interview.js +8 -8
  14. package/src/commands/job.js +41 -28
  15. package/src/commands/level.js +76 -0
  16. package/src/commands/progress.js +20 -20
  17. package/src/commands/questions.js +3 -3
  18. package/src/commands/skill.js +11 -1
  19. package/src/commands/stage.js +11 -1
  20. package/src/commands/tool.js +4 -3
  21. package/src/commands/track.js +11 -1
  22. package/src/components/action-buttons.js +3 -3
  23. package/src/components/builder.js +25 -25
  24. package/src/components/card.js +8 -104
  25. package/src/components/comparison-radar.js +4 -4
  26. package/src/components/detail.js +18 -120
  27. package/src/components/error-page.js +8 -68
  28. package/src/components/grid.js +12 -106
  29. package/src/components/list.js +7 -116
  30. package/src/components/nav.js +7 -60
  31. package/src/components/radar-chart.js +3 -3
  32. package/src/components/skill-matrix.js +7 -7
  33. package/src/css/bundles/app.css +25 -21
  34. package/src/css/bundles/handout.css +33 -33
  35. package/src/css/bundles/slides.css +25 -25
  36. package/src/css/pages/landing.css +5 -5
  37. package/src/formatters/index.js +5 -5
  38. package/src/formatters/interview/shared.js +23 -23
  39. package/src/formatters/job/description.js +18 -18
  40. package/src/formatters/job/dom.js +12 -12
  41. package/src/formatters/job/markdown.js +7 -7
  42. package/src/formatters/json-ld.js +24 -24
  43. package/src/formatters/{grade → level}/dom.js +31 -27
  44. package/src/formatters/{grade → level}/markdown.js +19 -28
  45. package/src/formatters/{grade → level}/microdata.js +28 -38
  46. package/src/formatters/level/shared.js +86 -0
  47. package/src/formatters/progress/markdown.js +2 -2
  48. package/src/formatters/progress/shared.js +51 -51
  49. package/src/formatters/questions/markdown.js +8 -6
  50. package/src/formatters/questions/shared.js +7 -7
  51. package/src/formatters/skill/dom.js +4 -4
  52. package/src/formatters/skill/markdown.js +1 -1
  53. package/src/formatters/skill/microdata.js +3 -3
  54. package/src/formatters/skill/shared.js +3 -3
  55. package/src/formatters/track/shared.js +1 -1
  56. package/src/handout-main.js +12 -12
  57. package/src/handout.html +32 -13
  58. package/src/index.html +33 -14
  59. package/src/lib/card-mappers.js +16 -16
  60. package/src/lib/cli-command.js +3 -3
  61. package/src/lib/cli-output.js +2 -2
  62. package/src/lib/error-boundary.js +3 -66
  63. package/src/lib/errors.js +7 -45
  64. package/src/lib/job-cache.js +12 -12
  65. package/src/lib/markdown.js +2 -109
  66. package/src/lib/reactive.js +7 -73
  67. package/src/lib/render.js +53 -201
  68. package/src/lib/router-core.js +2 -156
  69. package/src/lib/router-pages.js +2 -11
  70. package/src/lib/router-slides.js +2 -197
  71. package/src/lib/state.js +16 -65
  72. package/src/lib/utils.js +3 -10
  73. package/src/lib/yaml-loader.js +22 -80
  74. package/src/main.js +10 -10
  75. package/src/pages/agent-builder.js +12 -12
  76. package/src/pages/assessment-results.js +28 -24
  77. package/src/pages/interview-builder.js +6 -6
  78. package/src/pages/interview.js +8 -8
  79. package/src/pages/job-builder.js +7 -7
  80. package/src/pages/job.js +8 -8
  81. package/src/pages/landing.js +8 -8
  82. package/src/pages/level.js +122 -0
  83. package/src/pages/progress-builder.js +8 -8
  84. package/src/pages/progress.js +74 -74
  85. package/src/pages/self-assessment.js +7 -7
  86. package/src/pages/skill.js +1 -1
  87. package/src/slide-main.js +23 -23
  88. package/src/slides/chapter.js +4 -4
  89. package/src/slides/index.js +11 -11
  90. package/src/slides/interview.js +2 -2
  91. package/src/slides/job.js +4 -4
  92. package/src/slides/level.js +32 -0
  93. package/src/slides/overview.js +10 -10
  94. package/src/slides/progress.js +13 -13
  95. package/src/slides.html +32 -13
  96. package/src/types.js +1 -1
  97. package/templates/job.template.md +2 -2
  98. package/src/commands/grade.js +0 -60
  99. package/src/css/base.css +0 -56
  100. package/src/css/components/badges.css +0 -232
  101. package/src/css/components/buttons.css +0 -101
  102. package/src/css/components/forms.css +0 -191
  103. package/src/css/components/layout.css +0 -218
  104. package/src/css/components/nav.css +0 -206
  105. package/src/css/components/progress.css +0 -166
  106. package/src/css/components/states.css +0 -82
  107. package/src/css/components/surfaces.css +0 -347
  108. package/src/css/components/tables.css +0 -362
  109. package/src/css/components/top-bar.css +0 -180
  110. package/src/css/components/typography.css +0 -121
  111. package/src/css/components/utilities.css +0 -41
  112. package/src/css/pages/detail.css +0 -119
  113. package/src/css/reset.css +0 -50
  114. package/src/css/tokens.css +0 -162
  115. package/src/css/views/handout.css +0 -30
  116. package/src/css/views/print.css +0 -634
  117. package/src/css/views/slide-animations.css +0 -113
  118. package/src/css/views/slide-base.css +0 -331
  119. package/src/css/views/slide-sections.css +0 -597
  120. package/src/css/views/slide-tables.css +0 -275
  121. package/src/formatters/grade/shared.js +0 -86
  122. package/src/pages/grade.js +0 -122
  123. package/src/slides/grade.js +0 -32
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Level CLI Command
3
+ *
4
+ * Handles level summary, listing, and detail display in the terminal.
5
+ *
6
+ * Usage:
7
+ * npx pathway level # Summary with stats
8
+ * npx pathway level --list # IDs only (for piping)
9
+ * npx pathway level <id> # Detail view
10
+ * npx pathway level --validate # Validation checks
11
+ */
12
+
13
+ import { createEntityCommand } from "./command-factory.js";
14
+ import { levelToMarkdown } from "../formatters/level/markdown.js";
15
+ import { formatTable } from "../lib/cli-output.js";
16
+ import { getConceptEmoji } from "@forwardimpact/map/levels";
17
+ import { capitalize } from "../formatters/shared.js";
18
+
19
+ /**
20
+ * Format level list item for --list output
21
+ * @param {Object} level - Level entity
22
+ * @returns {string} Formatted list line
23
+ */
24
+ function formatListItem(level) {
25
+ return `${level.id}, ${level.professionalTitle || level.id}, ${level.managementTitle || level.id}`;
26
+ }
27
+
28
+ /**
29
+ * Format level summary output
30
+ * @param {Array} levels - Raw level entities
31
+ * @param {Object} data - Full data context
32
+ */
33
+ function formatSummary(levels, data) {
34
+ const { framework } = data;
35
+ const emoji = framework ? getConceptEmoji(framework, "level") : "📊";
36
+
37
+ console.log(`\n${emoji} Levels\n`);
38
+
39
+ const rows = levels.map((g) => [
40
+ g.id,
41
+ g.professionalTitle || g.displayName || g.id,
42
+ g.managementTitle || "-",
43
+ g.typicalExperienceRange || "-",
44
+ capitalize(g.baseSkillProficiencies?.primary || "-"),
45
+ ]);
46
+
47
+ console.log(
48
+ formatTable(
49
+ ["ID", "Professional Title", "Management Title", "Experience", "Primary Level"],
50
+ rows,
51
+ ),
52
+ );
53
+ console.log(`\nTotal: ${levels.length} levels`);
54
+ console.log(`\nRun 'npx pathway level --list' for IDs and titles`);
55
+ console.log(`Run 'npx pathway level <id>' for details\n`);
56
+ }
57
+
58
+ /**
59
+ * Format level detail output
60
+ * @param {Object} level - Raw level entity
61
+ * @param {Object} framework - Framework config
62
+ */
63
+ function formatDetail(level, framework) {
64
+ console.log(levelToMarkdown(level, framework));
65
+ }
66
+
67
+ export const runLevelCommand = createEntityCommand({
68
+ entityName: "level",
69
+ pluralName: "levels",
70
+ findEntity: (data, id) => data.levels.find((g) => g.id === id),
71
+ presentDetail: (entity) => entity,
72
+ formatSummary,
73
+ formatDetail,
74
+ formatListItem,
75
+ emojiIcon: "📊",
76
+ });
@@ -4,15 +4,15 @@
4
4
  * Shows career progression analysis in the terminal.
5
5
  *
6
6
  * Usage:
7
- * npx pathway progress <discipline> <grade> # Progress for trackless job
8
- * npx pathway progress <discipline> <grade> --track=<track> # Progress with track
9
- * npx pathway progress <discipline> <from_grade> --compare=<to_grade> # Compare grades
7
+ * npx pathway progress <discipline> <level> # Progress for trackless job
8
+ * npx pathway progress <discipline> <level> --track=<track> # Progress with track
9
+ * npx pathway progress <discipline> <from_level> --compare=<to_level> # Compare levels
10
10
  */
11
11
 
12
12
  import { createCompositeCommand } from "./command-factory.js";
13
13
  import {
14
14
  prepareProgressDetail,
15
- getDefaultTargetGrade,
15
+ getDefaultTargetLevel,
16
16
  } from "../formatters/progress/shared.js";
17
17
  import { progressToMarkdown } from "../formatters/progress/markdown.js";
18
18
 
@@ -26,53 +26,53 @@ function formatProgress(view) {
26
26
 
27
27
  export const runProgressCommand = createCompositeCommand({
28
28
  commandName: "progress",
29
- requiredArgs: ["discipline_id", "grade_id"],
29
+ requiredArgs: ["discipline_id", "level_id"],
30
30
  findEntities: (data, args, options) => {
31
31
  const discipline = data.disciplines.find((d) => d.id === args[0]);
32
- const grade = data.grades.find((g) => g.id === args[1]);
32
+ const level = data.levels.find((g) => g.id === args[1]);
33
33
  const track = options.track
34
34
  ? data.tracks.find((t) => t.id === options.track)
35
35
  : null;
36
36
 
37
- let targetGrade;
37
+ let targetLevel;
38
38
  if (options.compare) {
39
- targetGrade = data.grades.find((g) => g.id === options.compare);
40
- if (!targetGrade) {
41
- console.error(`Target grade not found: ${options.compare}`);
39
+ targetLevel = data.levels.find((g) => g.id === options.compare);
40
+ if (!targetLevel) {
41
+ console.error(`Target level not found: ${options.compare}`);
42
42
  process.exit(1);
43
43
  }
44
44
  } else {
45
- targetGrade = getDefaultTargetGrade(grade, data.grades);
46
- if (!targetGrade) {
47
- console.error("No next grade available for progression.");
45
+ targetLevel = getDefaultTargetLevel(level, data.levels);
46
+ if (!targetLevel) {
47
+ console.error("No next level available for progression.");
48
48
  process.exit(1);
49
49
  }
50
50
  }
51
51
 
52
- return { discipline, grade, track, targetGrade };
52
+ return { discipline, level, track, targetLevel };
53
53
  },
54
54
  validateEntities: (entities, _data, options) => {
55
55
  if (!entities.discipline) {
56
56
  return `Discipline not found`;
57
57
  }
58
- if (!entities.grade) {
59
- return `Grade not found`;
58
+ if (!entities.level) {
59
+ return `Level not found`;
60
60
  }
61
61
  if (options.track && !entities.track) {
62
62
  return `Track not found: ${options.track}`;
63
63
  }
64
- if (!entities.targetGrade) {
65
- return `Target grade not found`;
64
+ if (!entities.targetLevel) {
65
+ return `Target level not found`;
66
66
  }
67
67
  return null;
68
68
  },
69
69
  presenter: (entities, data) =>
70
70
  prepareProgressDetail({
71
71
  fromDiscipline: entities.discipline,
72
- fromGrade: entities.grade,
72
+ fromLevel: entities.level,
73
73
  fromTrack: entities.track,
74
74
  toDiscipline: entities.discipline,
75
- toGrade: entities.targetGrade,
75
+ toLevel: entities.targetLevel,
76
76
  toTrack: entities.track,
77
77
  skills: data.skills,
78
78
  behaviours: data.behaviours,
@@ -49,7 +49,7 @@ function showQuestionsSummary(data) {
49
49
  console.log(`\n❓ Questions\n`);
50
50
 
51
51
  // Skill questions by level
52
- const skillLevels = [
52
+ const skillProficiencies = [
53
53
  "awareness",
54
54
  "foundational",
55
55
  "working",
@@ -57,10 +57,10 @@ function showQuestionsSummary(data) {
57
57
  "expert",
58
58
  ];
59
59
  const roleTypes = ["professionalQuestions", "managementQuestions"];
60
- const skillRows = skillLevels.map((level) => {
60
+ const skillRows = skillProficiencies.map((level) => {
61
61
  let count = 0;
62
62
  for (const skill of skills) {
63
- const sq = questions.skillLevels?.[skill.id];
63
+ const sq = questions.skillProficiencies?.[skill.id];
64
64
  if (sq) {
65
65
  for (const roleType of roleTypes) {
66
66
  count += (sq[roleType]?.[level] || []).length;
@@ -16,7 +16,7 @@ import { skillToMarkdown } from "../formatters/skill/markdown.js";
16
16
  import { prepareSkillsList } from "../formatters/skill/shared.js";
17
17
  import { getConceptEmoji } from "@forwardimpact/map/levels";
18
18
  import { formatTable, formatError } from "../lib/cli-output.js";
19
- import { generateSkillMarkdown } from "@forwardimpact/libpathway/agent";
19
+ import { generateSkillMarkdown } from "@forwardimpact/libskill/agent";
20
20
  import { formatAgentSkill } from "../formatters/agent/skill.js";
21
21
  import { loadSkillTemplate } from "../lib/template-loader.js";
22
22
 
@@ -85,6 +85,15 @@ async function formatAgentDetail(skill, stages, dataDir) {
85
85
  console.log(output);
86
86
  }
87
87
 
88
+ /**
89
+ * Format skill list item for --list output
90
+ * @param {Object} skill - Skill entity
91
+ * @returns {string} Formatted list line
92
+ */
93
+ function formatListItem(skill) {
94
+ return `${skill.id}, ${skill.name}, ${skill.capability || "-"}`;
95
+ }
96
+
88
97
  const baseSkillCommand = createEntityCommand({
89
98
  entityName: "skill",
90
99
  pluralName: "skills",
@@ -98,6 +107,7 @@ const baseSkillCommand = createEntityCommand({
98
107
  }),
99
108
  formatSummary,
100
109
  formatDetail,
110
+ formatListItem,
101
111
  emojiIcon: "📚",
102
112
  });
103
113
 
@@ -21,6 +21,15 @@ import {
21
21
  formatBullet,
22
22
  } from "../lib/cli-output.js";
23
23
 
24
+ /**
25
+ * Format stage list item for --list output
26
+ * @param {Object} stage - Stage entity
27
+ * @returns {string} Formatted list line
28
+ */
29
+ function formatListItem(stage) {
30
+ return `${stage.id}, ${stage.name}`;
31
+ }
32
+
24
33
  /**
25
34
  * Format stage summary output
26
35
  * @param {Array} stages - Raw stage entities
@@ -43,7 +52,7 @@ function formatSummary(stages, _data) {
43
52
 
44
53
  console.log(formatTable(["ID", "Name", "Mode", "Tools", "Handoffs"], rows));
45
54
  console.log(`\nTotal: ${stages.length} stages`);
46
- console.log(`\nRun 'npx pathway stage --list' for IDs`);
55
+ console.log(`\nRun 'npx pathway stage --list' for IDs and names`);
47
56
  console.log(`Run 'npx pathway stage <id>' for details\n`);
48
57
  }
49
58
 
@@ -115,5 +124,6 @@ export const runStageCommand = createEntityCommand({
115
124
  }),
116
125
  formatSummary,
117
126
  formatDetail,
127
+ formatListItem,
118
128
  emojiIcon: "🔄",
119
129
  });
@@ -9,6 +9,7 @@
9
9
  * npx pathway tool <name> # Detail view for specific tool
10
10
  */
11
11
 
12
+ import { truncate } from "../formatters/shared.js";
12
13
  import { prepareToolsList } from "../formatters/tool/shared.js";
13
14
  import {
14
15
  formatTable,
@@ -27,10 +28,10 @@ export async function runToolCommand({ data, args, options }) {
27
28
  const [name] = args;
28
29
  const { tools, totalCount } = prepareToolsList(data.skills);
29
30
 
30
- // --list: Output clean newline-separated tool names for piping
31
+ // --list: Output descriptive comma-separated tool lines for piping
31
32
  if (options.list) {
32
33
  for (const tool of tools) {
33
- console.log(tool.name);
34
+ console.log(`${tool.name}, ${truncate(tool.description, 60)}`);
34
35
  }
35
36
  return;
36
37
  }
@@ -87,7 +88,7 @@ function formatSummary(tools, totalCount) {
87
88
  if (sorted.length > 15) {
88
89
  console.log(`(showing top 15 by usage)`);
89
90
  }
90
- console.log(`\nRun 'npx pathway tool --list' for all tool names`);
91
+ console.log(`\nRun 'npx pathway tool --list' for all tool names and descriptions`);
91
92
  console.log(`Run 'npx pathway tool <name>' for details\n`);
92
93
  }
93
94
 
@@ -16,6 +16,15 @@ import { sortTracksByName } from "../formatters/track/shared.js";
16
16
  import { formatTable } from "../lib/cli-output.js";
17
17
  import { getConceptEmoji } from "@forwardimpact/map/levels";
18
18
 
19
+ /**
20
+ * Format track list item for --list output
21
+ * @param {Object} track - Track entity
22
+ * @returns {string} Formatted list line
23
+ */
24
+ function formatListItem(track) {
25
+ return `${track.id}, ${track.name}`;
26
+ }
27
+
19
28
  /**
20
29
  * Format track summary output
21
30
  * @param {Array} tracks - Raw track entities
@@ -34,7 +43,7 @@ function formatSummary(tracks, data) {
34
43
 
35
44
  console.log(formatTable(["ID", "Name", "Modifiers"], rows));
36
45
  console.log(`\nTotal: ${tracks.length} tracks`);
37
- console.log(`\nRun 'npx pathway track --list' for IDs`);
46
+ console.log(`\nRun 'npx pathway track --list' for IDs and names`);
38
47
  console.log(`Run 'npx pathway track <id>' for details\n`);
39
48
  }
40
49
 
@@ -63,5 +72,6 @@ export const runTrackCommand = createEntityCommand({
63
72
  sortItems: sortTracksByName,
64
73
  formatSummary,
65
74
  formatDetail,
75
+ formatListItem,
66
76
  emojiIcon: "🛤️",
67
77
  });
@@ -30,7 +30,7 @@ export function createNavButton({ label, href, variant = "primary" }) {
30
30
  /**
31
31
  * Create a button to navigate to job builder with a parameter
32
32
  * @param {Object} options - Configuration options
33
- * @param {string} options.paramName - Parameter name (discipline, grade, track)
33
+ * @param {string} options.paramName - Parameter name (discipline, level, track)
34
34
  * @param {string} options.paramValue - Parameter value (the ID)
35
35
  * @param {string} [options.label] - Optional custom label
36
36
  * @returns {HTMLElement}
@@ -38,7 +38,7 @@ export function createNavButton({ label, href, variant = "primary" }) {
38
38
  export function createJobBuilderButton({ paramName, paramValue, label }) {
39
39
  const defaultLabels = {
40
40
  discipline: "Build Job with this Discipline →",
41
- grade: "Build Job at this Grade →",
41
+ level: "Build Job at this Level →",
42
42
  track: "Build Job with this Track →",
43
43
  };
44
44
 
@@ -52,7 +52,7 @@ export function createJobBuilderButton({ paramName, paramValue, label }) {
52
52
  /**
53
53
  * Create a button to navigate to interview prep with a parameter
54
54
  * @param {Object} options - Configuration options
55
- * @param {string} options.paramName - Parameter name (discipline, grade, track)
55
+ * @param {string} options.paramName - Parameter name (discipline, level, track)
56
56
  * @param {string} options.paramValue - Parameter value (the ID)
57
57
  * @param {string} [options.label] - Optional custom label
58
58
  * @returns {HTMLElement}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Builder component for discipline/grade/track selection pages
2
+ * Builder component for discipline/level/track selection pages
3
3
  */
4
4
 
5
5
  import {
@@ -32,7 +32,7 @@ import { createReactive } from "../lib/reactive.js";
32
32
  /**
33
33
  * @typedef {Object} BuilderSelection
34
34
  * @property {Object} discipline - Selected discipline
35
- * @property {Object} grade - Selected grade
35
+ * @property {Object} level - Selected level
36
36
  * @property {Object} track - Selected track
37
37
  */
38
38
 
@@ -78,10 +78,10 @@ export function createBuilder({
78
78
  const selection = createReactive({
79
79
  discipline: urlParams.get("discipline") || "",
80
80
  track: urlParams.get("track") || "",
81
- grade: urlParams.get("grade") || "",
81
+ level: urlParams.get("level") || "",
82
82
  });
83
83
 
84
- const sortedGrades = [...data.grades].sort((a, b) => a.level - b.level);
84
+ const sortedLevels = [...data.levels].sort((a, b) => a.level - b.level);
85
85
 
86
86
  // Create elements that need references
87
87
  const previewContainer = div(
@@ -155,9 +155,9 @@ export function createBuilder({
155
155
  }
156
156
 
157
157
  // Subscribe to selection changes - all updates happen here
158
- selection.subscribe(({ discipline, track, grade }) => {
159
- // Track is now optional - only discipline and grade are required
160
- if (!discipline || !grade) {
158
+ selection.subscribe(({ discipline, track, level }) => {
159
+ // Track is now optional - only discipline and level are required
160
+ if (!discipline || !level) {
161
161
  previewContainer.innerHTML = "";
162
162
  previewContainer.appendChild(
163
163
  p({ className: "text-muted" }, emptyPreviewText),
@@ -168,9 +168,9 @@ export function createBuilder({
168
168
 
169
169
  const disciplineObj = data.disciplines.find((d) => d.id === discipline);
170
170
  const trackObj = track ? data.tracks.find((t) => t.id === track) : null;
171
- const gradeObj = data.grades.find((g) => g.id === grade);
171
+ const levelObj = data.levels.find((g) => g.id === level);
172
172
 
173
- if (!disciplineObj || !gradeObj) {
173
+ if (!disciplineObj || !levelObj) {
174
174
  previewContainer.innerHTML = "";
175
175
  previewContainer.appendChild(
176
176
  p({ className: "text-muted" }, "Invalid selection. Please try again."),
@@ -182,7 +182,7 @@ export function createBuilder({
182
182
  const selectionObj = {
183
183
  discipline: disciplineObj,
184
184
  track: trackObj,
185
- grade: gradeObj,
185
+ level: levelObj,
186
186
  };
187
187
  const preview = previewPresenter(selectionObj, data);
188
188
 
@@ -193,8 +193,8 @@ export function createBuilder({
193
193
 
194
194
  // Wire up button
195
195
  actionButton.addEventListener("click", () => {
196
- const { discipline, track, grade } = selection.get();
197
- window.location.hash = detailPath({ discipline, track, grade });
196
+ const { discipline, track, level } = selection.get();
197
+ window.location.hash = detailPath({ discipline, track, level });
198
198
  });
199
199
 
200
200
  // Build the page
@@ -234,17 +234,17 @@ export function createBuilder({
234
234
  getDisplayName: (d) => d.specialization || d.name,
235
235
  }),
236
236
  ),
237
- // Grade selector (second)
237
+ // Level selector (second)
238
238
  div(
239
239
  { className: "form-group" },
240
- label({ className: "form-label" }, labels.grade || "Grade"),
240
+ label({ className: "form-label" }, labels.level || "Level"),
241
241
  createSelectWithValue({
242
- id: "grade-select",
243
- items: sortedGrades,
244
- initialValue: selection.get().grade,
245
- placeholder: "Select a grade...",
242
+ id: "level-select",
243
+ items: sortedLevels,
244
+ initialValue: selection.get().level,
245
+ placeholder: "Select a level...",
246
246
  onChange: (value) => {
247
- selection.update((prev) => ({ ...prev, grade: value }));
247
+ selection.update((prev) => ({ ...prev, level: value }));
248
248
  },
249
249
  getDisplayName: (g) => g.id,
250
250
  }),
@@ -284,7 +284,7 @@ export function createBuilder({
284
284
 
285
285
  // Trigger initial update if preselected
286
286
  const initial = selection.get();
287
- if (initial.discipline || initial.track || initial.grade) {
287
+ if (initial.discipline || initial.track || initial.level) {
288
288
  setTimeout(() => selection.set(selection.get()), 0);
289
289
  }
290
290
 
@@ -370,12 +370,12 @@ export function createProgressPreview(preview, selection) {
370
370
  );
371
371
  }
372
372
 
373
- const { discipline, grade, track } = selection;
373
+ const { discipline, level, track } = selection;
374
374
 
375
375
  // Build badges array - track is optional
376
376
  const badges = [
377
377
  createBadge(discipline.specialization, "discipline"),
378
- createBadge(grade.id, "grade"),
378
+ createBadge(level.id, "level"),
379
379
  ];
380
380
  if (track) {
381
381
  badges.push(createBadge(track.name, "track"));
@@ -394,19 +394,19 @@ export function createProgressPreview(preview, selection) {
394
394
  div({ className: "preview-label" }, "Progression Paths Available"),
395
395
  div(
396
396
  { className: "preview-paths" },
397
- preview.nextGrade
397
+ preview.nextLevel
398
398
  ? div(
399
399
  { className: "path-item" },
400
400
  span({ className: "path-icon" }, "📈"),
401
401
  span(
402
402
  {},
403
- `Next Grade: ${preview.nextGrade.id} - ${preview.nextGrade.name}`,
403
+ `Next Level: ${preview.nextLevel.id} - ${preview.nextLevel.name}`,
404
404
  ),
405
405
  )
406
406
  : div(
407
407
  { className: "path-item text-muted" },
408
408
  span({ className: "path-icon" }, "🏆"),
409
- span({}, "You're at the highest grade!"),
409
+ span({}, "You're at the highest level!"),
410
410
  ),
411
411
  preview.validTracks.length > 0
412
412
  ? div(
@@ -1,108 +1,12 @@
1
1
  /**
2
2
  * Reusable card component
3
+ *
4
+ * Re-exports from @forwardimpact/libui/components/card.
3
5
  */
4
6
 
5
- import { div, h3, p, span } from "../lib/render.js";
6
-
7
- /**
8
- * Create a card component
9
- * @param {Object} options
10
- * @param {string} options.title - Card title
11
- * @param {string} [options.description] - Card description
12
- * @param {string} [options.href] - Link destination (makes card clickable)
13
- * @param {HTMLElement[]} [options.badges] - Badges to display
14
- * @param {HTMLElement[]} [options.meta] - Meta information
15
- * @param {HTMLElement} [options.content] - Additional content
16
- * @param {HTMLElement} [options.icon] - Icon element to display
17
- * @param {string} [options.className] - Additional CSS class
18
- * @returns {HTMLElement}
19
- */
20
- export function createCard({
21
- title,
22
- description,
23
- href,
24
- badges = [],
25
- meta = [],
26
- content,
27
- icon,
28
- className = "",
29
- }) {
30
- const isClickable = !!href;
31
-
32
- const titleContent = icon
33
- ? div(
34
- { className: "card-title-with-icon" },
35
- icon,
36
- h3({ className: "card-title" }, title),
37
- )
38
- : h3({ className: "card-title" }, title);
39
-
40
- const cardHeader = div(
41
- { className: "card-header" },
42
- titleContent,
43
- badges.length > 0 ? div({ className: "card-badges" }, ...badges) : null,
44
- );
45
-
46
- const card = div(
47
- {
48
- className:
49
- `card ${isClickable ? "card-clickable" : ""} ${className}`.trim(),
50
- },
51
- cardHeader,
52
- description ? p({ className: "card-description" }, description) : null,
53
- content || null,
54
- meta.length > 0 ? div({ className: "card-meta" }, ...meta) : null,
55
- );
56
-
57
- if (isClickable) {
58
- card.addEventListener("click", () => {
59
- window.location.hash = href;
60
- });
61
- }
62
-
63
- return card;
64
- }
65
-
66
- /**
67
- * Create a stat card for the landing page
68
- * @param {Object} options
69
- * @param {number|string} options.value - The stat value
70
- * @param {string} options.label - The stat label
71
- * @param {string} [options.href] - Optional link
72
- * @returns {HTMLElement}
73
- */
74
- export function createStatCard({ value, label, href }) {
75
- const card = div(
76
- { className: "stat-card" },
77
- div({ className: "stat-value" }, String(value)),
78
- div({ className: "stat-label" }, label),
79
- );
80
-
81
- if (href) {
82
- card.style.cursor = "pointer";
83
- card.addEventListener("click", () => {
84
- window.location.hash = href;
85
- });
86
- }
87
-
88
- return card;
89
- }
90
-
91
- /**
92
- * Create a badge element
93
- * @param {string} text - Badge text
94
- * @param {string} [type] - Badge type (default, primary, secondary, broad, technical, ai, etc.)
95
- * @returns {HTMLElement}
96
- */
97
- export function createBadge(text, type = "default") {
98
- return span({ className: `badge badge-${type}` }, text);
99
- }
100
-
101
- /**
102
- * Create a tag element
103
- * @param {string} text
104
- * @returns {HTMLElement}
105
- */
106
- export function createTag(text) {
107
- return span({ className: "info-tag" }, text);
108
- }
7
+ export {
8
+ createCard,
9
+ createStatCard,
10
+ createBadge,
11
+ createTag,
12
+ } from "@forwardimpact/libui/components/card";
@@ -9,11 +9,11 @@
9
9
  import { ComparisonRadarChart } from "../lib/radar.js";
10
10
  import { div, h3 } from "../lib/render.js";
11
11
  import {
12
- getSkillLevelIndex,
12
+ getSkillProficiencyIndex,
13
13
  getBehaviourMaturityIndex,
14
14
  formatLevel,
15
15
  } from "../lib/render.js";
16
- import { compareByCapability } from "@forwardimpact/libpathway/policies";
16
+ import { compareByCapability } from "@forwardimpact/libskill/policies";
17
17
 
18
18
  /**
19
19
  * Create a comparison skill radar chart
@@ -97,7 +97,7 @@ export function createComparisonSkillRadar(
97
97
 
98
98
  currentData.push({
99
99
  label: skillName,
100
- value: currentSkill ? getSkillLevelIndex(currentSkill.level) : 0,
100
+ value: currentSkill ? getSkillProficiencyIndex(currentSkill.level) : 0,
101
101
  maxValue: 5,
102
102
  description: currentSkill
103
103
  ? `${formatLevel(currentSkill.type)} - ${formatLevel(currentSkill.level)}`
@@ -106,7 +106,7 @@ export function createComparisonSkillRadar(
106
106
 
107
107
  targetData.push({
108
108
  label: skillName,
109
- value: targetSkill ? getSkillLevelIndex(targetSkill.level) : 0,
109
+ value: targetSkill ? getSkillProficiencyIndex(targetSkill.level) : 0,
110
110
  maxValue: 5,
111
111
  description: targetSkill
112
112
  ? `${formatLevel(targetSkill.type)} - ${formatLevel(targetSkill.level)}`