Repository navigation
feat(blog): migrate every Medium post onto the site and retire blog.dddperth.com - #209
Merged
Merged
Conversation
JakeGinnivan
force-pushed
the
jakeginnivan/migrate-medium-blog-posts
branch
from
October 28, 2026 00:17
eea135c to
03469de
Compare
Brings across all 37 posts from the DDD Perth Medium publication (medium.com/ddd-perth, formerly blog.dddperth.com) and the @dddperth account, 2017-2025, with their 86 images downloaded locally under /images/blog/<slug>/. Converted from Medium's structured post data rather than scraped HTML, so emphasis, links, lists, quotes, captions and alt text carry across exactly. Links between posts are rewritten to their new /blog/ URLs. Embedded tweets and videos become a quote plus a link instead of an embed, so the build never fetches from a third party. The two posts already on the site are regenerated under their existing slugs, so their URLs don't change. The copy on main of the 2023 AGM post had picked up a code block the original never had; the regenerated post matches Medium. Adds the ten missing authors to authors.yml, since posts by an unlisted author render with no byline. Supersedes #97.
The blog baselines captured the conference's real blog, so every new post moved `blog index` and the `blog post` baseline was pinned to a real post. `e2e/start-dev-server.mjs` now sets E2E_BLOG_FIXTURES, and vite.config.ts then compiles three synthetic posts and authors from e2e/fixtures/blog/. Images are inline data URIs so nothing fixture-only ships from public/. A build with the flag set is refused. The authors YAML moves from an @conference/content alias import to a `virtual:blog-authors` module from the mdx-bundles plugin, so posts and authors come from the same build-manifest paths and swap together. Also sorts blog posts newest first. The bundle index lists them in directory order, which is oldest first, so the index featured the oldest post; and RSS items now carry their own date instead of the first post's.
A manifest `blog.legacyHosts` list names hosts the blog used to live on. A request for one is answered with a 301 to the canonical domain: to the post whose `legacyPaths` frontmatter lists the path, or to /blog. The host is read from X-Forwarded-Host, then Forwarded, then Host, so it still matches behind a CDN. The target is always absolute, because a relative Location would resolve against the legacy host and loop. Every migrated post lists its Medium paths (/<uniqueSlug> and /p/<id>). Also stops the oEmbed transformer fetching oembed.com's provider list for one-word paragraphs. remark-embedder offers "Treasurer" as https://treasurer/, so any post with a one-word list item (several AGM posts, and "Enjoy!" on main) made the build need oembed.com. Adds the fixture blog posts to the production-build trace test.
Adds Blog to the header nav and drops the footer's Medium link, which pointed at blog.dddperth.com. Six links plus both CTAs measured 1050px wide at a 1024px viewport even before Blog (with five links), so the nav gap and logo tighten between the lg and xl breakpoints. 1280px and up is unchanged.
Ten images had no alt text on Medium: four charts in the 2018 CFP numbers post, three hero images and three slides. Each now has a description, and the charts' alt text carries their data. 24 summaries were Medium's automatic excerpts, cut off mid-sentence with "…". Each is replaced with a complete one- or two-sentence summary of the post. Summaries the authors wrote themselves are unchanged.
…ooter The blog index and blog post baselines now render the fixture posts. The home tablet baselines lose the footer's Medium icon, so the social row no longer wraps.
The header is now Agenda, Blog, Sponsors, About. Sponsorship moves out of the header: the sponsors page for the current year gets a "Sponsor DDD Perth" button, and the footer keeps its "Become a sponsor" link. A new `features.venueReplacesAboutNearConference` flag shows Venue instead of About from midnight a week before conference day until the day ends; About the rest of the year. Off by default, so other forks keep Venue beside About whenever a venue is published. The placement is worked out in the layout loader from the date provider, so the admin and e2e date overrides move it too. The venue name in the home page hero now links to /venue.
Titles come from the team page and the posts themselves: Aidan was Secretary, Amy and Michelle are directors, Matt Ward signed the 2022 post as Treasurer, and the 2018 team post credits Rob Moore with founding the conference. Allen, Ashley and Rob Crowley were committee members with no named role. David Meacock and Jaimee Nobbs stay Contributor until their roles are confirmed.
The team page only covered parts of 2024 and 2025. Each year now has its full committee as elected at that year's AGM (minutes in the DDD WA Inc. Committee SharePoint), from the first AGM in November 2017 through 2025. Before 2022 the committee had no director roles, so those years list the office bearers and then the committee members. 2022 includes the three directors the committee appointed after the AGM to fill vacancies (Logistics, AV Solutions, Media), per the March 2022 committee minutes. Two author titles change to match: Rob Crowley was Director of Sponsorship in 2022-23, and David Meacock was a member in 2023.
Adds an avatar size to the MDX Image component: a 32px round headshot that sits inline before a name, with a negative block margin so rows with and without a photo stay the same height. Each year's committee is now a table, including the pre-2022 committee members that were a plain list, so every name can carry a photo. Nineteen people have one, from the team photos and the blog author avatars. Allen Azemia's author avatar is Medium's default placeholder, so his rows have none. Also fixes a hydration error the page already had. The bios wrapped indented text in literal <p> tags, which MDX turns into a paragraph inside the <p>. They are plain Markdown paragraphs now. Vicki's 2025 role is Director of Logistics, as confirmed.
…for overflow The header was only covered by the full-page home baseline, where the 2% ratio tolerance absorbed a nav-link change entirely. It now has a #header-scoped baseline (9 images), and a sweep that resizes from 320 to 1600px in each header state (CTAs and the Venue swap change its width), once per engine in the container, and fails if the page scrolls horizontally. HEADER_DATE_STATES in e2e/routes.ts is fork-owned. The sweep's first run found two overflows already on main: - Mobile header with CTAs: the logo sat in a bare 1fr track that can't shrink below 110px, so the row overflowed at 320-340px, and again at 460-488px where the secondary CTA appears. The track is now minmax(0, 1fr) and the logo is fluid up to 110px. It still renders at 110px at the 390px baseline width, so no existing baseline moved. - Content pages on phones: the same bare-1fr grid let a code block's min-content width stretch the column to 738px, so any page with a code block scrolled sideways. The old mobile content-page baselines had the clipped text baked in; regenerated (3 images). Full pnpm vr: 108 passed.
Arbitrary [px] values bypass the token system, so downstream forks can't retheme them. The logo now uses sizes.28/36/44 (112/144/176px, from 110/150/180px), with maxWidth matching width at lg/xl so no [none] escape is needed. The hero venue link's underline props have no token category, so they take plain CSS values.
main's new dates/use-conference-date-time lint rule rejects raw DateTime.fromISO with a zone. The provider's nowDate() is already in the conference zone, so its setZone was redundant too.
…ize steps main's size scale now carries sizes.27.5/37.5/45 (110/150/180px), so the logo no longer has to round to 112/144/176px to avoid bracket escapes. The header baselines were captured at the original widths.
…s zone The window was computed in now's zone, so a provider returning UTC would show Venue until midnight UTC, 8 hours into the day after the conference.
#211 removed luxon and conferenceDateTime from core/website. The rebase was textually clean but left header-nav and _layout referencing both. The window now runs from conference-zone midnight a week out, inclusive, to the next midnight, exclusive.
JakeGinnivan
force-pushed
the
jakeginnivan/migrate-medium-blog-posts
branch
from
October 29, 2026 00:15
03469de to
87f9555
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Moves every post from both Medium pages onto the site and retires
blog.dddperth.com. Supersedes #97.medium.com/ddd-perthis the publication (blog.dddperth.com, now a 502). It has 35 posts from 2017–2024.medium.com/@DDDPerthis the account. It has 2 posts of its own: "Hello World!" and the 2026 AGM announcement.Old-host redirect. It runs in the Worker before anything path-based:
The target is absolute on purpose. A relative
Locationwould resolve againstblog.dddperth.comand loop. The DNS change is still needed: pointblog.dddperth.comat the Worker.Blog content in e2e/VR is now fixtures.
start-dev-server.mjssetsE2E_BLOG_FIXTURES, and the suites then render 3 synthetic posts frome2e/fixtures/blog/. Publishing or editing a real post no longer moves theblog indexandblog postbaselines. A build with the flag set is refused, and the production-build trace test asserts no fixture post ships.Header nav.
/sponsorspage gets a Sponsor DDD Perth button, and the footer keeps Become a sponsor.features.venueReplacesAboutNearConferenceflag shows Venue instead of About from midnight a week before conference day until that day ends, and About the rest of the year. Other forks keep the old behaviour by default./venue.Team page.
/teamnow lists every committee from 2018 to 2025, taken from the AGM minutes in the DDD WA Inc. Committee SharePoint. 2022 also includes the three directors appointed after the AGM. Each year is a table, with a photo beside the 19 people we have one for, using a newavatarsize on the MDXImagecomponent in core. Blog author titles come from the same minutes. This also fixes a hydration error the page already had: the bios wrapped text in literal<p>tags, which MDX turned into<p>inside<p>.Header VR and an overflow sweep. The header had no real coverage: a nav-link change passed every
homebaseline under the 2% tolerance. It now has a#header-scoped baseline (9 images). A new sweep resizes from 320 to 1600px in each header state (HEADER_DATE_STATES), once per engine, and fails if the page scrolls horizontally. Its first run found two overflows already onmain, both fixed here:1frtrack can't shrink below 110pxminmax(0, 1fr); the logo is fluid up tosizes.281fr: the code block stretched the column to 738pxminmax(0, 1fr); the code block scrolls inside itselfThe old mobile content-page baselines had the clipped text baked in, so those 3 are regenerated.
Smaller fixes found on the way:
oembed.com/providers.json, because remark-embedder treats "Treasurer" ashttps://treasurer/.mainalready did this for "Enjoy!" in the chairperson report.lgandxl.Content: Internal links are rewritten to
/blog/<slug>. Tweets and YouTube embeds became quotes and links, so the build never fetches them. Ten images had no alt text on Medium and now have written descriptions; the charts' alt text carries their data. 24 summaries that were Medium's truncated "…" excerpts are rewritten.Evidence
Redirect (dev server,
X-Forwarded-Host: blog.dddperth.com):Unit tests:
legacy-blog-redirect.server.test.ts(7 tests).Nav by date:
header-nav.test.ts(7 tests), plus curl against the dev server with the date-override cookie (table above).Header at 1024px, CFP open and tickets on sale (date override
2026-06-08):Before: page 1050px wide, so it scrolls horizontally, with 5 links.
After: 1024px wide with 6 links, before Sponsorship came out; it's 4 links now. 1100, 1279, 1280 and 1440px all fit too.
Sorting:
mdx-content-service.server.test.tschecks posts come out newest first. Before, the index led with the 2017 "Hello World!".Fidelity: all 1,223 text paragraphs from Medium are present in the output, and every image and avatar path exists on disk.
Checks:
nx build website✅.tsc --noEmit✅.vitest: 843 passed ✅.nx lint website: 0 errors ✅.VR: regenerated in the container with
pnpm vr --update-snapshots:blog index/blog postbaselines, which are now fixtures;hometablet baselines, because the footer loses its Medium icon and the social row stops wrapping (24px shorter).A full
pnpm vragainst the new baselines: 78/81 passed. The 3 failures were WebKit-mobilepage.gototimeouts under parallel load, not pixel diffs; one was the talk-detail page, which this PR doesn't touch.pnpm vr --project visual-webkit-mobile --workers=1passes 9/9. The nav change onhomedesktop stays within the 2% tolerance, so it needs no new baseline.Merge Danger
Door: two-way
Reverting restores the old state. The one thing a revert can't undo is the DNS change, but that's made separately.
Blast Radius: site-wide-chrome
main.