Build Log 02 — Vibecoding as a PM: what I built, what broke, what I'd do differently
Built an AI prototype for aiEDU almost entirely with AI. The product thinking that held up, the bugs that didn't, and the loop that turned every break into a permanent rule.
aiEDU runs a gallery of AI tools built by educators. The intended flow: an educator browses, sees what a peer built, feels “I could make that for my classroom,” and builds their own. In practice it breaks at the critical moment.
The existing path routes them to a generic build page with a one-size-fits-all prompt. Three clicks after the inspiration hit, and they're away from the gallery. By the time they get there, the moment has passed. Inspiration is perishable.
The hypothesis the prototype tests
The navigation break, plus a non-contextual prompt, is the primary cause of churn between gallery visit and first build attempt. Close the loop where the intent lives, in the gallery itself, in context, the moment the educator signals interest, and more of them will actually start building.
Before writing a line of code
The first thing I did wasn't open an editor. It was pressure-test the brief. Two decisions came out of that pass, and both still hold.
The first: pick the right north star. My original instinct was to measure gallery submissions, which is the mission outcome. But it's a terrible metric for a single feature. Submissions happen weeks later, influenced by a dozen other factors. I pulled back to the behavior I could actually observe: did the educator click “Build with Lovable” or “Build with Bolt.new” or Copy Prompt after seeing a generated prompt? That's the hypothesis, and the metric. Anything farther downstream is a different experiment.
The second: pick the right trigger. An early version surfaced the prompt panel on a 45-second dwell timer. I killed it. A timer is a popup in disguise. It interrupts before the user has signaled anything. Card-close is a genuine intent signal: the educator opened a project, looked at it, closed it, came back to browse. That's the moment to meet them. Both decisions sound like implementation details. Neither is.
The product decisions that held up
An early version of the third onboarding question asked educators how big a project they wanted: “quick tool” versus “longer-term project.” I killed it. That question contradicts the platform's core thesis (you can build something real in one session) and creates an escape hatch for uncertainty. Output type (questions, plan, randomizer, ideas, tracker) is more honest about what the platform does and more useful for generating a specific prompt.
The three questions are a UI. The system prompt is the actual product.
Whether any of this works comes down to one thing: is the generated prompt good enough that an educator clicks Build? That's the riskiest assumption in the entire build, and it's still the open question going into production testing. So the prompt is versioned in the repo, documented in the PRD, treated as a first-class artifact, not buried in an API route where nobody reviews it.
How AI was in the loop at every stage
AI touched every phase: discovery, spec, build, debugging. The code wasn't the interesting part.
When I was debating the card-close trigger versus the dwell timer, Claude flagged that a timer-based trigger is behaviorally indistinguishable from a popup. That reframe wasn't mine. It changed the spec. On architecture: the decision to hoist all cross-component state to a single parent came out of a planning conversation before the first line of code, which meant I didn't hit the unmount/reset bug that cost me time on a prior project.
The part I'm most proud of: every bug I hit, I fed the root cause back as a permanent rule. Browser platform gotchas, UX principles, API hygiene, all of it lives as cursor rules that future builds inherit. Don't just fix the bug. Upgrade the system that produces bugs. The player-coach loop.
The mistakes that taught the most
- API docs drift. When the product decision changed from Anthropic to Google Gemini, the code was updated. The docs weren't. CLAUDE.md, .env.example, and inline comments still referenced the wrong API key name. A collaborator hit it and lost an hour. The fix: update every reference in one commit, never piecemeal.
- Free-tier model retirement. gemini-2.0-flash had its free tier quietly retired mid-build. No error, just silence and empty responses. Two hours of diagnostic commits before I found it. Now: pin model names in .env.example with the last verified date and a note on where to confirm the free tier is still active.
- window.open() on HTTPS. External navigation buttons worked perfectly in development. In production: completely dead. window.open() inside an event handler is treated as a blocked popup on HTTPS. Localhost applies looser rules, which is why it slipped through. Native anchors are trusted everywhere, right-clickable, keyboard-accessible. No reason to use window.open() for “open in new tab.”
- sessionStorage doesn't cross tab boundaries. A new tab is a new browser context with no access to the parent tab's storage. Encode the prompt in the URL as a query parameter instead.
What's deliberately not built
Auth, a database, saved prompt history, A/B infrastructure, live-site embedding. Each a conscious scope decision. PostHog events are wired and named correctly. Swapping console.log for the SDK is an afternoon, not a blocking dependency. But analytics infrastructure before you have users is premature optimization, and A/B testing needs a baseline, which means getting the thing live first. These aren't shortcuts. They're sequencing.
For a real production version, the additions are well-defined: API rate limiting, proper auth, and the student-data privacy requirements any K–12 tool must meet. My job is to spec those requirements precisely and partner with the right engineer, not skip them because a prototype didn't need them.
What I'd do differently
Test on a production-equivalent origin earlier. The window.open() and sessionStorage bugs would have been caught in hour one if I'd deployed a Vercel preview before calling the feature done. Localhost security policies lie.
And validating the system prompt should have been a parallel track, not an afterthought. The most important thing I built, the AI generation, has the most validation still outstanding. In a real sprint, testing ten or more educator input combinations against the system prompt runs alongside implementation, not after the prototype ships.
What the prototype actually proves
The prototype doesn't validate the hypothesis. That requires real educators, a real trigger, and production analytics. What it does is prove the concept is buildable at the right fidelity, and make the hypothesis testable. That's a different and lower bar, and it's the right one for a single-session prototype.
The open question it leaves behind is the one that matters more than any UX decision in the build: is the generated prompt good enough? An elegant on-ramp that sends educators to a vague brief is just faster churn. Same loop I run with subscription clients in the first ninety days: ship the hypothesis, watch the behavior, fix the rule, not the dashboard. The mechanism is the same; only the surface changes.
The prompt quality is the product.
Work with me
Running this play on a real subscription?
First-90-days engagements for Performance Health companies before Series B. Three or four per year.
Q3 2026: 2 of 2 spots open · 48-hr response
More reading