Skip to content

Generate lab page blocks as markdown instead of Liquid includes - #237

Closed
ivorb wants to merge 4 commits into
gated-task-noticesfrom
prerender-lab-blocks
Closed

Generate lab page blocks as markdown instead of Liquid includes#237
ivorb wants to merge 4 commits into
gated-task-noticesfrom
prerender-lab-blocks

Conversation

@ivorb

@ivorb ivorb commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator

Recap of the problem

The lab instruction pages are read directly by platforms that never run Liquid. GitHub's own markdown renderer is one — passing a page through the GitHub markdown API returns:

<p>{% include lab-tasks-table.html lab='A' %}</p>

So anyone browsing a lab on github.com sees the raw tag where the task table should be.

No lab page on main has ever contained Liquid — only index.md. This restores an existing convention rather than inventing one.

How it works

tools/generate_lab_blocks.py writes both generated blocks as real markdown between HTML comment markers, which are invisible in every renderer:

<!-- BEGIN GENERATED: task-table - do not edit by hand; run: python tools/generate_lab_blocks.py -->
| Section | Task | Level | Time |
| **Core** | [Task 1 – Create and ground an agent](A1-create-and-ground-an-agent.md) | ▰▰▱▱▱ L200 | ~15 min |
...
<!-- END GENERATED: task-table -->

Two blocks are generated: the task table on each landing page, and the access notice on each gated task.

Frontmatter stays the single source of truth. --check runs in content-checks and makes drift a build failure — the same pattern as Labfiles/_shared/sync.py --check. A lab page missing its markers is a hard error, so a new lab can't silently ship without its table.

_includes/ is removed entirely. The web-only pages — workshop.md, explore.md, labs.json — keep Liquid, because they're never consumed as raw markdown.

Two problems this fixes beyond portability

Link checking is back to 117 references, from 103. The 14 task links became literal markdown again, so check_links.py can see them. That regression was introduced when the tables became generated.

The cross-branch dependency disappears. Lab D's landing page referenced an include that existed only on the PoC branch — it would have failed the site build, and is why #234 is currently a draft. With generated markdown there's nothing to depend on.

Verification

  • Generated output matches what the Liquid produced, including the totals — lab A still reports 35 minutes core and 2 hours 25 minutes full
  • The generator is idempotent: a second run reports 0 files updated
  • Negative test: corrupting a block makes --check exit 1 with a diff, and re-running the generator restores it
  • All Tier 0 checks pass; the three web views still render

Stacking

Branches off gated-task-notices (#233), because it converts both includes — the table from #231 and the notice from #233. Merge order: #231#233 → this.

If you'd rather not merge an "add include, then replace it" sequence, I can fold this back into #231 and #233 so they ship pre-rendered from the start. Say the word — it's mechanical, but it means force-pushing two PRs you may already have reviewed.

ivorb and others added 4 commits August 14, 2026 14:34
Per-task section, difficulty and duration previously existed only as prose in
the hand-written "Lab at a glance" tables on the three landing pages. Nothing
validated them against the task pages they described.

Moves that data into task frontmatter (type, parent, order, section,
difficulty, duration, access) and replaces the hand-written tables with an
include that generates them, so the tables can no longer drift.

Also:
- Corrects level on six task pages. Every page carried level 300 inherited
  from its lab, but the tables listed per-task levels of L200-L400. Level now
  matches difficulty on every task.
- Drops islab from task pages. Tasks are steps within a lab, not labs.
- Marks B2, B3 and B4 as access: gated, each with what it requires and a
  quick way to check, since they need tenant rights or a Copilot licence.

Verified by rendering the include against the real frontmatter: row counts and
core/full totals match the values the tables previously stated by hand
(A 35/2h25, B 35/1h50, C 30/2h).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dc4690cc-57d6-48a7-abc8-bc8570113ab1
Three surfaces over the same content, each generated from the frontmatter of
the pages under Instructions/Consolidated. Nothing is hard-coded and there is
no generated file to keep in sync: add a task page and it appears in all
three.

- workshop.html - instructor-led core path, ordered by lab then task, with
  cumulative elapsed time, optional tasks listed per session, and gated tasks
  called out as demo-only with what they require.
- explore.html - every task as a card, filterable by lab, section, level,
  duration and access.
- labs.json - the same catalogue as a machine-readable endpoint, for anything
  that needs the data without scraping the pages.

index.md is untouched: it still filters to /Instructions/Exercises, so the
published lab list is unaffected.

Verified by rendering all three against the real frontmatter. Totals match the
values the labs previously stated by hand - A 35/145 min, B 35/110, C 30/120 -
and labs.json parses as valid JSON.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dc4690cc-57d6-48a7-abc8-bc8570113ab1
Tasks B2, B3 and B4 need permissions or licensing many learners don't have,
but each buried that below the setup instructions. B4 was the worst case:
it told you to create a Foundry project, clone the code and run a preflight
check, and only then mentioned that the whole task needs a Microsoft 365
Copilot licence.

Adds _includes/gated-notice.html, rendered from the page's own frontmatter
and placed directly under the title on all three, so a learner who can't do
the task finds out in about thirty seconds instead of after provisioning.

The notice states what the task needs, how to check, and - importantly -
that skipping it costs nothing, because nothing else in the lab depends on
these tasks.

Also corrects the metadata to match what the pages actually say. B3 recorded
'permission to publish in your tenant' when the page asks for a Copilot
licence, and B4's Node.js requirement existed only in prose. Splits the check
into �erify and �erify_command so a shell command renders as code rather
than being guessed at.

Verified by rendering the include against all 30 consolidated pages: it
appears on exactly the 3 gated ones and nowhere else.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dc4690cc-57d6-48a7-abc8-bc8570113ab1
The lab instruction pages are read directly by platforms that never run
Liquid. GitHub's own markdown renderer is one: passing a page through the
GitHub markdown API returns

    <p>{% include lab-tasks-table.html lab='A' %}</p>

so anyone browsing a lab on github.com sees the raw tag where the task table
should be. No lab page on main has ever contained Liquid - only index.md -
so this restores an existing convention rather than inventing one.

tools/generate_lab_blocks.py now writes both generated blocks - the task
table on each landing page, and the access notice on each gated task - as
real markdown between HTML comment markers, which are invisible in every
renderer. Frontmatter stays the single source of truth, and --check makes
drift a build failure, in the same pattern as Labfiles/_shared/sync.py.

A lab page missing its markers is an error, so a new lab cannot silently
ship without its table.

Removes _includes/ entirely. The web-only pages - workshop.md, explore.md,
labs.json - keep using Liquid directly; they are never consumed as raw
markdown.

Two things this fixes beyond portability:

- Link checking is back to 117 references from 103. The 14 task links became
  literal markdown again, so check_links.py can see them.
- The cross-branch dependency disappears. Lab D's landing page referenced an
  include that existed only on the PoC branch, which would have failed the
  site build and is why that PR is currently a draft.

Verified: generated output matches what the Liquid produced, including the
totals (A 35 min core / 2 hours 25 minutes); the generator is idempotent;
and corrupting a block makes --check fail with a diff.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dc4690cc-57d6-48a7-abc8-bc8570113ab1
@ivorb
ivorb force-pushed the gated-task-notices branch from a19c23a to 7410980 Compare August 17, 2026 14:34
@ivorb

ivorb commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator Author

Folded into #231 and #233 - both now ship pre-rendered markdown from the start, rather than adding a Liquid include and replacing it in a follow-up. Same final content, verified byte-identical to this branch.

@ivorb ivorb closed this Aug 17, 2026
@ivorb
ivorb deleted the prerender-lab-blocks branch August 17, 2026 14:34
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.

1 participant