@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
@@ -17,7 +17,7 @@ export function renderInterviewPrep() {
17
17
  createBuilder({
18
18
  title: "Interview Prep",
19
19
  description:
20
- "Select a discipline, track, and grade to generate tailored interview questions " +
20
+ "Select a discipline, track, and level to generate tailored interview questions " +
21
21
  "based on the role's skill requirements and expected behaviours.",
22
22
  formTitle: "Select Role",
23
23
  emptyPreviewText: "Select all three components to preview the interview.",
@@ -26,21 +26,21 @@ export function renderInterviewPrep() {
26
26
  prepareInterviewBuilderPreview({
27
27
  ...selection,
28
28
  behaviourCount: data.behaviours.length,
29
- grades: data.grades,
29
+ levels: data.levels,
30
30
  }),
31
31
  detailPath: (sel) =>
32
32
  sel.track
33
- ? `/interview/${sel.discipline}/${sel.grade}/${sel.track}`
34
- : `/interview/${sel.discipline}/${sel.grade}`,
33
+ ? `/interview/${sel.discipline}/${sel.level}/${sel.track}`
34
+ : `/interview/${sel.discipline}/${sel.level}`,
35
35
  renderPreview: createStandardPreview,
36
36
  helpItems: [
37
37
  {
38
38
  label: "Role Selection",
39
- text: "Choose a discipline, track, and grade to define the target role for the interview.",
39
+ text: "Choose a discipline, track, and level to define the target role for the interview.",
40
40
  },
41
41
  {
42
42
  label: "Skill Questions",
43
- text: "Questions are generated based on the required skill levels for the role.",
43
+ text: "Questions are generated based on the required skill proficiencies for the role.",
44
44
  },
45
45
  {
46
46
  label: "Behaviour Questions",
@@ -32,18 +32,18 @@ import {
32
32
  * @param {Object} params - Route params
33
33
  */
34
34
  export function renderInterviewDetail(params) {
35
- const { discipline: disciplineId, grade: gradeId, track: trackId } = params;
35
+ const { discipline: disciplineId, level: levelId, track: trackId } = params;
36
36
  const { data } = getState();
37
37
 
38
38
  // Find the components
39
39
  const discipline = data.disciplines.find((d) => d.id === disciplineId);
40
- const grade = data.grades.find((g) => g.id === gradeId);
40
+ const level = data.levels.find((g) => g.id === levelId);
41
41
  const track = trackId ? data.tracks.find((t) => t.id === trackId) : null;
42
42
 
43
- if (!discipline || !grade) {
43
+ if (!discipline || !level) {
44
44
  renderError({
45
45
  title: "Interview Not Found",
46
- message: "Invalid combination. Discipline or grade not found.",
46
+ message: "Invalid combination. Discipline or level not found.",
47
47
  backPath: "/interview-prep",
48
48
  backText: "← Back to Interview Prep",
49
49
  });
@@ -64,7 +64,7 @@ export function renderInterviewDetail(params) {
64
64
  // Use formatter shared module to get all interview types
65
65
  const interviewsView = prepareAllInterviews({
66
66
  discipline,
67
- grade,
67
+ level,
68
68
  track,
69
69
  skills: data.skills,
70
70
  behaviours: data.behaviours,
@@ -74,7 +74,7 @@ export function renderInterviewDetail(params) {
74
74
  if (!interviewsView) {
75
75
  renderError({
76
76
  title: "Invalid Combination",
77
- message: "This discipline, track, and grade combination is not valid.",
77
+ message: "This discipline, track, and level combination is not valid.",
78
78
  backPath: "/interview-prep",
79
79
  backText: "← Back to Interview Prep",
80
80
  });
@@ -100,8 +100,8 @@ export function renderInterviewDetail(params) {
100
100
  ),
101
101
  " × ",
102
102
  a(
103
- { href: `#/grade/${interviewsView.gradeId}` },
104
- interviewsView.gradeId,
103
+ { href: `#/level/${interviewsView.levelId}` },
104
+ interviewsView.levelId,
105
105
  ),
106
106
  " × ",
107
107
  a(
@@ -5,7 +5,7 @@
5
5
  import { render } from "../lib/render.js";
6
6
  import { getState } from "../lib/state.js";
7
7
  import { createBuilder, createStandardPreview } from "../components/builder.js";
8
- import { prepareJobBuilderPreview } from "@forwardimpact/libpathway/job";
8
+ import { prepareJobBuilderPreview } from "@forwardimpact/libskill/job";
9
9
 
10
10
  /**
11
11
  * Render job builder page
@@ -17,7 +17,7 @@ export function renderJobBuilder() {
17
17
  createBuilder({
18
18
  title: "Job Builder",
19
19
  description:
20
- "Combine a discipline, track, and grade to generate a complete job definition " +
20
+ "Combine a discipline, track, and level to generate a complete job definition " +
21
21
  "with skill matrix and behaviour profile.",
22
22
  formTitle: "Select Components",
23
23
  emptyPreviewText:
@@ -27,12 +27,12 @@ export function renderJobBuilder() {
27
27
  prepareJobBuilderPreview({
28
28
  ...selection,
29
29
  behaviourCount: data.behaviours.length,
30
- grades: data.grades,
30
+ levels: data.levels,
31
31
  }),
32
32
  detailPath: (sel) =>
33
33
  sel.track
34
- ? `/job/${sel.discipline}/${sel.grade}/${sel.track}`
35
- : `/job/${sel.discipline}/${sel.grade}`,
34
+ ? `/job/${sel.discipline}/${sel.level}/${sel.track}`
35
+ : `/job/${sel.discipline}/${sel.level}`,
36
36
  renderPreview: createStandardPreview,
37
37
  helpItems: [
38
38
  {
@@ -40,8 +40,8 @@ export function renderJobBuilder() {
40
40
  text: "Defines the T-shaped skill profile with primary, secondary, and broad skills.",
41
41
  },
42
42
  {
43
- label: "Grade",
44
- text: "Sets base skill levels and behaviour maturity expectations for career level.",
43
+ label: "Level",
44
+ text: "Sets base skill proficiencies and behaviour maturity expectations for career level.",
45
45
  },
46
46
  {
47
47
  label: "Track",
package/src/pages/job.js CHANGED
@@ -5,7 +5,7 @@
5
5
  import { render, div, p } from "../lib/render.js";
6
6
  import { getState } from "../lib/state.js";
7
7
  import { renderError } from "../components/error-page.js";
8
- import { prepareJobDetail } from "@forwardimpact/libpathway/job";
8
+ import { prepareJobDetail } from "@forwardimpact/libskill/job";
9
9
  import { jobToDOM } from "../formatters/job/dom.js";
10
10
 
11
11
  /** @type {string|null} Cached job template */
@@ -28,18 +28,18 @@ async function getJobTemplate() {
28
28
  * @param {Object} params - Route params
29
29
  */
30
30
  export async function renderJobDetail(params) {
31
- const { discipline: disciplineId, grade: gradeId, track: trackId } = params;
31
+ const { discipline: disciplineId, level: levelId, track: trackId } = params;
32
32
  const { data } = getState();
33
33
 
34
34
  // Find the components
35
35
  const discipline = data.disciplines.find((d) => d.id === disciplineId);
36
- const grade = data.grades.find((g) => g.id === gradeId);
36
+ const level = data.levels.find((g) => g.id === levelId);
37
37
  const track = trackId ? data.tracks.find((t) => t.id === trackId) : null;
38
38
 
39
- if (!discipline || !grade) {
39
+ if (!discipline || !level) {
40
40
  renderError({
41
41
  title: "Job Not Found",
42
- message: "Invalid job combination. Discipline or grade not found.",
42
+ message: "Invalid job combination. Discipline or level not found.",
43
43
  backPath: "/job-builder",
44
44
  backText: "← Back to Job Builder",
45
45
  });
@@ -60,7 +60,7 @@ export async function renderJobDetail(params) {
60
60
  // Use formatter shared module to get job detail view
61
61
  const jobView = prepareJobDetail({
62
62
  discipline,
63
- grade,
63
+ level,
64
64
  track,
65
65
  skills: data.skills,
66
66
  behaviours: data.behaviours,
@@ -72,7 +72,7 @@ export async function renderJobDetail(params) {
72
72
  if (!jobView) {
73
73
  renderError({
74
74
  title: "Invalid Combination",
75
- message: "This discipline, track, and grade combination is not valid.",
75
+ message: "This discipline, track, and level combination is not valid.",
76
76
  backPath: "/job-builder",
77
77
  backText: "← Back to Job Builder",
78
78
  });
@@ -89,6 +89,6 @@ export async function renderJobDetail(params) {
89
89
 
90
90
  // Load template and format
91
91
  const jobTemplate = await getJobTemplate();
92
- const page = jobToDOM(jobView, { discipline, grade, track, jobTemplate });
92
+ const page = jobToDOM(jobView, { discipline, level, track, jobTemplate });
93
93
  render(page);
94
94
  }
@@ -105,9 +105,9 @@ export function renderLanding() {
105
105
  href: "/discipline",
106
106
  }),
107
107
  createStatCard({
108
- value: data.grades.length,
109
- label: "Grades",
110
- href: "/grade",
108
+ value: data.levels.length,
109
+ label: "Levels",
110
+ href: "/level",
111
111
  }),
112
112
  createStatCard({
113
113
  value: data.tracks.length,
@@ -169,9 +169,9 @@ export function renderLanding() {
169
169
  "/discipline",
170
170
  ),
171
171
  createQuickLinkCard(
172
- `${getConceptEmoji(framework, "grade")} ${framework.entityDefinitions.grade.title}`,
173
- `${data.grades.length} ${framework.entityDefinitions.grade.title.toLowerCase()} — ${framework.entityDefinitions.grade.description.trim().split("\n")[0]}`,
174
- "/grade",
172
+ `${getConceptEmoji(framework, "level")} ${framework.entityDefinitions.level.title}`,
173
+ `${data.levels.length} ${framework.entityDefinitions.level.title.toLowerCase()} — ${framework.entityDefinitions.level.description.trim().split("\n")[0]}`,
174
+ "/level",
175
175
  ),
176
176
  createQuickLinkCard(
177
177
  `${getConceptEmoji(framework, "track")} ${framework.entityDefinitions.track.title}`,
@@ -220,7 +220,7 @@ export function renderLanding() {
220
220
  h2({}, "🔨 Build a Job Definition"),
221
221
  p(
222
222
  {},
223
- "Combine a discipline, track, and grade to generate a complete job definition " +
223
+ "Combine a discipline, track, and level to generate a complete job definition " +
224
224
  "with skill matrices and behaviour profiles.",
225
225
  ),
226
226
  div(
@@ -256,7 +256,7 @@ export function renderLanding() {
256
256
  h2({}, "📈 Plan Your Career"),
257
257
  p(
258
258
  {},
259
- "Visualize your progression to the next grade and compare expectations " +
259
+ "Visualize your progression to the next level and compare expectations " +
260
260
  "across different tracks to plan your career development.",
261
261
  ),
262
262
  div(
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Levels pages
3
+ */
4
+
5
+ import { render, div, h1, h3, p, formatLevel } from "../lib/render.js";
6
+ import { getState } from "../lib/state.js";
7
+ import { createBadge } from "../components/card.js";
8
+ import { renderNotFound } from "../components/error-page.js";
9
+ import { prepareLevelsList } from "../formatters/level/shared.js";
10
+ import { levelToDOM } from "../formatters/level/dom.js";
11
+ import { getConceptEmoji } from "@forwardimpact/map/levels";
12
+
13
+ /**
14
+ * Render levels list page
15
+ */
16
+ export function renderLevelsList() {
17
+ const { data } = getState();
18
+ const { framework } = data;
19
+ const levelEmoji = getConceptEmoji(framework, "level");
20
+
21
+ // Transform data for list view
22
+ const { items } = prepareLevelsList(data.levels);
23
+
24
+ const page = div(
25
+ { className: "levels-page" },
26
+ // Header
27
+ div(
28
+ { className: "page-header" },
29
+ h1(
30
+ { className: "page-title" },
31
+ `${levelEmoji} ${framework.entityDefinitions.level.title}`,
32
+ ),
33
+ p(
34
+ { className: "page-description" },
35
+ framework.entityDefinitions.level.description.trim(),
36
+ ),
37
+ ),
38
+
39
+ // Levels timeline
40
+ div(
41
+ { className: "levels-timeline" },
42
+ ...items.map((level) => createLevelTimelineItem(level)),
43
+ ),
44
+ );
45
+
46
+ render(page);
47
+ }
48
+
49
+ /**
50
+ * Create a level timeline item
51
+ * @param {Object} level
52
+ * @returns {HTMLElement}
53
+ */
54
+ function createLevelTimelineItem(level) {
55
+ const item = div(
56
+ { className: "level-timeline-item" },
57
+ div({ className: "level-level-marker" }, String(level.ordinalRank)),
58
+ div(
59
+ { className: "level-timeline-content card card-clickable" },
60
+ div(
61
+ { className: "card-header" },
62
+ h3({ className: "card-title" }, level.displayName),
63
+ createBadge(level.id, "default"),
64
+ ),
65
+ level.typicalExperienceRange
66
+ ? p(
67
+ { className: "text-muted", style: "margin: 0.25rem 0" },
68
+ `${level.typicalExperienceRange} experience`,
69
+ )
70
+ : null,
71
+ div(
72
+ { className: "card-meta", style: "margin-top: 0.5rem" },
73
+ createBadge(
74
+ `Primary: ${formatLevel(level.baseSkillProficiencies?.primary)}`,
75
+ "primary",
76
+ ),
77
+ createBadge(
78
+ `Secondary: ${formatLevel(level.baseSkillProficiencies?.secondary)}`,
79
+ "secondary",
80
+ ),
81
+ createBadge(
82
+ `Broad: ${formatLevel(level.baseSkillProficiencies?.broad)}`,
83
+ "broad",
84
+ ),
85
+ ),
86
+ level.scope
87
+ ? p(
88
+ { className: "card-description", style: "margin-top: 0.75rem" },
89
+ `Scope: ${level.scope}`,
90
+ )
91
+ : null,
92
+ ),
93
+ );
94
+
95
+ item.querySelector(".card").addEventListener("click", () => {
96
+ window.location.hash = `/level/${level.id}`;
97
+ });
98
+
99
+ return item;
100
+ }
101
+
102
+ /**
103
+ * Render level detail page
104
+ * @param {Object} params - Route params
105
+ */
106
+ export function renderLevelDetail(params) {
107
+ const { data } = getState();
108
+ const level = data.levels.find((g) => g.id === params.id);
109
+
110
+ if (!level) {
111
+ renderNotFound({
112
+ entityType: "Level",
113
+ entityId: params.id,
114
+ backPath: "/level",
115
+ backText: "← Back to Levels",
116
+ });
117
+ return;
118
+ }
119
+
120
+ // Use DOM formatter - it handles transformation internally
121
+ render(levelToDOM(level, { framework: data.framework }));
122
+ }
@@ -18,7 +18,7 @@ export function renderCareerProgress() {
18
18
  title: "Career Progress",
19
19
  description:
20
20
  "Select your current role to visualize career progression. See what skills and behaviours " +
21
- "change as you advance to the next grade, or compare expectations across different tracks.",
21
+ "change as you advance to the next level, or compare expectations across different tracks.",
22
22
  formTitle: "Select Your Current Role",
23
23
  emptyPreviewText:
24
24
  "Select all three components to preview progression paths.",
@@ -26,25 +26,25 @@ export function renderCareerProgress() {
26
26
  previewPresenter: (selection) =>
27
27
  prepareCareerProgressPreview({
28
28
  ...selection,
29
- grades: data.grades,
29
+ levels: data.levels,
30
30
  tracks: data.tracks,
31
31
  }),
32
32
  detailPath: (sel) =>
33
33
  sel.track
34
- ? `/progress/${sel.discipline}/${sel.grade}/${sel.track}`
35
- : `/progress/${sel.discipline}/${sel.grade}`,
34
+ ? `/progress/${sel.discipline}/${sel.level}/${sel.track}`
35
+ : `/progress/${sel.discipline}/${sel.level}`,
36
36
  renderPreview: createProgressPreview,
37
37
  labels: {
38
- grade: "Current Grade",
38
+ level: "Current Level",
39
39
  },
40
40
  helpItems: [
41
41
  {
42
- label: "📈 Grade Progression",
43
- text: "See exactly which skills and behaviours need to grow to advance to the next grade level.",
42
+ label: "📈 Level Progression",
43
+ text: "See exactly which skills and behaviours need to grow to advance to the next level rank.",
44
44
  },
45
45
  {
46
46
  label: "🔀 Track Comparison",
47
- text: "Compare how expectations differ across tracks at the same grade - useful for exploring lateral moves.",
47
+ text: "Compare how expectations differ across tracks at the same level - useful for exploring lateral moves.",
48
48
  },
49
49
  {
50
50
  label: "🎯 Development Focus",