Skip to content

feat: filter WorkshopExperience steps by journey/adventure frontmatter - #46582

Merged
pelikhan merged 3 commits into
mainfrom
copilot/update-workshopexperience-filter
Jul 19, 2026
Merged

feat: filter WorkshopExperience steps by journey/adventure frontmatter#46582
pelikhan merged 3 commits into
mainfrom
copilot/update-workshopexperience-filter

Conversation

Copilot AI commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Replaces the hardcoded curriculum-route system in WorkshopExperience with data-driven filtering based on journey and adventure frontmatter fields now present in workshop content files (aligned to gh-aw-workshop#1538).

Frontmatter schema

Each workshop .md file now declares:

---
journey: all | terminal | ui | local | codespace | copilot
adventure: core | setup | scenario-a | scenario-b | scenario-c | scenario-d | advanced | side-quest
---

Sync script (sync-workshop-content.js)

  • Added parseFrontmatter() to extract journey/adventure from markdown files and strip frontmatter from body
  • WorkshopContentEntry type gains journey: string and adventure: string

Manifest (manifest.ts)

  • WorkshopJourney gains contentJourneyIds: string[] mapping manifest journey IDs to content frontmatter values:
    • github → ['ui'], terminal → ['terminal', 'local'], vscode → ['local', 'terminal'], copilot → ['copilot', 'ui']
  • Added workshopScenarioAdventures mapping scenario IDs to adventure values (scenario-a/b/c)
  • buildWorkshopFlow replaced with a filter that:
    • Includes entries matching contentJourneyIds and relevant adventures (core, setup, advanced, selected scenario, scenario-d for copilot)
    • Excludes hub/overview pages via prefix matching: numeric prefixes (06) are hubs when letter-variants exist (06a/b/c); alphanumeric prefixes (11a) are hubs when same-prefix specific-journey entries exist
    • Copilot special-case: excludes ui-journey scenario-a/b/c build steps since 11d (scenario-d) covers all scenarios; hub detection uses full content so hub pages are still suppressed even when the trigger variant was filtered out

WorkshopExperience.astro

  • journey/adventure threaded through WorkshopEntryWorkshopStep → step JSON payload
  • Manifest JSON: routes removed, scenarioAdventures added; journeys now includes contentJourneyIds
  • Client-side buildFlow rewritten to mirror server-side filter logic (using steps array metadata rather than route maps)

Copilot AI and others added 2 commits July 19, 2026 09:01
…frontmatter

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
…n in callbacks

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title feat: update WorkshopExperience to filter steps by journey/adventure frontmatter feat: filter WorkshopExperience steps by journey/adventure frontmatter Jul 19, 2026
Copilot AI requested a review from pelikhan July 19, 2026 09:04
@pelikhan
pelikhan marked this pull request as ready for review July 19, 2026 09:04
Copilot AI review requested due to automatic review settings July 19, 2026 09:04
@github-actions

github-actions Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

No test files were added or modified in this PR. Test Quality Sentinel skipped. PR #46582 only modified source files in docs/: WorkshopExperience.astro, workshop-content.ts, manifest.ts, and sync-workshop-content.js.

@github-actions

github-actions Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actions Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

No ADR enforcement needed: PR #46582 does not have the 'implementation' label and has 0 new lines of code in business logic directories (threshold: 100).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Replaces hardcoded workshop routing with frontmatter-driven journey and adventure filtering.

Changes:

  • Parses and propagates workshop frontmatter metadata.
  • Filters server and client workshop flows by journey/scenario.
  • Adds hub detection and Copilot-specific routing.
Show a summary per file
File Description
docs/src/lib/workshop/manifest.ts Defines mappings and server-side filtering.
docs/src/components/workshop/WorkshopExperience.astro Implements matching client-side flow selection.
docs/scripts/sync-workshop-content.js Extracts frontmatter into generated metadata.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 3/4 changed files
  • Comments generated: 2
  • Review effort level: Medium

Comment thread docs/scripts/sync-workshop-content.js Outdated
Comment on lines +574 to +579
const candidates = steps.filter((step) => {
if (isCopilot && step.journey === 'ui' && !['core', 'setup', 'advanced', 'scenario-d'].includes(step.adventure)) {
return false;
}
const journeyMatch = step.journey === 'all' || contentJourneyIds.includes(step.journey);
return journeyMatch && includedAdventures.has(step.adventure) && step.adventure !== 'side-quest';
@github-actions github-actions Bot mentioned this pull request Jul 19, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: feat: filter WorkshopExperience steps by journey/adventure frontmatter

The data-driven filtering approach is a clean architectural improvement over the hardcoded route tables. Three issues need to be addressed before merging:

Blocking

1. Local dev artifact committed (docs/src/generated/workshop-content.ts, line 4)
The source URL was changed from the real upstream GitHub URL to /tmp/workshop-source. This is a local path that should never appear in committed code and will break reproducibility documentation.

2. Redundant parseFrontmatter call (docs/scripts/sync-workshop-content.js, line ~232)
After addEntryMetadata strips and re-assigns the frontmatter-free body back to entry.body, the loop at line 232 calls parseFrontmatter(entry.body) again on the already-stripped body. The second call is harmless but misleading — entry.body should be used directly.

Non-blocking (worth verifying)

3. hubPrefixes filter excludes journey === 'all' variants (WorkshopExperience.astro line ~171, manifest.ts line ~1211)
Hub detection builds hubPrefixes from steps matching contentJourneyIds.includes(s.journey). Since 'all' is never in any contentJourneyIds array, 'all'-journey variants with letter prefixes won't suppress their numeric hub entry. This appears safe with the current content (all letter-variant steps carry specific journey IDs), but the assumption should be documented or guarded.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 68.8 AIC · ⌖ 4.83 AIC · ⊞ 5K

Comments that could not be inline-anchored

docs/src/generated/workshop-content.ts:4

The source URL comment was changed to /tmp/workshop-source — this is a local dev artifact and should not be committed. The comment should point to the real upstream workshop source URL (e.g. https://github.com/githubnext/gh-aw-workshop/tree/main/workshop) before merging.

@copilot please address this.

docs/scripts/sync-workshop-content.js:232

Double frontmatter stripping: addEntryMetadata already parses and strips the frontmatter from entry.body, reassigning the body-without-frontmatter back. Calling parseFrontmatter(entry.body) again here on the already-stripped body has no effect but is misleading. Use entry.body directly — the frontmatter was already removed by addEntryMetadata.

// Instead of:
const { body } = parseFrontmatter(entry.body);
writeFileSync(..., rewriteWorkshopMarkdownForAstro(body, ...));

// Use:
w…

</details>

<details><summary>docs/src/components/workshop/WorkshopExperience.astro:171</summary>

The `hubPrefixes` set is built by filtering steps where `contentJourneyIds.includes(s.journey)`. This never matches steps with `journey === &#39;all&#39;` (since `&#39;all&#39;` is never a `contentJourneyId`). If a hub-letter variant (e.g. `11a-build-foo-ui.md`) has `journey: &#39;ui&#39;`, it will be captured. But if such a variant has `journey: &#39;all&#39;`, it won&#39;t contribute to `hubPrefixes`, so its numeric hub entry (e.g. `11.md`) won&#39;t be suppressed.

Verify this is intentional  if hub variants can have `journey: &#39;a…

</details>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /diagnosing-bugs, /tdd, /codebase-design, and /grill-with-docs — requesting changes on two correctness issues and flagging architecture and documentation gaps.

📋 Key Themes & Highlights

Key Issues

  1. Double-strip bug (sync-workshop-content.js line 54): parseFrontmatter called on already-stripped bodies from the existing-entries cache path. The existing review comment (id 3610257883) correctly flags this.
  2. Trailing \r in values (parseFrontmatter line 14): splitting on \n after a \r\n regex match leaves \r on each value in CRLF files, silently breaking journey/adventure comparisons.
  3. Duplicated filter logic (manifest.ts + WorkshopExperience.astro): server-side buildWorkshopFlow and client-side buildFlow implement the same hub-detection algorithm separately — a future change must be made in both places.
  4. No unit tests for hub detection: the two-case regex-based hub-suppression logic is untested; naming-convention changes will silently break it.
  5. Undocumented copilotui coupling: the compensating exclusion in buildWorkshopFlow is non-obvious and will surprise the next person who adds a UI-journey adventure.

Positive Highlights

  • ✅ Clean replacement of hardcoded route maps with data-driven frontmatter — the approach is sound and aligns well with the content model.
  • ✅ Good use of contentJourneyIds to decouple manifest IDs from content-file values.
  • ✅ Hub detection handles both naming conventions (numeric-only 06 and alphanumeric 11a) explicitly.
  • normalizeStepId helper avoids repeated inline .replace(/\.md$/u, '').

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 80 AIC · ⌖ 5.72 AIC · ⊞ 6.7K
Comment /matt to run again

Comments that could not be inline-anchored

docs/scripts/sync-workshop-content.js:54

[/diagnosing-bugs] Double-stripping bug: parseFrontmatter is called here on entries from loadExistingGeneratedWorkshopEntries(), but those entries have already had frontmatter stripped by addEntryMetadata. On a cache hit entry.body no longer starts with ---, so the parse is a no-op — journey/adventure won't be reparsed, meaning cached entries silently keep their stored values only if the generated file is already up to date.

<details>
<summary>💡 Suggested fix</summary>

Writ…

docs/scripts/sync-workshop-content.js:14

[/diagnosing-bugs] Trailing \r in frontmatter values: the regex matches \r\n line endings, but yaml.split(&#39;\n&#39;) leaves a trailing \r on each value in CRLF files. A frontmatter entry like journey: terminal\r will produce frontmatter[&#39;journey&#39;] === &#39;terminal\r&#39;, which won't match contentJourneyIds comparisons downstream.

<details>
<summary>💡 Suggested fix</summary>

Strip \r when splitting:

for (const line of yaml.split(/\r?\n/u)) {

Or trim the captured value:

`…

docs/src/lib/workshop/manifest.ts:1201

[/tdd] The hub-detection algorithm is the most complex new logic in this PR — two cases, a regex, a Set scan — but there are no unit tests for it. A stale journey ID or a new step naming convention could silently drop or include hub pages for only one journey.

<details>
<summary>💡 Suggested tests</summary>

At minimum, test these cases for buildWorkshopFlow:

it(&#39;excludes numeric hub when letter-variant step exists for journey&#39;, () =&gt; {
  // e.g. &#39;06&#39; is excluded when &#39;06a-...&#39; ex…

</details>

<details><summary>docs/src/lib/workshop/manifest.ts:1192</summary>

**[/codebase-design]** The server-side `buildWorkshopFlow` and the client-side `buildFlow` in `WorkshopExperience.astro` now implement the same hub-detection and journey-filter algorithm in two places. Any future change to the logic (new hub-naming convention, new copilot exclusion rule) must be made in both places and kept in sync manually  a maintenance hazard.

&lt;details&gt;
&lt;summary&gt;💡 Suggested approach&lt;/summary&gt;

Emit the pre-computed step array (result of `buildWorkshopFlow`) into the manif…

</details>

<details><summary>docs/src/lib/workshop/manifest.ts:1141</summary>

**[/grill-with-docs]** The `copilot` manifest journey maps to `contentJourneyIds: [&#39;copilot&#39;, &#39;ui&#39;]`. This means UI-journey steps are included in the copilot flow by default, and the copilot-specific exclusion at line 1198 (`isCopilot &amp;&amp; entry.journey === &#39;ui&#39; &amp;&amp; scenario-a/b/c`) is compensating for that. The coupling is fragile: adding a new UI-journey adventure value would silently include it in the copilot flow unless a matching exclusion rule is added.

&lt;details&gt;
&lt;summary&gt;💡 Suggested appro…

</details>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REQUEST_CHANGES — three blocking issues found

The data-driven filtering approach is sound, but three issues need resolution before merge.

### Blocking issues

1. journey value is unmapped (high) — Workshop content files use frontmatter (3 entries: , , ), but no manifest journey declares containing "codespace". These steps are silently dropped from every flow. See inline comment on manifest.ts line 88.

2. Generated file has committed local path (high)docs/src/generated/workshop-content.ts line 2 reads // Source: /tmp/workshop-source. This should be regenerated from upstream before merge, or the script should normalize the source comment for local builds. See inline comment.

3. parseFrontmatter doesn't strip YAML quotes (medium) — Quoted YAML values like journey: "all" produce literal '"all"' strings, silently breaking journey/adventure comparisons for any content file that uses quoted values. See inline comment on sync-workshop-content.js line 17.

The cached-content fallback issue (destroying journey/adventure on network failure) was already flagged in an earlier review comment and is not duplicated here.

🔎 Code quality review by PR Code Quality Reviewer · 101.1 AIC · ⌖ 6.37 AIC · ⊞ 5.6K
Comment /review to run again

Comments that could not be inline-anchored

docs/src/lib/workshop/manifest.ts:88

codespace journey value has no matching contentJourneyIds entry — codespace-journey steps are silently excluded from all flows.

<details>
<summary>💡 Details and suggested fix</summary>

The workshop content now uses journey: codespace frontmatter (e.g. 02a-setup-codespace.md, 06a-install-terminal.md), but no entry in workshopJourneys declares contentJourneyIds containing &quot;codespace&quot;. The filter entry.journey === &#39;all&#39; || contentJourneyIds.includes(entry.journey) therefor…

docs/scripts/sync-workshop-content.js:17

parseFrontmatter doesn't strip quotes from YAML values — journey: &quot;all&quot; produces &#39;&quot;all&quot;&#39; with literal quote characters, silently breaking all journey/adventure comparisons.

<details>
<summary>💡 Details and suggested fix</summary>

The parser does line.slice(colonIdx + 1).trim() which preserves any surrounding quotes in the value. If a workshop content file uses quoted YAML values (valid YAML), the extracted string will include the quote characters:

// Input frontmatter:  jo…

</details>

<details><summary>docs/src/generated/workshop-content.ts:2</summary>

**Generated file contains a local filesystem path `/tmp/workshop-source` as the source comment — this leaks a machine-specific path and will appear as a permanent diff in CI.**

&lt;details&gt;
&lt;summary&gt;💡 Details&lt;/summary&gt;

Line 2 of the generated file reads:

```ts
// Source: /tmp/workshop-source

This was generated with GH_AW_WORKSHOP_SOURCE_DIR=/tmp/workshop-source set locally. The sync script uses source: sourceDir (the resolved local path) directly when localWorkshopSourceDir is set —…

@github-actions

Copy link
Copy Markdown
Contributor

Thanks for working on the data-driven workshop filtering system! 🎯 This is a clean refactor that makes the routing logic much more maintainable.

Here's what would help get this across the finish line:

  • Add test coverage — The new filtering logic in manifest.ts (hub detection, journey/adventure mapping) and the client-side buildFlow mirror logic in WorkshopExperience.astro are complex and deserve unit tests. Cover:
    • Journey ID to content journey mapping (e.g., github → ['ui'])
    • Adventure scenario filtering (core, setup, selected scenario, scenario-d for copilot)
    • Hub detection for numeric prefixes (06 vs 06a/b/c) and alphanumeric prefixes (11a when 11a, 11b, 11c exist)
    • Copilot special-case exclusion of ui-journey scenario-a/b/c when 11d is present

Tests would validate that the refactored filtering matches the original hardcoded routes and catch edge cases.

If you'd like a hand, assign this to your coding agent:

Add unit tests for the workshop filtering logic in docs/src/lib/workshop/manifest.ts. Test the buildWorkshopFlow function and hub detection regex logic:

1. Journey ID mapping (github journey → ui content, terminal → terminal/local, etc.)
2. Adventure filtering (core, setup, advanced, scenario selection)
3. Hub page detection: numeric prefixes (06 is hub if 06a/b/c exist) and alphanumeric prefixes (11a is hub if matching scenario entries exist)
4. Copilot edge case: ui-journey scenario-a/b/c excluded when scenario-d exists
5. Ensure mirrors the client-side logic in WorkshopExperience.astro

Generated by ✅ Contribution Check · 60.1 AIC · ⌖ 9.88 AIC · ⊞ 6.2K ·

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill, address the outstanding requested-review feedback and inline comments, add the requested test coverage, update the branch if needed, and rerun checks once complete.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • pi.dev

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "pi.dev"

See Network Configuration for more information.

Generated by 👨‍🍳 PR Sous Chef · 13.5 AIC · ⌖ 6.01 AIC · ⊞ 7.5K ·
Comment /souschef to run again

… Playwright tests

- addEntryMetadata: fall back to entry.journey/entry.adventure when frontmatter
  is empty (cached entries have already had frontmatter stripped); add explanatory
  comment for the three-level fallback chain
- docs/tests/workshop.spec.ts: add 10 Playwright tests covering scenario
  isolation, hub page removal, and Copilot scenario-d substitution

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jul 19, 2026

Copy link
Copy Markdown
Contributor Author

@copilot run pr-finisher skill

Done. Two issues from the review threads were addressed in commit fix: preserve journey/adventure on cached entries; add flow-filtering Playwright tests:

  1. Cached-content fallback bug (sync-workshop-content.js:139-140): addEntryMetadata now falls back to entry.journey / entry.adventure before the static defaults, so cached entries keep their correct metadata on offline/transient builds. Added an explanatory comment for the three-level chain.

  2. Test coverage (WorkshopExperience.astro:574-579): Added 10 Playwright tests in docs/tests/workshop.spec.ts across three test.describe blocks — scenario isolation (4 tests across github/terminal journeys × multiple scenarios), hub page removal (3 tests verifying numeric-prefix and alphanumeric-prefix hub suppression), and Copilot scenario-d substitution (3 tests covering all three scenario IDs).

@pelikhan
pelikhan merged commit b80d5cb into main Jul 19, 2026
8 of 9 checks passed
@pelikhan
pelikhan deleted the copilot/update-workshopexperience-filter branch July 19, 2026 10:14
Copilot stopped work on behalf of gh-aw-bot due to an error July 19, 2026 10:14
Copilot AI requested a review from gh-aw-bot July 19, 2026 10:14
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.82.14

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants