@forwardimpact/pathway 0.4.0 → 0.6.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 (245) hide show
  1. package/bin/{pathway.js → fit-pathway.js} +65 -153
  2. package/package.json +18 -41
  3. package/{app → src}/commands/agent.js +5 -2
  4. package/{app → src}/commands/behaviour.js +1 -1
  5. package/{app → src}/commands/command-factory.js +2 -2
  6. package/{app → src}/commands/discipline.js +1 -1
  7. package/{app → src}/commands/driver.js +2 -2
  8. package/{app → src}/commands/grade.js +2 -2
  9. package/{app → src}/commands/job.js +3 -3
  10. package/{app → src}/commands/serve.js +26 -4
  11. package/{app → src}/commands/site.js +24 -4
  12. package/{app → src}/commands/skill.js +3 -3
  13. package/{app → src}/commands/stage.js +1 -1
  14. package/{app → src}/commands/track.js +2 -2
  15. package/{app → src}/components/card.js +11 -1
  16. package/{app → src}/components/checklist.js +1 -1
  17. package/src/components/code-display.js +153 -0
  18. package/{app → src}/components/comparison-radar.js +1 -1
  19. package/{app → src}/components/detail.js +1 -1
  20. package/src/components/markdown-textarea.js +153 -0
  21. package/{app → src}/components/skill-matrix.js +1 -1
  22. package/{app → src}/css/bundles/app.css +14 -0
  23. package/{app → src}/css/components/badges.css +15 -8
  24. package/{app → src}/css/components/forms.css +23 -13
  25. package/{app → src}/css/components/surfaces.css +49 -3
  26. package/{app → src}/css/components/typography.css +1 -2
  27. package/{app → src}/css/pages/agent-builder.css +11 -102
  28. package/{app → src}/css/pages/detail.css +11 -1
  29. package/{app → src}/css/tokens.css +3 -0
  30. package/{app → src}/formatters/agent/dom.js +26 -71
  31. package/{app → src}/formatters/agent/profile.js +11 -6
  32. package/{app → src}/formatters/behaviour/dom.js +1 -1
  33. package/{app → src}/formatters/discipline/dom.js +1 -1
  34. package/{app → src}/formatters/driver/dom.js +1 -1
  35. package/{app → src}/formatters/grade/dom.js +7 -7
  36. package/{app → src}/formatters/grade/markdown.js +1 -1
  37. package/{app → src}/formatters/interview/dom.js +1 -1
  38. package/{app → src}/formatters/interview/markdown.js +1 -1
  39. package/{app → src}/formatters/interview/shared.js +3 -3
  40. package/{app → src}/formatters/job/description.js +1 -1
  41. package/{app → src}/formatters/job/dom.js +3 -3
  42. package/{app → src}/formatters/job/markdown.js +1 -1
  43. package/{app → src}/formatters/json-ld.js +1 -1
  44. package/{app → src}/formatters/progress/shared.js +3 -3
  45. package/{app → src}/formatters/skill/dom.js +69 -57
  46. package/{app → src}/formatters/skill/markdown.js +1 -1
  47. package/{app → src}/formatters/skill/shared.js +5 -3
  48. package/{app → src}/formatters/stage/microdata.js +2 -2
  49. package/{app → src}/formatters/stage/shared.js +3 -3
  50. package/{app → src}/formatters/tool/shared.js +6 -0
  51. package/{app → src}/formatters/track/dom.js +1 -1
  52. package/{app → src}/formatters/track/markdown.js +1 -1
  53. package/{app → src}/formatters/track/shared.js +4 -1
  54. package/{app → src}/handout-main.js +16 -12
  55. package/src/handout.html +43 -0
  56. package/{app → src}/index.html +23 -2
  57. package/{app → src}/lib/card-mappers.js +28 -1
  58. package/{app → src}/lib/job-cache.js +1 -1
  59. package/{app → src}/lib/render.js +1 -1
  60. package/{app → src}/pages/agent-builder.js +120 -76
  61. package/{app → src}/pages/assessment-results.js +1 -1
  62. package/{app → src}/pages/interview.js +1 -1
  63. package/{app → src}/pages/job-builder.js +1 -1
  64. package/{app → src}/pages/job.js +1 -1
  65. package/{app → src}/pages/landing.js +5 -2
  66. package/{app → src}/pages/self-assessment.js +1 -1
  67. package/{app → src}/pages/skill.js +1 -1
  68. package/{app → src}/pages/stage.js +5 -5
  69. package/{app → src}/pages/tool.js +1 -1
  70. package/{app → src}/slide-main.js +2 -2
  71. package/{app → src}/slides/chapter.js +8 -8
  72. package/{app → src}/slides/index.js +3 -3
  73. package/{app → src}/slides/job.js +1 -1
  74. package/{app → src}/slides/overview.js +9 -9
  75. package/{app → src}/slides/skill.js +1 -0
  76. package/{app → src}/slides.html +16 -1
  77. package/templates/agent.template.md +44 -13
  78. package/templates/job.template.md +14 -20
  79. package/templates/skill.template.md +20 -23
  80. package/LICENSE +0 -201
  81. package/README.md +0 -104
  82. package/app/components/markdown-textarea.js +0 -132
  83. package/app/handout.html +0 -28
  84. package/app/model/agent.js +0 -738
  85. package/app/model/checklist.js +0 -103
  86. package/app/model/derivation.js +0 -766
  87. package/app/model/index-generator.js +0 -65
  88. package/app/model/interview.js +0 -539
  89. package/app/model/job.js +0 -228
  90. package/app/model/levels.js +0 -601
  91. package/app/model/loader.js +0 -599
  92. package/app/model/matching.js +0 -888
  93. package/app/model/modifiers.js +0 -158
  94. package/app/model/profile.js +0 -259
  95. package/app/model/progression.js +0 -507
  96. package/app/model/schema-validation.js +0 -438
  97. package/app/model/validation.js +0 -2130
  98. package/examples/agents/.claude/skills/architecture-design/SKILL.md +0 -130
  99. package/examples/agents/.claude/skills/cloud-platforms/SKILL.md +0 -131
  100. package/examples/agents/.claude/skills/code-quality-review/SKILL.md +0 -108
  101. package/examples/agents/.claude/skills/devops-cicd/SKILL.md +0 -142
  102. package/examples/agents/.claude/skills/full-stack-development/SKILL.md +0 -134
  103. package/examples/agents/.claude/skills/sre-practices/SKILL.md +0 -163
  104. package/examples/agents/.claude/skills/technical-debt-management/SKILL.md +0 -164
  105. package/examples/agents/.github/agents/se-platform-code.agent.md +0 -132
  106. package/examples/agents/.github/agents/se-platform-plan.agent.md +0 -131
  107. package/examples/agents/.github/agents/se-platform-review.agent.md +0 -136
  108. package/examples/agents/.vscode/settings.json +0 -8
  109. package/examples/behaviours/_index.yaml +0 -8
  110. package/examples/behaviours/outcome_ownership.yaml +0 -43
  111. package/examples/behaviours/polymathic_knowledge.yaml +0 -41
  112. package/examples/behaviours/precise_communication.yaml +0 -39
  113. package/examples/behaviours/relentless_curiosity.yaml +0 -37
  114. package/examples/behaviours/systems_thinking.yaml +0 -40
  115. package/examples/capabilities/_index.yaml +0 -8
  116. package/examples/capabilities/business.yaml +0 -189
  117. package/examples/capabilities/delivery.yaml +0 -303
  118. package/examples/capabilities/people.yaml +0 -68
  119. package/examples/capabilities/reliability.yaml +0 -412
  120. package/examples/capabilities/scale.yaml +0 -378
  121. package/examples/copilot-setup-steps.yaml +0 -25
  122. package/examples/devcontainer.yaml +0 -21
  123. package/examples/disciplines/_index.yaml +0 -6
  124. package/examples/disciplines/data_engineering.yaml +0 -78
  125. package/examples/disciplines/engineering_management.yaml +0 -63
  126. package/examples/disciplines/software_engineering.yaml +0 -78
  127. package/examples/drivers.yaml +0 -202
  128. package/examples/framework.yaml +0 -69
  129. package/examples/grades.yaml +0 -115
  130. package/examples/questions/behaviours/outcome_ownership.yaml +0 -51
  131. package/examples/questions/behaviours/polymathic_knowledge.yaml +0 -47
  132. package/examples/questions/behaviours/precise_communication.yaml +0 -54
  133. package/examples/questions/behaviours/relentless_curiosity.yaml +0 -50
  134. package/examples/questions/behaviours/systems_thinking.yaml +0 -52
  135. package/examples/questions/skills/architecture_design.yaml +0 -53
  136. package/examples/questions/skills/cloud_platforms.yaml +0 -47
  137. package/examples/questions/skills/code_quality.yaml +0 -48
  138. package/examples/questions/skills/data_modeling.yaml +0 -45
  139. package/examples/questions/skills/devops.yaml +0 -46
  140. package/examples/questions/skills/full_stack_development.yaml +0 -47
  141. package/examples/questions/skills/sre_practices.yaml +0 -43
  142. package/examples/questions/skills/stakeholder_management.yaml +0 -48
  143. package/examples/questions/skills/team_collaboration.yaml +0 -42
  144. package/examples/questions/skills/technical_writing.yaml +0 -42
  145. package/examples/self-assessments.yaml +0 -64
  146. package/examples/stages.yaml +0 -131
  147. package/examples/tracks/_index.yaml +0 -5
  148. package/examples/tracks/platform.yaml +0 -49
  149. package/examples/tracks/sre.yaml +0 -48
  150. package/examples/vscode-settings.yaml +0 -17
  151. /package/{app → src}/commands/index.js +0 -0
  152. /package/{app → src}/commands/init.js +0 -0
  153. /package/{app → src}/commands/interview.js +0 -0
  154. /package/{app → src}/commands/progress.js +0 -0
  155. /package/{app → src}/commands/questions.js +0 -0
  156. /package/{app → src}/commands/tool.js +0 -0
  157. /package/{app → src}/components/action-buttons.js +0 -0
  158. /package/{app → src}/components/behaviour-profile.js +0 -0
  159. /package/{app → src}/components/builder.js +0 -0
  160. /package/{app → src}/components/error-page.js +0 -0
  161. /package/{app → src}/components/grid.js +0 -0
  162. /package/{app → src}/components/list.js +0 -0
  163. /package/{app → src}/components/modifier-table.js +0 -0
  164. /package/{app → src}/components/nav.js +0 -0
  165. /package/{app → src}/components/progression-table.js +0 -0
  166. /package/{app → src}/components/radar-chart.js +0 -0
  167. /package/{app → src}/css/base.css +0 -0
  168. /package/{app → src}/css/bundles/handout.css +0 -0
  169. /package/{app → src}/css/bundles/slides.css +0 -0
  170. /package/{app → src}/css/components/buttons.css +0 -0
  171. /package/{app → src}/css/components/layout.css +0 -0
  172. /package/{app → src}/css/components/nav.css +0 -0
  173. /package/{app → src}/css/components/progress.css +0 -0
  174. /package/{app → src}/css/components/states.css +0 -0
  175. /package/{app → src}/css/components/tables.css +0 -0
  176. /package/{app → src}/css/components/utilities.css +0 -0
  177. /package/{app → src}/css/pages/assessment-results.css +0 -0
  178. /package/{app → src}/css/pages/interview-builder.css +0 -0
  179. /package/{app → src}/css/pages/job-builder.css +0 -0
  180. /package/{app → src}/css/pages/landing.css +0 -0
  181. /package/{app → src}/css/pages/lifecycle.css +0 -0
  182. /package/{app → src}/css/pages/progress-builder.css +0 -0
  183. /package/{app → src}/css/pages/self-assessment.css +0 -0
  184. /package/{app → src}/css/reset.css +0 -0
  185. /package/{app → src}/css/views/handout.css +0 -0
  186. /package/{app → src}/css/views/print.css +0 -0
  187. /package/{app → src}/css/views/slide-animations.css +0 -0
  188. /package/{app → src}/css/views/slide-base.css +0 -0
  189. /package/{app → src}/css/views/slide-sections.css +0 -0
  190. /package/{app → src}/css/views/slide-tables.css +0 -0
  191. /package/{app → src}/formatters/agent/skill.js +0 -0
  192. /package/{app → src}/formatters/behaviour/markdown.js +0 -0
  193. /package/{app → src}/formatters/behaviour/microdata.js +0 -0
  194. /package/{app → src}/formatters/behaviour/shared.js +0 -0
  195. /package/{app → src}/formatters/discipline/markdown.js +0 -0
  196. /package/{app → src}/formatters/discipline/microdata.js +0 -0
  197. /package/{app → src}/formatters/discipline/shared.js +0 -0
  198. /package/{app → src}/formatters/driver/microdata.js +0 -0
  199. /package/{app → src}/formatters/driver/shared.js +0 -0
  200. /package/{app → src}/formatters/grade/microdata.js +0 -0
  201. /package/{app → src}/formatters/grade/shared.js +0 -0
  202. /package/{app → src}/formatters/index.js +0 -0
  203. /package/{app → src}/formatters/microdata-shared.js +0 -0
  204. /package/{app → src}/formatters/progress/dom.js +0 -0
  205. /package/{app → src}/formatters/progress/markdown.js +0 -0
  206. /package/{app → src}/formatters/questions/json.js +0 -0
  207. /package/{app → src}/formatters/questions/markdown.js +0 -0
  208. /package/{app → src}/formatters/questions/shared.js +0 -0
  209. /package/{app → src}/formatters/questions/yaml.js +0 -0
  210. /package/{app → src}/formatters/shared.js +0 -0
  211. /package/{app → src}/formatters/skill/microdata.js +0 -0
  212. /package/{app → src}/formatters/stage/dom.js +0 -0
  213. /package/{app → src}/formatters/stage/index.js +0 -0
  214. /package/{app → src}/formatters/track/microdata.js +0 -0
  215. /package/{app → src}/lib/cli-output.js +0 -0
  216. /package/{app → src}/lib/error-boundary.js +0 -0
  217. /package/{app → src}/lib/errors.js +0 -0
  218. /package/{app → src}/lib/form-controls.js +0 -0
  219. /package/{app → src}/lib/markdown.js +0 -0
  220. /package/{app → src}/lib/radar.js +0 -0
  221. /package/{app → src}/lib/reactive.js +0 -0
  222. /package/{app → src}/lib/router-core.js +0 -0
  223. /package/{app → src}/lib/router-pages.js +0 -0
  224. /package/{app → src}/lib/router-slides.js +0 -0
  225. /package/{app → src}/lib/state.js +0 -0
  226. /package/{app → src}/lib/template-loader.js +0 -0
  227. /package/{app → src}/lib/utils.js +0 -0
  228. /package/{app → src}/lib/yaml-loader.js +0 -0
  229. /package/{app → src}/main.js +0 -0
  230. /package/{app → src}/pages/behaviour.js +0 -0
  231. /package/{app → src}/pages/discipline.js +0 -0
  232. /package/{app → src}/pages/driver.js +0 -0
  233. /package/{app → src}/pages/grade.js +0 -0
  234. /package/{app → src}/pages/interview-builder.js +0 -0
  235. /package/{app → src}/pages/progress-builder.js +0 -0
  236. /package/{app → src}/pages/progress.js +0 -0
  237. /package/{app → src}/pages/track.js +0 -0
  238. /package/{app → src}/slides/behaviour.js +0 -0
  239. /package/{app → src}/slides/discipline.js +0 -0
  240. /package/{app → src}/slides/driver.js +0 -0
  241. /package/{app → src}/slides/grade.js +0 -0
  242. /package/{app → src}/slides/interview.js +0 -0
  243. /package/{app → src}/slides/progress.js +0 -0
  244. /package/{app → src}/slides/track.js +0 -0
  245. /package/{app → src}/types.js +0 -0
@@ -18,8 +18,9 @@ import {
18
18
  } from "../../lib/render.js";
19
19
  import { createBackLink } from "../../components/nav.js";
20
20
  import { createLevelCell } from "../../components/detail.js";
21
- import { createMarkdownTextarea } from "../../components/markdown-textarea.js";
22
- import { SKILL_LEVEL_ORDER } from "../../model/levels.js";
21
+ import { createCodeDisplay } from "../../components/code-display.js";
22
+ import { createToolIcon } from "../../lib/card-mappers.js";
23
+ import { SKILL_LEVEL_ORDER } from "@forwardimpact/schema/levels";
23
24
  import { prepareSkillDetail } from "./shared.js";
24
25
  import { createJsonLdScript, skillToJsonLd } from "../json-ld.js";
25
26
 
@@ -32,11 +33,19 @@ import { createJsonLdScript, skillToJsonLd } from "../json-ld.js";
32
33
  * @param {Array} context.drivers - All drivers
33
34
  * @param {Array} context.capabilities - Capability entities
34
35
  * @param {boolean} [context.showBackLink=true] - Whether to show back navigation link
36
+ * @param {boolean} [context.showToolsAndPatterns=true] - Whether to show recommended tools and implementation patterns
35
37
  * @returns {HTMLElement}
36
38
  */
37
39
  export function skillToDOM(
38
40
  skill,
39
- { disciplines, tracks, drivers, capabilities, showBackLink = true } = {},
41
+ {
42
+ disciplines,
43
+ tracks,
44
+ drivers,
45
+ capabilities,
46
+ showBackLink = true,
47
+ showToolsAndPatterns = true,
48
+ } = {},
40
49
  ) {
41
50
  const view = prepareSkillDetail(skill, {
42
51
  disciplines,
@@ -98,60 +107,6 @@ export function skillToDOM(
98
107
  ),
99
108
  ),
100
109
 
101
- // Recommended Tools
102
- view.toolReferences.length > 0
103
- ? div(
104
- { className: "detail-section" },
105
- heading2({ className: "section-title" }, "Recommended Tools"),
106
- table(
107
- { className: "tools-table" },
108
- thead({}, tr({}, th({}, "Tool"), th({}, "Use When"))),
109
- tbody(
110
- {},
111
- ...view.toolReferences.map((tool) =>
112
- tr(
113
- {},
114
- td(
115
- {},
116
- tool.url
117
- ? a(
118
- {
119
- href: tool.url,
120
- target: "_blank",
121
- rel: "noopener noreferrer",
122
- },
123
- tool.name,
124
- )
125
- : tool.name,
126
- ),
127
- td({}, tool.useWhen),
128
- ),
129
- ),
130
- ),
131
- ),
132
- showBackLink
133
- ? p(
134
- { className: "see-all-link" },
135
- a({ href: "#/tool" }, "See all tools →"),
136
- )
137
- : null,
138
- )
139
- : null,
140
-
141
- // Implementation Reference
142
- view.implementationReference
143
- ? div(
144
- { className: "detail-section" },
145
- heading2({ className: "section-title" }, "Implementation Patterns"),
146
- createMarkdownTextarea({
147
- markdown: view.implementationReference,
148
- description:
149
- "Project-specific implementation guidance for this skill.",
150
- minHeight: 450,
151
- }),
152
- )
153
- : null,
154
-
155
110
  // Used in Disciplines and Linked to Drivers in two columns
156
111
  view.relatedDisciplines.length > 0 || view.relatedDrivers.length > 0
157
112
  ? div(
@@ -222,5 +177,62 @@ export function skillToDOM(
222
177
  ),
223
178
  )
224
179
  : null,
180
+
181
+ // Recommended Tools
182
+ showToolsAndPatterns && view.toolReferences.length > 0
183
+ ? div(
184
+ { className: "detail-section" },
185
+ heading2({ className: "section-title" }, "Recommended Tools"),
186
+ table(
187
+ { className: "tools-table" },
188
+ thead({}, tr({}, th({}, "Tool"), th({}, "Use When"))),
189
+ tbody(
190
+ {},
191
+ ...view.toolReferences.map((tool) =>
192
+ tr(
193
+ {},
194
+ td(
195
+ { className: "tool-name-cell" },
196
+ tool.simpleIcon
197
+ ? createToolIcon(tool.simpleIcon, tool.name)
198
+ : null,
199
+ tool.url
200
+ ? a(
201
+ {
202
+ href: tool.url,
203
+ target: "_blank",
204
+ rel: "noopener noreferrer",
205
+ },
206
+ tool.name,
207
+ )
208
+ : tool.name,
209
+ ),
210
+ td({}, tool.useWhen),
211
+ ),
212
+ ),
213
+ ),
214
+ ),
215
+ showBackLink
216
+ ? p(
217
+ { className: "see-all-link" },
218
+ a({ href: "#/tool" }, "See all tools →"),
219
+ )
220
+ : null,
221
+ )
222
+ : null,
223
+
224
+ // Implementation Reference
225
+ showToolsAndPatterns && view.implementationReference
226
+ ? div(
227
+ { className: "detail-section" },
228
+ heading2({ className: "section-title" }, "Implementation Patterns"),
229
+ createCodeDisplay({
230
+ content: view.implementationReference,
231
+ description:
232
+ "Project-specific implementation guidance for this skill.",
233
+ minHeight: 450,
234
+ }),
235
+ )
236
+ : null,
225
237
  );
226
238
  }
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { tableToMarkdown, capitalize } from "../shared.js";
6
6
  import { prepareSkillsList, prepareSkillDetail } from "./shared.js";
7
- import { getConceptEmoji } from "../../model/levels.js";
7
+ import { getConceptEmoji } from "@forwardimpact/schema/levels";
8
8
 
9
9
  /**
10
10
  * Format skill list as markdown
@@ -7,8 +7,8 @@
7
7
  import {
8
8
  groupSkillsByCapability,
9
9
  getCapabilityEmoji,
10
- } from "../../model/levels.js";
11
- import { getSkillTypeForDiscipline } from "../../model/derivation.js";
10
+ } from "@forwardimpact/schema/levels";
11
+ import { getSkillTypeForDiscipline } from "@forwardimpact/model/derivation";
12
12
  import { truncate } from "../shared.js";
13
13
 
14
14
  /**
@@ -127,7 +127,9 @@ export function prepareSkillDetail(
127
127
  relatedDisciplines,
128
128
  relatedTracks,
129
129
  relatedDrivers,
130
- toolReferences: skill.toolReferences || [],
130
+ toolReferences: (skill.toolReferences || [])
131
+ .slice()
132
+ .sort((a, b) => a.name.localeCompare(b.name)),
131
133
  implementationReference: skill.implementationReference || null,
132
134
  };
133
135
  }
@@ -32,7 +32,7 @@ export function stageListToMicrodata(stages) {
32
32
  ? `→ ${stage.handoffs.map((h) => h.target).join(", ")}`
33
33
  : "";
34
34
  return `${openTag("article", { itemtype: "Stage", itemid: `#${stage.id}` })}
35
- ${prop("h2", "name", `${stage.emoji} ${stage.name}`)}
35
+ ${prop("h2", "name", `${stage.emojiIcon} ${stage.name}`)}
36
36
  ${prop("p", "description", stage.truncatedDescription)}
37
37
  ${handoffText ? `<p>Handoffs: ${handoffText}</p>` : ""}
38
38
  </article>`;
@@ -100,7 +100,7 @@ ${prop("p", "prompt", h.prompt)}
100
100
  ${openTag("article", { itemtype: "Stage", itemid: `#${view.id}` })}
101
101
  ${prop("h1", "name", view.name)}
102
102
  ${metaTag("id", view.id)}
103
- ${stage.emoji ? metaTag("emoji", stage.emoji) : ""}
103
+ ${stage.emojiIcon ? metaTag("emojiIcon", stage.emojiIcon) : ""}
104
104
  ${prop("p", "description", view.description)}
105
105
  ${sections.join("\n")}
106
106
  </article>
@@ -10,7 +10,7 @@ import { truncate } from "../shared.js";
10
10
  * @typedef {Object} StageListItem
11
11
  * @property {string} id
12
12
  * @property {string} name
13
- * @property {string} emoji
13
+ * @property {string} emojiIcon
14
14
  * @property {string} description
15
15
  * @property {string} truncatedDescription
16
16
  * @property {Array<{target: string, label: string}>} handoffs
@@ -27,7 +27,7 @@ export function prepareStagesList(stages, descriptionLimit = 150) {
27
27
  return {
28
28
  id: stage.id,
29
29
  name: stage.name,
30
- emoji: stage.emoji,
30
+ emojiIcon: stage.emojiIcon,
31
31
  description: stage.description,
32
32
  truncatedDescription: truncate(stage.description, descriptionLimit),
33
33
  handoffs: (stage.handoffs || []).map((h) => ({
@@ -80,5 +80,5 @@ export function prepareStageDetail(stage) {
80
80
  */
81
81
  export function getStageEmoji(stages, stageId) {
82
82
  const stage = stages.find((s) => s.id === stageId);
83
- return stage?.emoji;
83
+ return stage?.emojiIcon;
84
84
  }
@@ -16,6 +16,7 @@
16
16
  * @typedef {Object} AggregatedTool
17
17
  * @property {string} name
18
18
  * @property {string} [url]
19
+ * @property {string} [simpleIcon]
19
20
  * @property {string} description
20
21
  * @property {ToolUsage[]} usages
21
22
  */
@@ -42,10 +43,15 @@ export function aggregateTools(skills) {
42
43
  const existing = toolMap.get(tool.name);
43
44
  if (existing) {
44
45
  existing.usages.push(usage);
46
+ // Prefer simpleIcon from first occurrence that has one
47
+ if (!existing.simpleIcon && tool.simpleIcon) {
48
+ existing.simpleIcon = tool.simpleIcon;
49
+ }
45
50
  } else {
46
51
  toolMap.set(tool.name, {
47
52
  name: tool.name,
48
53
  url: tool.url,
54
+ simpleIcon: tool.simpleIcon,
49
55
  description: tool.description,
50
56
  usages: [usage],
51
57
  });
@@ -15,7 +15,7 @@ import {
15
15
  createBehaviourModifierTable,
16
16
  createSkillModifierTableWithCapabilities,
17
17
  } from "../../components/modifier-table.js";
18
- import { getConceptEmoji } from "../../model/levels.js";
18
+ import { getConceptEmoji } from "@forwardimpact/schema/levels";
19
19
  import { prepareTrackDetail } from "./shared.js";
20
20
  import { createJsonLdScript, trackToJsonLd } from "../json-ld.js";
21
21
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { tableToMarkdown } from "../shared.js";
6
6
  import { prepareTracksList, prepareTrackDetail } from "./shared.js";
7
- import { getConceptEmoji } from "../../model/levels.js";
7
+ import { getConceptEmoji } from "@forwardimpact/schema/levels";
8
8
 
9
9
  /**
10
10
  * Format track list as markdown
@@ -4,7 +4,10 @@
4
4
  * Shared utilities for formatting track data across DOM and markdown outputs.
5
5
  */
6
6
 
7
- import { isCapability, getSkillsByCapability } from "../../model/modifiers.js";
7
+ import {
8
+ isCapability,
9
+ getSkillsByCapability,
10
+ } from "@forwardimpact/model/modifiers";
8
11
  import { truncate } from "../shared.js";
9
12
 
10
13
  /**
@@ -25,7 +25,10 @@ import {
25
25
  } from "./lib/render.js";
26
26
 
27
27
  // Import model functions
28
- import { getCapabilityOrder, getConceptEmoji } from "./model/levels.js";
28
+ import {
29
+ getCapabilityOrder,
30
+ getConceptEmoji,
31
+ } from "@forwardimpact/schema/levels";
29
32
 
30
33
  // Import formatters
31
34
  import {
@@ -41,17 +44,17 @@ import { sortTracksByName } from "./formatters/track/shared.js";
41
44
  /**
42
45
  * Create a chapter cover page
43
46
  * @param {Object} params
44
- * @param {string} params.emoji - Chapter emoji
47
+ * @param {string} params.emojiIcon - Chapter emoji
45
48
  * @param {string} params.title - Chapter title
46
49
  * @param {string} params.description - Chapter description
47
50
  * @returns {HTMLElement}
48
51
  */
49
- function createChapterCover({ emoji, title, description }) {
52
+ function createChapterCover({ emojiIcon, title, description }) {
50
53
  return div(
51
54
  { className: "chapter-cover" },
52
55
  h1(
53
56
  { className: "chapter-title" },
54
- emoji,
57
+ emojiIcon,
55
58
  " ",
56
59
  span({ className: "gradient-text" }, title),
57
60
  ),
@@ -111,7 +114,7 @@ function renderIndex(data) {
111
114
  { className: "page-header" },
112
115
  heading1(
113
116
  { className: "page-title" },
114
- `${framework.emoji} ${framework.title} Handouts`,
117
+ `${framework.emojiIcon} ${framework.title} Handouts`,
115
118
  ),
116
119
  p(
117
120
  { className: "page-description" },
@@ -190,7 +193,7 @@ function renderDriverHandout(data) {
190
193
  const content = div(
191
194
  {},
192
195
  createChapterCover({
193
- emoji: getConceptEmoji(framework, "driver"),
196
+ emojiIcon: getConceptEmoji(framework, "driver"),
194
197
  title: framework.entityDefinitions.driver.title,
195
198
  description: framework.entityDefinitions.driver.description,
196
199
  }),
@@ -227,13 +230,14 @@ function renderSkillHandout(data) {
227
230
  drivers: data.drivers,
228
231
  capabilities: data.capabilities,
229
232
  showBackLink: false,
233
+ showToolsAndPatterns: false,
230
234
  });
231
235
  });
232
236
 
233
237
  const content = div(
234
238
  {},
235
239
  createChapterCover({
236
- emoji: getConceptEmoji(framework, "skill"),
240
+ emojiIcon: getConceptEmoji(framework, "skill"),
237
241
  title: framework.entityDefinitions.skill.title,
238
242
  description: framework.entityDefinitions.skill.description,
239
243
  }),
@@ -261,7 +265,7 @@ function renderBehaviourHandout(data) {
261
265
  const content = div(
262
266
  {},
263
267
  createChapterCover({
264
- emoji: getConceptEmoji(framework, "behaviour"),
268
+ emojiIcon: getConceptEmoji(framework, "behaviour"),
265
269
  title: framework.entityDefinitions.behaviour.title,
266
270
  description: framework.entityDefinitions.behaviour.description,
267
271
  }),
@@ -322,7 +326,7 @@ function renderJobHandout(data) {
322
326
  {},
323
327
  // Disciplines chapter
324
328
  createChapterCover({
325
- emoji: getConceptEmoji(framework, "discipline"),
329
+ emojiIcon: getConceptEmoji(framework, "discipline"),
326
330
  title: framework.entityDefinitions.discipline.title,
327
331
  description: framework.entityDefinitions.discipline.description,
328
332
  }),
@@ -330,7 +334,7 @@ function renderJobHandout(data) {
330
334
 
331
335
  // Grades chapter (moved before Tracks)
332
336
  createChapterCover({
333
- emoji: getConceptEmoji(framework, "grade"),
337
+ emojiIcon: getConceptEmoji(framework, "grade"),
334
338
  title: framework.entityDefinitions.grade.title,
335
339
  description: framework.entityDefinitions.grade.description,
336
340
  }),
@@ -338,7 +342,7 @@ function renderJobHandout(data) {
338
342
 
339
343
  // Tracks chapter (moved after Grades)
340
344
  createChapterCover({
341
- emoji: getConceptEmoji(framework, "track"),
345
+ emojiIcon: getConceptEmoji(framework, "track"),
342
346
  title: framework.entityDefinitions.track.title,
343
347
  description: framework.entityDefinitions.track.description,
344
348
  }),
@@ -393,7 +397,7 @@ function populateBrandHeader(framework) {
393
397
  header.appendChild(
394
398
  a(
395
399
  { className: "brand-title", href: "#/" },
396
- `${framework.emoji} ${framework.title}`,
400
+ `${framework.emojiIcon} ${framework.title}`,
397
401
  ),
398
402
  );
399
403
  header.appendChild(span({ className: "brand-tag" }, framework.tag));
@@ -0,0 +1,43 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Engineering Pathway - Handout View</title>
7
+ <link rel="stylesheet" href="css/bundles/handout.css" />
8
+ <script type="importmap">
9
+ {
10
+ "imports": {
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",
16
+ "@forwardimpact/model": "/model/lib/index.js",
17
+ "@forwardimpact/model/derivation": "/model/lib/derivation.js",
18
+ "@forwardimpact/model/modifiers": "/model/lib/modifiers.js",
19
+ "@forwardimpact/model/agent": "/model/lib/agent.js",
20
+ "@forwardimpact/model/interview": "/model/lib/interview.js",
21
+ "@forwardimpact/model/job": "/model/lib/job.js",
22
+ "@forwardimpact/model/job-cache": "/model/lib/job-cache.js",
23
+ "@forwardimpact/model/checklist": "/model/lib/checklist.js",
24
+ "@forwardimpact/model/matching": "/model/lib/matching.js",
25
+ "@forwardimpact/model/profile": "/model/lib/profile.js",
26
+ "@forwardimpact/model/progression": "/model/lib/progression.js"
27
+ }
28
+ }
29
+ </script>
30
+ </head>
31
+ <body class="slide-view handout-view">
32
+ <header
33
+ id="page-brand-header"
34
+ class="page-brand-header"
35
+ style="display: none"
36
+ ></header>
37
+ <div id="slide-loading" class="slide-loading">
38
+ <p>Loading...</p>
39
+ </div>
40
+ <main id="handout-content"></main>
41
+ <script type="module" src="handout-main.js"></script>
42
+ </body>
43
+ </html>
@@ -4,17 +4,38 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Engineering Pathway</title>
7
- <link rel="stylesheet" href="css/bundles/app.css" />
7
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
+ <link
10
+ href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&display=swap"
11
+ rel="stylesheet"
12
+ />
8
13
  <link
9
14
  rel="stylesheet"
10
15
  href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism.min.css"
11
16
  />
17
+ <link rel="stylesheet" href="css/bundles/app.css" />
12
18
  <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js"></script>
13
19
  <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-markdown.min.js"></script>
14
20
  <script type="importmap">
15
21
  {
16
22
  "imports": {
17
- "mustache": "https://esm.sh/mustache@4.2.0"
23
+ "mustache": "https://esm.sh/mustache@4.2.0",
24
+ "@forwardimpact/schema": "/schema/lib/index.js",
25
+ "@forwardimpact/schema/levels": "/schema/lib/levels.js",
26
+ "@forwardimpact/schema/loader": "/schema/lib/loader.js",
27
+ "@forwardimpact/schema/validation": "/schema/lib/validation.js",
28
+ "@forwardimpact/model": "/model/lib/index.js",
29
+ "@forwardimpact/model/derivation": "/model/lib/derivation.js",
30
+ "@forwardimpact/model/modifiers": "/model/lib/modifiers.js",
31
+ "@forwardimpact/model/agent": "/model/lib/agent.js",
32
+ "@forwardimpact/model/interview": "/model/lib/interview.js",
33
+ "@forwardimpact/model/job": "/model/lib/job.js",
34
+ "@forwardimpact/model/job-cache": "/model/lib/job-cache.js",
35
+ "@forwardimpact/model/checklist": "/model/lib/checklist.js",
36
+ "@forwardimpact/model/matching": "/model/lib/matching.js",
37
+ "@forwardimpact/model/profile": "/model/lib/profile.js",
38
+ "@forwardimpact/model/progression": "/model/lib/progression.js"
18
39
  }
19
40
  }
20
41
  </script>
@@ -7,7 +7,7 @@
7
7
 
8
8
  import { createBadge } from "../components/card.js";
9
9
  import { formatLevel } from "./render.js";
10
- import { getCapabilityEmoji } from "../model/levels.js";
10
+ import { getCapabilityEmoji } from "@forwardimpact/schema/levels";
11
11
 
12
12
  /**
13
13
  * Create an external link element styled as a badge
@@ -181,13 +181,40 @@ export function toolToCardConfig(tool, capabilities) {
181
181
  // Create skills list as card content
182
182
  const skillsList = createSkillsList(tool.usages, capabilities);
183
183
 
184
+ // Create icon element if available
185
+ const icon = tool.simpleIcon
186
+ ? createToolIcon(tool.simpleIcon, tool.name)
187
+ : null;
188
+
184
189
  return {
185
190
  title: tool.name,
186
191
  description: tool.description,
187
192
  // Docs link in header badges (upper right)
188
193
  badges: tool.url ? [createExternalLink("Docs →", tool.url)] : [],
189
194
  content: skillsList,
195
+ icon,
196
+ };
197
+ }
198
+
199
+ /**
200
+ * Create a tool icon element using Simple Icons CDN
201
+ * @param {string} slug - Simple Icons slug (e.g., 'terraform', 'docker')
202
+ * @param {string} name - Tool name for alt text
203
+ * @returns {HTMLElement}
204
+ */
205
+ export function createToolIcon(slug, name) {
206
+ const img = document.createElement("img");
207
+ // Use black color for consistent monochrome appearance
208
+ img.src = `https://cdn.simpleicons.org/${slug}/000000`;
209
+ img.alt = `${name} icon`;
210
+ img.className = "tool-icon";
211
+ img.width = 28;
212
+ img.height = 28;
213
+ // Gracefully handle missing icons
214
+ img.onerror = () => {
215
+ img.style.display = "none";
190
216
  };
217
+ return img;
191
218
  }
192
219
 
193
220
  /**
@@ -5,7 +5,7 @@
5
5
  * Provides consistent key generation and get-or-create pattern.
6
6
  */
7
7
 
8
- import { deriveJob } from "../model/derivation.js";
8
+ import { deriveJob } from "@forwardimpact/model/derivation";
9
9
 
10
10
  /** @type {Map<string, Object>} */
11
11
  const cache = new Map();
@@ -5,7 +5,7 @@
5
5
  import {
6
6
  SKILL_LEVEL_ORDER,
7
7
  BEHAVIOUR_MATURITY_ORDER,
8
- } from "../model/levels.js";
8
+ } from "@forwardimpact/schema/levels";
9
9
 
10
10
  /**
11
11
  * Get the main content container