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 — ]() + ![Finding 1 — missing H1](https://github.com/.../finding-1.png?raw=1) + ![Finding 2 — unnamed checkbox](https://github.com/.../finding-2.png?raw=1) ``` 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; } }