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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .github/workflows/daily-multi-device-docs-tester.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .github/workflows/daily-multi-device-docs-tester.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment on lines +83 to +88
- name: Start docs server
env:
EXPR_GITHUB_RUN_ID: ${{ github.run_id }}
Expand Down
1 change: 1 addition & 0 deletions docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Comment on lines 9 to 12
"build": "rm -rf dist && astro build",
Expand Down
41 changes: 41 additions & 0 deletions docs/tests/mobile-responsive.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});
64 changes: 55 additions & 9 deletions scripts/ensure-docs-slide-pdf.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<</Type /Catalog /Pages 2 0 R>>\nendobj\n");

offsets[2] = currentOffset();
write("2 0 obj\n<</Type /Pages /Kids [3 0 R] /Count 1>>\nendobj\n");

offsets[3] = currentOffset();
write("3 0 obj\n<</Type /Page /Parent 2 0 R /MediaBox [0 0 612 792]>>\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<</Size 4 /Root 1 0 R>>\n");
write("startxref\n" + xrefOffset + "\n%%EOF\n");

return Buffer.concat(parts);
}

async function readPdfBytes() {
const bytes = fs.readFileSync(SOURCE_PATH);
if (isPdf(bytes)) {
Expand All @@ -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() {
Expand Down