@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.
Files changed (69) hide show
  1. package/app/commands/behaviour.js +1 -1
  2. package/app/commands/command-factory.js +2 -2
  3. package/app/commands/discipline.js +1 -1
  4. package/app/commands/driver.js +1 -1
  5. package/app/commands/grade.js +1 -1
  6. package/app/commands/serve.js +2 -2
  7. package/app/commands/site.js +22 -2
  8. package/app/commands/skill.js +1 -1
  9. package/app/commands/stage.js +1 -1
  10. package/app/commands/track.js +1 -1
  11. package/app/components/card.js +11 -1
  12. package/app/components/code-display.js +153 -0
  13. package/app/components/markdown-textarea.js +68 -47
  14. package/app/css/bundles/app.css +14 -0
  15. package/app/css/components/badges.css +15 -8
  16. package/app/css/components/forms.css +23 -13
  17. package/app/css/components/surfaces.css +49 -3
  18. package/app/css/components/typography.css +1 -2
  19. package/app/css/pages/agent-builder.css +11 -102
  20. package/app/css/pages/detail.css +11 -1
  21. package/app/css/tokens.css +3 -0
  22. package/app/formatters/agent/dom.js +26 -71
  23. package/app/formatters/agent/profile.js +11 -6
  24. package/app/formatters/grade/dom.js +6 -6
  25. package/app/formatters/job/dom.js +3 -3
  26. package/app/formatters/json-ld.js +1 -1
  27. package/app/formatters/skill/dom.js +68 -56
  28. package/app/formatters/skill/shared.js +3 -1
  29. package/app/formatters/stage/microdata.js +2 -2
  30. package/app/formatters/stage/shared.js +3 -3
  31. package/app/formatters/tool/shared.js +6 -0
  32. package/app/handout-main.js +12 -11
  33. package/app/index.html +7 -1
  34. package/app/lib/card-mappers.js +27 -0
  35. package/app/model/agent.js +21 -5
  36. package/app/model/checklist.js +2 -2
  37. package/app/model/derivation.js +2 -2
  38. package/app/model/levels.js +2 -2
  39. package/app/model/validation.js +3 -3
  40. package/app/pages/agent-builder.js +119 -75
  41. package/app/pages/landing.js +1 -1
  42. package/app/pages/stage.js +4 -4
  43. package/app/slide-main.js +1 -1
  44. package/app/slides/chapter.js +8 -8
  45. package/app/slides/index.js +1 -1
  46. package/app/slides/overview.js +8 -8
  47. package/app/slides/skill.js +1 -0
  48. package/examples/capabilities/business.yaml +1 -1
  49. package/examples/capabilities/delivery.yaml +3 -1
  50. package/examples/capabilities/people.yaml +1 -1
  51. package/examples/capabilities/reliability.yaml +3 -1
  52. package/examples/capabilities/scale.yaml +1 -1
  53. package/examples/framework.yaml +11 -11
  54. package/examples/stages.yaml +18 -10
  55. package/package.json +2 -1
  56. package/templates/agent.template.md +47 -17
  57. package/templates/job.template.md +8 -8
  58. package/templates/skill.template.md +12 -9
  59. package/examples/agents/.claude/skills/architecture-design/SKILL.md +0 -130
  60. package/examples/agents/.claude/skills/cloud-platforms/SKILL.md +0 -131
  61. package/examples/agents/.claude/skills/code-quality-review/SKILL.md +0 -108
  62. package/examples/agents/.claude/skills/devops-cicd/SKILL.md +0 -142
  63. package/examples/agents/.claude/skills/full-stack-development/SKILL.md +0 -134
  64. package/examples/agents/.claude/skills/sre-practices/SKILL.md +0 -163
  65. package/examples/agents/.claude/skills/technical-debt-management/SKILL.md +0 -164
  66. package/examples/agents/.github/agents/se-platform-code.agent.md +0 -132
  67. package/examples/agents/.github/agents/se-platform-plan.agent.md +0 -131
  68. package/examples/agents/.github/agents/se-platform-review.agent.md +0 -136
  69. package/examples/agents/.vscode/settings.json +0 -8
@@ -57,5 +57,5 @@ export const runBehaviourCommand = createEntityCommand({
57
57
  }),
58
58
  formatSummary,
59
59
  formatDetail,
60
- emoji: "🧠",
60
+ emojiIcon: "🧠",
61
61
  });
@@ -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.emoji] - Optional emoji for the entity
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
- _emoji = "",
39
+ _emojiIcon = "",
40
40
  }) {
41
41
  return async function runCommand({ data, args, options }) {
42
42
  const [id] = args;
@@ -54,5 +54,5 @@ export const runDisciplineCommand = createEntityCommand({
54
54
  }),
55
55
  formatSummary,
56
56
  formatDetail,
57
- emoji: "📋",
57
+ emojiIcon: "📋",
58
58
  });
@@ -90,5 +90,5 @@ export const runDriverCommand = createEntityCommand({
90
90
  }),
91
91
  formatSummary,
92
92
  formatDetail,
93
- emoji: "🎯",
93
+ emojiIcon: "🎯",
94
94
  });
@@ -56,5 +56,5 @@ export const runGradeCommand = createEntityCommand({
56
56
  presentDetail: (entity) => entity,
57
57
  formatSummary,
58
58
  formatDetail,
59
- emoji: "📊",
59
+ emojiIcon: "📊",
60
60
  });
@@ -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 = { emoji: "🚀", title: "Engineering Pathway" };
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.emoji} ${framework.title} running at http://localhost:${port}
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.
@@ -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 = { emoji: "🚀", title: "Engineering Pathway" };
61
+ framework = { emojiIcon: "🚀", title: "Engineering Pathway" };
57
62
  }
58
63
 
59
64
  console.log(`
60
- ${framework.emoji} Generating ${framework.title} static site...
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");
@@ -98,7 +98,7 @@ const baseSkillCommand = createEntityCommand({
98
98
  }),
99
99
  formatSummary,
100
100
  formatDetail,
101
- emoji: "📚",
101
+ emojiIcon: "📚",
102
102
  });
103
103
 
104
104
  /**
@@ -115,5 +115,5 @@ export const runStageCommand = createEntityCommand({
115
115
  }),
116
116
  formatSummary,
117
117
  formatDetail,
118
- emoji: "🔄",
118
+ emojiIcon: "🔄",
119
119
  });
@@ -63,5 +63,5 @@ export const runTrackCommand = createEntityCommand({
63
63
  sortItems: sortTracksByName,
64
64
  formatSummary,
65
65
  formatDetail,
66
- emoji: "🛤️",
66
+ emojiIcon: "🛤️",
67
67
  });
@@ -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
- h3({ className: "card-title" }, title),
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
- * Markdown Textarea Component
2
+ * Code Display Component
3
3
  *
4
- * Reusable read-only textarea with copy buttons for displaying markdown content.
5
- * Used by job descriptions and skill implementation patterns.
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: `${className} copy-btn`,
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 = label;
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 = label;
36
+ btn.textContent = COPY_LABEL;
40
37
  }, 2000);
41
38
  }
42
39
  },
43
40
  },
44
- label,
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
- export function createCopyHtmlButton(html, label) {
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 = label;
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 = label;
70
+ btn.textContent = COPY_HTML_LABEL;
75
71
  }, 2000);
76
72
  }
77
73
  },
78
74
  },
79
- label,
75
+ COPY_HTML_LABEL,
80
76
  );
81
77
  return btn;
82
78
  }
83
79
 
84
80
  /**
85
- * Create a markdown textarea with copy buttons
81
+ * Create a code display component with syntax highlighting and copy button
86
82
  * @param {Object} options
87
- * @param {string} options.markdown - The markdown content to display
88
- * @param {string} [options.description] - Optional description text above the textarea
89
- * @param {string} [options.copyLabel="Copy Markdown"] - Label for the copy button
90
- * @param {Function} [options.toHtml] - Optional function to convert markdown to HTML for rich copy
91
- * @param {string} [options.copyHtmlLabel="Copy as HTML"] - Label for the HTML copy button
92
- * @param {number} [options.minHeight=300] - Minimum height in pixels
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 createMarkdownTextarea({
96
- markdown,
92
+ export function createCodeDisplay({
93
+ content,
94
+ language = "markdown",
95
+ filename,
97
96
  description,
98
- copyLabel = "Copy Markdown",
99
97
  toHtml,
100
- copyHtmlLabel = "Copy as HTML",
101
- minHeight = 300,
98
+ minHeight,
99
+ maxHeight,
102
100
  }) {
103
101
  // Create highlighted code block
104
102
  const pre = document.createElement("pre");
105
- pre.className = "markdown-display";
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
- code.className = "language-markdown";
110
- code.textContent = markdown;
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
- const buttons = [createCopyButton(markdown, copyLabel)];
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(markdown), copyHtmlLabel));
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: "markdown-textarea-container" },
125
- div(
126
- { className: "markdown-textarea-header" },
127
- description ? p({ className: "text-muted" }, description) : null,
128
- div({ className: "button-group" }, ...buttons),
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
  }
@@ -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-block;
11
- padding: var(--space-xs) var(--space-sm);
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-block;
84
- padding: var(--space-xs) var(--space-sm);
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 - consolidated definition */
151
+ /* Modifier tags - 28px height matches badges and tool icons */
146
152
  .modifier {
147
153
  display: inline-flex;
148
154
  align-items: center;
149
- padding: var(--space-xs) var(--space-sm);
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
- /* Markdown display - read-only code block with copy buttons */
107
- .markdown-textarea-container {
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-md);
110
+ gap: var(--space-sm);
111
111
  }
112
112
 
113
- .markdown-textarea-header {
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
- .markdown-textarea-header .text-muted {
122
- margin: 0;
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
- .markdown-display {
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
- ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
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) !important;
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
- .markdown-display code {
155
+ .code-display code {
145
156
  background: transparent;
146
157
  padding: 0;
147
- font-size: inherit;
148
158
  color: inherit;
149
159
  }
150
160
  }