gitstack
closed

Explicit mount primitive (`@component`/`@mount`) for function templates

#115
metrofun · 2026-07-10T20:11:18Z view on GitHub

Function templates have no way to mount a Go-written component returned by a plain function. A function returning tui.Component is not covered by v0.18.1's restriction on component elements/expressions/struct calls, and generation still reaches invalid Go. We'd like an explicit mounting primitive.

Environment

  • go-tui: v0.17.0 (also reproduced against latest v0.18.1)

Minimal reproduction

 package repro

 import tui "github.com/grindlemire/go-tui"

 type row struct{}


 func NewRow() tui.Component { return &row{} }

 func (r *row) Render(*tui.App) *tui.Element {
     return tui.New(tui.WithText("row"))
 }

 templ Field() {
     @NewRow()
 }

Actual

Generation succeeds but emits code that treats the returned tui.Component as a generated function-template view:

__tui_0.GetWatchers undefined (type tui.Component has no field or method GetWatchers)
__tui_0.Root undefined (type tui.Component has no field or method Root)

v0.18.1's release notes say function templates reject component elements, expressions, and struct calls — but a plain function returning tui.Component is not covered and still reaches invalid generated Go.

Requested

An explicit mounting primitive, e.g.

 templ ModelField(w *TargetConfig) {
     @component(ModelCatalogLoading(w))
 }

(or equivalently @mount(...)) that emits an app.Mount call with a stable key mechanism and type-checks that the expression implements tui.Component. We'd prefer an explicit primitive over implicit name/return-type inference.

grindlemire grindlemire · 2026-07-10T16:27:55Z

Thanks for the report! I'll look into this.

grindlemire grindlemire · 2026-07-10T19:35:36Z

Thanks for the clear repro. I put up #117 which makes this error explicit rather than emit broken Go code.

I don't think we will be able to support adding @component(...)/@mount(...), for two reasons:

  1. Struct based components can't exist in a pure function template. Mounting needs a *tui.App and a stable owner for the component cache. A function templ compiles to a plain builder that constructs its tree eagerly, before any app exists. A keyword can't fix that, it would take redesigning function templs to build lazily inside Render(app).
  2. I support this already using a method based render template. @Factory() compiles to app.Mount with a stable key, and the closure returns tui.Component, so the compiler type-checks the expression. The keyword would add syntax without adding capability.

So the supported way to mount a Go-written component is in a struct component rather than a pure one.

I have more info about the difference between the component types in the docs here: https://go-tui.dev/guide/components

If that doesn't cover your use case I would love to know more about what you are trying to do.

metrofun metrofun · 2026-07-10T20:07:31Z

Thanks for looking into it 👍 Long-term I think it's worth revisiting "the supported way to mount a Go-written component is in a struct component rather than a pure one". For example, this is a friction to common case of having imperative primitives (go-written) with declarative higher-order components (gsx). Think, encapsulating interaction grammar or inputs into components and building mostly gsx ui around them