feat: zero-JS Astro site, writing infrastructure, TS6 compat#158
feat: zero-JS Astro site, writing infrastructure, TS6 compat#158jbdevprimary wants to merge 4 commits into
Conversation
… components With Work and Skills gone, the only remaining interactivity was the nav scroll-spy and one entrance fade. The React island (and its entire dependency tree) was carrying a dozen lines of behavior. - HeroSection/SiteNav/OpenSource/SiteFooter (.tsx) → Hero/SiteNav/ OpenSourceSection/Footer (.astro), statically rendered from resume.ts - Scroll-spy is a ~15-line inline script; the hero fade is CSS - shadcn Button visuals preserved as plain .btn utility classes; lucide icons inlined as SVGs - Removed: @astrojs/react, react, react-dom, motion, lucide-react, radix-ui, class-variance-authority, clsx, tailwind-merge, shadcn, tw-animate-css, @types/react(-dom) — and src/components/ui/ entirely - tsconfig: drop baseUrl (TS 6 deprecates it; paths-only works on TS 5 too) and the react-jsx setting Also permanently kills the dev-server React-hydration failure class, unblocks dependabot #129 (TS 6 baseUrl error) and #155 (lint failure in the now-deleted ui/tabs.tsx), and cuts build time ~3x (2.3s → 0.7s). Verified: dist contains zero .js files (inline scroll-spy + JSON-LD only); full-page screenshot pixel-equivalent to v1.6.0; 25 unit + 13 e2e green; astro check + tsc 0 errors. Decision: keep Astro, drop React — not the reverse Why: Astro earns its place (static rendering from typed data, JSON-LD, sitemap); React carried 15 lines of scroll-spy Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…r, contact scroll-spy, shadcn remnants, dead CSS - Footer: read __APP_VERSION__/__BUILD_DATE__ in frontmatter (Vite define is not substituted in Astro template expressions) — verified the stamp renders real values in dist - SiteNav: the footer (#contact) never crosses the observer band; a bottom-of-page check activates the Contact anchor - Delete components.json (live shadcn config — 'shadcn add' would have silently reintroduced the React stack) and stale package keywords - Strip dead shadcn-era CSS: chart/sidebar tokens, radius-xl..4xl, card/popover-foreground aliases, scroll-fade-x Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… RSS Ports the one genuinely valuable piece of the astrofy template (reviewed at ~/src/reference-codebases/astrofy) into our design system: content- collection blog scaffolding. The template itself was not adopted — its DaisyUI sidebar look is the clone-army aesthetic this redesign exists to escape, and it would regress the stack (Astro 4, Tailwind 3, React-era patterns). - src/content/writing/*.md → /writing/ index + /writing/<slug>/ pages, styled in the site's voice (serif titles, mono dates, minimal prose CSS) - /rss.xml via @astrojs/rss - The Writing nav link surfaces only once a first non-draft post exists — an empty 'blog coming soon' is the anti-signal the portfolio research flagged; publishing a markdown file lights it up automatically - WritingEntry type inferred from our zod schema directly — Astro's generated InferEntrySchema resolves to any under this tsconfig (typegen quirk); call sites annotate explicitly - tsconfig: include .astro/types.d.ts (required for astro:content types) Decision: port astrofy's blog architecture, reject the template wholesale Why: its value is the collections scaffolding; its visual identity is the template look the site just escaped Resolves: 'writing presence' gap (research) + astrofy review directive Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…slug] route, astro sync in check script Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Warning Review limit reached
More reviews will be available in 16 minutes and 18 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (30)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Review Summary
I've completed a thorough review of this comprehensive PR. The changes are well-executed and introduce significant improvements:
Key Changes Verified:
- ✅ Successfully removes React and all related dependencies (react, react-dom, @astrojs/react, motion, lucide-react, radix-ui, cva, clsx, tailwind-merge, shadcn)
- ✅ Adds writing infrastructure with content collections, RSS feed, and blog pages
- ✅ Fixes TS6 compatibility by removing deprecated
baseUrlfrom tsconfig - ✅ Improves build process with
astro syncin the check script - ✅ Implements proper TypeScript typing throughout
- ✅ Correctly filters draft posts in all locations (RSS, index, nav)
- ✅ Clean scroll-spy implementation with inline script
Code Quality:
- All async operations handled appropriately
- Proper TypeScript typing with WritingEntry interface
- Consistent sorting logic for posts (newest first)
- Clean separation of concerns
- No security vulnerabilities identified
Verification Noted:
Per the PR description: 25 unit + 13 e2e tests passing, astro sync && astro check + tsc with 0 errors, biome clean, and full-page screenshot verified against production baseline.
No blocking issues found. The implementation is solid and ready for merge.
You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.
There was a problem hiding this comment.
Code Review
This pull request transitions the site to a zero-JavaScript architecture by replacing React islands and shadcn/ui with pure Astro components, while also introducing a markdown-based writing infrastructure with RSS support. Feedback on these changes highlights a scroll-spy bug on non-home pages that incorrectly clears the active state of the 'Writing' navigation link, potential date-shifting bugs due to missing timezone specifications in 'toLocaleDateString', and fragile array access to 'resume.education' in the footer that could lead to build failures if the array is empty.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| <script> | ||
| const anchors = [...document.querySelectorAll<HTMLAnchorElement>('.nav-anchor')] | ||
| const sections = anchors | ||
| .map((a) => document.getElementById(a.dataset.spy ?? '')) | ||
| .filter((el): el is HTMLElement => el !== null) |
There was a problem hiding this comment.
On writing/blog pages, the scroll-spy script still runs because the #contact footer is present in the DOM. When the user scrolls to the bottom, activate('contact') is called, which toggles the active class on all anchors. Since the "Writing" nav link has no data-spy attribute, it loses its active class, and the "Contact" link gets highlighted instead. Since scroll-spy is only intended for in-page navigation on the home page, we should disable it entirely on non-home pages by returning early if the home-page-specific #open-source section is not present.
<script>
if (!document.getElementById('open-source')) return
const anchors = [...document.querySelectorAll<HTMLAnchorElement>('.nav-anchor')]
const sections = anchors
.map((a) => document.getElementById(a.dataset.spy ?? ''))
.filter((el): el is HTMLElement => el !== null)
| const formatDate = (d: Date) => | ||
| d.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }) |
There was a problem hiding this comment.
Using toLocaleDateString without specifying a timezone will format the date using the local timezone of the build environment (e.g., GitHub Actions runner or local machine). Since z.coerce.date() parses dates as UTC midnight, any local timezone behind UTC (such as US timezones) will shift the formatted date to the previous day. To ensure consistent and correct date rendering regardless of the build environment's timezone, specify timeZone: 'UTC' in the options.
const formatDate = (d: Date) =>
d.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric', timeZone: 'UTC' })
| const formatDate = (d: Date) => | ||
| d.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }) |
There was a problem hiding this comment.
Using toLocaleDateString without specifying a timezone will format the date using the local timezone of the build environment. Since z.coerce.date() parses dates as UTC midnight, any local timezone behind UTC will shift the formatted date to the previous day. Specify timeZone: 'UTC' in the options to ensure consistent and correct date rendering.
const formatDate = (d: Date) =>
d.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric', timeZone: 'UTC' })
| */ | ||
| import resume from '@/content/resume' | ||
|
|
||
| const edu = resume.education[0] |
There was a problem hiding this comment.
Accessing resume.education[0] directly is fragile. If the education array is ever empty, edu will be undefined, and accessing edu.studyType on line 58 will throw a runtime error during the build. To follow defensive programming best practices, use optional chaining and guard the rendering block.
const edu = resume.education?.[0]
| <span> | ||
| {edu.studyType.replace(/ \(.*\)/, '')}, {edu.area} — {edu.institution} ({edu.endDate}), with | ||
| honors | ||
| </span> |
The one comprehensive branch (per directive: everything built and locally reviewed before any remote feedback)
Five commits, three local review passes (two scoped reviewers + one final integration pass), all findings absorbed as forward commits before this PR was opened.
What
1. Zero-JavaScript site — the React island carried ~15 lines of behavior after the lobby redesign. All components rewritten as pure Astro (
Hero,SiteNav,OpenSourceSection,Footer); scroll-spy is a small inline script; the hero fade is CSS. Removed: react, react-dom, @astrojs/react, motion, lucide-react (icons inlined as SVG), radix-ui, cva, clsx, tailwind-merge, shadcn, tw-animate-css, src/components/ui/.distcontains zero .js files; build time 2.3s → 0.7s; the dev-server hydration failure class is gone. Full-page screenshot pixel-equivalent to v1.6.0.2. Writing infrastructure (the valuable piece of the astrofy review — template itself rejected: its DaisyUI sidebar look is the clone aesthetic this site just escaped, and it regresses the stack). Content collection +
/writing/index + post pages +/rss.xml, styled in the site's voice. The Writing nav link surfaces automatically with the first non-draft markdown post — no empty-blog anti-signal. Drop a file insrc/content/writing/to publish.3. TS6 compat + dependabot unblockers — tsconfig drops
baseUrl(TS 6 deprecation that fails #129) and the deletedui/tabs.tsxwas #155's lint failure. Both rebase clean after this merges.4. Cleanups from review — Vite define substitution in Astro frontmatter (version stamp rendered literally before), contact scroll-spy activation,
components.jsonfootgun deleted, dead shadcn-era CSS stripped, RSS sorted, flat glob matching the[slug]route,astro syncin the check script for fresh clones.Verification
astro sync && astro check+ tsc 0 errors; biome clean/writing/and/rss.xmlsmoke-tested (200s); Writing link verified hidden with zero posts🤖 Generated with Claude Code