From 65a3b95769bbbfe0657cf96dbe13f99377b0814f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 21 Jul 2026 07:55:46 +0000
Subject: [PATCH 1/4] Initial plan
From 5560de48d26a1c5c786e19f612d5a0bc1a59239a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 21 Jul 2026 08:00:07 +0000
Subject: [PATCH 2/4] Fix workshop docs accessibility QA handling
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
.../workflows/workshop-playwright-qa.lock.yml | 158 +++++++++++++++---
.github/workflows/workshop-playwright-qa.md | 32 ++--
scripts/build-docs.js | 37 +++-
3 files changed, 188 insertions(+), 39 deletions(-)
diff --git a/.github/workflows/workshop-playwright-qa.lock.yml b/.github/workflows/workshop-playwright-qa.lock.yml
index 99231804..62693d69 100644
--- a/.github/workflows/workshop-playwright-qa.lock.yml
+++ b/.github/workflows/workshop-playwright-qa.lock.yml
@@ -1,4 +1,4 @@
-# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"1dc5bf35ba06eb283084a91122aae015241e5bdaef25d4ace4019671d1d3f2df","body_hash":"86513b24e16f0979ffb328c51172355c0c65fcefee687b48fc55d1db9c66a106","compiler_version":"v0.82.13","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.70"}}
+# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"9b744b20789c192efba651f00712d804447bbcd3dfd71146403c65bbd7edf5db","body_hash":"03df5894a5fb91bc0cd013da6a923ee095a9fe5d548d063e3c25857766229a00","compiler_version":"v0.82.13","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.70"}}
# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"a5d8b7df9d4f9137bedb3d074d0a0b005f1e1996","version":"v0.82.13"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35","digest":"sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35","digest":"sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.35","digest":"sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.35@sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35","digest":"sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.1","digest":"sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]}
# This file was automatically generated by gh-aw (v0.82.13). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md
#
@@ -54,7 +54,7 @@
name: "Workshop Playwright QA"
on:
schedule:
- - cron: "10 7 * * *" # Friendly format: daily (scattered)
+ - cron: "40 19 * * *" # Friendly format: daily (scattered)
workflow_dispatch:
inputs:
aw_context:
@@ -247,21 +247,23 @@ jobs:
run: |
bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh"
{
- cat << 'GH_AW_PROMPT_30eec136205a39fa_EOF'
+ cat << 'GH_AW_PROMPT_7e8d519f6c7e392b_EOF'
- GH_AW_PROMPT_30eec136205a39fa_EOF
+ GH_AW_PROMPT_7e8d519f6c7e392b_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/playwright_prompt.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md"
- cat << 'GH_AW_PROMPT_30eec136205a39fa_EOF'
+ cat << 'GH_AW_PROMPT_7e8d519f6c7e392b_EOF'
- Tools: add_comment(max:5), create_issue(max:10), missing_tool, missing_data, noop
+ Tools: add_comment(max:5), create_issue(max:10), upload_asset(max:30), missing_tool, missing_data, noop
+
+ upload_asset: provide a file path; returns a URL; assets are published after the workflow completes (safeoutputs).
- GH_AW_PROMPT_30eec136205a39fa_EOF
+ GH_AW_PROMPT_7e8d519f6c7e392b_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md"
- cat << 'GH_AW_PROMPT_30eec136205a39fa_EOF'
+ cat << 'GH_AW_PROMPT_7e8d519f6c7e392b_EOF'
The following GitHub context information is available for this workflow:
{{#if github.actor}}
@@ -290,12 +292,12 @@ jobs:
{{/if}}
- GH_AW_PROMPT_30eec136205a39fa_EOF
+ GH_AW_PROMPT_7e8d519f6c7e392b_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/cli_proxy_with_safeoutputs_prompt.md"
- cat << 'GH_AW_PROMPT_30eec136205a39fa_EOF'
+ cat << 'GH_AW_PROMPT_7e8d519f6c7e392b_EOF'
{{#runtime-import .github/workflows/workshop-playwright-qa.md}}
- GH_AW_PROMPT_30eec136205a39fa_EOF
+ GH_AW_PROMPT_7e8d519f6c7e392b_EOF
} > "$GH_AW_PROMPT"
- name: Interpolate variables and render templates
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -386,9 +388,9 @@ jobs:
queue: max
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
- GH_AW_ASSETS_ALLOWED_EXTS: ""
- GH_AW_ASSETS_BRANCH: ""
- GH_AW_ASSETS_MAX_SIZE_KB: 0
+ GH_AW_ASSETS_ALLOWED_EXTS: ".png"
+ GH_AW_ASSETS_BRANCH: "assets/${{ github.workflow }}"
+ GH_AW_ASSETS_MAX_SIZE_KB: 10240
GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
GH_AW_WORKFLOW_ID_SANITIZED: workshopplaywrightqa
@@ -451,7 +453,7 @@ jobs:
name: activation
path: /tmp/gh-aw
- name: Build workshop docs and start static server
- run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/data /tmp/gh-aw/screenshots\n\n# Install dependencies (same set as deploy-pages.yml)\nnpm install --no-save marked github-slugger marked-alert \\\n @primer/css \"@fontsource-variable/mona-sans\"\n\n# Build the static workshop site to dist/\nnode scripts/build-docs.js\n\n# Start a static HTTP server on port 4000\nnpx --yes http-server dist -p 4000 --silent &\necho $! > /tmp/gh-aw/data/server.pid\n\n# Wait until the server responds (up to 20 s)\ntimeout 20 bash -c \\\n 'until curl -sf http://127.0.0.1:4000/ > /dev/null 2>&1; do sleep 0.5; done'\necho \"Server ready at http://127.0.0.1:4000/\"\n\n# Enumerate pages via hash anchors found in the built HTML\n# Each workshop step is a element — collect their IDs\npython3 - <<'PYEOF'\nimport re, json, pathlib\n\nhtml = pathlib.Path(\"dist/index.html\").read_text(encoding=\"utf-8\")\n# Match top-level elements which correspond to workshop steps\nids = re.findall(r']+\\bid=[\"\\']([^\"\\']+)[\"\\']', html)\n\nbase = \"http://127.0.0.1:4000/\"\npages = [{\"id\": pid, \"url\": f\"{base}#{pid}\"} for pid in ids]\n# Always include the root (no hash) as the first entry\npages.insert(0, {\"id\": \"__root__\", \"url\": base})\n\nout = {\n \"base_url\": base,\n \"pages\": pages,\n \"viewports\": [\n {\"name\": \"mobile\", \"width\": 375, \"height\": 667},\n {\"name\": \"tablet\", \"width\": 768, \"height\": 1024},\n {\"name\": \"desktop\", \"width\": 1280, \"height\": 800},\n ],\n}\npathlib.Path(\"/tmp/gh-aw/data/config.json\").write_text(\n json.dumps(out, indent=2), encoding=\"utf-8\"\n)\nprint(f\"Discovered {len(pages)} pages\")\nPYEOF\n\necho \"=== Config ===\" && cat /tmp/gh-aw/data/config.json"
+ run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/agent/data /tmp/gh-aw/agent/screenshots\n\n# Install dependencies (same set as deploy-pages.yml)\nnpm install --no-save marked github-slugger marked-alert \\\n @primer/css \"@fontsource-variable/mona-sans\"\n\n# Build the static workshop site to dist/\nnode scripts/build-docs.js\n\n# Start a static HTTP server on port 4000\nnpx --yes http-server dist -p 4000 --silent &\necho $! > /tmp/gh-aw/agent/data/server.pid\n\n# Wait until the server responds (up to 20 s)\ntimeout 20 bash -c \\\n 'until curl -sf http://127.0.0.1:4000/ > /dev/null 2>&1; do sleep 0.5; done'\necho \"Server ready at http://127.0.0.1:4000/\"\n\n# Enumerate pages via hash anchors found in the built HTML\n# Each workshop step is a element — collect their IDs\npython3 - <<'PYEOF'\nimport re, json, pathlib\n\nhtml = pathlib.Path(\"dist/index.html\").read_text(encoding=\"utf-8\")\n# Match top-level elements which correspond to workshop steps\nids = re.findall(r']+\\bid=[\"\\']([^\"\\']+)[\"\\']', html)\n\nbase = \"http://127.0.0.1:4000/\"\npages = [{\"id\": pid, \"url\": f\"{base}#{pid}\"} for pid in ids]\n# Always include the root (no hash) as the first entry\npages.insert(0, {\"id\": \"__root__\", \"url\": base})\n\nout = {\n \"base_url\": base,\n \"pages\": pages,\n \"viewports\": [\n {\"name\": \"mobile\", \"width\": 375, \"height\": 667},\n {\"name\": \"tablet\", \"width\": 768, \"height\": 1024},\n {\"name\": \"desktop\", \"width\": 1280, \"height\": 800},\n ],\n}\npathlib.Path(\"/tmp/gh-aw/agent/data/config.json\").write_text(\n json.dumps(out, indent=2), encoding=\"utf-8\"\n)\nprint(f\"Discovered {len(pages)} pages\")\nPYEOF\n\necho \"=== Config ===\" && cat /tmp/gh-aw/agent/data/config.json"
- name: Configure Git credentials
env:
@@ -517,20 +519,24 @@ jobs:
- name: Download container images
run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04 ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.35@sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5 ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3 ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3
- name: Generate Safe Outputs Config
+ env:
+ GITHUB_WORKFLOW: ${{ github.workflow }}
run: |
mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs"
mkdir -p /tmp/gh-aw/safeoutputs
mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
- cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_ec1bdfdf65052bbd_EOF'
- {"add_comment":{"max":5},"create_issue":{"deduplicate_by_title":true,"expires":24,"labels":["bug","accessibility"],"max":10,"title_prefix":"[workshop-playwright-qa] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}}
- GH_AW_SAFE_OUTPUTS_CONFIG_ec1bdfdf65052bbd_EOF
+ mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs/assets"
+ cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_e173516f7d8c408c_EOF'
+ {"add_comment":{"max":5},"create_issue":{"deduplicate_by_title":true,"expires":24,"labels":["bug","accessibility"],"max":10,"title_prefix":"[workshop-playwright-qa] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"upload_asset":{"allowed-exts":[".png"],"branch":"assets/${GITHUB_WORKFLOW}","max":30,"max-size":10240}}
+ GH_AW_SAFE_OUTPUTS_CONFIG_e173516f7d8c408c_EOF
- name: Generate Safe Outputs Tools
env:
GH_AW_TOOLS_META_JSON: |
{
"description_suffixes": {
"add_comment": " CONSTRAINTS: Maximum 5 comment(s) can be added. Supports reply_to_id for discussion threading.",
- "create_issue": " CONSTRAINTS: Maximum 10 issue(s) can be created. Title will be prefixed with \"[workshop-playwright-qa] \". Labels [\"bug\" \"accessibility\"] will be automatically added."
+ "create_issue": " CONSTRAINTS: Maximum 10 issue(s) can be created. Title will be prefixed with \"[workshop-playwright-qa] \". Labels [\"bug\" \"accessibility\"] will be automatically added.",
+ "upload_asset": " CONSTRAINTS: Maximum 30 asset(s) can be uploaded. Maximum file size: 10240KB. Allowed file extensions: [.png]."
},
"repo_params": {},
"dynamic_tools": []
@@ -668,6 +674,15 @@ jobs:
"maxLength": 1024
}
}
+ },
+ "upload_asset": {
+ "defaultMax": 10,
+ "fields": {
+ "path": {
+ "required": true,
+ "type": "string"
+ }
+ }
}
}
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -680,6 +695,9 @@ jobs:
- name: Start MCP Gateway
id: start-mcp-gateway
env:
+ GH_AW_ASSETS_ALLOWED_EXTS: ${{ env.GH_AW_ASSETS_ALLOWED_EXTS }}
+ GH_AW_ASSETS_BRANCH: ${{ env.GH_AW_ASSETS_BRANCH }}
+ GH_AW_ASSETS_MAX_SIZE_KB: ${{ env.GH_AW_ASSETS_MAX_SIZE_KB }}
GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST: ${{ vars.GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST || 'true' }}
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }}
@@ -834,6 +852,9 @@ jobs:
COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode
COPILOT_GITHUB_TOKEN: ${{ github.token }}
COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }}
+ GH_AW_ASSETS_ALLOWED_EXTS: ".png"
+ GH_AW_ASSETS_BRANCH: "assets/${{ github.workflow }}"
+ GH_AW_ASSETS_MAX_SIZE_KB: 10240
GH_AW_LLM_PROVIDER: github
GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }}
GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }}
@@ -980,6 +1001,15 @@ jobs:
if [ ! -f /tmp/gh-aw/agent_output.json ]; then
echo '{"items":[]}' > /tmp/gh-aw/agent_output.json
fi
+ # Upload safe-outputs assets for upload_assets job
+ - name: Upload Safe Outputs Assets
+ if: always()
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: safe-outputs-assets
+ path: ${{ runner.temp }}/gh-aw/safeoutputs/assets/
+ retention-days: 1
+ if-no-files-found: ignore
- name: Upload agent artifacts
if: always()
continue-on-error: true
@@ -1012,6 +1042,7 @@ jobs:
- agent
- detection
- safe_outputs
+ - upload_assets
if: >
always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' ||
needs.activation.outputs.oauth_token_check_failed == 'true' || needs.activation.outputs.stale_lock_file_failed == 'true' ||
@@ -1600,7 +1631,7 @@ jobs:
GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,127.0.0.1,::1,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,cdn.playwright.dev,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,docs.github.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,localhost,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,patchdiff.githubusercontent.com,playwright.download.prss.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
GITHUB_SERVER_URL: ${{ github.server_url }}
GITHUB_API_URL: ${{ github.api_url }}
- GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":5},\"create_issue\":{\"deduplicate_by_title\":true,\"expires\":24,\"labels\":[\"bug\",\"accessibility\"],\"max\":10,\"title_prefix\":\"[workshop-playwright-qa] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}"
+ GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":5},\"create_issue\":{\"deduplicate_by_title\":true,\"expires\":24,\"labels\":[\"bug\",\"accessibility\"],\"max\":10,\"title_prefix\":\"[workshop-playwright-qa] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{},\"upload_asset\":{\"allowed-exts\":[\".png\"],\"branch\":\"assets/${{ github.workflow }}\",\"max\":30,\"max-size\":10240}}"
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
@@ -1617,3 +1648,90 @@ jobs:
/tmp/gh-aw/safe-output-items.jsonl
/tmp/gh-aw/temporary-id-map.json
if-no-files-found: ignore
+
+ upload_assets:
+ needs:
+ - activation
+ - agent
+ if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'upload_asset')
+ runs-on: ubuntu-slim
+ permissions:
+ contents: write
+ timeout-minutes: 10
+ env:
+ GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
+ outputs:
+ branch_name: ${{ steps.upload_assets.outputs.branch_name }}
+ published_count: ${{ steps.upload_assets.outputs.published_count }}
+ steps:
+ - name: Setup Scripts
+ id: setup
+ uses: github/gh-aw-actions/setup@a5d8b7df9d4f9137bedb3d074d0a0b005f1e1996 # v0.82.13
+ with:
+ destination: ${{ runner.temp }}/gh-aw/actions
+ job-name: ${{ github.job }}
+ trace-id: ${{ needs.activation.outputs.setup-trace-id }}
+ parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }}
+ env:
+ GH_AW_SETUP_WORKFLOW_NAME: "Workshop Playwright QA"
+ GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/workshop-playwright-qa.lock.yml@${{ github.ref }}
+ GH_AW_INFO_VERSION: "1.0.70"
+ GH_AW_INFO_AWF_VERSION: "v0.27.35"
+ GH_AW_INFO_ENGINE_ID: "copilot"
+ - name: Checkout repository
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ with:
+ persist-credentials: false
+ fetch-depth: 0
+ - name: Configure Git credentials
+ env:
+ GITHUB_REPOSITORY: ${{ github.repository }}
+ GITHUB_SERVER_URL: ${{ github.server_url }}
+ GITHUB_TOKEN: ${{ github.token }}
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh"
+ - name: Download assets
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: safe-outputs-assets
+ path: /tmp/gh-aw/safeoutputs/assets/
+ - name: List downloaded asset files
+ continue-on-error: true
+ run: |
+ echo "Downloaded asset files:"
+ find /tmp/gh-aw/safeoutputs/assets/ -maxdepth 1 -ls
+ - name: Download agent output artifact
+ id: download-agent-output
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: agent
+ path: /tmp/gh-aw/
+ - name: Setup agent output environment variable
+ id: setup-agent-output-env
+ if: steps.download-agent-output.outcome == 'success'
+ run: |
+ mkdir -p /tmp/gh-aw/
+ find "/tmp/gh-aw/" -type f -print
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ - name: Push assets
+ id: upload_assets
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
+ GH_AW_ASSETS_DIR: "/tmp/gh-aw/safeoutputs/assets"
+ GH_AW_ASSETS_BRANCH: "assets/${{ github.workflow }}"
+ GH_AW_ASSETS_MAX_SIZE_KB: 10240
+ GH_AW_ASSETS_ALLOWED_EXTS: ".png"
+ GH_AW_WORKFLOW_NAME: "Workshop Playwright QA"
+ GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/workshop-playwright-qa.md"
+ GH_AW_ENGINE_ID: "copilot"
+ GH_AW_ENGINE_VERSION: "1.0.70"
+ GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }}
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io, getOctokit);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/upload_assets.cjs');
+ await main();
diff --git a/.github/workflows/workshop-playwright-qa.md b/.github/workflows/workshop-playwright-qa.md
index 8f7d035b..2587fb96 100644
--- a/.github/workflows/workshop-playwright-qa.md
+++ b/.github/workflows/workshop-playwright-qa.md
@@ -38,12 +38,15 @@ safe-outputs:
expires: 1d
add-comment:
max: 5
+ upload-asset:
+ allowed-exts: [.png]
+ max: 30
timeout-minutes: 30
steps:
- name: Build workshop docs and start static server
run: |
set -euo pipefail
- mkdir -p /tmp/gh-aw/data /tmp/gh-aw/screenshots
+ mkdir -p /tmp/gh-aw/agent/data /tmp/gh-aw/agent/screenshots
# Install dependencies (same set as deploy-pages.yml)
npm install --no-save marked github-slugger marked-alert \
@@ -54,7 +57,7 @@ steps:
# Start a static HTTP server on port 4000
npx --yes http-server dist -p 4000 --silent &
- echo $! > /tmp/gh-aw/data/server.pid
+ echo $! > /tmp/gh-aw/agent/data/server.pid
# Wait until the server responds (up to 20 s)
timeout 20 bash -c \
@@ -84,13 +87,13 @@ steps:
{"name": "desktop", "width": 1280, "height": 800},
],
}
- pathlib.Path("/tmp/gh-aw/data/config.json").write_text(
+ pathlib.Path("/tmp/gh-aw/agent/data/config.json").write_text(
json.dumps(out, indent=2), encoding="utf-8"
)
print(f"Discovered {len(pages)} pages")
PYEOF
- echo "=== Config ===" && cat /tmp/gh-aw/data/config.json
+ echo "=== Config ===" && cat /tmp/gh-aw/agent/data/config.json
---
# Workshop Playwright QA
@@ -105,7 +108,7 @@ screenshots.
## Load Inputs
-1. Read `/tmp/gh-aw/data/config.json`. It contains:
+1. Read `/tmp/gh-aw/agent/data/config.json`. It contains:
- `base_url` — the local server URL (`http://127.0.0.1:4000/`)
- `pages` — array of `{id, url}` objects, one per discovered workshop step
plus a root entry
@@ -176,12 +179,17 @@ For every failed check, record a finding with:
- `description` — a concise description of what failed and what was
observed (include element selectors or text snippets where useful)
- `screenshot_path` — path where a screenshot of the failure was saved
- (use `/tmp/gh-aw/screenshots/--.png`)
+ (use `/tmp/gh-aw/agent/screenshots/--.png`)
+- `screenshot_asset_url` — GitHub asset URL returned by `upload_asset`
+ after publishing the screenshot
For each finding, take a targeted screenshot:
- Use Playwright to capture a screenshot of the offending element when
possible, or a full-page screenshot if the element is not isolatable.
- Save the screenshot to the `screenshot_path` recorded in the finding.
+- Immediately call the `upload_asset` safe-output tool with the absolute
+ `screenshot_path`, then store the returned asset URL in
+ `screenshot_asset_url`.
If the same visual defect appears on all three viewports, record one
finding with `viewport: "all"` and a single representative screenshot
@@ -227,10 +235,8 @@ Group findings by `page_id`. For each page that has findings:
### Screenshots
-
+ ![Finding 1 — ]()
+ ![Finding 2 — ]()
```
4. Call `create-issue` (or `add-comment` if the issue already exists)
@@ -248,5 +254,7 @@ Group findings by `page_id`. For each page that has findings:
a description specific enough for a human to reproduce it.
- Screenshots must be taken before moving to the next finding so the
browser state is captured at the moment of failure.
-- Never call write tools other than `create-issue`, `add-comment`, and
- `noop`.
+- Every issue or comment with findings must embed the uploaded screenshot
+ assets in Markdown, not local `/tmp/...` paths.
+- Never call write tools other than `create-issue`, `add-comment`,
+ `upload_asset`, and `noop`.
diff --git a/scripts/build-docs.js b/scripts/build-docs.js
index 6ca92592..065e0667 100644
--- a/scripts/build-docs.js
+++ b/scripts/build-docs.js
@@ -7,6 +7,24 @@ const { marked } = require('marked');
const { default: GithubSlugger } = require('github-slugger');
const markedAlert = require('marked-alert');
+function flattenTokenText(tokenOrTokens) {
+ if (Array.isArray(tokenOrTokens)) {
+ return tokenOrTokens.map(flattenTokenText).join('');
+ }
+ if (!tokenOrTokens) return '';
+ return tokenOrTokens.tokens
+ ? flattenTokenText(tokenOrTokens.tokens)
+ : (tokenOrTokens.text || tokenOrTokens.raw || '');
+}
+
+function escapeHtmlAttribute(value) {
+ return String(value)
+ .replaceAll('&', '&')
+ .replaceAll('"', '"')
+ .replaceAll('<', '<')
+ .replaceAll('>', '>');
+}
+
// Plugin: clickable heading anchors with GitHub-compatible IDs
const slugger = new GithubSlugger();
marked.use({
@@ -19,9 +37,7 @@ marked.use({
heading({ tokens, depth }) {
const text = this.parser.parseInline(tokens);
// Extract plain text from tokens (avoids regex-based HTML stripping)
- const raw = tokens.map(function flattenTokenText(t) {
- return t.tokens ? t.tokens.map(flattenTokenText).join('') : (t.text || t.raw || '');
- }).join('').trim();
+ const raw = flattenTokenText(tokens).trim();
// slugger.slug() always returns a URL-safe [a-z0-9-] string, safe for attribute interpolation
const id = slugger.slug(raw);
// text is the HTML output of parseInline(), which escapes user content
@@ -37,9 +53,13 @@ marked.use({
? item.tokens[0].tokens.slice(1)
: item.tokens.slice(1);
const text = this.parser.parseInline(inlineTokens);
+ const accessibleName = flattenTokenText(inlineTokens).replace(/\s+/g, ' ').trim();
const baseAttrs = 'class="task-list-item-checkbox" disabled="" type="checkbox"';
const attrs = item.checked ? `${baseAttrs} checked=""` : baseAttrs;
- return ` ${text}\n`;
+ const labelAttr = accessibleName
+ ? ` aria-label="${escapeHtmlAttribute(accessibleName)}"`
+ : '';
+ return ` ${text}\n`;
}
return false; // use default rendering for non-task items
},
@@ -150,12 +170,14 @@ const htmlContent = files.map((f, index) => {
// regex safely finds the first heading regardless of leading comment lines.
const headingMatch = markdown.match(headingRegex);
const title = pageTitleByFile.get(f);
+ const titleHtml = marked.parseInline(title);
const sectionId = sectionIdsByFile.get(f);
+ const sectionTitleId = `${sectionId}-title`;
// Intentionally remove only the first heading because it is promoted to .
const markdownWithoutTitle = headingMatch ? markdown.replace(headingRegex, '').trimStart() : markdown;
const content = marked(renderWorkshopNavigation(markdownWithoutTitle, f));
const detailsOpenAttr = index === 0 ? ' open' : '';
- return `\n${title}
\n${content}\n `;
+ return `\n${titleHtml}
\n${content}\n `;
}).join('\n\n');
const menuGroups = [
@@ -418,7 +440,8 @@ body,
cursor: default;
list-style: none;
}
-.markdown-body > details > summary > h4 {
+.markdown-body > details > summary > .workshop-page-title {
+ margin: 0;
font-size: 32px;
line-height: 1.25;
}
@@ -427,7 +450,7 @@ body,
}
@media (max-width: 543px) {
- .markdown-body > details > summary > h4 {
+ .markdown-body > details > summary > .workshop-page-title {
font-size: 28px;
}
}
From d9f487d54430a56955adf449846c12eba7635d87 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 21 Jul 2026 08:02:31 +0000
Subject: [PATCH 3/4] Refine workshop accessibility and QA updates
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
.../workflows/workshop-playwright-qa.lock.yml | 4 ++--
.github/workflows/workshop-playwright-qa.md | 16 ++++++++--------
scripts/build-docs.js | 11 ++++++-----
3 files changed, 16 insertions(+), 15 deletions(-)
diff --git a/.github/workflows/workshop-playwright-qa.lock.yml b/.github/workflows/workshop-playwright-qa.lock.yml
index 62693d69..c453bd21 100644
--- a/.github/workflows/workshop-playwright-qa.lock.yml
+++ b/.github/workflows/workshop-playwright-qa.lock.yml
@@ -1,4 +1,4 @@
-# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"9b744b20789c192efba651f00712d804447bbcd3dfd71146403c65bbd7edf5db","body_hash":"03df5894a5fb91bc0cd013da6a923ee095a9fe5d548d063e3c25857766229a00","compiler_version":"v0.82.13","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.70"}}
+# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"86657a0e9726f2ea203023f0fe9526768f2259602ac48236b19f641b3324e37e","body_hash":"0da169cf49f1146846984102711c0fc5f82b01e8e7c003d1edeb4e0e892ae187","compiler_version":"v0.82.13","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.70"}}
# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"a5d8b7df9d4f9137bedb3d074d0a0b005f1e1996","version":"v0.82.13"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35","digest":"sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35","digest":"sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.35","digest":"sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.35@sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35","digest":"sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.1","digest":"sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]}
# This file was automatically generated by gh-aw (v0.82.13). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md
#
@@ -453,7 +453,7 @@ jobs:
name: activation
path: /tmp/gh-aw
- name: Build workshop docs and start static server
- run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/agent/data /tmp/gh-aw/agent/screenshots\n\n# Install dependencies (same set as deploy-pages.yml)\nnpm install --no-save marked github-slugger marked-alert \\\n @primer/css \"@fontsource-variable/mona-sans\"\n\n# Build the static workshop site to dist/\nnode scripts/build-docs.js\n\n# Start a static HTTP server on port 4000\nnpx --yes http-server dist -p 4000 --silent &\necho $! > /tmp/gh-aw/agent/data/server.pid\n\n# Wait until the server responds (up to 20 s)\ntimeout 20 bash -c \\\n 'until curl -sf http://127.0.0.1:4000/ > /dev/null 2>&1; do sleep 0.5; done'\necho \"Server ready at http://127.0.0.1:4000/\"\n\n# Enumerate pages via hash anchors found in the built HTML\n# Each workshop step is a element — collect their IDs\npython3 - <<'PYEOF'\nimport re, json, pathlib\n\nhtml = pathlib.Path(\"dist/index.html\").read_text(encoding=\"utf-8\")\n# Match top-level elements which correspond to workshop steps\nids = re.findall(r']+\\bid=[\"\\']([^\"\\']+)[\"\\']', html)\n\nbase = \"http://127.0.0.1:4000/\"\npages = [{\"id\": pid, \"url\": f\"{base}#{pid}\"} for pid in ids]\n# Always include the root (no hash) as the first entry\npages.insert(0, {\"id\": \"__root__\", \"url\": base})\n\nout = {\n \"base_url\": base,\n \"pages\": pages,\n \"viewports\": [\n {\"name\": \"mobile\", \"width\": 375, \"height\": 667},\n {\"name\": \"tablet\", \"width\": 768, \"height\": 1024},\n {\"name\": \"desktop\", \"width\": 1280, \"height\": 800},\n ],\n}\npathlib.Path(\"/tmp/gh-aw/agent/data/config.json\").write_text(\n json.dumps(out, indent=2), encoding=\"utf-8\"\n)\nprint(f\"Discovered {len(pages)} pages\")\nPYEOF\n\necho \"=== Config ===\" && cat /tmp/gh-aw/agent/data/config.json"
+ run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/data /tmp/gh-aw/screenshots\n\n# Install dependencies (same set as deploy-pages.yml)\nnpm install --no-save marked github-slugger marked-alert \\\n @primer/css \"@fontsource-variable/mona-sans\"\n\n# Build the static workshop site to dist/\nnode scripts/build-docs.js\n\n# Start a static HTTP server on port 4000\nnpx --yes http-server dist -p 4000 --silent &\necho $! > /tmp/gh-aw/data/server.pid\n\n# Wait until the server responds (up to 20 s)\ntimeout 20 bash -c \\\n 'until curl -sf http://127.0.0.1:4000/ > /dev/null 2>&1; do sleep 0.5; done'\necho \"Server ready at http://127.0.0.1:4000/\"\n\n# Enumerate pages via hash anchors found in the built HTML\n# Each workshop step is a element — collect their IDs\npython3 - <<'PYEOF'\nimport re, json, pathlib\n\nhtml = pathlib.Path(\"dist/index.html\").read_text(encoding=\"utf-8\")\n# Match top-level elements which correspond to workshop steps\nids = re.findall(r']+\\bid=[\"\\']([^\"\\']+)[\"\\']', html)\n\nbase = \"http://127.0.0.1:4000/\"\npages = [{\"id\": pid, \"url\": f\"{base}#{pid}\"} for pid in ids]\n# Always include the root (no hash) as the first entry\npages.insert(0, {\"id\": \"__root__\", \"url\": base})\n\nout = {\n \"base_url\": base,\n \"pages\": pages,\n \"viewports\": [\n {\"name\": \"mobile\", \"width\": 375, \"height\": 667},\n {\"name\": \"tablet\", \"width\": 768, \"height\": 1024},\n {\"name\": \"desktop\", \"width\": 1280, \"height\": 800},\n ],\n}\npathlib.Path(\"/tmp/gh-aw/data/config.json\").write_text(\n json.dumps(out, indent=2), encoding=\"utf-8\"\n)\nprint(f\"Discovered {len(pages)} pages\")\nPYEOF\n\necho \"=== Config ===\" && cat /tmp/gh-aw/data/config.json"
- name: Configure Git credentials
env:
diff --git a/.github/workflows/workshop-playwright-qa.md b/.github/workflows/workshop-playwright-qa.md
index 2587fb96..cd2d8160 100644
--- a/.github/workflows/workshop-playwright-qa.md
+++ b/.github/workflows/workshop-playwright-qa.md
@@ -46,7 +46,7 @@ steps:
- name: Build workshop docs and start static server
run: |
set -euo pipefail
- mkdir -p /tmp/gh-aw/agent/data /tmp/gh-aw/agent/screenshots
+ mkdir -p /tmp/gh-aw/data /tmp/gh-aw/screenshots
# Install dependencies (same set as deploy-pages.yml)
npm install --no-save marked github-slugger marked-alert \
@@ -57,7 +57,7 @@ steps:
# Start a static HTTP server on port 4000
npx --yes http-server dist -p 4000 --silent &
- echo $! > /tmp/gh-aw/agent/data/server.pid
+ echo $! > /tmp/gh-aw/data/server.pid
# Wait until the server responds (up to 20 s)
timeout 20 bash -c \
@@ -87,13 +87,13 @@ steps:
{"name": "desktop", "width": 1280, "height": 800},
],
}
- pathlib.Path("/tmp/gh-aw/agent/data/config.json").write_text(
+ pathlib.Path("/tmp/gh-aw/data/config.json").write_text(
json.dumps(out, indent=2), encoding="utf-8"
)
print(f"Discovered {len(pages)} pages")
PYEOF
- echo "=== Config ===" && cat /tmp/gh-aw/agent/data/config.json
+ echo "=== Config ===" && cat /tmp/gh-aw/data/config.json
---
# Workshop Playwright QA
@@ -108,7 +108,7 @@ screenshots.
## Load Inputs
-1. Read `/tmp/gh-aw/agent/data/config.json`. It contains:
+1. Read `/tmp/gh-aw/data/config.json`. It contains:
- `base_url` — the local server URL (`http://127.0.0.1:4000/`)
- `pages` — array of `{id, url}` objects, one per discovered workshop step
plus a root entry
@@ -179,7 +179,7 @@ For every failed check, record a finding with:
- `description` — a concise description of what failed and what was
observed (include element selectors or text snippets where useful)
- `screenshot_path` — path where a screenshot of the failure was saved
- (use `/tmp/gh-aw/agent/screenshots/--.png`)
+ (use `/tmp/gh-aw/screenshots/--.png`)
- `screenshot_asset_url` — GitHub asset URL returned by `upload_asset`
after publishing the screenshot
@@ -235,8 +235,8 @@ Group findings by `page_id`. For each page that has findings:
### Screenshots
- ![Finding 1 — ]()
- ![Finding 2 — ]()
+ 
+ 
```
4. Call `create-issue` (or `add-comment` if the issue already exists)
diff --git a/scripts/build-docs.js b/scripts/build-docs.js
index 065e0667..1b542cac 100644
--- a/scripts/build-docs.js
+++ b/scripts/build-docs.js
@@ -17,12 +17,13 @@ function flattenTokenText(tokenOrTokens) {
: (tokenOrTokens.text || tokenOrTokens.raw || '');
}
-function escapeHtmlAttribute(value) {
+function escapeHtml(value) {
return String(value)
.replaceAll('&', '&')
.replaceAll('"', '"')
.replaceAll('<', '<')
- .replaceAll('>', '>');
+ .replaceAll('>', '>')
+ .replaceAll('\'', ''');
}
// Plugin: clickable heading anchors with GitHub-compatible IDs
@@ -57,7 +58,7 @@ marked.use({
const baseAttrs = 'class="task-list-item-checkbox" disabled="" type="checkbox"';
const attrs = item.checked ? `${baseAttrs} checked=""` : baseAttrs;
const labelAttr = accessibleName
- ? ` aria-label="${escapeHtmlAttribute(accessibleName)}"`
+ ? ` aria-label="${escapeHtml(accessibleName)}"`
: '';
return ` ${text}\n`;
}
@@ -170,14 +171,14 @@ const htmlContent = files.map((f, index) => {
// regex safely finds the first heading regardless of leading comment lines.
const headingMatch = markdown.match(headingRegex);
const title = pageTitleByFile.get(f);
- const titleHtml = marked.parseInline(title);
+ const titleHtml = escapeHtml(title);
const sectionId = sectionIdsByFile.get(f);
const sectionTitleId = `${sectionId}-title`;
// Intentionally remove only the first heading because it is promoted to .
const markdownWithoutTitle = headingMatch ? markdown.replace(headingRegex, '').trimStart() : markdown;
const content = marked(renderWorkshopNavigation(markdownWithoutTitle, f));
const detailsOpenAttr = index === 0 ? ' open' : '';
- return `\n${titleHtml}
\n${content}\n `;
+ return `\n${titleHtml}
\n${content}\n `;
}).join('\n\n');
const menuGroups = [
From ca627e7d5f36df1101a4681fffd00aae399e3d35 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 21 Jul 2026 08:04:59 +0000
Subject: [PATCH 4/4] Adjust workshop page title semantics
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
scripts/build-docs.js | 12 ++++--------
1 file changed, 4 insertions(+), 8 deletions(-)
diff --git a/scripts/build-docs.js b/scripts/build-docs.js
index 1b542cac..abc3e505 100644
--- a/scripts/build-docs.js
+++ b/scripts/build-docs.js
@@ -178,7 +178,7 @@ const htmlContent = files.map((f, index) => {
const markdownWithoutTitle = headingMatch ? markdown.replace(headingRegex, '').trimStart() : markdown;
const content = marked(renderWorkshopNavigation(markdownWithoutTitle, f));
const detailsOpenAttr = index === 0 ? ' open' : '';
- return `\n${titleHtml}
\n${content}\n `;
+ return `\n${titleHtml}
\n${titleHtml}
\n${content}\n `;
}).join('\n\n');
const menuGroups = [
@@ -438,20 +438,16 @@ body,
scroll-margin-top: 72px;
}
.markdown-body > details > summary {
- cursor: default;
- list-style: none;
+ display: none;
}
-.markdown-body > details > summary > .workshop-page-title {
+.markdown-body > details > .workshop-page-title {
margin: 0;
font-size: 32px;
line-height: 1.25;
}
-.markdown-body > details > summary::-webkit-details-marker {
- display: none;
-}
@media (max-width: 543px) {
- .markdown-body > details > summary > .workshop-page-title {
+ .markdown-body > details > .workshop-page-title {
font-size: 28px;
}
}