@forwardimpact/pathway 0.20.0 → 0.22.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 (96) hide show
  1. package/README.md +3 -3
  2. package/bin/fit-pathway.js +28 -28
  3. package/package.json +4 -4
  4. package/src/commands/agent.js +8 -8
  5. package/src/commands/build.js +7 -7
  6. package/src/commands/dev.js +7 -7
  7. package/src/commands/driver.js +1 -1
  8. package/src/commands/index.js +1 -1
  9. package/src/commands/init.js +1 -1
  10. package/src/commands/interview.js +8 -8
  11. package/src/commands/job.js +19 -19
  12. package/src/commands/level.js +60 -0
  13. package/src/commands/progress.js +20 -20
  14. package/src/commands/questions.js +3 -3
  15. package/src/commands/skill.js +1 -1
  16. package/src/commands/track.js +1 -1
  17. package/src/commands/update.js +12 -14
  18. package/src/components/action-buttons.js +3 -3
  19. package/src/components/builder.js +25 -25
  20. package/src/components/checklist.js +1 -1
  21. package/src/components/comparison-radar.js +3 -3
  22. package/src/components/detail.js +3 -3
  23. package/src/components/grid.js +1 -1
  24. package/src/components/radar-chart.js +3 -3
  25. package/src/components/skill-matrix.js +7 -7
  26. package/src/css/pages/landing.css +5 -5
  27. package/src/formatters/behaviour/dom.js +1 -1
  28. package/src/formatters/discipline/dom.js +1 -1
  29. package/src/formatters/driver/dom.js +1 -1
  30. package/src/formatters/index.js +5 -5
  31. package/src/formatters/interview/dom.js +1 -1
  32. package/src/formatters/interview/markdown.js +1 -1
  33. package/src/formatters/interview/shared.js +20 -20
  34. package/src/formatters/job/description.js +18 -18
  35. package/src/formatters/job/dom.js +12 -12
  36. package/src/formatters/job/markdown.js +7 -7
  37. package/src/formatters/json-ld.js +24 -24
  38. package/src/formatters/{grade → level}/dom.js +32 -28
  39. package/src/formatters/{grade → level}/markdown.js +20 -29
  40. package/src/formatters/{grade → level}/microdata.js +28 -38
  41. package/src/formatters/level/shared.js +86 -0
  42. package/src/formatters/progress/markdown.js +2 -2
  43. package/src/formatters/progress/shared.js +48 -48
  44. package/src/formatters/questions/markdown.js +8 -6
  45. package/src/formatters/questions/shared.js +7 -7
  46. package/src/formatters/skill/dom.js +4 -4
  47. package/src/formatters/skill/markdown.js +2 -2
  48. package/src/formatters/skill/microdata.js +3 -3
  49. package/src/formatters/skill/shared.js +3 -3
  50. package/src/formatters/track/dom.js +1 -1
  51. package/src/formatters/track/markdown.js +1 -1
  52. package/src/handout-main.js +13 -16
  53. package/src/handout.html +4 -4
  54. package/src/index.html +5 -5
  55. package/src/lib/card-mappers.js +17 -17
  56. package/src/lib/cli-command.js +3 -3
  57. package/src/lib/cli-output.js +2 -2
  58. package/src/lib/job-cache.js +11 -11
  59. package/src/lib/render.js +6 -6
  60. package/src/lib/state.js +2 -2
  61. package/src/lib/yaml-loader.js +9 -9
  62. package/src/main.js +10 -10
  63. package/src/pages/agent-builder.js +11 -11
  64. package/src/pages/assessment-results.js +27 -23
  65. package/src/pages/behaviour.js +1 -1
  66. package/src/pages/discipline.js +1 -1
  67. package/src/pages/driver.js +1 -1
  68. package/src/pages/interview-builder.js +6 -6
  69. package/src/pages/interview.js +9 -9
  70. package/src/pages/job-builder.js +6 -6
  71. package/src/pages/job.js +7 -7
  72. package/src/pages/landing.js +9 -9
  73. package/src/pages/level.js +122 -0
  74. package/src/pages/progress-builder.js +8 -8
  75. package/src/pages/progress.js +74 -74
  76. package/src/pages/self-assessment.js +8 -8
  77. package/src/pages/skill.js +1 -4
  78. package/src/pages/stage.js +1 -1
  79. package/src/pages/tool.js +1 -1
  80. package/src/pages/track.js +1 -1
  81. package/src/slide-main.js +22 -22
  82. package/src/slides/chapter.js +4 -4
  83. package/src/slides/index.js +11 -11
  84. package/src/slides/interview.js +2 -2
  85. package/src/slides/job.js +3 -3
  86. package/src/slides/level.js +32 -0
  87. package/src/slides/overview.js +9 -9
  88. package/src/slides/progress.js +13 -13
  89. package/src/slides.html +4 -4
  90. package/src/types.js +1 -1
  91. package/templates/install.template.sh +11 -8
  92. package/templates/job.template.md +2 -2
  93. package/src/commands/grade.js +0 -60
  94. package/src/formatters/grade/shared.js +0 -86
  95. package/src/pages/grade.js +0 -122
  96. package/src/slides/grade.js +0 -32
@@ -20,7 +20,7 @@ import { interviewToDOM } from "../formatters/index.js";
20
20
  */
21
21
  export function renderInterviewSlide({ render, data, params }) {
22
22
  const discipline = data.disciplines.find((d) => d.id === params.discipline);
23
- const grade = data.grades.find((g) => g.id === params.grade);
23
+ const level = data.levels.find((g) => g.id === params.level);
24
24
  const track = data.tracks.find((t) => t.id === params.track);
25
25
 
26
26
  // Get interview type from URL query or default to full
@@ -29,7 +29,7 @@ export function renderInterviewSlide({ render, data, params }) {
29
29
 
30
30
  const view = prepareInterviewDetail({
31
31
  discipline,
32
- grade,
32
+ level,
33
33
  track,
34
34
  skills: data.skills,
35
35
  behaviours: data.behaviours,
package/src/slides/job.js CHANGED
@@ -17,12 +17,12 @@ import { jobToDOM } from "../formatters/index.js";
17
17
  */
18
18
  export function renderJobSlide({ render, data, params }) {
19
19
  const discipline = data.disciplines.find((d) => d.id === params.discipline);
20
- const grade = data.grades.find((g) => g.id === params.grade);
20
+ const level = data.levels.find((g) => g.id === params.level);
21
21
  const track = data.tracks.find((t) => t.id === params.track);
22
22
 
23
23
  const view = prepareJobDetail({
24
24
  discipline,
25
- grade,
25
+ level,
26
26
  track,
27
27
  skills: data.skills,
28
28
  behaviours: data.behaviours,
@@ -49,7 +49,7 @@ export function renderJobSlide({ render, data, params }) {
49
49
  showJobDescriptionHtml: true,
50
50
  showJobDescriptionMarkdown: false,
51
51
  discipline,
52
- grade,
52
+ level,
53
53
  track,
54
54
  }),
55
55
  );
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Level Slide View
3
+ *
4
+ * Printer-friendly view of a level.
5
+ */
6
+
7
+ import { div, h1, p } from "../lib/render.js";
8
+ import { levelToDOM } from "../formatters/index.js";
9
+
10
+ /**
11
+ * Render level slide
12
+ * @param {Object} params
13
+ * @param {Function} params.render
14
+ * @param {Object} params.data
15
+ * @param {Object} params.params
16
+ */
17
+ export function renderLevelSlide({ render, data, params }) {
18
+ const level = data.levels.find((g) => g.id === params.id);
19
+
20
+ if (!level) {
21
+ render(
22
+ div(
23
+ { className: "slide-error" },
24
+ h1({}, "Level Not Found"),
25
+ p({}, `No level found with ID: ${params.id}`),
26
+ ),
27
+ );
28
+ return;
29
+ }
30
+
31
+ render(levelToDOM(level, { framework: data.framework, showBackLink: false }));
32
+ }
@@ -12,7 +12,7 @@ import {
12
12
  skillToCardConfig,
13
13
  behaviourToCardConfig,
14
14
  driverToCardConfig,
15
- gradeToCardConfig,
15
+ levelToCardConfig,
16
16
  trackToCardConfig,
17
17
  jobToCardConfig,
18
18
  } from "../lib/card-mappers.js";
@@ -20,7 +20,7 @@ import { prepareDisciplinesList } from "../formatters/discipline/shared.js";
20
20
  import { prepareSkillsList } from "../formatters/skill/shared.js";
21
21
  import { prepareBehavioursList } from "../formatters/behaviour/shared.js";
22
22
  import { prepareDriversList } from "../formatters/driver/shared.js";
23
- import { prepareGradesList } from "../formatters/grade/shared.js";
23
+ import { prepareLevelsList } from "../formatters/level/shared.js";
24
24
  import { prepareTracksList } from "../formatters/track/shared.js";
25
25
  import { generateAllJobs } from "@forwardimpact/libpathway/derivation";
26
26
 
@@ -92,12 +92,12 @@ export function renderOverviewSlide({ render, data, params }) {
92
92
  mapper: disciplineToCardConfig,
93
93
  isGrouped: true,
94
94
  },
95
- grade: {
96
- title: framework.entityDefinitions.grade.title,
97
- emojiIcon: framework.entityDefinitions.grade.emojiIcon,
98
- description: framework.entityDefinitions.grade.description,
99
- entities: prepareGradesList(data.grades).items,
100
- mapper: gradeToCardConfig,
95
+ level: {
96
+ title: framework.entityDefinitions.level.title,
97
+ emojiIcon: framework.entityDefinitions.level.emojiIcon,
98
+ description: framework.entityDefinitions.level.description,
99
+ entities: prepareLevelsList(data.levels).items,
100
+ mapper: levelToCardConfig,
101
101
  },
102
102
  track: {
103
103
  title: framework.entityDefinitions.track.title,
@@ -112,7 +112,7 @@ export function renderOverviewSlide({ render, data, params }) {
112
112
  description: framework.entityDefinitions.job.description,
113
113
  entities: generateAllJobs({
114
114
  disciplines: data.disciplines,
115
- grades: data.grades,
115
+ levels: data.levels,
116
116
  tracks: data.tracks,
117
117
  skills: data.skills,
118
118
  behaviours: data.behaviours,
@@ -7,7 +7,7 @@
7
7
  import { div, h1, p } from "../lib/render.js";
8
8
  import {
9
9
  prepareProgressDetail,
10
- getDefaultTargetGrade,
10
+ getDefaultTargetLevel,
11
11
  } from "../formatters/progress/shared.js";
12
12
  import { progressToDOM } from "../formatters/index.js";
13
13
 
@@ -20,12 +20,12 @@ import { progressToDOM } from "../formatters/index.js";
20
20
  */
21
21
  export function renderProgressSlide({ render, data, params }) {
22
22
  const discipline = data.disciplines.find((d) => d.id === params.discipline);
23
- const grade = data.grades.find((g) => g.id === params.grade);
23
+ const level = data.levels.find((g) => g.id === params.level);
24
24
  const track = params.track
25
25
  ? data.tracks.find((t) => t.id === params.track)
26
26
  : null;
27
27
 
28
- if (!discipline || !grade) {
28
+ if (!discipline || !level) {
29
29
  render(
30
30
  div(
31
31
  { className: "slide-error" },
@@ -36,23 +36,23 @@ export function renderProgressSlide({ render, data, params }) {
36
36
  return;
37
37
  }
38
38
 
39
- // Get compare grade from URL query or default to next grade
39
+ // Get compare level from URL query or default to next level
40
40
  const urlParams = new URLSearchParams(window.location.search);
41
- const compareGradeId = urlParams.get("compare");
41
+ const compareLevelId = urlParams.get("compare");
42
42
 
43
- let targetGrade;
44
- if (compareGradeId) {
45
- targetGrade = data.grades.find((g) => g.id === compareGradeId);
43
+ let targetLevel;
44
+ if (compareLevelId) {
45
+ targetLevel = data.levels.find((g) => g.id === compareLevelId);
46
46
  } else {
47
- targetGrade = getDefaultTargetGrade(grade, data.grades);
47
+ targetLevel = getDefaultTargetLevel(level, data.levels);
48
48
  }
49
49
 
50
- if (!targetGrade) {
50
+ if (!targetLevel) {
51
51
  render(
52
52
  div(
53
53
  { className: "slide-error" },
54
54
  h1({}, "No Progression Available"),
55
- p({}, "No next grade available for this role."),
55
+ p({}, "No next level available for this role."),
56
56
  ),
57
57
  );
58
58
  return;
@@ -60,10 +60,10 @@ export function renderProgressSlide({ render, data, params }) {
60
60
 
61
61
  const view = prepareProgressDetail({
62
62
  fromDiscipline: discipline,
63
- fromGrade: grade,
63
+ fromLevel: level,
64
64
  fromTrack: track,
65
65
  toDiscipline: discipline,
66
- toGrade: targetGrade,
66
+ toLevel: targetLevel,
67
67
  toTrack: track,
68
68
  skills: data.skills,
69
69
  behaviours: data.behaviours,
package/src/slides.html CHANGED
@@ -9,10 +9,10 @@
9
9
  {
10
10
  "imports": {
11
11
  "mustache": "https://esm.sh/mustache@4.2.0",
12
- "@forwardimpact/schema": "/schema/lib/index.js",
13
- "@forwardimpact/schema/levels": "/schema/lib/levels.js",
14
- "@forwardimpact/schema/loader": "/schema/lib/loader.js",
15
- "@forwardimpact/schema/validation": "/schema/lib/validation.js",
12
+ "@forwardimpact/map": "/map/lib/index.js",
13
+ "@forwardimpact/map/levels": "/map/lib/levels.js",
14
+ "@forwardimpact/map/loader": "/map/lib/loader.js",
15
+ "@forwardimpact/map/validation": "/map/lib/validation.js",
16
16
  "@forwardimpact/libpathway": "/model/lib/index.js",
17
17
  "@forwardimpact/libpathway/derivation": "/model/lib/derivation.js",
18
18
  "@forwardimpact/libpathway/modifiers": "/model/lib/modifiers.js",
package/src/types.js CHANGED
@@ -19,7 +19,7 @@
19
19
  * @property {boolean} [humanOnly] - Whether this skill requires human presence
20
20
  * @property {'primary'|'secondary'|'tertiary'} type - Skill type in this role
21
21
  * @property {string} level - Level ID (e.g., "advanced", "expert")
22
- * @property {string} levelDescription - Human-readable level description
22
+ * @property {string} proficiencyDescription - Human-readable level description
23
23
  */
24
24
 
25
25
  // ============================================================================
@@ -2,7 +2,8 @@
2
2
  # {{{frameworkTitle}}} — Local Install
3
3
  # Generated by @forwardimpact/pathway v{{{version}}}
4
4
  #
5
- # Installs to ~/.fit/pathway/
5
+ # Installs fit-pathway globally via npm and downloads organization data
6
+ # to ~/.fit/pathway/data/.
6
7
  #
7
8
  # Usage:
8
9
  # curl -fsSL {{{siteUrl}}}/install.sh | bash
@@ -15,7 +16,12 @@ INSTALL_DIR="${HOME}/.fit/pathway"
15
16
  command -v node >/dev/null 2>&1 || { echo "Error: Node.js 18+ is required. https://nodejs.org"; exit 1; }
16
17
  command -v npm >/dev/null 2>&1 || { echo "Error: npm is required."; exit 1; }
17
18
 
18
- echo "Installing to ${INSTALL_DIR}..."
19
+ # Install fit-pathway globally (provides the fit-pathway binary on PATH)
20
+ echo "Installing @forwardimpact/pathway globally..."
21
+ npm install -g @forwardimpact/pathway@{{{version}}}
22
+
23
+ # Download organization data to ~/.fit/pathway/data/
24
+ echo "Downloading organization data to ${INSTALL_DIR}/data/..."
19
25
  mkdir -p "${INSTALL_DIR}"
20
26
 
21
27
  TMPFILE=$(mktemp)
@@ -23,11 +29,8 @@ trap 'rm -f "$TMPFILE"' EXIT
23
29
  curl -fsSL "${SITE_URL}/bundle.tar.gz" -o "$TMPFILE"
24
30
  tar -xzf "$TMPFILE" -C "${INSTALL_DIR}" --strip-components=1
25
31
 
26
- (cd "${INSTALL_DIR}" && npm install --production --ignore-scripts --no-audit --no-fund --silent)
27
-
28
32
  echo ""
29
33
  echo "Done. Usage:"
30
- echo " cd ${INSTALL_DIR}"
31
- echo " npx fit-pathway skill --list"
32
- echo " npx fit-pathway job --list"
33
- echo " npx fit-pathway agent --list"
34
+ echo " fit-pathway skill --list"
35
+ echo " fit-pathway job --list"
36
+ echo " fit-pathway agent --list"
@@ -1,6 +1,6 @@
1
1
  # {{{title}}}
2
2
 
3
- - **Level:** {{{gradeId}}}
3
+ - **Level:** {{{levelId}}}
4
4
  - **Experience:** {{{typicalExperienceRange}}}
5
5
  {{#hasTrack}}- **Track:** {{{trackName}}}
6
6
  {{/hasTrack}}
@@ -34,7 +34,7 @@
34
34
  {{{responsibilityDescription}}}:
35
35
 
36
36
  {{#skills}}
37
- - **{{{skillName}}}:** {{{levelDescription}}}
37
+ - **{{{skillName}}}:** {{{proficiencyDescription}}}
38
38
  {{/skills}}
39
39
  {{/capabilitySkills}}
40
40
  {{/hasCapabilitySkills}}
@@ -1,60 +0,0 @@
1
- /**
2
- * Grade CLI Command
3
- *
4
- * Handles grade summary, listing, and detail display in the terminal.
5
- *
6
- * Usage:
7
- * npx pathway grade # Summary with stats
8
- * npx pathway grade --list # IDs only (for piping)
9
- * npx pathway grade <id> # Detail view
10
- * npx pathway grade --validate # Validation checks
11
- */
12
-
13
- import { createEntityCommand } from "./command-factory.js";
14
- import { gradeToMarkdown } from "../formatters/grade/markdown.js";
15
- import { formatTable } from "../lib/cli-output.js";
16
- import { getConceptEmoji } from "@forwardimpact/schema/levels";
17
- import { capitalize } from "../formatters/shared.js";
18
-
19
- /**
20
- * Format grade summary output
21
- * @param {Array} grades - Raw grade entities
22
- * @param {Object} data - Full data context
23
- */
24
- function formatSummary(grades, data) {
25
- const { framework } = data;
26
- const emoji = framework ? getConceptEmoji(framework, "grade") : "📊";
27
-
28
- console.log(`\n${emoji} Grades\n`);
29
-
30
- const rows = grades.map((g) => [
31
- g.id,
32
- g.displayName || g.id,
33
- g.typicalExperienceRange || "-",
34
- capitalize(g.baseSkillLevels?.primary || "-"),
35
- ]);
36
-
37
- console.log(formatTable(["ID", "Name", "Experience", "Primary Level"], rows));
38
- console.log(`\nTotal: ${grades.length} grades`);
39
- console.log(`\nRun 'npx pathway grade --list' for IDs`);
40
- console.log(`Run 'npx pathway grade <id>' for details\n`);
41
- }
42
-
43
- /**
44
- * Format grade detail output
45
- * @param {Object} grade - Raw grade entity
46
- * @param {Object} framework - Framework config
47
- */
48
- function formatDetail(grade, framework) {
49
- console.log(gradeToMarkdown(grade, framework));
50
- }
51
-
52
- export const runGradeCommand = createEntityCommand({
53
- entityName: "grade",
54
- pluralName: "grades",
55
- findEntity: (data, id) => data.grades.find((g) => g.id === id),
56
- presentDetail: (entity) => entity,
57
- formatSummary,
58
- formatDetail,
59
- emojiIcon: "📊",
60
- });
@@ -1,86 +0,0 @@
1
- /**
2
- * Grade presentation helpers
3
- *
4
- * Shared utilities for formatting grade data across DOM and markdown outputs.
5
- */
6
-
7
- /**
8
- * Get grade display name (shows both professional and management titles)
9
- * @param {Object} grade
10
- * @returns {string}
11
- */
12
- export function getGradeDisplayName(grade) {
13
- if (grade.professionalTitle && grade.managementTitle) {
14
- if (grade.professionalTitle === grade.managementTitle) {
15
- return grade.professionalTitle;
16
- }
17
- return `${grade.professionalTitle} / ${grade.managementTitle}`;
18
- }
19
- return grade.professionalTitle || grade.managementTitle || grade.id;
20
- }
21
-
22
- /**
23
- * @typedef {Object} GradeListItem
24
- * @property {string} id
25
- * @property {string} displayName
26
- * @property {number} ordinalRank
27
- * @property {string|null} typicalExperienceRange
28
- * @property {Object} baseSkillLevels
29
- * @property {string|null} impactScope
30
- */
31
-
32
- /**
33
- * Transform grades for list view
34
- * @param {Array} grades - Raw grade entities
35
- * @returns {{ items: GradeListItem[] }}
36
- */
37
- export function prepareGradesList(grades) {
38
- const sortedGrades = [...grades].sort(
39
- (a, b) => a.ordinalRank - b.ordinalRank,
40
- );
41
-
42
- const items = sortedGrades.map((grade) => ({
43
- id: grade.id,
44
- displayName: getGradeDisplayName(grade),
45
- ordinalRank: grade.ordinalRank,
46
- typicalExperienceRange: grade.typicalExperienceRange || null,
47
- baseSkillLevels: grade.baseSkillLevels || {},
48
- impactScope: grade.expectations?.impactScope || null,
49
- }));
50
-
51
- return { items };
52
- }
53
-
54
- /**
55
- * @typedef {Object} GradeDetailView
56
- * @property {string} id
57
- * @property {string} displayName
58
- * @property {string} professionalTitle
59
- * @property {string} managementTitle
60
- * @property {number} ordinalRank
61
- * @property {string|null} typicalExperienceRange
62
- * @property {Object} baseSkillLevels
63
- * @property {Object} baseBehaviourMaturity
64
- * @property {Object} expectations
65
- */
66
-
67
- /**
68
- * Transform grade for detail view
69
- * @param {Object} grade - Raw grade entity
70
- * @returns {GradeDetailView|null}
71
- */
72
- export function prepareGradeDetail(grade) {
73
- if (!grade) return null;
74
-
75
- return {
76
- id: grade.id,
77
- displayName: getGradeDisplayName(grade),
78
- professionalTitle: grade.professionalTitle || null,
79
- managementTitle: grade.managementTitle || null,
80
- ordinalRank: grade.ordinalRank,
81
- typicalExperienceRange: grade.typicalExperienceRange || null,
82
- baseSkillLevels: grade.baseSkillLevels || {},
83
- baseBehaviourMaturity: grade.baseBehaviourMaturity || {},
84
- expectations: grade.expectations || {},
85
- };
86
- }
@@ -1,122 +0,0 @@
1
- /**
2
- * Grades 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 { prepareGradesList } from "../formatters/grade/shared.js";
10
- import { gradeToDOM } from "../formatters/grade/dom.js";
11
- import { getConceptEmoji } from "@forwardimpact/schema/levels";
12
-
13
- /**
14
- * Render grades list page
15
- */
16
- export function renderGradesList() {
17
- const { data } = getState();
18
- const { framework } = data;
19
- const gradeEmoji = getConceptEmoji(framework, "grade");
20
-
21
- // Transform data for list view
22
- const { items } = prepareGradesList(data.grades);
23
-
24
- const page = div(
25
- { className: "grades-page" },
26
- // Header
27
- div(
28
- { className: "page-header" },
29
- h1(
30
- { className: "page-title" },
31
- `${gradeEmoji} ${framework.entityDefinitions.grade.title}`,
32
- ),
33
- p(
34
- { className: "page-description" },
35
- framework.entityDefinitions.grade.description.trim(),
36
- ),
37
- ),
38
-
39
- // Grades timeline
40
- div(
41
- { className: "grades-timeline" },
42
- ...items.map((grade) => createGradeTimelineItem(grade)),
43
- ),
44
- );
45
-
46
- render(page);
47
- }
48
-
49
- /**
50
- * Create a grade timeline item
51
- * @param {Object} grade
52
- * @returns {HTMLElement}
53
- */
54
- function createGradeTimelineItem(grade) {
55
- const item = div(
56
- { className: "grade-timeline-item" },
57
- div({ className: "grade-level-marker" }, String(grade.ordinalRank)),
58
- div(
59
- { className: "grade-timeline-content card card-clickable" },
60
- div(
61
- { className: "card-header" },
62
- h3({ className: "card-title" }, grade.displayName),
63
- createBadge(grade.id, "default"),
64
- ),
65
- grade.typicalExperienceRange
66
- ? p(
67
- { className: "text-muted", style: "margin: 0.25rem 0" },
68
- `${grade.typicalExperienceRange} experience`,
69
- )
70
- : null,
71
- div(
72
- { className: "card-meta", style: "margin-top: 0.5rem" },
73
- createBadge(
74
- `Primary: ${formatLevel(grade.baseSkillLevels?.primary)}`,
75
- "primary",
76
- ),
77
- createBadge(
78
- `Secondary: ${formatLevel(grade.baseSkillLevels?.secondary)}`,
79
- "secondary",
80
- ),
81
- createBadge(
82
- `Broad: ${formatLevel(grade.baseSkillLevels?.broad)}`,
83
- "broad",
84
- ),
85
- ),
86
- grade.scope
87
- ? p(
88
- { className: "card-description", style: "margin-top: 0.75rem" },
89
- `Scope: ${grade.scope}`,
90
- )
91
- : null,
92
- ),
93
- );
94
-
95
- item.querySelector(".card").addEventListener("click", () => {
96
- window.location.hash = `/grade/${grade.id}`;
97
- });
98
-
99
- return item;
100
- }
101
-
102
- /**
103
- * Render grade detail page
104
- * @param {Object} params - Route params
105
- */
106
- export function renderGradeDetail(params) {
107
- const { data } = getState();
108
- const grade = data.grades.find((g) => g.id === params.id);
109
-
110
- if (!grade) {
111
- renderNotFound({
112
- entityType: "Grade",
113
- entityId: params.id,
114
- backPath: "/grade",
115
- backText: "← Back to Grades",
116
- });
117
- return;
118
- }
119
-
120
- // Use DOM formatter - it handles transformation internally
121
- render(gradeToDOM(grade, { framework: data.framework }));
122
- }
@@ -1,32 +0,0 @@
1
- /**
2
- * Grade Slide View
3
- *
4
- * Printer-friendly view of a grade.
5
- */
6
-
7
- import { div, h1, p } from "../lib/render.js";
8
- import { gradeToDOM } from "../formatters/index.js";
9
-
10
- /**
11
- * Render grade slide
12
- * @param {Object} params
13
- * @param {Function} params.render
14
- * @param {Object} params.data
15
- * @param {Object} params.params
16
- */
17
- export function renderGradeSlide({ render, data, params }) {
18
- const grade = data.grades.find((g) => g.id === params.id);
19
-
20
- if (!grade) {
21
- render(
22
- div(
23
- { className: "slide-error" },
24
- h1({}, "Grade Not Found"),
25
- p({}, `No grade found with ID: ${params.id}`),
26
- ),
27
- );
28
- return;
29
- }
30
-
31
- render(gradeToDOM(grade, { framework: data.framework, showBackLink: false }));
32
- }