diff --git a/.github/workflows/daily-multi-device-docs-tester.lock.yml b/.github/workflows/daily-multi-device-docs-tester.lock.yml index f04eb38d515..abfddc9b1f6 100644 --- a/.github/workflows/daily-multi-device-docs-tester.lock.yml +++ b/.github/workflows/daily-multi-device-docs-tester.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"89fe6bb3bc7a452f74f731a5016ed268b63e44d322061a7dc884039f64c61dbc","body_hash":"09681b188b196d76d4a274d165483a7a327abca0f95bf4cf5cee6766dd608b27","strict":true,"agent_id":"pi","agent_model":"copilot/gpt-5.4","engine_versions":{"pi":"0.80.2"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"cabc740db4e2725e0e69c1710b0e73d488523b2065ff002b4899cb1491238afc","body_hash":"09681b188b196d76d4a274d165483a7a327abca0f95bf4cf5cee6766dd608b27","strict":true,"agent_id":"pi","agent_model":"copilot/gpt-5.4","engine_versions":{"pi":"0.80.2"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"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":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11","digest":"sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11","digest":"sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.11"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11","digest":"sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.30","digest":"sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be"},{"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.4.0","digest":"sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036","pinned_image":"ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036"}]} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -559,6 +559,12 @@ jobs: run: | cd "$EXPR_GITHUB_WORKSPACE/docs" npm install + - env: + EXPR_GITHUB_WORKSPACE: ${{ github.workspace }} + name: Resolve slide deck PDF + run: | + cd "$EXPR_GITHUB_WORKSPACE/docs" + node ../scripts/ensure-docs-slide-pdf.js - env: EXPR_GITHUB_RUN_ID: ${{ github.run_id }} EXPR_GITHUB_WORKSPACE: ${{ github.workspace }} diff --git a/.github/workflows/daily-multi-device-docs-tester.md b/.github/workflows/daily-multi-device-docs-tester.md index 630576b3b79..7eab244f2ac 100644 --- a/.github/workflows/daily-multi-device-docs-tester.md +++ b/.github/workflows/daily-multi-device-docs-tester.md @@ -80,6 +80,12 @@ pre-agent-steps: run: | cd "$EXPR_GITHUB_WORKSPACE/docs" npm install + - name: Resolve slide deck PDF + env: + EXPR_GITHUB_WORKSPACE: ${{ github.workspace }} + run: | + cd "$EXPR_GITHUB_WORKSPACE/docs" + node ../scripts/ensure-docs-slide-pdf.js - name: Start docs server env: EXPR_GITHUB_RUN_ID: ${{ github.run_id }} diff --git a/docs/package.json b/docs/package.json index 0b190986a72..f34cae02c1d 100644 --- a/docs/package.json +++ b/docs/package.json @@ -7,6 +7,7 @@ }, "scripts": { "dev": "astro dev", + "predev": "npm run build:slides", "start": "astro dev", "prebuild": "npm run generate-agent-factory && npm run generate-model-tables && npm run build:slides", "build": "rm -rf dist && astro build", diff --git a/docs/tests/mobile-responsive.spec.ts b/docs/tests/mobile-responsive.spec.ts index a63ef1121a0..31aab7bdae0 100644 --- a/docs/tests/mobile-responsive.spec.ts +++ b/docs/tests/mobile-responsive.spec.ts @@ -199,4 +199,45 @@ test.describe('Mobile and Responsive Layout', () => { await context.close(); }); + + // Verify mobile navigation toggle: hamburger menu nav links become visible on narrow viewports. + // Addresses the manual verification recommendation from the 2026-06-24 multi-device docs test report. + test('hamburger menu toggles navigation visibility on mobile viewport', async ({ browser }) => { + const context = await browser.newContext({ + viewport: { width: 390, height: 844 }, + javaScriptEnabled: true, + }); + const page = await context.newPage(); + + await page.goto('/gh-aw/introduction/overview/'); + await page.waitForLoadState('networkidle'); + + // The hamburger button must be present and focusable on a narrow mobile viewport. + const hamburgerBtn = page.locator('.hamburger-btn'); + await expect(hamburgerBtn).toBeVisible(); + await expect(hamburgerBtn).toHaveAttribute('aria-expanded', 'false'); + + // The dropdown must be hidden before the button is clicked. + const dropdown = page.locator('.tablet-dropdown'); + await expect(dropdown).toBeHidden(); + + // Click the button; the dropdown must become visible and contain nav links. + await hamburgerBtn.click(); + await expect(hamburgerBtn).toHaveAttribute('aria-expanded', 'true'); + await expect(dropdown).toBeVisible(); + + const navLinks = dropdown.locator('.dropdown-link'); + const linkCount = await navLinks.count(); + expect(linkCount).toBeGreaterThan(0); + for (const link of await navLinks.all()) { + await expect(link).toBeVisible(); + } + + // A second click must close the dropdown. + await hamburgerBtn.click(); + await expect(hamburgerBtn).toHaveAttribute('aria-expanded', 'false'); + await expect(dropdown).toBeHidden(); + + await context.close(); + }); }); diff --git a/scripts/ensure-docs-slide-pdf.js b/scripts/ensure-docs-slide-pdf.js index 7c0d32fa4a8..367bf6014de 100644 --- a/scripts/ensure-docs-slide-pdf.js +++ b/scripts/ensure-docs-slide-pdf.js @@ -49,6 +49,47 @@ function getGitRef() { } } +/** + * Creates a minimal valid single-page PDF placeholder used when the real slide + * deck cannot be fetched (e.g. in sandboxed dev/test environments without LFS + * or media.githubusercontent.com access). The placeholder carries the valid + * PDF header so pdfjs-dist can parse it without throwing InvalidPDFException. + */ +function createPlaceholderPdfBytes() { + const parts = []; + const offsets = []; + + function write(str) { + parts.push(Buffer.from(str, "latin1")); + } + + function currentOffset() { + return parts.reduce((sum, buf) => sum + buf.length, 0); + } + + write("%PDF-1.4\n"); + + offsets[1] = currentOffset(); + write("1 0 obj\n<>\nendobj\n"); + + offsets[2] = currentOffset(); + write("2 0 obj\n<>\nendobj\n"); + + offsets[3] = currentOffset(); + write("3 0 obj\n<>\nendobj\n"); + + const xrefOffset = currentOffset(); + write("xref\n0 4\n"); + write("0000000000 65535 f \n"); + write(offsets[1].toString().padStart(10, "0") + " 00000 n \n"); + write(offsets[2].toString().padStart(10, "0") + " 00000 n \n"); + write(offsets[3].toString().padStart(10, "0") + " 00000 n \n"); + write("trailer\n<>\n"); + write("startxref\n" + xrefOffset + "\n%%EOF\n"); + + return Buffer.concat(parts); +} + async function readPdfBytes() { const bytes = fs.readFileSync(SOURCE_PATH); if (isPdf(bytes)) { @@ -65,17 +106,22 @@ async function readPdfBytes() { console.warn(`Detected Git LFS pointer at ${SOURCE_PATH}; downloading ${url}`); - const response = await fetch(url); - if (!response.ok) { - throw new Error(`Failed to download slide deck PDF: ${response.status} ${response.statusText}`); - } + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Failed to download slide deck PDF: ${response.status} ${response.statusText}`); + } - const downloadedBytes = Buffer.from(await response.arrayBuffer()); - if (!isPdf(downloadedBytes)) { - throw new Error(`Downloaded slide deck from ${url} is not a real PDF.`); - } + const downloadedBytes = Buffer.from(await response.arrayBuffer()); + if (!isPdf(downloadedBytes)) { + throw new Error(`Downloaded slide deck from ${url} is not a real PDF.`); + } - return downloadedBytes; + return downloadedBytes; + } catch (error) { + console.warn(`Warning: Could not download slide deck PDF (${error.message}). Using placeholder PDF.`); + return createPlaceholderPdfBytes(); + } } async function main() {