view

package
v0.13.2 Latest Latest
Warning

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

Go to latest
Published: Aug 7, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package view is the view layer: kyse for markup, HTMX for interaction, Alpine for ephemeral client state, Tailwind for style. It is a binary and it is never Node.

A project that uses it still runs with `git clone && aru dev`: no node_modules, no package.json, no lockfile of JavaScript, nothing installed beyond Go and the standalone binaries the CLI fetches. Having a build step is allowed; being Node is not (RULE 13).

It lives in the framework and the views do not. That is the split the Laravel mirror asks for: resources/views/ belongs to laravel/laravel, the rendering machinery to laravel/framework. It used to be a repository of its own, `porang`, and dissolving it is ADR 0021.

The error page deliberately does not use this package. It has to render when the rest is broken, including when the view build failed, so it stays as html/template inline in observability/errorpage.

Index

Constants

View Source
const AssetPath = "/_arandu/assets/"

AssetPath is where assets are served from. The hash is in the path, so the response can be cached forever and a new build simply has a new URL.

View Source
const Stylesheet = "app.css"

Stylesheet is the name of the one stylesheet, and there is only one.

The framework embeds a default under this name and RegisterStylesheet replaces it. Not a second file, not a second URL, not a cascade order: one name, one URL, one set of bytes (RULE 9).

Variables

This section is empty.

Functions

func CSRF

func CSRF(w io.Writer, data any) error

CSRF writes the hidden input a form needs.

The token comes from the data, through an interface the page data satisfies. It is not read from a global: a template that reaches for request state outside the data it was given is how a form ends up with another session's token under load.

func Handler

func Handler(w http.ResponseWriter, r *http.Request)

Handler serves the embedded assets.

Anything whose path carries the right hash is immutable and cached for a year; a wrong hash is served without caching, so a stale reference degrades into a slow page rather than a broken one.

func Include

func Include(w io.Writer, name string, data any) error

Include renders a partial with the same data as the page.

Blade's @include shares the parent's variables. Here the data is one typed struct, so the partial receives exactly it -- and a partial that wants something else is a partial that takes different data, which is what a component is for.

func Register

func Register(name string, f Func)

Register records a compiled view under the name a controller renders it by.

view.Register("invoices/index", renderInvoicesIndex)

Generated code calls it from init(), so importing the views package is what makes them reachable -- the same shape as a database/sql driver.

Registering twice panics rather than replacing. Two views for one name is a build artifact that outlived its source, and finding out at boot beats finding out from a page that renders the wrong thing.

func RegisterLayout

func RegisterLayout(name string, f LayoutFunc)

RegisterLayout records a compiled layout. Generated code calls it from init() when the view contains a @yield.

func RegisterStylesheet added in v0.12.0

func RegisterStylesheet(css []byte)

RegisterStylesheet replaces the embedded stylesheet with the application's.

`aru view:build` compiles resources/css/app.css into assets/app.css, and the skeleton hands those bytes over from init(), the same shape as Register:

//go:embed assets/app.css
var appCSS []byte

func init() { view.RegisterStylesheet(appCSS) }

It replaces rather than adds. The framework's copy is a default so that a project renders before its first view:build, not a base layer to cascade on top of -- two stylesheets would mean two URLs, an order that matters, and a specificity fight nobody can win from the application side.

Without it the browser received the framework's stylesheet, md5 identical, and every class written in a project's own views did nothing. Nothing failed: the page was served, with 200, unstyled.

Registering twice panics rather than replacing, for the same reason Register does: two stylesheets for one name is a build artifact that outlived its source, and finding out at boot beats finding out from a page that renders with somebody else's design.

func Registered

func Registered() []string

Registered returns the known view names, sorted. `aru doctor` reads it to check that every ctx.View("x") has a view named x.

func RenderInto

func RenderInto(w io.Writer, layout string, data any, sections map[string]func(io.Writer) error) error

RenderInto renders a layout, handing it the sections of the child view.

This is what `@extends` compiles to: the child does not write markup, it renders the layout and passes what goes in the holes. Same shape as Blade, where the child template is evaluated to fill sections before the layout runs.

func Text

func Text(v any) string

Text renders a value as a string, for interpolation.

It handles the types a view actually interpolates, and formats anything else with %v. It is not reflection over a struct: the field access already happened in generated Go, and this only turns the result into characters.

func URL

func URL(name string) string

URL returns the versioned path of an asset: /_arandu/assets/<hash>/htmx.min.js

The hash comes from the content, so upgrading HTMX changes the URL and no browser serves a stale script -- without anyone remembering to bump a version.

func Version

func Version() string

Version reports the served version of each asset, for `aru doctor` and for the debug page.

It reports what is served rather than what is embedded, so a stylesheet that never reached the browser shows up here as the framework's hash next to a project that thought it had built its own.

func WrongData

func WrongData(view, want string, got any) error

WrongData is what a generated view returns when the data is not the struct it declared.

Generated code calls it, so the message is the same everywhere:

d, ok := data.(HomeData)
if !ok { return view.WrongData("home", "HomeData", data) }

The alternative -- rendering the zero value -- is a blank page with a 200, which is the failure this framework exists to make impossible.

func Yield

func Yield(w io.Writer, sections map[string]func(io.Writer) error, name string) error

Yield renders the section a child view declared, or nothing.

A layout yields sections that a given child may not have. Blade answers with the default -- an empty string -- and so does this. A missing section is a page without a sidebar, not an error.

Types

type Func

type Func func(w io.Writer, data any) error

Func is what a compiled view is: a function that writes HTML.

`aru view:build` emits one per `.kyse.go` and registers it by name. The data arrives as `any` and the generated function asserts it back to the struct the view declared -- which is why a wrong type is an error naming both sides rather than a blank page.

type LayoutFunc

type LayoutFunc func(w io.Writer, data any, sections map[string]func(io.Writer) error) error

LayoutFunc is a view that receives sections, which is what a layout is.

type Module

type Module struct{}

Module serves the embedded assets and wires the renderer.

It is a kernel.Module for one reason: it was not, and every application shipped a page that asked for its own stylesheet and got 404.

The route used to be exported as Mount, a plain function, with a comment arguing that "a module that exists only to serve two files is a module people have to remember to register". That reasoning was backwards, and the code proved it: Mount had zero call sites across every repository -- the kernel did not call it, the generated main.go did not call it, and neither did the sign-in screen that `aru make:auth` writes. That screen emits three tags, and against a real server all three answered 404: no stylesheet, no HTMX, no Alpine. Found by audit, reproduced end to end.

A function has to be remembered. A module appears in the Register call next to events, jobs and the scheduler, which is where somebody reading main.go already looks to learn what an application is made of.

func NewModule

func NewModule() *Module

NewModule returns the module.

k.Register(view.NewModule(), auth.New(...), events.NewModule())

func (*Module) Name

func (*Module) Name() string

Name is the module identifier.

func (*Module) Renderer

func (*Module) Renderer() httpx.Renderer

Renderer supplies the view renderer to the kernel.

It is kernel.RendererProvider, an optional interface: the kernel asks every registered module whether it brings one, before any route is registered. That is what makes ctx.View work without the application calling a wiring function that somebody eventually forgets.

func (*Module) Routes

func (*Module) Routes(r *httpx.Router)

Routes registers the content-addressed asset route.

One route, one handler. The hash in the path is what makes the response cacheable forever, and what makes a deploy invalidate it without anybody clearing anything.

type Renderer

type Renderer struct{}

Renderer draws a view. It is the concrete side of httpx.Renderer.

func NewRenderer

func NewRenderer() *Renderer

NewRenderer returns the renderer. The kernel hands it to the router at boot.

func (*Renderer) Render

func (*Renderer) Render(ctx context.Context, w http.ResponseWriter, status int, name string, data any) error

Render writes a named view to the response.

The render is recorded on the Collector, so a slow page shows on the console whether the time went to the database or to the markup -- which is the difference between two very different afternoons.

Jump to

Keyboard shortcuts

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