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
4 changes: 3 additions & 1 deletion docs/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { unified } from '@astrojs/markdown-remark';
import remarkStripEmojis from './src/lib/remark/stripEmojis.js';
import remarkTableDataLabels from './src/lib/remark/tableDataLabels.js';
import rehypeTableWrapper from './src/lib/rehype/tableWrapper.js';
import { WORKSHOP_SLUGS } from './src/lib/workshop/config.ts';

/**
* Creates blog authors config with GitHub profile pictures
Expand Down Expand Up @@ -295,7 +296,8 @@ export default defineConfig({
errorOnLocalLinks: true,
exclude: ({ file, link }) =>
file.includes('/src/generated/workshop-markdown/')
&& link.startsWith('/gh-aw/workshop/?__gh_aw_workshop_local__='),
&& link.includes('?__gh_aw_workshop_local__=')
&& WORKSHOP_SLUGS.some((slug) => link.startsWith(`/gh-aw/workshops/${slug}/`)),
})
],
sidebar: [
Expand Down
3 changes: 2 additions & 1 deletion docs/scripts/sync-workshop-content.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ const outputFile = join(generatedDir, 'workshop-content.ts');
const workshopRepo = process.env.GH_AW_WORKSHOP_REPO || 'githubnext/gh-aw-workshop';
const workshopRef = process.env.GH_AW_WORKSHOP_REF || 'main';
const localWorkshopSourceDir = process.env.GH_AW_WORKSHOP_SOURCE_DIR;
const workshopLocalLinkPrefix = '/gh-aw/workshop/?__gh_aw_workshop_local__=';
const workshopOrgSlug = process.env.GH_AW_WORKSHOP_ORG_SLUG || '2026-07-24-hackathon-blue-bat-18';
const workshopLocalLinkPrefix = `/gh-aw/workshops/${workshopOrgSlug}/?__gh_aw_workshop_local__=`;
const publicWorkshopBase = `https://github.com/${workshopRepo}`;
const publicWorkshopTreeUrl = `${publicWorkshopBase}/tree/${workshopRef}/workshop`;
const publicWorkshopBlobBaseUrl = `${publicWorkshopBase}/blob/${workshopRef}/workshop/`;
Expand Down
8 changes: 7 additions & 1 deletion docs/src/components/workshop/WorkshopExperience.astro
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ import {
} from '../../lib/workshop/manifest';
import { workshopContent, workshopSource, type WorkshopContentEntry } from '../../generated/workshop-content.ts';

interface Props {
org: string;
}

const { org } = Astro.props;

type WorkshopEntry = {
id: string;
title: string;
Expand All @@ -35,7 +41,7 @@ type WorkshopMarkdownModule = {

const workshopGithubBase = workshopSource.githubBaseUrl;
const workshopImageBase = workshopSource.rawBaseUrl;
const workshopLocalLinkPrefix = '/gh-aw/workshop/?__gh_aw_workshop_local__=';
const workshopLocalLinkPrefix = `/gh-aw/workshops/${org}/?__gh_aw_workshop_local__=`;
const workshopMarkdownModules = import.meta.glob('../../generated/workshop-markdown/*.md', { eager: true }) as Record<string, WorkshopMarkdownModule>;
const primerIcons = octicons as Record<string, { toSVG: (options: Record<string, string | number>) => string }>;

Expand Down
3 changes: 2 additions & 1 deletion docs/src/content/docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import FeatureGrid from '../../components/FeatureGrid.astro';
import Video from '../../components/Video.astro';
import BlogLinkSection from '../../components/BlogLinkSection.astro';
import WorkflowHero from '../../components/WorkflowHero.astro';
import { CURRENT_WORKSHOP_SLUG } from '../../lib/workshop/config';

<WorkflowHero />

Expand Down Expand Up @@ -202,7 +203,7 @@ Create custom agentic workflows directly from the GitHub web interface using nat
## Workshop

<FeatureGrid columns={2}>
<FeatureCard icon="workflow" title="Interactive workshop" href="/gh-aw/workshop/" badge="New">
<FeatureCard icon="workflow" title="Interactive workshop" href={`/gh-aw/workshops/${CURRENT_WORKSHOP_SLUG}/`} badge="New">
Choose a terminal, browser, or Copilot path and work through the workshop directly in the docs with saved progress.
</FeatureCard>
<FeatureCard icon="mark-github" title="Source repository" href="https://github.com/githubnext/gh-aw-workshop">
Expand Down
10 changes: 10 additions & 0 deletions docs/src/lib/workshop/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* All workshop slugs, ordered newest-first, in the form `YYYY-MM-DD-org-name`.
* Add a new entry at the top of the array for each new workshop.
*/
export const WORKSHOP_SLUGS = [
'2026-07-24-hackathon-blue-bat-18',
] as const;

/** The slug for the current (latest) active workshop. */
export const CURRENT_WORKSHOP_SLUG = WORKSHOP_SLUGS[0];
29 changes: 29 additions & 0 deletions docs/src/pages/workshops/[org].astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
import StarlightPage from '@astrojs/starlight/components/StarlightPage.astro';
import WorkshopExperience from '../../components/workshop/WorkshopExperience.astro';
import { WORKSHOP_SLUGS } from '../../lib/workshop/config';

export function getStaticPaths() {
return WORKSHOP_SLUGS.map((org) => ({ params: { org } }));
}

const { org } = Astro.params;
if (!org) throw new Error('Workshop route: missing org param');
---

<StarlightPage
hasSidebar={false}
frontmatter={{
title: 'Workshop',
description: 'Work through the gh-aw workshop directly in the docs. Choose where you like to work and track your progress as you go.',
tableOfContents: false,
}}
>
<WorkshopExperience org={org} />
</StarlightPage>

<style>
:global(.pagination-links) {
display: none;
}
</style>
12 changes: 7 additions & 5 deletions docs/tests/workshop.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { expect, test, type Page } from '@playwright/test';
import { CURRENT_WORKSHOP_SLUG } from '../src/lib/workshop/config';

const WORKSHOP_URL = `/gh-aw/workshops/${CURRENT_WORKSHOP_SLUG}/`;
const PIXEL_TOLERANCE = 1;
const ZEN_MODE_MOBILE_BREAKPOINT = 800;

Expand All @@ -12,7 +14,7 @@ const workshopDevices = [
];

async function startWorkshop(page: Page) {
await page.goto('/gh-aw/workshop/');
await page.goto(WORKSHOP_URL);
await page.waitForLoadState('networkidle');
await page.locator('[data-workshop-entry-path="ui-learner"]').click();
await page.locator('[data-workshop-scenario="daily-status"]').click();
Expand Down Expand Up @@ -188,7 +190,7 @@ test.describe('Workshop tutorial', () => {

test.describe('Workshop URL hash navigation', () => {
test('encodes journey and scenario in the URL hash after setup', async ({ page }) => {
await page.goto('/gh-aw/workshop/');
await page.goto(WORKSHOP_URL);
await page.waitForLoadState('networkidle');

await page.locator('[data-workshop-journey="github"]').click();
Expand Down Expand Up @@ -220,7 +222,7 @@ test.describe('Workshop URL hash navigation', () => {
const stepPosition = await page.locator('[data-workshop-step-position]').textContent();

// Navigate away so storage would otherwise default back to step 1.
await page.goto('/gh-aw/workshop/');
await page.goto(WORKSHOP_URL);
await page.waitForLoadState('networkidle');
// Clear session storage so the only source of truth for the step is the URL hash.
await page.evaluate(() => sessionStorage.clear());
Expand All @@ -235,7 +237,7 @@ test.describe('Workshop URL hash navigation', () => {
});

test('supports browser back navigation from tutorial to setup', async ({ page }) => {
await page.goto('/gh-aw/workshop/');
await page.goto(WORKSHOP_URL);
await page.waitForLoadState('networkidle');

await page.locator('[data-workshop-journey="github"]').click();
Expand All @@ -250,7 +252,7 @@ test.describe('Workshop URL hash navigation', () => {
});

test('supports browser back navigation from scenario picker to workspace picker', async ({ page }) => {
await page.goto('/gh-aw/workshop/');
await page.goto(WORKSHOP_URL);
await page.waitForLoadState('networkidle');

await page.locator('[data-workshop-journey="github"]').click();
Expand Down
Loading