@forwardimpact/pathway 0.22.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/package.json +3 -2
  2. package/src/commands/agent.js +7 -3
  3. package/src/commands/behaviour.js +11 -1
  4. package/src/commands/build.js +11 -2
  5. package/src/commands/command-factory.js +4 -2
  6. package/src/commands/dev.js +9 -2
  7. package/src/commands/discipline.js +19 -2
  8. package/src/commands/driver.js +11 -1
  9. package/src/commands/job.js +25 -12
  10. package/src/commands/level.js +19 -3
  11. package/src/commands/skill.js +11 -1
  12. package/src/commands/stage.js +11 -1
  13. package/src/commands/tool.js +4 -3
  14. package/src/commands/track.js +11 -1
  15. package/src/components/card.js +8 -104
  16. package/src/components/comparison-radar.js +1 -1
  17. package/src/components/detail.js +16 -118
  18. package/src/components/error-page.js +8 -68
  19. package/src/components/grid.js +12 -106
  20. package/src/components/list.js +7 -116
  21. package/src/components/nav.js +7 -60
  22. package/src/css/bundles/app.css +25 -21
  23. package/src/css/bundles/handout.css +33 -33
  24. package/src/css/bundles/slides.css +25 -25
  25. package/src/formatters/interview/shared.js +3 -3
  26. package/src/formatters/job/description.js +2 -2
  27. package/src/formatters/progress/shared.js +3 -3
  28. package/src/formatters/skill/shared.js +1 -1
  29. package/src/formatters/track/shared.js +1 -1
  30. package/src/handout.html +32 -13
  31. package/src/index.html +32 -13
  32. package/src/lib/error-boundary.js +3 -66
  33. package/src/lib/errors.js +7 -45
  34. package/src/lib/job-cache.js +1 -1
  35. package/src/lib/markdown.js +2 -109
  36. package/src/lib/reactive.js +7 -73
  37. package/src/lib/render.js +49 -197
  38. package/src/lib/router-core.js +2 -156
  39. package/src/lib/router-pages.js +2 -11
  40. package/src/lib/router-slides.js +2 -197
  41. package/src/lib/state.js +14 -63
  42. package/src/lib/utils.js +3 -10
  43. package/src/lib/yaml-loader.js +13 -71
  44. package/src/pages/agent-builder.js +1 -1
  45. package/src/pages/assessment-results.js +1 -1
  46. package/src/pages/job-builder.js +1 -1
  47. package/src/pages/job.js +1 -1
  48. package/src/pages/skill.js +1 -1
  49. package/src/slide-main.js +1 -1
  50. package/src/slides/index.js +1 -1
  51. package/src/slides/job.js +1 -1
  52. package/src/slides/overview.js +1 -1
  53. package/src/slides.html +32 -13
  54. package/src/css/base.css +0 -56
  55. package/src/css/components/badges.css +0 -232
  56. package/src/css/components/buttons.css +0 -101
  57. package/src/css/components/forms.css +0 -191
  58. package/src/css/components/layout.css +0 -218
  59. package/src/css/components/nav.css +0 -206
  60. package/src/css/components/progress.css +0 -166
  61. package/src/css/components/states.css +0 -82
  62. package/src/css/components/surfaces.css +0 -347
  63. package/src/css/components/tables.css +0 -362
  64. package/src/css/components/top-bar.css +0 -180
  65. package/src/css/components/typography.css +0 -121
  66. package/src/css/components/utilities.css +0 -41
  67. package/src/css/pages/detail.css +0 -119
  68. package/src/css/reset.css +0 -50
  69. package/src/css/tokens.css +0 -162
  70. package/src/css/views/handout.css +0 -30
  71. package/src/css/views/print.css +0 -634
  72. package/src/css/views/slide-animations.css +0 -113
  73. package/src/css/views/slide-base.css +0 -331
  74. package/src/css/views/slide-sections.css +0 -597
  75. package/src/css/views/slide-tables.css +0 -275
package/src/lib/render.js CHANGED
@@ -1,209 +1,61 @@
1
1
  /**
2
2
  * DOM rendering utilities
3
+ *
4
+ * Re-exports generic utilities from @forwardimpact/libui/render
5
+ * and adds domain-specific display helpers.
3
6
  */
4
7
 
8
+ export {
9
+ getContainer,
10
+ render,
11
+ createElement,
12
+ div,
13
+ span,
14
+ h1,
15
+ h2,
16
+ h3,
17
+ h4,
18
+ p,
19
+ a,
20
+ ul,
21
+ li,
22
+ table,
23
+ thead,
24
+ tbody,
25
+ tr,
26
+ th,
27
+ td,
28
+ pre,
29
+ code,
30
+ button,
31
+ input,
32
+ select,
33
+ option,
34
+ optgroup,
35
+ label,
36
+ form,
37
+ section,
38
+ article,
39
+ header,
40
+ footer,
41
+ nav,
42
+ main,
43
+ details,
44
+ summary,
45
+ heading1,
46
+ heading2,
47
+ heading3,
48
+ fragment,
49
+ showLoading,
50
+ showError,
51
+ formatLevel,
52
+ } from "@forwardimpact/libui/render";
53
+
5
54
  import {
6
55
  SKILL_PROFICIENCY_ORDER,
7
56
  BEHAVIOUR_MATURITY_ORDER,
8
57
  } from "@forwardimpact/map/levels";
9
58
 
10
- /**
11
- * Get the main content container
12
- * @returns {HTMLElement}
13
- */
14
- export function getContainer() {
15
- return document.getElementById("app-content");
16
- }
17
-
18
- /**
19
- * Clear and render content to the main container
20
- * @param {HTMLElement|string} content
21
- */
22
- export function render(content) {
23
- const container = getContainer();
24
- container.innerHTML = "";
25
-
26
- if (typeof content === "string") {
27
- container.innerHTML = content;
28
- } else if (content instanceof HTMLElement) {
29
- container.appendChild(content);
30
- }
31
- }
32
-
33
- /**
34
- * Create an element with attributes and children
35
- * @param {string} tag - HTML tag name
36
- * @param {Object} [attrs] - Attributes and properties
37
- * @param {...(HTMLElement|string)} children - Child elements or text
38
- * @returns {HTMLElement}
39
- */
40
- export function createElement(tag, attrs = {}, ...children) {
41
- const element = document.createElement(tag);
42
-
43
- for (const [key, value] of Object.entries(attrs)) {
44
- if (key === "className") {
45
- element.className = value;
46
- } else if (key === "style" && typeof value === "object") {
47
- Object.assign(element.style, value);
48
- } else if (key.startsWith("on") && typeof value === "function") {
49
- element.addEventListener(key.slice(2).toLowerCase(), value);
50
- } else if (key === "dataset" && typeof value === "object") {
51
- Object.assign(element.dataset, value);
52
- } else if (typeof value === "boolean") {
53
- // Handle boolean attributes - only set if true, skip if false
54
- if (value) {
55
- element.setAttribute(key, "");
56
- }
57
- } else {
58
- element.setAttribute(key, value);
59
- }
60
- }
61
-
62
- for (const child of children) {
63
- if (child == null || child === false) continue;
64
- if (typeof child === "string" || typeof child === "number") {
65
- element.appendChild(document.createTextNode(String(child)));
66
- } else if (child instanceof HTMLElement) {
67
- element.appendChild(child);
68
- } else if (Array.isArray(child)) {
69
- child.forEach((c) => {
70
- if (c == null || c === false) return;
71
- if (c instanceof HTMLElement) {
72
- element.appendChild(c);
73
- } else if (typeof c === "string" || typeof c === "number") {
74
- element.appendChild(document.createTextNode(String(c)));
75
- }
76
- });
77
- }
78
- }
79
-
80
- return element;
81
- }
82
-
83
- // Shorthand element creators
84
- export const div = (attrs, ...children) =>
85
- createElement("div", attrs, ...children);
86
- export const span = (attrs, ...children) =>
87
- createElement("span", attrs, ...children);
88
- export const h1 = (attrs, ...children) =>
89
- createElement("h1", attrs, ...children);
90
- export const h2 = (attrs, ...children) =>
91
- createElement("h2", attrs, ...children);
92
- export const h3 = (attrs, ...children) =>
93
- createElement("h3", attrs, ...children);
94
- export const h4 = (attrs, ...children) =>
95
- createElement("h4", attrs, ...children);
96
- export const p = (attrs, ...children) => createElement("p", attrs, ...children);
97
- export const a = (attrs, ...children) => createElement("a", attrs, ...children);
98
- export const ul = (attrs, ...children) =>
99
- createElement("ul", attrs, ...children);
100
- export const li = (attrs, ...children) =>
101
- createElement("li", attrs, ...children);
102
- export const table = (attrs, ...children) =>
103
- createElement("table", attrs, ...children);
104
- export const thead = (attrs, ...children) =>
105
- createElement("thead", attrs, ...children);
106
- export const tbody = (attrs, ...children) =>
107
- createElement("tbody", attrs, ...children);
108
- export const tr = (attrs, ...children) =>
109
- createElement("tr", attrs, ...children);
110
- export const th = (attrs, ...children) =>
111
- createElement("th", attrs, ...children);
112
- export const td = (attrs, ...children) =>
113
- createElement("td", attrs, ...children);
114
- export const pre = (attrs, ...children) =>
115
- createElement("pre", attrs, ...children);
116
- export const code = (attrs, ...children) =>
117
- createElement("code", attrs, ...children);
118
- export const button = (attrs, ...children) =>
119
- createElement("button", attrs, ...children);
120
- export const input = (attrs) => createElement("input", attrs);
121
- export const select = (attrs, ...children) =>
122
- createElement("select", attrs, ...children);
123
- export const option = (attrs, ...children) =>
124
- createElement("option", attrs, ...children);
125
- export const optgroup = (attrs, ...children) =>
126
- createElement("optgroup", attrs, ...children);
127
- export const label = (attrs, ...children) =>
128
- createElement("label", attrs, ...children);
129
- export const form = (attrs, ...children) =>
130
- createElement("form", attrs, ...children);
131
- export const section = (attrs, ...children) =>
132
- createElement("section", attrs, ...children);
133
- export const article = (attrs, ...children) =>
134
- createElement("article", attrs, ...children);
135
- export const header = (attrs, ...children) =>
136
- createElement("header", attrs, ...children);
137
- export const footer = (attrs, ...children) =>
138
- createElement("footer", attrs, ...children);
139
- export const nav = (attrs, ...children) =>
140
- createElement("nav", attrs, ...children);
141
- export const main = (attrs, ...children) =>
142
- createElement("main", attrs, ...children);
143
- export const details = (attrs, ...children) =>
144
- createElement("details", attrs, ...children);
145
- export const summary = (attrs, ...children) =>
146
- createElement("summary", attrs, ...children);
147
-
148
- /**
149
- * Semantic heading aliases that match heading levels
150
- * Use these instead of h1/h2/h3 for clarity in slides and formatters
151
- */
152
- export const heading1 = h1;
153
- export const heading2 = h2;
154
- export const heading3 = h3;
155
-
156
- /**
157
- * Create a fragment from multiple elements
158
- * @param {...HTMLElement} children
159
- * @returns {DocumentFragment}
160
- */
161
- export function fragment(...children) {
162
- const frag = document.createDocumentFragment();
163
- children.forEach((child) => {
164
- if (child instanceof HTMLElement) {
165
- frag.appendChild(child);
166
- }
167
- });
168
- return frag;
169
- }
170
-
171
- /**
172
- * Show a loading state
173
- */
174
- export function showLoading() {
175
- render(
176
- div(
177
- { className: "loading" },
178
- div({ className: "loading-spinner" }),
179
- p({}, "Loading..."),
180
- ),
181
- );
182
- }
183
-
184
- /**
185
- * Show an error message
186
- * @param {string} message
187
- */
188
- export function showError(message) {
189
- render(div({ className: "error-message" }, h2({}, "Error"), p({}, message)));
190
- }
191
-
192
- /**
193
- * Format a skill proficiency or behaviour maturity for display
194
- * Handles both snake_case and camelCase
195
- * @param {string} value - The level/maturity value
196
- * @returns {string}
197
- */
198
- export function formatLevel(value) {
199
- if (!value) return "";
200
- // Insert space before uppercase letters (for camelCase), then handle snake_case
201
- return value
202
- .replace(/([a-z])([A-Z])/g, "$1 $2")
203
- .replace(/_/g, " ")
204
- .replace(/\b\w/g, (c) => c.toUpperCase());
205
- }
206
-
207
59
  /**
208
60
  * Get the index for a skill proficiency (1-5)
209
61
  * @param {string} level
@@ -1,161 +1,7 @@
1
1
  /**
2
2
  * Core Router Factory
3
3
  *
4
- * Pure factory function for hash-based routing with no dependencies.
4
+ * Re-exports from @forwardimpact/libui/router-core.
5
5
  */
6
6
 
7
- import { withErrorBoundary } from "./error-boundary.js";
8
-
9
- /**
10
- * @typedef {Object} Route
11
- * @property {string} pattern - Route pattern with :params
12
- * @property {RegExp} regex - Compiled pattern
13
- * @property {string[]} paramNames - Extracted param names
14
- * @property {Function} handler - Route handler
15
- */
16
-
17
- /**
18
- * @typedef {Object} RouteMatch
19
- * @property {Function} handler - Matched handler
20
- * @property {Object} params - Extracted parameters
21
- */
22
-
23
- /**
24
- * @typedef {Object} Router
25
- * @property {(pattern: string, handler: Function) => void} on - Register route
26
- * @property {(path: string) => void} navigate - Navigate to path
27
- * @property {() => string} currentPath - Get current hash path
28
- * @property {() => void} handleRoute - Process current route
29
- * @property {() => void} start - Begin listening for hash changes
30
- * @property {() => void} stop - Stop listening for hash changes
31
- * @property {() => string[]} patterns - Get registered patterns
32
- */
33
-
34
- /**
35
- * Parse route pattern into regex and param names
36
- * @param {string} pattern
37
- * @returns {{ regex: RegExp, paramNames: string[] }}
38
- */
39
- function parsePattern(pattern) {
40
- const paramNames = [];
41
- const regexStr = pattern
42
- .replace(/:([^/]+)/g, (_, name) => {
43
- paramNames.push(name);
44
- return "([^/]+)";
45
- })
46
- .replace(/\//g, "\\/");
47
- return {
48
- regex: new RegExp(`^${regexStr}$`),
49
- paramNames,
50
- };
51
- }
52
-
53
- /**
54
- * Create a router instance
55
- * @param {{ onNotFound?: (path: string) => void, onError?: (error: Error) => void, renderError?: (title: string, message: string) => void }} options
56
- * @returns {Router}
57
- */
58
- export function createRouter(options = {}) {
59
- const { onNotFound = () => {}, onError, renderError } = options;
60
- /** @type {Route[]} */
61
- const routes = [];
62
- let hashChangeHandler = null;
63
-
64
- /**
65
- * Match a path to a route
66
- * @param {string} path
67
- * @returns {RouteMatch|null}
68
- */
69
- function matchRoute(path) {
70
- for (const route of routes) {
71
- const match = path.match(route.regex);
72
- if (match) {
73
- const params = {};
74
- route.paramNames.forEach((name, i) => {
75
- params[name] = decodeURIComponent(match[i + 1]);
76
- });
77
- return { handler: route.handler, params };
78
- }
79
- }
80
- return null;
81
- }
82
-
83
- /**
84
- * Get current path from hash (including query string)
85
- * @returns {string}
86
- */
87
- function currentPath() {
88
- return window.location.hash.slice(1) || "/";
89
- }
90
-
91
- /**
92
- * Handle the current route
93
- */
94
- function handleRoute() {
95
- const fullPath = currentPath();
96
- const path = fullPath.split("?")[0];
97
- const matched = matchRoute(path);
98
- if (matched) {
99
- window.scrollTo(0, 0);
100
- matched.handler(matched.params);
101
- } else {
102
- onNotFound(path);
103
- }
104
- }
105
-
106
- return {
107
- /**
108
- * Register a route
109
- * @param {string} pattern - Route pattern (e.g., '/skills/:id')
110
- * @param {Function} handler - Handler function
111
- */
112
- on(pattern, handler) {
113
- const { regex, paramNames } = parsePattern(pattern);
114
- const wrappedHandler = withErrorBoundary(handler, {
115
- onError,
116
- backPath: "/",
117
- backText: "← Back to Home",
118
- renderErrorFn: renderError,
119
- });
120
- routes.push({ pattern, regex, paramNames, handler: wrappedHandler });
121
- },
122
-
123
- /**
124
- * Navigate to a path
125
- * @param {string} path
126
- */
127
- navigate(path) {
128
- window.location.hash = path;
129
- },
130
-
131
- currentPath,
132
- handleRoute,
133
-
134
- /**
135
- * Start listening for hash changes
136
- */
137
- start() {
138
- hashChangeHandler = () => handleRoute();
139
- window.addEventListener("hashchange", hashChangeHandler);
140
- handleRoute();
141
- },
142
-
143
- /**
144
- * Stop listening for hash changes
145
- */
146
- stop() {
147
- if (hashChangeHandler) {
148
- window.removeEventListener("hashchange", hashChangeHandler);
149
- hashChangeHandler = null;
150
- }
151
- },
152
-
153
- /**
154
- * Get all registered route patterns
155
- * @returns {string[]}
156
- */
157
- patterns() {
158
- return routes.map((r) => r.pattern);
159
- },
160
- };
161
- }
7
+ export { createRouter } from "@forwardimpact/libui/router-core";
@@ -1,16 +1,7 @@
1
1
  /**
2
2
  * Pages Router
3
3
  *
4
- * Router instance for the main app pages.
4
+ * Re-exports from @forwardimpact/libui/router-pages.
5
5
  */
6
6
 
7
- import { createRouter } from "./router-core.js";
8
-
9
- /**
10
- * Create the pages router for the main app
11
- * @param {{ onNotFound?: (path: string) => void }} options
12
- * @returns {import('./router-core.js').Router}
13
- */
14
- export function createPagesRouter(options = {}) {
15
- return createRouter(options);
16
- }
7
+ export { createPagesRouter } from "@forwardimpact/libui/router-pages";
@@ -1,202 +1,7 @@
1
1
  /**
2
2
  * Slide Router
3
3
  *
4
- * Extended router with navigation state and keyboard controls for slides.
4
+ * Re-exports from @forwardimpact/libui/router-slides.
5
5
  */
6
6
 
7
- import { createRouter } from "./router-core.js";
8
-
9
- /**
10
- * @typedef {Object} SlideRouter
11
- * @property {(pattern: string, handler: Function) => void} on - Register route
12
- * @property {(path: string) => void} navigate - Navigate to path
13
- * @property {() => string} currentPath - Get current hash path
14
- * @property {() => void} handleRoute - Process current route
15
- * @property {() => void} start - Begin listening for hash changes
16
- * @property {() => void} stop - Stop listening for hash changes
17
- * @property {() => string[]} patterns - Get registered patterns
18
- * @property {(paths: string[]) => void} setSlideOrder - Define navigation order
19
- * @property {() => void} next - Navigate to next slide
20
- * @property {() => void} prev - Navigate to previous slide
21
- * @property {() => void} home - Navigate to index
22
- * @property {() => number} currentIndex - Current position in order
23
- * @property {() => number} totalSlides - Total slide count
24
- * @property {() => void} startKeyboardNav - Enable keyboard shortcuts
25
- * @property {() => void} stopKeyboardNav - Disable keyboard shortcuts
26
- */
27
-
28
- /**
29
- * Create a slide router with navigation capabilities
30
- * @param {{ onNotFound?: (path: string) => void, renderError?: (title: string, message: string) => void }} options
31
- * @returns {SlideRouter}
32
- */
33
- export function createSlideRouter(options = {}) {
34
- const router = createRouter(options);
35
- let slideOrder = [];
36
- let chapterBoundaries = [];
37
- let keyHandler = null;
38
-
39
- /**
40
- * Find current position in slide order
41
- * @returns {number}
42
- */
43
- function findCurrentIndex() {
44
- const path = router.currentPath();
45
- return slideOrder.indexOf(path);
46
- }
47
-
48
- /**
49
- * Navigate to slide at given index
50
- * @param {number} index
51
- */
52
- function navigateToIndex(index) {
53
- if (index >= 0 && index < slideOrder.length) {
54
- router.navigate(slideOrder[index]);
55
- }
56
- }
57
-
58
- const slideRouter = {
59
- // Expose core router methods
60
- on: router.on,
61
- navigate: router.navigate,
62
- currentPath: router.currentPath,
63
- handleRoute: router.handleRoute,
64
- start: router.start,
65
- stop: router.stop,
66
- patterns: router.patterns,
67
-
68
- /**
69
- * Define the slide navigation order
70
- * @param {string[]} paths
71
- * @param {number[]} boundaries - Indices where chapters start
72
- */
73
- setSlideOrder(paths, boundaries = []) {
74
- slideOrder = paths;
75
- chapterBoundaries = boundaries;
76
- },
77
-
78
- /**
79
- * Navigate to next slide
80
- */
81
- next() {
82
- const idx = findCurrentIndex();
83
- navigateToIndex(idx + 1);
84
- },
85
-
86
- /**
87
- * Navigate to previous slide
88
- */
89
- prev() {
90
- const idx = findCurrentIndex();
91
- navigateToIndex(idx - 1);
92
- },
93
-
94
- /**
95
- * Navigate to previous chapter
96
- */
97
- prevChapter() {
98
- const idx = findCurrentIndex();
99
- // Find the previous chapter boundary before current position
100
- const prevBoundary = chapterBoundaries.filter((b) => b < idx).pop();
101
- if (prevBoundary !== undefined) {
102
- navigateToIndex(prevBoundary);
103
- } else if (idx > 0) {
104
- navigateToIndex(0);
105
- }
106
- },
107
-
108
- /**
109
- * Navigate to next chapter
110
- */
111
- nextChapter() {
112
- const idx = findCurrentIndex();
113
- // Find the next chapter boundary after current position
114
- const nextBoundary = chapterBoundaries.find((b) => b > idx);
115
- if (nextBoundary !== undefined) {
116
- navigateToIndex(nextBoundary);
117
- }
118
- },
119
-
120
- /**
121
- * Navigate to first slide (index)
122
- */
123
- home() {
124
- navigateToIndex(0);
125
- },
126
-
127
- /**
128
- * Get current slide index
129
- * @returns {number}
130
- */
131
- currentIndex() {
132
- return findCurrentIndex();
133
- },
134
-
135
- /**
136
- * Get total number of slides
137
- * @returns {number}
138
- */
139
- totalSlides() {
140
- return slideOrder.length;
141
- },
142
-
143
- /**
144
- * Start keyboard navigation
145
- */
146
- startKeyboardNav() {
147
- keyHandler = (e) => {
148
- // Ignore if typing in an input
149
- if (e.target.tagName === "INPUT" || e.target.tagName === "TEXTAREA") {
150
- return;
151
- }
152
-
153
- switch (e.key) {
154
- case "ArrowRight":
155
- case " ":
156
- case "PageDown":
157
- e.preventDefault();
158
- slideRouter.next();
159
- break;
160
- case "ArrowLeft":
161
- case "PageUp":
162
- e.preventDefault();
163
- slideRouter.prev();
164
- break;
165
- case "ArrowDown":
166
- e.preventDefault();
167
- slideRouter.nextChapter();
168
- break;
169
- case "ArrowUp":
170
- e.preventDefault();
171
- slideRouter.prevChapter();
172
- break;
173
- case "Home":
174
- e.preventDefault();
175
- slideRouter.home();
176
- break;
177
- case "End":
178
- e.preventDefault();
179
- navigateToIndex(slideOrder.length - 1);
180
- break;
181
- case "Escape":
182
- e.preventDefault();
183
- slideRouter.home();
184
- break;
185
- }
186
- };
187
- document.addEventListener("keydown", keyHandler);
188
- },
189
-
190
- /**
191
- * Stop keyboard navigation
192
- */
193
- stopKeyboardNav() {
194
- if (keyHandler) {
195
- document.removeEventListener("keydown", keyHandler);
196
- keyHandler = null;
197
- }
198
- },
199
- };
200
-
201
- return slideRouter;
202
- }
7
+ export { createSlideRouter } from "@forwardimpact/libui/router-slides";