The Vibe, The Mystery, The Reveal — Building “Livewire in Go”
I wanted a counter. I got a tiny framework. The vibe did the rest.
Promise: you won’t know if we went server-side, client-side, or both… until the reveal.
Chapter 1 — A Quiet Refactor
An inline script in internal/controllers/admin/home_controller.go asked to be set free.
“Centralize it. Make it reusable.”
We birthed a tiny runtime helper:
livewire.JS() — the runtime as a string.
livewire.Script() — same runtime, wrapped and ready.
The tempo picked up.
Chapter 2 — Languages, Unentangled
Go shouldn’t cradle long JS strings. So we split the worlds:
- Moved the runtime to
pkg/livewire/script.js.
- Embedded it with
//go:embed in pkg/livewire/script.go.
“Keep JS in JS, Go in Go.”
The code exhaled.
Chapter 3 — Small Knives, Clean Cuts
We didn’t add weight; we added edges:
mountPlaceholders() — first paint for components.
handleActionClick(e) — precise, tiny interactions.
Readable. Testable. Vibes only.
Chapter 4 — The SEO Dilemma
Placeholders are fast, but robots need HTML.
“Can we render it on the server when we want?”
We coined clear names:
SSR(...) — returns an HB tag.
SSRHTML(...) — returns raw HTML.
No ambiguity. Just intention.
Chapter 5 — Symmetry Wins
If SSR takes params, placeholder should too:
livewire.Placeholder(c, map[string]string{"foo":"bar"})
- The runtime reads
data-lw-param-* and posts them on mount.
“Make both the same.”
Done.
Chapter 6 — The Showcase Page
The demo in internal/controllers/admin/home_controller.go:
- SSR counter:
livewire.SSR(counter.New(app))
- Client-mount:
livewire.Placeholder(counter.New(app))
- Script once:
livewire.Script()
Clicks landed. HTML swapped. Smiles appeared.
The Co‑Pilot — Cascade and GPT‑5‑Class Capabilities
I paired with an AI coding assistant: Cascade.
- Model: Cascade (agentic, IDE-native, code-first)
- What it did here:
- Precise file edits and diffs across Go + JS.
- Suggested API names (SSR vs SSRHTML) that improved clarity.
- Kept multi-file context in working memory; no thread drops.
- Guarded boundaries (no mid-file imports, clean embeds).
- Why GPT‑5‑class capability mattered:
- Long-context reasoning across controllers, helpers, and the runtime.
- Tool-using precision for patching exact code regions.
- Semantic awareness (HB tag vs HTML string; SSR vs placeholder tradeoffs).
- Why older models struggle:
- Shorter context → lost decisions and regressions.
- Fragile edits → accidental overwrites or misplaced imports.
- Weaker code semantics → ambiguous APIs and naming drift.
Cascade didn’t replace the craft; it accelerated it.
Favorite Snippets From the Session
- “Embed as script.js and read it — keep languages apart.”
- “Create the function, then attach the listener.”
- “Placeholder is fast, but SEO wants HTML.”
- “Render is misleading — some consumers need a string.”
- “Symmetry. Params everywhere.”
Each nudge sharpened the result.
The Reveal (Voila)
We shipped both paths:
- Server-side:
SSR(...) and SSRHTML(...).
- Client-mount:
Placeholder(...).
- Both accept params. Minimal API. Maximum vibe.
What Lives Where
pkg/livewire/script.js — tiny runtime (mount + act)
pkg/livewire/script.go — embeds runtime, exposes JS()/Script()
pkg/livewire/placeholder.go — placeholders with params
pkg/livewire/render.go — SSR(...) (HB tag), SSRHTML(...) (string)
pkg/livewire/handler.go — HTTP mount/action endpoint
pkg/livewire/registry.go — alias constructors
internal/components/counter/counter.go — demo component
internal/controllers/admin/home_controller.go — the showcase
Next Moves
- Session-backed state for distributed setups.
- Diff/patch DOM updates.
- Runtime error/validation UX.
- Tiny hot-reload for components.
I came for a counter. I left with a tiny, joyful framework. That’s vibe coding.