Skip to content

feat: cut over educates.dev to the redesigned site - #55

Merged
jorgemoralespou merged 218 commits into
mainfrom
develop
Oct 8, 2026
Merged

jorgemoralespou merged 218 commits into
mainfrom
develop

Conversation

@jorgemoralespou

Copy link
Copy Markdown
Contributor

Summary

Cutover: publishes the redesigned educates.dev (#54) on the live domain.

merge into main
  deploy.yaml (push to main)
    npm ci, astro build, site check
    upload Pages artifact, deploy to educates.dev

Also carries "Updating dependencies" (71352bf), which was on develop but never reached main; the redesign replaces the files it touched.

Evidence

  • Before: npm run url-check -- https://educates.dev (Docusaurus): 98 of 111 URLs resolve.
    After (expected once deployed): 111 of 111 URLs resolve, as the same build already does in the serve image. Checks on feat: redesign educates.dev on Astro #54: site check 0 errors, 0 warnings with the live sitemap comparison, 374 unit tests, lychee 0 errors, Lighthouse assertions passing on 5 URLs.

Merge Danger

Door: two-way

Rollback: revert this merge commit on main (git revert -m 1 <merge>) in a pull request, merged as an administrator, since the old workflows never report the required "Checks". The deploy then publishes the Docusaurus site again. Once the new site is live, Google Analytics stops receiving data and GoatCounter starts counting; a rollback would load Google Analytics again.

Blast Radius: site-wide

Every page of educates.dev changes. Old URLs keep working as pages or redirect pages. The Pages custom domain and HTTPS setting are unchanged.

Define the site's domain language: Content and its kinds, Featured
Content, the Content hub and Topics, Features and use cases, and the demo
vocabulary (Demo Platform, Session, Demo, Hands-on event and the Sponsor,
Builder, Presenter and Attendee roles).
ADR 0001 chooses plain Astro 7 for educates.dev. ADR 0002 keeps the
Docusaurus URL form (no trailing slash, page.html files) and the blog
feeds' item identity.

The Docusaurus docs plugin publishes everything under docs/, so its
exclude list now adds adr/** to the plugin's defaults, and ADRs never
become pages while Docusaurus still builds the site.
The Hub will become a section of educates.dev, so the site cannot have two hubs. The glossary defines Educates Hub, and the page that lists all Content becomes the Learn page.
Click-to-enlarge on diagrams is a small inline script, so React islands are limited to the Asciinema player and the Learn page's filters.
Build educates.dev with Astro 7 and npm instead of Docusaurus and Yarn,
on Node 26 pinned through Volta with engines.node at >=24. Pages build
as <path>.html with no trailing slash, as ADR 0002 records. Prettier
with its Astro plugin formats code files only.

The kept assets move from static/ to public/, with a root favicon.ico
added; CNAME, the unused Featured Content images and customer logos,
and the old screenshots are dropped. The Content directories stay in
place, unbuilt.
npm run build now runs the site check over dist/ after astro build. It
reads the output as GitHub Pages serves it: /page from page.html, /dir/
from dir/index.html, case-sensitive. The committed must-resolve list
holds the 67 pages of the old sitemap, the 404 page, both feeds and
their stylesheets, the crawler and host files, the kept assets, the 24
feed images and the redirect sources; an entry the build does not
serve is a warning for now. The command exits 1 when a rule reports an
error.
Every page needs one canonical URL and a matching og:url, both the
absolute https://educates.dev URL the page is served at, without .html,
/index, a trailing slash, a query or a fragment. A page built as
<path>/index.html fails too, since GitHub Pages serves it only with a
trailing slash. Redirect pages are skipped.
@astrojs/sitemap always writes an index and numbered parts, so a build
step renames sitemap-0.xml to sitemap.xml and deletes the index. The
site check fails when the sitemap is missing, split, or lists a URL
that is not a page in the site's URL form: a redirect page, the 404
page, a file or a URL the build does not serve.
The tokens are CSS custom properties named by role in one file,
src/styles/tokens.css, with direction D's light and dark values plus the
Session dashboard's product colors, under :root and the theme attribute
only. src/styles/tokens.ts reads the same file, so the light values are
available to JavaScript.

The theme follows prefers-color-scheme until the visitor uses the
toggle, which sets data-theme on the root element and remembers the
choice in localStorage; an inline head script applies a stored choice
before first paint. Blocked storage leaves the page on the system
theme, and the toggle is hidden without JavaScript.
BaseLayout writes the title with the site name, the description, the
canonical URL and og:url built without .html or /index, the Open Graph
basics, the site-wide JSON-LD and the page's section label as
data-section on <body>. It loads Onest and JetBrains Mono at 400, 600
and 700 from the site itself, the tokens and the theme script, and
wraps the page in a skip link, a minimal header with the theme toggle
and <main>. The placeholder homepage carries the hero copy; the 404
page links home and to the Learn page.
The workflows build with npm on Node 26 and upload dist/. The Dockerfile
has a dev target running the Astro dev server and a serve target
serving the build with nginx, for the host architecture. The README
lists the npm commands and the site check.
The serve target builds the site, runs the site check and serves dist/
through nginx with GitHub Pages' URL behavior: /page serves page.html, a
URL ending in a slash serves only its directory's index.html, a directory
URL without the slash redirects to add it, misses get the site's
404.html, and the feeds and their XSL stylesheets are typed as XML. The
dev target runs the Astro dev server over a mounted checkout, keeping the
image's Linux dependencies in an anonymous volume.

npm run docker-build builds a target as educates-dev:<target> with Node
at the Volta pin, for the host architecture unless TARGET_PLATFORMS lists
platforms.
The checks workflow, on pull requests to develop and main, replaces the
test build: astro check, the Prettier check, the build, the site check,
the unit tests, lychee offline over dist/ (a broken internal link fails
it) and Lighthouse on a mobile profile, the median of three runs, on the
homepage, a use case page, a Feature deep page, a blog post and /learn.
Accessibility or SEO below 90 fails; performance below 90 warns. Each
check runs even when an earlier one fails. Node comes from the Volta pin.

lychee.toml holds the link settings both link checks share, and
npm run link-check runs the internal check locally.
Build main with withastro/action@v6, which runs npm run build, the site
check included, at the Node version of the Volta pin, and publish it
with actions/deploy-pages@v5. Only the deploy job gets the Pages and
ID token permissions, and deployments do not overlap.
Every Monday, and by hand, build the site and run lychee online over its
http and https links, leaving educates.dev itself to the internal check.
Dead links open or update the one "Dead external links" issue; a clean
run closes it. The run never fails on a dead link, only when lychee
itself fails.
Dependabot groups the minor and patch updates of each ecosystem into one
pull request and gives each major update a pull request of its own, so a
new Astro major is never missed. Commit messages follow Conventional
Commits: build(deps) and build(deps-dev) for npm, ci(deps) for actions.
The README lists npm run link-check and npm run docker-build, explains
the serve and dev targets and TARGET_PLATFORMS, and summarizes the
checks, deploy and external links workflows and Dependabot.
Each link uses GitHub's latest-release download URL, so the Downloads page
stays current with every platform release.
The project pages and the homepage strip share one call to action that
follows the site's Sponsors setting.
jorgemoralespou and others added 28 commits October 7, 2026 21:09
…3.8.0 runs

The 3.8.0 CLI creates its Kind cluster with Kind v0.32.0's default node
image, kindest/node:v1.36.1, so the package repositories move from
v1.31 to v1.36 and the sample output shows kubectl v1.36.5 with its
bundled Kustomize v5.8.1.
The install commands fetch the latest release, which is 3.8.0, and its
`educates version` prints only the version number.
The 3.8.0 CLI sets no node image, so Kind v0.32.0 uses its default,
kindest/node:v1.36.1. The config path now prints on one line, as the
CLI's Println does, and the status lines keep the leading space Kind
prints them with.
3.8.0 installs Kyverno v1.15.1, which ships no report cleanup CronJobs,
and Contour v1.30.2, whose certgen Job carries that version in its name.
Its ClusterPolicy columns no longer include VALIDATE ACTION, and the
baseline set no longer has restrict-apparmor-profiles: sixteen
policies in all.
…ates

The hugo template now writes a security.token block under the Session's
namespaces and indents its lists as the template does, so the example
matches the file a reader opens. The examiner lines move to 28-29.
List, page by page, the versions and sample outputs in the Getting
Started Guides that follow an Educates release, where each comes from at
the release tag, and how to refresh them for the next release and for
4.0.
Add an Example kind to a use case's proof links, for something built on
Educates outside the project's own publications, and list Graham
Dumpleton's labs and a Next.js front end on the lookup service as
examples to study on the Demo Platform page.
List his public labs, self-paced courses hosted on Educates, as an
example in the Customer and partner enablement proof.
A What you bring point can name an example built on Educates, by its
link text and URL, shown after its docs link. The Demo Platform's front
end and sign-in points link the Apache 2.0 OAuth front end on the lookup
service as a place to start.
Customer and partner enablement and Team training both leave sign-in
through your identity provider to a front end of yours; link the Apache
2.0 OAuth front end as a place to start.
A workshop, Educates Feature Tour, whose pages each set up a screenshot
or recording of the Features pages: clickable actions, examiner checks,
the editor, console and slides, the Session's Docker daemon, registry
and Git server, its namespace and quota, the portal REST API, the lookup
service, workshop definitions, the Terraform modules' usage and a course
written with the AI authoring skills. Two more definitions run pages of
the same content on a virtual cluster and with no Kubernetes access.

Example Academy is a small front end of the kind a training team builds:
it lists workshops from the lookup service and starts Sessions, embeds
the training portal, receives its analytics events, and runs an examiner
check from outside a Session.
Every Feature on the overview gets a screenshot, and each deep page its
loop and a screenshot for each thing, with alt text describing what each
shows: 51 screenshots and 6 muted loops, taken from Educates 4.0 as
built from develop, next to their entries.
A manifest names every screenshot and loop on the Features pages: the
Feature entry and slot it fills, where it is taken, the steps that set
it up, its window and its alt text. The tool drives Chrome through
puppeteer-core against a training portal of its own, records loops
through the DevTools screencast into muted H.264 MP4 with a poster,
renders terminals on this machine with asciinema-player, and wires each
capture into its entry. Its setup and teardown deploy and remove the
capture workshop, the lookup service configuration and Example Academy.

Tests check that the manifest fills every visual slot with one shot of
its kind and that every shot is wired into its entry.
The header gains a Project menu with Get started, Get help, Community and
Downloads, sharing its links with the footer's Project column. The phone
sheet lists it with the other menus.

Four menus no longer fit beside the header's links below 1024px, so the
phone menu now replaces the header menus from 1023px down (PHONE_MENU),
while the page layouts keep switching at 960px (PHONE_LAYOUT).
Copy and source corrections from the review of every drafted page:

- Homepage: a shorter hero caption, new frame captions, and "Ways to get
  help" on the strip's help link.
- Use cases: the SpringOne 2020 figure, sourced to VMware's "Open
  Sourcing Educates"; the academies fact cites ADOPTERS.md; Viam
  Education and the NWS Playground as named examples; portals pick a
  workshop's pathway; Team training's sign-in goes through the lookup
  service, as the front-end example's title now says.
- Features: Built-in services offers injected SSH key pairs for remote
  machines; examiner checks and local authoring headlines no longer say
  "learner" or overclaim; alt texts speak of the Session or a person;
  "Limits" as the deep page heading; the AI page pins skill version 4.1
  and names only the authoring skill's releases.
- History: SpringOne 2020, the Learning Center fork and the November 2023
  acquisition, each with its source, and all five adopters named.
- Get help starts with the docs and names the #educates channel; Learn's
  intro, the PyCon AU description and the Working locally topic; the
  privacy page no longer lists language among what is counted.
Chrome Stable stops running XSLT on 2026-11-17, and Firefox and WebKit
plan to follow, so the styled feed pages would break soon after launch.
The RSS and Atom feeds keep their URLs and entries but no longer link a
stylesheet.

The XSL files, the CSS routes that served them, and their shared
stylesheet module go. The must-resolve list no longer requires the four
stylesheet URLs, which only the feeds ever linked; the list header and
ADR 0002 record the exception. nginx serves only .xml files as XML.
Air-gapped install, shown once Educates 4.0 is released, gets its visual:
a terminal listing the digest-pinned image list the 4.0.0-alpha.10
release publishes, kept as a fixture.

The capture tooling changes to take it:

- 4.0-only Features have visual slots, so `captures check` and the
  manifest test expect their shots before the 4.0 setting turns on.
- The capture portal and Example Academy are looked up when a shot first
  needs them, so terminal shots run without the capture setup.
- A terminal wider than the default 100 columns gets a smaller font in
  proportion, so its window still fits the capture window.
A Feature's docs link that points at GitHub opens a repository README, so
"Read the docs" promised something it did not deliver. One rule,
docsLinkLabels in src/lib/features.ts, now labels every Feature docs link
by where it points: a GitHub link reads "Read the README" on the deep
page, "README" on the Features overview and "What the README says" under
a limit; any other keeps "Read the docs", "Docs" and "What the docs say".
ArrowLink laid out its label and icon with inline-flex, so a label that
wrapped, such as a use case page's long Example title, filled the line
and pushed the arrow or external mark to the column's far edge.

The link now flows as text. A word joiner before the icon, in a span that
does not wrap, keeps the icon on the label's last line, 6px after the last
word, and never alone on a line. The rendered label is trimmed, so a label
written on its own line leaves no space before the icon. Unwrapped links
render as before; only the focus ring of a link in running text is now
the text's height rather than the line's.

Screen-reader context after a label, such as the title after "What the
docs say", moves into a screenReaderContext prop, so no caller can leave
a line break, and so a space, between the label and the icon.
The KCD Spain 2023 talk has a Spanish title that screen readers read as
English. An outside entry can now name its language with an optional
`lang`, a BCP 47 tag such as `es`, and the talk sets `lang: es`.

Wherever the entry's card shows, on the Learn page, in Featured Content
and in a post's Related Content, the title is wrapped in a span with that
`lang`. The " (external site)" note for screen readers stays outside it,
in English. Entries without a `lang` render as before.

A `lang` that is not a well-formed BCP 47 tag, such as `es_ES`, fails the
build; `isLanguageTag()` in src/lib/outside-content.ts checks it.
Related Content took the first three entries sharing any Topic in the
Learn page's order, newest first, so a post showed recent entries that
had little to do with it: the cloud provider install posts listed
unrelated recent posts, and "Your first workshop" listed AI posts.

relatedEntries() now puts the entries sharing the most Topics with the
post first, and among those, the closest in date before or after it. An
undated entry, such as a guide, counts as farther than any dated one, and
remaining ties keep the Learn page's order. The parts of the post's own
series are left out, since its series box lists them; a Content entry
now carries its post's series for that. A post with fewer than three
candidates shows what there is, and one with none shows no section.
Two visuals on the Lookup service deep page did not show what their text
describes. "Your own front end on one portal" showed a terminal calling
the portal's REST API, and "A tenant for each customer" showed the
tenant configuration in the editor.

The first now shows Example Academy's catalog for the person signed in,
built on one training portal's REST API with its robot account alone:
each workshop with the Sessions it has free, and Resume on the one they
already have a Session for. The second shows two customers' own sites
side by side, Acme Training and Globex Academy, each logging in to the
lookup service with a client granted only its own tenant, and listing
the workshops of its own portal.

To take them, the capture setup creates a portal, a tenant and a client
for each customer, and passes the portal's robot account and the
customers' clients to Example Academy. Example Academy serves the
catalog at /portal and the customers' sites at /customers/<id>. The
capture tool takes two pages of Example Academy side by side, and gives
a site shot that starts a Session a browser of its own. The terminal
and editor shots these replace are gone; their setup steps move to the
next shots of the same Session.
The privacy page now says Google Analytics ran until the cutover, 2026-10-09, and its last-updated date matches. The site check no longer warns about the cutover date placeholder.
feat: redesign educates.dev on Astro
@jorgemoralespou jorgemoralespou changed the title Cutover: publish the redesigned educates.dev feat: cut over educates.dev to the redesigned site Oct 8, 2026
@jorgemoralespou
jorgemoralespou merged commit cbedc34 into main Oct 8, 2026
4 checks passed
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