Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/guidelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
31 changes: 31 additions & 0 deletions scripts/build-docs.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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('<span class="code-block-filename">.github/workflows/hello.yml</span>'));
assert.ok(html.includes('<span class="code-block-filename">README.md</span>'));
assert.ok(html.includes('<pre class="markdown-editor-pre"><code class="hljs language-yaml">'));
});

test("rendered workshop images use GitHub-like rounded corners", () => {
const { css } = buildDocs();

Expand Down
45 changes: 45 additions & 0 deletions scripts/lib/docs.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down
42 changes: 37 additions & 5 deletions scripts/lib/marked-setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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
? `<span class="code-block-filename">${escapeHtml(filename)}</span>`
: '';
if (langKey === 'prompt') {
return `<div class="agent-prompt-block" role="region" aria-label="Agent prompt">\n<div class="agent-prompt-bar"><span class="agent-prompt-icon" aria-hidden="true">✦</span><span class="agent-prompt-label">Agent prompt</span></div>\n<pre class="agent-prompt-pre"><code class="language-prompt">${codeHtml}</code></pre>\n</div>\n`;
return `<div class="agent-prompt-block" role="region" aria-label="Agent prompt">\n<div class="agent-prompt-bar"><span class="agent-prompt-icon" aria-hidden="true">✦</span><span class="agent-prompt-label">Agent prompt</span>${filenameHtml}</div>\n<pre class="agent-prompt-pre"><code class="language-prompt">${codeHtml}</code></pre>\n</div>\n`;
}
if (langKey === 'markdown' || langKey === 'md') {
return `<div class="markdown-editor-block" role="region" aria-label="Markdown">\n<div class="markdown-editor-bar"><span class="markdown-editor-icon" aria-hidden="true">◇</span></div>\n<pre class="markdown-editor-pre"><code class="hljs language-yaml">${codeHtml}</code></pre>\n</div>\n`;
return `<div class="markdown-editor-block" role="region" aria-label="Markdown">\n<div class="markdown-editor-bar"><span class="markdown-editor-icon" aria-hidden="true">◇</span>${filenameHtml}</div>\n<pre class="markdown-editor-pre"><code class="hljs language-yaml">${codeHtml}</code></pre>\n</div>\n`;
}
if (langKey === 'yaml' || langKey === 'yml') {
return `<div class="yaml-editor-block" role="region" aria-label="YAML">\n<div class="yaml-editor-bar"><span class="yaml-editor-icon" aria-hidden="true">≡</span></div>\n<pre class="yaml-editor-pre"><code class="hljs language-yaml">${codeHtml}</code></pre>\n</div>\n`;
return `<div class="yaml-editor-block" role="region" aria-label="YAML">\n<div class="yaml-editor-bar"><span class="yaml-editor-icon" aria-hidden="true">≡</span>${filenameHtml}</div>\n<pre class="yaml-editor-pre"><code class="hljs language-yaml">${codeHtml}</code></pre>\n</div>\n`;
}
const terminalLabel = shellLangs.has(langKey) ? langKey : terminalOutputLangs.has(langKey) ? 'output' : '';
if (!terminalLabel) return false;
const escapedLang = escapeHtml(langKey);
return `<div class="terminal-block">\n<div class="terminal-bar" aria-hidden="true"><span class="terminal-dot"></span><span class="terminal-dot"></span><span class="terminal-dot"></span><span class="terminal-label">${terminalLabel}</span></div>\n<pre class="terminal-pre"><code class="language-${escapedLang}">${codeHtml}</code></pre>\n</div>\n`;
const terminalBarAria = filename ? '' : ' aria-hidden="true"';
return `<div class="terminal-block">\n<div class="terminal-bar"${terminalBarAria}><span class="terminal-dot"></span><span class="terminal-dot"></span><span class="terminal-dot"></span><span class="terminal-label">${terminalLabel}</span>${filenameHtml}</div>\n<pre class="terminal-pre"><code class="language-${escapedLang}">${codeHtml}</code></pre>\n</div>\n`;
},
},
});
Expand Down
7 changes: 3 additions & 4 deletions workshop/04-github-actions-intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
---
Expand All @@ -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:
Expand Down Expand Up @@ -137,4 +137,3 @@ Open a real workflow file and find the three core building blocks — no termina
<!-- journey: all -->
**Next:** [What Are Agentic Workflows?](05-agentic-workflows-intro.md)
<!-- /journey -->

2 changes: 1 addition & 1 deletion workshop/07-your-first-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion workshop/07d-confirm-model-access.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions workshop/14b-pr-reviewer-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions workshop/15-conditional-logic.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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'
---
Expand Down
6 changes: 3 additions & 3 deletions workshop/16-connect-data-source.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand All @@ -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: |
Expand All @@ -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 …
---
Expand Down
Loading
Loading