Blog

The Vibe, The Mystery, The Reveal — Building “Livewire in Go

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.goSSR(...) (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.