~ / blog / building-this-portfolio

17 June 2026

Beyond Vibe Coding: Directing AI to Build This Portfolio

Astro Cloudflare AI-Assisted Development Debugging Meta

There’s a term going around right now — vibe coding. You describe roughly what you want, accept whatever the AI produces, and ship it without fully understanding what’s in the diff. It works until it doesn’t.

This site was built differently. I used Claude Code for every line of code, but I directed every decision, caught the mistakes the AI made, understood the root cause of every bug before accepting a fix, and owned the outcomes. That’s a different skill, and this post is the honest case study of it — including the part where I had to roll back a commit because the AI’s interpretation didn’t match what was in my head.

If you’re evaluating whether I can ship things independently: this is the real version of that story.


Vibe Coding vs. Directing AI

Vibe coding is autocomplete at scale. The AI is the driver; you’re along for the ride. It’s fast and it’s fun, and for throwaway scripts or personal experiments it’s fine.

Directing AI is closer to managing a capable but context-free contractor. They’re technically strong, they’ll solve the problem you described — but if that problem description is ambiguous, they’ll solve the wrong thing and not tell you. The skill is:

  • Specifying precisely enough that the output matches your intent
  • Catching the gap quickly when it doesn’t
  • Knowing which parts of the diff to trust and which to verify
  • Being specific about what to keep versus discard when things go sideways

The diagnostic: if you can’t explain why the AI’s fix worked, you’re vibing. If you can explain it, you’re directing.


The Brief

Domain bought, Cloudflare DNS pointed, goal: a portfolio that signals “Platform Engineer in progress” to a hiring manager within 60 seconds. Before writing code, I had Claude Code interview me — vision, target audience, projects to feature, stack tradeoffs. That interview phase mattered: it forced me to articulate that I wanted “growing fast” and “thinks like a platform engineer” as the two takeaways, which shaped every later decision (the certs timeline, the dark terminal aesthetic, leading project write-ups with real production numbers instead of generic descriptions).

Stack decision: Astro + Tailwind + MDX. Static-first, ships zero JS by default, content collections for projects/blog as structured Markdown. Fast Cloudflare Pages builds, good Lighthouse scores, MDX lets project write-ups embed real code snippets without a separate CMS.

Building in Buckets

Rather than one giant build-and-pray, the work was split into agile buckets — scaffold, hero/about/skills, project pages, certifications, blog, SEO — each one built, deployed to a Cloudflare Pages preview, and reviewed before moving to the next. This caught problems early: I flagged a salary-disclosure line in the contact section before it ever went live, and caught customer-name leakage in project write-ups during review rather than after.

This is the same instinct as deploying behind a feature flag instead of pushing straight to prod — small verified increments beat one large unverified one.


Real Bugs, Real Fixes

1. The Shiki syntax highlighter silently overriding my theme

Every code block on the project pages looked subtly wrong — backgrounds that didn’t match my #0d1117 dark theme. I asked Claude Code to diagnose it, not just “fix the colors.”

The fix wasn’t guessed — it was verified by inspecting the actual built HTML output:

<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto">

Astro’s default Shiki theme (github-dark) injects an inline style directly on every <pre> tag. Inline styles always beat CSS classes, so my Tailwind prose-pre:bg-[#0d1117] override was being silently ignored — not a CSS bug, a specificity bug. The real fix: set markdown.shikiConfig.theme to css-variables in astro.config.mjs, so syntax highlighting respects my color tokens instead of hardcoding GitHub’s palette.

Lesson on directing AI: asking “why is this happening” instead of “make this look right” got me a root-cause fix in one pass instead of CSS patch-on-patch.

2. ASCII diagram alignment — two bugs, not one

The architecture diagrams in my project write-ups looked like a staircase instead of clean rectangles. I’d actually asked for them to be removed at one point, frustrated with the visual bug — then asked to roll that back, because I liked the diagrams and just wanted the bug fixed, not the feature deleted.

There turned out to be two separate causes, found one at a time.

Bug 1 — inconsistent character counts. By counting exact Unicode character lengths per line:

47: +---------------------------------------------+
48: |              Local Development               |   <- 1 char too wide
47: |                                             |

Lines that should have been identical width were off by 1–2 characters — hand-typed ASCII art with inconsistent padding. Fix: build each diagram programmatically, compute exact padding with a script, verify every line length matches before writing it to the file.

Bug 2 — font subsetting. This one survived even after all character counts were correct. The original diagrams used Unicode box-drawing characters (┌─┐│└┘), which live in the U+2500–U+257F range. Google Fonts serves JetBrains Mono with a reduced character subset to keep file sizes small — and that subset doesn’t include box-drawing characters. So the browser silently fell back to a different system font for those specific characters. That fallback font has slightly different character widths, which breaks alignment even when every line has an identical character count. The misalignment is invisible in your editor, only visible in the browser.

The fix: replace all Unicode box-drawing characters with plain ASCII equivalents — +, -, |, v. ASCII characters (U+0000–U+007F) are by definition equal-width in any monospace font. No font can break that guarantee.

Before (Unicode):  ┌────────────────────┐
After (ASCII):     +--------------------+

Lesson: character count is a necessary condition for diagram alignment, not a sufficient one. The rendered font also has to include those characters — otherwise the browser quietly substitutes a different font mid-line, and the alignment breaks in a way that’s invisible in your editor but visible in the browser.

3. The Cloudflare www subdomain 522 error

After redirecting www.sandeeprn.insandeeprn.in, the www version returned a 522 (connection timeout) instead of redirecting. The DNS record existed and was proxied — but Cloudflare Pages didn’t recognize www.sandeeprn.in as belonging to my project. DNS routing and Pages’ own custom-domain registration are two separate steps; I’d only done the first. Adding www.sandeeprn.in explicitly under Pages → Custom Domains fixed it immediately.

4. Astro 5’s breaking change to content collections

Midway through, npm run build failed with LegacyContentConfigError — Astro 5 moved src/content/config.ts to src/content.config.ts and requires an explicit loader per collection instead of just a type. Astro 5 also renamed entry.slug to entry.id and replaced entry.render() with a standalone render(entry) import. Three breaking changes discovered by reading the actual error messages and fixing one at a time, rather than assuming the docs I’d memorised were still current.


The Coordination Failure — and Why It Matters

The most instructive moment wasn’t a code bug. It was a coordination failure between what I asked for and what got built.

I’d flagged the diagram alignment issue and asked for “the diagrams fixed.” Claude Code’s first response was to replace the ASCII diagrams with plain numbered text — technically solving the alignment problem by removing the thing that could misalign. Functionally correct. Not what I wanted.

This is exactly the failure mode of vibe coding: you accept the output because it looks like it works, without checking whether it matches your intent. I caught it immediately because I was paying attention to the diff, not just the result.

The fix wasn’t a small edit — by that point, several other legitimate changes (anonymizing internal team names, adding cron-job scheduling details) had landed in the same commits as the diagram removal. Untangling “keep this, not that” from a tangled commit history needed a real git operation: git reset --hard two commits back, then manually reapplying only the two changes I wanted, verified with a clean build, then a force-push to sync the rewritten history.

What made that recoverable instead of messy:

  • I caught the mismatch immediately, not three commits later — the longer a wrong direction runs, the more it costs to unwind
  • I was specific about scope: “roll back to this exact commit, reapply only these two named changes, do not touch the third thing” — not “redo it” (vague) or “undo everything” (overcorrects)
  • Verification before trusting the result — every diagram rebuild after that point was checked with an actual character-count script before being written to a file, not eyeballed

What This Demonstrates

  • Architectural ownership — stack choice, content structure, and deployment pipeline were directed decisions, not defaults accepted blindly
  • Real debugging discipline — every fix in this post came from inspecting actual output (built HTML, exact character counts, raw error messages), not guessing
  • Git hygiene under pressure — a tangled commit history got untangled with a deliberate reset + selective reapply, verified before force-pushing
  • The difference from vibe coding — I can explain why every fix worked. That’s the bar.