← Home

About this site

This site is itself the demo. Rather than just describe what I build, it runs a small AI feature end-to-end — the Chat with my CV widget — while staying a fast, ordinary site for anyone who'd rather just read. This page is the colophon: what the site is made of, and why each piece was chosen.

The stack

  • Astro 6, server-rendered inside a Cloudflare Worker (via @astrojs/cloudflare) rather than exported as static files — so one real server route, the chat API, can run alongside the otherwise-static pages.
  • A UI framework only where it earns its keep. Every page ships as zero-JS HTML — even the chat, whose pill is prerendered into the markup. No framework JavaScript runs on the critical initial load: SolidJS hydrates the tiny pill shell only once the page goes idle, and the heavy chat UI (Markdown rendering plus the streaming chat client) is a separate chunk that loads on first open — quietly warmed at idle so that open feels instant.
  • No Tailwind or component library — scoped Astro styles over a small set of CSS custom properties. At this size a design system would be more weight than help.
  • TypeScript throughout, in strict mode.

How the chat works

  • It answers from a curated corpus — a career summary, my open-source skills, and the project writeups — assembled at build time and bundled into the Worker. Nothing is read from disk at runtime.
  • No vector database, no retrieval step. The whole corpus fits comfortably in the model's context, so it's sent as the system prompt and the model answers from it directly. At this size, RAG would add latency and lose context for no benefit.
  • It runs on Workers AI through Cloudflare's AI binding, so there's no external API key in the loop.
  • Answers stream in. Questions outside the corpus — rate, availability, anything personal — are redirected to contacting me directly, so the chat never improvises facts. The endpoint is rate-limited per visitor to protect the quota.
  • Your place is kept — locally. The conversation and any half-written, unsent question are saved to the browser tab's sessionStorage, so a reload or a click through to another page doesn't lose them: reopen the chat and the history, plus the draft still sitting in the input, are right where you left off. This state never leaves your browser and is tab-scoped by design — close the tab and it's gone.

How it ships

The site is server-rendered inside a Cloudflare Worker, not a static export. Changes land on main through pull requests, never direct pushes. Two GitHub Actions workflows back that flow:

  • CI runs on every PR to main — the unit suite plus type-checking — mirroring the local pre-commit hook so local and CI runs agree.
  • Deploy runs on merge to main — it builds the site, then uses headless Chromium in that same build to render my CV to a PDF and this site's social-share card to an image (each from its own page, so neither can drift from the site), and ships to Cloudflare with Wrangler.

Fittingly, this site was specified, planned, and built with an AI coding agent (Claude Code). The reusable parts of that workflow live in edshav/skills — portable skills for AI coding agents.