CRUDUI

Go API

github.com/polyspec/crudui/packages/validator/go/testsupport/conformance

package conformance // import "github.com/polyspec/crudui/packages/validator/go/testsupport/conformance"

Package conformance records conformance evidence for Go tests.

A test that runs a shared fixture case records the feature it proves,
the fixture, the runtime and the case. Nothing is written unless the
environment variable CRUDUI_CONFORMANCE_EVIDENCE names a directory;
scripts/check-conformance.mts reads it. The format matches
tests/conformance/evidence.mts.

FUNCTIONS

func Record(t testing.TB, feature, fixture, name string)
    Record registers a cleanup on t that records whether the fixture case name
    passed. Call it at the start of the subtest that runs exactly that case,
    so the result reflects the case and every subtest it runs. Empty arguments
    fail the test.

github.com/polyspec/crudui/packages/validator/go/validator

package validator // import "github.com/polyspec/crudui/packages/validator/go/validator"

Package model is the canonical (SPEC-mechanized, single-truth) Go model of the
crudui field.

The model encodes SPEC §3 exactly:

  - Four role slots (validate / design / behavior / options). Each is
    polymorphic across three shapes: false | {} | true. false cancels a composed
    inheritance; {} carries the slot's sub-keys; true is the bare opt-in.
    Every shape survives a round trip (custom UnmarshalJSON).
  - Dependency isolation. A dependent key lives next to its target, never at
    top level: scalar-type dependents go in options; multiple / lang / items
    dependents go under that key. The structure keys multiple / lang are
    themselves polymorphic false | {} | true.
  - design node map. design addresses appearance per DOM node by key
    (show / class / style / attributes / layout / label.{class,style}
    / wrapper.{class,style,attributes} / group.{class,style} /
    prepend.{class,style}) — R8: which node a style targets is visible in the
    key, not inferred.
  - condition maps. A declaration-ordered map whose default key is the
    literal true (R4 — no convention sigils such as _). Keys are evaluated in
    declaration order; the first truthy key wins; order is preserved on a round
    trip.
  - composition. $ref (base inheritance) expands first, then $patch (add /
    remove / replace).

Only canonical model keys are recognized. Other spellings (multiple_max,
lang:append, sortable*, add_buttons, …) and magic tokens (`*`, `:`) are NOT
valid keys here; they have no field on any struct and are rejected (R2 / R4).

Buckets are closed or open, as in the JSON schema. The closed buckets design,
design.{label,wrapper,group,prepend}, behavior, multiple and lang reject any key
they do not model on Unmarshal, and validate and messages accept the registered
rule names only (RuleNames). The open bucket options (Options.Extra) keeps
unknown keys.

Forbidden meta keys are rejected GLOBALLY, not only at top level: every open
bucket guards against them on Unmarshal, one level under the bucket too.
A schema layer expresses the same with propertyNames:{not:{enum:[…]}} rather
than additionalProperties:true, so extension is allowed but forbidden keys are
blocked everywhere.

x{key} comment keys (x-prefixed) are rejected here. The premise is that a
meta-schema x-strips them first, then validates the canonical spec; the strip is
the documented reason a raw x{key} never reaches this model.

CONSTANTS

const ConditionDefaultKey = "true"
    ConditionDefaultKey is the only allowed default key of a condition map:
    the literal true. Convention sigils (e.g. "_") are an R4 violation.

const ForbiddenKeyPrefix = "x"
    ForbiddenKeyPrefix is the x{key} comment prefix. A meta-schema x-strips
    such keys before validating the canonical spec; the strip is the documented
    reason a raw x-prefixed key never legitimately reaches this model, so any
    survivor is rejected. (xclass / xstyle are also listed literally above for
    clarity.)


VARIABLES

var CompositionDirectives = []string{"$ref", "$patch"}
    CompositionDirectives is the closed set of recognized composition keys.

var DesignNodeMap = []string{
	"show",
	"class",
	"style",
	"attributes",
	"label.class",
	"label.style",
	"wrapper.class",
	"wrapper.style",
	"wrapper.attributes",
	"group.class",
	"group.style",
	"prepend.class",
	"prepend.style",
}
    DesignNodeMap is the closed set of design node-map address keys (SPEC
    design_node_map). design may address only these node paths.

var ForbiddenMetaKeys = []string{
	"display_switch",
	"display_target",
	"if",
	"when",
	"show_if",
	"_",
	"seqtokey",
	"__13hex__",
	"$after",
	"$before",
	"$merge",
	"$remove",
	"xclass",
	"xstyle",
}
    ForbiddenMetaKeys are keys that must never appear in a model document,
    at ANY depth. They have no field on any model struct; every Unmarshal
    that owns an open bucket (and every typed slot / structure key) rejects
    them via rejectForbiddenKeys. A schema layer expresses the same with
    propertyNames:{not:{enum:[…]}}.

    Condition-only meta keys (display_switch / display_target / if / when /
    show_if) are gone — conditions live in condition maps and design.show.
    Convention sigils (_) are gone; $after / $before / $merge / $remove are
    expressed with $patch; xclass / xstyle fold into the design node map. The
    x{key} comment pattern is handled by ForbiddenKeyPrefix, not this literal
    list.

var RuleNames = []string{
	"required", "email", "minlength", "maxlength", "min", "max", "match", "pattern",
	"unique", "in", "range", "rangelength", "number", "digits", "equalTo", "containedIn", "typeFrom", "minFrom", "maxFrom", "notEqual",
	"date", "dateISO", "enddate", "url", "accept", "mincount", "maxcount", "step",
	"conditionTree",
	"reference",
}
    RuleNames are the registered rule names, the only keys of validate and
    messages. The validate package's rule registry has exactly these rules.


FUNCTIONS

func ScanForbiddenKeys(spec any, rootPath []string) *compose.ComposeLoadError
    ScanForbiddenKeys recursively scans a composed single spec (compose value
    model: *compose.OMap / []any / scalars) for any forbidden meta key at any
    depth. It returns a *compose.ComposeLoadError (code FORBIDDEN_META_KEY)
    on the first hit, with the dotted path to the offending key in Message and
    Trace; nil when the spec is clean.

    The scan descends into every object value AND every array element (a
    forbidden key nested inside an array of sub-specs is caught too). Map keys
    are checked before descending into their values, so the reported path points
    at the shallowest offending key. rootPath is the optional path prefix for
    the error trace.


TYPES

type BehaviorSlot struct {
	// Cancel is the false shape: cancels a composed-in behavior slot.
	Cancel bool `json:"-"`
	// Bare is the true shape: bare opt-in with no body.
	Bare bool `json:"-"`

	// Onchange is the opaque change-handler script.
	Onchange any `json:"onchange,omitempty"`
	// Onclick is the opaque click-handler script.
	Onclick any `json:"onclick,omitempty"`
	// Onload is the opaque load-handler script.
	Onload any `json:"onload,omitempty"`
}
    BehaviorSlot is the behavior role slot — common across all types.
    Scripts are passed through opaquely to client JS and never enter the
    expression engine (G1 / §4). false (Cancel) cancels a composed-in behavior.
    A behavior's label is behavior.{action}.label, adjacent to the action.

    Polymorphic: Cancel = the false shape, Bare = the true shape, otherwise the
    body carries the {} shape. behavior is a closed bucket: Unmarshal rejects
    any sub-key outside onchange / onclick / onload.

func (s *BehaviorSlot) MarshalJSON() ([]byte, error)
    MarshalJSON emits a BehaviorSlot as false, true, or the body object.

func (s *BehaviorSlot) UnmarshalJSON(data []byte) error
    UnmarshalJSON reads a BehaviorSlot in its polymorphic shape.

type CompositionDirective struct {
	// Ref is base inheritance (file / path). It expands before anything else.
	Ref any `json:"$ref,omitempty"`
	// Patch is the change set (add / remove / replace), applied after $ref.
	Patch any `json:"$patch,omitempty"`
}
    CompositionDirective is a composition key. $ref expands first (base
    inheritance from a file / path; an unresolved $ref cannot load),
    then $patch applies changes (add / remove / replace, JSON-Patch style,
    with deep-path set support). Resolution order: $ref → $patch → single spec
    → field layer. $after / $before / $merge / $remove are forbidden meta keys;
    $patch expresses those changes.

type ConditionMap struct {
	// Keys holds condition expressions in declaration order; the last is
	// typically ConditionDefaultKey.
	Keys []string
	// Values maps each condition expression to its result value.
	Values map[string]any
}
    ConditionMap is the declaration-ordered condition map (SPEC condition_map).
    Keys are evaluated in declaration order; the first truthy key's value
    is returned. If none match, the value under the literal "true" key (if
    present) is returned, else null. The default key is always the literal
    true (ConditionDefaultKey) — R4 forbids convention sigils such as "_".
    Each key is an expressions.md §2 expression; the map is a thin wrapper that
    re-invokes the engine, not a separate parser. A single ternary "...?...:..."
    is the shorthand (same semantics). Evaluated value type — boolean | string |
    number | null — is decided by the call site, not the map. Order is preserved
    on a round trip.

func (c *ConditionMap) MarshalJSON() ([]byte, error)
    MarshalJSON emits ConditionMap as a JSON object in declaration order.

func (c *ConditionMap) UnmarshalJSON(data []byte) error
    UnmarshalJSON decodes a condition map preserving declaration order.

type Content struct {
	// Text is the monolingual value.
	Text *string
	// LangKeys holds language codes in declaration order.
	LangKeys []string
	// Lang is the per-language value, language code → text; a nil entry is a
	// null (empty) label slot for that language (G3 — no effect).
	Lang map[string]*string
	// Null is the explicit null-content form: the whole content is empty/absent.
	// It round-trips as the JSON literal null (SPEC §2 G3).
	Null bool
}
    Content is a content value that is a plain string, a language map (G3
    content translation, e.g. {ko, en}), or null (empty content — identical to
    absent, no render/validate effect, SPEC §2 G3). The language map preserves
    declaration order on a round trip (LangKeys), since emission order is
    observable; a per-language slot may itself be null (empty label for that
    language), kept as a nil *string so the empty slot round-trips as null.

func (c *Content) MarshalJSON() ([]byte, error)
    MarshalJSON emits a Content as null, a string, or an ordered language object
    (per-language null slots emit as JSON null).

func (c *Content) UnmarshalJSON(data []byte) error
    UnmarshalJSON reads a Content from a JSON string, language object, or null,
    preserving language-key order via a token walk. A per-language null value
    is kept as a nil *string (G3 empty slot — no effect); a bare null is the
    null-content form.

type DeclaredAttributes struct {
	// Keys holds the attribute names in declaration order.
	Keys []string
	// Values maps each attribute name to its value.
	Values map[string]string
}
    DeclaredAttributes are the attributes a form field declares for an element,
    in declaration order. Every value is a string; compilation checks the names.

func (a *DeclaredAttributes) MarshalJSON() ([]byte, error)
    MarshalJSON emits DeclaredAttributes as a JSON object in declaration order.

func (a *DeclaredAttributes) UnmarshalJSON(data []byte) error
    UnmarshalJSON reads DeclaredAttributes and rejects a value that is not an
    object and a member value that is not a string.

type DesignNode struct {
	// Class is the node's appearance class.
	Class any `json:"class,omitempty"`
	// Style is the node's inline appearance.
	Style any `json:"style,omitempty"`
}
    DesignNode is the appearance of one named DOM node within the design node
    map: its class and inline style. Either may be an expression or condition
    map. It is a closed bucket: Unmarshal rejects any key beyond class / style.

func (n *DesignNode) UnmarshalJSON(data []byte) error
    UnmarshalJSON reads a DesignNode and rejects any key beyond class / style.

type DesignSlot struct {
	// Cancel is the false shape: cancels a composed-in design slot.
	Cancel bool `json:"-"`
	// Bare is the true shape: bare opt-in with no body.
	Bare bool `json:"-"`

	// Show is the display condition (expression or condition map), not a node.
	// A false result hides the field; validation of a hidden field is skipped.
	Show any `json:"show,omitempty"`
	// Class is the appearance class for the field's own (main / input) node.
	Class any `json:"class,omitempty"`
	// Style is the inline appearance for the field's own node.
	Style any `json:"style,omitempty"`
	// Attributes are the attributes a form field declares for its control.
	Attributes *DeclaredAttributes `json:"attributes,omitempty"`
	// Layout is the layout a group declares for its fields: stacked, inline or line.
	Layout string `json:"layout,omitempty"`
	// Label is the appearance map for the label node.
	Label *DesignNode `json:"label,omitempty"`
	// Wrapper is the appearance map and the declared attributes of the wrapper node.
	Wrapper *WrapperNode `json:"wrapper,omitempty"`
	// Group is the appearance map for the group node.
	Group *DesignNode `json:"group,omitempty"`
	// Prepend is the appearance map for the prepend node.
	Prepend *DesignNode `json:"prepend,omitempty"`
}
    DesignSlot is the design role slot — appearance = display condition (Show) +
    a per-DOM-node appearance map. R8: the targeted node is visible in the key;
    per-node classes are declared here, never as element_class / label_class /
    group_class / input_class / wrapper_class / prepend_class siblings.

    Polymorphic: Cancel = the false shape, Bare = the true shape, otherwise the
    body carries the {} shape. design is a closed bucket: Unmarshal rejects any
    key outside show / class / style / attributes / layout / label / wrapper /
    group / prepend, and any node key outside class / style, and attributes on
    the wrapper (DesignNodeMap).

func (s *DesignSlot) MarshalJSON() ([]byte, error)
    MarshalJSON emits a DesignSlot as false, true, or the body object.

func (s *DesignSlot) UnmarshalJSON(data []byte) error
    UnmarshalJSON reads a DesignSlot in its polymorphic shape.

type FieldSpec struct {
	// Type is structure / identity — the field's widget type. Its target is a
	// scalar, so type-dependent detail is isolated in the Options slot.
	Type string `json:"type,omitempty"`

	// Name is structure / identity — the field name (identifier). Explicit and
	// semantic (R4).
	Name string `json:"name,omitempty"`

	// Default is structure / identity — the default value the evaluator reads
	// when a path is unresolved (expressions.md §5 Path).
	Default any `json:"default,omitempty"`

	// Properties is structure / identity — the child-field map (group / object),
	// in declaration order. It is the $ref / $patch composition entry point.
	Properties *PropertyMap `json:"properties,omitempty"`

	// Items is structure / identity — the choice source: a static array, a
	// static value→label map, or a dynamic source. The dynamic
	// source (type: search) is a model (name string OR nested
	// {table,relations,keys} query) + a sibling api_server (runtime HTTP fn
	// reference) + a placeholder static items, all isolated under it and
	// preserved verbatim. Runtime resolution is out of scope (SPEC §6 R1).
	Items *Items `json:"items,omitempty"`

	// Multiple is structure / identity — repeated rows (true = on). Polymorphic
	// false | {} | true; repetition dependents are isolated under it. Row
	// identity (G4) is NOT a multiple field — at runtime it is a hidden server PK
	// carried in the submitted data, not a build-time spec field (no id key).
	Multiple *Multiple `json:"multiple,omitempty"`

	// Lang is structure / identity — the input-multilingual dimension (the field
	// value is per language, G3). Same structural axis as Multiple. Polymorphic
	// false | {} | true; lang dependents are isolated under it.
	Lang *Lang `json:"lang,omitempty"`

	// Label is content — the field label. May be a language map {ko,en} (G3
	// axis 1). The label sits at top level, next to the field it modifies.
	Label *Content `json:"label,omitempty"`

	// Description is content — the description (may be a language map, G3).
	Description *Content `json:"description,omitempty"`

	// Placeholder is content — the placeholder text.
	Placeholder *Content `json:"placeholder,omitempty"`

	// Prepend is content — text before the input (distinct from the
	// design.prepend node appearance: this is the content text itself).
	Prepend *Content `json:"prepend,omitempty"`

	// Append is content — text after the input.
	Append *Content `json:"append,omitempty"`

	// Help is content — help text (may be a language map, G3).
	Help *Content `json:"help,omitempty"`

	// Content is content — the control text of a button or action field (may
	// be a language map, G3).
	Content *Content `json:"content,omitempty"`

	// Validate is the validation role slot (common, every field).
	Validate *ValidateSlot `json:"validate,omitempty"`

	// Messages are error message overrides keyed by registered rule name.
	Messages *Messages `json:"messages,omitempty"`

	// Design is the appearance role slot — display condition plus a per-node
	// appearance map.
	Design *DesignSlot `json:"design,omitempty"`

	// Behavior is the behavior role slot — opaque scripts that never pass
	// through the expression engine.
	Behavior *BehaviorSlot `json:"behavior,omitempty"`

	// Options is the type-dependent role slot — the type defines and validates
	// it; the core does not participate.
	Options *OptionsSlot `json:"options,omitempty"`

	// Buttons are the form buttons rendered in the form footer. Honored on the
	// form root only.
	Buttons []FormButton `json:"buttons,omitempty"`

	// Action is the submission target kept unchanged in the template. Honored on the
	// form root only.
	Action *FormAction `json:"action,omitempty"`

	// DataLimits bounds submitted data on the form root (CUSC-087).
	DataLimits *FormDataLimits `json:"dataLimits,omitempty"`
}
    FieldSpec is the canonical model field. Every member maps to one top-level
    SPEC key. The four role slots (Validate / Design / Behavior / Options) and
    the polymorphic structure keys (Multiple / Lang) are pointers so the absent
    / false / {} / true shapes all survive a round trip.

    A schema layer over this type rejects every key not listed below and every
    ForbiddenMetaKey; the absent condition-only meta keys must never return.

func (f *FieldSpec) UnmarshalJSON(data []byte) error
    UnmarshalJSON decodes a FieldSpec and rejects any forbidden meta key present
    at the top level (the schema's additionalProperties:false / propertyNames
    guard, mechanized).

type FormAction struct {
	// Method is the HTTP method.
	Method string `json:"method,omitempty"`
	// URL is the submission URL.
	URL string `json:"url,omitempty"`
	// Enctype is the submission encoding.
	Enctype string `json:"enctype,omitempty"`
}
    FormAction is the submission target of the form, kept unchanged in the
    template.

type FormButton struct {
	// Type is submit, reset, button or link.
	Type string `json:"type"`
	// Text is the button text, optionally a language map.
	Text *Content `json:"text,omitempty"`
	// Name is the submitted name.
	Name string `json:"name,omitempty"`
	// Value is the submitted value.
	Value string `json:"value,omitempty"`
	// Href is the link target.
	Href string `json:"href,omitempty"`
	// Design is the button appearance.
	Design *DesignSlot `json:"design,omitempty"`
	// Behavior holds opaque behavior scripts.
	Behavior *BehaviorSlot `json:"behavior,omitempty"`
}
    FormButton is one form button. A button or link needs text; a link needs
    href.

type FormDataLimits struct {
	// Depth is the maximum number of nested container levels.
	Depth *int `json:"depth,omitempty"`
	// Values is the maximum number of submitted value nodes.
	Values *int `json:"values,omitempty"`
}
    FormDataLimits declares the submission depth and value limits (CUSC-087).
    A missing member uses the validation default (CUVR-083).

type Items struct {
	// Static is the static choice array, when items is a literal list.
	Static []any `json:"-"`

	// StaticMapKeys holds the value→label map keys in declaration order (the
	// option values), so the map round-trips in order.
	StaticMapKeys []string `json:"-"`
	// StaticMap is the value→label map: option value (key) → label (string |
	// LangMap | null), kept as raw JSON so any label shape round-trips.
	StaticMap map[string]json.RawMessage `json:"-"`

	// Model is the dynamic source model: a name string OR a nested
	// {table, relations, keys} query. Raw JSON so both shapes round-trip; the
	// runtime, not this model, builds and runs the query.
	Model json.RawMessage `json:"model,omitempty"`
	// Method is the dynamic source method.
	Method any `json:"method,omitempty"`
	// Table is the dynamic source table (top-level shorthand of model.table).
	Table any `json:"table,omitempty"`
	// Relations is the dynamic source relations.
	Relations any `json:"relations,omitempty"`
	// ApiServer is the runtime HTTP-endpoint fn reference (an opaque source
	// callback string). Preserved verbatim; the engine never calls it —
	// invoking the endpoint is runtime, out of scope.
	ApiServer json.RawMessage `json:"api_server,omitempty"`
	// PlaceholderItems is the placeholder static items that coexists with the
	// dynamic source (the pre-fetch choices shown before the source resolves,
	// often [] or a single {"": "선택하세요"} prompt). Raw JSON so the array /
	// value→label shapes round-trip; the runtime replaces it with fetched rows.
	PlaceholderItems json.RawMessage `json:"items,omitempty"`
}
    Items is the choice source: a static array (Static), a static value→label
    map (StaticMap, G3), or a dynamic source. It is the dependency-isolation
    bucket for the items target — dynamic-source keys live here, not at top
    level.

    A static value→label map (SPEC §2 G3) has the option VALUE as the KEY and
    the display label (string | LangMap | null) as the value. The label is
    display-only and never a membership value; an empty (null) label has no
    effect.

    A dynamic source is STRUCTURE ONLY. The supported shape (type: search) is a
    Model (a name string OR a nested {table, relations, keys} relational query)
    plus a sibling ApiServer (a runtime HTTP-endpoint fn reference) plus a
    placeholder static Items. Model / ApiServer / Items are kept as raw JSON and
    preserved verbatim — the engine never runs the query or calls the endpoint.
    Runtime resolution is out of scope (SPEC §6 R1).

func (i *Items) MarshalJSON() ([]byte, error)
    MarshalJSON emits Items as the static array, the static value→label map (in
    declaration order), else the dynamic source object.

func (i *Items) UnmarshalJSON(data []byte) error
    UnmarshalJSON reads Items as a static array, a static value→label map (G3),
    or a dynamic source object. An items object whose keys are not all
    dynamic-source keys is a static value→label map (key = option value, value =
    display label).

type Lang struct {
	// Cancel is the false shape: lang disabled (cancels a composed-in lang).
	Cancel bool `json:"-"`
	// Enabled is the true shape: the bare on switch with no body.
	Enabled bool `json:"-"`

	// Mode is the multilingual mode.
	Mode any `json:"mode,omitempty"`
	// Only has two shapes (SPEC §3 C): a language allowlist []string
	// (["ko","en"]) OR a per-language override map
	// map[string]{validate/design/behavior/options} ({ja:{validate:…}}). The
	// allowlist restricts rendered languages; the override map redefines role
	// slots per language. Held as any so both shapes round-trip.
	Only any `json:"only,omitempty"`
	// Name is the per-language name binding.
	Name any `json:"name,omitempty"`
	// Key is the per-language key binding.
	Key any `json:"key,omitempty"`
	// Frame toggles the language frame.
	Frame any `json:"frame,omitempty"`
	// Title is the language-frame title.
	Title any `json:"title,omitempty"`
	// GroupClass is the language group's appearance class.
	GroupClass any `json:"group_class,omitempty"`
}
    Lang is the input-multilingual dimension (the field value is per language,
    G3 axis 2) and the dependency-isolation bucket for the lang target —
    multilingual control keys live under it, never at top level. Same structural
    axis as Multiple. Polymorphic false | {} | true.

    lang:append / langs / lang_name / lang_key / remove_lang_frame /
    remove_lang_title / lang_group_class are NOT fields here; the canonical
    keys are mode / only / name / key / frame / title / group_class (R2 / R4).
    The magic `:` token in lang:append is rejected outright. lang is a closed
    bucket: Unmarshal rejects any key not modeled below.

func (l *Lang) MarshalJSON() ([]byte, error)
    MarshalJSON emits Lang as false, true, or the body object.

func (l *Lang) UnmarshalJSON(data []byte) error
    UnmarshalJSON reads Lang in its polymorphic false | {} | true shape.

type Messages struct {
	// Keys holds rule names in declaration order.
	Keys []string
	// Values maps each rule name to its message.
	Values map[string]string
}
    Messages are error message overrides keyed by registered rule name,
    in declaration order. Every message is a string.

func (m *Messages) MarshalJSON() ([]byte, error)
    MarshalJSON emits Messages as a JSON object in declaration order.

func (m *Messages) UnmarshalJSON(data []byte) error
    UnmarshalJSON reads Messages and rejects a key that is not a registered rule
    name and a message that is not a string.

type Multiple struct {
	// Cancel is the false shape: multiple disabled (cancels a composed-in
	// multiple).
	Cancel bool `json:"-"`
	// Enabled is the true shape: the bare on switch with no body.
	Enabled bool `json:"-"`
	// DataOnly is the "only" shape: rows come only from the data.
	DataOnly bool `json:"-"`

	// Only declares rows that exist only in the data.
	Only any `json:"only,omitempty"`
	// Min is the minimum row count.
	Min any `json:"min,omitempty"`
	// Max caps the row count.
	Max any `json:"max,omitempty"`
	// Copy toggles the row copy control.
	Copy any `json:"copy,omitempty"`
	// Sortable toggles row reordering.
	Sortable any `json:"sortable,omitempty"`
	// Title names the direct child field whose value titles each row.
	Title any `json:"title,omitempty"`
	// Controls positions row controls: header, footer or outline.
	Controls any `json:"controls,omitempty"`
	// Header selects static or sticky row headers.
	Header any `json:"header,omitempty"`
	// Onclick is the row-action script.
	Onclick any `json:"onclick,omitempty"`
}
    Multiple is repeated rows (true = on) and the dependency-isolation bucket
    for the multiple target — repetition-control keys live under it, never at
    top level. Polymorphic false | {} | true | "only"; "only" is the same as
    {"only": true}, rows that exist only in the data (docs/spec/schema.md).

    Row identity is not a Multiple field. Repeated data is an object keyed by
    row identity, and object member order is row order; the specification has no
    hidden identity or order field (see docs/spec/form-runtime.md).

    multiple_max / sortable* / add_buttons / remove_list_button /
    list_button_text / multiple_button_onclick are NOT fields here; the
    canonical keys are max / sortable / copy / onclick (R2 / R4). multiple is a
    closed bucket: Unmarshal rejects any key not modeled below.

func (m *Multiple) MarshalJSON() ([]byte, error)
    MarshalJSON emits Multiple as false, true, or the body object.

func (m *Multiple) UnmarshalJSON(data []byte) error
    UnmarshalJSON reads Multiple in its polymorphic false | {} | true | "only"
    shape.

type OptionsSlot struct {
	// Cancel is the false shape: cancels a composed-in options slot.
	Cancel bool `json:"-"`
	// Bare is the true shape: bare opt-in with no body.
	Bare bool `json:"-"`

	// KeywordMinLength is the minimum keyword length before lookup fires.
	KeywordMinLength any `json:"keyword_min_length,omitempty"`
	// MarkerDraggable toggles a draggable map marker.
	MarkerDraggable any `json:"marker_draggable,omitempty"`
	// Zoom is the initial map zoom level.
	Zoom any `json:"zoom,omitempty"`
	// GeometryType is the map geometry kind.
	GeometryType any `json:"geometry_type,omitempty"`
	// MaxTags caps the number of tags.
	MaxTags any `json:"max_tags,omitempty"`
	// CheckboxLabel is the inline checkbox label.
	CheckboxLabel any `json:"checkbox_label,omitempty"`
	// OnLabel is the toggle's on-state label.
	OnLabel any `json:"on_label,omitempty"`
	// Collapse toggles a collapsible container.
	Collapse any `json:"collapse,omitempty"`
	// Expend toggles an expandable container.
	Expend any `json:"expend,omitempty"`
	// ViewTotal toggles a total view.
	ViewTotal any `json:"view_total,omitempty"`
	// Stepper toggles stepper controls.
	Stepper any `json:"stepper,omitempty"`
	// BlankMessage is the empty-state message.
	BlankMessage any `json:"blank_message,omitempty"`
	// Callback is the type-dependent callback reference.
	Callback any `json:"callback,omitempty"`
	// Event is the type-dependent event reference.
	Event any `json:"event,omitempty"`

	// Extra holds any further type-owned option keys not modeled above. The
	// core does not interpret them; only the type does. Forbidden meta keys are
	// rejected here too.
	Extra map[string]any `json:"-"`
}
    OptionsSlot is the type-dependent role slot and the dependency-isolation
    bucket for the scalar Type target: the type defines and validates these
    settings, the core stays uninvolved, and a new widget needs no core change.
    Container-type chrome and type-dependent scripts / callbacks live here too.

    Polymorphic: Cancel = the false shape, Bare = the true shape, otherwise the
    body carries the {} shape. The named fields below are the documented scalar
    dependents; Extra holds any further type-owned keys the core does not model.
    Extra is an open bucket but still rejects every ForbiddenMetaKey on
    Unmarshal — extension is allowed, forbidden keys are blocked one level under
    the bucket.

func (s *OptionsSlot) MarshalJSON() ([]byte, error)
    MarshalJSON emits an OptionsSlot as false, true, or the body object,
    inlining Extra keys after the named fields.

func (s *OptionsSlot) UnmarshalJSON(data []byte) error
    UnmarshalJSON reads an OptionsSlot in its polymorphic shape, routing unknown
    keys into Extra and rejecting every ForbiddenMetaKey first.

type PropertyMap struct {
	// Keys holds property names in declaration order.
	Keys []string
	// Values maps each property name to its FieldSpec.
	Values map[string]*FieldSpec
}
    PropertyMap is a declaration-ordered child-field map (group / object). Order
    is preserved on a round trip because emission and composition depend on it.
    It is the $ref / $patch composition entry point.

func (p *PropertyMap) MarshalJSON() ([]byte, error)
    MarshalJSON emits PropertyMap as a JSON object in declaration order.

func (p *PropertyMap) UnmarshalJSON(data []byte) error
    UnmarshalJSON decodes a property object while preserving declaration order
    via a token walk. Each child value is decoded into a FieldSpec, so forbidden
    keys nested in a child are rejected by the child's own UnmarshalJSON.

type ValidateSlot struct {
	// Cancel is the false shape: cancels a composed-in validate slot.
	Cancel bool `json:"-"`
	// Bare is the true shape: bare opt-in with no body.
	Bare bool `json:"-"`

	// Required is the required rule (bool, expression, or condition map).
	Required any `json:"required,omitempty"`
	// Email is the email-format rule.
	Email any `json:"email,omitempty"`
	// Match is the cross-field match rule.
	Match any `json:"match,omitempty"`

	// Extra holds the further registered rules not modeled above.
	Extra map[string]any `json:"-"`
}
    ValidateSlot is the validate role slot. Polymorphic: Cancel = the false
    shape (cancels composed inheritance), Bare = the true shape, otherwise the
    body fields carry the {} shape.

    Each sub-key value may be an expression or a condition map, so conditional
    validation is expressed without a separate key (e.g. required: '.subscribe',
    email: true). validate accepts the registered rule names only (RuleNames):
    Extra keeps every further registered rule, and any other key is rejected on
    Unmarshal.

func (s *ValidateSlot) MarshalJSON() ([]byte, error)
    MarshalJSON emits a ValidateSlot as false, true, or the body object.

func (s *ValidateSlot) UnmarshalJSON(data []byte) error
    UnmarshalJSON reads a ValidateSlot in its polymorphic shape.

type WrapperNode struct {
	// Class is the node's appearance class.
	Class any `json:"class,omitempty"`
	// Style is the node's inline appearance.
	Style any `json:"style,omitempty"`
	// Attributes are the attributes declared for the node root.
	Attributes *DeclaredAttributes `json:"attributes,omitempty"`
}
    WrapperNode is the appearance of the wrapper node (the node root) and the
    attributes a form field declares for it. It is a closed bucket: Unmarshal
    rejects any key beyond class / style / attributes.

func (n *WrapperNode) UnmarshalJSON(data []byte) error
    UnmarshalJSON reads a WrapperNode and rejects any key beyond class / style /
    attributes.

github.com/polyspec/crudui/packages/validator/go/validator/compose

package compose // import "github.com/polyspec/crudui/packages/validator/go/validator/compose"

Package compose is the Go port of the model composition engine (SPEC §5, G5).

The parser's FIRST pass: expand $ref (base inheritance) then $patch
(add/remove/replace + deep-path set) into a single, composition-free spec,
BEFORE the field layer / validation / render. Unresolved composition is a LOAD
ERROR (ComposeLoadError) — never valid:true.

Byte-for-byte parity with the JS reference (validator-ts/src/compose). Both load
the SAME shared fixture tests/fixtures/compose/cases.json and must reproduce
it identically (G-B 4-language idempotence). interface{} is spelled any (Go ≥
1.18). No eval.

FUNCTIONS

func DecodeOrdered(data []byte) (any, error)
    DecodeOrdered parses a JSON document into the engine value universe (*OMap
    / []any / scalars), preserving object declaration order. It is the exported
    entry the model validate pass uses to load a spec into the compose value
    model before composing it. Same semantics as the internal decodeOrdered.

    Strings and member names keep their text exactly: an unpaired surrogate
    escape becomes the three-byte encoding of that surrogate and a byte that is
    not UTF-8 is kept, so both remain text that is not valid UTF-8 and the input
    text check rejects them (docs/spec/input-text.md). Nothing is replaced with
    U+FFFD.

func OrderMembers(value any) any
    OrderMembers returns a copy of a specification value in specification member
    order, the own-property order of a JavaScript object: member names that are
    array indexes come first in ascending numeric order, then all other names in
    insertion order. Objects are reordered at every depth; arrays stay arrays
    with their elements ordered the same way; scalars are returned unchanged.
    Record data keeps insertion order and is never passed here.


TYPES

type ComposeErrorCode string
    ComposeErrorCode is a stable machine-readable load-error code (one per
    unresolved-behavior class). Mirrors errors.ts ComposeErrorCode exactly.

const (
	// RefFileNotFound: $ref points at a file that does not exist (the file cannot be loaded).
	RefFileNotFound ComposeErrorCode = "REF_FILE_NOT_FOUND"
	// RefFormatError: $ref string is malformed: "(…" with no closing ").keys", or empty path.
	RefFormatError ComposeErrorCode = "REF_FORMAT_ERROR"
	// RefDetectKeyNotFound: a detectKey path segment (or the trailing "properties") is absent.
	RefDetectKeyNotFound ComposeErrorCode = "REF_DETECT_KEY_NOT_FOUND"
	// RefCycle: $ref cycle detected (A→B→A); resolution stops instead of recursing forever.
	RefCycle ComposeErrorCode = "REF_CYCLE"
	// RefValueType: $ref value is neither a string nor an array of strings.
	RefValueType ComposeErrorCode = "REF_VALUE_TYPE"
	// PatchShape: $patch is not an object of deep-path → value entries.
	PatchShape ComposeErrorCode = "PATCH_SHAPE"
	// PatchPathConflict: $patch op targets a path whose intermediate node is a non-object scalar.
	PatchPathConflict ComposeErrorCode = "PATCH_PATH_CONFLICT"
	// PatchRemoveTargetMissing: $patch remove targets a path that does not exist (strict remove).
	PatchRemoveTargetMissing ComposeErrorCode = "PATCH_REMOVE_TARGET_MISSING"
	// ForbiddenMetaKey: a forbidden meta key (condition-only / composition-directive / magic-symbol,
	// or an x{key} comment that survived x-strip) was found at any depth in the
	// composed single spec. The forbidden-scan runs in the LOAD path after compose
	// expansion; a hit is a load failure, never valid:true. Mirrors JS forbidden-scan.ts.
	ForbiddenMetaKey ComposeErrorCode = "FORBIDDEN_META_KEY"
	// InvalidRuleParameter: a validate rule parameter outside the rule's
	// definition (docs/spec/validation-rules.md, Parameter errors). Trace is the
	// field's declaration path.
	InvalidRuleParameter ComposeErrorCode = "INVALID_RULE_PARAMETER"
	// InvalidRulePattern: a pattern or match parameter outside the CRUDUI pattern
	// language. Trace is the field's declaration path.
	InvalidRulePattern ComposeErrorCode = "INVALID_RULE_PATTERN"
	// UnknownRule: a validate or messages key that is not a registered rule name
	// (docs/spec/validation-rules.md, Parameter errors). Trace is the field's
	// declaration path.
	UnknownRule ComposeErrorCode = "UNKNOWN_RULE"
	// InvalidText: a string or member name in the specification or a composition
	// file that is not valid UTF-8 (docs/spec/input-text.md). Trace is its path.
	InvalidText ComposeErrorCode = "INVALID_TEXT"
)
type ComposeLoadError struct {
	// Code is the stable machine-readable code (one per unresolved-behavior class).
	Code ComposeErrorCode
	// Message is the human-readable detail.
	Message string
	// Trace is the composition path stack when the error occurred (for cycle/trace).
	Trace []string
}
    ComposeLoadError is every composition load failure. NOT a validation error.

    Composition is a pre-processing pass that runs BEFORE validation/render:
    the parser expands $ref/$patch into a single spec first (G5). An unresolved
    composition is therefore NOT a validation failure (valid:false) — it is a
    LOAD FAILURE: the spec itself does not come into existence. Never let an
    unresolved $ref pass as valid:true.

func (e *ComposeLoadError) Error() string
    Error implements the error interface.

type ComposeOptions struct {
	// Basepath for relative $ref resolution.
	Basepath string
}
    ComposeOptions configures a composition pass.

type FileLoader interface {
	// Normalize resolves path against basepath to the canonical key the map uses.
	Normalize(path, basepath string) string
	// Load returns the parsed document at the canonical key, or a NOT_FOUND error.
	Load(key string) (*OMap, error)
}
    FileLoader resolves a normalized file key to its parsed document. Load
    returns a *ComposeLoadError with code RefFileNotFound when the key is absent
    — the engine relies on that exact code (an unresolved $ref is a load error,
    never valid:true).

type MemoryLoader struct {
	// Has unexported fields.
}
    MemoryLoader is an in-memory loader over a fixed { key: doc } map. Shared
    fixtures pass a file set here; the engine resolves $ref against it with no
    disk access. Keys are the normalized identifiers (Normalize output).

func NewMemoryLoader(files map[string]*OMap) *MemoryLoader
    NewMemoryLoader builds a MemoryLoader from a { key: doc } map. Each doc
    must be an *OMap (a parsed object). A defensive clone is taken on Load so
    resolution never mutates the source set.

func (l *MemoryLoader) Load(key string) (*OMap, error)
    Load returns a copy of the parsed doc at key in specification member order,
    or a RefFileNotFound load error. The copy keeps resolution from mutating the
    source file set (mirrors structuredClone(doc) on the JS side).

func (l *MemoryLoader) Normalize(path, basepath string) string
    Normalize passes an absolute path through unchanged; a relative path is
    prefixed by basepath.

type OMap struct {
	// Has unexported fields.
}
    OMap is an insertion-order-preserving string map — the Go stand-in for a JS
    object. Declaration order is LOAD-BEARING in composition (a key declared
    after $ref overrides the inherited base; a key before it is overridden by
    it). Go's native map randomizes iteration, and encoding/json drops object
    key order, so the engine carries order explicitly in OMap and decodes JSON
    through it.

    The compose engine operates only on OMap / []any / scalars — the same value
    universe the JS engine sees (object / array / primitive). Every node that is
    a JS "plain object" is an *OMap here.

func ComposeProperties(properties *OMap, loader FileLoader, opts ComposeOptions) (*OMap, error)
    ComposeProperties composes a properties map: expand $ref to a base,
    overlay $patch, return the single (composition-free) properties map. Named
    sibling keys follow declaration order — a key declared after $ref overrides
    the base; a key declared before it is overridden by the base.

    The input and the result are in specification member order (see
    OrderMembers).

func ComposeSpec(spec *OMap, loader FileLoader, opts ComposeOptions) (*OMap, error)
    ComposeSpec composes a full field spec: expand a field-level $ref/$patch,
    then recurse into its properties (which may itself compose). Returns the
    single spec. The input and the result are in specification member order (see
    OrderMembers).

func NewOMap() *OMap
    NewOMap returns an empty ordered map.

func (m *OMap) Clone() *OMap
    Clone makes a shallow copy (new key slice + value map; values shared). The
    engine never mutates shared value subtrees in place — it rebuilds (mirrors
    the JS spread `{ ...node }`).

func (m *OMap) Delete(key string)
    Delete removes key (and its position). A no-op if absent.

func (m *OMap) Get(key string) (any, bool)
    Get returns the value for key and whether it was present.

func (m *OMap) Has(key string) bool
    Has reports whether key is present.

func (m *OMap) Keys() []string
    Keys returns the keys in insertion order (a copy; mutating it is harmless).

func (m *OMap) Len() int
    Len returns the number of entries.

func (m *OMap) MarshalJSON() ([]byte, error)
    MarshalJSON emits the entries in insertion order so a round trip is stable.

func (m *OMap) Set(key string, value any)
    Set inserts or updates key. A new key appends to the order; an existing key
    keeps its position (matching JS object assignment semantics).

type RawMember struct {
	Name  string
	Value []byte
}
    RawMember is one object member of a JSON document: its name, kept exactly as
    DecodeOrdered keeps text, and the raw JSON text of its value.

func DecodeRawMembers(data []byte) ([]RawMember, error)
    DecodeRawMembers splits a JSON object document into its members in document
    order without decoding the values. A document that is not one JSON object is
    an error.

github.com/polyspec/crudui/packages/validator/go/validator/expr

package expr // import "github.com/polyspec/crudui/packages/validator/go/validator/expr"

Package expr is the Go model expression engine (expressions.md §0-§7).

Pipeline: string -Lexer-> []Token -Parser-> Node (AST) -[Evaluator + data]->
value. The three stages are separate (GRAMMAR §0): the lexer knows no
grammar, the parser knows no data, the evaluator knows no string. No eval,
no regex/string split evaluation (GRAMMAR §10) — a single forward scan plus
recursive descent.

The single truth is the shared 4-language fixture tests/fixtures/expr/cases.json
(already passing in JS and PHP). Tokens, AST shape, and evaluated value/truthy
are byte-compatible with the JS reference (validator-ts ConditionParser.ts
+ PathResolver.ts). AST serialization omits position (Rust holds none → the
4-language equivalence compares position-free nodes).

CONSTANTS

const DefaultKey = "true"
    DefaultKey is the literal expression used as the condition-map default.

const MaxExpressionDepth = 64
    MaxExpressionDepth is the most nodes on a path from the root of an
    expression's syntax tree to a leaf. A deeper expression is a parse error in
    every runtime.


FUNCTIONS

func ClearCache()
    ClearCache clears the parsed-AST cache.

func Evaluate(expression string, formData map[string]any, currentPath []string, rowKeys []int) (bool, error)
    Evaluate evaluates an expression to a truthy boolean (show/display call
    site).

func EvaluateValue(expression string, formData map[string]any, currentPath []string, rowKeys []int) (any, error)
    EvaluateValue evaluates an expression to its raw value
    (class/style/number/null call site).

func IsConditionExpression(value any) bool
    IsConditionExpression distinguishes expression strings from literal
    appearance values.

func IsNumber(value any) bool
    IsNumber reports whether a value is carried by a Go numeric type.

func IsTruthy(value any) bool
    IsTruthy is the truthy test (JS Boolean, GRAMMAR §6). null/false/0/0.0/""
    are falsy; "0", "false", empty list, empty object, and every non-empty value
    are truthy.

func NumberValue(value any) (float64, bool)
    NumberValue returns the double value of a number, whatever Go numeric type
    carries it: every signed and unsigned integer width, both float widths and a
    json.Number. An integer converts to the nearest double. A json.Number whose
    text does not read as a double is not a number.

func RelativeBase(currentPath []string, rowKeys []int, levelsUp int) []string
    RelativeBase returns the path that a relative path with levelsUp further
    dots starts from: the current field is one level and each further dot
    moves one more level up, and a row key leaves together with the name of its
    collection (expressions.md, "Evaluation").

func ResolveConditionMap(entries []ConditionEntry, formData map[string]any, currentPath []string, rowKeys []int) (any, error)
    ResolveConditionMap resolves an ordered condition map against form data.
    Declaration order is the slice order. The default key (literal "true") is
    matched by text, not by evaluation, so it never short-circuits an earlier
    real condition. Returns (value, nil) on a default fallthrough miss with no
    default key present.

func ValueAtSegments(data any, path []string) any
    ValueAtSegments reads the value at concrete path segments (JS
    getValueByPath). A missing key, or a descent into a value that is neither an
    object nor an array, yields nil. Numeric segments index into arrays.


TYPES

type BinaryNode struct {
	Operator string
	Left     Node
	Right    Node
}
    BinaryNode is a comparison (== != > >= < <=) or logical (&& ||) operation.

func (n *BinaryNode) ToMap() map[string]any
    ToMap serializes the Binary node and its operands to the shared-fixture AST
    shape {type, operator, left, right}.

type ConditionEntry struct {
	Expr  string
	Value any
}
    ConditionEntry is one ordered expr→value pair of a condition map.

type Evaluator struct {
	// Has unexported fields.
}
    Evaluator walks an AST against form data (GRAMMAR §5/§6/§7, JS PathResolver
    parity). The shared 4-language fixture is generated from the JS reference
    engine, so this evaluator mirrors validator-ts PathResolver function for
    function:

      - Evaluate(node) → bool (JS evaluateCondition): isTruthy of the resolved
        value for Path/Literal; logical short-circuit; comparison; in; unary;
        a ternary is isTruthy(its branch value).
      - EvaluateValue(node) → value (JS evaluateExpressionValue): a ternary
        returns its branch value; everything else returns the boolean condition.

    truthy (GRAMMAR §6): null/false/0/"" falsy; "0", "false", [], {}, and every
    non-empty value truthy (matches JS Boolean: empty array/object are truthy).
    == / != use JS loose equality; > >= < <= coerce BOTH sides to number and
    compare numerically — no lexicographic branch.

    No eval / no regex evaluation (GRAMMAR §10) — pure AST walk.

func NewEvaluator(formData map[string]any, currentPath []string, rowKeys []int) *Evaluator
    NewEvaluator binds form data, the current field path (the path of the field
    carrying the condition, including its own name) and the positions of the row
    keys in that path.

func (e *Evaluator) Evaluate(node Node) bool
    Evaluate evaluates a node to a boolean condition result (JS
    evaluateCondition).

func (e *Evaluator) EvaluateValue(node Node) any
    EvaluateValue evaluates a node to a value (JS evaluateExpressionValue).
    A ternary returns its chosen branch's raw value; every other node returns
    its boolean condition result (the shared fixture's value for non-ternary
    mirrors truthy).

type GroupNode struct {
	Expression Node
}
    GroupNode is a parenthesized expression.

func (n *GroupNode) ToMap() map[string]any
    ToMap serializes the Group node and its inner expression to the
    shared-fixture AST shape {type, expression}.

type InNode struct {
	Negated bool
	Value   Node
	List    []Node
}
    InNode is value in/not-in list. List holds Literal nodes.

func (n *InNode) ToMap() map[string]any
    ToMap serializes the In node and its list to the shared-fixture AST shape
    {type, negated, value, list}.

type Lexer struct {
	// Has unexported fields.
}
    Lexer turns a condition string into a []Token ending in EOF (GRAMMAR §1,
    JS Lexer parity). WHITESPACE is consumed but never emitted. The token order,
    multi-char operator precedence, multi-dot handling, string escapes,
    and number rules mirror validator-ts exactly, because the shared fixture's
    tokens array is byte-compared across 4 languages.

    Single forward scan over bytes — no regex split, no string split (GRAMMAR
    §10). Identifiers are ASCII ([A-Za-z_][A-Za-z0-9_]*), so byte scanning is
    exact.

func NewLexer(input string) *Lexer
    NewLexer builds a lexer over the given expression string.

func (l *Lexer) Tokenize() []Token
    Tokenize scans the whole input. WHITESPACE tokens are dropped; an EOF token
    is always appended last.

type LiteralNode struct {
	ValueType string
	Value     any
}
    LiteralNode is a constant. ValueType is "string"|"number"|"boolean"|"null".

func (n *LiteralNode) ToMap() map[string]any
    ToMap serializes the Literal node to the shared-fixture AST shape {type,
    valueType, value}.

type Node interface {
	// ToMap serializes the node (and its children) to the shared-fixture AST shape.
	ToMap() map[string]any
}
    Node is one AST node. ToMap renders the JS serialization shape.

func Parse(expression string) (node Node, err error)
    Parse parses an expression into its AST (cached). Returns *ParseError on
    malformed input.

type ParseError struct {
	Message string
}
    ParseError is raised by the lexer or parser on malformed input. It is
    panicked internally and recovered by Parse/Tokenize wrappers into a returned
    error.

func (e *ParseError) Error() string
    Error returns the parse failure message (implements the error interface).

type Parser struct {
	// Has unexported fields.
}
    Parser builds an AST from a token stream (GRAMMAR §3/§4, JS Parser parity).

    Recursive descent (EBNF, lowest precedence first):

        ternary    = or [ "?" ternary ":" ternary ]        (right-associative)
        or         = and { "||" and }
        and        = not { "&&" not }
        not        = "!" not | comparison
        comparison = primary [ cmp_op cmp_value | in_op value_list ]
        primary    = "(" or ")" | path | literal
        path       = [ "." | ".." ] identifier { "." (identifier | number | "*") }

    Precedence and right-associativity come from the productions, not a
    heuristic. No eval, no split (GRAMMAR §10).

func NewParser(tokens []Token) *Parser
    NewParser builds a parser over a lexer's token stream (must end in EOF).

func (p *Parser) Parse() Node
    Parse parses the full token stream into one AST root. Panics with
    *ParseError on trailing tokens or malformed input; callers use Parse() in
    expression.go to recover into an error.

type PathNode struct {
	Relative bool
	LevelsUp int
	Segments []PathSegment
}
    PathNode is a field reference. Relative true => "."/".."; LevelsUp is
    parents to ascend (".x"=0, "..x"=1). Segments are the dotted path after the
    prefix.

func (n *PathNode) ToMap() map[string]any
    ToMap serializes the Path node and its segments to the shared-fixture AST
    shape {type, relative, levelsUp, segments}.

type PathSegment struct {
	Type  string
	Value string // identifier name
	Index int    // index value
}
    PathSegment is one Path step. Type is "identifier", "wildcard", or "index".

func (s PathSegment) ToMap() map[string]any
    ToMap serializes the segment to the JS segment shape (index value as
    number).

type TernaryNode struct {
	Condition  Node
	TrueValue  Node
	FalseValue Node
}
    TernaryNode is cond ? trueValue : falseValue (right-associative).

func (n *TernaryNode) ToMap() map[string]any
    ToMap serializes the Ternary node and its children to the shared-fixture AST
    shape {type, condition, trueValue, falseValue}.

type Token struct {
	Type    TokenType
	Value   string
	Literal any
}
    Token is a single lexical token. Value is the raw source text; Literal is
    the parsed value for value tokens (string content, number int/float, bool,
    the dot count for DOT_DOT) and nil otherwise.

    No position field: the 4-language token contract excludes positions (the
    Rust AST holds none), matching the shared fixture's token shape.

func Tokenize(expression string) (tokens []Token, err error)
    Tokenize tokenizes an expression. WHITESPACE excluded; ends with EOF.
    Returns an error only if the lexer hits malformed input (e.g. unterminated
    string).

func (t Token) ToMap() map[string]any
    ToMap serializes to the shared-fixture token shape {type, value, literal}.

type TokenType string
    TokenType tags a lexical token (expressions.md §1, JS TokenType parity).

    String-valued so a fixture's token.type string ("DOT"|"IDENTIFIER"|...) maps
    directly. WHITESPACE never reaches the token slice (the lexer drops it);
    EOF is always the final token. INVALID is one unrecognized character.

    Intentionally absent (GRAMMAR §1·§10): arithmetic + - * / %, function calls,
    method calls, regex, assignment =, bitwise, root path /. Do not add them.

const (
	TokenString     TokenType = "STRING"
	TokenNumber     TokenType = "NUMBER"
	TokenBoolean    TokenType = "BOOLEAN"
	TokenNull       TokenType = "NULL"
	TokenIdentifier TokenType = "IDENTIFIER"
	TokenDot        TokenType = "DOT"
	TokenDotDot     TokenType = "DOT_DOT"
	TokenAsterisk   TokenType = "ASTERISK"
	TokenEQ         TokenType = "EQ"
	TokenNE         TokenType = "NE"
	TokenGT         TokenType = "GT"
	TokenGE         TokenType = "GE"
	TokenLT         TokenType = "LT"
	TokenLE         TokenType = "LE"
	TokenAnd        TokenType = "AND"
	TokenOr         TokenType = "OR"
	TokenNot        TokenType = "NOT"
	TokenIn         TokenType = "IN"
	TokenNotIn      TokenType = "NOT_IN"
	TokenLParen     TokenType = "LPAREN"
	TokenRParen     TokenType = "RPAREN"
	TokenLBracket   TokenType = "LBRACKET"
	TokenRBracket   TokenType = "RBRACKET"
	TokenComma      TokenType = "COMMA"
	TokenQuestion   TokenType = "QUESTION"
	TokenColon      TokenType = "COLON"
	TokenEOF        TokenType = "EOF"
	TokenWhitespace TokenType = "WHITESPACE"
	TokenInvalid    TokenType = "INVALID"
)
    The Token* constants enumerate every TokenType the lexer emits
    (expressions.md §1). Their string values are the canonical fixture token
    type names and are byte-compatible with the JS reference. TokenWhitespace
    and TokenInvalid are lexer-internal: WHITESPACE is dropped before the token
    slice and INVALID marks one unrecognized character.

type UnaryNode struct {
	Operator string
	Operand  Node
}
    UnaryNode is the logical-not operation ("!" operand).

func (n *UnaryNode) ToMap() map[string]any
    ToMap serializes the Unary node and its operand to the shared-fixture AST
    shape {type, operator, operand}.

github.com/polyspec/crudui/packages/validator/go/validator/internal/conformance

package conformance // import "github.com/polyspec/crudui/packages/validator/go/validator/internal/conformance"

Package conformance records the validation fixtures through the shared Go test
support.

FUNCTIONS

func Record(t testing.TB, feature, fixture, name string)
    Record records the fixture case result after its cleanup has completed.

github.com/polyspec/crudui/packages/validator/go/validator/text

package text // import "github.com/polyspec/crudui/packages/validator/go/validator/text"

Package text checks input text (docs/spec/input-text.md). Every string and
object member name in a specification, a composition file and caller data is
valid UTF-8, the Go form of a sequence of Unicode scalar values. Invalid text is
rejected before any other check of an operation; it is never replaced and never
passed on.

CONSTANTS

const LimitMessage = "Recursive or excessively nested value"
    LimitMessage is the message of every value beyond the limits of a value.

const Message = "Text must be Unicode scalar values"
    Message is the message of every input text failure.

const NestingLimit = 512
    NestingLimit is the number of levels of arrays and objects a value may nest,
    the value itself included.

const NodeLimit = 1000000
    NodeLimit is the number of nodes a value may hold: every array, object,
    string, number, boolean and null, the value itself included.


FUNCTIONS

func CheckInputs(inputs ...Input) string
    CheckInputs checks named caller values in order and returns the failure
    message of the first invalid text, which names the value and its path,
    or of the first value beyond its limits, which names the value; the empty
    string when every value passes.

func CheckSpecification(spec any, files any) (*compose.ComposeLoadError, string)
    CheckSpecification checks a specification and then the composition files
    an operation reads. Invalid text is the INVALID_TEXT load failure located
    at its specification path, or at the file name followed by its path in the
    file; an invalid file name is located at the empty path. A value beyond its
    limits is an input failure, whose message naming spec or files is the second
    result.

func CheckedLoader(loader compose.FileLoader) compose.FileLoader
    CheckedLoader returns a loader that checks each document it loads, located
    as a composition file.

func LoadFailure(trace []string) *compose.ComposeLoadError
    LoadFailure returns the INVALID_TEXT load failure at trace.

func Size(value any) (levels, nodes int)
    Size returns the levels of lists and objects that value nests, value itself
    the first, and the nodes it holds, counted as the value limits count them.


TYPES

type Failure struct {
	Path  []string
	Limit bool
}
    Failure is the first failure in a value: invalid text at Path, or, when
    Limit is true, a value beyond its limits.

func ValueFailure(value any) *Failure
    ValueFailure returns the first failure in value, walked as the tree it
    denotes: list items in index order; for an object, every member name first,
    then the members in code point order of their names, which is the byte
    order of valid UTF-8. Invalid text is located at the path of its string,
    or of the object whose member name is invalid. A value that nests more than
    NestingLimit levels or holds more than NodeLimit nodes, which includes every
    value that contains itself, is beyond its limits where the walk passes them.
    The result is nil when there is no failure.

type Input struct {
	// Name is the documented argument name, such as data or options.basepath.
	Name string
	// Value is the caller value.
	Value any
}
    Input is one named caller value of an operation.

github.com/polyspec/crudui/packages/validator/go/validator/validate

package validate // import "github.com/polyspec/crudui/packages/validator/go/validator/validate"

Package validate is the Go model form validator (SPEC §2 G5 → §3 → §2 G1) — the
THIRD pass of the model pipeline.

It consumes a model field model (the validate / design / behavior / options role
slots) AFTER the compose pass (validator-go/validator/compose) has expanded $ref
/ $patch into a single spec. It does NOT re-implement the expression engine —
it CALLS the existing engine (validator-go/validator/expr) for conditional rule
values. The rule semantics mirror the JS reference (validator-ts/src/rules) byte
for byte; the shared 4-language fixture tests/fixtures/validate/cases.json is
the single truth, generated from the JS engine.

Pipeline (SPEC):

    (1) compose  — ComposeProperties / ComposeSpec (compose/) runs BEFORE this
        engine (Validate in index.go). An unresolved $ref is a *ComposeLoadError
        there (never valid:true).
    (2) field traversal — recurse properties; group nesting, multiple arrays
        (items.i), object-key multiple (sorted keys, items.__uid__). Data order is
        preserved by form state and rendering; sorted order is validation errors.
    (3) validate-slot evaluation — per field, walk the validate slot in
        declaration order; for each rule evaluate its (possibly conditional) value
        to an effective param (expression / condition map); skip when false/null;
        else run the rule. type:number runs an implicit number rule first when no
        explicit number rule is present.
    (4) error collection — first error per field stops that field; collect into a
        flat []ValidationError; valid = len(errors) == 0.

Go ≥ 1.18: interface{} is spelled any. No eval.

FUNCTIONS

func CanonicalNumber(v float64) string
    CanonicalNumber writes a finite number as ECMAScript
    Number.prototype.toString does: the shortest digits that read back as the
    same double, laid out in plain notation for magnitudes from 10^-6 up to
    10^21 and in exponent notation otherwise; negative zero is "0". A nonfinite
    number, which has no canonical text, is written as ECMAScript writes it
    (NaN, Infinity) for messages and keys.

func GroupSourceFailure(field *compose.OMap, path string, root *compose.OMap) string
    GroupSourceFailure returns the message of the first rule that the group
    source of field, declared at path, breaks under the root properties, or ""
    when it breaks none. The members of items are checked in declaration order,
    then the field, from, the type of its field, label and the type of its
    field. A field whose items is not a group source has no failure.

func GroupSourceItems(items any) (*compose.OMap, bool)
    GroupSourceItems returns the items of a field as a group source, and whether
    the items are one: an object with the member from.

func IsStepMultiple(value, step float64) bool
    IsStepMultiple reports whether value is an integer multiple of a positive
    step counted from 0, decided exactly as the step rule decides it.

func NumberRange(param any) (float64, float64, bool)
    NumberRange returns the minimum and maximum of a literal range parameter:
    two finite numbers with the minimum not above the maximum.

func StepSize(param any) (float64, bool)
    StepSize returns a literal step parameter: a finite number above 0.


TYPES

type FileSet map[string]*compose.OMap
    FileSet is a virtual file set $ref resolves against ({ key: doc }).

type FormInputError struct {
	// Message names the rule and the data path.
	Message string
}
    FormInputError reports submitted form data whose shape does not match the
    specification. It is a validation failure without a validation result,
    distinct from a composition load failure.

func (e *FormInputError) Code() string
    Code returns the stable machine-readable code shared with the form runtime.

func (e *FormInputError) Error() string
    Error implements the error interface.

type GroupChoice struct {
	Value string
	Label any
}
    GroupChoice is one choice of a group source: the canonical text of its
    value and its label. A literal choice keeps its declared label (CUSC-089);
    a row's label is the text of its label field, or the value when the source
    declares no label or the row gives none.

func GroupChoices(source *compose.OMap, data any) ([]GroupChoice, error)
    GroupChoices returns the choices of a checked group source for the root
    data: its literal choices in written order (CUSC-089), then one per row of
    the group in data order whose value is a string or a finite number with a
    non-empty canonical text, each value once. Data is an ordered object or a
    plain map; a plain map has no member order, so its rows are read in sorted
    key order. A row whose value is a literal choice returns a FormInputError
    with its path and value.

type ListValidationResult struct {
	// Valid reports whether Errors is empty.
	Valid bool `json:"valid"`
	// Errors is the flat list of failures.
	Errors []ValidationError `json:"errors"`
}
    ListValidationResult is the outcome of checking a list or detail
    declaration: valid plus the collected errors.

func ValidateDetail(spec *compose.OMap, opts Options) (ListValidationResult, error)
    ValidateDetail composes and forbidden-scans a detail-spec. It returns a
    clean ListValidationResult (valid:true, no errors) when the detail composes
    and scans clean. An unresolved composition or a forbidden meta key returns a
    *compose.ComposeLoadError.

func ValidateDetailJSON(specJSON []byte, filesJSON map[string][]byte, basepath string) (ListValidationResult, error)
    ValidateDetailJSON decodes a raw JSON detail-spec and the optional { key:
    rawJSON } file set, then runs ValidateDetail. A detail carries no data.

func ValidateList(spec *compose.OMap, opts Options) (ListValidationResult, error)
    ValidateList runs composition and forbidden-key scanning over a list-spec
    given as the engine value model (*compose.OMap). It returns a clean
    ListValidationResult (valid:true, no errors) when the list composes and
    scans clean — a list carries no rows, so there is no DATA pass to produce
    field errors. An unresolved composition or a forbidden meta key at any depth
    returns a *compose.ComposeLoadError (the caller distinguishes a LOAD failure
    from a validation result), identical to the form path.

func ValidateListJSON(specJSON []byte, filesJSON map[string][]byte, basepath string) (ListValidationResult, error)
    ValidateListJSON decodes a raw JSON list-spec (compose.DecodeOrdered so
    declaration order survives) and the optional { key: rawJSON } file set,
    then runs ValidateList. Mirrors ValidateJSON (the form entry) for the CLI
    / gateway subprocess wire. A list carries no data, so there is no data
    argument.

type Options struct {
	// Files is the virtual file set for $ref resolution (default: empty).
	Files FileSet
	// Basepath is the basepath for relative $ref resolution.
	Basepath string
}
    Options configures a model validation run.

type ValidationError struct {
	// Path is the dot-joined full path to the field (items.0.name, rows.__uid__.v).
	Path string `json:"path"`
	// Field is the last path segment (the field name).
	Field string `json:"field"`
	// Rule is the failed rule name, the invoked key verbatim (pattern vs match).
	// An implicit type:number failure reports "number".
	Rule string `json:"rule"`
	// Message is the display message: field messages[rule] override → rule default
	// (with {0}/{1} substituted) → "Validation failed." fallback.
	Message string `json:"message"`
	// Value is the failed value (the whole array for array-level rules).
	Value any `json:"value"`
}
    ValidationError is one validation failure. Field shape (path / field /
    rule / message / value) is identical in every runtime so the 4-language
    idempotence comparison (SPEC G-B) holds with the same (path, rule, message).

type ValidationResult struct {
	// Valid reports whether Errors is empty.
	Valid bool `json:"valid"`
	// Errors is the flat list of failures in traversal order.
	Errors []ValidationError `json:"errors"`
	// Hidden lists the data paths of the fields whose design.show resolves to
	// false, in declaration order (validation-rules.md, "Evaluation").
	Hidden []string `json:"hidden"`
}
    ValidationResult is the outcome of validating data: valid, the collected
    errors and the hidden paths.

func Validate(spec *compose.OMap, data any, opts Options) (ValidationResult, error)
    Validate validates data against a model spec given as the engine value
    model (*compose.OMap, decoded with compose.DecodeOrdered). The spec may
    carry $ref / $patch; they are expanded first. An unresolved composition
    returns a *compose.ComposeLoadError, and data with the wrong shape returns a
    *FormInputError; neither produces a validation result. G5 → §3 → §2 G1.

func ValidateJSON(specJSON []byte, dataJSON []byte, filesJSON map[string][]byte, basepath string) (ValidationResult, error)
    ValidateJSON is a convenience wrapper that decodes a raw JSON spec
    and raw JSON data, then runs Validate. The spec is decoded with
    compose.DecodeOrdered so declaration order survives; data is decoded with
    compose.DecodeOrdered into plain values (objects → map[string]any, arrays
    → []any, numbers → float64). Both keep text as written, so invalid text
    reaches the input text check. Empty data bytes validate an empty object;
    any decoded data value is passed to Validate unchanged. Files is a { key:
    rawJSON } map.

type Validator struct {
	// Has unexported fields.
}
    Validator validates data against a composed model spec. Construct via
    NewValidator from a composed properties OMap.

func NewValidator(properties *compose.OMap) (*Validator, error)
    NewValidator builds a validator from a COMPOSED properties OMap (composition
    keys already eliminated by the compose pass) and checks its declared rule
    names and parameters. A rule name that is not registered, or a parameter
    outside its rule's definition, returns a *compose.ComposeLoadError
    (UNKNOWN_RULE, INVALID_RULE_PARAMETER or INVALID_RULE_PATTERN) at the
    field's declaration path.

func (v *Validator) Validate(data any) (ValidationResult, error)
    Validate validates data against the composed model spec. Root, group and
    repeated data with the wrong shape return a *FormInputError and no result.
    A rule parameter a condition selects outside its definition returns a
    *compose.ComposeLoadError and no result.

github.com/polyspec/crudui/packages/generator/go

package generator // import "github.com/polyspec/crudui/packages/generator/go"

Package generator compiles, binds and renders CRUDUI forms and lists in Go.

CONSTANTS

const HistoryLimit = 100
    HistoryLimit is the number of records an undo history keeps in each
    direction (CURT-062).


FUNCTIONS

func CanRedo[T any](history History[T]) bool
    CanRedo reports whether the history has a record to restore in the forward
    direction (CURT-078).

func CanUndo[T any](history History[T]) bool
    CanUndo reports whether the history has a record to restore (CURT-078).

func CheckBindText(template any, data *Object, options BindOptions) error
    CheckBindText runs the input text checks BindForm, BindButtons and NewForm
    run first: the template, the data, then the options in code point order
    of their names. template is a *FormTemplate or its decoded JSON object;
    a caller that decodes a template from JSON text checks the decoded object
    before decoding it into a FormTemplate, whose JSON encoding replaces invalid
    text.

func CollapsibleRows(nodes []*Object) []string
    CollapsibleRows returns the row paths {collection}.{key} of every
    collapsible row of the evaluated nodes, depth first in node order.

func CreateRowKey() (string, error)
    CreateRowKey creates a row key containing 13 random hexadecimal characters.

func DecodeJSON(data []byte) (any, error)
    DecodeJSON preserves object order and rejects malformed or multiple
    documents.

func FormButtonsHTML(buttons any) (string, error)
    FormButtonsHTML renders evaluated buttons as the markup every renderer
    inserts into the form footer. The buttons are a list: a Go slice of button
    objects, or the list of a decoded JSON value, and nil is the empty list.
    Each button must be an object whose tag is a or button, whose text is a
    string and whose attrs object has only the named button attributes with
    string values; other members are ignored.

func RenderDetail(spec *Object, record any, options DetailOptions) (string, error)
    RenderDetail renders one read-only detail: the image preload links,
    the actions when the detail has any, and the definition list.

func RenderForm(form *Form, options any) (string, error)
    RenderForm renders one instance as the complete form without changing its
    template or data (form-runtime.md, "Complete form"). options is nil or an
    object with the optional members action, hidden, formErrors and errors;
    nil writes the crudui-form block alone.

func RenderList(spec *Object, rows any, options ListOptions) (string, error)
    RenderList renders a table or card list with image resource hints in
    first-use order. rows is a []*Object or a decoded JSON array of objects.

func SequenceRowKey(sequence any) (string, error)
    SequenceRowKey converts a nonnegative sequence of at most 13 digits to a
    scoped row key.


TYPES

type ActionInput struct {
	Action string
	Path   string
	Key    string
}
    ActionInput is the values a button declares: its action, collection path and
    row key (CURT-081). An empty member is absent.

type ActionResult struct {
	Focus *FocusTarget
}
    ActionResult is the outcome of an action that ran; Focus is nil when focus
    stays on the current control.

func RunAction(form *Form, target ActionTarget) (*ActionResult, error)
    RunAction runs a resolved action on the form. A nil result means the action
    had no result: its target lacks the path, or the key that the action needs,
    and nothing changes. A failing operation returns its error and leaves the
    form unchanged (CURT-090).

type ActionTarget struct {
	Name string
	Path string
	Key  string
}
    ActionTarget is the operation, collection path and row key that an action
    button targets; Path and Key are empty when the button declares none.

func ResolveAction(input ActionInput) (ActionTarget, bool)
    ResolveAction resolves the target of the values a button declares; ok is
    false when the action is none of the eleven form actions.

type AddRowOptions struct {
	Key           string
	AfterKey      string
	Value         any
	ValueProvided bool
}
    AddRowOptions preserves explicit null through ValueProvided.

type BindOptions struct {
	// IDPrefix defaults to crudui.
	IDPrefix any
	// Language defaults to ko and must be ko, en, ja or zh, so an empty string is rejected.
	Language any
	// KeyPrefix defaults to the template prefix; an empty string removes it.
	KeyPrefix any
	// Unsupported is throw (default) or marker, which renders unsupported fields as markers.
	Unsupported any
	// Collapsed lists the row paths {collection}.{key} rendered collapsed; the other rows are expanded.
	Collapsed []string
}
    BindOptions selects control IDs, content and interface language, form-name
    prefix and unsupported-field handling. Each option is nil for its default;
    decoded JSON of another type or value is rejected.

type CompileOptions struct {
	Files             map[string]*Object
	Loader            compose.FileLoader
	Basepath          string
	KeyPrefix         string
	KeyPrefixProvided bool
}
    CompileOptions supplies composition files and the optional form-name prefix.

type DetailOptions = ListOptions
    DetailOptions supplies composition, display language and caller-owned data
    options. The list-only Layout, Page and Total are ignored; detail output is
    always a read-only definition list.

type FieldTemplate struct {
	Name     string          `json:"name"`
	Spec     *Object         `json:"spec"`
	Children []FieldTemplate `json:"children"`
}
    FieldTemplate stores one resolved field specification and its ordered
    children.

type FocusTarget struct {
	Path string
	Key  string
}
    FocusTarget is the row that receives focus after an action; Key is empty
    when the collection has no rows left.

type Form struct {
	// Has unexported fields.
}
    Form owns an independent record, compiled template and current field models.

func NewForm(template *FormTemplate, data *Object, options BindOptions) (*Form, error)
    NewForm copies the template and record, applies missing defaults and binds
    fields.

func (f *Form) AddRow(path string, options AddRowOptions) (string, error)
    AddRow inserts a default or supplied row in the selected repeated
    collection.

func (f *Form) CanRedo() bool
    CanRedo reports whether Redo reapplies an undone record (CURT-061).

func (f *Form) CanUndo() bool
    CanUndo reports whether Undo restores an earlier record (CURT-061).

func (f *Form) CopyRow(path, key string, options AddRowOptions) (string, error)
    CopyRow copies a row and generates fresh keys for its repeated descendants.

func (f *Form) Fields() []*Object
    Fields returns deep copies of the current field models.

func (f *Form) GetData() *Object
    GetData returns a deep copy of the current ordered record.

func (f *Form) GetValue(path string) (any, error)
    GetValue returns a detached value at a record path; absent values return
    nil.

func (f *Form) MoveRow(path, key string, index int) error
    MoveRow moves an existing row to a zero-based collection position.

func (f *Form) Redo() error
    Redo reapplies the most recently undone data change (CURT-012); it fails
    when nothing can be redone.

func (f *Form) RekeyRow(path, oldKey, newKey string) error
    RekeyRow replaces a row key without changing its value or collection
    position.

func (f *Form) RemoveRow(path, key string) error
    RemoveRow removes a row when the collection minimum permits removal.

func (f *Form) Revision() uint64
    Revision returns the number of successful instance mutations.

func (f *Form) SetAllExpanded(expanded bool) error
    SetAllExpanded expands every row, or collapses every collapsible row;
    the record and the revision do not change.

func (f *Form) SetData(data *Object) error
    SetData replaces the complete record and rebinds fields atomically.

func (f *Form) SetValue(path string, value any) error
    SetValue replaces one path value and rebinds fields atomically.

func (f *Form) Template() (*FormTemplate, error)
    Template returns a deep copy of the reusable form structure.

func (f *Form) ToggleRow(path, key string) error
    ToggleRow expands a collapsed row or collapses an expanded one; the record
    and the revision do not change.

func (f *Form) Undo() error
    Undo restores the record before the last data change and makes the reverted
    record redoable (CURT-011); it fails when nothing can be undone.

type FormTemplate struct {
	Kind      string          `json:"kind"`
	KeyPrefix string          `json:"keyPrefix,omitempty"`
	Fields    []FieldTemplate `json:"fields"`
	// Buttons lists the form buttons in declaration order; one submit button when the spec declares none.
	Buttons []*Object `json:"buttons"`
	// Action is the submission target declared by the spec, kept unchanged in the template.
	Action *Object `json:"action,omitempty"`
	// Description is the root description content as the spec declares it, translated when the
	// form is rendered. It is a template member when the root declares description.
	Description any `json:"description,omitempty"`

	// Has unexported fields.
}
    FormTemplate stores reusable structure without record data.

func CompileForm(spec *Object, options CompileOptions) (*FormTemplate, error)
    CompileForm resolves field composition without binding record data.

func (t FormTemplate) MarshalJSON() ([]byte, error)
    MarshalJSON retains specification order, an explicitly empty form-name
    prefix and a declared null description.

func (t *FormTemplate) UnmarshalJSON(data []byte) error
    UnmarshalJSON reads the template in specification member order. A value that
    is not exactly the compiled shape is rejected: the template kind, a field
    list, a button object list, an optional string keyPrefix, an optional object
    action, an optional description of any value and no other member.

type History[T any] struct {
	// Entries are the earlier records, at most HistoryLimit.
	Entries []T
	// RedoEntries are the records that redo restores, at most HistoryLimit.
	RedoEntries []T
	// LastPath is the path of the last change when it was a value edit, and empty otherwise.
	LastPath string
}
    History is the undo history of a form: the records before earlier changes
    and the records after undone changes, oldest first. The functions below take
    and return it by value and never change their arguments.

func EmptyHistory[T any]() History[T]
    EmptyHistory returns the history with no record (CURT-076).

func RecordChange[T any](history History[T], previous T, path string) History[T]
    RecordChange appends the record before a change to the entries unless path
    equals the path of the last value edit, keeps the last HistoryLimit entries,
    clears the redo entries and sets the path of the history to path (CURT-077,
    CURT-063).

type HistoryResult[T any] struct {
	History History[T]
	Value   T
}
    HistoryResult is the history after an undo or a redo and the record that it
    restored.

func RedoChange[T any](history History[T], current T) (HistoryResult[T], error)
    RedoChange returns the history with current appended to the entries and
    without its last redo entry, and that redo entry as the record; it fails
    when the history has no redo entry (CURT-080).

func UndoChange[T any](history History[T], current T) (HistoryResult[T], error)
    UndoChange returns the history without its last entry, with current appended
    to the redo entries, and the last entry as the record; it fails when the
    history has no entry (CURT-079).

type ListOptions struct {
	Language string
	// Data is nil or an *Object; any other value fails with "List context must be an object".
	Data any
	// Page is nil (none) or a number holding an integer from 1 to 9007199254740991;
	// any other value fails with "List page must be a positive integer".
	Page any
	// Total is nil (none) or a number holding an integer from 0 to 9007199254740991;
	// any other value fails with "List total must be a nonnegative integer".
	Total    any
	Files    map[string]*Object
	Loader   compose.FileLoader
	Basepath string
	// Layout is nil (table) or the string "table" or "card"; any other value fails.
	Layout any
}
    ListOptions supplies composition, display language, layout and caller-owned
    pagination data. Data, Page, Total and Layout take decoded values unchanged
    so their type is checked like every runtime.

type Object = compose.OMap
    Object preserves JSON member order. Arrays use []any and null uses nil.

func BindButtons(template *FormTemplate, data *Object, options BindOptions) ([]*Object, error)
    BindButtons evaluates the template's buttons for a record in declaration
    order. Each button is an object with type, tag, text and attrs; attrs keeps
    the output order type, class, style, name, value, href, onclick. Only the
    Language option applies.

func BindForm(template *FormTemplate, data *Object, options BindOptions) ([]*Object, error)
    BindForm creates the node models of the form grammar without changing the
    template or record.

func BuildDetail(spec *Object, record any, options DetailOptions) (*Object, error)
    BuildDetail creates a read-only detail model from one supplied record.
    Field composition, design evaluation and cell formatting are shared with
    BuildList. record is an *Object or a decoded JSON value; any other value is
    no object (CUDF-033).

func BuildList(spec *Object, rows any, options ListOptions) (*Object, error)
    BuildList creates a complete list model from the specification and ordered
    records. rows is a []*Object or a decoded JSON array of objects.

func BuildOutline(nodes []*Object) []*Object
    BuildOutline builds the structure map: the form's rows, nested as in the
    form. Collections, counts and empty collections are not rows.

func NewObject(pairs ...any) *Object
    NewObject creates an ordered object from alternating string keys and values.

type UnsupportedFieldTypeError struct {
	Type string
	Path string
}
    UnsupportedFieldTypeError identifies a field type that has no supported
    widget.

func (e *UnsupportedFieldTypeError) Code() string
    Code returns the shared unsupported-field error code.

func (e *UnsupportedFieldTypeError) Error() string
    Error returns the unsupported type and its structural field path.

type ViewState struct {
	// Collapsed lists the collapsed row paths.
	Collapsed []string
}
    ViewState is the view state of a form kept outside the record data:
    the row paths {collection}.{key} rendered collapsed, in the order the rows
    were collapsed, without duplicates. It is never submitted with the data.

func InitialView() ViewState
    InitialView returns the view with no collapsed row. Replacing the record
    returns to it.

func RekeyRowView(view ViewState, oldPath, newPath string) ViewState
    RekeyRowView moves the collapsed state of a rekeyed row and of the rows
    inside it to the new row path.

func RemoveRowView(view ViewState, rowPath string) ViewState
    RemoveRowView drops the collapsed state of a removed row and of the rows
    inside it.

func SetAllExpandedView(nodes []*Object, expanded bool) ViewState
    SetAllExpandedView returns the view with no collapsed row when expanded is
    true, and otherwise the view that collapses every collapsible row of the
    evaluated nodes.

func ToggleRowView(view ViewState, rowPath string) ViewState
    ToggleRowView expands a collapsed row or collapses an expanded one,
    appending a collapsed row last.