A page is a struct, a click is a method call, and via streams what changed back to the tab. No JavaScript to write, no build step, plain HTTP until something on the page ticks.
The struct is the page and each button calls a method. The count is one *atomic.Int64 shared by every visitor, so the number you see includes everyone else's clicks.
The same counter on this page. Nothing in its View is live, so a click is one POST and its response is the patch.
Try this
Click + and read the Wire pane: the POST, its status, the patched element.
Wire
Use the demo to see its requests and patches here.
Source
packagedemosimport("sync/atomic""github.com/go-via/via""github.com/go-via/via/h""github.com/go-via/via/on")// LandingCounter is the front page's counter as it runs there. Nothing in its// View is live, so a click is one POST whose response is the patch.typeLandingCounterstruct{n*atomic.Int64}varlandingCountatomic.Int64funcNewLandingCounter()LandingCounter{returnLandingCounter{n:&landingCount}}funcresetLanding(){landingCount.Store(0)}func(c*LandingCounter)Inc(ctx*via.Ctx){c.n.Add(1)}func(c*LandingCounter)Dec(ctx*via.Ctx){c.n.Add(-1)}func(c*LandingCounter)View()h.H{returnh.Div(h.Class("row"),h.Button(on.Click(c.Dec),h.Str("−")),h.Output(h.Str(c.n.Load())),h.Button(on.Click(c.Inc),h.Str("+")),)}
owns it; rendering it with Display makes the unit live, and a Set is pushed
Where a value lives is the type of its field, so the compiler holds the client/server split: server code that reads a via.SignalCS does not build, and a via.State has no Ref for a client expression to reach. The full table is Where state lives, and Why Via makes the longer case.