Documentation
¶
Overview ¶
Package widget is the widget SDK: the contract a widget implements, the registry a host binary mounts them through, and the interpreter for the small Mermaid dialect widgets are declared in.
Monolithic microservices ¶
A widget is a vertical slice — its own state, its own events, its own live region, its own UI — and every widget a host serves runs inside one process. There is no per-widget port, container, deployment or connection. What separates two widgets is the same thing that separates two goroutines: the Go runtime schedules them across cores, a session's goroutine advances one widget's reducer at a time, and each effect gets a goroutine of its own at the actor boundary. The isolation a fleet of services buys with network calls, this buys with a routing table and a slice index.
That is the whole claim behind "monolithic microservices", and it is worth stating precisely because the parts of it that are true and the parts that are marketing are easy to run together. What holds: independent state, independent event namespaces, independent live regions, independent failure of an effect, and the scheduler's own parallelism across widgets. What does not: a widget cannot be deployed, restarted, scaled or rolled back on its own, because there is one binary. A panicking reducer is contained by the library's panic budget for that session and by nothing else.
Registry is where that cashes out. Registry.LiveConfig turns a set of registered widgets into one gotth-live configuration — one fragment per widget, the union of their event names, and a reducer that routes each event to the widget that owns it — which the host hands to live.New and serves.
The contract ¶
IWidget is the widget contract, and its methods are the lifecycle phases of docs/ontology.md rather than a shape chosen for convenience: register, mount, event, render, effect, unmount, plus the type-independent state projection a host reads. This is what a generated widget implements and what P4's fleet is written against.
It is generic in the widget's own state type, and everything author-facing stays that way. A registry holds widgets of several state types in one ordered sequence, which is heterogeneity Go has no other way to express, so Register erases it — once, behind a generic shell, into an unexported adapter nothing outside this package can hold. That one function is the whole of where a widget's state type is forgotten and the whole of where it is asserted back.
The dialect ¶
The contract the interpreter implements lives beside it in docs/ and is the authority on every question this comment does not settle: docs/dialect.md is the surface syntax, docs/ontology.md the typed concepts, docs/errors.md the error catalogue, and docs/examples/ four worked documents — three that validate and one that does not, whose annotations are this package's acceptance test.
The contract ¶
Interpret parses and validates in one call and returns both an IR value and every finding. Three properties hold, and callers may rely on all three:
- Both passes run to completion. The findings are every finding, never the first, sorted by (line, column, class) so two runs over one document produce byte-identical output. An author who fixes one error and re-runs to discover the next learns that the tool tells them a fraction of the truth, and starts guessing ahead of it.
- Every finding is anchored, classified and repairable: it carries a file:line:column position, the identifier of a class in docs/errors.md, a message naming its subject in the present indicative, and one imperative fix naming the exact spelling to write.
- Nothing generates before it validates. A returned document is sound only when no finding came with it; generating from an unsound document is generating from a guess.
What the IR guarantees ¶
A Document is resolved, total, ordered, anchored and closed: every reference is a handle rather than a name, no field's absence means "work it out", every collection is a sequence in declaration order, every record carries a SourceSpan, and nothing in it names a file path, a host, an address or a credential. EdgeGeometry, Legend and DirtyProjection are computed rather than parsed — they are in the IR so a generator need not re-derive them, and absent from the grammar so an author cannot contradict them.
What this package does not do ¶
It does not generate, render or serve. It does not read a palette: a document names seven semantic tokens and the design system owns their values. It does not compile the host's connection status into a motion gate — Motion carries HostStatusGate so that a generator reads the obligation rather than remembering it.
It also does not open a stream. A Registration carries the streams a widget declared, and the host resolves each source name against the data plane it has, because a widget document names no host, no address and no credential by construction — which is what makes one publishable.
Regenerating ¶
gen.sh writes every generated widget in this checkout and, with --check, asserts the committed output is byte-identical to a fresh generation. The directive below is a discoverability anchor for `go generate`; the script is what CI runs, and it must be run inside the toolchain container that carries the pinned templ CLI.
Index ¶
- Constants
- Variables
- func Interpret(documentName string, source []byte) (*Document, []Finding)
- func InterpretFile(path string) (*Document, []Finding, error)
- func KeyedFragment[B any, S any, I live.IIdentity, W IWidget[S, I]](collection *KeyedCollection[S, I, W], project func(state B) KeyedState[S]) live.Fragment[B]
- func MustRegister[S any, I live.IIdentity](registry *Registry[I], instance IWidget[S, I])
- func ParseCount(raw string, fallback int64) int64
- func ParseCounter(raw string, fallback uint64) uint64
- func ParseFlag(raw string, fallback bool) bool
- func Register[S any, I live.IIdentity](registry *Registry[I], instance IWidget[S, I]) error
- func Stylesheet(palette Palette) string
- func TokenNames() []string
- type Binding
- type BindingClause
- type Channel
- type Class
- type Comparison
- type Control
- type Direction
- type DirtyProjection
- type Document
- type Edge
- type EdgeGeometry
- type Emphasis
- type EventDeclaration
- type EventField
- type EventPayload
- type FieldType
- type Finding
- type GuardPolarity
- type HostState
- type IDirtyDeclarer
- type IWidget
- type Indicator
- type KeyedCollection
- func (collection *KeyedCollection[S, I, W]) Children(state KeyedState[S]) []live.Fragment[KeyedState[S]]
- func (collection *KeyedCollection[S, I, W]) Events() []string
- func (collection *KeyedCollection[S, I, W]) Fragment() live.Fragment[KeyedState[S]]
- func (collection *KeyedCollection[S, I, W]) Lookup(state KeyedState[S], region string) (string, S, bool)
- func (collection *KeyedCollection[S, I, W]) Reduce(state KeyedState[S], event live.Event) (KeyedState[S], []live.Effect[I])
- func (collection *KeyedCollection[S, I, W]) Region(key string) (string, error)
- func (collection *KeyedCollection[S, I, W]) Render(state KeyedState[S]) templ.Component
- func (collection *KeyedCollection[S, I, W]) RenderItem(state KeyedState[S], key string) templ.Component
- func (collection *KeyedCollection[S, I, W]) Snapshot(state KeyedState[S], key string) (Snapshot, bool)
- func (collection *KeyedCollection[S, I, W]) State(items []KeyedItem[S]) (KeyedState[S], error)
- type KeyedItem
- type KeyedState
- func (state KeyedState[S]) Get(key string) (S, bool)
- func (state KeyedState[S]) Items() []KeyedItem[S]
- func (state KeyedState[S]) Remove(key string) KeyedState[S]
- func (state KeyedState[S]) Reorder(keys []string) (KeyedState[S], error)
- func (state KeyedState[S]) Upsert(key string, value S) (KeyedState[S], error)
- type Label
- type LabelSourceKind
- type Legend
- type LegendEntry
- type Marker
- type Motion
- type MountOptions
- type Node
- type NumericBound
- type Orbit
- type Palette
- type Placement
- type Predicate
- type PredicateKind
- type Pulse
- type Registration
- type Registry
- type Related
- type Role
- type Scene
- type Slot
- type SlotKind
- type Snapshot
- type SnapshotField
- type SourcePosition
- type SourceSpan
- type StateField
- type Stream
- type StreamDeclaration
- type TemplateSegment
- type TextTemplate
- type Token
- type Trigger
- type Writer
- type WriterKind
Constants ¶
const ( FieldFlag = ir.FieldFlag FieldCounter = ir.FieldCounter FieldCount = ir.FieldCount FieldText = ir.FieldText )
The four state field types.
const ( TokenSurface = ir.TokenSurface TokenInk = ir.TokenInk TokenMuted = ir.TokenMuted TokenRule = ir.TokenRule TokenAccent = ir.TokenAccent TokenPositive = ir.TokenPositive TokenWarning = ir.TokenWarning )
The seven semantic tokens. The namespace is closed: a widget writes a token name, never a value, so one document renders under any palette that maps the seven.
const ( SlotTitle = ir.SlotTitle SlotSource = ir.SlotSource SlotDescription = ir.SlotDescription SlotStat = ir.SlotStat MarkerLarge = ir.MarkerLarge MarkerSmall = ir.MarkerSmall DirectionForward = ir.DirectionForward DirectionReverse = ir.DirectionReverse GuardWhen = ir.GuardWhen GuardWhenNot = ir.GuardWhenNot LabelLiteral = ir.LabelLiteral LabelBound = ir.LabelBound PredicateAtomic = ir.PredicateAtomic PredicateComposed = ir.PredicateComposed ComparisonAtLeast = ir.ComparisonAtLeast ComparisonAtMost = ir.ComparisonAtMost TriggerClick = ir.TriggerClick TriggerChange = ir.TriggerChange TriggerInput = ir.TriggerInput TriggerSubmit = ir.TriggerSubmit WriterEventField = ir.WriterEventField WriterEventToggle = ir.WriterEventToggle WriterSignal = ir.WriterSignal SignalSlowClient = ir.SignalSlowClient )
The four chrome slots, the two markers, the two channel directions, the two guard polarities, the two label sources, the two predicate forms, the two bound comparisons, the four triggers and the three writers.
const DialectVersion = validate.DialectVersion
DialectVersion is the one dialect version this interpreter implements. A document declaring any other version is refused rather than guessed at: a parser that recovers by assuming what was meant generates a widget the author did not write.
const ( // FieldStationPalette is the palette name the shipped exemplars declare. It // is the interpreter's own constant, because the closed set of palette // names is what the validator refuses a document against (W208) and one // name spelled in two places is one that eventually differs. FieldStationPalette = ir.PaletteFieldStation )
The one shipped palette, and the custom-property prefix every token resolves through.
A widget renders under any palette that maps the seven names; there is one here because one is what the exemplars name, and a second palette that no document referenced would be vocabulary nobody uses.
Variables ¶
var ( // ErrEmptyName is returned for a registration with no widget name. ErrEmptyName = errors.New("widget: a registration needs a name") // ErrInvalidRegion is returned for a region identity the wire cannot carry. ErrInvalidRegion = errors.New("widget: a region identity must match " + regionPattern) // ErrDuplicateName is returned when two widgets claim one name. ErrDuplicateName = errors.New("widget: two widgets claim one name") // ErrDuplicateRegion is returned when two widgets claim one live region. ErrDuplicateRegion = errors.New("widget: two widgets claim one live region") // ErrDuplicateEvent is returned when two widgets claim one wire name. ErrDuplicateEvent = errors.New("widget: two widgets claim one event wire name") // ErrEmptyEvent is returned for a wire name that is the empty string. ErrEmptyEvent = errors.New("widget: every event needs a wire name") // ErrUndeliveredStream is returned for a stream delivering an event the // widget did not declare, which is a subscription whose payload nothing // would route. ErrUndeliveredStream = errors.New("widget: a stream delivers an event the widget does not declare") // ErrUnknownPayload is returned for a payload describing an event the // widget does not declare, which is a set of field names nothing can fill. ErrUnknownPayload = errors.New("widget: a payload describes an event the widget does not declare") // ErrDuplicatePayload is returned when one event is described twice, which // leaves two answers to "what does this event carry". ErrDuplicatePayload = errors.New("widget: one event carries two payload declarations") // ErrEmptyField is returned for a payload field with no wire name. ErrEmptyField = errors.New("widget: every payload field needs a wire name") // ErrDuplicateField is returned when one event declares one field twice. ErrDuplicateField = errors.New("widget: one event declares one payload field twice") // ErrNoWidgets is returned when a host asks a registry holding nothing for // a configuration, which would serve a page with no live region on it. ErrNoWidgets = errors.New("widget: a host needs at least one registered widget") )
The registration faults a host can commit. Every one of them is a startup mistake in a literal somebody wrote, which is why they are reported when the widget is registered rather than at the first connection.
Functions ¶
func Interpret ¶
Interpret parses and validates one widget document and returns the resolved IR together with every finding, sorted by position. documentName is used verbatim in every finding's anchor — no absolute path is constructed and no working directory is printed, because a validator's output is pasted into issues, transcripts and pull requests.
The returned document is always non-nil and always safe to inspect; it is sound only when no finding is returned.
func InterpretFile ¶
InterpretFile reads a document from disk and interprets it. The error is returned only when the file could not be read at all, which is a failure of its own: an unrun check must never read as a pass.
func KeyedFragment ¶
func KeyedFragment[B any, S any, I live.IIdentity, W IWidget[S, I]]( collection *KeyedCollection[S, I, W], project func(state B) KeyedState[S], ) live.Fragment[B]
KeyedFragment projects a collection into its enclosing gotth-live state. A host may override the returned Render and Dirty for columns or lane changes, while retaining Children for targeted card patches. Render must include all members.
func MustRegister ¶
MustRegister is Register for a caller with nowhere to put the error: package initialisation and main, where the registration is a literal in the source and every fault Register reports is a mistake in that literal.
func ParseCount ¶
ParseCount reads a count, keeping fallback when the value is not an integer.
A count is an ordinary number and is not monotonic: it goes down as readily as up, so unlike a counter it takes whatever the wire said.
func ParseCounter ¶
ParseCounter reads a counter, keeping fallback when the value is not a non-negative integer OR when it is lower than the value already held.
The second half is the type's own meaning rather than defensive coding: a counter is monotonic, so a delivery carrying a lower value is a delivery that arrived late, and applying it would walk the counter backwards and re-arm every animation gated on it. Out-of-order delivery repairs itself this way; a counter that accepted the older number would show the wrong total until the next message and would have looked right the whole time.
func ParseFlag ¶
ParseFlag reads a flag, keeping fallback when the value is not a boolean.
The spellings are strconv.ParseBool's, which is what an HTML form and every serializer this library speaks produce: "true"/"false", "1"/"0", "t"/"f", and their capitalised forms.
func Register ¶
Register adds one widget, reporting the first fault in its registration or the first collision with a widget already registered.
It is a function rather than a method because a method cannot take a type parameter, and the type parameter is the point: this is the generic shell CS-7 § 2 asks for, and [erasedWidget] is the unexported adapter behind it. Everything above the shell knows S; nothing below it ever needs to.
Failing here rather than at the first connection is deliberate: a duplicated region identity is a region that stops updating for reasons nothing explains, and a duplicated wire name is an event delivered to the wrong widget. Both are mistakes in a literal somebody wrote, and startup is where a mistake in a literal belongs.
func Stylesheet ¶
Stylesheet is the whole of the CSS a generated widget's markup depends on: one palette's seven values as custom properties, followed by the token classes that read them, the scene's structure, the motion gate and the reduced-motion rule.
It is one string rather than two calls because a host serving the structure without the tokens, or the tokens without the structure, has a widget that renders wrongly in a way no error reports.
func TokenNames ¶
func TokenNames() []string
TokenNames are the seven semantic token names, in palette order. It is the closed namespace a widget document writes into.
Types ¶
type BindingClause ¶
type BindingClause = ir.BindingClause
The IR. Every type below is documented on the record itself.
type DirtyProjection ¶
type DirtyProjection = ir.DirtyProjection
The IR. Every type below is documented on the record itself.
type EdgeGeometry ¶
type EdgeGeometry = ir.EdgeGeometry
The IR. Every type below is documented on the record itself.
type EventDeclaration ¶
type EventDeclaration = ir.EventDeclaration
The IR. Every type below is documented on the record itself.
type EventField ¶
type EventField = ir.EventField
The IR. Every type below is documented on the record itself.
type EventPayload ¶
type EventPayload struct {
// Event is the event's wire name, and must be one this registration
// declares in Events or Internal.
Event string
// Fields are the event's wire field names, in the order the document
// declares them. Declaration order rather than sorted, for the same reason
// a Snapshot is ordered: two equal declarations compare equal
// element-for-element.
Fields []string
}
EventPayload is the wire field names one declared event carries.
type HostState ¶
type HostState struct {
// contains filtered or unexported fields
}
HostState is one session's state across every registered widget: one entry per widget, in registration order.
It is opaque on purpose, and it is the collection whose heterogeneity forces the erasure: one entry per widget, each of a type only its own widget knows. A host holds it, the library carries it between transitions, and the way to read one is Registry.Snapshots, which asks each widget for its own projection.
type IDirtyDeclarer ¶
type IDirtyDeclarer[S any] interface { // Dirty reports whether a transition may have changed this widget's markup. // It is handed this widget's own state, before and after, never the host's. // // Over-declaring costs a suppressed render; under-declaring is a // correctness bug, and live/livetest.AssertDirtyComplete is what catches // it. Dirty(previous S, next S) bool }
IDirtyDeclarer is a widget that declares which state changes its own region's markup depends on.
It is optional, and deliberately not part of IWidget: a widget that does not implement it gets the registry's whole-state comparison, which never reports equal for two states that differ and is therefore always safe. What the declaration buys is the other direction — a transition that moved state this region does not render is not a patch anybody needs — and what it costs if it is wrong is a region that stops updating, which is why the safe behaviour is the default and this is the opt-in.
A generated widget implements it from its document's computed dirty projection, so the declaration is derived from the same source the render is and cannot drift from it. A hand-written widget should implement it only when it can state which fields its markup reads; "all of them" is what the default already does.
type IWidget ¶
type IWidget[S any, I live.IIdentity] interface { // Register declares the widget, once per process and before any session. // It must be pure and must return the same registration on every call. Register() Registration // Mount opens one session's copy of the widget and returns its initial // state together with any effects that start it. It is the first phase of a // session and happens exactly once. Mount(ctx context.Context, session live.Session[I]) (S, []live.Effect[I], error) // Reduce is the event phase: the pure transition from one state to the // next. Given equal state and an equal event it must return equal state and // equal effects, perform no I/O, read no clock, and mutate nothing it was // given. Reduce(state S, event live.Event) (S, []live.Effect[I]) // Render draws the widget's live region. It must be a pure function of // state — equal state renders byte-identical markup — because that // comparison is what suppresses a patch nobody needs. Render(state S) templ.Component // Unmount releases whatever the session held. It is the last phase of a // session and happens exactly once, after which no other phase occurs. Unmount(ctx context.Context, session live.Session[I], state S) // Snapshot projects state into ordered name/value pairs, so a host, a test // or an operator can read a widget's state without knowing its type. Snapshot(state S) Snapshot }
IWidget is one widget: a self-contained slice of live UI that a host registers once and mounts per session.
S is the widget's own state type, and it is a type parameter rather than an opaque one because nothing an author writes has any reason not to know it. A widget is written by whoever owns S; only the host holds widgets of several different S at once, and that is the host's problem rather than the author's. Register is where it is solved, exactly once and under a comment saying so.
The six methods are the lifecycle phases of docs/ontology.md, in the order a session drives them — register, mount, event, render, unmount — plus the one projection a host can read without knowing the widget's type. The set is closed by the ontology rather than by convenience: a widget cannot invent a phase, and a generator emits code for these and no others.
`effect` is the second phase with no method of its own, and it lost one on 2026-09-03 rather than never having had one. A live.Effect is a concrete struct carrying its own Run, so an effect a widget schedules already holds the closure that performs it — over whatever that widget owns — and a method the host called to hand the effect back had nothing left to decide. The phase is still the widget's: it is spelled in the effects Mount and Reduce return.
`tick` is the one phase with no method of its own, and its absence is the ontology's own reading rather than an omission. A tick is what a Stream delivers without a user, so it arrives at Reduce as the event the stream carries — the same way an effect failure arrives at Reduce as an event, so that a reducer sees every cause in one switch and every one of them is replayable.
Nothing here is called concurrently with itself for one session: a session is one goroutine, and Reduce and Render run on it. An effect's own Run is the only application code that may perform I/O, and the library runs it on a goroutine of its own.
func LookupWidget ¶
func LookupWidget[S any, I live.IIdentity](registry *Registry[I], name string) (IWidget[S, I], bool)
LookupWidget returns the widget registered under a name, typed.
The caller supplies S, so unlike Register this assertion is not total: it reports false for a name nobody registered and for a widget whose state type is not the one asked for. That is deliberate — asking the wrong type is a question with no answer, and a package that panicked would be answering it.
type KeyedCollection ¶
type KeyedCollection[S any, I live.IIdentity, W IWidget[S, I]] struct { // contains filtered or unexported fields }
KeyedCollection reuses one generated IWidget definition for controlled, snapshot-driven instances. It has no mutable session state and owns no IO. Register, Reduce, Render and Snapshot are the actual widget methods; initial state comes from the host snapshot, so per-instance Mount/Unmount are not invoked. Use it for pure generated cards, not widgets owning mount resources. Host Init/effects/Teardown own subscriptions and their cancellation.
W preserves the concrete factory result. Neither authors nor this homogeneous collection erase S. Registry remains startup-only and is not modified here.
Validation and panics ¶
Call State after loading or changing membership before passing a snapshot to this collection. NewKeyedState and KeyedState.Upsert validate keys without a collection's region prefix; State also checks the combined wire identifier. State returns errors for invalid input. Rendering or reducing a snapshot that bypassed that check panics if its identifiers cannot fit this collection. A factory that changes its registration after construction also panics: its Go implementation violated the fixed identity/event contract used by gotth-live.
func NewKeyedCollection ¶
func NewKeyedCollection[S any, I live.IIdentity, W IWidget[S, I]]( region string, factory func(region string) W, ) (*KeyedCollection[S, I, W], error)
NewKeyedCollection validates a definition once at startup. factory must be pure, bind the supplied region, and preserve the definition's registration.
func (*KeyedCollection[S, I, W]) Children ¶
func (collection *KeyedCollection[S, I, W]) Children(state KeyedState[S]) []live.Fragment[KeyedState[S]]
Children declares independently dirty regions, without registering anything at runtime. Use Fragment or KeyedFragment to mount them in live.Config.
func (*KeyedCollection[S, I, W]) Events ¶
func (collection *KeyedCollection[S, I, W]) Events() []string
Events returns the definition's browser-sendable event names. Internal stream events stay out of live.Config.Events, preserving default-deny ingress.
func (*KeyedCollection[S, I, W]) Fragment ¶
func (collection *KeyedCollection[S, I, W]) Fragment() live.Fragment[KeyedState[S]]
Fragment mounts this definition once; sessions carry only KeyedState values.
func (*KeyedCollection[S, I, W]) Lookup ¶
func (collection *KeyedCollection[S, I, W]) Lookup(state KeyedState[S], region string) (string, S, bool)
Lookup addresses only an exact member of the current snapshot. It rejects removed IDs even if their browser event was queued before the removal patch.
func (*KeyedCollection[S, I, W]) Reduce ¶
func (collection *KeyedCollection[S, I, W]) Reduce(state KeyedState[S], event live.Event) (KeyedState[S], []live.Effect[I])
Reduce routes only declared events to the addressed live instance. The browser boundary additionally refuses Internal names before dispatch.
func (*KeyedCollection[S, I, W]) Region ¶
func (collection *KeyedCollection[S, I, W]) Region(key string) (string, error)
Region derives the unique wire identity without lossy key normalization.
func (*KeyedCollection[S, I, W]) Render ¶
func (collection *KeyedCollection[S, I, W]) Render(state KeyedState[S]) templ.Component
Render is the default parent, a div containing members in snapshot order.
func (*KeyedCollection[S, I, W]) RenderItem ¶
func (collection *KeyedCollection[S, I, W]) RenderItem(state KeyedState[S], key string) templ.Component
RenderItem renders one generated widget at its assigned region. This lets a host compose columns or other structural markup in the parent fragment.
func (*KeyedCollection[S, I, W]) Snapshot ¶
func (collection *KeyedCollection[S, I, W]) Snapshot(state KeyedState[S], key string) (Snapshot, bool)
Snapshot asks the addressed widget for its named state projection.
func (*KeyedCollection[S, I, W]) State ¶
func (collection *KeyedCollection[S, I, W]) State(items []KeyedItem[S]) (KeyedState[S], error)
State validates a loaded snapshot against this collection's region budget.
type KeyedItem ¶
KeyedItem is one instance's stable key and controlled state. A key is local to its collection; all instances share one widget definition and event set.
type KeyedState ¶
type KeyedState[S any] struct { // contains filtered or unexported fields }
KeyedState is an immutable ordered snapshot. Its zero value is empty. S must also be treated immutably, exactly as live.Config requires.
func NewKeyedState ¶
func NewKeyedState[S any](items []KeyedItem[S]) (KeyedState[S], error)
NewKeyedState copies items, rejecting duplicate or invalid keys. A collection also checks the complete region length through its State constructor.
func (KeyedState[S]) Get ¶
func (state KeyedState[S]) Get(key string) (S, bool)
Get looks up one current member.
func (KeyedState[S]) Items ¶
func (state KeyedState[S]) Items() []KeyedItem[S]
Items returns a copy in display order.
func (KeyedState[S]) Remove ¶
func (state KeyedState[S]) Remove(key string) KeyedState[S]
Remove returns a new snapshot without key; a missing key leaves it unchanged.
func (KeyedState[S]) Reorder ¶
func (state KeyedState[S]) Reorder(keys []string) (KeyedState[S], error)
Reorder accepts exactly one permutation of the current membership.
func (KeyedState[S]) Upsert ¶
func (state KeyedState[S]) Upsert(key string, value S) (KeyedState[S], error)
Upsert replaces one member without moving it, or appends a new member.
type LegendEntry ¶
type LegendEntry = ir.LegendEntry
The IR. Every type below is documented on the record itself.
type MountOptions ¶
type MountOptions[I live.IIdentity] struct { // Origins is the browser Origin allowlist, passed through unchanged. Origins []string // Authenticate, Authorize and CSRF are the live library's three security // hooks. Pass live.Anonymous, live.AllowAll and live.NoCSRFCheck to opt // out deliberately; a nil one is refused rather than defaulted. Authenticate func(request *http.Request) (I, error) Authorize func(ctx context.Context, session live.Session[I], event live.Event) error CSRF func(request *http.Request) error // Init schedules the host's own startup effects for a session, alongside // the effects each widget's Mount returned. It is where a host opens the // sources its widgets' declared streams name. // // There is no Execute beside it, and there was one until 2026-09-03. A // [live.Effect] carries its own Run, so a host effect is performed by the // closure the host built it with — over the source, broker or pool that // host owns — and the executor that used to type-switch an effect back to // its owner had nothing left to decide. What that executor guaranteed is // now the library's: an effect that names itself and carries no behaviour // fails deterministically rather than succeeding at nothing. Init func(ctx context.Context, session live.Session[I]) ([]live.Effect[I], error) // Logger and Dev are passed through to the live configuration. Logger *slog.Logger Dev bool }
MountOptions carries the decisions a registry cannot make for a host.
Everything here is either a security posture or a resource the host owns. None of it has a defensible default that a library could pick — an allowlist a library chose would be an allowlist nobody read — so the four hooks are passed straight through to the live configuration, which refuses a nil one.
type NumericBound ¶
type NumericBound = ir.NumericBound
The IR. Every type below is documented on the record itself.
type Palette ¶
type Palette struct {
// contains filtered or unexported fields
}
Palette is one resolved mapping from the dialect's seven semantic token names to CSS values.
A widget document writes a token name and never a value, so this is where the name becomes a colour, and it is the host that holds one: colour policy belongs to the design system, and a widget that could mint a colour would be a widget that could leave the design system (docs/ontology.md, Palette).
The values are literals in this package's own source and there is no constructor that takes any, which is why nothing here validates one. A token value reaches a stylesheet as CSS rather than as text, so the moment a host can supply values, this type needs the validator that already exists for exactly that — reporting every failing entry rather than the first — and the honest move then is to centralize that one rather than to write a second.
func FieldStation ¶
func FieldStation() Palette
FieldStation returns the shipped palette by name, for a host that knows which one it wants without asking.
func PaletteByName ¶
PaletteByName resolves the name a document's `palette` directive wrote.
An unknown name is reported rather than defaulted: a fallback palette renders a plausible-looking widget in the wrong colours, which is the failure mode hardest to notice in review (docs/dialect.md § 8).
type Registration ¶
type Registration struct {
// Name identifies the widget within one host, and is the name its own
// snapshots carry.
Name string
// Region is the widget's single server-owned live region, and the only
// identity that crosses the wire: a patch names it, so changing it is a
// client-visible change. It matches ^[A-Za-z0-9_:.-]{1,64}$.
Region string
// Events are the wire names a browser may send to this widget. The set is
// exhaustive and default-deny: the host registers exactly these with the
// live library, and a name absent here is refused before any reducer runs.
Events []string
// Internal are the wire names only this widget's own streams and effects
// emit. They are deliberately not registered with the live library —
// registration is what makes a name sendable by a browser — but the host
// still needs them to route an emitted event back to the widget that
// emitted it.
//
// A generated widget puts every stream-delivered event here, because the
// subscription is what delivers it and a browser posting one of its own
// would be forging the source's own truth.
Internal []string
// Streams are the subscriptions the widget declares and the host owns.
Streams []StreamDeclaration
// Payloads are the wire field names each declared event carries, in
// declaration order, for the events that carry any.
//
// They are here because the field names are a contract between two
// programs that never read each other's source: the widget takes them out
// of an event, and whatever fills that event — a host adapter resolving a
// declared stream, a browser control — puts them in. Until this existed the
// filling half was a literal somebody typed, so renaming a field in the
// document still compiled and silently stopped updating the widget.
Payloads []EventPayload
}
Registration is everything a widget declares once per process, before any session exists.
It is the `register` phase as a value. The ontology's cardinality is what makes it a value rather than a call: registration happens exactly once per widget per process and cannot depend on a session, so a widget that could only describe itself while mounted could not be registered at all.
func (Registration) Payload ¶
func (registration Registration) Payload(event string) ([]string, bool)
Payload returns the wire field names one declared event carries.
It is the read half of Registration.Payloads: a host filling an event, or a specification asserting that a host fills exactly the declared set, asks the registration rather than restating the names.
func (Registration) Validate ¶
func (registration Registration) Validate() error
Validate reports the first fault in a registration, or nil.
It checks what one registration can be wrong about on its own. What only a set can be wrong about — two widgets claiming one name, one region or one wire name — is checked by [Registry.Register], because neither registration is at fault by itself.
type Registry ¶
Registry is the set of widgets one host binary serves.
It is the `register` phase's home: a widget is registered once per process, before any session exists, and the registry is what turns that set into one gotth-live application. Registration order is preserved everywhere — fragment order, snapshot order, the order Mount and Unmount run in — because a host that rendered its widgets in map order would render two byte-different pages from one state.
A Registry is built at startup and read afterwards. Register is not safe to call concurrently with anything; everything else is read-only once the last widget is in.
The registry is not generic and cannot be. Its whole purpose is holding widgets of several different state types in one ordered sequence, which is the heterogeneity CS-7 § 2 is about: it is erased exactly once, in Register, into the unexported adapter below.
func NewRegistry ¶
NewRegistry returns an empty registry.
func (*Registry[I]) List ¶
func (registry *Registry[I]) List() []Registration
List returns every registration in registration order.
The slice is a copy, so a caller enumerating the host's widgets — a status page, a test, an operator command — cannot reorder what the host renders.
func (*Registry[I]) LiveConfig ¶
LiveConfig turns the registry into one gotth-live configuration: one fragment per widget, the union of every widget's browser-sendable event names, and a reducer that routes each event to the widget that owns it.
Only Registration.Events is registered with the live library. Registration.Internal is deliberately left out and is still routed, because registration is the only thing that makes a name sendable by a browser: an event a declared stream delivers has a server-side source, and a browser posting one of its own would be forging that source's truth. Default-deny is what the library does with a name nobody registered, so leaving the name out is the whole of the enforcement.
This is the whole of "mounting a widget into a host". The host calls live.New on the result and serves the handler. Every widget on a connection shares its event loop and transport; effects may add goroutines. There is no per-widget process, port, connection or mandatory goroutine.
func (*Registry[I]) Lookup ¶
func (registry *Registry[I]) Lookup(name string) (Registration, bool)
Lookup returns the registration filed under a name.
It returns the registration rather than the widget, and that is the one place the generic contract costs something: a name is a string, so a method that handed back an IWidget[S] would have to be told which S to assert to, which is a second erasure site for a question — "what does this widget declare" — that the registration already answers. LookupWidget is there for a caller that genuinely holds the widget's own type.
type Related ¶
Related is a finding's secondary anchor: the declaration a reference points at, or the first of two conflicting constructs.
type Snapshot ¶
type Snapshot struct {
// Widget is the name of the widget the snapshot came from. A widget fills
// it from its own registration, so a snapshot is self-describing once it
// has left the widget that made it.
Widget string
// Fields are the state fields in declaration order.
Fields []SnapshotField
}
Snapshot is a widget's whole state as ordered name/value pairs.
It is the only way a host, a test or an operator reads a widget's state without knowing its type, and it is ordered by state-field declaration order rather than by name so that two snapshots of equal state are equal element-for-element. A map would have made a snapshot's own text depend on iteration order, which is the same defect the IR forbids in a render.
type SnapshotField ¶
type SnapshotField struct {
// Name is the state field's declared name, as the document spells it.
Name string
// Value is the field's current value rendered as text: "true"/"false" for
// a flag, base-10 digits for a counter or a count, the string itself for
// text.
Value string
}
SnapshotField is one field of a widget's state, rendered as text.
type SourcePosition ¶
type SourcePosition = diag.SourcePosition
SourcePosition is one anchor. Columns are 1-based, in code points.
type SourceSpan ¶
type SourceSpan = diag.SourceSpan
SourceSpan is a construct's extent, carried by every IR record.
type StateField ¶
type StateField = ir.StateField
The IR. Every type below is documented on the record itself.
type StreamDeclaration ¶
type StreamDeclaration struct {
// Name is the stream's declared name in the document.
Name string
// Source is the source name the document declared. It is a name the host
// resolves, never an address.
Source string
// Delivers is the wire name of the event this stream delivers.
Delivers string
}
StreamDeclaration is one long-running subscription a widget declares and the host owns.
It is a declaration and never a connection: it names a source and the event that source delivers, and nothing else. The widget cannot open it — a widget document names no host, no address and no credential by construction — so the host reads this and wires the source it has.
type TemplateSegment ¶
type TemplateSegment = ir.TemplateSegment
The IR. Every type below is documented on the record itself.
type TextTemplate ¶
type TextTemplate = ir.TextTemplate
The IR. Every type below is documented on the record itself.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
internal
|
|
|
cmd/widgetc
command
Command widgetc validates widget documents and prints their findings.
|
Command widgetc validates widget documents and prints their findings. |
|
diag
Package diag carries the widget validator's location-anchored findings and the identifiers of the error catalogue every finding belongs to.
|
Package diag carries the widget validator's location-anchored findings and the identifiers of the error catalogue every finding belongs to. |
|
ir
Package ir holds the widget interpreter's typed intermediate representation: one resolved, ordered, total record per document.
|
Package ir holds the widget interpreter's typed intermediate representation: one resolved, ordered, total record per document. |
|
lex
Package lex turns widget source into positioned tokens, one slice per significant line.
|
Package lex turns widget source into positioned tokens, one slice per significant line. |
|
mocks
Package mocks is a generated GoMock package.
|
Package mocks is a generated GoMock package. |
|
parse
Package parse turns positioned tokens into a widget document's block structure, and reports every structural finding of the catalogue's W0 group.
|
Package parse turns positioned tokens into a widget document's block structure, and reports every structural finding of the catalogue's W0 group. |
|
uigen
Package uigen turns one resolved widget document into the files that make it a running widget: a templ view and a Go scaffold implementing the SDK's widget contract.
|
Package uigen turns one resolved widget document into the files that make it a running widget: a templ view and a Go scaffold implementing the SDK's widget contract. |
|
validate
Package validate resolves a parsed widget document into the typed IR and reports every finding of the error catalogue.
|
Package validate resolves a parsed widget document into the typed IR and reports every finding of the error catalogue. |
|
refinement
|
|
|
Package widgettest renders a registered widget's own live region, so a specification can assert on what a viewer receives rather than on how the markup was produced.
|
Package widgettest renders a registered widget's own live region, so a specification can assert on what a viewer receives rather than on how the markup was produced. |