@forwardimpact/pathway 0.12.0 → 0.14.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 (54) hide show
  1. package/bin/fit-pathway.js +32 -12
  2. package/package.json +3 -3
  3. package/src/commands/build.js +98 -2
  4. package/src/commands/index.js +1 -0
  5. package/src/commands/interview.js +52 -14
  6. package/src/commands/job.js +1 -0
  7. package/src/commands/questions.js +13 -10
  8. package/src/commands/stage.js +8 -8
  9. package/src/commands/update.js +133 -0
  10. package/src/components/command-prompt.js +85 -0
  11. package/src/components/nav.js +2 -2
  12. package/src/components/top-bar.js +97 -0
  13. package/src/css/bundles/app.css +2 -0
  14. package/src/css/components/badges.css +41 -11
  15. package/src/css/components/command-prompt.css +98 -0
  16. package/src/css/components/layout.css +0 -3
  17. package/src/css/components/nav.css +121 -81
  18. package/src/css/components/surfaces.css +1 -1
  19. package/src/css/components/top-bar.css +180 -0
  20. package/src/css/pages/agent-builder.css +0 -9
  21. package/src/css/pages/landing.css +4 -0
  22. package/src/css/pages/lifecycle.css +5 -2
  23. package/src/css/reset.css +1 -1
  24. package/src/css/tokens.css +25 -11
  25. package/src/css/views/slide-base.css +2 -1
  26. package/src/formatters/agent/dom.js +0 -26
  27. package/src/formatters/agent/profile.js +13 -7
  28. package/src/formatters/agent/skill.js +4 -4
  29. package/src/formatters/interview/markdown.js +62 -3
  30. package/src/formatters/interview/shared.js +89 -52
  31. package/src/formatters/questions/markdown.js +15 -0
  32. package/src/formatters/questions/shared.js +70 -58
  33. package/src/formatters/stage/dom.js +13 -10
  34. package/src/formatters/stage/microdata.js +14 -8
  35. package/src/formatters/stage/shared.js +4 -4
  36. package/src/index.html +69 -44
  37. package/src/lib/cli-command.js +145 -0
  38. package/src/lib/state.js +2 -0
  39. package/src/lib/yaml-loader.js +39 -21
  40. package/src/main.js +47 -26
  41. package/src/pages/agent-builder.js +0 -28
  42. package/src/pages/behaviour.js +3 -1
  43. package/src/pages/discipline.js +3 -1
  44. package/src/pages/driver.js +6 -1
  45. package/src/pages/grade.js +6 -1
  46. package/src/pages/interview.js +61 -5
  47. package/src/pages/job.js +1 -0
  48. package/src/pages/landing.js +7 -0
  49. package/src/pages/skill.js +9 -2
  50. package/src/pages/track.js +6 -1
  51. package/src/slides/job.js +1 -0
  52. package/templates/agent.template.md +17 -10
  53. package/templates/install.template.sh +33 -0
  54. package/templates/skill.template.md +15 -7
@@ -5,11 +5,11 @@
5
5
  import { div, a } from "../lib/render.js";
6
6
 
7
7
  /**
8
- * Update the active navigation link
8
+ * Update the active navigation link in the drawer
9
9
  * @param {string} path - Current path
10
10
  */
11
11
  export function updateActiveNav(path) {
12
- const links = document.querySelectorAll("#nav-links a");
12
+ const links = document.querySelectorAll("#drawer-nav a");
13
13
  links.forEach((link) => {
14
14
  const href = link.getAttribute("href").slice(1); // Remove #
15
15
  const isActive = path === href || (href !== "/" && path.startsWith(href));
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Top Bar Component
3
+ *
4
+ * Fixed bar across the top of the app with:
5
+ * - Sidebar toggle button (left)
6
+ * - CLI command display with copy button (center)
7
+ *
8
+ * Similar to Safari's URL bar with sidebar toggle.
9
+ */
10
+
11
+ import { getCliCommand } from "../lib/cli-command.js";
12
+
13
+ /** @type {HTMLElement|null} */
14
+ let commandDisplay = null;
15
+
16
+ /** @type {HTMLButtonElement|null} */
17
+ let copyButton = null;
18
+
19
+ /**
20
+ * Set up the top bar: wire toggle and initial command display.
21
+ * Call after DOM is ready.
22
+ */
23
+ export function setupTopBar() {
24
+ const app = document.getElementById("app");
25
+ const toggle = document.getElementById("sidebar-toggle");
26
+ const commandEl = document.getElementById("cli-command");
27
+ const copyBtn = document.getElementById("cli-copy");
28
+
29
+ commandDisplay = commandEl;
30
+ copyButton = copyBtn;
31
+
32
+ if (toggle) {
33
+ toggle.addEventListener("click", () => {
34
+ app.classList.toggle("drawer-open");
35
+ });
36
+ }
37
+
38
+ if (copyBtn) {
39
+ copyBtn.addEventListener("click", handleCopy);
40
+ }
41
+
42
+ // Intercept history.replaceState so CLI command updates when pages
43
+ // change the hash without triggering hashchange (e.g. agent builder)
44
+ const originalReplaceState = history.replaceState.bind(history);
45
+ history.replaceState = (...args) => {
46
+ originalReplaceState(...args);
47
+ updateCommand();
48
+ };
49
+
50
+ // Set initial command
51
+ updateCommand();
52
+ }
53
+
54
+ /**
55
+ * Update the CLI command display for the current route.
56
+ * Call on every route change.
57
+ */
58
+ export function updateCommand() {
59
+ if (!commandDisplay) return;
60
+ const path = window.location.hash.slice(1) || "/";
61
+ commandDisplay.textContent = getCliCommand(path);
62
+ // Reset copy button state
63
+ if (copyButton) {
64
+ copyButton.setAttribute("aria-label", "Copy command");
65
+ copyButton.classList.remove("copied");
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Copy the current CLI command to clipboard
71
+ */
72
+ async function handleCopy() {
73
+ if (!commandDisplay || !copyButton) return;
74
+ const text = commandDisplay.textContent;
75
+
76
+ try {
77
+ await navigator.clipboard.writeText(text);
78
+ copyButton.classList.add("copied");
79
+ copyButton.setAttribute("aria-label", "Copied!");
80
+ setTimeout(() => {
81
+ copyButton.classList.remove("copied");
82
+ copyButton.setAttribute("aria-label", "Copy command");
83
+ }, 2000);
84
+ } catch {
85
+ // Fallback for older browsers
86
+ const textarea = document.createElement("textarea");
87
+ textarea.value = text;
88
+ textarea.style.position = "fixed";
89
+ textarea.style.opacity = "0";
90
+ document.body.appendChild(textarea);
91
+ textarea.select();
92
+ document.execCommand("copy");
93
+ document.body.removeChild(textarea);
94
+ copyButton.classList.add("copied");
95
+ setTimeout(() => copyButton.classList.remove("copied"), 2000);
96
+ }
97
+ }
@@ -24,6 +24,8 @@
24
24
  @import "../components/progress.css" layer(components);
25
25
  @import "../components/states.css" layer(components);
26
26
  @import "../components/nav.css" layer(components);
27
+ @import "../components/top-bar.css" layer(components);
28
+ @import "../components/command-prompt.css" layer(components);
27
29
 
28
30
  /* Utilities */
29
31
  @import "../components/utilities.css" layer(utilities);
@@ -94,24 +94,34 @@
94
94
  }
95
95
 
96
96
  /* Capability badges - each capability ID maps to its own distinct color */
97
+ .badge-ai {
98
+ background: var(--color-cap-ai-light);
99
+ color: var(--color-cap-ai);
100
+ }
101
+
102
+ .badge-business {
103
+ background: var(--color-cap-business-light);
104
+ color: var(--color-cap-business);
105
+ }
106
+
107
+ .badge-data {
108
+ background: var(--color-cap-data-light);
109
+ color: var(--color-cap-data);
110
+ }
111
+
97
112
  .badge-delivery {
98
113
  background: var(--color-cap-delivery-light);
99
114
  color: var(--color-cap-delivery);
100
115
  }
101
116
 
102
- .badge-scale {
103
- background: var(--color-cap-scale-light);
104
- color: var(--color-cap-scale);
105
- }
106
-
107
- .badge-reliability {
108
- background: var(--color-cap-reliability-light);
109
- color: var(--color-cap-reliability);
117
+ .badge-documentation {
118
+ background: var(--color-cap-documentation-light);
119
+ color: var(--color-cap-documentation);
110
120
  }
111
121
 
112
- .badge-business {
113
- background: var(--color-cap-business-light);
114
- color: var(--color-cap-business);
122
+ .badge-ml {
123
+ background: var(--color-cap-ml-light);
124
+ color: var(--color-cap-ml);
115
125
  }
116
126
 
117
127
  .badge-people {
@@ -119,6 +129,26 @@
119
129
  color: var(--color-cap-people);
120
130
  }
121
131
 
132
+ .badge-process {
133
+ background: var(--color-cap-process-light);
134
+ color: var(--color-cap-process);
135
+ }
136
+
137
+ .badge-product {
138
+ background: var(--color-cap-product-light);
139
+ color: var(--color-cap-product);
140
+ }
141
+
142
+ .badge-reliability {
143
+ background: var(--color-cap-reliability-light);
144
+ color: var(--color-cap-reliability);
145
+ }
146
+
147
+ .badge-scale {
148
+ background: var(--color-cap-scale-light);
149
+ color: var(--color-cap-scale);
150
+ }
151
+
122
152
  /* Tool badge */
123
153
  .badge-tool {
124
154
  background: var(--color-bg);
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Command Prompt
3
+ *
4
+ * Terminal-style command display with copy button.
5
+ * Pill-shaped container with $ prompt, monospace text, and copy action.
6
+ */
7
+
8
+ @layer components {
9
+ .command-prompt {
10
+ width: 100%;
11
+ max-width: 600px;
12
+ margin: 0 auto;
13
+ display: flex;
14
+ align-items: center;
15
+ height: 36px;
16
+ padding: 0 var(--space-sm) 0 var(--space-md);
17
+ border: 1px solid var(--color-border);
18
+ border-radius: var(--radius-lg);
19
+ background: var(--color-surface);
20
+ gap: var(--space-xs);
21
+ transition:
22
+ border-color 0.15s,
23
+ box-shadow 0.15s;
24
+ }
25
+
26
+ .command-prompt:hover {
27
+ border-color: var(--color-text-light);
28
+ }
29
+
30
+ /* Terminal prompt symbol */
31
+ .command-prompt__prompt {
32
+ flex-shrink: 0;
33
+ font-size: var(--font-size-sm);
34
+ color: var(--color-text-light);
35
+ user-select: none;
36
+ }
37
+
38
+ /* Command text */
39
+ .command-prompt__text {
40
+ flex: 1;
41
+ min-width: 0;
42
+ font-family: var(--font-family-mono);
43
+ font-size: var(--font-size-xs);
44
+ color: var(--color-text-secondary);
45
+ white-space: nowrap;
46
+ overflow: hidden;
47
+ text-overflow: ellipsis;
48
+ user-select: all;
49
+ line-height: 36px;
50
+ }
51
+
52
+ /* Copy button */
53
+ .command-prompt__copy {
54
+ flex-shrink: 0;
55
+ display: flex;
56
+ align-items: center;
57
+ justify-content: center;
58
+ width: 28px;
59
+ height: 28px;
60
+ border: none;
61
+ border-radius: var(--radius-sm);
62
+ background: transparent;
63
+ color: var(--color-text-light);
64
+ cursor: pointer;
65
+ transition:
66
+ background 0.15s,
67
+ color 0.15s;
68
+ }
69
+
70
+ .command-prompt__copy:hover {
71
+ background: var(--color-border);
72
+ color: var(--color-text);
73
+ }
74
+
75
+ .command-prompt__copy svg {
76
+ width: 14px;
77
+ height: 14px;
78
+ fill: none;
79
+ stroke: currentColor;
80
+ stroke-width: 2;
81
+ stroke-linecap: round;
82
+ stroke-linejoin: round;
83
+ }
84
+
85
+ /* Copied state */
86
+ .command-prompt__copy.copied {
87
+ color: var(--color-success);
88
+ }
89
+
90
+ /* --------------------------------------------------------------------------
91
+ Mobile tweaks
92
+ -------------------------------------------------------------------------- */
93
+ @media (max-width: 768px) {
94
+ .command-prompt__prompt {
95
+ display: none;
96
+ }
97
+ }
98
+ }
@@ -8,8 +8,6 @@
8
8
  /* App container */
9
9
  #app {
10
10
  min-height: 100vh;
11
- display: flex;
12
- flex-direction: column;
13
11
  }
14
12
 
15
13
  #app-content {
@@ -18,7 +16,6 @@
18
16
  width: 100%;
19
17
  margin: 0 auto;
20
18
  padding: var(--space-lg);
21
- padding-top: calc(var(--nav-height) + var(--space-lg));
22
19
  }
23
20
 
24
21
  /* Stack - vertical flex container with gap */
@@ -1,44 +1,73 @@
1
1
  /**
2
- * Navigation Component
2
+ * Navigation Drawer
3
3
  *
4
- * Fixed navigation bar, brand, links, and mobile hamburger menu.
4
+ * Left-hand drawer with vertically stacked links, toggled by top bar button.
5
+ * Open by default on desktop, overlays on mobile.
5
6
  */
6
7
 
7
8
  @layer components {
8
- #app-nav {
9
+ /* --------------------------------------------------------------------------
10
+ Drawer Panel
11
+ -------------------------------------------------------------------------- */
12
+ #app-drawer {
9
13
  position: fixed;
10
- top: 0;
14
+ top: var(--top-bar-height);
11
15
  left: 0;
12
- right: 0;
13
- height: var(--nav-height);
16
+ bottom: 0;
17
+ width: var(--drawer-width);
14
18
  background: var(--color-surface);
15
- border-bottom: 1px solid var(--color-border);
16
- display: flex;
17
- align-items: center;
18
- padding: 0 var(--space-lg);
19
+ border-right: 1px solid var(--color-border);
19
20
  z-index: 100;
20
- box-shadow: var(--shadow-sm);
21
+ overflow-y: auto;
22
+ display: flex;
23
+ flex-direction: column;
24
+ transform: translateX(calc(-1 * var(--drawer-width)));
25
+ transition: transform 0.3s ease;
26
+ }
27
+
28
+ .drawer-open #app-drawer {
29
+ transform: translateX(0);
30
+ }
31
+
32
+ /* --------------------------------------------------------------------------
33
+ Drawer Header - brand area
34
+ -------------------------------------------------------------------------- */
35
+ .drawer-header {
36
+ padding: var(--space-lg) var(--space-md);
37
+ border-bottom: 1px solid var(--color-border);
38
+ flex-shrink: 0;
21
39
  }
22
40
 
23
41
  .nav-brand {
24
42
  display: flex;
43
+ flex-direction: column;
25
44
  align-items: center;
26
- gap: var(--space-sm);
45
+ gap: var(--space-xs);
46
+ text-decoration: none;
47
+ color: var(--color-text);
48
+ }
49
+
50
+ .brand-emoji {
51
+ font-size: 2.5rem;
52
+ line-height: 1;
27
53
  }
28
54
 
29
- .nav-brand a {
55
+ .nav-brand:hover {
56
+ text-decoration: none;
57
+ }
58
+
59
+ .brand-title {
30
60
  font-size: var(--font-size-lg);
31
61
  font-weight: 700;
32
62
  color: var(--color-text);
33
- text-decoration: none;
34
63
  }
35
64
 
36
- .nav-brand a:hover {
65
+ .nav-brand:hover .brand-title {
37
66
  color: var(--color-primary);
38
- text-decoration: none;
39
67
  }
40
68
 
41
69
  .brand-tag {
70
+ display: inline-block;
42
71
  font-size: var(--font-size-xs);
43
72
  font-weight: 600;
44
73
  letter-spacing: 0.5px;
@@ -70,97 +99,108 @@
70
99
  }
71
100
  }
72
101
 
73
- .nav-links {
102
+ /* --------------------------------------------------------------------------
103
+ Drawer Navigation - sections and links
104
+ -------------------------------------------------------------------------- */
105
+ .drawer-nav {
106
+ flex: 1;
107
+ overflow-y: auto;
108
+ padding: var(--space-sm) 0;
109
+ }
110
+
111
+ .drawer-section {
74
112
  display: flex;
75
- gap: var(--space-xs);
76
- margin-left: auto;
77
- align-items: center;
113
+ flex-direction: column;
114
+ padding: var(--space-sm) 0;
78
115
  }
79
116
 
80
- .nav-links a {
117
+ .drawer-section + .drawer-section {
118
+ border-top: 1px solid var(--color-border);
119
+ }
120
+
121
+ .drawer-section-label {
122
+ font-size: var(--font-size-xs);
123
+ font-weight: 600;
124
+ text-transform: uppercase;
125
+ letter-spacing: 0.05em;
126
+ color: var(--color-text-light);
127
+ padding: var(--space-xs) var(--space-md);
128
+ margin-bottom: var(--space-xs);
129
+ }
130
+
131
+ .drawer-nav a {
132
+ display: block;
81
133
  color: var(--color-text-muted);
82
134
  font-weight: 500;
83
- font-size: 0.9rem;
84
- padding: var(--space-xs) var(--space-sm);
85
- border-radius: var(--radius-md);
86
- transition: all 0.2s;
87
- white-space: nowrap;
135
+ font-size: var(--font-size-sm);
136
+ padding: var(--space-xs) var(--space-md);
137
+ text-decoration: none;
138
+ transition: all 0.15s;
139
+ border-left: 3px solid transparent;
88
140
  }
89
141
 
90
- .nav-links a:hover {
142
+ .drawer-nav a:hover {
91
143
  color: var(--color-primary);
92
- background: var(--color-primary-light);
93
- background: rgba(59, 130, 246, 0.1);
144
+ background: rgba(59, 130, 246, 0.06);
145
+ border-left-color: var(--color-primary-light);
94
146
  text-decoration: none;
95
147
  }
96
148
 
97
- .nav-cta {
98
- background: var(--color-primary) !important;
99
- color: white !important;
149
+ .drawer-nav a.active {
150
+ color: var(--color-primary);
151
+ background: rgba(59, 130, 246, 0.1);
152
+ border-left-color: var(--color-primary);
153
+ font-weight: 600;
100
154
  }
101
155
 
102
- .nav-cta:hover {
103
- background: var(--color-primary-dark) !important;
104
- color: white !important;
156
+ /* CTA links in drawer */
157
+ .drawer-cta {
158
+ color: var(--color-primary) !important;
159
+ font-weight: 600 !important;
105
160
  }
106
161
 
107
- .nav-toggle {
108
- display: none;
109
- flex-direction: column;
110
- gap: 4px;
111
- background: none;
112
- border: none;
113
- cursor: pointer;
114
- padding: var(--space-sm);
115
- margin-left: auto;
162
+ .drawer-cta:hover {
163
+ background: rgba(59, 130, 246, 0.1) !important;
116
164
  }
117
165
 
118
- .nav-toggle span {
119
- display: block;
120
- width: 24px;
121
- height: 2px;
122
- background: var(--color-text);
123
- transition: all 0.3s;
124
- }
125
-
126
- .nav-toggle-active span:nth-child(1) {
127
- transform: rotate(45deg) translate(4px, 4px);
166
+ /* --------------------------------------------------------------------------
167
+ App Body - content area that shifts with drawer
168
+ -------------------------------------------------------------------------- */
169
+ .app-body {
170
+ min-height: 100vh;
171
+ display: flex;
172
+ flex-direction: column;
173
+ transition: margin-left 0.3s ease;
128
174
  }
129
175
 
130
- .nav-toggle-active span:nth-child(2) {
131
- opacity: 0;
176
+ .drawer-open .app-body {
177
+ margin-left: var(--drawer-width);
132
178
  }
133
179
 
134
- .nav-toggle-active span:nth-child(3) {
135
- transform: rotate(-45deg) translate(4px, -4px);
180
+ /* --------------------------------------------------------------------------
181
+ Drawer Overlay - backdrop on mobile when drawer is open
182
+ -------------------------------------------------------------------------- */
183
+ .drawer-overlay {
184
+ display: none;
185
+ position: fixed;
186
+ top: var(--top-bar-height);
187
+ left: 0;
188
+ right: 0;
189
+ bottom: 0;
190
+ background: rgba(0, 0, 0, 0.3);
191
+ z-index: 90;
136
192
  }
137
193
 
138
- /* Mobile navigation */
139
- @media (max-width: 1280px) {
140
- .nav-toggle {
141
- display: flex;
142
- }
143
-
144
- .nav-links {
145
- position: absolute;
146
- top: var(--nav-height);
147
- left: 0;
148
- right: 0;
149
- flex-direction: column;
150
- background: var(--color-surface);
151
- border-bottom: 1px solid var(--color-border);
152
- padding: var(--space-md);
153
- display: none;
154
- gap: var(--space-xs);
155
- }
156
-
157
- .nav-links.nav-open {
158
- display: flex;
194
+ /* --------------------------------------------------------------------------
195
+ Mobile - overlay instead of push
196
+ -------------------------------------------------------------------------- */
197
+ @media (max-width: 768px) {
198
+ .drawer-open .app-body {
199
+ margin-left: 0;
159
200
  }
160
201
 
161
- .nav-links a {
202
+ .drawer-open .drawer-overlay {
162
203
  display: block;
163
- text-align: center;
164
204
  }
165
205
  }
166
206
  }
@@ -168,7 +168,7 @@
168
168
  .page-title {
169
169
  font-size: var(--font-size-3xl);
170
170
  font-weight: 700;
171
- margin: 0 0 var(--space-sm);
171
+ margin: 0 0 var(--space-md);
172
172
  }
173
173
 
174
174
  .page-description {