@forwardimpact/pathway 0.4.0 → 0.5.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/behaviour.js +1 -1
- package/app/commands/command-factory.js +2 -2
- package/app/commands/discipline.js +1 -1
- package/app/commands/driver.js +1 -1
- package/app/commands/grade.js +1 -1
- package/app/commands/serve.js +2 -2
- package/app/commands/site.js +22 -2
- package/app/commands/skill.js +1 -1
- package/app/commands/stage.js +1 -1
- package/app/commands/track.js +1 -1
- package/app/components/card.js +11 -1
- package/app/components/code-display.js +153 -0
- package/app/components/markdown-textarea.js +68 -47
- package/app/css/bundles/app.css +14 -0
- package/app/css/components/badges.css +15 -8
- package/app/css/components/forms.css +23 -13
- package/app/css/components/surfaces.css +49 -3
- package/app/css/components/typography.css +1 -2
- package/app/css/pages/agent-builder.css +11 -102
- package/app/css/pages/detail.css +11 -1
- package/app/css/tokens.css +3 -0
- package/app/formatters/agent/dom.js +26 -71
- package/app/formatters/agent/profile.js +11 -6
- package/app/formatters/grade/dom.js +6 -6
- package/app/formatters/job/dom.js +3 -3
- package/app/formatters/json-ld.js +1 -1
- package/app/formatters/skill/dom.js +68 -56
- package/app/formatters/skill/shared.js +3 -1
- package/app/formatters/stage/microdata.js +2 -2
- package/app/formatters/stage/shared.js +3 -3
- package/app/formatters/tool/shared.js +6 -0
- package/app/handout-main.js +12 -11
- package/app/index.html +7 -1
- package/app/lib/card-mappers.js +27 -0
- package/app/model/agent.js +21 -5
- package/app/model/checklist.js +2 -2
- package/app/model/derivation.js +2 -2
- package/app/model/levels.js +2 -2
- package/app/model/validation.js +3 -3
- package/app/pages/agent-builder.js +119 -75
- package/app/pages/landing.js +1 -1
- package/app/pages/stage.js +4 -4
- package/app/slide-main.js +1 -1
- package/app/slides/chapter.js +8 -8
- package/app/slides/index.js +1 -1
- package/app/slides/overview.js +8 -8
- package/app/slides/skill.js +1 -0
- package/examples/capabilities/business.yaml +1 -1
- package/examples/capabilities/delivery.yaml +3 -1
- package/examples/capabilities/people.yaml +1 -1
- package/examples/capabilities/reliability.yaml +3 -1
- package/examples/capabilities/scale.yaml +1 -1
- package/examples/framework.yaml +11 -11
- package/examples/stages.yaml +18 -10
- package/package.json +2 -1
- package/templates/agent.template.md +47 -17
- package/templates/job.template.md +8 -8
- package/templates/skill.template.md +12 -9
- package/examples/agents/.claude/skills/architecture-design/SKILL.md +0 -130
- package/examples/agents/.claude/skills/cloud-platforms/SKILL.md +0 -131
- package/examples/agents/.claude/skills/code-quality-review/SKILL.md +0 -108
- package/examples/agents/.claude/skills/devops-cicd/SKILL.md +0 -142
- package/examples/agents/.claude/skills/full-stack-development/SKILL.md +0 -134
- package/examples/agents/.claude/skills/sre-practices/SKILL.md +0 -163
- package/examples/agents/.claude/skills/technical-debt-management/SKILL.md +0 -164
- package/examples/agents/.github/agents/se-platform-code.agent.md +0 -132
- package/examples/agents/.github/agents/se-platform-plan.agent.md +0 -131
- package/examples/agents/.github/agents/se-platform-review.agent.md +0 -136
- package/examples/agents/.vscode/settings.json +0 -8
|
@@ -24,7 +24,7 @@ import { capitalize } from "../formatters/shared.js";
|
|
|
24
24
|
* @param {Function} config.formatDetail - Function to format detail output: (view, framework) => void
|
|
25
25
|
* @param {Function} [config.sortItems] - Optional function to sort items: (items) => sortedItems
|
|
26
26
|
* @param {Function} [config.validate] - Optional validation function: (data) => {errors: [], warnings: []}
|
|
27
|
-
* @param {string} [config.
|
|
27
|
+
* @param {string} [config.emojiIcon] - Optional emoji for the entity
|
|
28
28
|
* @returns {Function} Command handler
|
|
29
29
|
*/
|
|
30
30
|
export function createEntityCommand({
|
|
@@ -36,7 +36,7 @@ export function createEntityCommand({
|
|
|
36
36
|
formatDetail,
|
|
37
37
|
sortItems,
|
|
38
38
|
validate,
|
|
39
|
-
|
|
39
|
+
_emojiIcon = "",
|
|
40
40
|
}) {
|
|
41
41
|
return async function runCommand({ data, args, options }) {
|
|
42
42
|
const [id] = args;
|
package/app/commands/driver.js
CHANGED
package/app/commands/grade.js
CHANGED
package/app/commands/serve.js
CHANGED
|
@@ -94,7 +94,7 @@ export async function runServeCommand({ dataDir, options }) {
|
|
|
94
94
|
framework = await loadFrameworkConfig(dataDir);
|
|
95
95
|
} catch {
|
|
96
96
|
// Fallback if framework config fails
|
|
97
|
-
framework = {
|
|
97
|
+
framework = { emojiIcon: "🚀", title: "Engineering Pathway" };
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
// Generate _index.yaml files before serving
|
|
@@ -136,7 +136,7 @@ export async function runServeCommand({ dataDir, options }) {
|
|
|
136
136
|
|
|
137
137
|
server.listen(port, () => {
|
|
138
138
|
console.log(`
|
|
139
|
-
${framework.
|
|
139
|
+
${framework.emojiIcon} ${framework.title} running at http://localhost:${port}
|
|
140
140
|
📁 Data directory: ${dataDir}
|
|
141
141
|
|
|
142
142
|
Press Ctrl+C to stop the server.
|
package/app/commands/site.js
CHANGED
|
@@ -38,6 +38,11 @@ const PUBLIC_ASSETS = [
|
|
|
38
38
|
"formatters",
|
|
39
39
|
];
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Files and directories to copy from project root
|
|
43
|
+
*/
|
|
44
|
+
const ROOT_ASSETS = ["templates"];
|
|
45
|
+
|
|
41
46
|
/**
|
|
42
47
|
* Run the site command
|
|
43
48
|
* @param {Object} params - Command parameters
|
|
@@ -53,11 +58,11 @@ export async function runSiteCommand({ dataDir, options }) {
|
|
|
53
58
|
try {
|
|
54
59
|
framework = await loadFrameworkConfig(dataDir);
|
|
55
60
|
} catch {
|
|
56
|
-
framework = {
|
|
61
|
+
framework = { emojiIcon: "🚀", title: "Engineering Pathway" };
|
|
57
62
|
}
|
|
58
63
|
|
|
59
64
|
console.log(`
|
|
60
|
-
${framework.
|
|
65
|
+
${framework.emojiIcon} Generating ${framework.title} static site...
|
|
61
66
|
`);
|
|
62
67
|
|
|
63
68
|
// Clean output directory if requested
|
|
@@ -93,6 +98,21 @@ ${framework.emoji} Generating ${framework.title} static site...
|
|
|
93
98
|
}
|
|
94
99
|
}
|
|
95
100
|
|
|
101
|
+
// Copy root assets (templates, etc.)
|
|
102
|
+
const rootDir = join(appDir, "..");
|
|
103
|
+
for (const asset of ROOT_ASSETS) {
|
|
104
|
+
const src = join(rootDir, asset);
|
|
105
|
+
const dest = join(outputDir, asset);
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
await access(src);
|
|
109
|
+
await cp(src, dest, { recursive: true });
|
|
110
|
+
console.log(` ✓ ${asset}`);
|
|
111
|
+
} catch (err) {
|
|
112
|
+
console.log(` ⚠️ Skipped ${asset}: ${err.message}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
96
116
|
// Copy data directory (dereference symlinks to copy actual content)
|
|
97
117
|
console.log("📁 Copying data files...");
|
|
98
118
|
const dataOutputDir = join(outputDir, "data");
|
package/app/commands/skill.js
CHANGED
package/app/commands/stage.js
CHANGED
package/app/commands/track.js
CHANGED
package/app/components/card.js
CHANGED
|
@@ -13,6 +13,7 @@ import { div, h3, p, span } from "../lib/render.js";
|
|
|
13
13
|
* @param {HTMLElement[]} [options.badges] - Badges to display
|
|
14
14
|
* @param {HTMLElement[]} [options.meta] - Meta information
|
|
15
15
|
* @param {HTMLElement} [options.content] - Additional content
|
|
16
|
+
* @param {HTMLElement} [options.icon] - Icon element to display
|
|
16
17
|
* @param {string} [options.className] - Additional CSS class
|
|
17
18
|
* @returns {HTMLElement}
|
|
18
19
|
*/
|
|
@@ -23,13 +24,22 @@ export function createCard({
|
|
|
23
24
|
badges = [],
|
|
24
25
|
meta = [],
|
|
25
26
|
content,
|
|
27
|
+
icon,
|
|
26
28
|
className = "",
|
|
27
29
|
}) {
|
|
28
30
|
const isClickable = !!href;
|
|
29
31
|
|
|
32
|
+
const titleContent = icon
|
|
33
|
+
? div(
|
|
34
|
+
{ className: "card-title-with-icon" },
|
|
35
|
+
icon,
|
|
36
|
+
h3({ className: "card-title" }, title),
|
|
37
|
+
)
|
|
38
|
+
: h3({ className: "card-title" }, title);
|
|
39
|
+
|
|
30
40
|
const cardHeader = div(
|
|
31
41
|
{ className: "card-header" },
|
|
32
|
-
|
|
42
|
+
titleContent,
|
|
33
43
|
badges.length > 0 ? div({ className: "card-badges" }, ...badges) : null,
|
|
34
44
|
);
|
|
35
45
|
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code Display Component
|
|
3
|
+
*
|
|
4
|
+
* Reusable read-only code block with copy buttons and syntax highlighting.
|
|
5
|
+
* Used for markdown content, agent profiles, skills, and code snippets.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/* global Prism */
|
|
9
|
+
import { div, p, span, button } from "../lib/render.js";
|
|
10
|
+
|
|
11
|
+
const COPY_LABEL = "📋 Copy";
|
|
12
|
+
const COPY_HTML_LABEL = "Copy as HTML";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Create a copy button that copies content to clipboard
|
|
16
|
+
* @param {string} content - The text content to copy
|
|
17
|
+
* @returns {HTMLElement}
|
|
18
|
+
*/
|
|
19
|
+
export function createCopyButton(content) {
|
|
20
|
+
const btn = button(
|
|
21
|
+
{
|
|
22
|
+
className: "btn btn-sm copy-btn",
|
|
23
|
+
onClick: async () => {
|
|
24
|
+
try {
|
|
25
|
+
await navigator.clipboard.writeText(content);
|
|
26
|
+
btn.textContent = "✓ Copied!";
|
|
27
|
+
btn.classList.add("copied");
|
|
28
|
+
setTimeout(() => {
|
|
29
|
+
btn.textContent = COPY_LABEL;
|
|
30
|
+
btn.classList.remove("copied");
|
|
31
|
+
}, 2000);
|
|
32
|
+
} catch (err) {
|
|
33
|
+
console.error("Failed to copy:", err);
|
|
34
|
+
btn.textContent = "Copy failed";
|
|
35
|
+
setTimeout(() => {
|
|
36
|
+
btn.textContent = COPY_LABEL;
|
|
37
|
+
}, 2000);
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
COPY_LABEL,
|
|
42
|
+
);
|
|
43
|
+
return btn;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Create a copy button that copies HTML to clipboard (for rich text pasting)
|
|
48
|
+
* @param {string} html - The HTML content to copy
|
|
49
|
+
* @returns {HTMLElement}
|
|
50
|
+
*/
|
|
51
|
+
function createCopyHtmlButton(html) {
|
|
52
|
+
const btn = button(
|
|
53
|
+
{
|
|
54
|
+
className: "btn btn-sm btn-secondary copy-btn",
|
|
55
|
+
onClick: async () => {
|
|
56
|
+
try {
|
|
57
|
+
const blob = new Blob([html], { type: "text/html" });
|
|
58
|
+
const clipboardItem = new ClipboardItem({ "text/html": blob });
|
|
59
|
+
await navigator.clipboard.write([clipboardItem]);
|
|
60
|
+
btn.textContent = "✓ Copied!";
|
|
61
|
+
btn.classList.add("copied");
|
|
62
|
+
setTimeout(() => {
|
|
63
|
+
btn.textContent = COPY_HTML_LABEL;
|
|
64
|
+
btn.classList.remove("copied");
|
|
65
|
+
}, 2000);
|
|
66
|
+
} catch (err) {
|
|
67
|
+
console.error("Failed to copy:", err);
|
|
68
|
+
btn.textContent = "Copy failed";
|
|
69
|
+
setTimeout(() => {
|
|
70
|
+
btn.textContent = COPY_HTML_LABEL;
|
|
71
|
+
}, 2000);
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
COPY_HTML_LABEL,
|
|
76
|
+
);
|
|
77
|
+
return btn;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Create a code display component with syntax highlighting and copy button
|
|
82
|
+
* @param {Object} options
|
|
83
|
+
* @param {string} options.content - The code content to display
|
|
84
|
+
* @param {string} [options.language="markdown"] - Language for syntax highlighting
|
|
85
|
+
* @param {string} [options.filename] - Optional filename to display in header
|
|
86
|
+
* @param {string} [options.description] - Optional description text
|
|
87
|
+
* @param {Function} [options.toHtml] - Function to convert content to HTML (enables "Copy as HTML" button)
|
|
88
|
+
* @param {number} [options.minHeight] - Optional minimum height in pixels
|
|
89
|
+
* @param {number} [options.maxHeight] - Optional maximum height in pixels
|
|
90
|
+
* @returns {HTMLElement}
|
|
91
|
+
*/
|
|
92
|
+
export function createCodeDisplay({
|
|
93
|
+
content,
|
|
94
|
+
language = "markdown",
|
|
95
|
+
filename,
|
|
96
|
+
description,
|
|
97
|
+
toHtml,
|
|
98
|
+
minHeight,
|
|
99
|
+
maxHeight,
|
|
100
|
+
}) {
|
|
101
|
+
// Create highlighted code block
|
|
102
|
+
const pre = document.createElement("pre");
|
|
103
|
+
pre.className = "code-display";
|
|
104
|
+
if (minHeight) pre.style.minHeight = `${minHeight}px`;
|
|
105
|
+
if (maxHeight) {
|
|
106
|
+
pre.style.maxHeight = `${maxHeight}px`;
|
|
107
|
+
pre.style.overflowY = "auto";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const code = document.createElement("code");
|
|
111
|
+
if (language) {
|
|
112
|
+
code.className = `language-${language}`;
|
|
113
|
+
}
|
|
114
|
+
code.textContent = content;
|
|
115
|
+
pre.appendChild(code);
|
|
116
|
+
|
|
117
|
+
// Apply Prism highlighting if available and language specified
|
|
118
|
+
if (language && typeof Prism !== "undefined") {
|
|
119
|
+
Prism.highlightElement(code);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Build header content
|
|
123
|
+
const headerLeft = [];
|
|
124
|
+
if (filename) {
|
|
125
|
+
headerLeft.push(span({ className: "code-display-filename" }, filename));
|
|
126
|
+
}
|
|
127
|
+
if (description) {
|
|
128
|
+
headerLeft.push(p({ className: "text-muted" }, description));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Build buttons
|
|
132
|
+
const buttons = [createCopyButton(content)];
|
|
133
|
+
if (toHtml) {
|
|
134
|
+
buttons.push(createCopyHtmlButton(toHtml(content)));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Only show header if there's content for it
|
|
138
|
+
const hasHeader = headerLeft.length > 0 || buttons.length > 0;
|
|
139
|
+
|
|
140
|
+
return div(
|
|
141
|
+
{ className: "code-display-container" },
|
|
142
|
+
hasHeader
|
|
143
|
+
? div(
|
|
144
|
+
{ className: "code-display-header" },
|
|
145
|
+
headerLeft.length > 0
|
|
146
|
+
? div({ className: "code-display-info" }, ...headerLeft)
|
|
147
|
+
: null,
|
|
148
|
+
div({ className: "button-group" }, ...buttons),
|
|
149
|
+
)
|
|
150
|
+
: null,
|
|
151
|
+
pre,
|
|
152
|
+
);
|
|
153
|
+
}
|
|
@@ -1,47 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Code Display Component
|
|
3
3
|
*
|
|
4
|
-
* Reusable read-only
|
|
5
|
-
* Used
|
|
4
|
+
* Reusable read-only code block with copy buttons and syntax highlighting.
|
|
5
|
+
* Used for markdown content, agent profiles, skills, and code snippets.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
/* global Prism */
|
|
9
|
-
import { div, p, button } from "../lib/render.js";
|
|
9
|
+
import { div, p, span, button } from "../lib/render.js";
|
|
10
|
+
|
|
11
|
+
const COPY_LABEL = "📋 Copy";
|
|
12
|
+
const COPY_HTML_LABEL = "Copy as HTML";
|
|
10
13
|
|
|
11
14
|
/**
|
|
12
15
|
* Create a copy button that copies content to clipboard
|
|
13
16
|
* @param {string} content - The text content to copy
|
|
14
|
-
* @param {string} label - Button label text
|
|
15
|
-
* @param {string} [className="btn btn-primary"] - Button class
|
|
16
17
|
* @returns {HTMLElement}
|
|
17
18
|
*/
|
|
18
|
-
export function createCopyButton(
|
|
19
|
-
content,
|
|
20
|
-
label,
|
|
21
|
-
className = "btn btn-primary",
|
|
22
|
-
) {
|
|
19
|
+
export function createCopyButton(content) {
|
|
23
20
|
const btn = button(
|
|
24
21
|
{
|
|
25
|
-
className:
|
|
22
|
+
className: "btn btn-sm copy-btn",
|
|
26
23
|
onClick: async () => {
|
|
27
24
|
try {
|
|
28
25
|
await navigator.clipboard.writeText(content);
|
|
29
26
|
btn.textContent = "✓ Copied!";
|
|
30
27
|
btn.classList.add("copied");
|
|
31
28
|
setTimeout(() => {
|
|
32
|
-
btn.textContent =
|
|
29
|
+
btn.textContent = COPY_LABEL;
|
|
33
30
|
btn.classList.remove("copied");
|
|
34
31
|
}, 2000);
|
|
35
32
|
} catch (err) {
|
|
36
33
|
console.error("Failed to copy:", err);
|
|
37
34
|
btn.textContent = "Copy failed";
|
|
38
35
|
setTimeout(() => {
|
|
39
|
-
btn.textContent =
|
|
36
|
+
btn.textContent = COPY_LABEL;
|
|
40
37
|
}, 2000);
|
|
41
38
|
}
|
|
42
39
|
},
|
|
43
40
|
},
|
|
44
|
-
|
|
41
|
+
COPY_LABEL,
|
|
45
42
|
);
|
|
46
43
|
return btn;
|
|
47
44
|
}
|
|
@@ -49,13 +46,12 @@ export function createCopyButton(
|
|
|
49
46
|
/**
|
|
50
47
|
* Create a copy button that copies HTML to clipboard (for rich text pasting)
|
|
51
48
|
* @param {string} html - The HTML content to copy
|
|
52
|
-
* @param {string} label - Button label text
|
|
53
49
|
* @returns {HTMLElement}
|
|
54
50
|
*/
|
|
55
|
-
|
|
51
|
+
function createCopyHtmlButton(html) {
|
|
56
52
|
const btn = button(
|
|
57
53
|
{
|
|
58
|
-
className: "btn btn-secondary copy-btn",
|
|
54
|
+
className: "btn btn-sm btn-secondary copy-btn",
|
|
59
55
|
onClick: async () => {
|
|
60
56
|
try {
|
|
61
57
|
const blob = new Blob([html], { type: "text/html" });
|
|
@@ -64,69 +60,94 @@ export function createCopyHtmlButton(html, label) {
|
|
|
64
60
|
btn.textContent = "✓ Copied!";
|
|
65
61
|
btn.classList.add("copied");
|
|
66
62
|
setTimeout(() => {
|
|
67
|
-
btn.textContent =
|
|
63
|
+
btn.textContent = COPY_HTML_LABEL;
|
|
68
64
|
btn.classList.remove("copied");
|
|
69
65
|
}, 2000);
|
|
70
66
|
} catch (err) {
|
|
71
67
|
console.error("Failed to copy:", err);
|
|
72
68
|
btn.textContent = "Copy failed";
|
|
73
69
|
setTimeout(() => {
|
|
74
|
-
btn.textContent =
|
|
70
|
+
btn.textContent = COPY_HTML_LABEL;
|
|
75
71
|
}, 2000);
|
|
76
72
|
}
|
|
77
73
|
},
|
|
78
74
|
},
|
|
79
|
-
|
|
75
|
+
COPY_HTML_LABEL,
|
|
80
76
|
);
|
|
81
77
|
return btn;
|
|
82
78
|
}
|
|
83
79
|
|
|
84
80
|
/**
|
|
85
|
-
* Create a
|
|
81
|
+
* Create a code display component with syntax highlighting and copy button
|
|
86
82
|
* @param {Object} options
|
|
87
|
-
* @param {string} options.
|
|
88
|
-
* @param {string} [options.
|
|
89
|
-
* @param {string} [options.
|
|
90
|
-
* @param {
|
|
91
|
-
* @param {
|
|
92
|
-
* @param {number} [options.minHeight
|
|
83
|
+
* @param {string} options.content - The code content to display
|
|
84
|
+
* @param {string} [options.language="markdown"] - Language for syntax highlighting
|
|
85
|
+
* @param {string} [options.filename] - Optional filename to display in header
|
|
86
|
+
* @param {string} [options.description] - Optional description text
|
|
87
|
+
* @param {Function} [options.toHtml] - Function to convert content to HTML (enables "Copy as HTML" button)
|
|
88
|
+
* @param {number} [options.minHeight] - Optional minimum height in pixels
|
|
89
|
+
* @param {number} [options.maxHeight] - Optional maximum height in pixels
|
|
93
90
|
* @returns {HTMLElement}
|
|
94
91
|
*/
|
|
95
|
-
export function
|
|
96
|
-
|
|
92
|
+
export function createCodeDisplay({
|
|
93
|
+
content,
|
|
94
|
+
language = "markdown",
|
|
95
|
+
filename,
|
|
97
96
|
description,
|
|
98
|
-
copyLabel = "Copy Markdown",
|
|
99
97
|
toHtml,
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
minHeight,
|
|
99
|
+
maxHeight,
|
|
102
100
|
}) {
|
|
103
101
|
// Create highlighted code block
|
|
104
102
|
const pre = document.createElement("pre");
|
|
105
|
-
pre.className = "
|
|
106
|
-
pre.style.minHeight = `${minHeight}px`;
|
|
103
|
+
pre.className = "code-display";
|
|
104
|
+
if (minHeight) pre.style.minHeight = `${minHeight}px`;
|
|
105
|
+
if (maxHeight) {
|
|
106
|
+
pre.style.maxHeight = `${maxHeight}px`;
|
|
107
|
+
pre.style.overflowY = "auto";
|
|
108
|
+
}
|
|
107
109
|
|
|
108
110
|
const code = document.createElement("code");
|
|
109
|
-
|
|
110
|
-
|
|
111
|
+
if (language) {
|
|
112
|
+
code.className = `language-${language}`;
|
|
113
|
+
}
|
|
114
|
+
code.textContent = content;
|
|
111
115
|
pre.appendChild(code);
|
|
112
116
|
|
|
113
|
-
// Apply Prism highlighting if available
|
|
114
|
-
if (typeof Prism !== "undefined") {
|
|
117
|
+
// Apply Prism highlighting if available and language specified
|
|
118
|
+
if (language && typeof Prism !== "undefined") {
|
|
115
119
|
Prism.highlightElement(code);
|
|
116
120
|
}
|
|
117
121
|
|
|
118
|
-
|
|
122
|
+
// Build header content
|
|
123
|
+
const headerLeft = [];
|
|
124
|
+
if (filename) {
|
|
125
|
+
headerLeft.push(span({ className: "code-display-filename" }, filename));
|
|
126
|
+
}
|
|
127
|
+
if (description) {
|
|
128
|
+
headerLeft.push(p({ className: "text-muted" }, description));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Build buttons
|
|
132
|
+
const buttons = [createCopyButton(content)];
|
|
119
133
|
if (toHtml) {
|
|
120
|
-
buttons.push(createCopyHtmlButton(toHtml(
|
|
134
|
+
buttons.push(createCopyHtmlButton(toHtml(content)));
|
|
121
135
|
}
|
|
122
136
|
|
|
137
|
+
// Only show header if there's content for it
|
|
138
|
+
const hasHeader = headerLeft.length > 0 || buttons.length > 0;
|
|
139
|
+
|
|
123
140
|
return div(
|
|
124
|
-
{ className: "
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
141
|
+
{ className: "code-display-container" },
|
|
142
|
+
hasHeader
|
|
143
|
+
? div(
|
|
144
|
+
{ className: "code-display-header" },
|
|
145
|
+
headerLeft.length > 0
|
|
146
|
+
? div({ className: "code-display-info" }, ...headerLeft)
|
|
147
|
+
: null,
|
|
148
|
+
div({ className: "button-group" }, ...buttons),
|
|
149
|
+
)
|
|
150
|
+
: null,
|
|
130
151
|
pre,
|
|
131
152
|
);
|
|
132
153
|
}
|
package/app/css/bundles/app.css
CHANGED
|
@@ -38,3 +38,17 @@
|
|
|
38
38
|
@import "../pages/self-assessment.css" layer(pages);
|
|
39
39
|
@import "../pages/assessment-results.css" layer(pages);
|
|
40
40
|
@import "../pages/progress-builder.css" layer(pages);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Prism overrides (unlayered to beat Prism's unlayered CSS)
|
|
44
|
+
*/
|
|
45
|
+
pre.code-display[class*="language-"] {
|
|
46
|
+
font-family: var(--font-family-mono);
|
|
47
|
+
font-size: var(--font-size-xs);
|
|
48
|
+
background-color: var(--color-surface);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
pre.code-display > code[class*="language-"] {
|
|
52
|
+
font-family: inherit;
|
|
53
|
+
font-size: inherit;
|
|
54
|
+
}
|
|
@@ -5,10 +5,13 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
@layer components {
|
|
8
|
-
/* Base badge */
|
|
8
|
+
/* Base badge - 28px height matches tool icons for consistent row heights */
|
|
9
9
|
.badge {
|
|
10
|
-
display: inline-
|
|
11
|
-
|
|
10
|
+
display: inline-flex;
|
|
11
|
+
align-items: center;
|
|
12
|
+
justify-content: center;
|
|
13
|
+
height: 28px;
|
|
14
|
+
padding: 0 var(--space-sm);
|
|
12
15
|
border-radius: var(--radius-md);
|
|
13
16
|
font-size: var(--font-size-xs);
|
|
14
17
|
font-weight: 500;
|
|
@@ -78,10 +81,13 @@
|
|
|
78
81
|
color: #5b21b6;
|
|
79
82
|
}
|
|
80
83
|
|
|
81
|
-
/* Level badge base style */
|
|
84
|
+
/* Level badge base style - 28px height matches tool icons */
|
|
82
85
|
.level-badge {
|
|
83
|
-
display: inline-
|
|
84
|
-
|
|
86
|
+
display: inline-flex;
|
|
87
|
+
align-items: center;
|
|
88
|
+
justify-content: center;
|
|
89
|
+
height: 28px;
|
|
90
|
+
padding: 0 var(--space-sm);
|
|
85
91
|
border-radius: var(--radius-md);
|
|
86
92
|
font-size: var(--font-size-sm);
|
|
87
93
|
font-weight: 500;
|
|
@@ -142,11 +148,12 @@
|
|
|
142
148
|
padding: var(--space-xs) var(--space-sm);
|
|
143
149
|
}
|
|
144
150
|
|
|
145
|
-
/* Modifier tags -
|
|
151
|
+
/* Modifier tags - 28px height matches badges and tool icons */
|
|
146
152
|
.modifier {
|
|
147
153
|
display: inline-flex;
|
|
148
154
|
align-items: center;
|
|
149
|
-
|
|
155
|
+
height: 28px;
|
|
156
|
+
padding: 0 var(--space-sm);
|
|
150
157
|
border-radius: var(--radius-sm);
|
|
151
158
|
font-size: var(--font-size-sm);
|
|
152
159
|
font-weight: 600;
|
|
@@ -103,14 +103,14 @@
|
|
|
103
103
|
color: var(--color-primary);
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
/*
|
|
107
|
-
.
|
|
106
|
+
/* Code display - unified component for code/markdown with copy buttons */
|
|
107
|
+
.code-display-container {
|
|
108
108
|
display: flex;
|
|
109
109
|
flex-direction: column;
|
|
110
|
-
gap: var(--space-
|
|
110
|
+
gap: var(--space-sm);
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
.
|
|
113
|
+
.code-display-header {
|
|
114
114
|
display: flex;
|
|
115
115
|
justify-content: space-between;
|
|
116
116
|
align-items: center;
|
|
@@ -118,21 +118,32 @@
|
|
|
118
118
|
gap: var(--space-md);
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
.
|
|
122
|
-
|
|
121
|
+
.code-display-info {
|
|
122
|
+
display: flex;
|
|
123
|
+
flex-direction: column;
|
|
124
|
+
gap: var(--space-xs);
|
|
123
125
|
flex: 1;
|
|
124
126
|
min-width: 200px;
|
|
125
127
|
}
|
|
126
128
|
|
|
127
|
-
.
|
|
129
|
+
.code-display-info .text-muted {
|
|
130
|
+
margin: 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.code-display-filename {
|
|
134
|
+
font-family: var(--font-family-mono);
|
|
135
|
+
font-size: var(--font-size-xs);
|
|
136
|
+
color: var(--color-text-muted);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.code-display {
|
|
128
140
|
width: 100%;
|
|
129
141
|
margin: 0;
|
|
130
142
|
padding: var(--space-md);
|
|
131
|
-
font-family:
|
|
132
|
-
|
|
133
|
-
font-size: var(--font-size-sm);
|
|
143
|
+
font-family: var(--font-family-mono);
|
|
144
|
+
font-size: var(--font-size-xs);
|
|
134
145
|
line-height: 1.6;
|
|
135
|
-
background-color: var(--color-surface)
|
|
146
|
+
background-color: var(--color-surface);
|
|
136
147
|
border: 1px solid var(--color-border);
|
|
137
148
|
border-radius: var(--radius-md);
|
|
138
149
|
overflow: auto;
|
|
@@ -141,10 +152,9 @@
|
|
|
141
152
|
word-wrap: break-word;
|
|
142
153
|
}
|
|
143
154
|
|
|
144
|
-
.
|
|
155
|
+
.code-display code {
|
|
145
156
|
background: transparent;
|
|
146
157
|
padding: 0;
|
|
147
|
-
font-size: inherit;
|
|
148
158
|
color: inherit;
|
|
149
159
|
}
|
|
150
160
|
}
|