How I built an AI-assisted trade journal for a Rotary investment club, lost a fight with a folder, briefly deleted my own website, and eventually won — all for the going rate of craft beer.
There's a moment in every side project where you realize the hard part isn't the part you thought was hard.
I said yes to building a trade journal for Bob's investment club — twenty-odd members of the Lakewood Rotary crowd who meet on Wednesdays, argue about stocks, and pass around a paper “Trade Journal” template like it's 1994. Bob wanted to drag it into the present: a member fills out their homework, everyone sees it before the meeting, and — because it's the future — an AI reads the whole batch and tells the committee what to argue about.
Simple, right? The app took an afternoon. The deployment took my sanity, most of a day, and one genuinely terrifying moment where my entire website went dark.
Let me tell you about it. There's beer at the end.
The pitch is boring and the pitch is good: a web form that matches the club's paper template exactly — ticker, price levels, valuation, thesis, moat, “why now,” the pros and cons, the exit plan. Members submit. The submissions land in a shared Google Sheet. A committee dashboard reads them back, lets you stamp each one Approve / Watch / Pass, and — the fun part — hands the whole pile to Claude, which synthesizes the club's collective thinking into a briefing.
No servers to run. No database to babysit. Static HTML on the front, a Google Sheet on the back, a sprinkle of AI in the middle. The kind of thing I've shipped a dozen times. I have GitHub Actions humming all over my personal site pulling in cycling results and Garmin data. I know GitHub Pages.
Reader, I did not know GitHub Pages.
Act I: The folder that wasn't there. I dropped the app into a folder on my main site and pushed. The homepage worked. The /investment-club/ path? 404. Turns out when your user site (you.github.io) and a project repo share a name, they fight over the same URL — and Jekyll, quietly, was skipping my folder entirely during the build. My files were in the repo and not on the site at the same time, like Schrödinger's deploy.
Act II: The ghost file. I fixed the code, pushed it, and the live site kept serving the old version. So I fixed it again. And again. I'd stare at git show HEAD — the correct file, right there in the repo — then load the live URL and get a corpse. GitHub's CDN had cached a stale .js file and would not let go. I was debugging a ghost. Incognito didn't help; caches laugh at incognito.
Act III: The domain heist. Getting clever, I spun up a separate repo to host the app cleanly. It quietly grabbed brooksgroves.com as its own custom domain — and yanked the whole domain away from my main site. For a few minutes, my entire website was a 404. That's a special flavor of adrenaline: the “I think I just deleted myself from the internet” kind. (Everything was fine. It's always fine. It never feels fine.)
GitHub Pages maps exactly one live site to each custom domain at a time. The domain lives in a CNAME file inside whichever repo claims it last — there's no shared registry warning you a domain is already spoken for, and no merge conflict to catch it. Add a custom domain to a second repo and GitHub simply obeys the most recent instruction, silently detaching the domain from wherever it used to point.
Which means the failure mode isn't a build error you'd notice in a log. It's your production site returning a 404 with no warning, because somewhere else in your GitHub account, a brand-new repo just told the DNS system it lives here now.
Act IV: The Apps Script hydra. The Google side had its own trap. Every time I updated the backend code, I clicked “New deployment” — which mints a brand-new URL — instead of pushing a new version of the existing one. Before long I had a forest of deployments named “Untitled,” “Untitled,” “new,” “Untitled,” each at a different address, and my app pointing at whichever one was least correct. Apps Script's UI puts the trap and the fix one dropdown apart and wishes you luck.
Act V: The writes that went nowhere. This was the good one — the bug that actually taught me something. Submissions would vanish. The form said “submitted!” The sheet stayed empty. Reads worked fine; writes disappeared into the void.
Here's the culprit: when a browser fires a no-cors POST at a Google Apps Script endpoint, the script answers with a 302 redirect to a different Google host — and the browser refuses to carry a no-cors POST body through that redirect. The write dies in transit, silently, with a cheerful success message. Reads survived because they used JSONP — a <script> tag, which does follow the redirect.
A no-cors fetch is deliberately half-blind by design: the browser will send the request, but refuses to let your JavaScript read anything back — not the status code, not the body, nothing. That's normally a reasonable trade-off for a fire-and-forget POST.
The part almost nobody expects is what happens when the server answers with a redirect, which is exactly what Apps Script does on every request. Per the fetch spec, a no-cors request is not permitted to replay its body across a redirect to a different origin — so the browser quietly drops the POST payload and follows the redirect empty-handed. The server gets a valid, complete request. It's just missing everything you actually sent. And because no-cors never lets you see the response anyway, your code has no way to notice: the promise resolves, the UI says success, and the row that should exist simply doesn't.
The fix was to make writes travel the same road reads did: pack the payload into a URL and send it as JSONP too. Ugly? A little. Reliable? Completely. The moment I pasted a hand-built test URL into the browser and got back a clean {"ok":true} — and watched a row actually appear in the sheet — I may have made a noise. My partner did not ask about it.
After a day of fighting GitHub Pages' every head, I did the thing I should have done at hour two: I dragged the folder onto Netlify.
Thirty seconds. It just worked. Files served exactly as written, no Jekyll, no cache séance, no domain turf war. I renamed the site lakewood-investment-club, submitted a test trade — my semiconductor love, Intel — and watched it flow: form → sheet → dashboard → done.
Here's where it stops being a war story and starts being the reason I built the thing.
I seeded the club's real worldview into the journal — three aggressive AI-infrastructure bets (a chip alt-play, a data-center cooling name, and a space-based data center moonshot, because of course) alongside three defensive hedges pulled from Bob's reading pile: gold, ag commodities, utilities. Then I hit Synthesize and let Claude read the whole book.
It was good. Not “autocomplete” good — committee-chair good. On its own it noticed the club was running a barbell: a high-beta AI-capex core with a real-asset insurance layer bolted on, and it flagged the quiet contradiction that the same gang buying the AI trade was reading a stack of research warning the AI trade was overheating. It ranked the theses on merit. It caught that the “diversified” hedges all secretly needed a weak dollar — so they weren't as diversified as they looked. And when I fed it a lazy test entry whose entire thesis was “solid chip company,” it replied, with a straight face, that this “isn't a thesis — it's a vibe.”
Every report now auto-archives to a tab in the sheet, timestamped, so the club builds a running record of how its thinking shifts week to week. Download a clean copy, or let the log stack up. That's the part Bob's actually excited about: not one clever analysis, but a memory.
A few things I now believe with the confidence of the recently burned:
- The app is rarely the hard part. Hosting is. Budget accordingly.
- Caches and redirects lie to you. When a browser and a server disagree, build a test that bypasses both — a raw URL, a
git show, a direct read. Ground truth beats guessing every time. - When a platform fights you at every layer, change platforms. Loyalty to a deploy target is not a virtue.
- “New version,” not “new deployment.” If you know, you know. If you don't, you will.
- A dumb, reliable workaround beats an elegant, fragile one. JSONP writes aren't pretty. They ship.
The club has its tool now. Members submit from any device, the committee reviews together, and an AI sits in the corner playing the world's most caffeinated analyst — one who will absolutely call your thesis a vibe.
Bob's gang can stop circling the neighborhood in their Teslas, black t-shirts and all, waiting on me to ship. It works. It even archives its own opinions for posterity.
My invoice, as agreed, is payable in craft microbrews. I intend to collect the full flight.