uihost

package
v0.3.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 8, 2026 License: MIT Imports: 33 Imported by: 0

Documentation

Overview

Package uihost wires a core-ui application onto a framework.App's router. It mounts page rendering, runtime.js, compiled action JS, SSE island streaming, sessions, and signal-driven updates as routes — there is no standalone server. The framework.App owns the HTTP listener.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func GetBuilder

func GetBuilder() *strings.Builder

GetBuilder gets a strings.Builder from the pool, reset to zero length.

func PutBuilder

func PutBuilder(b *strings.Builder)

PutBuilder returns a builder to the pool.

func ReadCustomCSSFile

func ReadCustomCSSFile(path string) string

ReadCustomCSSFile reads a CSS file and returns its content. This is a helper for the demo main.go.

Types

type HreflangLink struct {
	Lang string // BCP-47 tag (e.g. "en", "en-US", "x-default")
	URL  string // canonical URL for that locale
}

HreflangLink declares a locale → URL alternate for the current page. Emitted as <link rel="alternate" hreflang="…" href="…">.

type NotFoundRenderer

type NotFoundRenderer interface {
	RenderNotFound(path string) render.HTML
}

NotFoundRenderer is an optional interface a custom 404 screen (see WithNotFoundScreen) may implement to receive the unmatched request path. The path arrives as an argument, so a single shared screen instance can render per-request detail without a data race. Screens that don't implement it just render via component.Component.Render.

type OG

type OG struct {
	Title       string
	Description string
	Image       string
	URL         string
	Type        string
}

OG holds Open Graph meta tag values for social sharing. Zero-value fields are omitted from output.

type Option

type Option func(*UIHost)

Option configures a UIHost.

func WithCanonicalURL

func WithCanonicalURL(url string) Option

WithCanonicalURL adds a <link rel="canonical"> tag to <head>. Unsafe URLs (non-http(s)/relative) are dropped.

func WithCustomCSS

func WithCustomCSS(css string) Option

WithCustomCSS adds extra CSS to inject into every page.

func WithDescription

func WithDescription(desc string) Option

WithDescription adds a <meta name="description"> tag to <head>.

func WithExtraScripts

func WithExtraScripts(urls ...string) Option

WithExtraScripts adds external <script src="…"> URLs to inject before </body> on every page. Use for dev-only tooling like livereload. CSP-safe — every URL becomes an external resource, no inline JS.

func WithFavicon

func WithFavicon(href string) Option

WithFavicon adds a <link rel="icon"> tag to <head>. The host also auto-serves 204 No Content at the configured URL when no static file matches, so a host that ships no favicon doesn't 404 on every page load. Place a real file at the path in staticDir / staticFS to override.

func WithHeadHTML

func WithHeadHTML(html string) Option

WithHeadHTML injects raw HTML into every page's <head>. This is the escape hatch for arbitrary head content. The HTML is injected verbatim — callers must ensure it is CSP-compatible (no inline <script> or <style> tags). For safe, auto-escaped alternatives, see WithFavicon, WithThemeColor, WithDescription, WithOpenGraph, WithTwitterCard, WithCanonicalURL, and WithPreconnect.

func WithNotFoundScreen

func WithNotFoundScreen(c component.Component) Option

WithNotFoundScreen overrides the default bare 404 fallback. When a request misses every registered screen, static file, and configured favicon, the host renders this component through the active layout — so the 404 page sees the same nav/footer chrome every other page gets. The component's Render() result is wrapped in the default layout; pages without their own layout end up with the framework's bare <main>.

func WithOpenGraph

func WithOpenGraph(og OG) Option

WithOpenGraph adds Open Graph <meta property="og:..."> tags to <head>. Zero-value fields are omitted. URL-typed fields (Image, URL) are dropped if they fail the head-URL allow-list (http(s)/relative only) — a `javascript:`/`data:` URL there is reflected XSS via any social preview crawler that auto-clicks the link.

func WithPreconnect

func WithPreconnect(origins ...string) Option

WithPreconnect adds <link rel="preconnect"> tags for the given origins. Use for early DNS/TCP/TLS connections to external resources (fonts, CDNs).

func WithPublicLLMMD

func WithPublicLLMMD() Option

WithPublicLLMMD opts the host into mounting the page-level LLM-friendly markdown routes (/llm-pages.md, /<screen>/llm.md). Disabled by default because the documents enumerate every screen and the data shape attached to it — useful for AI agents in trusted environments, schema disclosure elsewhere.

func WithRobots

func WithRobots(cfg RobotsConfig) Option

WithRobots registers a /robots.txt handler. A nil-zero RobotsConfig is fine — it ships the open default (allow everything).

func WithSitemap

func WithSitemap(cfg SitemapConfig) Option

WithSitemap registers a /sitemap.xml handler.

func WithStaticDir

func WithStaticDir(dir string) Option

WithStaticDir sets the directory to serve static files from.

func WithThemeColor

func WithThemeColor(color string) Option

WithThemeColor adds a <meta name="theme-color"> tag to <head>.

func WithTwitterCard

func WithTwitterCard(tc TwitterCard) Option

WithTwitterCard adds Twitter Card <meta name="twitter:..."> tags to <head>. Zero-value fields are omitted. URL-typed fields are scheme-restricted per WithOpenGraph.

type RobotsConfig

type RobotsConfig struct {
	// UserAgent is the target crawler. Empty defaults to "*" (all
	// crawlers).
	UserAgent string

	// Allow lists the path prefixes the crawler may visit. Empty +
	// empty Disallow is the open default ("Allow: /").
	Allow []string

	// Disallow lists path prefixes the crawler must not visit.
	Disallow []string

	// SitemapURL is the absolute URL of the sitemap. When empty and
	// WithSitemap was also configured, the handler derives it from
	// SitemapConfig.BaseURL + "/sitemap.xml".
	SitemapURL string

	// CrawlDelay seconds between requests. Zero omits the directive.
	CrawlDelay int
}

RobotsConfig configures the /robots.txt endpoint.

type SEO

type SEO struct {
	Description string         // <meta name="description">
	Canonical   string         // <link rel="canonical">
	Hreflangs   []HreflangLink // <link rel="alternate" hreflang>
	Robots      string         // <meta name="robots"> (e.g. "noindex,nofollow")
	OG          *OG            // Open Graph block
	Twitter     *TwitterCard   // Twitter Card block
	Schema      []seo.Thing    // JSON-LD items
}

SEO bundles every per-page SEO declaration in one struct. Use it as the return type of ScreenSEO when you'd rather declare everything from one method than implement the per-concern interfaces individually. Empty fields are silently skipped — only what's set is emitted.

type SEOScreen

type SEOScreen interface {
	HeadHTML() string
}

SEOScreen is an optional interface that screens can implement to inject per-screen HTML into <head>. This enables per-page SEO: Open Graph tags, description meta, structured data, etc. The returned HTML is injected alongside any global head tags from WithHeadHTML / WithFavicon / etc.

WARNING: the returned HTML is injected verbatim. Implementers must escape any dynamic content (e.g. html.EscapeString for user-supplied titles or descriptions) to prevent XSS.

type ScreenCanonical

type ScreenCanonical interface {
	ScreenCanonical() string
}

ScreenCanonical is an optional screen interface that declares the canonical URL for the current page. Emitted as <link rel="canonical" href="…">. Use to prevent duplicate-content issues when a page is reachable at multiple URLs (filters, sorts).

type ScreenHreflangs

type ScreenHreflangs interface {
	ScreenHreflangs() []HreflangLink
}

ScreenHreflangs is an optional screen interface that declares per-page hreflang alternates for multi-locale apps. When present, the host emits one <link rel="alternate"> per returned link.

type ScreenSEO

type ScreenSEO interface {
	ScreenSEO() SEO
}

ScreenSEO is the bundle-style alternative to the per-concern interfaces. When a screen implements both ScreenSEO AND any of ScreenDescriber / ScreenCanonical / ScreenHreflangs / ScreenSchema, ScreenSEO wins — its fields override.

Returning a zero-value SEO from ScreenSEO opts out of all per-page emission for the screen (useful for routes you want fully naked).

type ScreenSchema

type ScreenSchema interface {
	ScreenSchema() []seo.Thing
}

ScreenSchema is an optional screen interface that returns one or more typed Schema.org items (from core-ui/seo) to emit as <script type="application/ld+json"> blocks.

Implementations typically return:

[]seo.Thing{
    seo.NewArticle(),
    seo.NewBreadcrumbList(...),
}

type Session

type Session struct {
	ID      string
	Created time.Time
}

Session represents a connected browser session.

type SignalAny

type SignalAny interface {
	GetAsInterface() interface{}
	UpdateAsInterface(v interface{})
	Subscribe() <-chan struct{}
}

SignalAny is an interface to allow storing heterogeneous signals.

type SitemapConfig

type SitemapConfig struct {
	// BaseURL is the canonical origin (scheme + host) for emitted
	// <loc> elements. Required — sitemap.xml entries must be absolute
	// URLs per the protocol spec.
	BaseURL string

	// LastMod sets the <lastmod> timestamp emitted for every page.
	// Zero defaults to the time the server started, which is a
	// reasonable signal that anything older was content the build
	// already covered.
	LastMod time.Time

	// ExcludePaths lists route prefixes to omit from the sitemap.
	// Useful for admin routes, drafts, etc. Prefix match.
	ExcludePaths []string
}

SitemapConfig configures the /sitemap.xml endpoint.

type TwitterCard

type TwitterCard struct {
	Card        string // e.g. "summary", "summary_large_image"
	Title       string
	Description string
	Image       string
	Site        string // @username
}

TwitterCard holds Twitter Card meta tag values. Zero-value fields are omitted from output.

type UIHost

type UIHost struct {
	App     *app.App
	Islands *island.Manager
	// contains filtered or unexported fields
}

UIHost mounts a core-ui application onto a router. It serves rendered pages with runtime.js, compiled action JS, SSE streaming for islands, sessions, and signal-driven live updates. The framework.App is responsible for ListenAndServe.

func New

func New(application *app.App, opts ...Option) *UIHost

New creates a new development server.

func (*UIHost) ActiveTheme

func (ds *UIHost) ActiveTheme() style.Theme

ActiveTheme returns the configured theme or the default if unset. Exposed for tooling (e.g. the static-site builder) that needs to resolve theme tokens at build time.

func (*UIHost) AppCSS

func (ds *UIHost) AppCSS() string

AppCSS returns the merged app-level stylesheet body: theme :root custom properties + every registered theme override (.fui-theme-<hash> blocks for ui.Themed wrappers) + customCSS, in that order. Used by the SSG so static export ships the same single asset the live server serves.

The :root block is ALWAYS emitted, even when the host has no explicit App.Theme. Per-component CSS emits bare var(--*) references (no in-CSS fallbacks); without the :root floor every component would render with UA defaults. The DefaultTheme() floor guarantees var() resolution.

func (*UIHost) AutoCompileActions

func (ds *UIHost) AutoCompileActions()

AutoCompileActions scans all registered screens and compiles actions for any that implement InteractiveComponent. The component ID is derived from ScreenComponentID.ComponentID() if implemented, otherwise from the route path.

func (*UIHost) CompileActions

func (ds *UIHost) CompileActions(componentID string, comp component.Component) string

CompileActions compiles a component's action methods to JS and caches them. It also stores the action registry so handleServerAction can invoke Go handlers.

func (*UIHost) ComponentCSSFiles

func (ds *UIHost) ComponentCSSFiles() map[string]string

ComponentCSSFiles returns one asset per registered component: urlPath ("/__gofastr/comp/<name>.css") and the scoped CSS body resolved under the active theme. Used by the static-site builder.

func (*UIHost) CreateSession

func (ds *UIHost) CreateSession() *Session

CreateSession creates a new browser session. The ID is 16 bytes of crypto/rand encoded as hex — the prior `sess-<UnixNano()>` form could collide under load when two CreateSession calls landed in the same nanosecond.

func (*UIHost) CustomCSS

func (ds *UIHost) CustomCSS() string

CustomCSS returns the extra CSS string injected into every page.

func (*UIHost) GetActionJS

func (ds *UIHost) GetActionJS() string

GetActionJS returns all compiled action JS concatenated.

func (*UIHost) GetSession

func (ds *UIHost) GetSession(id string) (*Session, bool)

GetSession retrieves a session by ID.

func (*UIHost) HasStaticFS

func (ds *UIHost) HasStaticFS() bool

HasStaticFS reports whether an embedded static FS is configured.

func (*UIHost) Mount

func (ds *UIHost) Mount(r *router.Router)

Mount registers the UI's HTTP handlers on the given router.

It registers:

  • All `/__gofastr/*` infrastructure endpoints (runtime.js, actions.js, SSE, session, signal updates, server actions, widget JS, CSS chunks)
  • A NotFound handler that first attempts static-file resolution (from either staticDir or staticFS) and falls back to page rendering.

Mount must be called after the framework.App has registered its other routes (entity CRUD, custom endpoints) so the page handler only takes requests that nothing else claimed.

func (*UIHost) PushIsland

func (ds *UIHost) PushIsland(islandID string) error

PushIsland re-renders an island and pushes the update via SSE.

func (*UIHost) PushUpdate

func (ds *UIHost) PushUpdate(islandID string, html string, sessionID string)

PushUpdate pushes an island update for a specific session. This is a convenience method that wraps the island manager's push mechanism.

func (*UIHost) RegisterSignal

func (ds *UIHost) RegisterSignal(id string, s SignalAny)

RegisterSignal registers a signal with the devserver so the signal update endpoint can apply client-sent values. Safe for concurrent use; the signal-update HTTP handler reads the same map.

func (*UIHost) RegisterWidget

func (ds *UIHost) RegisterWidget(sessionID string, w *component.Widget) *island.Island

RegisterWidget registers a widget with the island manager for a session. Returns the widget wrapped as an island for rendering.

func (*UIHost) RenderStaticPage

func (ds *UIHost) RenderStaticPage(ctx context.Context, path string) (string, error)

RenderStaticPage produces a fully-rendered page suitable for static-site generation: it runs the screen's Load(ctx) hook, applies layout/theme, and injects runtime.js, compiled actions, custom CSS, and the route graph — but skips the SSE meta tag because there is no live session. The result is safe to write to disk and serve from any static host.

func (*UIHost) ServeHTTP

func (ds *UIHost) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP makes UIHost satisfy http.Handler by routing through a private router that has Mount called on it. Production wiring goes through framework.App.Mount(host); ServeHTTP exists so the host can also be used standalone (tests, embedded experiments) without dragging in the full framework App.

func (*UIHost) SetStaticFS

func (ds *UIHost) SetStaticFS(fsys fs.FS)

SetStaticFS sets an embedded filesystem for serving static files.

func (*UIHost) StaticDir

func (ds *UIHost) StaticDir() string

StaticDir returns the configured static directory path.

func (*UIHost) StaticFS

func (ds *UIHost) StaticFS() fs.FS

StaticFS returns the configured embedded static FS, or nil if none.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL