···11----
22-title: "Vibe Coding Trip Report: Making a sponsor panel"
33-desc: "I needed to ship this before my surgery, so I vibe coded it. It turned out well enough."
44-date: 2026-03-08
55-# hero:
66-# ai: ""
77-# file: ""
88-# prompt: ""
99-# social: false
1010----
1111-1212-import Conv from "../../_components/XeblogConv.tsx";
1313-import Admonition from "../../_components/Admonition.jsx";
1414-1515-A few weeks before surgery, I stared at my project list and felt the clock. If I don't ship this now, it rots for months. I'm [on medical leave recovering from that surgery now](/blog/2026/killing-my-inner-necron/), and the window for meaningful work was closing fast.
1616-1717-The project: a sponsor panel at [sponsors.xeiaso.net](https://sponsors.xeiaso.net). A dashboard showing who sponsors me on GitHub, what tiers they're at, the financial plumbing of open source. Not glamorous, but I'd procrastinated long enough that it had become a guilt object.
1818-1919-I didn't build it the way I normally would. I vibe coded it -- directed an agent team, prepared skills, let the AI handle the parts that had defeated me three separate times. It worked. I'm still kind of surprised.
2020-2121-## The GraphQL swamp
2222-2323-Go and GitHub's GraphQL API are oil and water. I've tried to build this sponsor panel three times, and every attempt died in the same swamp.
2424-2525-<Conv name="Cadey" mood="facepalm">
2626- Every. Single. Time.
2727-</Conv>
2828-2929-The GraphQL libraries in Go are genuinely awful. You choose between [shurcooL/graphql](https://github.com/shurcooL/graphql), which uses reflection-based struct tag gymnastics to build queries, and code generation tools that produce mountains of boilerplate you will never understand. Neither sparks joy. Both make you question your career.
3030-3131-GitHub removed their GraphQL explorer too, so you can't interactively poke at the schema to discover what fields exist. You're reading raw docs and guessing. It's the kind of developer experience that makes you close your laptop and go outside.
3232-3333-Each attempt followed the same arc: excitement, an evening wrestling struct tags, the dawning realization that sponsor pagination is more complex than expected, frustration, then the shelving.
3434-3535-## The hail mary
3636-3737-With surgery looming, I figured: worst case, the vibe coding attempt fails and I'm exactly where I started. Best case it works and I ship something before disappearing into a haze of anesthesia and hospital food.
3838-3939-Needless to say, it works. The panel is live. You can go look at it right now.
4040-4141-<Conv name="Aoi" mood="wut">
4242- Wait, you just... let AI write the GraphQL query code? The stuff that defeated
4343- you multiple times?
4444-</Conv>
4545-4646-<Conv name="Cadey" mood="coffee">
4747- Yep. Turns out when you stop caring whether the struct tags are "clean", you
4848- can just let it generate whatever accursed abomination parses the JSON
4949- correctly and move on with your life.
5050-</Conv>
5151-5252-Stack: Go, [Templ](https://templ.guide), [HTMX](https://htmx.org), Postgres via [Neon](https://neon.tech). GitHub OAuth handles login, the Sponsors GraphQL API feeds the sponsor data. Nothing exotic -- but solid enough to ship.
5353-5454-## Preparing the skills
5555-5656-Claude Code supports [agent skills](https://docs.anthropic.com/en/docs/claude-code/skills) -- pre-loaded context documents that tell the model how to work with a specific technology. I've been experimenting with them for months, and this project put them through their paces.
5757-5858-[Templ](https://templ.guide) barely exists in LLM training data. It's a young Go templating library, and most models confidently hallucinate syntax that looks plausible but doesn't compile. Without intervention, you get code that's 80% right and 100% broken.
5959-6060-Skills fix this. Loading reference documents into the context window gives the model needles to copy from its haystack rather than reconstructing syntax from vibes. It doesn't need to _infer_ how Templ works -- it reads the skills and copies the patterns.
6161-6262-I made four:
6363-6464-- **templ-syntax**: Templ's syntax, with detailed resources for edge cases. The most important one -- covers expressions, conditionals, loops, and how Go integration _actually_ works versus how you'd expect.
6565-- **templ-components**: How to break UI elements into reusable components with props, children, and composition. Without this, the model dumps everything into one giant template.
6666-- **templ-htmx**: Weird edge cases when combining Templ with HTMX. HTMX attributes interact with Templ's rendering model in subtle ways, and getting it wrong produces bugs that are maddening to track down.
6767-- **templ-http**: Best practices for wiring Templ components into `net/http` handlers. Glue code that's easy to get subtly wrong in ways that only surface at runtime.
6868-6969-With these loaded, the model goes from "confidently wrong about Templ" to "reliably correct." The difference between fighting your tools and directing them.
7070-7171-## Specification
7272-7373-Before turning the agents loose, I worked with [Mimi](https://github.com/anthropics/claude-code) to draft a spec -- not "build me a sponsor panel" and walk away. That's how you get a mess.
7474-7575-I described the OAuth flow, the data I needed from the GraphQL API, the page structure, the tier management. We went back and forth until it felt right. Then I ran a second agent over the spec -- one prompted as a cynic whose job was to cut anything that wasn't strictly necessary for an MVP. It was ruthless. It was correct. Half the features I'd specced didn't survive.
7676-7777-<Conv name="Numa" mood="smug">
7878- You mean you automated the "do we actually need this?" meeting. Like a normal
7979- person would do if they had access to an infinite supply of skeptical coworkers.
8080-</Conv>
8181-8282-What survived:
8383-8484-- GitHub OAuth login flow
8585-- Querying the Sponsors GraphQL API for sponsor data (name, tier, avatar, URL)
8686-- Caching sponsor data in Postgres
8787-- A dashboard showing current sponsors grouped by tier
8888-- Session management with secure cookies
8989-9090-Nothing revolutionary. But writing it down gave the agent team a clear target instead of letting them vibe their way into feature creep.
9191-9292-## Implementation with an agent team
9393-9494-Once I locked the spec, a second agent team split it into tasks and started building. Meanwhile, I did the one thing the agents couldn't do themselves: provision credentials.
9595-9696-GitHub OAuth client ID and secret. A Neon database URL. A personal access token with the `read:org` scope for the Sponsors API. The workflow: agent hits an error asking for a credential, I paste it in, agent continues.
9797-9898-Async pair programming. My partner wrote code faster than me but couldn't open a browser to create an OAuth app.
9999-100100-<Conv name="Aoi" mood="grin">
101101- So you were the intern in this arrangement.
102102-</Conv>
103103-104104-<Conv name="Cadey" mood="coffee">
105105- I was the one with the password manager. Arguably more important.
106106-</Conv>
107107-108108-The magic moment: OAuth worked on the first try. Click "Sign in with GitHub," get redirected, authorize the app, land on a page that said "Hi there, Xe!" with real sponsor data below. The first version looked incredibly generic -- bootstrap-y, default fonts, zero personality -- but it _worked_.
109109-110110-Sponsor avatars loaded. Tier names displayed correctly. Real data from the API that had defeated me for years.
111111-112112-<Conv name="Cadey" mood="aha">
113113- I genuinely shouted at my monitor. My husband came in to check on me.
114114-</Conv>
115115-116116-## UI v2: making it match my site
117117-118118-The generic look lasted about an hour before I couldn't stand it. I had the agents restyle everything -- the dark background, the fonts, the xeiaso.net vibe.
119119-120120-Mostly CSS and Templ component work. Swapping a bootstrap card for a custom styled component is where the Templ skills shine: the model knows the precise syntax for conditional classes, children composition, and attribute spreading. No hallucinated tag names, no broken renders.
121121-122122-<!-- TODO: screenshots of the final themed panel -->
123123-124124-## Org sponsorships
125125-126126-After the initial launch, I made another pass for organizational sponsorships. This proved gnarlier than individual ones -- different schema shape, different fields, different everything. GitHub treats org sponsors as a fundamentally different entity in the API.
127127-128128-<Conv name="Cadey" mood="facepalm">
129129- Because of course they do.
130130-</Conv>
131131-132132-Getting org sponsorships right required another round of GraphQL wrestling, but the pattern held: let the agent generate the query, verify the data comes back, move on. The code isn't pretty. The raw JSON parsing would make a linting tool weep. But it handles both individual and organizational sponsors, and that's what matters.
133133-134134-## Was it worth it?
135135-136136-The sponsor panel exists. It works. I shipped it before going under.
137137-138138-<Conv name="Cadey" mood="coffee">
139139- We'll see how much I still like it in a few months when I look at the code
140140- with fresh eyes. Post-surgery me will have opinions.
141141-</Conv>
142142-143143-Vibe coding works best for exactly this: punching through a wall you've hit repeatedly, shipping a clean-enough MVP before a hard deadline. I wouldn't use it for codebases that whole teams need to understand and maintain. But for "generate the cursed GraphQL code so I never touch shurcooL struct tags again"? Four Markdown files loaded into the context window turned that frustrating experiment into a working product. Skills aren't optional -- they're the difference between an AI that flails and one that ships.
144144-145145-I suspect I'll rewrite the GraphQL code after recovery. There's a version of this with proper query types instead of raw JSON parsing, and future-me will demand it. But right now the panel exists, the data is correct, and my sponsors can see I appreciate them.
146146-147147-Knowing that loose end was tied before going under -- that mattered more than clean code.