Skip to content

Dewain: Navigation, agenda flyouts, and issue reporting#230

Open
adilei wants to merge 1 commit intoredesign/air-themefrom
dewain/air-theme-enhancements
Open

Dewain: Navigation, agenda flyouts, and issue reporting#230
adilei wants to merge 1 commit intoredesign/air-themefrom
dewain/air-theme-enhancements

Conversation

@adilei
Copy link
Copy Markdown
Contributor

@adilei adilei commented Mar 21, 2026

Summary

Cherry-picked from Dewain's direct commit to redesign/air-theme — moved here for review.

Original commit: 52757e4 — "Enhance air-theme with navigation, agenda flyouts, and issue reporting"

For reviewer

Please review before merging into redesign/air-theme.

- Add workshop dropdown to masthead navigation with hover menu
- Reorder homepage: Workshops & Events before Learning Paths
- Update hero buttons: Browse all workshops + Browse all labs
- Add agenda flyout panel for Architecture Bootcamp (3-day) and MCS in a Day v2 (1-day)
  - Agenda persists open state across page navigations via sessionStorage
  - Auto-closes when navigating away from relevant workshop content
  - Highlights and scrolls to current lab when viewing bootcamp/v2 labs
  - Lab items are clickable links with correct titles from lab source files
- Add Report Issue button to masthead with lab-context pre-fill
- Add GitHub issue templates from main branch (bug report, enhancement, new lab)
- Rename webchat flyout label from "Ask" to "Lab Assistant"
- Update Feedback section in labs with Report Bug and Suggest Improvement buttons
- Remove feedback section from workshop pages
- Both flyout panels start below header, no overlay blocking page interaction
- Update workshop lab lists to match main branch configuration
- Show h2 and h3 levels in TOC sidebar for better lab navigation
Copy link
Copy Markdown
Contributor Author

@adilei adilei left a comment

Choose a reason for hiding this comment

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

1. Agenda flyout — what's the requirement?

What problem are we solving here? Is it that workshop participants lose track of where they are in the schedule? That they need to see what's next? Let's define the requirement first and then design a solution. A hard-coded flyout per workshop is one approach, but there may be simpler ones. Let's discuss before building.

2. Workshops dropdown in nav

I'd prefer to keep the current pattern — click "Workshops" → land on the index page. It's clean, works on mobile/touch, and scales naturally as we add workshops. A hover dropdown doesn't work on touch devices and will get long and unwieldy over time.

3. "Report Issue" button in masthead

Why do we need such a prominent report button on every page including the homepage? Feedback should be contextual — invited on each lab or workshop where it's relevant, not as a persistent global nav item. Let's discuss the right approach.

4. Lab feedback section (Report a Bug / Suggest an Improvement / collapsible utterances)

This feels cluttered — three separate feedback mechanisms stacked at the bottom. I'd like to explore routing feedback through the Lab Assistant instead. The "Leave feedback" pill could open the assistant and send an event over DirectLine with lab context pre-filled. Let's discuss.

5. Content changes

The bootcamp workshop has a completely new lab sequence, MCS in a Day changed 3 of 5 labs, Azure AI Workshop reordered labs, and the homepage hero CTA changed from "Browse all labs" to "Browse all workshops". Are these intentional content updates or side effects of the feature work?

Dewain27 added a commit that referenced this pull request Apr 17, 2026
- Introduces _data/agendas/<event_id>.yml as the structured event schedule
  source. Each agenda item is a typed row (lab / session / break / link /
  day) with time, duration, and optional description/speaker/url. Events
  without an agenda file fall back to their existing labs: array so
  migration is opt-in per event.
- _layouts/event.html now renders a full timeline (day headers + labs +
  sessions + breaks + external links) instead of a bare labs list.
- _layouts/lab.html emits a JSON manifest of every event's schedule and
  includes a new event-nav rail + bottom Prev/Next cards. A vanilla-JS
  IIFE (_includes/event-nav/script.html) resolves the active event from
  the URL's ?event= param or a sessionStorage sticky context, then:
    - replaces the breadcrumb with Home / Events / <Event> / <Lab>,
    - populates the rail (event name, Lab X of Y, Prev, Next, expand,
      exit) and the bottom cards with real labs,
    - renders an expandable agenda popover with the current lab
      highlighted and non-lab items shown inline,
    - persists the event context in sessionStorage so navigation that
      drops the ?event= query param keeps the rail visible, but only
      when the active event actually contains the current lab (silently
      clears stale sticky otherwise).
- Rail + popover follow the air-theme tokens; every tap target is
  >= 44x44 css px; the popover opens on explicit click only (no hover
  menus); mobile rail wraps to two rows and bottom cards stack.
- Hide minimal-mistakes' generic post pagination on lab pages when an
  event context is active.
- Homepage: remove the Learning paths section; replace with a quiet
  'Browse all labs ->' link.
- Masthead: reorder to Home / Events / All Labs / About.
- Real 3-day agenda for Architecture Bootcamp and 1-day MCS in a Day
  agenda sourced from PR #230.
- CHANGELOG updated.
Dewain27 added a commit that referenced this pull request Apr 17, 2026
…ration

The original PR #230 HTML stuffed lab entries into 15-minute start slots,
so importing those durations as-is made the agenda pill claim Lab 1 ran
for 15 minutes when _labs/agent-builder-m365.md declares 30. Remove the
slot-duration overrides on all lab rows in bootcamp.yml and
mcs-in-a-day.yml; the timeline template already falls back to
site.labs[slug].duration when an agenda lab entry has no duration.
Dewain27 added a commit that referenced this pull request Apr 17, 2026
* Add event-aware navigation and agenda data model

- Introduces _data/agendas/<event_id>.yml as the structured event schedule
  source. Each agenda item is a typed row (lab / session / break / link /
  day) with time, duration, and optional description/speaker/url. Events
  without an agenda file fall back to their existing labs: array so
  migration is opt-in per event.
- _layouts/event.html now renders a full timeline (day headers + labs +
  sessions + breaks + external links) instead of a bare labs list.
- _layouts/lab.html emits a JSON manifest of every event's schedule and
  includes a new event-nav rail + bottom Prev/Next cards. A vanilla-JS
  IIFE (_includes/event-nav/script.html) resolves the active event from
  the URL's ?event= param or a sessionStorage sticky context, then:
    - replaces the breadcrumb with Home / Events / <Event> / <Lab>,
    - populates the rail (event name, Lab X of Y, Prev, Next, expand,
      exit) and the bottom cards with real labs,
    - renders an expandable agenda popover with the current lab
      highlighted and non-lab items shown inline,
    - persists the event context in sessionStorage so navigation that
      drops the ?event= query param keeps the rail visible, but only
      when the active event actually contains the current lab (silently
      clears stale sticky otherwise).
- Rail + popover follow the air-theme tokens; every tap target is
  >= 44x44 css px; the popover opens on explicit click only (no hover
  menus); mobile rail wraps to two rows and bottom cards stack.
- Hide minimal-mistakes' generic post pagination on lab pages when an
  event context is active.
- Homepage: remove the Learning paths section; replace with a quiet
  'Browse all labs ->' link.
- Masthead: reorder to Home / Events / All Labs / About.
- Real 3-day agenda for Architecture Bootcamp and 1-day MCS in a Day
  agenda sourced from PR #230.
- CHANGELOG updated.

* security: build breadcrumb override via createElement, not innerHTML

CodeQL's js/xss-through-dom flagged 11 alerts because the override
concatenated DOM-sourced values (activeEvent.title, currentLab.title)
into an HTML string before assigning to innerHTML. The inline
escapeHtml() helper is correct, but CodeQL's taint analysis doesn't
trust unknown sanitizers.

Rebuild the breadcrumb tree with document.createElement and
element.textContent so no string flows into an HTML parser at all.
Also snapshot the original child nodes (not innerHTML) for restore.

* ui: make labs visually dominant in the agenda vs sessions/breaks

Before: every agenda row shared a card container, differentiated only
by background shade and border style. Labs and non-labs read as the
same thing at a glance.

Now:
- Lab rows on the event page use a bordered card with an accent-blue
  4 px left rail and more generous padding (72 px min-height).
- Session / break / link rows become compact unboxed rows (36 px,
  italic for breaks, muted gray) — visible as schedule context but not
  competing with labs.
- In the in-lab rail popover, lab rows carry an accent-blue left rail
  and a bolder title in accent color; non-lab rows stay italic and
  muted. The current-lab highlight keeps the accent-tint background.

* security: route every href write through safeUrl()

CodeQL still reported 10 js/xss-through-dom alerts after the innerHTML
fix because every a.href = manifest.url assignment is a potential
javascript: URL sink (the manifest ultimately comes from DOM text).

Add a safeUrl() sanitizer that:
- passes absolute paths and hash-only URLs through,
- parses anything else and accepts only http/https same-origin,
- returns '#' otherwise.

All same-origin href writes (rail link, nav-pair cards, popover lab
links, breadcrumb) now flow through safeUrl. External type=link items
keep a dedicated check that accepts any https URL via URL validation.
window.location.href writes switch to window.location.assign(safeUrl())
for the same reason.

* security: build every URL via URL object and assign url.href

Previous attempts (escapeHtml + safeUrl helper) didn't satisfy CodeQL
because it doesn't track custom sanitizers across function boundaries.

Switch to a pattern CodeQL recognizes natively:
  1. Construct every URL via new URL(candidate, window.location.origin)
  2. Reject anything whose origin doesn't match the page origin
  3. Assign url.href (the URL object's serialized form) to element.href

URL.prototype.href is a well-known sanitized string in CodeQL's model,
so the js/xss-through-dom source-to-sink flow terminates at the URL
object's .href accessor.

* ui: flatten On This Page TOC to top-level headings only

The TOC was showing both h2 and h3 levels, which produced a long
scrollable list on lab pages with many sub-steps (governance, tools).
Hide every nested <ul>; show only top-level entries (h2). Page content
still has sub-structure visible inline — no need to duplicate it in
the side panel.

* ui: bump On This Page TOC text sizes

- .nav__title (ON THIS PAGE header): 0.85em -> 1em (~16 px at A default)
- .toc__menu a (top-level items): 0.95em -> 1.05em (~16.8 px at A default,
  slightly above body copy for sidebar prominence)
- vertical padding nudged from 0.4em to 0.45em to keep breathing room

* ui: bump On This Page TOC text again

- .nav__title: 1em -> 1.1em, weight 600 -> 700
- .toc__menu a: 1.05em -> 1.15em (~18.4 px at A default), weight 400 -> 500,
  padding bumped for room at the larger size

* agendas: drop slot-duration from lab rows; fall back to lab's real duration

The original PR #230 HTML stuffed lab entries into 15-minute start slots,
so importing those durations as-is made the agenda pill claim Lab 1 ran
for 15 minutes when _labs/agent-builder-m365.md declares 30. Remove the
slot-duration overrides on all lab rows in bootcamp.yml and
mcs-in-a-day.yml; the timeline template already falls back to
site.labs[slug].duration when an agenda lab entry has no duration.

* agendas: match Day 1 schedule exactly; allow short title override on labs

User supplied the authoritative Day 1 schedule image. Differences from
my first pass of bootcamp.yml / mcs-in-a-day.yml:
- Module names lose the 'Module N –' prefix ('Agent Builder',
  'Copilot Studio Core Concepts - Part 1/2/3', 'Licensing').
- Lab 1 starts at 10:00 (parallel with the coffee break slot).
- Post-lunch slot is 'Q+A' rather than the longer 'Quiz / Review /
  AMA / Flex'.
- Closing slot is 'Open Q+A' rather than 'Open AMA'.

To render the short lab labels ('Lab 1: Agent Builder') the schema
now accepts an optional title override on lab rows:
  - type: lab
    slug: agent-builder-m365
    label: 'Lab 1'
    title: 'Agent Builder'   # overrides lab.title for this event
Both the event timeline (_layouts/event.html) and the in-lab popover
(via the manifest + script IIFE) use the override when present and
fall back to lab.title otherwise. Separator switched from em-dash to
colon ('Lab 1: Agent Builder'). MCS in a Day is a copy of Bootcamp
Day 1 per the supplied schedule.

* agendas: update Bootcamp Day 2 schedule from supplied image

Differences from the earlier guess:
- Re-welcome/Re-cap no longer includes 'Quiz' in the title.
- Module 6 / 7 / 8 / 9 use their full titles instead of abbreviations.
- Lab 5 now carries an event title override ('Export / Import Solutions
  …') matching the image; Lab 6 is 'Component Collections'; Lab 7 is
  'Tools'.
- Lunch is 12:15-13:15; a new 'Quiz Time' slot sits 13:15-13:30 in
  parallel with the first 15 minutes of Module 9 (per the image, Quiz
  and Module 9 share the 13:15 start time).
- Module 9 - Tools runs 13:15-14:45 (90 min).
- Final slot is 'Open Q+A' not 'Open AMA'.

* agendas: update Bootcamp Day 3 schedule from supplied image

- Re-Welcome/Re-cap loses 'Quiz' from title (Quiz Time moves to its own slot).
- Module titles match image: 'Module 10 - Additional Resources',
  'Module 11 - Governance', 'Module 12 - SDK /w Demo',
  'Module 13 - Multi-Agent', 'Module 14 - Autonomous Agents',
  'Module 15 - Azure Integration /w Demo', 'Module 16 - Agent 365 w/ Demo'.
- Lab 8 carries short title 'Governance - DLP' (full lab title remains in
  _labs/mcs-governance.md). Lab 9 'Multi-Agent'. Lab 10 'Autonomous Agents'.
- Post-lunch slot renamed 'Quiz Time / Review' (13:00, 15 min).
- Closing slot 'General Q+A' at 15:30-16:00.
Dewain27 added a commit that referenced this pull request Apr 17, 2026
* Add event-aware navigation and agenda data model

- Introduces _data/agendas/<event_id>.yml as the structured event schedule
  source. Each agenda item is a typed row (lab / session / break / link /
  day) with time, duration, and optional description/speaker/url. Events
  without an agenda file fall back to their existing labs: array so
  migration is opt-in per event.
- _layouts/event.html now renders a full timeline (day headers + labs +
  sessions + breaks + external links) instead of a bare labs list.
- _layouts/lab.html emits a JSON manifest of every event's schedule and
  includes a new event-nav rail + bottom Prev/Next cards. A vanilla-JS
  IIFE (_includes/event-nav/script.html) resolves the active event from
  the URL's ?event= param or a sessionStorage sticky context, then:
    - replaces the breadcrumb with Home / Events / <Event> / <Lab>,
    - populates the rail (event name, Lab X of Y, Prev, Next, expand,
      exit) and the bottom cards with real labs,
    - renders an expandable agenda popover with the current lab
      highlighted and non-lab items shown inline,
    - persists the event context in sessionStorage so navigation that
      drops the ?event= query param keeps the rail visible, but only
      when the active event actually contains the current lab (silently
      clears stale sticky otherwise).
- Rail + popover follow the air-theme tokens; every tap target is
  >= 44x44 css px; the popover opens on explicit click only (no hover
  menus); mobile rail wraps to two rows and bottom cards stack.
- Hide minimal-mistakes' generic post pagination on lab pages when an
  event context is active.
- Homepage: remove the Learning paths section; replace with a quiet
  'Browse all labs ->' link.
- Masthead: reorder to Home / Events / All Labs / About.
- Real 3-day agenda for Architecture Bootcamp and 1-day MCS in a Day
  agenda sourced from PR #230.
- CHANGELOG updated.

* security: build breadcrumb override via createElement, not innerHTML

CodeQL's js/xss-through-dom flagged 11 alerts because the override
concatenated DOM-sourced values (activeEvent.title, currentLab.title)
into an HTML string before assigning to innerHTML. The inline
escapeHtml() helper is correct, but CodeQL's taint analysis doesn't
trust unknown sanitizers.

Rebuild the breadcrumb tree with document.createElement and
element.textContent so no string flows into an HTML parser at all.
Also snapshot the original child nodes (not innerHTML) for restore.

* ui: make labs visually dominant in the agenda vs sessions/breaks

Before: every agenda row shared a card container, differentiated only
by background shade and border style. Labs and non-labs read as the
same thing at a glance.

Now:
- Lab rows on the event page use a bordered card with an accent-blue
  4 px left rail and more generous padding (72 px min-height).
- Session / break / link rows become compact unboxed rows (36 px,
  italic for breaks, muted gray) — visible as schedule context but not
  competing with labs.
- In the in-lab rail popover, lab rows carry an accent-blue left rail
  and a bolder title in accent color; non-lab rows stay italic and
  muted. The current-lab highlight keeps the accent-tint background.

* security: route every href write through safeUrl()

CodeQL still reported 10 js/xss-through-dom alerts after the innerHTML
fix because every a.href = manifest.url assignment is a potential
javascript: URL sink (the manifest ultimately comes from DOM text).

Add a safeUrl() sanitizer that:
- passes absolute paths and hash-only URLs through,
- parses anything else and accepts only http/https same-origin,
- returns '#' otherwise.

All same-origin href writes (rail link, nav-pair cards, popover lab
links, breadcrumb) now flow through safeUrl. External type=link items
keep a dedicated check that accepts any https URL via URL validation.
window.location.href writes switch to window.location.assign(safeUrl())
for the same reason.

* security: build every URL via URL object and assign url.href

Previous attempts (escapeHtml + safeUrl helper) didn't satisfy CodeQL
because it doesn't track custom sanitizers across function boundaries.

Switch to a pattern CodeQL recognizes natively:
  1. Construct every URL via new URL(candidate, window.location.origin)
  2. Reject anything whose origin doesn't match the page origin
  3. Assign url.href (the URL object's serialized form) to element.href

URL.prototype.href is a well-known sanitized string in CodeQL's model,
so the js/xss-through-dom source-to-sink flow terminates at the URL
object's .href accessor.

* ui: flatten On This Page TOC to top-level headings only

The TOC was showing both h2 and h3 levels, which produced a long
scrollable list on lab pages with many sub-steps (governance, tools).
Hide every nested <ul>; show only top-level entries (h2). Page content
still has sub-structure visible inline — no need to duplicate it in
the side panel.

* ui: bump On This Page TOC text sizes

- .nav__title (ON THIS PAGE header): 0.85em -> 1em (~16 px at A default)
- .toc__menu a (top-level items): 0.95em -> 1.05em (~16.8 px at A default,
  slightly above body copy for sidebar prominence)
- vertical padding nudged from 0.4em to 0.45em to keep breathing room

* ui: bump On This Page TOC text again

- .nav__title: 1em -> 1.1em, weight 600 -> 700
- .toc__menu a: 1.05em -> 1.15em (~18.4 px at A default), weight 400 -> 500,
  padding bumped for room at the larger size

* agendas: drop slot-duration from lab rows; fall back to lab's real duration

The original PR #230 HTML stuffed lab entries into 15-minute start slots,
so importing those durations as-is made the agenda pill claim Lab 1 ran
for 15 minutes when _labs/agent-builder-m365.md declares 30. Remove the
slot-duration overrides on all lab rows in bootcamp.yml and
mcs-in-a-day.yml; the timeline template already falls back to
site.labs[slug].duration when an agenda lab entry has no duration.

* agendas: match Day 1 schedule exactly; allow short title override on labs

User supplied the authoritative Day 1 schedule image. Differences from
my first pass of bootcamp.yml / mcs-in-a-day.yml:
- Module names lose the 'Module N –' prefix ('Agent Builder',
  'Copilot Studio Core Concepts - Part 1/2/3', 'Licensing').
- Lab 1 starts at 10:00 (parallel with the coffee break slot).
- Post-lunch slot is 'Q+A' rather than the longer 'Quiz / Review /
  AMA / Flex'.
- Closing slot is 'Open Q+A' rather than 'Open AMA'.

To render the short lab labels ('Lab 1: Agent Builder') the schema
now accepts an optional title override on lab rows:
  - type: lab
    slug: agent-builder-m365
    label: 'Lab 1'
    title: 'Agent Builder'   # overrides lab.title for this event
Both the event timeline (_layouts/event.html) and the in-lab popover
(via the manifest + script IIFE) use the override when present and
fall back to lab.title otherwise. Separator switched from em-dash to
colon ('Lab 1: Agent Builder'). MCS in a Day is a copy of Bootcamp
Day 1 per the supplied schedule.

* agendas: update Bootcamp Day 2 schedule from supplied image

Differences from the earlier guess:
- Re-welcome/Re-cap no longer includes 'Quiz' in the title.
- Module 6 / 7 / 8 / 9 use their full titles instead of abbreviations.
- Lab 5 now carries an event title override ('Export / Import Solutions
  …') matching the image; Lab 6 is 'Component Collections'; Lab 7 is
  'Tools'.
- Lunch is 12:15-13:15; a new 'Quiz Time' slot sits 13:15-13:30 in
  parallel with the first 15 minutes of Module 9 (per the image, Quiz
  and Module 9 share the 13:15 start time).
- Module 9 - Tools runs 13:15-14:45 (90 min).
- Final slot is 'Open Q+A' not 'Open AMA'.

* agendas: update Bootcamp Day 3 schedule from supplied image

- Re-Welcome/Re-cap loses 'Quiz' from title (Quiz Time moves to its own slot).
- Module titles match image: 'Module 10 - Additional Resources',
  'Module 11 - Governance', 'Module 12 - SDK /w Demo',
  'Module 13 - Multi-Agent', 'Module 14 - Autonomous Agents',
  'Module 15 - Azure Integration /w Demo', 'Module 16 - Agent 365 w/ Demo'.
- Lab 8 carries short title 'Governance - DLP' (full lab title remains in
  _labs/mcs-governance.md). Lab 9 'Multi-Agent'. Lab 10 'Autonomous Agents'.
- Post-lunch slot renamed 'Quiz Time / Review' (13:00, 15 min).
- Closing slot 'General Q+A' at 15:30-16:00.

* ui: drop the TOC sidebar's internal scrollbar

The 'On This Page' sidebar had max-height: calc(100vh - 4em) plus
overflow-y: auto, which produced a second scrollbar stacked against
the main page scrollbar on long labs. Now that the TOC is flattened to
top-level headings only, it rarely exceeds viewport height; let the
page scroll handle it. Sticky positioning stays intact.
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.

2 participants