Migrate to Astro 6 (phase 2): render route content as static HTML#321
Migrate to Astro 6 (phase 2): render route content as static HTML#321dionysuzx wants to merge 11 commits into
Conversation
- Add static Astro primitives: Tooltip.astro, StatusBadge.astro, MacroPhaseBar.astro, UpgradeCard.astro (faithful ports of the React leaves). - Convert /upgrades to fully-static .astro (zero islands; verified pixel-perfect vs the Phase 1 baseline at desktop-light/dark + mobile). - Add docs/astro-migration-phase-2.md with the per-route island architecture.
- home: full static HTML (upgrades/EIPs/calls/planning/footer + build-time
Recent Decisions); analytics link events preserved via scripts/trackLinks.
- decisions: all ACD key decisions read at build time and rendered static;
type filter is a visibility-toggle script (content stays crawlable).
- Shared decisions subsystem: domain/decisions/{keyDecisions,decisionText} +
components/decisions/{KeyDecisionText,EipPreviewLink,EipPreviewLayer}.astro
(static EIP hover-preview replacing the React portal tooltip).
- devnets index: static cards (domain/devnets/devnetCards + SeriesCard/InactiveCard
.astro); 'Active only' toggle is a script. Delete DevnetsIndexPage + useDevnetNetworks.
- devnets/[id]: full static spec/network page (DevnetStatusBadge/SupportCell/
ClientMatrix/ResourceLinks.astro); arrow-key nav via define:vars script.
Delete DevnetSpecPage. Add utils/markdownHtml for build-time inline markdown.
All routes verified pixel-perfect vs the Phase 1 baseline (desktop+mobile, light+dark).
- Delete HomePage/DecisionsPage/UpgradesIndexPage and the React UpgradeCard/ MacroPhaseBar (replaced by .astro equivalents, no remaining importers). - navigation.tsx: drop the ignored react-router compat props (state/replace) from Link and the dead 'state' NavigateOption (phase-2-and-beyond cleanup).
…islands PublicNetworkUpgradePage now server-renders its full content at build time: - derive the fork's EIPs synchronously via useMemo (was a useEffect), - gate URL-param filters behind an isHydrated flag so the client's first render matches the server HTML (no hydration mismatch on ?filter=/?layer= deep links), - switch pectra/fusaka islands from client:only to client:load. Static HTML for these pages grows ~16KB -> ~150KB (full EIP directory + timeline). Verified pixel-perfect (desktop+mobile, light+dark), clean hydration on canonical and param'd URLs, and filter/TOC interactivity intact.
- Flip glamsterdam (overview/stakeholders/client-priority/devnet-inclusion/ test-complexity) and hegota (overview/test-complexity) islands to client:load. - StakeholderUpgradePage: derive EIPs via useMemo and apply the ?view= param after mount (hydration-safe), matching the PublicNetworkUpgradePage pattern. - Tab wrappers + fetch-based tabs (TestComplexity/ClientPriority/Prioritization) server-render their shell/loading state; live data still fetches on the client. Verified 21/21 pixel-perfect (desktop+dark+mobile) and clean hydration on all URLs including ?view= deep links.
- Flip EipsIndexPage island to client:load (data already synchronous; default filter/sort state is deterministic, so it server-renders the full table). - Fix a hydration mismatch in the shared search trigger: the ⌘K/Ctrl+K keycap depends on navigator (absent in SSR), so render the SSR-stable label first and resolve the platform label after mount (useSearchShortcutLabel). Verified pixel-perfect + clean hydration.
EipPage now server-renders the default tab's content (the rich layman analysis / spec shell + meta header) at build time, with ?tab=/#anchor applied after mount via an isHydrated gate. Flip the island to client:load. Pre-existing history-JSON fetch behavior unchanged. Verified pixel-perfect + clean hydration on tab/hash URLs.
SchedulePage renders deterministically from static config (no date/window/effect deps), so flip the island to client:load — it server-renders the full planning table + Gantt and hydrates for editing. Static HTML 15KB -> 117KB. Pixel-perfect, clean hydration.
…, plan - Move route-snapshot refresh to a scheduled committing workflow (snapshot-routes.yml); deploy.yml + predeploy now run plain 'build' off committed snapshots (deterministic, not blocked by third-party outages). - Update the <noscript> copy and public/llms.txt: most route bodies now render as static HTML; only the call pages, agenda, and rank stay client-rendered. - Add unit tests for the new pure helpers (decisionText, markdownHtml). - Update docs/astro-migration-phase-2.md to the implemented two-strategy approach (static .astro vs SSR-rendered client:load islands) and per-route outcomes.
|
Reviewed for correctness and architecture/style. I found two blocking issues:
Verification: |
1. EipsIndexPage.formatDate: read the calendar date directly from the date string instead of new Date()+local getters, which UTC-parse date-only values and shift them under local getters — rendering off-by-one across timezones (UTC build vs the viewer's browser) once /eips is server-rendered. Now timezone-stable. 2. /schedule: revert to client:only — ForkGanttChart's timeline/Today marker and EditableDateCell's overdue state read new Date() during render, so server-rendering would bake build-time 'today' into static HTML and drift after midnight / across timezones. As a date-driven planning sandbox (not crawler content), it stays an island.
|
Both blocking issues resolved — good catches; my pixel-diff harness ran build and browser in the same timezone, so the cross-timezone (UTC build vs viewer) drift wasn't visible locally.
I also re-audited the other server-rendered trees for the same class of bug and confirmed they're safe: Verification: @dionysuz-bot please re-review. |
|
Re-reviewed for correctness and architecture/style. I don’t see any remaining blocking issues. The I also spot-checked the adjacent date paths called out: Verification: after |
This is phase 2 of the Astro migration, built on top of #308 (phase 1) and targeting that branch.
Phase 1 set up the Astro routing/shell and hydrated each page body as a single
client:only="react"island, so the server-rendered HTML was essentially empty. Phase 2 systematically converts each route into idiomatic Astro so that route content renders as real static HTML (the LLM/crawler motivation), with no visual or functional change — every route is verified pixel-for-pixel against its phase-1 rendering.See
docs/astro-migration-phase-2.mdfor the full per-route design and rationale.Two conversion strategies (chosen per route)
Both produce real static HTML for crawlers:
.astrorewrite — pure-content routes are rewritten as.astro(zero client JS), with small<script>s for leaf interactivity (filter toggles, hover previews, keyboard nav).client:load) — interactive-but-SSR-safe routes keep their React component but server-render it to static HTML at build time and hydrate for interactivity. Recipe: derive build-time data synchronously (useMemoinstead of a data-loadinguseEffect), gate URL-param state behind anisHydratedflag so the client's first render matches the server HTML (no hydration mismatch on deep links), then flipclient:only→client:load.A few genuinely runtime-dependent routes stay
client:onlyby design (phase 1 anticipated some pages would remain islands).Coverage — 18 of 23 routes now render full static HTML
Static
.astro:/,/upgrades,/decisions,/devnets,/devnets/[id],/404SSR islands (
client:load):/upgrade/pectra,/upgrade/fusaka,/upgrade/hegota,/upgrade/hegota/test-complexity,/upgrade/glamsterdam+/stakeholders/client-priority/devnet-inclusion/test-complexity,/eips,/eips/[id],/scheduleKept
client:only(runtime/timezone/drag-dependent):/calls,/calls/[type],/calls/[...path],/agenda,/rankExample impact:
/upgrade/pectrastatic HTML grew ~16 KB → ~150 KB;/eips/7702~16 KB → ~32 KB;/schedule~16 KB → ~117 KB.Shared primitives added
components/ui/{Tooltip,StatusBadge,MacroPhaseBar,UpgradeCard}.astro— static ports of the React leaves.components/decisions/*+domain/decisions/*— render ACD key decisions as static HTML with a static EIP hover-preview (replacing the React portal tooltip), shared by/and/decisions.components/devnets/*+domain/devnets/devnetCards.ts— static devnet cards / spec page.utils/markdownHtml.ts— build-time inline-markdown → HTML (with tests).scripts/trackLinks.ts— preserves the custom Matomo link-click events on static pages.Phase-1-and-beyond cleanups (from the phase-1 doc)
state,replace) from theLinkhelper and the deadstateNavigateOption.useDevnetNetworkshook (and its dead loading/error branches) and the orphaned React page/leaf components replaced by.astro.snapshot-routesto a scheduled committing workflow (snapshot-routes.yml);deploy.yml+predeploynow run plainbuildoff committed snapshots (deterministic, never blocked by a third-party outage).<noscript>copy andpublic/llms.txtto reflect that most route bodies now render as static HTML.Verification
client:onlycode, confirmed identical when both builds render on the same day — and 0.016% AA noise on decisions-mobile.)?filter=,?layer=,?view=,?tab=,#anchor) URLs.npm run lint,astro check,npm run test(89 tests), andnpm run build(816 pages) all green.@dionysuz-bot please review.