@forwardimpact/pathway 0.1.0 → 0.3.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.
- package/app/commands/agent.js +119 -31
- package/app/commands/command-factory.js +3 -3
- package/app/commands/interview.js +14 -7
- package/app/commands/job.js +52 -33
- package/app/commands/progress.js +14 -7
- package/app/commands/serve.js +5 -0
- package/app/commands/stage.js +0 -10
- package/app/commands/track.js +5 -8
- package/app/components/builder.js +117 -30
- package/app/css/components/surfaces.css +16 -0
- package/app/formatters/agent/profile.js +30 -115
- package/app/formatters/agent/skill.js +23 -44
- package/app/formatters/behaviour/dom.js +3 -0
- package/app/formatters/behaviour/microdata.js +106 -0
- package/app/formatters/discipline/dom.js +28 -1
- package/app/formatters/discipline/microdata.js +117 -0
- package/app/formatters/discipline/shared.js +49 -8
- package/app/formatters/driver/dom.js +3 -0
- package/app/formatters/driver/microdata.js +91 -0
- package/app/formatters/grade/dom.js +5 -4
- package/app/formatters/grade/microdata.js +151 -0
- package/app/formatters/index.js +32 -1
- package/app/formatters/interview/shared.js +13 -8
- package/app/formatters/job/description.js +70 -81
- package/app/formatters/job/dom.js +40 -113
- package/app/formatters/job/markdown.js +17 -13
- package/app/formatters/json-ld.js +242 -0
- package/app/formatters/microdata-shared.js +184 -0
- package/app/formatters/progress/shared.js +14 -11
- package/app/formatters/shared.js +7 -2
- package/app/formatters/skill/dom.js +3 -0
- package/app/formatters/skill/microdata.js +151 -0
- package/app/formatters/stage/dom.js +3 -18
- package/app/formatters/stage/microdata.js +110 -0
- package/app/formatters/stage/shared.js +0 -27
- package/app/formatters/track/dom.js +5 -30
- package/app/formatters/track/markdown.js +2 -25
- package/app/formatters/track/microdata.js +111 -0
- package/app/formatters/track/shared.js +6 -58
- package/app/handout-main.js +26 -12
- package/app/handout.html +7 -0
- package/app/index.html +11 -0
- package/app/lib/card-mappers.js +17 -12
- package/app/lib/form-controls.js +64 -1
- package/app/lib/job-cache.js +12 -9
- package/app/lib/render.js +8 -1
- package/app/lib/template-loader.js +75 -0
- package/app/lib/yaml-loader.js +25 -8
- package/app/main.js +8 -4
- package/app/model/agent.js +158 -130
- package/app/model/checklist.js +57 -91
- package/app/model/derivation.js +135 -68
- package/app/model/index-generator.js +1 -7
- package/app/model/job.js +19 -13
- package/app/model/levels.js +20 -12
- package/app/model/loader.js +41 -17
- package/app/model/matching.js +33 -3
- package/app/model/profile.js +38 -45
- package/app/model/schema-validation.js +438 -0
- package/app/model/validation.js +747 -68
- package/app/pages/agent-builder.js +125 -28
- package/app/pages/assessment-results.js +10 -4
- package/app/pages/discipline.js +36 -6
- package/app/pages/driver.js +9 -47
- package/app/pages/interview-builder.js +3 -1
- package/app/pages/interview.js +15 -4
- package/app/pages/job-builder.js +4 -1
- package/app/pages/job.js +43 -8
- package/app/pages/landing.js +10 -10
- package/app/pages/progress-builder.js +3 -1
- package/app/pages/progress.js +78 -26
- package/app/pages/self-assessment.js +3 -3
- package/app/pages/stage.js +3 -126
- package/app/slide-main.js +45 -17
- package/app/slides/index.js +3 -1
- package/app/slides/overview.js +40 -4
- package/app/slides/progress.js +4 -2
- package/app/slides.html +7 -0
- package/bin/pathway.js +28 -75
- package/examples/agents/.claude/skills/architecture-design/SKILL.md +58 -16
- package/examples/agents/.claude/skills/cloud-platforms/SKILL.md +59 -18
- package/examples/agents/.claude/skills/code-quality-review/SKILL.md +58 -17
- package/examples/agents/.claude/skills/devops-cicd/SKILL.md +64 -18
- package/examples/agents/.claude/skills/full-stack-development/SKILL.md +59 -15
- package/examples/agents/.claude/skills/sre-practices/SKILL.md +64 -18
- package/examples/agents/.claude/skills/technical-debt-management/SKILL.md +58 -17
- package/examples/agents/.github/agents/se-platform-code.agent.md +39 -88
- package/examples/agents/.github/agents/se-platform-plan.agent.md +41 -88
- package/examples/agents/.github/agents/se-platform-review.agent.md +38 -15
- package/examples/agents/.vscode/settings.json +1 -1
- package/examples/behaviours/outcome_ownership.yaml +1 -2
- package/examples/behaviours/polymathic_knowledge.yaml +1 -2
- package/examples/behaviours/precise_communication.yaml +1 -2
- package/examples/behaviours/relentless_curiosity.yaml +1 -2
- package/examples/behaviours/systems_thinking.yaml +1 -2
- package/examples/capabilities/business.yaml +80 -142
- package/examples/capabilities/delivery.yaml +155 -219
- package/examples/capabilities/people.yaml +2 -34
- package/examples/capabilities/reliability.yaml +161 -80
- package/examples/capabilities/scale.yaml +234 -252
- package/examples/copilot-setup-steps.yaml +25 -0
- package/examples/devcontainer.yaml +21 -0
- package/examples/disciplines/_index.yaml +1 -0
- package/examples/disciplines/data_engineering.yaml +14 -12
- package/examples/disciplines/engineering_management.yaml +63 -0
- package/examples/disciplines/software_engineering.yaml +14 -12
- package/examples/drivers.yaml +1 -4
- package/examples/framework.yaml +1 -2
- package/examples/grades.yaml +14 -15
- package/examples/questions/behaviours/outcome_ownership.yaml +1 -2
- package/examples/questions/behaviours/polymathic_knowledge.yaml +1 -2
- package/examples/questions/behaviours/precise_communication.yaml +1 -2
- package/examples/questions/behaviours/relentless_curiosity.yaml +1 -2
- package/examples/questions/behaviours/systems_thinking.yaml +1 -2
- package/examples/questions/skills/architecture_design.yaml +1 -2
- package/examples/questions/skills/cloud_platforms.yaml +1 -2
- package/examples/questions/skills/code_quality.yaml +1 -2
- package/examples/questions/skills/data_modeling.yaml +1 -2
- package/examples/questions/skills/devops.yaml +1 -2
- package/examples/questions/skills/full_stack_development.yaml +1 -2
- package/examples/questions/skills/sre_practices.yaml +1 -2
- package/examples/questions/skills/stakeholder_management.yaml +1 -2
- package/examples/questions/skills/team_collaboration.yaml +1 -2
- package/examples/questions/skills/technical_writing.yaml +1 -2
- package/examples/self-assessments.yaml +1 -3
- package/examples/stages.yaml +101 -46
- package/examples/tracks/_index.yaml +0 -1
- package/examples/tracks/platform.yaml +8 -13
- package/examples/tracks/sre.yaml +8 -18
- package/examples/vscode-settings.yaml +2 -7
- package/package.json +9 -3
- package/templates/agent.template.md +65 -0
- package/templates/job.template.md +47 -0
- package/templates/skill.template.md +28 -0
- package/examples/agents/.claude/skills/data-modeling/SKILL.md +0 -99
- package/examples/agents/.claude/skills/developer-experience/SKILL.md +0 -99
- package/examples/agents/.claude/skills/knowledge-management/SKILL.md +0 -100
- package/examples/agents/.claude/skills/pattern-generalization/SKILL.md +0 -102
- package/examples/agents/.claude/skills/technical-writing/SKILL.md +0 -129
- package/examples/tracks/manager.yaml +0 -53
package/app/pages/landing.js
CHANGED
|
@@ -89,16 +89,16 @@ export function renderLanding() {
|
|
|
89
89
|
label: "Disciplines",
|
|
90
90
|
href: "/discipline",
|
|
91
91
|
}),
|
|
92
|
-
createStatCard({
|
|
93
|
-
value: data.tracks.length,
|
|
94
|
-
label: "Tracks",
|
|
95
|
-
href: "/track",
|
|
96
|
-
}),
|
|
97
92
|
createStatCard({
|
|
98
93
|
value: data.grades.length,
|
|
99
94
|
label: "Grades",
|
|
100
95
|
href: "/grade",
|
|
101
96
|
}),
|
|
97
|
+
createStatCard({
|
|
98
|
+
value: data.tracks.length,
|
|
99
|
+
label: "Tracks",
|
|
100
|
+
href: "/track",
|
|
101
|
+
}),
|
|
102
102
|
createStatCard({
|
|
103
103
|
value: data.skills.length,
|
|
104
104
|
label: "Skills",
|
|
@@ -140,16 +140,16 @@ export function renderLanding() {
|
|
|
140
140
|
`${data.disciplines.length} ${framework.entityDefinitions.discipline.title.toLowerCase()} — ${framework.entityDefinitions.discipline.description.trim().split("\n")[0]}`,
|
|
141
141
|
"/discipline",
|
|
142
142
|
),
|
|
143
|
-
createQuickLinkCard(
|
|
144
|
-
`${getConceptEmoji(framework, "track")} ${framework.entityDefinitions.track.title}`,
|
|
145
|
-
`${data.tracks.length} ${framework.entityDefinitions.track.title.toLowerCase()} — ${framework.entityDefinitions.track.description.trim().split("\n")[0]}`,
|
|
146
|
-
"/track",
|
|
147
|
-
),
|
|
148
143
|
createQuickLinkCard(
|
|
149
144
|
`${getConceptEmoji(framework, "grade")} ${framework.entityDefinitions.grade.title}`,
|
|
150
145
|
`${data.grades.length} ${framework.entityDefinitions.grade.title.toLowerCase()} — ${framework.entityDefinitions.grade.description.trim().split("\n")[0]}`,
|
|
151
146
|
"/grade",
|
|
152
147
|
),
|
|
148
|
+
createQuickLinkCard(
|
|
149
|
+
`${getConceptEmoji(framework, "track")} ${framework.entityDefinitions.track.title}`,
|
|
150
|
+
`${data.tracks.length} ${framework.entityDefinitions.track.title.toLowerCase()} — ${framework.entityDefinitions.track.description.trim().split("\n")[0]}`,
|
|
151
|
+
"/track",
|
|
152
|
+
),
|
|
153
153
|
createQuickLinkCard(
|
|
154
154
|
`${getConceptEmoji(framework, "skill")} ${framework.entityDefinitions.skill.title}`,
|
|
155
155
|
`${data.skills.length} ${framework.entityDefinitions.skill.title.toLowerCase()} across ${capabilityCount} capabilities — ${framework.entityDefinitions.skill.description.trim().split("\n")[0]}`,
|
|
@@ -30,7 +30,9 @@ export function renderCareerProgress() {
|
|
|
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
36
|
renderPreview: createProgressPreview,
|
|
35
37
|
labels: {
|
|
36
38
|
grade: "Current Grade",
|
package/app/pages/progress.js
CHANGED
|
@@ -13,7 +13,10 @@ import {
|
|
|
13
13
|
} from "../components/comparison-radar.js";
|
|
14
14
|
import { createProgressionTable } from "../components/progression-table.js";
|
|
15
15
|
import { renderError } from "../components/error-page.js";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
createSelectWithValue,
|
|
18
|
+
createDisciplineSelect,
|
|
19
|
+
} from "../lib/form-controls.js";
|
|
17
20
|
import {
|
|
18
21
|
prepareCurrentJob,
|
|
19
22
|
prepareCustomProgression,
|
|
@@ -26,18 +29,29 @@ import {
|
|
|
26
29
|
* @param {Object} params - Route params
|
|
27
30
|
*/
|
|
28
31
|
export function renderProgressDetail(params) {
|
|
29
|
-
const { discipline: disciplineId,
|
|
32
|
+
const { discipline: disciplineId, grade: gradeId, track: trackId } = params;
|
|
30
33
|
const { data } = getState();
|
|
31
34
|
|
|
32
35
|
// Find the components
|
|
33
36
|
const discipline = data.disciplines.find((d) => d.id === disciplineId);
|
|
34
|
-
const track = data.tracks.find((t) => t.id === trackId);
|
|
35
37
|
const grade = data.grades.find((g) => g.id === gradeId);
|
|
38
|
+
const track = trackId ? data.tracks.find((t) => t.id === trackId) : null;
|
|
36
39
|
|
|
37
|
-
if (!discipline || !
|
|
40
|
+
if (!discipline || !grade) {
|
|
38
41
|
renderError({
|
|
39
42
|
title: "Role Not Found",
|
|
40
|
-
message: "Invalid role combination.
|
|
43
|
+
message: "Invalid role combination. Discipline or grade not found.",
|
|
44
|
+
backPath: "/career-progress",
|
|
45
|
+
backText: "← Back to Career Progress",
|
|
46
|
+
});
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// If trackId was provided but not found, error
|
|
51
|
+
if (trackId && !track) {
|
|
52
|
+
renderError({
|
|
53
|
+
title: "Role Not Found",
|
|
54
|
+
message: `Track "${trackId}" not found.`,
|
|
41
55
|
backPath: "/career-progress",
|
|
42
56
|
backText: "← Back to Career Progress",
|
|
43
57
|
});
|
|
@@ -79,8 +93,9 @@ export function renderProgressDetail(params) {
|
|
|
79
93
|
a({ href: `#/discipline/${discipline.id}` }, discipline.specialization),
|
|
80
94
|
" × ",
|
|
81
95
|
a({ href: `#/grade/${grade.id}` }, grade.id),
|
|
82
|
-
|
|
83
|
-
|
|
96
|
+
track
|
|
97
|
+
? [" × ", a({ href: `#/track/${track.id}` }, track.name)]
|
|
98
|
+
: " (Generalist)",
|
|
84
99
|
),
|
|
85
100
|
),
|
|
86
101
|
|
|
@@ -120,7 +135,9 @@ export function renderProgressDetail(params) {
|
|
|
120
135
|
{ className: "page-actions", style: "margin-top: 2rem" },
|
|
121
136
|
a(
|
|
122
137
|
{
|
|
123
|
-
href:
|
|
138
|
+
href: trackId
|
|
139
|
+
? `#/job/${disciplineId}/${gradeId}/${trackId}`
|
|
140
|
+
: `#/job/${disciplineId}/${gradeId}`,
|
|
124
141
|
className: "btn btn-secondary",
|
|
125
142
|
},
|
|
126
143
|
"View Full Job Definition",
|
|
@@ -160,17 +177,29 @@ function createComparisonSelectorsSection({
|
|
|
160
177
|
// State to track current selections - default to same discipline, same track, next grade
|
|
161
178
|
let selectedDisciplineId = discipline.id;
|
|
162
179
|
let selectedGradeId = nextGrade?.id || "";
|
|
163
|
-
let selectedTrackId = currentTrack
|
|
180
|
+
let selectedTrackId = currentTrack?.id || "";
|
|
164
181
|
|
|
165
182
|
// Get available options based on selected discipline
|
|
166
183
|
function getAvailableOptions(disciplineId) {
|
|
167
184
|
const selectedDisc = data.disciplines.find((d) => d.id === disciplineId);
|
|
168
|
-
if (!selectedDisc)
|
|
185
|
+
if (!selectedDisc)
|
|
186
|
+
return { grades: [], tracks: [], allowsTrackless: false };
|
|
169
187
|
|
|
170
188
|
const validGrades = [];
|
|
171
189
|
const validTracks = new Set();
|
|
190
|
+
let allowsTrackless = false;
|
|
172
191
|
|
|
173
192
|
for (const grade of data.grades) {
|
|
193
|
+
// Check trackless combination
|
|
194
|
+
if (
|
|
195
|
+
isValidCombination({ discipline: selectedDisc, grade, track: null })
|
|
196
|
+
) {
|
|
197
|
+
if (!validGrades.find((g) => g.id === grade.id)) {
|
|
198
|
+
validGrades.push(grade);
|
|
199
|
+
}
|
|
200
|
+
allowsTrackless = true;
|
|
201
|
+
}
|
|
202
|
+
// Check each track combination
|
|
174
203
|
for (const track of data.tracks) {
|
|
175
204
|
if (isValidCombination({ discipline: selectedDisc, grade, track })) {
|
|
176
205
|
if (!validGrades.find((g) => g.id === grade.id)) {
|
|
@@ -186,6 +215,7 @@ function createComparisonSelectorsSection({
|
|
|
186
215
|
tracks: data.tracks
|
|
187
216
|
.filter((t) => validTracks.has(t.id))
|
|
188
217
|
.sort((a, b) => a.name.localeCompare(b.name)),
|
|
218
|
+
allowsTrackless,
|
|
189
219
|
};
|
|
190
220
|
}
|
|
191
221
|
|
|
@@ -196,13 +226,14 @@ function createComparisonSelectorsSection({
|
|
|
196
226
|
// Clear previous results
|
|
197
227
|
comparisonResultsContainer.innerHTML = "";
|
|
198
228
|
|
|
199
|
-
|
|
229
|
+
// Track can be empty string for generalist, but discipline and grade are required
|
|
230
|
+
if (!selectedDisciplineId || !selectedGradeId) {
|
|
200
231
|
comparisonResultsContainer.appendChild(
|
|
201
232
|
div(
|
|
202
233
|
{ className: "comparison-placeholder" },
|
|
203
234
|
p(
|
|
204
235
|
{ className: "text-muted" },
|
|
205
|
-
"Select a discipline
|
|
236
|
+
"Select a discipline and grade to see the comparison.",
|
|
206
237
|
),
|
|
207
238
|
),
|
|
208
239
|
);
|
|
@@ -213,9 +244,12 @@ function createComparisonSelectorsSection({
|
|
|
213
244
|
(d) => d.id === selectedDisciplineId,
|
|
214
245
|
);
|
|
215
246
|
const targetGrade = data.grades.find((g) => g.id === selectedGradeId);
|
|
216
|
-
|
|
247
|
+
// selectedTrackId can be empty string for generalist
|
|
248
|
+
const targetTrack = selectedTrackId
|
|
249
|
+
? data.tracks.find((t) => t.id === selectedTrackId)
|
|
250
|
+
: null;
|
|
217
251
|
|
|
218
|
-
if (!targetDiscipline || !targetGrade
|
|
252
|
+
if (!targetDiscipline || !targetGrade) {
|
|
219
253
|
return;
|
|
220
254
|
}
|
|
221
255
|
|
|
@@ -223,7 +257,7 @@ function createComparisonSelectorsSection({
|
|
|
223
257
|
if (
|
|
224
258
|
targetDiscipline.id === discipline.id &&
|
|
225
259
|
targetGrade.id === currentGrade.id &&
|
|
226
|
-
targetTrack
|
|
260
|
+
targetTrack?.id === currentTrack?.id
|
|
227
261
|
) {
|
|
228
262
|
comparisonResultsContainer.appendChild(
|
|
229
263
|
div(
|
|
@@ -343,10 +377,12 @@ function createComparisonSelectorsSection({
|
|
|
343
377
|
{ className: "page-actions" },
|
|
344
378
|
a(
|
|
345
379
|
{
|
|
346
|
-
href:
|
|
380
|
+
href: targetTrack
|
|
381
|
+
? `#/job/${targetDiscipline.id}/${targetGrade.id}/${targetTrack.id}`
|
|
382
|
+
: `#/job/${targetDiscipline.id}/${targetGrade.id}`,
|
|
347
383
|
className: "btn btn-secondary",
|
|
348
384
|
},
|
|
349
|
-
`View ${targetGrade.id} ${targetTrack.name} Job Definition →`,
|
|
385
|
+
`View ${targetGrade.id}${targetTrack ? ` ${targetTrack.name}` : ""} Job Definition →`,
|
|
350
386
|
),
|
|
351
387
|
),
|
|
352
388
|
);
|
|
@@ -394,10 +430,20 @@ function createComparisonSelectorsSection({
|
|
|
394
430
|
// Update track selector
|
|
395
431
|
if (trackSelectEl) {
|
|
396
432
|
trackSelectEl.innerHTML = "";
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
433
|
+
|
|
434
|
+
// Add generalist option if discipline allows trackless
|
|
435
|
+
if (availableOptions.allowsTrackless) {
|
|
436
|
+
const generalistOpt = document.createElement("option");
|
|
437
|
+
generalistOpt.value = "";
|
|
438
|
+
generalistOpt.textContent = "Generalist";
|
|
439
|
+
trackSelectEl.appendChild(generalistOpt);
|
|
440
|
+
} else {
|
|
441
|
+
const placeholderOpt = document.createElement("option");
|
|
442
|
+
placeholderOpt.value = "";
|
|
443
|
+
placeholderOpt.textContent = "Select track...";
|
|
444
|
+
placeholderOpt.disabled = true;
|
|
445
|
+
trackSelectEl.appendChild(placeholderOpt);
|
|
446
|
+
}
|
|
401
447
|
|
|
402
448
|
for (const track of availableOptions.tracks) {
|
|
403
449
|
const opt = document.createElement("option");
|
|
@@ -407,8 +453,16 @@ function createComparisonSelectorsSection({
|
|
|
407
453
|
}
|
|
408
454
|
|
|
409
455
|
// Try to keep current selection if valid
|
|
410
|
-
|
|
456
|
+
const hasValidTrack = availableOptions.tracks.find(
|
|
457
|
+
(t) => t.id === selectedTrackId,
|
|
458
|
+
);
|
|
459
|
+
const isValidGeneralist =
|
|
460
|
+
selectedTrackId === "" && availableOptions.allowsTrackless;
|
|
461
|
+
if (hasValidTrack || isValidGeneralist) {
|
|
411
462
|
trackSelectEl.value = selectedTrackId;
|
|
463
|
+
} else if (availableOptions.allowsTrackless) {
|
|
464
|
+
selectedTrackId = "";
|
|
465
|
+
trackSelectEl.value = "";
|
|
412
466
|
} else {
|
|
413
467
|
selectedTrackId = "";
|
|
414
468
|
trackSelectEl.value = "";
|
|
@@ -462,11 +516,9 @@ function createComparisonSelectorsSection({
|
|
|
462
516
|
div(
|
|
463
517
|
{ className: "form-group" },
|
|
464
518
|
label({ for: "compare-discipline-select" }, "Target Discipline"),
|
|
465
|
-
|
|
519
|
+
createDisciplineSelect({
|
|
466
520
|
id: "compare-discipline-select",
|
|
467
|
-
|
|
468
|
-
a.specialization.localeCompare(b.specialization),
|
|
469
|
-
),
|
|
521
|
+
disciplines: data.disciplines,
|
|
470
522
|
initialValue: selectedDisciplineId,
|
|
471
523
|
placeholder: "Select discipline...",
|
|
472
524
|
getDisplayName: (d) => d.specialization,
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
} from "../lib/render.js";
|
|
18
18
|
import { getState } from "../lib/state.js";
|
|
19
19
|
import { createBadge } from "../components/card.js";
|
|
20
|
-
import {
|
|
20
|
+
import { createDisciplineSelect } from "../lib/form-controls.js";
|
|
21
21
|
import {
|
|
22
22
|
SKILL_LEVEL_ORDER,
|
|
23
23
|
BEHAVIOUR_MATURITY_ORDER,
|
|
@@ -306,9 +306,9 @@ function renderIntroStep(data) {
|
|
|
306
306
|
"Select a discipline to highlight which skills are most relevant for that role. " +
|
|
307
307
|
"You can still assess all skills.",
|
|
308
308
|
),
|
|
309
|
-
|
|
309
|
+
createDisciplineSelect({
|
|
310
310
|
id: "discipline-filter-select",
|
|
311
|
-
|
|
311
|
+
disciplines: data.disciplines,
|
|
312
312
|
initialValue: assessmentState.discipline || "",
|
|
313
313
|
placeholder: "Select discipline",
|
|
314
314
|
onChange: (value) => {
|
package/app/pages/stage.js
CHANGED
|
@@ -5,14 +5,8 @@
|
|
|
5
5
|
import { render, div, h1, h2, p, span, a, section } from "../lib/render.js";
|
|
6
6
|
import { getState } from "../lib/state.js";
|
|
7
7
|
import { createCardList } from "../components/list.js";
|
|
8
|
-
import { createDetailHeader } from "../components/detail.js";
|
|
9
8
|
import { renderNotFound } from "../components/error-page.js";
|
|
10
|
-
import {
|
|
11
|
-
prepareStagesList,
|
|
12
|
-
prepareStageDetail,
|
|
13
|
-
getStageEmoji,
|
|
14
|
-
} from "../formatters/stage/index.js";
|
|
15
|
-
import { createBadge } from "../components/card.js";
|
|
9
|
+
import { prepareStagesList, stageToDOM } from "../formatters/stage/index.js";
|
|
16
10
|
|
|
17
11
|
/**
|
|
18
12
|
* Map stage to card configuration
|
|
@@ -24,7 +18,6 @@ function stageToCardConfig(stage) {
|
|
|
24
18
|
title: `${stage.emoji || "🔄"} ${stage.name}`,
|
|
25
19
|
description: stage.truncatedDescription,
|
|
26
20
|
href: `/stage/${stage.id}`,
|
|
27
|
-
meta: [createBadge(`${stage.tools.length} tools`, "default")],
|
|
28
21
|
};
|
|
29
22
|
}
|
|
30
23
|
|
|
@@ -110,122 +103,6 @@ export function renderStageDetail(params) {
|
|
|
110
103
|
return;
|
|
111
104
|
}
|
|
112
105
|
|
|
113
|
-
//
|
|
114
|
-
|
|
115
|
-
const emoji = getStageEmoji(stages, stage.id);
|
|
116
|
-
|
|
117
|
-
const page = div(
|
|
118
|
-
{ className: "stage-detail" },
|
|
119
|
-
createDetailHeader({
|
|
120
|
-
title: `${emoji} ${view.name}`,
|
|
121
|
-
description: view.description,
|
|
122
|
-
backLink: "/stage",
|
|
123
|
-
backText: "← Back to Stages",
|
|
124
|
-
}),
|
|
125
|
-
|
|
126
|
-
// Tools section
|
|
127
|
-
view.tools.length > 0
|
|
128
|
-
? section(
|
|
129
|
-
{ className: "section section-detail" },
|
|
130
|
-
h2({ className: "section-title" }, "Available Tools"),
|
|
131
|
-
div(
|
|
132
|
-
{ className: "tool-badges" },
|
|
133
|
-
...view.tools.map((tool) =>
|
|
134
|
-
span(
|
|
135
|
-
{ className: "badge badge-tool", title: tool.label },
|
|
136
|
-
`${tool.icon} ${tool.label}`,
|
|
137
|
-
),
|
|
138
|
-
),
|
|
139
|
-
),
|
|
140
|
-
)
|
|
141
|
-
: null,
|
|
142
|
-
|
|
143
|
-
// Entry/Exit Criteria
|
|
144
|
-
view.entryCriteria.length > 0 || view.exitCriteria.length > 0
|
|
145
|
-
? section(
|
|
146
|
-
{ className: "section section-detail" },
|
|
147
|
-
div(
|
|
148
|
-
{ className: "content-columns" },
|
|
149
|
-
// Entry criteria column
|
|
150
|
-
view.entryCriteria.length > 0
|
|
151
|
-
? div(
|
|
152
|
-
{ className: "column" },
|
|
153
|
-
h2({ className: "section-title" }, "Entry Criteria"),
|
|
154
|
-
div(
|
|
155
|
-
{ className: "criteria-list" },
|
|
156
|
-
...view.entryCriteria.map((item) =>
|
|
157
|
-
div({ className: "criteria-item" }, `✓ ${item}`),
|
|
158
|
-
),
|
|
159
|
-
),
|
|
160
|
-
)
|
|
161
|
-
: null,
|
|
162
|
-
// Exit criteria column
|
|
163
|
-
view.exitCriteria.length > 0
|
|
164
|
-
? div(
|
|
165
|
-
{ className: "column" },
|
|
166
|
-
h2({ className: "section-title" }, "Exit Criteria"),
|
|
167
|
-
div(
|
|
168
|
-
{ className: "criteria-list" },
|
|
169
|
-
...view.exitCriteria.map((item) =>
|
|
170
|
-
div({ className: "criteria-item" }, `✓ ${item}`),
|
|
171
|
-
),
|
|
172
|
-
),
|
|
173
|
-
)
|
|
174
|
-
: null,
|
|
175
|
-
),
|
|
176
|
-
)
|
|
177
|
-
: null,
|
|
178
|
-
|
|
179
|
-
// Constraints
|
|
180
|
-
view.constraints.length > 0
|
|
181
|
-
? section(
|
|
182
|
-
{ className: "section section-detail" },
|
|
183
|
-
h2({ className: "section-title" }, "Constraints"),
|
|
184
|
-
div(
|
|
185
|
-
{ className: "constraint-list" },
|
|
186
|
-
...view.constraints.map((item) =>
|
|
187
|
-
div({ className: "constraint-item" }, `⚠️ ${item}`),
|
|
188
|
-
),
|
|
189
|
-
),
|
|
190
|
-
)
|
|
191
|
-
: null,
|
|
192
|
-
|
|
193
|
-
// Handoffs
|
|
194
|
-
view.handoffs.length > 0
|
|
195
|
-
? section(
|
|
196
|
-
{ className: "section section-detail" },
|
|
197
|
-
h2({ className: "section-title" }, "Handoffs"),
|
|
198
|
-
div(
|
|
199
|
-
{ className: "handoff-list" },
|
|
200
|
-
...view.handoffs.map((handoff) => {
|
|
201
|
-
const targetStage = stages.find((s) => s.id === handoff.target);
|
|
202
|
-
const targetEmoji = getStageEmoji(stages, handoff.target);
|
|
203
|
-
return div(
|
|
204
|
-
{ className: "card handoff-card" },
|
|
205
|
-
div(
|
|
206
|
-
{ className: "handoff-header" },
|
|
207
|
-
targetStage
|
|
208
|
-
? a(
|
|
209
|
-
{
|
|
210
|
-
href: `#/stage/${handoff.target}`,
|
|
211
|
-
className: "handoff-link",
|
|
212
|
-
},
|
|
213
|
-
`${targetEmoji} ${handoff.label}`,
|
|
214
|
-
)
|
|
215
|
-
: span({}, `${targetEmoji} ${handoff.label}`),
|
|
216
|
-
),
|
|
217
|
-
handoff.prompt
|
|
218
|
-
? p(
|
|
219
|
-
{ className: "handoff-prompt text-muted" },
|
|
220
|
-
handoff.prompt,
|
|
221
|
-
)
|
|
222
|
-
: null,
|
|
223
|
-
);
|
|
224
|
-
}),
|
|
225
|
-
),
|
|
226
|
-
)
|
|
227
|
-
: null,
|
|
228
|
-
);
|
|
229
|
-
|
|
230
|
-
render(page);
|
|
106
|
+
// Use DOM formatter - it handles transformation internally
|
|
107
|
+
render(stageToDOM(stage, { stages }));
|
|
231
108
|
}
|
package/app/slide-main.js
CHANGED
|
@@ -9,7 +9,7 @@ import { setData, getState } from "./lib/state.js";
|
|
|
9
9
|
import { loadAllData } from "./lib/yaml-loader.js";
|
|
10
10
|
import { span, a } from "./lib/render.js";
|
|
11
11
|
import { generateAllJobs } from "./model/derivation.js";
|
|
12
|
-
import {
|
|
12
|
+
import { sortTracksByName } from "./formatters/track/shared.js";
|
|
13
13
|
|
|
14
14
|
// Import slide renderers
|
|
15
15
|
import { renderChapterSlide } from "./slides/chapter.js";
|
|
@@ -150,13 +150,21 @@ function setupRoutes() {
|
|
|
150
150
|
renderTrackSlide({ render: renderSlide, data: getState().data, params });
|
|
151
151
|
});
|
|
152
152
|
|
|
153
|
-
// Jobs
|
|
154
|
-
router.on("/job/:discipline/:track
|
|
153
|
+
// Jobs - new format: discipline/grade/track (track optional)
|
|
154
|
+
router.on("/job/:discipline/:grade/:track", (params) => {
|
|
155
155
|
renderJobSlide({ render: renderSlide, data: getState().data, params });
|
|
156
156
|
});
|
|
157
157
|
|
|
158
|
-
|
|
159
|
-
|
|
158
|
+
router.on("/job/:discipline/:grade", (params) => {
|
|
159
|
+
renderJobSlide({
|
|
160
|
+
render: renderSlide,
|
|
161
|
+
data: getState().data,
|
|
162
|
+
params: { ...params, track: null },
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Interviews - new format: discipline/grade/track (track optional)
|
|
167
|
+
router.on("/interview/:discipline/:grade/:track", (params) => {
|
|
160
168
|
renderInterviewSlide({
|
|
161
169
|
render: renderSlide,
|
|
162
170
|
data: getState().data,
|
|
@@ -164,10 +172,26 @@ function setupRoutes() {
|
|
|
164
172
|
});
|
|
165
173
|
});
|
|
166
174
|
|
|
167
|
-
|
|
168
|
-
|
|
175
|
+
router.on("/interview/:discipline/:grade", (params) => {
|
|
176
|
+
renderInterviewSlide({
|
|
177
|
+
render: renderSlide,
|
|
178
|
+
data: getState().data,
|
|
179
|
+
params: { ...params, track: null },
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// Progress - new format: discipline/grade/track (track optional)
|
|
184
|
+
router.on("/progress/:discipline/:grade/:track", (params) => {
|
|
169
185
|
renderProgressSlide({ render: renderSlide, data: getState().data, params });
|
|
170
186
|
});
|
|
187
|
+
|
|
188
|
+
router.on("/progress/:discipline/:grade", (params) => {
|
|
189
|
+
renderProgressSlide({
|
|
190
|
+
render: renderSlide,
|
|
191
|
+
data: getState().data,
|
|
192
|
+
params: { ...params, track: null },
|
|
193
|
+
});
|
|
194
|
+
});
|
|
171
195
|
}
|
|
172
196
|
|
|
173
197
|
/**
|
|
@@ -187,15 +211,7 @@ function buildSlideOrder(data) {
|
|
|
187
211
|
data.disciplines.forEach((d) => order.push(`/discipline/${d.id}`));
|
|
188
212
|
}
|
|
189
213
|
|
|
190
|
-
// Tracks
|
|
191
|
-
if (data.tracks && data.tracks.length > 0) {
|
|
192
|
-
boundaries.push(order.length);
|
|
193
|
-
order.push("/chapter/track");
|
|
194
|
-
order.push("/overview/track");
|
|
195
|
-
sortTracksByType(data.tracks).forEach((t) => order.push(`/track/${t.id}`));
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Grades
|
|
214
|
+
// Grades (moved before Tracks)
|
|
199
215
|
if (data.grades && data.grades.length > 0) {
|
|
200
216
|
boundaries.push(order.length);
|
|
201
217
|
order.push("/chapter/grade");
|
|
@@ -203,6 +219,14 @@ function buildSlideOrder(data) {
|
|
|
203
219
|
data.grades.forEach((g) => order.push(`/grade/${g.id}`));
|
|
204
220
|
}
|
|
205
221
|
|
|
222
|
+
// Tracks (moved after Grades)
|
|
223
|
+
if (data.tracks && data.tracks.length > 0) {
|
|
224
|
+
boundaries.push(order.length);
|
|
225
|
+
order.push("/chapter/track");
|
|
226
|
+
order.push("/overview/track");
|
|
227
|
+
sortTracksByName(data.tracks).forEach((t) => order.push(`/track/${t.id}`));
|
|
228
|
+
}
|
|
229
|
+
|
|
206
230
|
// Skills
|
|
207
231
|
if (data.skills && data.skills.length > 0) {
|
|
208
232
|
boundaries.push(order.length);
|
|
@@ -241,7 +265,11 @@ function buildSlideOrder(data) {
|
|
|
241
265
|
order.push("/chapter/job");
|
|
242
266
|
order.push("/overview/job");
|
|
243
267
|
jobs.forEach((job) =>
|
|
244
|
-
order.push(
|
|
268
|
+
order.push(
|
|
269
|
+
job.track
|
|
270
|
+
? `/job/${job.discipline.id}/${job.grade.id}/${job.track.id}`
|
|
271
|
+
: `/job/${job.discipline.id}/${job.grade.id}`,
|
|
272
|
+
),
|
|
245
273
|
);
|
|
246
274
|
}
|
|
247
275
|
|
package/app/slides/index.js
CHANGED
|
@@ -184,7 +184,9 @@ export function renderSlideIndex({ render, data }) {
|
|
|
184
184
|
{},
|
|
185
185
|
a(
|
|
186
186
|
{
|
|
187
|
-
href:
|
|
187
|
+
href: job.track
|
|
188
|
+
? `#/job/${job.discipline.id}/${job.grade.id}/${job.track.id}`
|
|
189
|
+
: `#/job/${job.discipline.id}/${job.grade.id}`,
|
|
188
190
|
},
|
|
189
191
|
job.title,
|
|
190
192
|
),
|
package/app/slides/overview.js
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* Displays overview slides for each chapter with cards for all entities.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { div, h1, p, span } from "../lib/render.js";
|
|
8
|
-
import { createCardList } from "../components/list.js";
|
|
7
|
+
import { div, h1, h2, p, span } from "../lib/render.js";
|
|
8
|
+
import { createCardList, createGroupedList } from "../components/list.js";
|
|
9
|
+
import { createBadge } from "../components/card.js";
|
|
9
10
|
import {
|
|
10
11
|
disciplineToCardConfig,
|
|
11
12
|
skillToCardConfig,
|
|
@@ -23,6 +24,31 @@ import { prepareGradesList } from "../formatters/grade/shared.js";
|
|
|
23
24
|
import { prepareTracksList } from "../formatters/track/shared.js";
|
|
24
25
|
import { generateAllJobs } from "../model/derivation.js";
|
|
25
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Format discipline group name for display
|
|
29
|
+
* @param {string} groupName - Group name (professional/management)
|
|
30
|
+
* @returns {string}
|
|
31
|
+
*/
|
|
32
|
+
function formatDisciplineGroupName(groupName) {
|
|
33
|
+
if (groupName === "professional") return "Professional";
|
|
34
|
+
if (groupName === "management") return "Management";
|
|
35
|
+
return groupName.charAt(0).toUpperCase() + groupName.slice(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Render discipline group header
|
|
40
|
+
* @param {string} groupName - Group name
|
|
41
|
+
* @param {number} count - Number of items in group
|
|
42
|
+
* @returns {HTMLElement}
|
|
43
|
+
*/
|
|
44
|
+
function renderDisciplineGroupHeader(groupName, count) {
|
|
45
|
+
return div(
|
|
46
|
+
{ className: "capability-header" },
|
|
47
|
+
h2({ className: "capability-title" }, formatDisciplineGroupName(groupName)),
|
|
48
|
+
createBadge(`${count}`, "default"),
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
26
52
|
/**
|
|
27
53
|
* Render overview slide
|
|
28
54
|
* @param {Object} params
|
|
@@ -62,8 +88,9 @@ export function renderOverviewSlide({ render, data, params }) {
|
|
|
62
88
|
title: framework.entityDefinitions.discipline.title,
|
|
63
89
|
emoji: framework.entityDefinitions.discipline.emoji,
|
|
64
90
|
description: framework.entityDefinitions.discipline.description,
|
|
65
|
-
|
|
91
|
+
groups: prepareDisciplinesList(data.disciplines).groups,
|
|
66
92
|
mapper: disciplineToCardConfig,
|
|
93
|
+
isGrouped: true,
|
|
67
94
|
},
|
|
68
95
|
grade: {
|
|
69
96
|
title: framework.entityDefinitions.grade.title,
|
|
@@ -108,6 +135,15 @@ export function renderOverviewSlide({ render, data, params }) {
|
|
|
108
135
|
return;
|
|
109
136
|
}
|
|
110
137
|
|
|
138
|
+
// Render content based on whether it's grouped or flat
|
|
139
|
+
const contentElement = config.isGrouped
|
|
140
|
+
? createGroupedList(
|
|
141
|
+
config.groups,
|
|
142
|
+
config.mapper,
|
|
143
|
+
renderDisciplineGroupHeader,
|
|
144
|
+
)
|
|
145
|
+
: createCardList(config.entities, config.mapper, "No items found.");
|
|
146
|
+
|
|
111
147
|
const slide = div(
|
|
112
148
|
{ className: "slide overview-slide" },
|
|
113
149
|
div(
|
|
@@ -119,7 +155,7 @@ export function renderOverviewSlide({ render, data, params }) {
|
|
|
119
155
|
),
|
|
120
156
|
p({ className: "overview-description" }, config.description.trim()),
|
|
121
157
|
),
|
|
122
|
-
|
|
158
|
+
contentElement,
|
|
123
159
|
);
|
|
124
160
|
|
|
125
161
|
render(slide);
|
package/app/slides/progress.js
CHANGED
|
@@ -21,9 +21,11 @@ import { progressToDOM } from "../formatters/index.js";
|
|
|
21
21
|
export function renderProgressSlide({ render, data, params }) {
|
|
22
22
|
const discipline = data.disciplines.find((d) => d.id === params.discipline);
|
|
23
23
|
const grade = data.grades.find((g) => g.id === params.grade);
|
|
24
|
-
const track =
|
|
24
|
+
const track = params.track
|
|
25
|
+
? data.tracks.find((t) => t.id === params.track)
|
|
26
|
+
: null;
|
|
25
27
|
|
|
26
|
-
if (!discipline || !grade
|
|
28
|
+
if (!discipline || !grade) {
|
|
27
29
|
render(
|
|
28
30
|
div(
|
|
29
31
|
{ className: "slide-error" },
|
package/app/slides.html
CHANGED
|
@@ -5,6 +5,13 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Engineering Pathway - Slide View</title>
|
|
7
7
|
<link rel="stylesheet" href="css/bundles/slides.css" />
|
|
8
|
+
<script type="importmap">
|
|
9
|
+
{
|
|
10
|
+
"imports": {
|
|
11
|
+
"mustache": "https://esm.sh/mustache@4.2.0"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
</script>
|
|
8
15
|
</head>
|
|
9
16
|
<body class="slide-view">
|
|
10
17
|
<header
|