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.