feat(build): upgrade to Astro 7 and Tailwind CSS 4 - #2315

Open
isaque-bock-azion wants to merge 18 commits into
mainfrom
feat/astro7-tailwind4
Open

feat(build): upgrade to Astro 7 and Tailwind CSS 4#2315
isaque-bock-azion wants to merge 18 commits into
mainfrom
feat/astro7-tailwind4

Conversation

@isaque-bock-azion

@isaque-bock-azionisaque-bock-azion commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

What

Base branch for the docs modernization stack (MM-15897). Two commits, one per migration:

  1. Tailwind CSS 3 → 4 — removes the deprecated @astrojs/tailwind integration (capped at Astro 5). New CSS-first entry in src/styles/main.css (important flag, cascade layers keeping PrimeVue between base and utilities, @source for azion-webkit@1's raw .vue components, v3 preflight compat). azion-webkit@1's entry CSS is vendored into src/styles/webkit-v1-main.css until webkit v4 lands.
  2. Astro 5 → 7 — Astro 7.2.3, Vite 8, Vue 3.5, Content Layer API (content.config.ts + glob loader, slugid, render(entry)), integration bumps (@astrojs/sitemap 1.0.1→3.7.3 included), and the markdown pipeline kept on unified() so all custom remark/rehype plugins keep working. Also fixes the 9 published vulnerabilities in astro <7.0.4.

Notable plumbing

  • Astro 7 renders pages in a new prerender Vite environment that ignores vite.ssr.noExternal and force-externalizes primevue; a small inline config plugin (azion:server-noexternal) bundles primevue/azion-webkit/azion-theme there (PrimeVue 3's ESM uses directory imports Node can't resolve).
  • TablePricing now caches pricing API responses per product_slug for the whole build — the prerender pass fetched once per page and caused transient DNS failures.

Verification

  • pnpm run build:local: 1494 pages in ~2m48s, frontmatter test passing.
  • Output spot-checked: asides, heading anchors, expressive-code blocks, doc-all-data.json / docs-path-by-url.json / docs-llms.txt / .md endpoints, both sitemaps, Tailwind 4 CSS inlined with !important parity.
  • astro preview smoke test in the browser: layout matches production, Algolia search dialog opens and returns results, zero console errors.

Known notes

  • Dev server only: the Algolia search dialog fails to hydrate under astro dev (Vite 8 optimize-deps CJS interop with algoliasearch inside azion-webkit@1). The production build is unaffected — worth a follow-up.
  • Porting the remark/rehype plugins to Sätteri is intentionally out of scope (next PR in the stack).
  • astro-expressive-code 0.44 warns about unknown code-fence languages (code, env, gitignore) in a few MDX files — content fixes, separate PR.

🤖 Generated with Claude Code

Replaces the deprecated @astrojs/tailwind integration (capped at Astro 5)
with Tailwind 4 via @tailwindcss/postcss, unblocking the Astro 7 upgrade.
- Vendor azion-webkit@1's entry CSS (src/styles/webkit-v1-main.css) since
it contained Tailwind v3 directives; the file is removed once webkit v4
lands.
- New Tailwind entry in src/styles/main.css: cascade-layer order keeps
PrimeVue between base and utilities (same cascade as v3), `important`
import flag preserves v3's important:true, @source compiles the raw
.vue components azion-webkit@1 ships, and v3 preflight compatibility
styles keep border/placeholder/cursor defaults.
- Slim tailwind.config.cjs: theme-only copy of what azion-webkit@1's
config provided; its v3-only addUtilities plugins now live in CSS as
@Utility px-shell/px-container. The unused `container.padding` config
is dropped (crashes Tailwind 4's compat layer with custom screens).
- Rename removed v3 utilities: flex-shrink-0 -> shrink-0, rounded ->
rounded-sm.
- Drop autoprefixer (built into Tailwind 4).
Astro 7.2.3, Vite 8, Vue 3.5, and the Content Layer API. Also patches the
9 published vulnerabilities in astro <7.0.4.
Content Layer (mandatory since Astro 6 removed legacy collections):
- Move src/content/config.ts to src/content.config.ts with a glob() loader
and `astro/zod` imports.
- entry.slug -> entry.id and entry.render() -> render(entry) across pages,
layouts, components, and utils. The [lang]/[...slug] route rebuilds the
legacy id (path with extension) from entry.filePath for getGithubEditUrl,
and docs-path-by-url.json now uses entry.filePath directly.
Markdown: keep the unified() processor from @astrojs/markdown-remark so the
custom remark/rehype plugins keep running; porting them to Sätteri is a
follow-up.
Integrations: @astrojs/mdx 7, @astrojs/vue 7, @astrojs/preact 6,
@astrojs/sitemap 1.0.1 -> 3.7.3, astro-expressive-code 0.44,
astro-auto-import 0.5, astro-og-canvas 0.13.
Vite 8 adjustments:
- Tailwind 4 moves from @tailwindcss/postcss to @tailwindcss/vite (Vite 8's
postcss-import no longer resolves the bare `tailwindcss` import).
- A small config plugin bundles primevue/azion-webkit/azion-theme into the
new prerender environment: Astro marks primevue as external there, and
primevue 3's ESM files use directory imports Node cannot resolve.
- Drop the obsolete vite.ssrBuild flag.
TablePricing now caches pricing API responses per product_slug for the whole
build; the prerender pass fetching once per page caused transient DNS
failures.
Build verified: 1494 pages, frontmatter test passing, asides/heading
anchors/expressive-code/JSON+md+llms.txt endpoints/sitemaps all present in
the output.
…th Tailwind (#2316)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@isaque-bock-azion
isaque-bock-azion requested a review from a team as a code ownerAugust 28, 2026 21:28
Ports the header, search trigger and palette from the webkit design
system sample (feat/doc-masthead-action-belt) to the docs site.
- Header: rebuilt on GlobalHeader (.Left/.Brand/.Nav/.Right) with
@container so the search trigger can switch shapes via container
query instead of viewport breakpoints
- HeaderSearch: two-root trigger (IconButton narrow / field-shaped
wide) with @min-[47rem] container query, verbatim from the sample
- HeaderSearchDialog: CommandMenu shell (⌘K shortcut, Dialog/Overlay
owned by webkit) feeding Algolia via direct client.search() across
the four i18n indices; replaces vue-instantsearch + AlgoliaDialog
- HeaderRightSidebar: mobile nav migrated from manual Teleport to
webkit Drawer (side="left", size="small"); closes on palette-open
cross-island event
- DocsTopNav: NavigationMenu extracted into its own client:idle island
so the hover-pill highlight (pointerenter + provide/inject) hydrates
correctly; was dead inside the SSR-only Header.vue
- main.css: added text-overline-md / text-overline-sm Tailwind shims
so Kbd keycaps (⌘K, ESC) render at the token's 12px, not 16px
- Removed: AlgoliaDialog, AlgoliaInstantSearch, AlgoliaIndexHit,
KeyboardKey, DocsSearchTrigger, vue.config.js; dep vue-instantsearch
Deletes public/assets/docs/css/ (theme.css, index.css), and the
orphaned SCSS layers under src/styles/ (breakpoints, functions, icons,
tokens) — none of these were imported anywhere after the webkit/theme
migration; all design tokens now come from @aziontech/theme.
Replaces the hand-rolled footer flex layout with webkit's Footer
component, aligned with the design system's own sample (SiteFooter.vue).
Updates the i18n model: drops the legacy `cta` (phone numbers) and
`copyright` exports, adds `tagline` in their place, as the DS Footer
now renders the signature band with the brand mark and tagline from
the component itself.
- LeftSidebar: drops SidebarContent (Astro/SCSS/TSX trio) and the
sidebarpanelmenu/ pair; now delegates to DocsSidebarMenu (webkit DS)
via the menuToWebkit adapter
- ReadableContent: rewired to webkit's DocProse for article typography
(heading ladder, prose body, callout, code chip); removes the local
.prose/.article SCSS that duplicated those rules
- RightSidebar: swaps the local TableOfContents/TableOfContentsItem
pair for webkit's OnThisPage component
- PageContent/MainLayout: layout adjustments for the webkit rail's
sizing contract (auto column instead of fixed 20rem)
…egrations
- Aside: replaced hand-rolled admonition (inline SVGs + SCSS) with
webkit's doc-callout; maps caution→warning, unwraps <p> to inline
prose, preserves authored titles as lead-in text
- Code: switched from astro-expressive-code to local CodeBlock wrapper
(webkit's syntax highlighting); removes expressive-code integration files
- LinkButton: wraps button roots in `data-doc-chrome` span (display:contents)
so DocProse prose-link paint does not repaint buttons inside article content
- AzionLogo.vue: deleted (unused after Header.vue moved to GlobalHeader.Brand)
- plugins/rehype-autolink-config: adds data-doc-anchor on heading anchors
so DocProse's link rules skip them
- .gitignore: add Azion CLI build artifacts (.edge/, .vulcan, .open-next)
- pnpm-workspace.yaml: pin @aziontech/theme and webkit to current versions
New components used by the sidebar/toc/code refactor:
- CodeBlock: webkit syntax highlighting shell (replaces expressive-code)
- DocPageHeader: page title + metadata band above article content
- DocsSidebar: webkit Sidebar rail wrapper for the left nav
- DocsSidebarMenu: webkit Menu tree rendering the navigation tree
- OnThisPage: webkit table-of-contents for the right sidebar
- menuToWebkit: adapter converting the docs navigation data model
(getNavigationMenu) to webkit's Menu/tree props contract
- integrations/astro-code-blocks: Astro integration wiring CodeBlock
into the remark/rehype pipeline
Adds the auto-generated azion.config.mjs (build preset: astro,
storage bucket for docs-preview-webkit) and the GitHub Actions workflow
for Azion deploy, alongside the azion/ config directory generated by
the CLI.
Replaces the LinkButton compatibility wrapper with @aziontech/webkit/button
imported directly. Props mapped: link→href, severity="secondary"→kind="secondary";
added size="medium" and class="not-prose no-underline" on every call site.
Removes the unused Container import.
DocProse styles every `a` in the article body as a prose link -- link ink,
underline, flat radius -- with `!important`, and steps aside only for
`[data-doc-chrome]`. A webkit Button rendered with `href` is such an `a`, so
every button in the docs body came out blue, underlined and square on top of
its own fill. `class="not-prose no-underline"` never had a chance: the
contract's selector is more specific and important.
The attribute cannot go on the Button itself -- it sets `inheritAttrs: false`
and forwards only `class`, so `data-doc-chrome` is dropped before it reaches
the DOM. DocButton puts it on a wrapping span instead, whose subtree the
contract also excludes, and adds `data-doc-block` for the standard block
spacing.
Same treatment for HeroHome's actions row: the hero CTAs live inside
ReadableContent too and were painted as prose links for the same reason.
…utton
The 650 pages that import the button rendered it as a prose link -- blue,
underlined, square corners over the button's own fill -- because DocProse
styles every `a` in the article body and only stops at `[data-doc-chrome]`.
DocButton carries that boundary; swapping the import is the whole fix.
Two leftovers from the LinkButton era go with it. `class="not-prose
no-underline"` was reaching for this same escape and never worked, and
`iconPos="left"` names a prop the webkit Button does not have -- it has one
icon slot, always leading -- so it was dropped on the floor either way.
Mechanical: the import line was identical in all 650 files, `<Button` has no
closing-tag form here, and occurrences inside code fences were left alone.
All 650 files re-checked through the MDX compiler.
… mounted
Every documentation page logged "Hydration completed but contains mismatches".
DocProse's copy tip lives in a `<Teleport to="body">`, and Astro does not
re-emit Vue's teleport payload into the served markup -- so on hydration Vue
walks into `<body>` looking for the teleport anchor and finds the page's own
first element instead. Vue then discards the server DOM for that subtree and
re-renders it on the client, on every page.
Gating the teleport on a mounted flag keeps the server render and the client's
first render identical -- the tip is a `v-if` comment inline in the root either
way -- and it costs nothing, since the tip only ever opens on pointerover.
Carried as a pnpm patch because the fix belongs upstream in @aziontech/webkit;
drop `patches/` and the `patchedDependencies` entry once a release ships it.
The authoring API is unchanged: 328 tab blocks across 178 content files keep
writing `<Tabs>` with `<Fragment slot="tab.x">` / `slot="panel.x">`, so not one
.mdx was touched. `Tabs.ts` re-exports the SFC because the content imports
`~/components/tabs/Tabs` without an extension and Vite's resolve.extensions
does not include .vue.
Panels stay in the DOM behind `hidden` instead of using TabView's Panel, which
mounts its slot behind `v-if`. Dropping inactive panels would take half the
prose of every tabbed page out of the served HTML -- out of Ctrl+F, out of the
crawl, and out of reach of the heading-anchor wiring ReadableContent installs
once on mount. The cost is TabView's sliding indicator and edge fades; this
component owns the active value, the ids and the arrow keys instead, and the
chips fall back to Item's standalone `selected` styling, which paints the same.
Two things the port had to fix rather than carry over. Astro hands slots to the
island in resolution order, not authoring order, so a panel holding an island
(`<Code>`) overtakes the panel written above it -- the tabs are now the source
of order and panels are paired by key, which also moves the default from "first
panel" to "first tab" (they diverge in 23 of the 328 blocks, some of them real
content typos). And the shared store subscribes only on mount: `tabStore` is a
module singleton, so subscribing during SSR would leak a listener per render
and let one request's choice bleed into the next.
PricingTableOfContents comes along as Vue over webkit's DocOnThisPage, keeping
the two pricing-specific behaviors: the `-1` anchor remap for the `real` tab,
and the scroll-spy that skips headings under `[hidden]`.
… originals
Overline, TitleSection and the four-part card family (CardBase,
CardBaseClickable, CardTitle, CardDescription) all had an equivalent already
shipping in @aziontech/webkit, so they are gone: 502 lines of local markup
replaced by Overline, SectionTitle, DocCard and DocCardGroup.
The card grid is the change worth reading. DocCardGroup is both the frame and
the rules -- cards abut inside it and the hairlines between them are the
group's own gaps -- so Cardlist no longer draws a grid, a gap or a border of
its own. Two deliberate omissions: no icon, because the content data carries
SVG paths while DocCard's `icon` expects a PrimeIcons class, and no CTA link,
because the data has no call-to-action copy and inventing i18n strings is not a
refactor. The old card's arrow goes with them; the whole card is still the
link.
Overline is a `w-fit` inline element rather than a block, so centering moved
from `text-center` to `self-center` on the parent. And in the branch where
SectionTitle takes over the whole header, `titleTag` stops having an effect --
the design system fixes h2 there. That branch compiles but no page exercises it
today: none of the 70 call sites passes a `title`.
The hero stays hand-built on purpose. webkit's HeroTitle always renders the
highlighted span at the *start* of the headline, and both homes author it at
the end ("Welcome to <span>Azion Docs</span>"). With no title slot, adopting it
would mean losing the accent, inverting the sentence, or rewriting both
headlines -- an editorial call, not a refactor.
ThemeSwitcher becomes an adapter over webkit's ThemeSwitcher (196 -> 112
lines): the dropdown, its trigger, the viewport measuring and the document
click/Escape listeners all go, and what stays is the part the design system
does not own -- reading and writing the preference. The stored value keeps its
exact old shape, the `prefers-color-scheme` key holding 'System' | 'Dark' |
'Light', because BaseLayout reads that same key in a blocking inline script and
every returning visitor already has one of those strings saved; the lowercase
mode names webkit speaks are paired to them here rather than migrating anyone's
storage. Visually this turns a dropdown into a segmented icon group.
SystemStatus takes its dot and label from StatusIndicator, so severity comes
from theme tokens instead of the inline hex the component used to paint with.
The pill around it stays a plain anchor: webkit's Button takes its content
through a `label` string and exposes no default slot, so wrapping the indicator
in one would render the text and drop the dot. Also removes STATUS_PAGE and its
`status` ref, which nothing read.
SelectLang moves to webkit's Dropdown -- not Select, which is still draft in
the catalog. `placement="auto"` replaces the hand-rolled "is there room below?"
measurement. The options stay real anchors so the translated route is still
crawlable and middle-clickable.
Two known defects are documented in BaseLayout rather than worked around.
Nothing nested in this Footer island hydrates today, SystemStatus included, and
that predates the webkit adoption -- the controls paint but do not respond to a
click. And webkit's Dropdown and ThemeSwitcher teleport an overlay to <body>,
which Astro never re-emits into the served markup; the fix is the doc-prose
patch in patches/ extended upstream to tooltip and dropdown. `client:only`
looks like the answer to both and is a trap: an island nested inside a Vue
island never mounts under it, so the controls vanish entirely instead of merely
being inert.
BaseLayout also carries an unrelated change that was already pending in the
working tree: the ScrollArea rules for the "On this page" rail.
This is not one change; it is everything that was sitting uncommitted in the
tree, swept in at the author's request so nothing is left dangling. Two
distinct bodies of work:
The webkit/Tailwind 4 adoption in progress: 68 legacy components removed
(BackendGuidesNav, Badge, Breadcrumb, Button, the NavGrid and tabs .astro
families, the Preact leftovers TableOfContents, SidebarToggleTabGroup,
ExternalLinkIcon and Spoiler, and the rest), Footer rebuilt on the design
system's own Footer, and the supporting changes across content.config.ts,
content.ts, the i18n helpers, src/util and the layouts.
The lint toolchain migration (MM-15912), authored in a parallel session:
flat config in eslint.config.mjs replacing .eslintrc.js and .eslintignore,
eslint 9 with typescript-eslint 8, stylelint 16 with postcss-html and
postcss-scss, and the call sites those rules touched.
Also included: azion.config.mjs and azion/azion.json from the preview deploy,
and .claude/launch.json.
Neither body of work is mine and neither was reviewed here. They are committed
as they stood, so the history has a point to return to before the deploy.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@isaque-bock-azion
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(build): upgrade to Astro 7 and Tailwind CSS 4 - #2315

Open
isaque-bock-azion wants to merge 18 commits into
mainfrom
feat/astro7-tailwind4
Open

feat(build): upgrade to Astro 7 and Tailwind CSS 4#2315
isaque-bock-azion wants to merge 18 commits into
mainfrom
feat/astro7-tailwind4

Conversation

@isaque-bock-azion

@isaque-bock-azionisaque-bock-azion commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

What

Base branch for the docs modernization stack (MM-15897). Two commits, one per migration:

  1. Tailwind CSS 3 → 4 — removes the deprecated @astrojs/tailwind integration (capped at Astro 5). New CSS-first entry in src/styles/main.css (important flag, cascade layers keeping PrimeVue between base and utilities, @source for azion-webkit@1's raw .vue components, v3 preflight compat). azion-webkit@1's entry CSS is vendored into src/styles/webkit-v1-main.css until webkit v4 lands.
  2. Astro 5 → 7 — Astro 7.2.3, Vite 8, Vue 3.5, Content Layer API (content.config.ts + glob loader, slugid, render(entry)), integration bumps (@astrojs/sitemap 1.0.1→3.7.3 included), and the markdown pipeline kept on unified() so all custom remark/rehype plugins keep working. Also fixes the 9 published vulnerabilities in astro <7.0.4.

Notable plumbing

  • Astro 7 renders pages in a new prerender Vite environment that ignores vite.ssr.noExternal and force-externalizes primevue; a small inline config plugin (azion:server-noexternal) bundles primevue/azion-webkit/azion-theme there (PrimeVue 3's ESM uses directory imports Node can't resolve).
  • TablePricing now caches pricing API responses per product_slug for the whole build — the prerender pass fetched once per page and caused transient DNS failures.

Verification

  • pnpm run build:local: 1494 pages in ~2m48s, frontmatter test passing.
  • Output spot-checked: asides, heading anchors, expressive-code blocks, doc-all-data.json / docs-path-by-url.json / docs-llms.txt / .md endpoints, both sitemaps, Tailwind 4 CSS inlined with !important parity.
  • astro preview smoke test in the browser: layout matches production, Algolia search dialog opens and returns results, zero console errors.

Known notes

  • Dev server only: the Algolia search dialog fails to hydrate under astro dev (Vite 8 optimize-deps CJS interop with algoliasearch inside azion-webkit@1). The production build is unaffected — worth a follow-up.
  • Porting the remark/rehype plugins to Sätteri is intentionally out of scope (next PR in the stack).
  • astro-expressive-code 0.44 warns about unknown code-fence languages (code, env, gitignore) in a few MDX files — content fixes, separate PR.

🤖 Generated with Claude Code

Replaces the deprecated @astrojs/tailwind integration (capped at Astro 5)
with Tailwind 4 via @tailwindcss/postcss, unblocking the Astro 7 upgrade.
- Vendor azion-webkit@1's entry CSS (src/styles/webkit-v1-main.css) since
it contained Tailwind v3 directives; the file is removed once webkit v4
lands.
- New Tailwind entry in src/styles/main.css: cascade-layer order keeps
PrimeVue between base and utilities (same cascade as v3), `important`
import flag preserves v3's important:true, @source compiles the raw
.vue components azion-webkit@1 ships, and v3 preflight compatibility
styles keep border/placeholder/cursor defaults.
- Slim tailwind.config.cjs: theme-only copy of what azion-webkit@1's
config provided; its v3-only addUtilities plugins now live in CSS as
@Utility px-shell/px-container. The unused `container.padding` config
is dropped (crashes Tailwind 4's compat layer with custom screens).
- Rename removed v3 utilities: flex-shrink-0 -> shrink-0, rounded ->
rounded-sm.
- Drop autoprefixer (built into Tailwind 4).
Astro 7.2.3, Vite 8, Vue 3.5, and the Content Layer API. Also patches the
9 published vulnerabilities in astro <7.0.4.
Content Layer (mandatory since Astro 6 removed legacy collections):
- Move src/content/config.ts to src/content.config.ts with a glob() loader
and `astro/zod` imports.
- entry.slug -> entry.id and entry.render() -> render(entry) across pages,
layouts, components, and utils. The [lang]/[...slug] route rebuilds the
legacy id (path with extension) from entry.filePath for getGithubEditUrl,
and docs-path-by-url.json now uses entry.filePath directly.
Markdown: keep the unified() processor from @astrojs/markdown-remark so the
custom remark/rehype plugins keep running; porting them to Sätteri is a
follow-up.
Integrations: @astrojs/mdx 7, @astrojs/vue 7, @astrojs/preact 6,
@astrojs/sitemap 1.0.1 -> 3.7.3, astro-expressive-code 0.44,
astro-auto-import 0.5, astro-og-canvas 0.13.
Vite 8 adjustments:
- Tailwind 4 moves from @tailwindcss/postcss to @tailwindcss/vite (Vite 8's
postcss-import no longer resolves the bare `tailwindcss` import).
- A small config plugin bundles primevue/azion-webkit/azion-theme into the
new prerender environment: Astro marks primevue as external there, and
primevue 3's ESM files use directory imports Node cannot resolve.
- Drop the obsolete vite.ssrBuild flag.
TablePricing now caches pricing API responses per product_slug for the whole
build; the prerender pass fetching once per page caused transient DNS
failures.
Build verified: 1494 pages, frontmatter test passing, asides/heading
anchors/expressive-code/JSON+md+llms.txt endpoints/sitemaps all present in
the output.
…th Tailwind (#2316)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@isaque-bock-azion
isaque-bock-azion requested a review from a team as a code ownerAugust 28, 2026 21:28
Ports the header, search trigger and palette from the webkit design
system sample (feat/doc-masthead-action-belt) to the docs site.
- Header: rebuilt on GlobalHeader (.Left/.Brand/.Nav/.Right) with
@container so the search trigger can switch shapes via container
query instead of viewport breakpoints
- HeaderSearch: two-root trigger (IconButton narrow / field-shaped
wide) with @min-[47rem] container query, verbatim from the sample
- HeaderSearchDialog: CommandMenu shell (⌘K shortcut, Dialog/Overlay
owned by webkit) feeding Algolia via direct client.search() across
the four i18n indices; replaces vue-instantsearch + AlgoliaDialog
- HeaderRightSidebar: mobile nav migrated from manual Teleport to
webkit Drawer (side="left", size="small"); closes on palette-open
cross-island event
- DocsTopNav: NavigationMenu extracted into its own client:idle island
so the hover-pill highlight (pointerenter + provide/inject) hydrates
correctly; was dead inside the SSR-only Header.vue
- main.css: added text-overline-md / text-overline-sm Tailwind shims
so Kbd keycaps (⌘K, ESC) render at the token's 12px, not 16px
- Removed: AlgoliaDialog, AlgoliaInstantSearch, AlgoliaIndexHit,
KeyboardKey, DocsSearchTrigger, vue.config.js; dep vue-instantsearch
Deletes public/assets/docs/css/ (theme.css, index.css), and the
orphaned SCSS layers under src/styles/ (breakpoints, functions, icons,
tokens) — none of these were imported anywhere after the webkit/theme
migration; all design tokens now come from @aziontech/theme.
Replaces the hand-rolled footer flex layout with webkit's Footer
component, aligned with the design system's own sample (SiteFooter.vue).
Updates the i18n model: drops the legacy `cta` (phone numbers) and
`copyright` exports, adds `tagline` in their place, as the DS Footer
now renders the signature band with the brand mark and tagline from
the component itself.
- LeftSidebar: drops SidebarContent (Astro/SCSS/TSX trio) and the
sidebarpanelmenu/ pair; now delegates to DocsSidebarMenu (webkit DS)
via the menuToWebkit adapter
- ReadableContent: rewired to webkit's DocProse for article typography
(heading ladder, prose body, callout, code chip); removes the local
.prose/.article SCSS that duplicated those rules
- RightSidebar: swaps the local TableOfContents/TableOfContentsItem
pair for webkit's OnThisPage component
- PageContent/MainLayout: layout adjustments for the webkit rail's
sizing contract (auto column instead of fixed 20rem)
…egrations
- Aside: replaced hand-rolled admonition (inline SVGs + SCSS) with
webkit's doc-callout; maps caution→warning, unwraps <p> to inline
prose, preserves authored titles as lead-in text
- Code: switched from astro-expressive-code to local CodeBlock wrapper
(webkit's syntax highlighting); removes expressive-code integration files
- LinkButton: wraps button roots in `data-doc-chrome` span (display:contents)
so DocProse prose-link paint does not repaint buttons inside article content
- AzionLogo.vue: deleted (unused after Header.vue moved to GlobalHeader.Brand)
- plugins/rehype-autolink-config: adds data-doc-anchor on heading anchors
so DocProse's link rules skip them
- .gitignore: add Azion CLI build artifacts (.edge/, .vulcan, .open-next)
- pnpm-workspace.yaml: pin @aziontech/theme and webkit to current versions
New components used by the sidebar/toc/code refactor:
- CodeBlock: webkit syntax highlighting shell (replaces expressive-code)
- DocPageHeader: page title + metadata band above article content
- DocsSidebar: webkit Sidebar rail wrapper for the left nav
- DocsSidebarMenu: webkit Menu tree rendering the navigation tree
- OnThisPage: webkit table-of-contents for the right sidebar
- menuToWebkit: adapter converting the docs navigation data model
(getNavigationMenu) to webkit's Menu/tree props contract
- integrations/astro-code-blocks: Astro integration wiring CodeBlock
into the remark/rehype pipeline
Adds the auto-generated azion.config.mjs (build preset: astro,
storage bucket for docs-preview-webkit) and the GitHub Actions workflow
for Azion deploy, alongside the azion/ config directory generated by
the CLI.
Replaces the LinkButton compatibility wrapper with @aziontech/webkit/button
imported directly. Props mapped: link→href, severity="secondary"→kind="secondary";
added size="medium" and class="not-prose no-underline" on every call site.
Removes the unused Container import.
DocProse styles every `a` in the article body as a prose link -- link ink,
underline, flat radius -- with `!important`, and steps aside only for
`[data-doc-chrome]`. A webkit Button rendered with `href` is such an `a`, so
every button in the docs body came out blue, underlined and square on top of
its own fill. `class="not-prose no-underline"` never had a chance: the
contract's selector is more specific and important.
The attribute cannot go on the Button itself -- it sets `inheritAttrs: false`
and forwards only `class`, so `data-doc-chrome` is dropped before it reaches
the DOM. DocButton puts it on a wrapping span instead, whose subtree the
contract also excludes, and adds `data-doc-block` for the standard block
spacing.
Same treatment for HeroHome's actions row: the hero CTAs live inside
ReadableContent too and were painted as prose links for the same reason.
…utton
The 650 pages that import the button rendered it as a prose link -- blue,
underlined, square corners over the button's own fill -- because DocProse
styles every `a` in the article body and only stops at `[data-doc-chrome]`.
DocButton carries that boundary; swapping the import is the whole fix.
Two leftovers from the LinkButton era go with it. `class="not-prose
no-underline"` was reaching for this same escape and never worked, and
`iconPos="left"` names a prop the webkit Button does not have -- it has one
icon slot, always leading -- so it was dropped on the floor either way.
Mechanical: the import line was identical in all 650 files, `<Button` has no
closing-tag form here, and occurrences inside code fences were left alone.
All 650 files re-checked through the MDX compiler.
… mounted
Every documentation page logged "Hydration completed but contains mismatches".
DocProse's copy tip lives in a `<Teleport to="body">`, and Astro does not
re-emit Vue's teleport payload into the served markup -- so on hydration Vue
walks into `<body>` looking for the teleport anchor and finds the page's own
first element instead. Vue then discards the server DOM for that subtree and
re-renders it on the client, on every page.
Gating the teleport on a mounted flag keeps the server render and the client's
first render identical -- the tip is a `v-if` comment inline in the root either
way -- and it costs nothing, since the tip only ever opens on pointerover.
Carried as a pnpm patch because the fix belongs upstream in @aziontech/webkit;
drop `patches/` and the `patchedDependencies` entry once a release ships it.
The authoring API is unchanged: 328 tab blocks across 178 content files keep
writing `<Tabs>` with `<Fragment slot="tab.x">` / `slot="panel.x">`, so not one
.mdx was touched. `Tabs.ts` re-exports the SFC because the content imports
`~/components/tabs/Tabs` without an extension and Vite's resolve.extensions
does not include .vue.
Panels stay in the DOM behind `hidden` instead of using TabView's Panel, which
mounts its slot behind `v-if`. Dropping inactive panels would take half the
prose of every tabbed page out of the served HTML -- out of Ctrl+F, out of the
crawl, and out of reach of the heading-anchor wiring ReadableContent installs
once on mount. The cost is TabView's sliding indicator and edge fades; this
component owns the active value, the ids and the arrow keys instead, and the
chips fall back to Item's standalone `selected` styling, which paints the same.
Two things the port had to fix rather than carry over. Astro hands slots to the
island in resolution order, not authoring order, so a panel holding an island
(`<Code>`) overtakes the panel written above it -- the tabs are now the source
of order and panels are paired by key, which also moves the default from "first
panel" to "first tab" (they diverge in 23 of the 328 blocks, some of them real
content typos). And the shared store subscribes only on mount: `tabStore` is a
module singleton, so subscribing during SSR would leak a listener per render
and let one request's choice bleed into the next.
PricingTableOfContents comes along as Vue over webkit's DocOnThisPage, keeping
the two pricing-specific behaviors: the `-1` anchor remap for the `real` tab,
and the scroll-spy that skips headings under `[hidden]`.
… originals
Overline, TitleSection and the four-part card family (CardBase,
CardBaseClickable, CardTitle, CardDescription) all had an equivalent already
shipping in @aziontech/webkit, so they are gone: 502 lines of local markup
replaced by Overline, SectionTitle, DocCard and DocCardGroup.
The card grid is the change worth reading. DocCardGroup is both the frame and
the rules -- cards abut inside it and the hairlines between them are the
group's own gaps -- so Cardlist no longer draws a grid, a gap or a border of
its own. Two deliberate omissions: no icon, because the content data carries
SVG paths while DocCard's `icon` expects a PrimeIcons class, and no CTA link,
because the data has no call-to-action copy and inventing i18n strings is not a
refactor. The old card's arrow goes with them; the whole card is still the
link.
Overline is a `w-fit` inline element rather than a block, so centering moved
from `text-center` to `self-center` on the parent. And in the branch where
SectionTitle takes over the whole header, `titleTag` stops having an effect --
the design system fixes h2 there. That branch compiles but no page exercises it
today: none of the 70 call sites passes a `title`.
The hero stays hand-built on purpose. webkit's HeroTitle always renders the
highlighted span at the *start* of the headline, and both homes author it at
the end ("Welcome to <span>Azion Docs</span>"). With no title slot, adopting it
would mean losing the accent, inverting the sentence, or rewriting both
headlines -- an editorial call, not a refactor.
ThemeSwitcher becomes an adapter over webkit's ThemeSwitcher (196 -> 112
lines): the dropdown, its trigger, the viewport measuring and the document
click/Escape listeners all go, and what stays is the part the design system
does not own -- reading and writing the preference. The stored value keeps its
exact old shape, the `prefers-color-scheme` key holding 'System' | 'Dark' |
'Light', because BaseLayout reads that same key in a blocking inline script and
every returning visitor already has one of those strings saved; the lowercase
mode names webkit speaks are paired to them here rather than migrating anyone's
storage. Visually this turns a dropdown into a segmented icon group.
SystemStatus takes its dot and label from StatusIndicator, so severity comes
from theme tokens instead of the inline hex the component used to paint with.
The pill around it stays a plain anchor: webkit's Button takes its content
through a `label` string and exposes no default slot, so wrapping the indicator
in one would render the text and drop the dot. Also removes STATUS_PAGE and its
`status` ref, which nothing read.
SelectLang moves to webkit's Dropdown -- not Select, which is still draft in
the catalog. `placement="auto"` replaces the hand-rolled "is there room below?"
measurement. The options stay real anchors so the translated route is still
crawlable and middle-clickable.
Two known defects are documented in BaseLayout rather than worked around.
Nothing nested in this Footer island hydrates today, SystemStatus included, and
that predates the webkit adoption -- the controls paint but do not respond to a
click. And webkit's Dropdown and ThemeSwitcher teleport an overlay to <body>,
which Astro never re-emits into the served markup; the fix is the doc-prose
patch in patches/ extended upstream to tooltip and dropdown. `client:only`
looks like the answer to both and is a trap: an island nested inside a Vue
island never mounts under it, so the controls vanish entirely instead of merely
being inert.
BaseLayout also carries an unrelated change that was already pending in the
working tree: the ScrollArea rules for the "On this page" rail.
This is not one change; it is everything that was sitting uncommitted in the
tree, swept in at the author's request so nothing is left dangling. Two
distinct bodies of work:
The webkit/Tailwind 4 adoption in progress: 68 legacy components removed
(BackendGuidesNav, Badge, Breadcrumb, Button, the NavGrid and tabs .astro
families, the Preact leftovers TableOfContents, SidebarToggleTabGroup,
ExternalLinkIcon and Spoiler, and the rest), Footer rebuilt on the design
system's own Footer, and the supporting changes across content.config.ts,
content.ts, the i18n helpers, src/util and the layouts.
The lint toolchain migration (MM-15912), authored in a parallel session:
flat config in eslint.config.mjs replacing .eslintrc.js and .eslintignore,
eslint 9 with typescript-eslint 8, stylelint 16 with postcss-html and
postcss-scss, and the call sites those rules touched.
Also included: azion.config.mjs and azion/azion.json from the preview deploy,
and .claude/launch.json.
Neither body of work is mine and neither was reviewed here. They are committed
as they stood, so the history has a point to return to before the deploy.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@isaque-bock-azion
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(build): upgrade to Astro 7 and Tailwind CSS 4 - #2315

Open
isaque-bock-azion wants to merge 18 commits into
mainfrom
feat/astro7-tailwind4
Open

feat(build): upgrade to Astro 7 and Tailwind CSS 4#2315
isaque-bock-azion wants to merge 18 commits into
mainfrom
feat/astro7-tailwind4

Conversation

@isaque-bock-azion

@isaque-bock-azionisaque-bock-azion commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

What

Base branch for the docs modernization stack (MM-15897). Two commits, one per migration:

  1. Tailwind CSS 3 → 4 — removes the deprecated @astrojs/tailwind integration (capped at Astro 5). New CSS-first entry in src/styles/main.css (important flag, cascade layers keeping PrimeVue between base and utilities, @source for azion-webkit@1's raw .vue components, v3 preflight compat). azion-webkit@1's entry CSS is vendored into src/styles/webkit-v1-main.css until webkit v4 lands.
  2. Astro 5 → 7 — Astro 7.2.3, Vite 8, Vue 3.5, Content Layer API (content.config.ts + glob loader, slugid, render(entry)), integration bumps (@astrojs/sitemap 1.0.1→3.7.3 included), and the markdown pipeline kept on unified() so all custom remark/rehype plugins keep working. Also fixes the 9 published vulnerabilities in astro <7.0.4.

Notable plumbing

  • Astro 7 renders pages in a new prerender Vite environment that ignores vite.ssr.noExternal and force-externalizes primevue; a small inline config plugin (azion:server-noexternal) bundles primevue/azion-webkit/azion-theme there (PrimeVue 3's ESM uses directory imports Node can't resolve).
  • TablePricing now caches pricing API responses per product_slug for the whole build — the prerender pass fetched once per page and caused transient DNS failures.

Verification

  • pnpm run build:local: 1494 pages in ~2m48s, frontmatter test passing.
  • Output spot-checked: asides, heading anchors, expressive-code blocks, doc-all-data.json / docs-path-by-url.json / docs-llms.txt / .md endpoints, both sitemaps, Tailwind 4 CSS inlined with !important parity.
  • astro preview smoke test in the browser: layout matches production, Algolia search dialog opens and returns results, zero console errors.

Known notes

  • Dev server only: the Algolia search dialog fails to hydrate under astro dev (Vite 8 optimize-deps CJS interop with algoliasearch inside azion-webkit@1). The production build is unaffected — worth a follow-up.
  • Porting the remark/rehype plugins to Sätteri is intentionally out of scope (next PR in the stack).
  • astro-expressive-code 0.44 warns about unknown code-fence languages (code, env, gitignore) in a few MDX files — content fixes, separate PR.

🤖 Generated with Claude Code

Replaces the deprecated @astrojs/tailwind integration (capped at Astro 5)
with Tailwind 4 via @tailwindcss/postcss, unblocking the Astro 7 upgrade.
- Vendor azion-webkit@1's entry CSS (src/styles/webkit-v1-main.css) since
it contained Tailwind v3 directives; the file is removed once webkit v4
lands.
- New Tailwind entry in src/styles/main.css: cascade-layer order keeps
PrimeVue between base and utilities (same cascade as v3), `important`
import flag preserves v3's important:true, @source compiles the raw
.vue components azion-webkit@1 ships, and v3 preflight compatibility
styles keep border/placeholder/cursor defaults.
- Slim tailwind.config.cjs: theme-only copy of what azion-webkit@1's
config provided; its v3-only addUtilities plugins now live in CSS as
@Utility px-shell/px-container. The unused `container.padding` config
is dropped (crashes Tailwind 4's compat layer with custom screens).
- Rename removed v3 utilities: flex-shrink-0 -> shrink-0, rounded ->
rounded-sm.
- Drop autoprefixer (built into Tailwind 4).
Astro 7.2.3, Vite 8, Vue 3.5, and the Content Layer API. Also patches the
9 published vulnerabilities in astro <7.0.4.
Content Layer (mandatory since Astro 6 removed legacy collections):
- Move src/content/config.ts to src/content.config.ts with a glob() loader
and `astro/zod` imports.
- entry.slug -> entry.id and entry.render() -> render(entry) across pages,
layouts, components, and utils. The [lang]/[...slug] route rebuilds the
legacy id (path with extension) from entry.filePath for getGithubEditUrl,
and docs-path-by-url.json now uses entry.filePath directly.
Markdown: keep the unified() processor from @astrojs/markdown-remark so the
custom remark/rehype plugins keep running; porting them to Sätteri is a
follow-up.
Integrations: @astrojs/mdx 7, @astrojs/vue 7, @astrojs/preact 6,
@astrojs/sitemap 1.0.1 -> 3.7.3, astro-expressive-code 0.44,
astro-auto-import 0.5, astro-og-canvas 0.13.
Vite 8 adjustments:
- Tailwind 4 moves from @tailwindcss/postcss to @tailwindcss/vite (Vite 8's
postcss-import no longer resolves the bare `tailwindcss` import).
- A small config plugin bundles primevue/azion-webkit/azion-theme into the
new prerender environment: Astro marks primevue as external there, and
primevue 3's ESM files use directory imports Node cannot resolve.
- Drop the obsolete vite.ssrBuild flag.
TablePricing now caches pricing API responses per product_slug for the whole
build; the prerender pass fetching once per page caused transient DNS
failures.
Build verified: 1494 pages, frontmatter test passing, asides/heading
anchors/expressive-code/JSON+md+llms.txt endpoints/sitemaps all present in
the output.
…th Tailwind (#2316)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@isaque-bock-azion
isaque-bock-azion requested a review from a team as a code ownerAugust 28, 2026 21:28
Ports the header, search trigger and palette from the webkit design
system sample (feat/doc-masthead-action-belt) to the docs site.
- Header: rebuilt on GlobalHeader (.Left/.Brand/.Nav/.Right) with
@container so the search trigger can switch shapes via container
query instead of viewport breakpoints
- HeaderSearch: two-root trigger (IconButton narrow / field-shaped
wide) with @min-[47rem] container query, verbatim from the sample
- HeaderSearchDialog: CommandMenu shell (⌘K shortcut, Dialog/Overlay
owned by webkit) feeding Algolia via direct client.search() across
the four i18n indices; replaces vue-instantsearch + AlgoliaDialog
- HeaderRightSidebar: mobile nav migrated from manual Teleport to
webkit Drawer (side="left", size="small"); closes on palette-open
cross-island event
- DocsTopNav: NavigationMenu extracted into its own client:idle island
so the hover-pill highlight (pointerenter + provide/inject) hydrates
correctly; was dead inside the SSR-only Header.vue
- main.css: added text-overline-md / text-overline-sm Tailwind shims
so Kbd keycaps (⌘K, ESC) render at the token's 12px, not 16px
- Removed: AlgoliaDialog, AlgoliaInstantSearch, AlgoliaIndexHit,
KeyboardKey, DocsSearchTrigger, vue.config.js; dep vue-instantsearch
Deletes public/assets/docs/css/ (theme.css, index.css), and the
orphaned SCSS layers under src/styles/ (breakpoints, functions, icons,
tokens) — none of these were imported anywhere after the webkit/theme
migration; all design tokens now come from @aziontech/theme.
Replaces the hand-rolled footer flex layout with webkit's Footer
component, aligned with the design system's own sample (SiteFooter.vue).
Updates the i18n model: drops the legacy `cta` (phone numbers) and
`copyright` exports, adds `tagline` in their place, as the DS Footer
now renders the signature band with the brand mark and tagline from
the component itself.
- LeftSidebar: drops SidebarContent (Astro/SCSS/TSX trio) and the
sidebarpanelmenu/ pair; now delegates to DocsSidebarMenu (webkit DS)
via the menuToWebkit adapter
- ReadableContent: rewired to webkit's DocProse for article typography
(heading ladder, prose body, callout, code chip); removes the local
.prose/.article SCSS that duplicated those rules
- RightSidebar: swaps the local TableOfContents/TableOfContentsItem
pair for webkit's OnThisPage component
- PageContent/MainLayout: layout adjustments for the webkit rail's
sizing contract (auto column instead of fixed 20rem)
…egrations
- Aside: replaced hand-rolled admonition (inline SVGs + SCSS) with
webkit's doc-callout; maps caution→warning, unwraps <p> to inline
prose, preserves authored titles as lead-in text
- Code: switched from astro-expressive-code to local CodeBlock wrapper
(webkit's syntax highlighting); removes expressive-code integration files
- LinkButton: wraps button roots in `data-doc-chrome` span (display:contents)
so DocProse prose-link paint does not repaint buttons inside article content
- AzionLogo.vue: deleted (unused after Header.vue moved to GlobalHeader.Brand)
- plugins/rehype-autolink-config: adds data-doc-anchor on heading anchors
so DocProse's link rules skip them
- .gitignore: add Azion CLI build artifacts (.edge/, .vulcan, .open-next)
- pnpm-workspace.yaml: pin @aziontech/theme and webkit to current versions
New components used by the sidebar/toc/code refactor:
- CodeBlock: webkit syntax highlighting shell (replaces expressive-code)
- DocPageHeader: page title + metadata band above article content
- DocsSidebar: webkit Sidebar rail wrapper for the left nav
- DocsSidebarMenu: webkit Menu tree rendering the navigation tree
- OnThisPage: webkit table-of-contents for the right sidebar
- menuToWebkit: adapter converting the docs navigation data model
(getNavigationMenu) to webkit's Menu/tree props contract
- integrations/astro-code-blocks: Astro integration wiring CodeBlock
into the remark/rehype pipeline
Adds the auto-generated azion.config.mjs (build preset: astro,
storage bucket for docs-preview-webkit) and the GitHub Actions workflow
for Azion deploy, alongside the azion/ config directory generated by
the CLI.
Replaces the LinkButton compatibility wrapper with @aziontech/webkit/button
imported directly. Props mapped: link→href, severity="secondary"→kind="secondary";
added size="medium" and class="not-prose no-underline" on every call site.
Removes the unused Container import.
DocProse styles every `a` in the article body as a prose link -- link ink,
underline, flat radius -- with `!important`, and steps aside only for
`[data-doc-chrome]`. A webkit Button rendered with `href` is such an `a`, so
every button in the docs body came out blue, underlined and square on top of
its own fill. `class="not-prose no-underline"` never had a chance: the
contract's selector is more specific and important.
The attribute cannot go on the Button itself -- it sets `inheritAttrs: false`
and forwards only `class`, so `data-doc-chrome` is dropped before it reaches
the DOM. DocButton puts it on a wrapping span instead, whose subtree the
contract also excludes, and adds `data-doc-block` for the standard block
spacing.
Same treatment for HeroHome's actions row: the hero CTAs live inside
ReadableContent too and were painted as prose links for the same reason.
…utton
The 650 pages that import the button rendered it as a prose link -- blue,
underlined, square corners over the button's own fill -- because DocProse
styles every `a` in the article body and only stops at `[data-doc-chrome]`.
DocButton carries that boundary; swapping the import is the whole fix.
Two leftovers from the LinkButton era go with it. `class="not-prose
no-underline"` was reaching for this same escape and never worked, and
`iconPos="left"` names a prop the webkit Button does not have -- it has one
icon slot, always leading -- so it was dropped on the floor either way.
Mechanical: the import line was identical in all 650 files, `<Button` has no
closing-tag form here, and occurrences inside code fences were left alone.
All 650 files re-checked through the MDX compiler.
… mounted
Every documentation page logged "Hydration completed but contains mismatches".
DocProse's copy tip lives in a `<Teleport to="body">`, and Astro does not
re-emit Vue's teleport payload into the served markup -- so on hydration Vue
walks into `<body>` looking for the teleport anchor and finds the page's own
first element instead. Vue then discards the server DOM for that subtree and
re-renders it on the client, on every page.
Gating the teleport on a mounted flag keeps the server render and the client's
first render identical -- the tip is a `v-if` comment inline in the root either
way -- and it costs nothing, since the tip only ever opens on pointerover.
Carried as a pnpm patch because the fix belongs upstream in @aziontech/webkit;
drop `patches/` and the `patchedDependencies` entry once a release ships it.
The authoring API is unchanged: 328 tab blocks across 178 content files keep
writing `<Tabs>` with `<Fragment slot="tab.x">` / `slot="panel.x">`, so not one
.mdx was touched. `Tabs.ts` re-exports the SFC because the content imports
`~/components/tabs/Tabs` without an extension and Vite's resolve.extensions
does not include .vue.
Panels stay in the DOM behind `hidden` instead of using TabView's Panel, which
mounts its slot behind `v-if`. Dropping inactive panels would take half the
prose of every tabbed page out of the served HTML -- out of Ctrl+F, out of the
crawl, and out of reach of the heading-anchor wiring ReadableContent installs
once on mount. The cost is TabView's sliding indicator and edge fades; this
component owns the active value, the ids and the arrow keys instead, and the
chips fall back to Item's standalone `selected` styling, which paints the same.
Two things the port had to fix rather than carry over. Astro hands slots to the
island in resolution order, not authoring order, so a panel holding an island
(`<Code>`) overtakes the panel written above it -- the tabs are now the source
of order and panels are paired by key, which also moves the default from "first
panel" to "first tab" (they diverge in 23 of the 328 blocks, some of them real
content typos). And the shared store subscribes only on mount: `tabStore` is a
module singleton, so subscribing during SSR would leak a listener per render
and let one request's choice bleed into the next.
PricingTableOfContents comes along as Vue over webkit's DocOnThisPage, keeping
the two pricing-specific behaviors: the `-1` anchor remap for the `real` tab,
and the scroll-spy that skips headings under `[hidden]`.
… originals
Overline, TitleSection and the four-part card family (CardBase,
CardBaseClickable, CardTitle, CardDescription) all had an equivalent already
shipping in @aziontech/webkit, so they are gone: 502 lines of local markup
replaced by Overline, SectionTitle, DocCard and DocCardGroup.
The card grid is the change worth reading. DocCardGroup is both the frame and
the rules -- cards abut inside it and the hairlines between them are the
group's own gaps -- so Cardlist no longer draws a grid, a gap or a border of
its own. Two deliberate omissions: no icon, because the content data carries
SVG paths while DocCard's `icon` expects a PrimeIcons class, and no CTA link,
because the data has no call-to-action copy and inventing i18n strings is not a
refactor. The old card's arrow goes with them; the whole card is still the
link.
Overline is a `w-fit` inline element rather than a block, so centering moved
from `text-center` to `self-center` on the parent. And in the branch where
SectionTitle takes over the whole header, `titleTag` stops having an effect --
the design system fixes h2 there. That branch compiles but no page exercises it
today: none of the 70 call sites passes a `title`.
The hero stays hand-built on purpose. webkit's HeroTitle always renders the
highlighted span at the *start* of the headline, and both homes author it at
the end ("Welcome to <span>Azion Docs</span>"). With no title slot, adopting it
would mean losing the accent, inverting the sentence, or rewriting both
headlines -- an editorial call, not a refactor.
ThemeSwitcher becomes an adapter over webkit's ThemeSwitcher (196 -> 112
lines): the dropdown, its trigger, the viewport measuring and the document
click/Escape listeners all go, and what stays is the part the design system
does not own -- reading and writing the preference. The stored value keeps its
exact old shape, the `prefers-color-scheme` key holding 'System' | 'Dark' |
'Light', because BaseLayout reads that same key in a blocking inline script and
every returning visitor already has one of those strings saved; the lowercase
mode names webkit speaks are paired to them here rather than migrating anyone's
storage. Visually this turns a dropdown into a segmented icon group.
SystemStatus takes its dot and label from StatusIndicator, so severity comes
from theme tokens instead of the inline hex the component used to paint with.
The pill around it stays a plain anchor: webkit's Button takes its content
through a `label` string and exposes no default slot, so wrapping the indicator
in one would render the text and drop the dot. Also removes STATUS_PAGE and its
`status` ref, which nothing read.
SelectLang moves to webkit's Dropdown -- not Select, which is still draft in
the catalog. `placement="auto"` replaces the hand-rolled "is there room below?"
measurement. The options stay real anchors so the translated route is still
crawlable and middle-clickable.
Two known defects are documented in BaseLayout rather than worked around.
Nothing nested in this Footer island hydrates today, SystemStatus included, and
that predates the webkit adoption -- the controls paint but do not respond to a
click. And webkit's Dropdown and ThemeSwitcher teleport an overlay to <body>,
which Astro never re-emits into the served markup; the fix is the doc-prose
patch in patches/ extended upstream to tooltip and dropdown. `client:only`
looks like the answer to both and is a trap: an island nested inside a Vue
island never mounts under it, so the controls vanish entirely instead of merely
being inert.
BaseLayout also carries an unrelated change that was already pending in the
working tree: the ScrollArea rules for the "On this page" rail.
This is not one change; it is everything that was sitting uncommitted in the
tree, swept in at the author's request so nothing is left dangling. Two
distinct bodies of work:
The webkit/Tailwind 4 adoption in progress: 68 legacy components removed
(BackendGuidesNav, Badge, Breadcrumb, Button, the NavGrid and tabs .astro
families, the Preact leftovers TableOfContents, SidebarToggleTabGroup,
ExternalLinkIcon and Spoiler, and the rest), Footer rebuilt on the design
system's own Footer, and the supporting changes across content.config.ts,
content.ts, the i18n helpers, src/util and the layouts.
The lint toolchain migration (MM-15912), authored in a parallel session:
flat config in eslint.config.mjs replacing .eslintrc.js and .eslintignore,
eslint 9 with typescript-eslint 8, stylelint 16 with postcss-html and
postcss-scss, and the call sites those rules touched.
Also included: azion.config.mjs and azion/azion.json from the preview deploy,
and .claude/launch.json.
Neither body of work is mine and neither was reviewed here. They are committed
as they stood, so the history has a point to return to before the deploy.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@isaque-bock-azion
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(build): upgrade to Astro 7 and Tailwind CSS 4 - #2315

Open
isaque-bock-azion wants to merge 18 commits into
mainfrom
feat/astro7-tailwind4
Open

feat(build): upgrade to Astro 7 and Tailwind CSS 4#2315
isaque-bock-azion wants to merge 18 commits into
mainfrom
feat/astro7-tailwind4

Conversation

@isaque-bock-azion

@isaque-bock-azionisaque-bock-azion commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

What

Base branch for the docs modernization stack (MM-15897). Two commits, one per migration:

  1. Tailwind CSS 3 → 4 — removes the deprecated @astrojs/tailwind integration (capped at Astro 5). New CSS-first entry in src/styles/main.css (important flag, cascade layers keeping PrimeVue between base and utilities, @source for azion-webkit@1's raw .vue components, v3 preflight compat). azion-webkit@1's entry CSS is vendored into src/styles/webkit-v1-main.css until webkit v4 lands.
  2. Astro 5 → 7 — Astro 7.2.3, Vite 8, Vue 3.5, Content Layer API (content.config.ts + glob loader, slugid, render(entry)), integration bumps (@astrojs/sitemap 1.0.1→3.7.3 included), and the markdown pipeline kept on unified() so all custom remark/rehype plugins keep working. Also fixes the 9 published vulnerabilities in astro <7.0.4.

Notable plumbing

  • Astro 7 renders pages in a new prerender Vite environment that ignores vite.ssr.noExternal and force-externalizes primevue; a small inline config plugin (azion:server-noexternal) bundles primevue/azion-webkit/azion-theme there (PrimeVue 3's ESM uses directory imports Node can't resolve).
  • TablePricing now caches pricing API responses per product_slug for the whole build — the prerender pass fetched once per page and caused transient DNS failures.

Verification

  • pnpm run build:local: 1494 pages in ~2m48s, frontmatter test passing.
  • Output spot-checked: asides, heading anchors, expressive-code blocks, doc-all-data.json / docs-path-by-url.json / docs-llms.txt / .md endpoints, both sitemaps, Tailwind 4 CSS inlined with !important parity.
  • astro preview smoke test in the browser: layout matches production, Algolia search dialog opens and returns results, zero console errors.

Known notes

  • Dev server only: the Algolia search dialog fails to hydrate under astro dev (Vite 8 optimize-deps CJS interop with algoliasearch inside azion-webkit@1). The production build is unaffected — worth a follow-up.
  • Porting the remark/rehype plugins to Sätteri is intentionally out of scope (next PR in the stack).
  • astro-expressive-code 0.44 warns about unknown code-fence languages (code, env, gitignore) in a few MDX files — content fixes, separate PR.

🤖 Generated with Claude Code

Replaces the deprecated @astrojs/tailwind integration (capped at Astro 5)
with Tailwind 4 via @tailwindcss/postcss, unblocking the Astro 7 upgrade.
- Vendor azion-webkit@1's entry CSS (src/styles/webkit-v1-main.css) since
it contained Tailwind v3 directives; the file is removed once webkit v4
lands.
- New Tailwind entry in src/styles/main.css: cascade-layer order keeps
PrimeVue between base and utilities (same cascade as v3), `important`
import flag preserves v3's important:true, @source compiles the raw
.vue components azion-webkit@1 ships, and v3 preflight compatibility
styles keep border/placeholder/cursor defaults.
- Slim tailwind.config.cjs: theme-only copy of what azion-webkit@1's
config provided; its v3-only addUtilities plugins now live in CSS as
@Utility px-shell/px-container. The unused `container.padding` config
is dropped (crashes Tailwind 4's compat layer with custom screens).
- Rename removed v3 utilities: flex-shrink-0 -> shrink-0, rounded ->
rounded-sm.
- Drop autoprefixer (built into Tailwind 4).
Astro 7.2.3, Vite 8, Vue 3.5, and the Content Layer API. Also patches the
9 published vulnerabilities in astro <7.0.4.
Content Layer (mandatory since Astro 6 removed legacy collections):
- Move src/content/config.ts to src/content.config.ts with a glob() loader
and `astro/zod` imports.
- entry.slug -> entry.id and entry.render() -> render(entry) across pages,
layouts, components, and utils. The [lang]/[...slug] route rebuilds the
legacy id (path with extension) from entry.filePath for getGithubEditUrl,
and docs-path-by-url.json now uses entry.filePath directly.
Markdown: keep the unified() processor from @astrojs/markdown-remark so the
custom remark/rehype plugins keep running; porting them to Sätteri is a
follow-up.
Integrations: @astrojs/mdx 7, @astrojs/vue 7, @astrojs/preact 6,
@astrojs/sitemap 1.0.1 -> 3.7.3, astro-expressive-code 0.44,
astro-auto-import 0.5, astro-og-canvas 0.13.
Vite 8 adjustments:
- Tailwind 4 moves from @tailwindcss/postcss to @tailwindcss/vite (Vite 8's
postcss-import no longer resolves the bare `tailwindcss` import).
- A small config plugin bundles primevue/azion-webkit/azion-theme into the
new prerender environment: Astro marks primevue as external there, and
primevue 3's ESM files use directory imports Node cannot resolve.
- Drop the obsolete vite.ssrBuild flag.
TablePricing now caches pricing API responses per product_slug for the whole
build; the prerender pass fetching once per page caused transient DNS
failures.
Build verified: 1494 pages, frontmatter test passing, asides/heading
anchors/expressive-code/JSON+md+llms.txt endpoints/sitemaps all present in
the output.
…th Tailwind (#2316)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@isaque-bock-azion
isaque-bock-azion requested a review from a team as a code ownerAugust 28, 2026 21:28
Ports the header, search trigger and palette from the webkit design
system sample (feat/doc-masthead-action-belt) to the docs site.
- Header: rebuilt on GlobalHeader (.Left/.Brand/.Nav/.Right) with
@container so the search trigger can switch shapes via container
query instead of viewport breakpoints
- HeaderSearch: two-root trigger (IconButton narrow / field-shaped
wide) with @min-[47rem] container query, verbatim from the sample
- HeaderSearchDialog: CommandMenu shell (⌘K shortcut, Dialog/Overlay
owned by webkit) feeding Algolia via direct client.search() across
the four i18n indices; replaces vue-instantsearch + AlgoliaDialog
- HeaderRightSidebar: mobile nav migrated from manual Teleport to
webkit Drawer (side="left", size="small"); closes on palette-open
cross-island event
- DocsTopNav: NavigationMenu extracted into its own client:idle island
so the hover-pill highlight (pointerenter + provide/inject) hydrates
correctly; was dead inside the SSR-only Header.vue
- main.css: added text-overline-md / text-overline-sm Tailwind shims
so Kbd keycaps (⌘K, ESC) render at the token's 12px, not 16px
- Removed: AlgoliaDialog, AlgoliaInstantSearch, AlgoliaIndexHit,
KeyboardKey, DocsSearchTrigger, vue.config.js; dep vue-instantsearch
Deletes public/assets/docs/css/ (theme.css, index.css), and the
orphaned SCSS layers under src/styles/ (breakpoints, functions, icons,
tokens) — none of these were imported anywhere after the webkit/theme
migration; all design tokens now come from @aziontech/theme.
Replaces the hand-rolled footer flex layout with webkit's Footer
component, aligned with the design system's own sample (SiteFooter.vue).
Updates the i18n model: drops the legacy `cta` (phone numbers) and
`copyright` exports, adds `tagline` in their place, as the DS Footer
now renders the signature band with the brand mark and tagline from
the component itself.
- LeftSidebar: drops SidebarContent (Astro/SCSS/TSX trio) and the
sidebarpanelmenu/ pair; now delegates to DocsSidebarMenu (webkit DS)
via the menuToWebkit adapter
- ReadableContent: rewired to webkit's DocProse for article typography
(heading ladder, prose body, callout, code chip); removes the local
.prose/.article SCSS that duplicated those rules
- RightSidebar: swaps the local TableOfContents/TableOfContentsItem
pair for webkit's OnThisPage component
- PageContent/MainLayout: layout adjustments for the webkit rail's
sizing contract (auto column instead of fixed 20rem)
…egrations
- Aside: replaced hand-rolled admonition (inline SVGs + SCSS) with
webkit's doc-callout; maps caution→warning, unwraps <p> to inline
prose, preserves authored titles as lead-in text
- Code: switched from astro-expressive-code to local CodeBlock wrapper
(webkit's syntax highlighting); removes expressive-code integration files
- LinkButton: wraps button roots in `data-doc-chrome` span (display:contents)
so DocProse prose-link paint does not repaint buttons inside article content
- AzionLogo.vue: deleted (unused after Header.vue moved to GlobalHeader.Brand)
- plugins/rehype-autolink-config: adds data-doc-anchor on heading anchors
so DocProse's link rules skip them
- .gitignore: add Azion CLI build artifacts (.edge/, .vulcan, .open-next)
- pnpm-workspace.yaml: pin @aziontech/theme and webkit to current versions
New components used by the sidebar/toc/code refactor:
- CodeBlock: webkit syntax highlighting shell (replaces expressive-code)
- DocPageHeader: page title + metadata band above article content
- DocsSidebar: webkit Sidebar rail wrapper for the left nav
- DocsSidebarMenu: webkit Menu tree rendering the navigation tree
- OnThisPage: webkit table-of-contents for the right sidebar
- menuToWebkit: adapter converting the docs navigation data model
(getNavigationMenu) to webkit's Menu/tree props contract
- integrations/astro-code-blocks: Astro integration wiring CodeBlock
into the remark/rehype pipeline
Adds the auto-generated azion.config.mjs (build preset: astro,
storage bucket for docs-preview-webkit) and the GitHub Actions workflow
for Azion deploy, alongside the azion/ config directory generated by
the CLI.
Replaces the LinkButton compatibility wrapper with @aziontech/webkit/button
imported directly. Props mapped: link→href, severity="secondary"→kind="secondary";
added size="medium" and class="not-prose no-underline" on every call site.
Removes the unused Container import.
DocProse styles every `a` in the article body as a prose link -- link ink,
underline, flat radius -- with `!important`, and steps aside only for
`[data-doc-chrome]`. A webkit Button rendered with `href` is such an `a`, so
every button in the docs body came out blue, underlined and square on top of
its own fill. `class="not-prose no-underline"` never had a chance: the
contract's selector is more specific and important.
The attribute cannot go on the Button itself -- it sets `inheritAttrs: false`
and forwards only `class`, so `data-doc-chrome` is dropped before it reaches
the DOM. DocButton puts it on a wrapping span instead, whose subtree the
contract also excludes, and adds `data-doc-block` for the standard block
spacing.
Same treatment for HeroHome's actions row: the hero CTAs live inside
ReadableContent too and were painted as prose links for the same reason.
…utton
The 650 pages that import the button rendered it as a prose link -- blue,
underlined, square corners over the button's own fill -- because DocProse
styles every `a` in the article body and only stops at `[data-doc-chrome]`.
DocButton carries that boundary; swapping the import is the whole fix.
Two leftovers from the LinkButton era go with it. `class="not-prose
no-underline"` was reaching for this same escape and never worked, and
`iconPos="left"` names a prop the webkit Button does not have -- it has one
icon slot, always leading -- so it was dropped on the floor either way.
Mechanical: the import line was identical in all 650 files, `<Button` has no
closing-tag form here, and occurrences inside code fences were left alone.
All 650 files re-checked through the MDX compiler.
… mounted
Every documentation page logged "Hydration completed but contains mismatches".
DocProse's copy tip lives in a `<Teleport to="body">`, and Astro does not
re-emit Vue's teleport payload into the served markup -- so on hydration Vue
walks into `<body>` looking for the teleport anchor and finds the page's own
first element instead. Vue then discards the server DOM for that subtree and
re-renders it on the client, on every page.
Gating the teleport on a mounted flag keeps the server render and the client's
first render identical -- the tip is a `v-if` comment inline in the root either
way -- and it costs nothing, since the tip only ever opens on pointerover.
Carried as a pnpm patch because the fix belongs upstream in @aziontech/webkit;
drop `patches/` and the `patchedDependencies` entry once a release ships it.
The authoring API is unchanged: 328 tab blocks across 178 content files keep
writing `<Tabs>` with `<Fragment slot="tab.x">` / `slot="panel.x">`, so not one
.mdx was touched. `Tabs.ts` re-exports the SFC because the content imports
`~/components/tabs/Tabs` without an extension and Vite's resolve.extensions
does not include .vue.
Panels stay in the DOM behind `hidden` instead of using TabView's Panel, which
mounts its slot behind `v-if`. Dropping inactive panels would take half the
prose of every tabbed page out of the served HTML -- out of Ctrl+F, out of the
crawl, and out of reach of the heading-anchor wiring ReadableContent installs
once on mount. The cost is TabView's sliding indicator and edge fades; this
component owns the active value, the ids and the arrow keys instead, and the
chips fall back to Item's standalone `selected` styling, which paints the same.
Two things the port had to fix rather than carry over. Astro hands slots to the
island in resolution order, not authoring order, so a panel holding an island
(`<Code>`) overtakes the panel written above it -- the tabs are now the source
of order and panels are paired by key, which also moves the default from "first
panel" to "first tab" (they diverge in 23 of the 328 blocks, some of them real
content typos). And the shared store subscribes only on mount: `tabStore` is a
module singleton, so subscribing during SSR would leak a listener per render
and let one request's choice bleed into the next.
PricingTableOfContents comes along as Vue over webkit's DocOnThisPage, keeping
the two pricing-specific behaviors: the `-1` anchor remap for the `real` tab,
and the scroll-spy that skips headings under `[hidden]`.
… originals
Overline, TitleSection and the four-part card family (CardBase,
CardBaseClickable, CardTitle, CardDescription) all had an equivalent already
shipping in @aziontech/webkit, so they are gone: 502 lines of local markup
replaced by Overline, SectionTitle, DocCard and DocCardGroup.
The card grid is the change worth reading. DocCardGroup is both the frame and
the rules -- cards abut inside it and the hairlines between them are the
group's own gaps -- so Cardlist no longer draws a grid, a gap or a border of
its own. Two deliberate omissions: no icon, because the content data carries
SVG paths while DocCard's `icon` expects a PrimeIcons class, and no CTA link,
because the data has no call-to-action copy and inventing i18n strings is not a
refactor. The old card's arrow goes with them; the whole card is still the
link.
Overline is a `w-fit` inline element rather than a block, so centering moved
from `text-center` to `self-center` on the parent. And in the branch where
SectionTitle takes over the whole header, `titleTag` stops having an effect --
the design system fixes h2 there. That branch compiles but no page exercises it
today: none of the 70 call sites passes a `title`.
The hero stays hand-built on purpose. webkit's HeroTitle always renders the
highlighted span at the *start* of the headline, and both homes author it at
the end ("Welcome to <span>Azion Docs</span>"). With no title slot, adopting it
would mean losing the accent, inverting the sentence, or rewriting both
headlines -- an editorial call, not a refactor.
ThemeSwitcher becomes an adapter over webkit's ThemeSwitcher (196 -> 112
lines): the dropdown, its trigger, the viewport measuring and the document
click/Escape listeners all go, and what stays is the part the design system
does not own -- reading and writing the preference. The stored value keeps its
exact old shape, the `prefers-color-scheme` key holding 'System' | 'Dark' |
'Light', because BaseLayout reads that same key in a blocking inline script and
every returning visitor already has one of those strings saved; the lowercase
mode names webkit speaks are paired to them here rather than migrating anyone's
storage. Visually this turns a dropdown into a segmented icon group.
SystemStatus takes its dot and label from StatusIndicator, so severity comes
from theme tokens instead of the inline hex the component used to paint with.
The pill around it stays a plain anchor: webkit's Button takes its content
through a `label` string and exposes no default slot, so wrapping the indicator
in one would render the text and drop the dot. Also removes STATUS_PAGE and its
`status` ref, which nothing read.
SelectLang moves to webkit's Dropdown -- not Select, which is still draft in
the catalog. `placement="auto"` replaces the hand-rolled "is there room below?"
measurement. The options stay real anchors so the translated route is still
crawlable and middle-clickable.
Two known defects are documented in BaseLayout rather than worked around.
Nothing nested in this Footer island hydrates today, SystemStatus included, and
that predates the webkit adoption -- the controls paint but do not respond to a
click. And webkit's Dropdown and ThemeSwitcher teleport an overlay to <body>,
which Astro never re-emits into the served markup; the fix is the doc-prose
patch in patches/ extended upstream to tooltip and dropdown. `client:only`
looks like the answer to both and is a trap: an island nested inside a Vue
island never mounts under it, so the controls vanish entirely instead of merely
being inert.
BaseLayout also carries an unrelated change that was already pending in the
working tree: the ScrollArea rules for the "On this page" rail.
This is not one change; it is everything that was sitting uncommitted in the
tree, swept in at the author's request so nothing is left dangling. Two
distinct bodies of work:
The webkit/Tailwind 4 adoption in progress: 68 legacy components removed
(BackendGuidesNav, Badge, Breadcrumb, Button, the NavGrid and tabs .astro
families, the Preact leftovers TableOfContents, SidebarToggleTabGroup,
ExternalLinkIcon and Spoiler, and the rest), Footer rebuilt on the design
system's own Footer, and the supporting changes across content.config.ts,
content.ts, the i18n helpers, src/util and the layouts.
The lint toolchain migration (MM-15912), authored in a parallel session:
flat config in eslint.config.mjs replacing .eslintrc.js and .eslintignore,
eslint 9 with typescript-eslint 8, stylelint 16 with postcss-html and
postcss-scss, and the call sites those rules touched.
Also included: azion.config.mjs and azion/azion.json from the preview deploy,
and .claude/launch.json.
Neither body of work is mine and neither was reviewed here. They are committed
as they stood, so the history has a point to return to before the deploy.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@isaque-bock-azion
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(build): upgrade to Astro 7 and Tailwind CSS 4 - #2315

Open
isaque-bock-azion wants to merge 18 commits into
mainfrom
feat/astro7-tailwind4
Open

feat(build): upgrade to Astro 7 and Tailwind CSS 4#2315
isaque-bock-azion wants to merge 18 commits into
mainfrom
feat/astro7-tailwind4

Conversation

@isaque-bock-azion

@isaque-bock-azionisaque-bock-azion commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

What

Base branch for the docs modernization stack (MM-15897). Two commits, one per migration:

  1. Tailwind CSS 3 → 4 — removes the deprecated @astrojs/tailwind integration (capped at Astro 5). New CSS-first entry in src/styles/main.css (important flag, cascade layers keeping PrimeVue between base and utilities, @source for azion-webkit@1's raw .vue components, v3 preflight compat). azion-webkit@1's entry CSS is vendored into src/styles/webkit-v1-main.css until webkit v4 lands.
  2. Astro 5 → 7 — Astro 7.2.3, Vite 8, Vue 3.5, Content Layer API (content.config.ts + glob loader, slugid, render(entry)), integration bumps (@astrojs/sitemap 1.0.1→3.7.3 included), and the markdown pipeline kept on unified() so all custom remark/rehype plugins keep working. Also fixes the 9 published vulnerabilities in astro <7.0.4.

Notable plumbing

  • Astro 7 renders pages in a new prerender Vite environment that ignores vite.ssr.noExternal and force-externalizes primevue; a small inline config plugin (azion:server-noexternal) bundles primevue/azion-webkit/azion-theme there (PrimeVue 3's ESM uses directory imports Node can't resolve).
  • TablePricing now caches pricing API responses per product_slug for the whole build — the prerender pass fetched once per page and caused transient DNS failures.

Verification

  • pnpm run build:local: 1494 pages in ~2m48s, frontmatter test passing.
  • Output spot-checked: asides, heading anchors, expressive-code blocks, doc-all-data.json / docs-path-by-url.json / docs-llms.txt / .md endpoints, both sitemaps, Tailwind 4 CSS inlined with !important parity.
  • astro preview smoke test in the browser: layout matches production, Algolia search dialog opens and returns results, zero console errors.

Known notes

  • Dev server only: the Algolia search dialog fails to hydrate under astro dev (Vite 8 optimize-deps CJS interop with algoliasearch inside azion-webkit@1). The production build is unaffected — worth a follow-up.
  • Porting the remark/rehype plugins to Sätteri is intentionally out of scope (next PR in the stack).
  • astro-expressive-code 0.44 warns about unknown code-fence languages (code, env, gitignore) in a few MDX files — content fixes, separate PR.

🤖 Generated with Claude Code

Replaces the deprecated @astrojs/tailwind integration (capped at Astro 5)
with Tailwind 4 via @tailwindcss/postcss, unblocking the Astro 7 upgrade.
- Vendor azion-webkit@1's entry CSS (src/styles/webkit-v1-main.css) since
it contained Tailwind v3 directives; the file is removed once webkit v4
lands.
- New Tailwind entry in src/styles/main.css: cascade-layer order keeps
PrimeVue between base and utilities (same cascade as v3), `important`
import flag preserves v3's important:true, @source compiles the raw
.vue components azion-webkit@1 ships, and v3 preflight compatibility
styles keep border/placeholder/cursor defaults.
- Slim tailwind.config.cjs: theme-only copy of what azion-webkit@1's
config provided; its v3-only addUtilities plugins now live in CSS as
@Utility px-shell/px-container. The unused `container.padding` config
is dropped (crashes Tailwind 4's compat layer with custom screens).
- Rename removed v3 utilities: flex-shrink-0 -> shrink-0, rounded ->
rounded-sm.
- Drop autoprefixer (built into Tailwind 4).
Astro 7.2.3, Vite 8, Vue 3.5, and the Content Layer API. Also patches the
9 published vulnerabilities in astro <7.0.4.
Content Layer (mandatory since Astro 6 removed legacy collections):
- Move src/content/config.ts to src/content.config.ts with a glob() loader
and `astro/zod` imports.
- entry.slug -> entry.id and entry.render() -> render(entry) across pages,
layouts, components, and utils. The [lang]/[...slug] route rebuilds the
legacy id (path with extension) from entry.filePath for getGithubEditUrl,
and docs-path-by-url.json now uses entry.filePath directly.
Markdown: keep the unified() processor from @astrojs/markdown-remark so the
custom remark/rehype plugins keep running; porting them to Sätteri is a
follow-up.
Integrations: @astrojs/mdx 7, @astrojs/vue 7, @astrojs/preact 6,
@astrojs/sitemap 1.0.1 -> 3.7.3, astro-expressive-code 0.44,
astro-auto-import 0.5, astro-og-canvas 0.13.
Vite 8 adjustments:
- Tailwind 4 moves from @tailwindcss/postcss to @tailwindcss/vite (Vite 8's
postcss-import no longer resolves the bare `tailwindcss` import).
- A small config plugin bundles primevue/azion-webkit/azion-theme into the
new prerender environment: Astro marks primevue as external there, and
primevue 3's ESM files use directory imports Node cannot resolve.
- Drop the obsolete vite.ssrBuild flag.
TablePricing now caches pricing API responses per product_slug for the whole
build; the prerender pass fetching once per page caused transient DNS
failures.
Build verified: 1494 pages, frontmatter test passing, asides/heading
anchors/expressive-code/JSON+md+llms.txt endpoints/sitemaps all present in
the output.
…th Tailwind (#2316)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@isaque-bock-azion
isaque-bock-azion requested a review from a team as a code ownerAugust 28, 2026 21:28
Ports the header, search trigger and palette from the webkit design
system sample (feat/doc-masthead-action-belt) to the docs site.
- Header: rebuilt on GlobalHeader (.Left/.Brand/.Nav/.Right) with
@container so the search trigger can switch shapes via container
query instead of viewport breakpoints
- HeaderSearch: two-root trigger (IconButton narrow / field-shaped
wide) with @min-[47rem] container query, verbatim from the sample
- HeaderSearchDialog: CommandMenu shell (⌘K shortcut, Dialog/Overlay
owned by webkit) feeding Algolia via direct client.search() across
the four i18n indices; replaces vue-instantsearch + AlgoliaDialog
- HeaderRightSidebar: mobile nav migrated from manual Teleport to
webkit Drawer (side="left", size="small"); closes on palette-open
cross-island event
- DocsTopNav: NavigationMenu extracted into its own client:idle island
so the hover-pill highlight (pointerenter + provide/inject) hydrates
correctly; was dead inside the SSR-only Header.vue
- main.css: added text-overline-md / text-overline-sm Tailwind shims
so Kbd keycaps (⌘K, ESC) render at the token's 12px, not 16px
- Removed: AlgoliaDialog, AlgoliaInstantSearch, AlgoliaIndexHit,
KeyboardKey, DocsSearchTrigger, vue.config.js; dep vue-instantsearch
Deletes public/assets/docs/css/ (theme.css, index.css), and the
orphaned SCSS layers under src/styles/ (breakpoints, functions, icons,
tokens) — none of these were imported anywhere after the webkit/theme
migration; all design tokens now come from @aziontech/theme.
Replaces the hand-rolled footer flex layout with webkit's Footer
component, aligned with the design system's own sample (SiteFooter.vue).
Updates the i18n model: drops the legacy `cta` (phone numbers) and
`copyright` exports, adds `tagline` in their place, as the DS Footer
now renders the signature band with the brand mark and tagline from
the component itself.
- LeftSidebar: drops SidebarContent (Astro/SCSS/TSX trio) and the
sidebarpanelmenu/ pair; now delegates to DocsSidebarMenu (webkit DS)
via the menuToWebkit adapter
- ReadableContent: rewired to webkit's DocProse for article typography
(heading ladder, prose body, callout, code chip); removes the local
.prose/.article SCSS that duplicated those rules
- RightSidebar: swaps the local TableOfContents/TableOfContentsItem
pair for webkit's OnThisPage component
- PageContent/MainLayout: layout adjustments for the webkit rail's
sizing contract (auto column instead of fixed 20rem)
…egrations
- Aside: replaced hand-rolled admonition (inline SVGs + SCSS) with
webkit's doc-callout; maps caution→warning, unwraps <p> to inline
prose, preserves authored titles as lead-in text
- Code: switched from astro-expressive-code to local CodeBlock wrapper
(webkit's syntax highlighting); removes expressive-code integration files
- LinkButton: wraps button roots in `data-doc-chrome` span (display:contents)
so DocProse prose-link paint does not repaint buttons inside article content
- AzionLogo.vue: deleted (unused after Header.vue moved to GlobalHeader.Brand)
- plugins/rehype-autolink-config: adds data-doc-anchor on heading anchors
so DocProse's link rules skip them
- .gitignore: add Azion CLI build artifacts (.edge/, .vulcan, .open-next)
- pnpm-workspace.yaml: pin @aziontech/theme and webkit to current versions
New components used by the sidebar/toc/code refactor:
- CodeBlock: webkit syntax highlighting shell (replaces expressive-code)
- DocPageHeader: page title + metadata band above article content
- DocsSidebar: webkit Sidebar rail wrapper for the left nav
- DocsSidebarMenu: webkit Menu tree rendering the navigation tree
- OnThisPage: webkit table-of-contents for the right sidebar
- menuToWebkit: adapter converting the docs navigation data model
(getNavigationMenu) to webkit's Menu/tree props contract
- integrations/astro-code-blocks: Astro integration wiring CodeBlock
into the remark/rehype pipeline
Adds the auto-generated azion.config.mjs (build preset: astro,
storage bucket for docs-preview-webkit) and the GitHub Actions workflow
for Azion deploy, alongside the azion/ config directory generated by
the CLI.
Replaces the LinkButton compatibility wrapper with @aziontech/webkit/button
imported directly. Props mapped: link→href, severity="secondary"→kind="secondary";
added size="medium" and class="not-prose no-underline" on every call site.
Removes the unused Container import.
DocProse styles every `a` in the article body as a prose link -- link ink,
underline, flat radius -- with `!important`, and steps aside only for
`[data-doc-chrome]`. A webkit Button rendered with `href` is such an `a`, so
every button in the docs body came out blue, underlined and square on top of
its own fill. `class="not-prose no-underline"` never had a chance: the
contract's selector is more specific and important.
The attribute cannot go on the Button itself -- it sets `inheritAttrs: false`
and forwards only `class`, so `data-doc-chrome` is dropped before it reaches
the DOM. DocButton puts it on a wrapping span instead, whose subtree the
contract also excludes, and adds `data-doc-block` for the standard block
spacing.
Same treatment for HeroHome's actions row: the hero CTAs live inside
ReadableContent too and were painted as prose links for the same reason.
…utton
The 650 pages that import the button rendered it as a prose link -- blue,
underlined, square corners over the button's own fill -- because DocProse
styles every `a` in the article body and only stops at `[data-doc-chrome]`.
DocButton carries that boundary; swapping the import is the whole fix.
Two leftovers from the LinkButton era go with it. `class="not-prose
no-underline"` was reaching for this same escape and never worked, and
`iconPos="left"` names a prop the webkit Button does not have -- it has one
icon slot, always leading -- so it was dropped on the floor either way.
Mechanical: the import line was identical in all 650 files, `<Button` has no
closing-tag form here, and occurrences inside code fences were left alone.
All 650 files re-checked through the MDX compiler.
… mounted
Every documentation page logged "Hydration completed but contains mismatches".
DocProse's copy tip lives in a `<Teleport to="body">`, and Astro does not
re-emit Vue's teleport payload into the served markup -- so on hydration Vue
walks into `<body>` looking for the teleport anchor and finds the page's own
first element instead. Vue then discards the server DOM for that subtree and
re-renders it on the client, on every page.
Gating the teleport on a mounted flag keeps the server render and the client's
first render identical -- the tip is a `v-if` comment inline in the root either
way -- and it costs nothing, since the tip only ever opens on pointerover.
Carried as a pnpm patch because the fix belongs upstream in @aziontech/webkit;
drop `patches/` and the `patchedDependencies` entry once a release ships it.
The authoring API is unchanged: 328 tab blocks across 178 content files keep
writing `<Tabs>` with `<Fragment slot="tab.x">` / `slot="panel.x">`, so not one
.mdx was touched. `Tabs.ts` re-exports the SFC because the content imports
`~/components/tabs/Tabs` without an extension and Vite's resolve.extensions
does not include .vue.
Panels stay in the DOM behind `hidden` instead of using TabView's Panel, which
mounts its slot behind `v-if`. Dropping inactive panels would take half the
prose of every tabbed page out of the served HTML -- out of Ctrl+F, out of the
crawl, and out of reach of the heading-anchor wiring ReadableContent installs
once on mount. The cost is TabView's sliding indicator and edge fades; this
component owns the active value, the ids and the arrow keys instead, and the
chips fall back to Item's standalone `selected` styling, which paints the same.
Two things the port had to fix rather than carry over. Astro hands slots to the
island in resolution order, not authoring order, so a panel holding an island
(`<Code>`) overtakes the panel written above it -- the tabs are now the source
of order and panels are paired by key, which also moves the default from "first
panel" to "first tab" (they diverge in 23 of the 328 blocks, some of them real
content typos). And the shared store subscribes only on mount: `tabStore` is a
module singleton, so subscribing during SSR would leak a listener per render
and let one request's choice bleed into the next.
PricingTableOfContents comes along as Vue over webkit's DocOnThisPage, keeping
the two pricing-specific behaviors: the `-1` anchor remap for the `real` tab,
and the scroll-spy that skips headings under `[hidden]`.
… originals
Overline, TitleSection and the four-part card family (CardBase,
CardBaseClickable, CardTitle, CardDescription) all had an equivalent already
shipping in @aziontech/webkit, so they are gone: 502 lines of local markup
replaced by Overline, SectionTitle, DocCard and DocCardGroup.
The card grid is the change worth reading. DocCardGroup is both the frame and
the rules -- cards abut inside it and the hairlines between them are the
group's own gaps -- so Cardlist no longer draws a grid, a gap or a border of
its own. Two deliberate omissions: no icon, because the content data carries
SVG paths while DocCard's `icon` expects a PrimeIcons class, and no CTA link,
because the data has no call-to-action copy and inventing i18n strings is not a
refactor. The old card's arrow goes with them; the whole card is still the
link.
Overline is a `w-fit` inline element rather than a block, so centering moved
from `text-center` to `self-center` on the parent. And in the branch where
SectionTitle takes over the whole header, `titleTag` stops having an effect --
the design system fixes h2 there. That branch compiles but no page exercises it
today: none of the 70 call sites passes a `title`.
The hero stays hand-built on purpose. webkit's HeroTitle always renders the
highlighted span at the *start* of the headline, and both homes author it at
the end ("Welcome to <span>Azion Docs</span>"). With no title slot, adopting it
would mean losing the accent, inverting the sentence, or rewriting both
headlines -- an editorial call, not a refactor.
ThemeSwitcher becomes an adapter over webkit's ThemeSwitcher (196 -> 112
lines): the dropdown, its trigger, the viewport measuring and the document
click/Escape listeners all go, and what stays is the part the design system
does not own -- reading and writing the preference. The stored value keeps its
exact old shape, the `prefers-color-scheme` key holding 'System' | 'Dark' |
'Light', because BaseLayout reads that same key in a blocking inline script and
every returning visitor already has one of those strings saved; the lowercase
mode names webkit speaks are paired to them here rather than migrating anyone's
storage. Visually this turns a dropdown into a segmented icon group.
SystemStatus takes its dot and label from StatusIndicator, so severity comes
from theme tokens instead of the inline hex the component used to paint with.
The pill around it stays a plain anchor: webkit's Button takes its content
through a `label` string and exposes no default slot, so wrapping the indicator
in one would render the text and drop the dot. Also removes STATUS_PAGE and its
`status` ref, which nothing read.
SelectLang moves to webkit's Dropdown -- not Select, which is still draft in
the catalog. `placement="auto"` replaces the hand-rolled "is there room below?"
measurement. The options stay real anchors so the translated route is still
crawlable and middle-clickable.
Two known defects are documented in BaseLayout rather than worked around.
Nothing nested in this Footer island hydrates today, SystemStatus included, and
that predates the webkit adoption -- the controls paint but do not respond to a
click. And webkit's Dropdown and ThemeSwitcher teleport an overlay to <body>,
which Astro never re-emits into the served markup; the fix is the doc-prose
patch in patches/ extended upstream to tooltip and dropdown. `client:only`
looks like the answer to both and is a trap: an island nested inside a Vue
island never mounts under it, so the controls vanish entirely instead of merely
being inert.
BaseLayout also carries an unrelated change that was already pending in the
working tree: the ScrollArea rules for the "On this page" rail.
This is not one change; it is everything that was sitting uncommitted in the
tree, swept in at the author's request so nothing is left dangling. Two
distinct bodies of work:
The webkit/Tailwind 4 adoption in progress: 68 legacy components removed
(BackendGuidesNav, Badge, Breadcrumb, Button, the NavGrid and tabs .astro
families, the Preact leftovers TableOfContents, SidebarToggleTabGroup,
ExternalLinkIcon and Spoiler, and the rest), Footer rebuilt on the design
system's own Footer, and the supporting changes across content.config.ts,
content.ts, the i18n helpers, src/util and the layouts.
The lint toolchain migration (MM-15912), authored in a parallel session:
flat config in eslint.config.mjs replacing .eslintrc.js and .eslintignore,
eslint 9 with typescript-eslint 8, stylelint 16 with postcss-html and
postcss-scss, and the call sites those rules touched.
Also included: azion.config.mjs and azion/azion.json from the preview deploy,
and .claude/launch.json.
Neither body of work is mine and neither was reviewed here. They are committed
as they stood, so the history has a point to return to before the deploy.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@isaque-bock-azion
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(build): upgrade to Astro 7 and Tailwind CSS 4 - #2315

Open
isaque-bock-azion wants to merge 18 commits into
mainfrom
feat/astro7-tailwind4
Open

feat(build): upgrade to Astro 7 and Tailwind CSS 4#2315
isaque-bock-azion wants to merge 18 commits into
mainfrom
feat/astro7-tailwind4

Conversation

@isaque-bock-azion

@isaque-bock-azionisaque-bock-azion commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

What

Base branch for the docs modernization stack (MM-15897). Two commits, one per migration:

  1. Tailwind CSS 3 → 4 — removes the deprecated @astrojs/tailwind integration (capped at Astro 5). New CSS-first entry in src/styles/main.css (important flag, cascade layers keeping PrimeVue between base and utilities, @source for azion-webkit@1's raw .vue components, v3 preflight compat). azion-webkit@1's entry CSS is vendored into src/styles/webkit-v1-main.css until webkit v4 lands.
  2. Astro 5 → 7 — Astro 7.2.3, Vite 8, Vue 3.5, Content Layer API (content.config.ts + glob loader, slugid, render(entry)), integration bumps (@astrojs/sitemap 1.0.1→3.7.3 included), and the markdown pipeline kept on unified() so all custom remark/rehype plugins keep working. Also fixes the 9 published vulnerabilities in astro <7.0.4.

Notable plumbing

  • Astro 7 renders pages in a new prerender Vite environment that ignores vite.ssr.noExternal and force-externalizes primevue; a small inline config plugin (azion:server-noexternal) bundles primevue/azion-webkit/azion-theme there (PrimeVue 3's ESM uses directory imports Node can't resolve).
  • TablePricing now caches pricing API responses per product_slug for the whole build — the prerender pass fetched once per page and caused transient DNS failures.

Verification

  • pnpm run build:local: 1494 pages in ~2m48s, frontmatter test passing.
  • Output spot-checked: asides, heading anchors, expressive-code blocks, doc-all-data.json / docs-path-by-url.json / docs-llms.txt / .md endpoints, both sitemaps, Tailwind 4 CSS inlined with !important parity.
  • astro preview smoke test in the browser: layout matches production, Algolia search dialog opens and returns results, zero console errors.

Known notes

  • Dev server only: the Algolia search dialog fails to hydrate under astro dev (Vite 8 optimize-deps CJS interop with algoliasearch inside azion-webkit@1). The production build is unaffected — worth a follow-up.
  • Porting the remark/rehype plugins to Sätteri is intentionally out of scope (next PR in the stack).
  • astro-expressive-code 0.44 warns about unknown code-fence languages (code, env, gitignore) in a few MDX files — content fixes, separate PR.

🤖 Generated with Claude Code

Replaces the deprecated @astrojs/tailwind integration (capped at Astro 5)
with Tailwind 4 via @tailwindcss/postcss, unblocking the Astro 7 upgrade.
- Vendor azion-webkit@1's entry CSS (src/styles/webkit-v1-main.css) since
it contained Tailwind v3 directives; the file is removed once webkit v4
lands.
- New Tailwind entry in src/styles/main.css: cascade-layer order keeps
PrimeVue between base and utilities (same cascade as v3), `important`
import flag preserves v3's important:true, @source compiles the raw
.vue components azion-webkit@1 ships, and v3 preflight compatibility
styles keep border/placeholder/cursor defaults.
- Slim tailwind.config.cjs: theme-only copy of what azion-webkit@1's
config provided; its v3-only addUtilities plugins now live in CSS as
@Utility px-shell/px-container. The unused `container.padding` config
is dropped (crashes Tailwind 4's compat layer with custom screens).
- Rename removed v3 utilities: flex-shrink-0 -> shrink-0, rounded ->
rounded-sm.
- Drop autoprefixer (built into Tailwind 4).
Astro 7.2.3, Vite 8, Vue 3.5, and the Content Layer API. Also patches the
9 published vulnerabilities in astro <7.0.4.
Content Layer (mandatory since Astro 6 removed legacy collections):
- Move src/content/config.ts to src/content.config.ts with a glob() loader
and `astro/zod` imports.
- entry.slug -> entry.id and entry.render() -> render(entry) across pages,
layouts, components, and utils. The [lang]/[...slug] route rebuilds the
legacy id (path with extension) from entry.filePath for getGithubEditUrl,
and docs-path-by-url.json now uses entry.filePath directly.
Markdown: keep the unified() processor from @astrojs/markdown-remark so the
custom remark/rehype plugins keep running; porting them to Sätteri is a
follow-up.
Integrations: @astrojs/mdx 7, @astrojs/vue 7, @astrojs/preact 6,
@astrojs/sitemap 1.0.1 -> 3.7.3, astro-expressive-code 0.44,
astro-auto-import 0.5, astro-og-canvas 0.13.
Vite 8 adjustments:
- Tailwind 4 moves from @tailwindcss/postcss to @tailwindcss/vite (Vite 8's
postcss-import no longer resolves the bare `tailwindcss` import).
- A small config plugin bundles primevue/azion-webkit/azion-theme into the
new prerender environment: Astro marks primevue as external there, and
primevue 3's ESM files use directory imports Node cannot resolve.
- Drop the obsolete vite.ssrBuild flag.
TablePricing now caches pricing API responses per product_slug for the whole
build; the prerender pass fetching once per page caused transient DNS
failures.
Build verified: 1494 pages, frontmatter test passing, asides/heading
anchors/expressive-code/JSON+md+llms.txt endpoints/sitemaps all present in
the output.
…th Tailwind (#2316)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@isaque-bock-azion
isaque-bock-azion requested a review from a team as a code ownerAugust 28, 2026 21:28
Ports the header, search trigger and palette from the webkit design
system sample (feat/doc-masthead-action-belt) to the docs site.
- Header: rebuilt on GlobalHeader (.Left/.Brand/.Nav/.Right) with
@container so the search trigger can switch shapes via container
query instead of viewport breakpoints
- HeaderSearch: two-root trigger (IconButton narrow / field-shaped
wide) with @min-[47rem] container query, verbatim from the sample
- HeaderSearchDialog: CommandMenu shell (⌘K shortcut, Dialog/Overlay
owned by webkit) feeding Algolia via direct client.search() across
the four i18n indices; replaces vue-instantsearch + AlgoliaDialog
- HeaderRightSidebar: mobile nav migrated from manual Teleport to
webkit Drawer (side="left", size="small"); closes on palette-open
cross-island event
- DocsTopNav: NavigationMenu extracted into its own client:idle island
so the hover-pill highlight (pointerenter + provide/inject) hydrates
correctly; was dead inside the SSR-only Header.vue
- main.css: added text-overline-md / text-overline-sm Tailwind shims
so Kbd keycaps (⌘K, ESC) render at the token's 12px, not 16px
- Removed: AlgoliaDialog, AlgoliaInstantSearch, AlgoliaIndexHit,
KeyboardKey, DocsSearchTrigger, vue.config.js; dep vue-instantsearch
Deletes public/assets/docs/css/ (theme.css, index.css), and the
orphaned SCSS layers under src/styles/ (breakpoints, functions, icons,
tokens) — none of these were imported anywhere after the webkit/theme
migration; all design tokens now come from @aziontech/theme.
Replaces the hand-rolled footer flex layout with webkit's Footer
component, aligned with the design system's own sample (SiteFooter.vue).
Updates the i18n model: drops the legacy `cta` (phone numbers) and
`copyright` exports, adds `tagline` in their place, as the DS Footer
now renders the signature band with the brand mark and tagline from
the component itself.
- LeftSidebar: drops SidebarContent (Astro/SCSS/TSX trio) and the
sidebarpanelmenu/ pair; now delegates to DocsSidebarMenu (webkit DS)
via the menuToWebkit adapter
- ReadableContent: rewired to webkit's DocProse for article typography
(heading ladder, prose body, callout, code chip); removes the local
.prose/.article SCSS that duplicated those rules
- RightSidebar: swaps the local TableOfContents/TableOfContentsItem
pair for webkit's OnThisPage component
- PageContent/MainLayout: layout adjustments for the webkit rail's
sizing contract (auto column instead of fixed 20rem)
…egrations
- Aside: replaced hand-rolled admonition (inline SVGs + SCSS) with
webkit's doc-callout; maps caution→warning, unwraps <p> to inline
prose, preserves authored titles as lead-in text
- Code: switched from astro-expressive-code to local CodeBlock wrapper
(webkit's syntax highlighting); removes expressive-code integration files
- LinkButton: wraps button roots in `data-doc-chrome` span (display:contents)
so DocProse prose-link paint does not repaint buttons inside article content
- AzionLogo.vue: deleted (unused after Header.vue moved to GlobalHeader.Brand)
- plugins/rehype-autolink-config: adds data-doc-anchor on heading anchors
so DocProse's link rules skip them
- .gitignore: add Azion CLI build artifacts (.edge/, .vulcan, .open-next)
- pnpm-workspace.yaml: pin @aziontech/theme and webkit to current versions
New components used by the sidebar/toc/code refactor:
- CodeBlock: webkit syntax highlighting shell (replaces expressive-code)
- DocPageHeader: page title + metadata band above article content
- DocsSidebar: webkit Sidebar rail wrapper for the left nav
- DocsSidebarMenu: webkit Menu tree rendering the navigation tree
- OnThisPage: webkit table-of-contents for the right sidebar
- menuToWebkit: adapter converting the docs navigation data model
(getNavigationMenu) to webkit's Menu/tree props contract
- integrations/astro-code-blocks: Astro integration wiring CodeBlock
into the remark/rehype pipeline
Adds the auto-generated azion.config.mjs (build preset: astro,
storage bucket for docs-preview-webkit) and the GitHub Actions workflow
for Azion deploy, alongside the azion/ config directory generated by
the CLI.
Replaces the LinkButton compatibility wrapper with @aziontech/webkit/button
imported directly. Props mapped: link→href, severity="secondary"→kind="secondary";
added size="medium" and class="not-prose no-underline" on every call site.
Removes the unused Container import.
DocProse styles every `a` in the article body as a prose link -- link ink,
underline, flat radius -- with `!important`, and steps aside only for
`[data-doc-chrome]`. A webkit Button rendered with `href` is such an `a`, so
every button in the docs body came out blue, underlined and square on top of
its own fill. `class="not-prose no-underline"` never had a chance: the
contract's selector is more specific and important.
The attribute cannot go on the Button itself -- it sets `inheritAttrs: false`
and forwards only `class`, so `data-doc-chrome` is dropped before it reaches
the DOM. DocButton puts it on a wrapping span instead, whose subtree the
contract also excludes, and adds `data-doc-block` for the standard block
spacing.
Same treatment for HeroHome's actions row: the hero CTAs live inside
ReadableContent too and were painted as prose links for the same reason.
…utton
The 650 pages that import the button rendered it as a prose link -- blue,
underlined, square corners over the button's own fill -- because DocProse
styles every `a` in the article body and only stops at `[data-doc-chrome]`.
DocButton carries that boundary; swapping the import is the whole fix.
Two leftovers from the LinkButton era go with it. `class="not-prose
no-underline"` was reaching for this same escape and never worked, and
`iconPos="left"` names a prop the webkit Button does not have -- it has one
icon slot, always leading -- so it was dropped on the floor either way.
Mechanical: the import line was identical in all 650 files, `<Button` has no
closing-tag form here, and occurrences inside code fences were left alone.
All 650 files re-checked through the MDX compiler.
… mounted
Every documentation page logged "Hydration completed but contains mismatches".
DocProse's copy tip lives in a `<Teleport to="body">`, and Astro does not
re-emit Vue's teleport payload into the served markup -- so on hydration Vue
walks into `<body>` looking for the teleport anchor and finds the page's own
first element instead. Vue then discards the server DOM for that subtree and
re-renders it on the client, on every page.
Gating the teleport on a mounted flag keeps the server render and the client's
first render identical -- the tip is a `v-if` comment inline in the root either
way -- and it costs nothing, since the tip only ever opens on pointerover.
Carried as a pnpm patch because the fix belongs upstream in @aziontech/webkit;
drop `patches/` and the `patchedDependencies` entry once a release ships it.
The authoring API is unchanged: 328 tab blocks across 178 content files keep
writing `<Tabs>` with `<Fragment slot="tab.x">` / `slot="panel.x">`, so not one
.mdx was touched. `Tabs.ts` re-exports the SFC because the content imports
`~/components/tabs/Tabs` without an extension and Vite's resolve.extensions
does not include .vue.
Panels stay in the DOM behind `hidden` instead of using TabView's Panel, which
mounts its slot behind `v-if`. Dropping inactive panels would take half the
prose of every tabbed page out of the served HTML -- out of Ctrl+F, out of the
crawl, and out of reach of the heading-anchor wiring ReadableContent installs
once on mount. The cost is TabView's sliding indicator and edge fades; this
component owns the active value, the ids and the arrow keys instead, and the
chips fall back to Item's standalone `selected` styling, which paints the same.
Two things the port had to fix rather than carry over. Astro hands slots to the
island in resolution order, not authoring order, so a panel holding an island
(`<Code>`) overtakes the panel written above it -- the tabs are now the source
of order and panels are paired by key, which also moves the default from "first
panel" to "first tab" (they diverge in 23 of the 328 blocks, some of them real
content typos). And the shared store subscribes only on mount: `tabStore` is a
module singleton, so subscribing during SSR would leak a listener per render
and let one request's choice bleed into the next.
PricingTableOfContents comes along as Vue over webkit's DocOnThisPage, keeping
the two pricing-specific behaviors: the `-1` anchor remap for the `real` tab,
and the scroll-spy that skips headings under `[hidden]`.
… originals
Overline, TitleSection and the four-part card family (CardBase,
CardBaseClickable, CardTitle, CardDescription) all had an equivalent already
shipping in @aziontech/webkit, so they are gone: 502 lines of local markup
replaced by Overline, SectionTitle, DocCard and DocCardGroup.
The card grid is the change worth reading. DocCardGroup is both the frame and
the rules -- cards abut inside it and the hairlines between them are the
group's own gaps -- so Cardlist no longer draws a grid, a gap or a border of
its own. Two deliberate omissions: no icon, because the content data carries
SVG paths while DocCard's `icon` expects a PrimeIcons class, and no CTA link,
because the data has no call-to-action copy and inventing i18n strings is not a
refactor. The old card's arrow goes with them; the whole card is still the
link.
Overline is a `w-fit` inline element rather than a block, so centering moved
from `text-center` to `self-center` on the parent. And in the branch where
SectionTitle takes over the whole header, `titleTag` stops having an effect --
the design system fixes h2 there. That branch compiles but no page exercises it
today: none of the 70 call sites passes a `title`.
The hero stays hand-built on purpose. webkit's HeroTitle always renders the
highlighted span at the *start* of the headline, and both homes author it at
the end ("Welcome to <span>Azion Docs</span>"). With no title slot, adopting it
would mean losing the accent, inverting the sentence, or rewriting both
headlines -- an editorial call, not a refactor.
ThemeSwitcher becomes an adapter over webkit's ThemeSwitcher (196 -> 112
lines): the dropdown, its trigger, the viewport measuring and the document
click/Escape listeners all go, and what stays is the part the design system
does not own -- reading and writing the preference. The stored value keeps its
exact old shape, the `prefers-color-scheme` key holding 'System' | 'Dark' |
'Light', because BaseLayout reads that same key in a blocking inline script and
every returning visitor already has one of those strings saved; the lowercase
mode names webkit speaks are paired to them here rather than migrating anyone's
storage. Visually this turns a dropdown into a segmented icon group.
SystemStatus takes its dot and label from StatusIndicator, so severity comes
from theme tokens instead of the inline hex the component used to paint with.
The pill around it stays a plain anchor: webkit's Button takes its content
through a `label` string and exposes no default slot, so wrapping the indicator
in one would render the text and drop the dot. Also removes STATUS_PAGE and its
`status` ref, which nothing read.
SelectLang moves to webkit's Dropdown -- not Select, which is still draft in
the catalog. `placement="auto"` replaces the hand-rolled "is there room below?"
measurement. The options stay real anchors so the translated route is still
crawlable and middle-clickable.
Two known defects are documented in BaseLayout rather than worked around.
Nothing nested in this Footer island hydrates today, SystemStatus included, and
that predates the webkit adoption -- the controls paint but do not respond to a
click. And webkit's Dropdown and ThemeSwitcher teleport an overlay to <body>,
which Astro never re-emits into the served markup; the fix is the doc-prose
patch in patches/ extended upstream to tooltip and dropdown. `client:only`
looks like the answer to both and is a trap: an island nested inside a Vue
island never mounts under it, so the controls vanish entirely instead of merely
being inert.
BaseLayout also carries an unrelated change that was already pending in the
working tree: the ScrollArea rules for the "On this page" rail.
This is not one change; it is everything that was sitting uncommitted in the
tree, swept in at the author's request so nothing is left dangling. Two
distinct bodies of work:
The webkit/Tailwind 4 adoption in progress: 68 legacy components removed
(BackendGuidesNav, Badge, Breadcrumb, Button, the NavGrid and tabs .astro
families, the Preact leftovers TableOfContents, SidebarToggleTabGroup,
ExternalLinkIcon and Spoiler, and the rest), Footer rebuilt on the design
system's own Footer, and the supporting changes across content.config.ts,
content.ts, the i18n helpers, src/util and the layouts.
The lint toolchain migration (MM-15912), authored in a parallel session:
flat config in eslint.config.mjs replacing .eslintrc.js and .eslintignore,
eslint 9 with typescript-eslint 8, stylelint 16 with postcss-html and
postcss-scss, and the call sites those rules touched.
Also included: azion.config.mjs and azion/azion.json from the preview deploy,
and .claude/launch.json.
Neither body of work is mine and neither was reviewed here. They are committed
as they stood, so the history has a point to return to before the deploy.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@isaque-bock-azion
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(build): upgrade to Astro 7 and Tailwind CSS 4 - #2315

Open
isaque-bock-azion wants to merge 18 commits into
mainfrom
feat/astro7-tailwind4
Open

feat(build): upgrade to Astro 7 and Tailwind CSS 4#2315
isaque-bock-azion wants to merge 18 commits into
mainfrom
feat/astro7-tailwind4

Conversation

@isaque-bock-azion

@isaque-bock-azionisaque-bock-azion commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

What

Base branch for the docs modernization stack (MM-15897). Two commits, one per migration:

  1. Tailwind CSS 3 → 4 — removes the deprecated @astrojs/tailwind integration (capped at Astro 5). New CSS-first entry in src/styles/main.css (important flag, cascade layers keeping PrimeVue between base and utilities, @source for azion-webkit@1's raw .vue components, v3 preflight compat). azion-webkit@1's entry CSS is vendored into src/styles/webkit-v1-main.css until webkit v4 lands.
  2. Astro 5 → 7 — Astro 7.2.3, Vite 8, Vue 3.5, Content Layer API (content.config.ts + glob loader, slugid, render(entry)), integration bumps (@astrojs/sitemap 1.0.1→3.7.3 included), and the markdown pipeline kept on unified() so all custom remark/rehype plugins keep working. Also fixes the 9 published vulnerabilities in astro <7.0.4.

Notable plumbing

  • Astro 7 renders pages in a new prerender Vite environment that ignores vite.ssr.noExternal and force-externalizes primevue; a small inline config plugin (azion:server-noexternal) bundles primevue/azion-webkit/azion-theme there (PrimeVue 3's ESM uses directory imports Node can't resolve).
  • TablePricing now caches pricing API responses per product_slug for the whole build — the prerender pass fetched once per page and caused transient DNS failures.

Verification

  • pnpm run build:local: 1494 pages in ~2m48s, frontmatter test passing.
  • Output spot-checked: asides, heading anchors, expressive-code blocks, doc-all-data.json / docs-path-by-url.json / docs-llms.txt / .md endpoints, both sitemaps, Tailwind 4 CSS inlined with !important parity.
  • astro preview smoke test in the browser: layout matches production, Algolia search dialog opens and returns results, zero console errors.

Known notes

  • Dev server only: the Algolia search dialog fails to hydrate under astro dev (Vite 8 optimize-deps CJS interop with algoliasearch inside azion-webkit@1). The production build is unaffected — worth a follow-up.
  • Porting the remark/rehype plugins to Sätteri is intentionally out of scope (next PR in the stack).
  • astro-expressive-code 0.44 warns about unknown code-fence languages (code, env, gitignore) in a few MDX files — content fixes, separate PR.

🤖 Generated with Claude Code

Replaces the deprecated @astrojs/tailwind integration (capped at Astro 5)
with Tailwind 4 via @tailwindcss/postcss, unblocking the Astro 7 upgrade.
- Vendor azion-webkit@1's entry CSS (src/styles/webkit-v1-main.css) since
it contained Tailwind v3 directives; the file is removed once webkit v4
lands.
- New Tailwind entry in src/styles/main.css: cascade-layer order keeps
PrimeVue between base and utilities (same cascade as v3), `important`
import flag preserves v3's important:true, @source compiles the raw
.vue components azion-webkit@1 ships, and v3 preflight compatibility
styles keep border/placeholder/cursor defaults.
- Slim tailwind.config.cjs: theme-only copy of what azion-webkit@1's
config provided; its v3-only addUtilities plugins now live in CSS as
@Utility px-shell/px-container. The unused `container.padding` config
is dropped (crashes Tailwind 4's compat layer with custom screens).
- Rename removed v3 utilities: flex-shrink-0 -> shrink-0, rounded ->
rounded-sm.
- Drop autoprefixer (built into Tailwind 4).
Astro 7.2.3, Vite 8, Vue 3.5, and the Content Layer API. Also patches the
9 published vulnerabilities in astro <7.0.4.
Content Layer (mandatory since Astro 6 removed legacy collections):
- Move src/content/config.ts to src/content.config.ts with a glob() loader
and `astro/zod` imports.
- entry.slug -> entry.id and entry.render() -> render(entry) across pages,
layouts, components, and utils. The [lang]/[...slug] route rebuilds the
legacy id (path with extension) from entry.filePath for getGithubEditUrl,
and docs-path-by-url.json now uses entry.filePath directly.
Markdown: keep the unified() processor from @astrojs/markdown-remark so the
custom remark/rehype plugins keep running; porting them to Sätteri is a
follow-up.
Integrations: @astrojs/mdx 7, @astrojs/vue 7, @astrojs/preact 6,
@astrojs/sitemap 1.0.1 -> 3.7.3, astro-expressive-code 0.44,
astro-auto-import 0.5, astro-og-canvas 0.13.
Vite 8 adjustments:
- Tailwind 4 moves from @tailwindcss/postcss to @tailwindcss/vite (Vite 8's
postcss-import no longer resolves the bare `tailwindcss` import).
- A small config plugin bundles primevue/azion-webkit/azion-theme into the
new prerender environment: Astro marks primevue as external there, and
primevue 3's ESM files use directory imports Node cannot resolve.
- Drop the obsolete vite.ssrBuild flag.
TablePricing now caches pricing API responses per product_slug for the whole
build; the prerender pass fetching once per page caused transient DNS
failures.
Build verified: 1494 pages, frontmatter test passing, asides/heading
anchors/expressive-code/JSON+md+llms.txt endpoints/sitemaps all present in
the output.
…th Tailwind (#2316)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@isaque-bock-azion
isaque-bock-azion requested a review from a team as a code ownerAugust 28, 2026 21:28
Ports the header, search trigger and palette from the webkit design
system sample (feat/doc-masthead-action-belt) to the docs site.
- Header: rebuilt on GlobalHeader (.Left/.Brand/.Nav/.Right) with
@container so the search trigger can switch shapes via container
query instead of viewport breakpoints
- HeaderSearch: two-root trigger (IconButton narrow / field-shaped
wide) with @min-[47rem] container query, verbatim from the sample
- HeaderSearchDialog: CommandMenu shell (⌘K shortcut, Dialog/Overlay
owned by webkit) feeding Algolia via direct client.search() across
the four i18n indices; replaces vue-instantsearch + AlgoliaDialog
- HeaderRightSidebar: mobile nav migrated from manual Teleport to
webkit Drawer (side="left", size="small"); closes on palette-open
cross-island event
- DocsTopNav: NavigationMenu extracted into its own client:idle island
so the hover-pill highlight (pointerenter + provide/inject) hydrates
correctly; was dead inside the SSR-only Header.vue
- main.css: added text-overline-md / text-overline-sm Tailwind shims
so Kbd keycaps (⌘K, ESC) render at the token's 12px, not 16px
- Removed: AlgoliaDialog, AlgoliaInstantSearch, AlgoliaIndexHit,
KeyboardKey, DocsSearchTrigger, vue.config.js; dep vue-instantsearch
Deletes public/assets/docs/css/ (theme.css, index.css), and the
orphaned SCSS layers under src/styles/ (breakpoints, functions, icons,
tokens) — none of these were imported anywhere after the webkit/theme
migration; all design tokens now come from @aziontech/theme.
Replaces the hand-rolled footer flex layout with webkit's Footer
component, aligned with the design system's own sample (SiteFooter.vue).
Updates the i18n model: drops the legacy `cta` (phone numbers) and
`copyright` exports, adds `tagline` in their place, as the DS Footer
now renders the signature band with the brand mark and tagline from
the component itself.
- LeftSidebar: drops SidebarContent (Astro/SCSS/TSX trio) and the
sidebarpanelmenu/ pair; now delegates to DocsSidebarMenu (webkit DS)
via the menuToWebkit adapter
- ReadableContent: rewired to webkit's DocProse for article typography
(heading ladder, prose body, callout, code chip); removes the local
.prose/.article SCSS that duplicated those rules
- RightSidebar: swaps the local TableOfContents/TableOfContentsItem
pair for webkit's OnThisPage component
- PageContent/MainLayout: layout adjustments for the webkit rail's
sizing contract (auto column instead of fixed 20rem)
…egrations
- Aside: replaced hand-rolled admonition (inline SVGs + SCSS) with
webkit's doc-callout; maps caution→warning, unwraps <p> to inline
prose, preserves authored titles as lead-in text
- Code: switched from astro-expressive-code to local CodeBlock wrapper
(webkit's syntax highlighting); removes expressive-code integration files
- LinkButton: wraps button roots in `data-doc-chrome` span (display:contents)
so DocProse prose-link paint does not repaint buttons inside article content
- AzionLogo.vue: deleted (unused after Header.vue moved to GlobalHeader.Brand)
- plugins/rehype-autolink-config: adds data-doc-anchor on heading anchors
so DocProse's link rules skip them
- .gitignore: add Azion CLI build artifacts (.edge/, .vulcan, .open-next)
- pnpm-workspace.yaml: pin @aziontech/theme and webkit to current versions
New components used by the sidebar/toc/code refactor:
- CodeBlock: webkit syntax highlighting shell (replaces expressive-code)
- DocPageHeader: page title + metadata band above article content
- DocsSidebar: webkit Sidebar rail wrapper for the left nav
- DocsSidebarMenu: webkit Menu tree rendering the navigation tree
- OnThisPage: webkit table-of-contents for the right sidebar
- menuToWebkit: adapter converting the docs navigation data model
(getNavigationMenu) to webkit's Menu/tree props contract
- integrations/astro-code-blocks: Astro integration wiring CodeBlock
into the remark/rehype pipeline
Adds the auto-generated azion.config.mjs (build preset: astro,
storage bucket for docs-preview-webkit) and the GitHub Actions workflow
for Azion deploy, alongside the azion/ config directory generated by
the CLI.
Replaces the LinkButton compatibility wrapper with @aziontech/webkit/button
imported directly. Props mapped: link→href, severity="secondary"→kind="secondary";
added size="medium" and class="not-prose no-underline" on every call site.
Removes the unused Container import.
DocProse styles every `a` in the article body as a prose link -- link ink,
underline, flat radius -- with `!important`, and steps aside only for
`[data-doc-chrome]`. A webkit Button rendered with `href` is such an `a`, so
every button in the docs body came out blue, underlined and square on top of
its own fill. `class="not-prose no-underline"` never had a chance: the
contract's selector is more specific and important.
The attribute cannot go on the Button itself -- it sets `inheritAttrs: false`
and forwards only `class`, so `data-doc-chrome` is dropped before it reaches
the DOM. DocButton puts it on a wrapping span instead, whose subtree the
contract also excludes, and adds `data-doc-block` for the standard block
spacing.
Same treatment for HeroHome's actions row: the hero CTAs live inside
ReadableContent too and were painted as prose links for the same reason.
…utton
The 650 pages that import the button rendered it as a prose link -- blue,
underlined, square corners over the button's own fill -- because DocProse
styles every `a` in the article body and only stops at `[data-doc-chrome]`.
DocButton carries that boundary; swapping the import is the whole fix.
Two leftovers from the LinkButton era go with it. `class="not-prose
no-underline"` was reaching for this same escape and never worked, and
`iconPos="left"` names a prop the webkit Button does not have -- it has one
icon slot, always leading -- so it was dropped on the floor either way.
Mechanical: the import line was identical in all 650 files, `<Button` has no
closing-tag form here, and occurrences inside code fences were left alone.
All 650 files re-checked through the MDX compiler.
… mounted
Every documentation page logged "Hydration completed but contains mismatches".
DocProse's copy tip lives in a `<Teleport to="body">`, and Astro does not
re-emit Vue's teleport payload into the served markup -- so on hydration Vue
walks into `<body>` looking for the teleport anchor and finds the page's own
first element instead. Vue then discards the server DOM for that subtree and
re-renders it on the client, on every page.
Gating the teleport on a mounted flag keeps the server render and the client's
first render identical -- the tip is a `v-if` comment inline in the root either
way -- and it costs nothing, since the tip only ever opens on pointerover.
Carried as a pnpm patch because the fix belongs upstream in @aziontech/webkit;
drop `patches/` and the `patchedDependencies` entry once a release ships it.
The authoring API is unchanged: 328 tab blocks across 178 content files keep
writing `<Tabs>` with `<Fragment slot="tab.x">` / `slot="panel.x">`, so not one
.mdx was touched. `Tabs.ts` re-exports the SFC because the content imports
`~/components/tabs/Tabs` without an extension and Vite's resolve.extensions
does not include .vue.
Panels stay in the DOM behind `hidden` instead of using TabView's Panel, which
mounts its slot behind `v-if`. Dropping inactive panels would take half the
prose of every tabbed page out of the served HTML -- out of Ctrl+F, out of the
crawl, and out of reach of the heading-anchor wiring ReadableContent installs
once on mount. The cost is TabView's sliding indicator and edge fades; this
component owns the active value, the ids and the arrow keys instead, and the
chips fall back to Item's standalone `selected` styling, which paints the same.
Two things the port had to fix rather than carry over. Astro hands slots to the
island in resolution order, not authoring order, so a panel holding an island
(`<Code>`) overtakes the panel written above it -- the tabs are now the source
of order and panels are paired by key, which also moves the default from "first
panel" to "first tab" (they diverge in 23 of the 328 blocks, some of them real
content typos). And the shared store subscribes only on mount: `tabStore` is a
module singleton, so subscribing during SSR would leak a listener per render
and let one request's choice bleed into the next.
PricingTableOfContents comes along as Vue over webkit's DocOnThisPage, keeping
the two pricing-specific behaviors: the `-1` anchor remap for the `real` tab,
and the scroll-spy that skips headings under `[hidden]`.
… originals
Overline, TitleSection and the four-part card family (CardBase,
CardBaseClickable, CardTitle, CardDescription) all had an equivalent already
shipping in @aziontech/webkit, so they are gone: 502 lines of local markup
replaced by Overline, SectionTitle, DocCard and DocCardGroup.
The card grid is the change worth reading. DocCardGroup is both the frame and
the rules -- cards abut inside it and the hairlines between them are the
group's own gaps -- so Cardlist no longer draws a grid, a gap or a border of
its own. Two deliberate omissions: no icon, because the content data carries
SVG paths while DocCard's `icon` expects a PrimeIcons class, and no CTA link,
because the data has no call-to-action copy and inventing i18n strings is not a
refactor. The old card's arrow goes with them; the whole card is still the
link.
Overline is a `w-fit` inline element rather than a block, so centering moved
from `text-center` to `self-center` on the parent. And in the branch where
SectionTitle takes over the whole header, `titleTag` stops having an effect --
the design system fixes h2 there. That branch compiles but no page exercises it
today: none of the 70 call sites passes a `title`.
The hero stays hand-built on purpose. webkit's HeroTitle always renders the
highlighted span at the *start* of the headline, and both homes author it at
the end ("Welcome to <span>Azion Docs</span>"). With no title slot, adopting it
would mean losing the accent, inverting the sentence, or rewriting both
headlines -- an editorial call, not a refactor.
ThemeSwitcher becomes an adapter over webkit's ThemeSwitcher (196 -> 112
lines): the dropdown, its trigger, the viewport measuring and the document
click/Escape listeners all go, and what stays is the part the design system
does not own -- reading and writing the preference. The stored value keeps its
exact old shape, the `prefers-color-scheme` key holding 'System' | 'Dark' |
'Light', because BaseLayout reads that same key in a blocking inline script and
every returning visitor already has one of those strings saved; the lowercase
mode names webkit speaks are paired to them here rather than migrating anyone's
storage. Visually this turns a dropdown into a segmented icon group.
SystemStatus takes its dot and label from StatusIndicator, so severity comes
from theme tokens instead of the inline hex the component used to paint with.
The pill around it stays a plain anchor: webkit's Button takes its content
through a `label` string and exposes no default slot, so wrapping the indicator
in one would render the text and drop the dot. Also removes STATUS_PAGE and its
`status` ref, which nothing read.
SelectLang moves to webkit's Dropdown -- not Select, which is still draft in
the catalog. `placement="auto"` replaces the hand-rolled "is there room below?"
measurement. The options stay real anchors so the translated route is still
crawlable and middle-clickable.
Two known defects are documented in BaseLayout rather than worked around.
Nothing nested in this Footer island hydrates today, SystemStatus included, and
that predates the webkit adoption -- the controls paint but do not respond to a
click. And webkit's Dropdown and ThemeSwitcher teleport an overlay to <body>,
which Astro never re-emits into the served markup; the fix is the doc-prose
patch in patches/ extended upstream to tooltip and dropdown. `client:only`
looks like the answer to both and is a trap: an island nested inside a Vue
island never mounts under it, so the controls vanish entirely instead of merely
being inert.
BaseLayout also carries an unrelated change that was already pending in the
working tree: the ScrollArea rules for the "On this page" rail.
This is not one change; it is everything that was sitting uncommitted in the
tree, swept in at the author's request so nothing is left dangling. Two
distinct bodies of work:
The webkit/Tailwind 4 adoption in progress: 68 legacy components removed
(BackendGuidesNav, Badge, Breadcrumb, Button, the NavGrid and tabs .astro
families, the Preact leftovers TableOfContents, SidebarToggleTabGroup,
ExternalLinkIcon and Spoiler, and the rest), Footer rebuilt on the design
system's own Footer, and the supporting changes across content.config.ts,
content.ts, the i18n helpers, src/util and the layouts.
The lint toolchain migration (MM-15912), authored in a parallel session:
flat config in eslint.config.mjs replacing .eslintrc.js and .eslintignore,
eslint 9 with typescript-eslint 8, stylelint 16 with postcss-html and
postcss-scss, and the call sites those rules touched.
Also included: azion.config.mjs and azion/azion.json from the preview deploy,
and .claude/launch.json.
Neither body of work is mine and neither was reviewed here. They are committed
as they stood, so the history has a point to return to before the deploy.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@isaque-bock-azion
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(build): upgrade to Astro 7 and Tailwind CSS 4 - #2315

Open
isaque-bock-azion wants to merge 18 commits into
mainfrom
feat/astro7-tailwind4
Open

feat(build): upgrade to Astro 7 and Tailwind CSS 4#2315
isaque-bock-azion wants to merge 18 commits into
mainfrom
feat/astro7-tailwind4

Conversation

@isaque-bock-azion

@isaque-bock-azionisaque-bock-azion commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

What

Base branch for the docs modernization stack (MM-15897). Two commits, one per migration:

  1. Tailwind CSS 3 → 4 — removes the deprecated @astrojs/tailwind integration (capped at Astro 5). New CSS-first entry in src/styles/main.css (important flag, cascade layers keeping PrimeVue between base and utilities, @source for azion-webkit@1's raw .vue components, v3 preflight compat). azion-webkit@1's entry CSS is vendored into src/styles/webkit-v1-main.css until webkit v4 lands.
  2. Astro 5 → 7 — Astro 7.2.3, Vite 8, Vue 3.5, Content Layer API (content.config.ts + glob loader, slugid, render(entry)), integration bumps (@astrojs/sitemap 1.0.1→3.7.3 included), and the markdown pipeline kept on unified() so all custom remark/rehype plugins keep working. Also fixes the 9 published vulnerabilities in astro <7.0.4.

Notable plumbing

  • Astro 7 renders pages in a new prerender Vite environment that ignores vite.ssr.noExternal and force-externalizes primevue; a small inline config plugin (azion:server-noexternal) bundles primevue/azion-webkit/azion-theme there (PrimeVue 3's ESM uses directory imports Node can't resolve).
  • TablePricing now caches pricing API responses per product_slug for the whole build — the prerender pass fetched once per page and caused transient DNS failures.

Verification

  • pnpm run build:local: 1494 pages in ~2m48s, frontmatter test passing.
  • Output spot-checked: asides, heading anchors, expressive-code blocks, doc-all-data.json / docs-path-by-url.json / docs-llms.txt / .md endpoints, both sitemaps, Tailwind 4 CSS inlined with !important parity.
  • astro preview smoke test in the browser: layout matches production, Algolia search dialog opens and returns results, zero console errors.

Known notes

  • Dev server only: the Algolia search dialog fails to hydrate under astro dev (Vite 8 optimize-deps CJS interop with algoliasearch inside azion-webkit@1). The production build is unaffected — worth a follow-up.
  • Porting the remark/rehype plugins to Sätteri is intentionally out of scope (next PR in the stack).
  • astro-expressive-code 0.44 warns about unknown code-fence languages (code, env, gitignore) in a few MDX files — content fixes, separate PR.

🤖 Generated with Claude Code

Replaces the deprecated @astrojs/tailwind integration (capped at Astro 5)
with Tailwind 4 via @tailwindcss/postcss, unblocking the Astro 7 upgrade.
- Vendor azion-webkit@1's entry CSS (src/styles/webkit-v1-main.css) since
it contained Tailwind v3 directives; the file is removed once webkit v4
lands.
- New Tailwind entry in src/styles/main.css: cascade-layer order keeps
PrimeVue between base and utilities (same cascade as v3), `important`
import flag preserves v3's important:true, @source compiles the raw
.vue components azion-webkit@1 ships, and v3 preflight compatibility
styles keep border/placeholder/cursor defaults.
- Slim tailwind.config.cjs: theme-only copy of what azion-webkit@1's
config provided; its v3-only addUtilities plugins now live in CSS as
@Utility px-shell/px-container. The unused `container.padding` config
is dropped (crashes Tailwind 4's compat layer with custom screens).
- Rename removed v3 utilities: flex-shrink-0 -> shrink-0, rounded ->
rounded-sm.
- Drop autoprefixer (built into Tailwind 4).
Astro 7.2.3, Vite 8, Vue 3.5, and the Content Layer API. Also patches the
9 published vulnerabilities in astro <7.0.4.
Content Layer (mandatory since Astro 6 removed legacy collections):
- Move src/content/config.ts to src/content.config.ts with a glob() loader
and `astro/zod` imports.
- entry.slug -> entry.id and entry.render() -> render(entry) across pages,
layouts, components, and utils. The [lang]/[...slug] route rebuilds the
legacy id (path with extension) from entry.filePath for getGithubEditUrl,
and docs-path-by-url.json now uses entry.filePath directly.
Markdown: keep the unified() processor from @astrojs/markdown-remark so the
custom remark/rehype plugins keep running; porting them to Sätteri is a
follow-up.
Integrations: @astrojs/mdx 7, @astrojs/vue 7, @astrojs/preact 6,
@astrojs/sitemap 1.0.1 -> 3.7.3, astro-expressive-code 0.44,
astro-auto-import 0.5, astro-og-canvas 0.13.
Vite 8 adjustments:
- Tailwind 4 moves from @tailwindcss/postcss to @tailwindcss/vite (Vite 8's
postcss-import no longer resolves the bare `tailwindcss` import).
- A small config plugin bundles primevue/azion-webkit/azion-theme into the
new prerender environment: Astro marks primevue as external there, and
primevue 3's ESM files use directory imports Node cannot resolve.
- Drop the obsolete vite.ssrBuild flag.
TablePricing now caches pricing API responses per product_slug for the whole
build; the prerender pass fetching once per page caused transient DNS
failures.
Build verified: 1494 pages, frontmatter test passing, asides/heading
anchors/expressive-code/JSON+md+llms.txt endpoints/sitemaps all present in
the output.
…th Tailwind (#2316)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@isaque-bock-azion
isaque-bock-azion requested a review from a team as a code ownerAugust 28, 2026 21:28
Ports the header, search trigger and palette from the webkit design
system sample (feat/doc-masthead-action-belt) to the docs site.
- Header: rebuilt on GlobalHeader (.Left/.Brand/.Nav/.Right) with
@container so the search trigger can switch shapes via container
query instead of viewport breakpoints
- HeaderSearch: two-root trigger (IconButton narrow / field-shaped
wide) with @min-[47rem] container query, verbatim from the sample
- HeaderSearchDialog: CommandMenu shell (⌘K shortcut, Dialog/Overlay
owned by webkit) feeding Algolia via direct client.search() across
the four i18n indices; replaces vue-instantsearch + AlgoliaDialog
- HeaderRightSidebar: mobile nav migrated from manual Teleport to
webkit Drawer (side="left", size="small"); closes on palette-open
cross-island event
- DocsTopNav: NavigationMenu extracted into its own client:idle island
so the hover-pill highlight (pointerenter + provide/inject) hydrates
correctly; was dead inside the SSR-only Header.vue
- main.css: added text-overline-md / text-overline-sm Tailwind shims
so Kbd keycaps (⌘K, ESC) render at the token's 12px, not 16px
- Removed: AlgoliaDialog, AlgoliaInstantSearch, AlgoliaIndexHit,
KeyboardKey, DocsSearchTrigger, vue.config.js; dep vue-instantsearch
Deletes public/assets/docs/css/ (theme.css, index.css), and the
orphaned SCSS layers under src/styles/ (breakpoints, functions, icons,
tokens) — none of these were imported anywhere after the webkit/theme
migration; all design tokens now come from @aziontech/theme.
Replaces the hand-rolled footer flex layout with webkit's Footer
component, aligned with the design system's own sample (SiteFooter.vue).
Updates the i18n model: drops the legacy `cta` (phone numbers) and
`copyright` exports, adds `tagline` in their place, as the DS Footer
now renders the signature band with the brand mark and tagline from
the component itself.
- LeftSidebar: drops SidebarContent (Astro/SCSS/TSX trio) and the
sidebarpanelmenu/ pair; now delegates to DocsSidebarMenu (webkit DS)
via the menuToWebkit adapter
- ReadableContent: rewired to webkit's DocProse for article typography
(heading ladder, prose body, callout, code chip); removes the local
.prose/.article SCSS that duplicated those rules
- RightSidebar: swaps the local TableOfContents/TableOfContentsItem
pair for webkit's OnThisPage component
- PageContent/MainLayout: layout adjustments for the webkit rail's
sizing contract (auto column instead of fixed 20rem)
…egrations
- Aside: replaced hand-rolled admonition (inline SVGs + SCSS) with
webkit's doc-callout; maps caution→warning, unwraps <p> to inline
prose, preserves authored titles as lead-in text
- Code: switched from astro-expressive-code to local CodeBlock wrapper
(webkit's syntax highlighting); removes expressive-code integration files
- LinkButton: wraps button roots in `data-doc-chrome` span (display:contents)
so DocProse prose-link paint does not repaint buttons inside article content
- AzionLogo.vue: deleted (unused after Header.vue moved to GlobalHeader.Brand)
- plugins/rehype-autolink-config: adds data-doc-anchor on heading anchors
so DocProse's link rules skip them
- .gitignore: add Azion CLI build artifacts (.edge/, .vulcan, .open-next)
- pnpm-workspace.yaml: pin @aziontech/theme and webkit to current versions
New components used by the sidebar/toc/code refactor:
- CodeBlock: webkit syntax highlighting shell (replaces expressive-code)
- DocPageHeader: page title + metadata band above article content
- DocsSidebar: webkit Sidebar rail wrapper for the left nav
- DocsSidebarMenu: webkit Menu tree rendering the navigation tree
- OnThisPage: webkit table-of-contents for the right sidebar
- menuToWebkit: adapter converting the docs navigation data model
(getNavigationMenu) to webkit's Menu/tree props contract
- integrations/astro-code-blocks: Astro integration wiring CodeBlock
into the remark/rehype pipeline
Adds the auto-generated azion.config.mjs (build preset: astro,
storage bucket for docs-preview-webkit) and the GitHub Actions workflow
for Azion deploy, alongside the azion/ config directory generated by
the CLI.
Replaces the LinkButton compatibility wrapper with @aziontech/webkit/button
imported directly. Props mapped: link→href, severity="secondary"→kind="secondary";
added size="medium" and class="not-prose no-underline" on every call site.
Removes the unused Container import.
DocProse styles every `a` in the article body as a prose link -- link ink,
underline, flat radius -- with `!important`, and steps aside only for
`[data-doc-chrome]`. A webkit Button rendered with `href` is such an `a`, so
every button in the docs body came out blue, underlined and square on top of
its own fill. `class="not-prose no-underline"` never had a chance: the
contract's selector is more specific and important.
The attribute cannot go on the Button itself -- it sets `inheritAttrs: false`
and forwards only `class`, so `data-doc-chrome` is dropped before it reaches
the DOM. DocButton puts it on a wrapping span instead, whose subtree the
contract also excludes, and adds `data-doc-block` for the standard block
spacing.
Same treatment for HeroHome's actions row: the hero CTAs live inside
ReadableContent too and were painted as prose links for the same reason.
…utton
The 650 pages that import the button rendered it as a prose link -- blue,
underlined, square corners over the button's own fill -- because DocProse
styles every `a` in the article body and only stops at `[data-doc-chrome]`.
DocButton carries that boundary; swapping the import is the whole fix.
Two leftovers from the LinkButton era go with it. `class="not-prose
no-underline"` was reaching for this same escape and never worked, and
`iconPos="left"` names a prop the webkit Button does not have -- it has one
icon slot, always leading -- so it was dropped on the floor either way.
Mechanical: the import line was identical in all 650 files, `<Button` has no
closing-tag form here, and occurrences inside code fences were left alone.
All 650 files re-checked through the MDX compiler.
… mounted
Every documentation page logged "Hydration completed but contains mismatches".
DocProse's copy tip lives in a `<Teleport to="body">`, and Astro does not
re-emit Vue's teleport payload into the served markup -- so on hydration Vue
walks into `<body>` looking for the teleport anchor and finds the page's own
first element instead. Vue then discards the server DOM for that subtree and
re-renders it on the client, on every page.
Gating the teleport on a mounted flag keeps the server render and the client's
first render identical -- the tip is a `v-if` comment inline in the root either
way -- and it costs nothing, since the tip only ever opens on pointerover.
Carried as a pnpm patch because the fix belongs upstream in @aziontech/webkit;
drop `patches/` and the `patchedDependencies` entry once a release ships it.
The authoring API is unchanged: 328 tab blocks across 178 content files keep
writing `<Tabs>` with `<Fragment slot="tab.x">` / `slot="panel.x">`, so not one
.mdx was touched. `Tabs.ts` re-exports the SFC because the content imports
`~/components/tabs/Tabs` without an extension and Vite's resolve.extensions
does not include .vue.
Panels stay in the DOM behind `hidden` instead of using TabView's Panel, which
mounts its slot behind `v-if`. Dropping inactive panels would take half the
prose of every tabbed page out of the served HTML -- out of Ctrl+F, out of the
crawl, and out of reach of the heading-anchor wiring ReadableContent installs
once on mount. The cost is TabView's sliding indicator and edge fades; this
component owns the active value, the ids and the arrow keys instead, and the
chips fall back to Item's standalone `selected` styling, which paints the same.
Two things the port had to fix rather than carry over. Astro hands slots to the
island in resolution order, not authoring order, so a panel holding an island
(`<Code>`) overtakes the panel written above it -- the tabs are now the source
of order and panels are paired by key, which also moves the default from "first
panel" to "first tab" (they diverge in 23 of the 328 blocks, some of them real
content typos). And the shared store subscribes only on mount: `tabStore` is a
module singleton, so subscribing during SSR would leak a listener per render
and let one request's choice bleed into the next.
PricingTableOfContents comes along as Vue over webkit's DocOnThisPage, keeping
the two pricing-specific behaviors: the `-1` anchor remap for the `real` tab,
and the scroll-spy that skips headings under `[hidden]`.
… originals
Overline, TitleSection and the four-part card family (CardBase,
CardBaseClickable, CardTitle, CardDescription) all had an equivalent already
shipping in @aziontech/webkit, so they are gone: 502 lines of local markup
replaced by Overline, SectionTitle, DocCard and DocCardGroup.
The card grid is the change worth reading. DocCardGroup is both the frame and
the rules -- cards abut inside it and the hairlines between them are the
group's own gaps -- so Cardlist no longer draws a grid, a gap or a border of
its own. Two deliberate omissions: no icon, because the content data carries
SVG paths while DocCard's `icon` expects a PrimeIcons class, and no CTA link,
because the data has no call-to-action copy and inventing i18n strings is not a
refactor. The old card's arrow goes with them; the whole card is still the
link.
Overline is a `w-fit` inline element rather than a block, so centering moved
from `text-center` to `self-center` on the parent. And in the branch where
SectionTitle takes over the whole header, `titleTag` stops having an effect --
the design system fixes h2 there. That branch compiles but no page exercises it
today: none of the 70 call sites passes a `title`.
The hero stays hand-built on purpose. webkit's HeroTitle always renders the
highlighted span at the *start* of the headline, and both homes author it at
the end ("Welcome to <span>Azion Docs</span>"). With no title slot, adopting it
would mean losing the accent, inverting the sentence, or rewriting both
headlines -- an editorial call, not a refactor.
ThemeSwitcher becomes an adapter over webkit's ThemeSwitcher (196 -> 112
lines): the dropdown, its trigger, the viewport measuring and the document
click/Escape listeners all go, and what stays is the part the design system
does not own -- reading and writing the preference. The stored value keeps its
exact old shape, the `prefers-color-scheme` key holding 'System' | 'Dark' |
'Light', because BaseLayout reads that same key in a blocking inline script and
every returning visitor already has one of those strings saved; the lowercase
mode names webkit speaks are paired to them here rather than migrating anyone's
storage. Visually this turns a dropdown into a segmented icon group.
SystemStatus takes its dot and label from StatusIndicator, so severity comes
from theme tokens instead of the inline hex the component used to paint with.
The pill around it stays a plain anchor: webkit's Button takes its content
through a `label` string and exposes no default slot, so wrapping the indicator
in one would render the text and drop the dot. Also removes STATUS_PAGE and its
`status` ref, which nothing read.
SelectLang moves to webkit's Dropdown -- not Select, which is still draft in
the catalog. `placement="auto"` replaces the hand-rolled "is there room below?"
measurement. The options stay real anchors so the translated route is still
crawlable and middle-clickable.
Two known defects are documented in BaseLayout rather than worked around.
Nothing nested in this Footer island hydrates today, SystemStatus included, and
that predates the webkit adoption -- the controls paint but do not respond to a
click. And webkit's Dropdown and ThemeSwitcher teleport an overlay to <body>,
which Astro never re-emits into the served markup; the fix is the doc-prose
patch in patches/ extended upstream to tooltip and dropdown. `client:only`
looks like the answer to both and is a trap: an island nested inside a Vue
island never mounts under it, so the controls vanish entirely instead of merely
being inert.
BaseLayout also carries an unrelated change that was already pending in the
working tree: the ScrollArea rules for the "On this page" rail.
This is not one change; it is everything that was sitting uncommitted in the
tree, swept in at the author's request so nothing is left dangling. Two
distinct bodies of work:
The webkit/Tailwind 4 adoption in progress: 68 legacy components removed
(BackendGuidesNav, Badge, Breadcrumb, Button, the NavGrid and tabs .astro
families, the Preact leftovers TableOfContents, SidebarToggleTabGroup,
ExternalLinkIcon and Spoiler, and the rest), Footer rebuilt on the design
system's own Footer, and the supporting changes across content.config.ts,
content.ts, the i18n helpers, src/util and the layouts.
The lint toolchain migration (MM-15912), authored in a parallel session:
flat config in eslint.config.mjs replacing .eslintrc.js and .eslintignore,
eslint 9 with typescript-eslint 8, stylelint 16 with postcss-html and
postcss-scss, and the call sites those rules touched.
Also included: azion.config.mjs and azion/azion.json from the preview deploy,
and .claude/launch.json.
Neither body of work is mine and neither was reviewed here. They are committed
as they stood, so the history has a point to return to before the deploy.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@isaque-bock-azion