diff --git a/.github/workflows/guidelines.md b/.github/workflows/guidelines.md index f827acfe..db8bbf95 100644 --- a/.github/workflows/guidelines.md +++ b/.github/workflows/guidelines.md @@ -229,6 +229,9 @@ Multi-line callout (summary + body): - Use ` ```text ` for terminal output that learners read but do not type (for example, expected command output or error messages). - Use ` ```yaml ` for standalone YAML configuration fragments, ` ```markdown ` for Markdown syntax examples, and ` ```html ` or ` ```xml ` for markup. - When showing an agentic workflow example that includes both frontmatter and prompt body, prefer a ` ```markdown ` block that shows the full Markdown+frontmatter file shape instead of isolating the frontmatter in a standalone ` ```yaml ` region. +- When a fenced snippet represents the contents of a file, add the filename after the language token so the rendered snippet chrome can surface it (for example, ` ```markdown .github/workflows/daily-status.md ` or ` ```yaml .github/workflows/daily-status.lock.yml `). +- Prefer the bare relative path form for filename metadata in workshop content. The renderer also supports keyed metadata such as `title=` or `file=`, but workshop pages should use the bare path consistently unless a page needs a more complex title. +- Match the filename extension to the snippet language (`.md` for `markdown`, `.yml`/`.yaml` for `yaml`, and so on), and keep the same filename across a page while the learner is still editing that same file. ## Enterprise user preference in design decisions diff --git a/scripts/build-docs.test.js b/scripts/build-docs.test.js index adbe2f51..4c78a24e 100644 --- a/scripts/build-docs.test.js +++ b/scripts/build-docs.test.js @@ -26,6 +26,22 @@ function buildDocs() { }; } +function renderSnippet(markdown) { + return execFileSync( + process.execPath, + [ + "-e", + ` + const { marked } = require("marked"); + const { setupBasePlugins } = require("./scripts/lib/marked-setup"); + setupBasePlugins(); + process.stdout.write(marked.parse(${JSON.stringify(markdown)})); + `, + ], + { cwd: repoDir, encoding: "utf8" } + ); +} + test("workshop SPA renders a single document h1", () => { const { html } = buildDocs(); @@ -125,6 +141,21 @@ test("markdown, md, yaml, and yml code blocks use compact icon-only editor chrom assert.ok(css.includes("padding: 12px;"), "expected compact YAML code padding"); }); +test("code blocks wrap long lines without relying on mobile-only styles", () => { + const { css } = buildDocs(); + + assert.ok(css.includes(".markdown-body pre {\n position: relative;\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}")); + assert.ok(css.includes(".markdown-body pre > code {\n font-size: 1em;\n white-space: inherit;\n}")); +}); + +test("code block headers can render filenames from fence metadata", () => { + const html = renderSnippet("```yaml .github/workflows/hello.yml\nname: Hello\n```\n\n```markdown title=\"README.md\"\n# Hello\n```"); + + assert.ok(html.includes('.github/workflows/hello.yml')); + assert.ok(html.includes('README.md')); + assert.ok(html.includes('
'));
+});
+
 test("rendered workshop images use GitHub-like rounded corners", () => {
   const { css } = buildDocs();
 
diff --git a/scripts/lib/docs.css b/scripts/lib/docs.css
index 986280ec..faca862c 100644
--- a/scripts/lib/docs.css
+++ b/scripts/lib/docs.css
@@ -605,9 +605,12 @@ html[data-color-mode="dark"] .workshop-nav-btn-secondary:focus-visible {
 /* Code block copy button */
 .markdown-body pre {
   position: relative;
+  white-space: pre-wrap;
+  overflow-wrap: anywhere;
 }
 .markdown-body pre > code {
   font-size: 1em;
+  white-space: inherit;
 }
 .code-copy-btn {
   position: absolute;
@@ -671,6 +674,7 @@ html[data-color-mode="dark"] .code-copy-btn:hover {
   display: flex;
   align-items: center;
   gap: 6px;
+  min-width: 0;
   padding: 8px 14px;
   background-color: #161b22;
   border-bottom: 1px solid #30363d;
@@ -694,6 +698,20 @@ html[data-color-mode="dark"] .code-copy-btn:hover {
   font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
 }
 
+.code-block-filename {
+  min-width: 0;
+  margin-left: auto;
+  overflow: hidden;
+  font-size: 11px;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.terminal-bar .code-block-filename {
+  color: #8b949e;
+  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
+}
+
 .markdown-body .terminal-pre {
   margin: 0;
   padding: 16px;
@@ -736,6 +754,7 @@ html[data-color-mode="dark"] .code-copy-btn:hover {
   display: flex;
   align-items: center;
   gap: 8px;
+  min-width: 0;
   padding: 8px 14px;
   background: linear-gradient(90deg, #271052, #3b1b6f);
   border-bottom: 1px solid #8957e5;
@@ -761,6 +780,10 @@ html[data-color-mode="dark"] .code-copy-btn:hover {
   letter-spacing: 0.02em;
 }
 
+.agent-prompt-bar .code-block-filename {
+  color: #c6a7ff;
+}
+
 .markdown-body .agent-prompt-pre {
   margin: 0;
   padding: 16px 16px 16px 20px;
@@ -976,6 +999,7 @@ html[data-color-mode="dark"] .markdown-body li.task-list-item:not(.is-static).is
   display: flex;
   align-items: center;
   gap: 8px;
+  min-width: 0;
   padding: 8px 14px;
   background-color: var(--bgColor-muted, #f6f8fa);
   border-bottom: 1px solid var(--borderColor-muted, #d0d7de);
@@ -988,6 +1012,10 @@ html[data-color-mode="dark"] .markdown-body li.task-list-item:not(.is-static).is
   line-height: 1;
 }
 
+.markdown-editor-bar .code-block-filename {
+  color: var(--fgColor-muted, #59636e);
+}
+
 .markdown-body .markdown-editor-pre {
   margin: 0;
   padding: 12px 12px 12px 16px;
@@ -1029,6 +1057,9 @@ html[data-color-mode="dark"] .markdown-body li.task-list-item:not(.is-static).is
   html[data-color-mode="auto"] .markdown-editor-icon {
     color: #8b949e;
   }
+  html[data-color-mode="auto"] .markdown-editor-bar .code-block-filename {
+    color: #8b949e;
+  }
   html[data-color-mode="auto"] .markdown-body .markdown-editor-pre {
     background-color: #0d1117;
     box-shadow: inset 3px 0 #30363d;
@@ -1057,6 +1088,9 @@ html[data-color-mode="dark"] .markdown-editor-bar {
 html[data-color-mode="dark"] .markdown-editor-icon {
   color: #8b949e;
 }
+html[data-color-mode="dark"] .markdown-editor-bar .code-block-filename {
+  color: #8b949e;
+}
 html[data-color-mode="dark"] .markdown-body .markdown-editor-pre {
   background-color: #0d1117;
   box-shadow: inset 3px 0 #30363d;
@@ -1086,6 +1120,7 @@ html[data-color-mode="dark"] .markdown-editor-block .code-copy-btn:hover {
   display: flex;
   align-items: center;
   gap: 8px;
+  min-width: 0;
   padding: 8px 14px;
   background-color: var(--bgColor-muted, #f6f8fa);
   border-bottom: 1px solid var(--borderColor-muted, #d0d7de);
@@ -1098,6 +1133,10 @@ html[data-color-mode="dark"] .markdown-editor-block .code-copy-btn:hover {
   line-height: 1;
 }
 
+.yaml-editor-bar .code-block-filename {
+  color: var(--fgColor-muted, #59636e);
+}
+
 .markdown-body .yaml-editor-pre {
   margin: 0;
   padding: 12px;
@@ -1137,6 +1176,9 @@ html[data-color-mode="dark"] .markdown-editor-block .code-copy-btn:hover {
   html[data-color-mode="auto"] .yaml-editor-icon {
     color: #8b949e;
   }
+  html[data-color-mode="auto"] .yaml-editor-bar .code-block-filename {
+    color: #8b949e;
+  }
   html[data-color-mode="auto"] .markdown-body .yaml-editor-pre {
     background-color: #0d1117;
   }
@@ -1164,6 +1206,9 @@ html[data-color-mode="dark"] .yaml-editor-bar {
 html[data-color-mode="dark"] .yaml-editor-icon {
   color: #8b949e;
 }
+html[data-color-mode="dark"] .yaml-editor-bar .code-block-filename {
+  color: #8b949e;
+}
 html[data-color-mode="dark"] .markdown-body .yaml-editor-pre {
   background-color: #0d1117;
 }
diff --git a/scripts/lib/marked-setup.js b/scripts/lib/marked-setup.js
index f400d951..7bfcf292 100644
--- a/scripts/lib/marked-setup.js
+++ b/scripts/lib/marked-setup.js
@@ -19,6 +19,34 @@ const slugger = new GithubSlugger();
 const shellLangs = new Set(['bash', 'sh', 'shell', 'zsh']);
 const terminalOutputLangs = new Set(['console', 'output', 'plaintext', 'text']);
 
+function extractCodeBlockFilename(meta = '') {
+  let normalizedMeta = meta.trim();
+  if (!normalizedMeta) return '';
+
+  if (normalizedMeta.startsWith('{') && normalizedMeta.endsWith('}')) {
+    normalizedMeta = normalizedMeta.slice(1, -1).trim();
+  }
+
+  const keyedFilenameMatch = normalizedMeta.match(
+    /(?:^|\s)(?:file|filename|path|title)=(?:"([^"]+)"|'([^']+)'|(\S+))/
+  );
+  if (keyedFilenameMatch) {
+    return keyedFilenameMatch[1] || keyedFilenameMatch[2] || keyedFilenameMatch[3] || '';
+  }
+
+  return /\s|=/.test(normalizedMeta) ? '' : normalizedMeta;
+}
+
+function parseCodeBlockInfo(lang = '') {
+  const info = lang.trim();
+  const firstTokenMatch = info.match(/^\S*/);
+  const langKey = (firstTokenMatch?.[0] || '').toLowerCase();
+  const meta = info.slice(firstTokenMatch?.[0]?.length || 0).trim();
+  const filename = extractCodeBlockFilename(meta);
+
+  return { langKey, filename };
+}
+
 // Register the heading anchor, task-list item, alert, syntax-highlight, and
 // code-block renderer plugins.  Call this once before processing any markdown.
 function setupBasePlugins() {
@@ -91,22 +119,26 @@ function setupBasePlugins() {
     useNewRenderer: true,
     renderer: {
       code({ text, lang, escaped }) {
-        const langKey = (lang || '').match(/^\S*/)?.[0]?.toLowerCase() ?? '';
+        const { langKey, filename } = parseCodeBlockInfo(lang || '');
         const codeText = text.replace(/\n$/, '') + '\n';
         const codeHtml = escaped ? codeText : escapeHtml(codeText);
+        const filenameHtml = filename
+          ? `${escapeHtml(filename)}`
+          : '';
         if (langKey === 'prompt') {
-          return `
\n
Agent prompt
\n
${codeHtml}
\n
\n`; + return `
\n
Agent prompt${filenameHtml}
\n
${codeHtml}
\n
\n`; } if (langKey === 'markdown' || langKey === 'md') { - return `
\n
\n
${codeHtml}
\n
\n`; + return `
\n
${filenameHtml}
\n
${codeHtml}
\n
\n`; } if (langKey === 'yaml' || langKey === 'yml') { - return `
\n
\n
${codeHtml}
\n
\n`; + return `
\n
${filenameHtml}
\n
${codeHtml}
\n
\n`; } const terminalLabel = shellLangs.has(langKey) ? langKey : terminalOutputLangs.has(langKey) ? 'output' : ''; if (!terminalLabel) return false; const escapedLang = escapeHtml(langKey); - return `
\n\n
${codeHtml}
\n
\n`; + const terminalBarAria = filename ? '' : ' aria-hidden="true"'; + return `
\n
${terminalLabel}${filenameHtml}
\n
${codeHtml}
\n
\n`; }, }, }); diff --git a/workshop/04-github-actions-intro.md b/workshop/04-github-actions-intro.md index 275cfaea..90e50ac6 100644 --- a/workshop/04-github-actions-intro.md +++ b/workshop/04-github-actions-intro.md @@ -39,7 +39,7 @@ A GitHub Actions workflow is a YAML file in `.github/workflows/` that tells GitH Annotated example — each comment names the key term (this is a standard Actions workflow, not an agentic workflow): -```yaml +```yaml .github/workflows/hello-workflow.yml # Standard GitHub Actions workflow — not an agentic workflow name: Hello Workflow @@ -57,7 +57,7 @@ jobs: A **runner** is the machine GitHub provisions for each job — fresh and isolated for every run. -```markdown +```markdown .github/workflows/hello-workflow.md --- runs-on: ubuntu-latest # also: windows-latest, macos-latest --- @@ -84,7 +84,7 @@ The diagram below shows how the five key parts fit together in every workflow fi Before reading on, label each highlighted part of the workflow below with its type: `trigger`, `job`, `runner`, `step`, or `action`. -```yaml +```yaml .github/workflows/hello-workflow.yml on: [push] jobs: test: @@ -137,4 +137,3 @@ Open a real workflow file and find the three core building blocks — no termina **Next:** [What Are Agentic Workflows?](05-agentic-workflows-intro.md) - diff --git a/workshop/07-your-first-workflow.md b/workshop/07-your-first-workflow.md index 0f94368b..ab647d67 100644 --- a/workshop/07-your-first-workflow.md +++ b/workshop/07-your-first-workflow.md @@ -62,7 +62,7 @@ Review the agent's edit, then continue. Prefer this path over hand-editing each **What the agent created** — the generated file should look roughly like this: -```markdown +```markdown .github/workflows/daily-report-status.md --- name: Daily Report Status on: diff --git a/workshop/07d-confirm-model-access.md b/workshop/07d-confirm-model-access.md index 8cb45439..c4178e38 100644 --- a/workshop/07d-confirm-model-access.md +++ b/workshop/07d-confirm-model-access.md @@ -96,7 +96,7 @@ Use this path when the organization that owns the repository has centralized Cop 1. Ask your organization administrator to confirm centralized billing is enabled. 2. Open `daily-report-status.md` and confirm the `permissions:` block includes `copilot-requests: write`: -```markdown +```markdown .github/workflows/daily-report-status.md --- permissions: contents: read diff --git a/workshop/14b-pr-reviewer-workflow.md b/workshop/14b-pr-reviewer-workflow.md index 02d33096..2f55b701 100644 --- a/workshop/14b-pr-reviewer-workflow.md +++ b/workshop/14b-pr-reviewer-workflow.md @@ -48,7 +48,7 @@ Review the agent's diff before accepting it. The source should contain one paren The workflow frontmatter should follow this shape: -```markdown +```markdown .github/workflows/pr-reviewer.md --- on: pull_request: @@ -76,7 +76,7 @@ Notice that the agent job has no repository or pull request write permission. `c Near the bottom, look for the two reusable blocks: -```markdown +```markdown .github/workflows/pr-reviewer.md ## agent: `pr-reviewer` --- description: Reviews one pull request for actionable problems diff --git a/workshop/15-conditional-logic.md b/workshop/15-conditional-logic.md index 365c5a0d..71a1a7d4 100644 --- a/workshop/15-conditional-logic.md +++ b/workshop/15-conditional-logic.md @@ -47,7 +47,7 @@ The skill adds this step to the frontmatter `steps:` block and recompiles the lo Open your daily-status workflow file (e.g., `.github/workflows/daily-status.md`) and add the following block inside the YAML frontmatter under `steps:`: -```markdown +```markdown .github/workflows/daily-status.md --- steps: - name: Count recent commits @@ -64,7 +64,7 @@ After adding it, run `gh aw compile` to regenerate the lock file. Here is the step structure the skill will add: -```markdown +```markdown .github/workflows/daily-status.md --- steps: - name: Count recent commits @@ -89,7 +89,7 @@ This shell command uses `git log` with a `--since` time filter to list only comm In the same frontmatter block, add a top-level `if:` key at the same indentation level as `on:` and `steps:`: -```markdown +```markdown .github/workflows/daily-status.md --- if: steps.recent.outputs.commit_count != '0' --- diff --git a/workshop/16-connect-data-source.md b/workshop/16-connect-data-source.md index a478a210..dabe317f 100644 --- a/workshop/16-connect-data-source.md +++ b/workshop/16-connect-data-source.md @@ -57,7 +57,7 @@ Here is what the first step looks like — the skill will add this for you: First, fetch the recent commit log: -```markdown +```markdown .github/workflows/daily-status.md - name: Fetch recent commits id: recent # step ID — referenced as steps.recent.outputs.… run: | @@ -75,7 +75,7 @@ First, fetch the recent commit log: Next, add a step to fetch open issues: -```markdown +```markdown .github/workflows/daily-status.md - name: Fetch open issues id: issues # step ID — referenced as steps.issues.outputs.… run: | @@ -101,7 +101,7 @@ Next, add a step to fetch open issues: The AI prompt lives in the Markdown body after the frontmatter. Update that section so it uses the step outputs: -```markdown +```markdown .github/workflows/daily-status.md --- # … your existing frontmatter with the two new steps … --- diff --git a/workshop/17-add-mcp-tools.md b/workshop/17-add-mcp-tools.md index a58a27e1..fac81dfa 100644 --- a/workshop/17-add-mcp-tools.md +++ b/workshop/17-add-mcp-tools.md @@ -56,7 +56,7 @@ The skill adds the `tools:` block and updates the brief. Review the diff before Here is the `tools:` block the skill will add: -```markdown +```markdown .github/workflows/daily-status.md --- name: Daily Status Report on: @@ -99,7 +99,7 @@ Open your daily-status workflow file (`.github/workflows/daily-status.md`) and f Below the frontmatter, update the task brief to tell the agent it can use the MCP tools: -```markdown +```markdown .github/workflows/daily-status.md You have access to GitHub tools via MCP. Use them to: 1. Fetch the last 5 commits on the default branch. 2. List all open issues labelled `bug`. diff --git a/workshop/18-share-and-reuse.md b/workshop/18-share-and-reuse.md index a13573c8..b9a0971d 100644 --- a/workshop/18-share-and-reuse.md +++ b/workshop/18-share-and-reuse.md @@ -74,7 +74,7 @@ Your teammate runs this inside their repository. `gh aw add` copies the Markdown Add a short comment at the top of your workflow's Markdown task brief so users know what to customise: -```markdown +```markdown .github/workflows/daily-status.md ``` diff --git a/workshop/19-research-driven-training-node.md b/workshop/19-research-driven-training-node.md index bc218866..69fed6a1 100644 --- a/workshop/19-research-driven-training-node.md +++ b/workshop/19-research-driven-training-node.md @@ -49,7 +49,7 @@ Write a one-paragraph node scope and list the exact [artifacts](https://github.g Add XML comments to preserve reasoning without interrupting learner flow: -```markdown +```markdown workshop/28-safe-outputs-selection.md