Migrate the latest Omarchy redesign to Astro with Cloudflare previews - #195
Open
ryanrhughes wants to merge 143 commits into
Open
Migrate the latest Omarchy redesign to Astro with Cloudflare previews#195ryanrhughes wants to merge 143 commits into
ryanrhughes wants to merge 143 commits into
Conversation
The redesign of omarchy.org, as this repository. Nothing about how the site is written, generated or served changes: a post is still a Markdown file rendered by bin/build-news and committed with the feed, the manual is still rendered from the Omarchy repository and committed, a theme is still a pull request that edits themes/index.html and adds a screenshot, and GitHub Pages still uploads a folder on every push to master. The app renders that folder from the files already here, and pages.yml gains one build step in front of the upload it already did. Every route is rendered at build time. Server functions run at build time and leave their results as static files, so nothing runs on a server. The plugin listing arrives with its default page rendered and does every filter, sort and page in the browser, over a trimmed catalogue fetched once. All 2,186 plugin pages are rendered by name. A /404/ route is written to 404.html, the file Pages serves for an unknown address. The site's own URLs are kept: a trailing slash on every page, news at its dated address. The feed, the installer scripts, the security contact, the assets, the images beside posts and chapters, and the pages not yet redesigned are laid over the output byte for byte. Two addresses folded into other pages, /manual/toc/ and /security/credits/, get redirect pages the way /discord/ always has. A scheduled workflow refreshes the plugin catalogue from the marketplace repository and commits it. Proven cold, from a fresh checkout with no environment set: npm ci, npm run build, npm run parity. Every one of the 571 addresses the site serves today answers from the build, and 480 of 481 files pass through byte-identical; the one that differs is 404.html, on purpose. Nothing is removed. The generated pages and the hand-written ones stay committed; the build reads them. Retiring any of it is a separate decision.
The 01-05 numerals next to each pillar were extra chrome. Titles and bodies stay, as a plain list.
Both faces come from Fontsource. Headings, card titles, and other display type use Geist Variable. Everything else uses JetBrains Mono Variable. Inter is gone.
The pull quote stays in the heading face. The name and role sit in the body face with the rest of the copy.
Heading and body stacks named only those two faces. Canvas labels on the plugin map do the same.
paintFavicon removed every rel=icon node, including the one React keeps in the route head. The next commit then called removeChild on a node with no parent. The tab icon is created outside React and only that tagged link is replaced.
The Inter helper was renamed in two steps and HMR left the old name in the mounted effect, which threw out of the document shell. The snap now inlines the Geist and JetBrains checks and swallows failures. The wordmark preload uses the same CORS mode as mask-image, which was discarding it. The root route also has an error page.
Home was waiting on the full search index, which parses the 3.5MB plugin catalogue, even though the palette loads that index when it opens. The build still writes the file from the plugins page. Fontsource was also registering Cyrillic, Greek and Vietnamese faces the site never uses; only Latin and Latin-ext load now, with font-display block so headings do not reflow. Home plugin teasers serialize as catalogue cards rather than full plugin records.
Vite was serving styles.css as JavaScript, and the SSR stylesheet was empty, so a reload showed unstyled markup until hydration. Importing the CSS as a module fills that sheet. The old site's stylesheets are kept out of it. Fonts use swap so copy is visible while they arrive. The pixel field and the router devtools load after first paint, and the video thumbnails no longer preload ahead of the wordmark.
The pinned reveal grabbed the wheel at the bottom and finished the scroll itself. The footer is now ordinary content after the page.
Six lattice cells is about 66px on a desktop row, which cannot hold "Install" in JetBrains Mono plus the button's horizontal padding. The label sat against the edges. Size the button from its label and keep the blended ghost the same width.
37signals is red, Basecamp gold, HEY purple, Cloudflare orange. The White theme uses deeper versions of those hues so 13px type still reads on the light field. The marks inherit the same color.
The name sat in a box that covered the preview. It is now just the letters, with a stroke in the page ground so they still read on the dimmer, including on White.
Fourteen and fifteen lattice cells were narrower than the labels in JetBrains Mono plus the large button's padding, so Get Omarchy and See it in action sat on the edges. Size them from the label. The 404 button had the same lock; it follows the label too.
The header already outlines keyboard focus. Footer chrome was hover-only, so tabbing through the same links on every page was invisible. Use the same 2px ring.
Iterate on the redesign
Without the plate, the name sat on the dimmer in the theme's text colour with a stroke in its background. Right way round for the four dark themes, wrong for Rosé Pine and White: dark letters on a dark dimmer, held up by a thin light edge. Now the lighter of the two inks fills the letters and the darker edges them, so the light themes get their own cream or white, edged in their text colour.
The partner marks in the footer take a darkened set on White and the true brand colours everywhere else. Rosé Pine defined none, so it inherited the dark-ground set: Basecamp's yellow at 1.3:1 on the cream. It now uses the same darkened set as White, 4.6:1 and up.
Five team photos on the teams page were circular crops with transparent corners while the rest were square. The redesign had square replacements parked in public/team and a swap in the porter that pointed the page at them, which stopped matching once the build read this repository's own files instead of the live site. Now there is one place. The five photos in assets/images/team are replaced with square crops of the same pictures, from the same profile photos, at the same 240x240 size and name. The six theme preview screenshots move from public/ to assets/images/theme-previews. public/team and the porter's swap go. public/ is left holding only what the build generates.
The bar has two copies of its labels: a blended ghost over the hero and the real layer, and one swap between them happens when the pointer nears the bar and another when the hero scrolls away. The real Install button wears a 1px transparent border; the ghost's placeholder for it did not, so the ghost's icons sat 2px right of the real ones and every swap read as a wiggle. The placeholder now wears the same border, and the two layers land on the same pixel.
The same Base UI primitive underneath, with the edge fade as a prop. It reads how far the content runs past each edge, which the viewport already publishes, so the fade follows the scroll in every browser rather than only in those with scroll-driven animations.
The chapter list and the column around it move to a layout route, so every chapter renders inside one shell. They used to belong to each page, and the manual's opening page and a chapter are two routes: every move between them rebuilt the list, which restarted its scrollbar's fade under a pointer that had never left it, and lost where the list was scrolled to. The list itself is a scroll area now. It keeps the page's own 3rem above and below it, its entries are one line each with an ellipsis for the long ones, and its scrollbar sits out by the rule rather than against the names. It also sticks at the offset it already sits at rather than at the bar, which used to move it the moment the page scrolled.
A chapter's prose was set to 65 characters inside an 800px column, so a third of the column stood empty beside every paragraph. Now one measure holds everything in a page's prose - paragraphs, headings, tables, code blocks and screenshots alike - and it is the width a news post reads at, named once as --measure so the column, the title and the prose cannot drift apart. The chapter sits at the page's right edge, so the room left over is in one place, between the sidebar's rule and the words. A heading jumped to lands where the chapter's title does rather than pinned against the bar; that offset is the manual's own, and elsewhere a jump still arrives at the bar.
They wore each partner's brand colour, which made four bright spots in a quiet corner. They are the page's text colour again, a shade above the sentence around them so they still read as links, and they brighten on hover. Every theme gets this from its own palette, so the marks are light on a dark page and dark on White and Rosé Pine. The partner colour tokens go with them; nothing else used them.
The two links carrying a partner mark were laid out as flex boxes aligned to the middle of the line, which put their words a little below the baseline the rest of the sentence sits on. They are plain inline text again, with the mark beside them as a glyph nudged onto that same baseline, so "37signals" and "Cloudflare" line up with the words around them.
The footer credited four partners and drew two of them. Basecamp comes in as one shape with an even-odd fill, so the peak and the slope stay as cut-outs in a single ink rather than flattening into a blob, and HEY as the drawn weight of its hand rather than the hairline of its pinned-tab icon, which went spidery beside the others at this size. Both take the page's text colour like the two already there, sit on the sentence's own baseline, and keep Cloudflare's gap between mark and word.
The three paragraphs under the wordmark sat at three different distances: 20px after the lede, 8px between the two credit lines. They are 16px apart now, the same as the space under the wordmark, so the column steps down evenly.
A column of five pillars ran down the right of this section, making in a list the points the sentences and the quote beside it already make. It goes: the page introduces the idea here, and the manual does the explaining. What is left is set across the page rather than stacked - the words on the left at their own measure, the quote holding the right edge across from them - so the section reads as one thought instead of a column with an empty half beside it.
Stamping a target on every outgoing link as soon as the page loaded changed the server HTML before React had taken it over, and every one of those links came up as a hydration mismatch. Now a click on a link to another host sets the target right before the browser follows it. Same result, nothing touched up front, and a modifier or middle click keeps its own meaning.
The refresh job now reads the Omarchy calendar on Luma into src/data/meetups.json: title, when, where, link, hosts, and the cover picture saved small under public/images/meetups. With a LUMA_API_KEY in the repository secrets it uses Luma's API, which is where the covers come from; without one it reads the calendar's public feed, which has everything else, so the page can be built and checked either way. The key is read and write for the whole calendar and Luma makes no read only kind, so it lives in the secrets only, and only the list is ever read. The first snapshot here is from the feed.
The download figure was quoted by hand from the news. The refresh job can now count it: each run asks Cloudflare, which serves iso.omarchy.org, how many bytes the ISO files sent on the days since the last count, divides by the size of the ISO, and adds that to the total, so the figure only moves forward and a missed run is caught up. A browser's one request and a download manager's many pieces count the same. Countries are the ones that took at least a whole ISO in a day. It needs CLOUDFLARE_API_TOKEN, a token that can only read that zone's analytics, and CLOUDFLARE_ZONE_ID, both repository secrets; without them the figure stays as it is.
Port redesign a91c428 to native Astro components, content collections, layered CSS, shared metadata, Markdown twins, and curated LLM endpoints. Preserve the content, public URLs, original assets, canvas, themes, music, and keyboard interactions. Update the static Pages deployment to dist/. Prerender named Storybook states for a usable static component library. Validate content and asset parity, site interactions, accessibility, and all Storybook stories in development and production.
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
omarchy | 26ee4e7 | Commit Preview URL Branch Preview URL |
Sep 07 2026, 02:05 AM |
| "robots.txt", | ||
| ]) | ||
| check( | ||
| read(path.join(root, endpoint)).includes("https://omarchy.org"), |
| } | ||
|
|
||
| voices.push(entry) | ||
| writeFileSync(DATA, JSON.stringify(voices, null, 2) + '\n') |
Comment on lines
+64
to
+66
| export const themeInitScript = `(function(){try{var t=localStorage.getItem('${THEME_KEY}');var ok=${JSON.stringify( | ||
| SITE_THEMES.map((t) => t.id), | ||
| )};if(ok.indexOf(t)<0){t=ok[Math.floor(Math.random()*ok.length)];localStorage.setItem('${THEME_KEY}',t)}document.documentElement.dataset.theme=t}catch(e){document.documentElement.dataset.theme='${DEFAULT_THEME}'}if(!document.querySelector('link[rel="icon"][data-theme-icon]')){var l=document.createElement('link');l.rel='icon';l.type='image/svg+xml';l.href='/brand/omarchy-logo.svg';l.setAttribute('data-theme-icon','');document.head.appendChild(l)}})()`; |
Port the current redesign's layouts, native interactions, content, and assets while retaining the Astro framework. Capture desktop and mobile layout references from source commit 8419a5a and run those regressions, motion tests, and Storybook checks in PR validation.
Comment on lines
+29
to
+30
| s | ||
| .replaceAll("&", "&") |
Comment on lines
+10
to
+11
| plainText(s) | ||
| .replace(/&/g, "&") |
| })); | ||
| await writeFile( | ||
| path.join(OUT, "plugins.json"), | ||
| JSON.stringify({ generatedAt: catalog.generatedAt, plugins }), |
| } | ||
| await writeFile( | ||
| path.join(PUBLIC_DATA, "explorer.json"), | ||
| JSON.stringify(explorer), |
| // wants it and a refresh never shows up as a formatting change. | ||
| await writeFile( | ||
| MOMENTUM, | ||
| await prettier.format(JSON.stringify(momentum), { parser: "json" }), |
| momentum.checked = day(new Date()); | ||
| await writeFile( | ||
| MOMENTUM, | ||
| await prettier.format(JSON.stringify(momentum), { parser: "json" }), |
| .slice(at + 1) | ||
| .replaceAll("\\n", "\n") | ||
| .replaceAll("\\,", ",") | ||
| .replaceAll("\;", ";"); |
Comment on lines
+71
to
+73
| )};var light=${JSON.stringify( | ||
| SITE_THEMES.filter((t) => t.light).map((t) => t.id), | ||
| )};var dark=${JSON.stringify( |
Comment on lines
+73
to
+75
| )};var dark=${JSON.stringify( | ||
| SITE_THEMES.filter((t) => !t.light).map((t) => t.id), | ||
| )};if(ok.indexOf(t)<0){var pool=window.matchMedia&&matchMedia('(prefers-color-scheme: light)').matches?light:dark;t=pool[Math.floor(Math.random()*pool.length)];localStorage.setItem('${THEME_KEY}',t)}document.documentElement.dataset.theme=t}catch(e){document.documentElement.dataset.theme='${DEFAULT_THEME}'}if(!document.querySelector('link[rel="icon"][data-theme-icon]')){var l=document.createElement('link');l.rel='icon';l.type='image/svg+xml';l.href='/brand/omarchy-logo.svg';l.setAttribute('data-theme-icon','');document.head.appendChild(l)}})()`; |
Port the latest page headers, Windows showcase, videos, meetup map and rails, and theme/menu transitions. Record the reviewed source commit and compare workflow for future updates, with source-derived content/layout fixtures and browser coverage.
| await page.locator("[data-carousel-video]").first().click(); | ||
| await expect(page.locator("[data-carousel] iframe")).toHaveAttribute( | ||
| "src", | ||
| /youtube-nocookie\.com\/embed\//, |
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.
Port
barisgirismen/omarchy-site:redesignthrough221b61bf912123f04dd8a53c3b5fc231d05393bfto ThinkOodle's Astro 6 starter framework. Public pages ship static HTML from native Astro components, typed content collections, and committed data snapshots. TypeScript preserves the interactive controls and original canvas/audio engines; styling uses native CSS layers and semantic tokens.Preserves the redesign discussed in #171, including its footer, header/menu transitions, parallelogram theme picker and frosted wipe, video glide/drag controls, persistent music, portrait clusters, testimonials, manual, news, and community pages. Imported HTML collection bodies render directly so indented source elements remain HTML. Important routes have Markdown twins, shared SEO metadata, LLM endpoints, and component Storybook stories. Meetups retain the country map, filters, linked pins, responsive cards, archive, and homepage rail. Astro's default page fade remains disabled.
The latest sync reviews 12 source commits since
bb6cdcf:article:published_time; search uses the phone keyboard's search action and announces result counts. The skip link matches the source styling.redesign-source.jsonrecords the full reviewed source hash and prior baseline. README documents the fetch/compare workflow. Reference fixtures come from the source checkout, never the Astro output, and validation checks that their hashes agree with the sync record. Builds use committed snapshots and do not fetch changing content.Wrangler builds and serves
dist/on the existingomarchyWorker, with preview versions for non-production branches. GitHub PR validation runs Astro, Cloudflare packaging, content/asset checks, Storybook, and browser regressions. Storybook owns copying public assets, avoiding a race with Vite's duplicate copy.Local validation:
Layout fixtures use loaded fonts, Tokyo Night, a fixed date, and reduced motion. Motion interactions run separately. Live service counts and meetup availability can differ between snapshot refreshes.
Cloudflare Workers Builds succeeded for
26ee4e7e0269bf33fe1eee99df85579de85c7596and deployed preview versiond68ce809-0373-43ce-83ad-c49a6a7a27ec. The branch preview passes all 32 live layout comparisons, meetup filters/pins, the phone header wordmark and section-edge backgrounds, menu blur, theme selection, search announcements, malformed hashes, article dates, and Markdown/RSS/assets. The GitHub Astro validation workflow also passes on this commit, including all 107 browser tests. This PR has not been merged or promoted to production.CodeQL remains red on
26ee4e7: Actions and JavaScript analyses pass, but default setup still includes Ruby after removal of the Ruby development server; the PR scan reports 13 findings (4 high, 9 medium), the same counts as before this sync. These require security review before merge. Scanning settings have not been changed. The Astro 6/Storybook dependency tree retains the previously documented upstream audit advisories.