Documentation
¶
Overview ¶
Package mappingplanview is an interactive ImZero2 widget for authoring a leeway mappingplan spec and live-previewing the code it compiles to.
What it is ¶
A dockable playground. The editor pane (left) edits a Model — the entity kind, plain columns, lw:-tagged value/const fields with their membership, section, sub-column, channel and flags, and dynamic-membership tuple rows (ADR-0103: a slice-of-struct field whose elements each emit one attribute of ONE section, carrying its own verbatim membership — authored as an `@membership` element plus one value element per sub-column). The output panes (right, one dock tab each) show what the resulting plan compiles to: the schema-agnostic Go codec (marshallgen), the parsed Plan IR (JSON), and the dql SQL read-back artefacts (presence / projection / validator, bound to a seeded schema; a tuple plan explains instead that membership-addressed read-back does not apply to per-row memberships). The whole plan is re-validated through mappingplan.PlanBuilder on every edit; a status line reports the plan-level verdict plus a per-field roll-up.
Per-field validity ¶
Every field card carries its own validity state machine (FieldState) shown as a tethered inspector chip (built on fsmview): a colour-coded badge (empty / incomplete / valid / rejected / conflict / blocked) you click to open a floating window with the state graph, the transition history (each move tagged with the reason it fired), and the rejection text. The widget decides Empty / Incomplete from the row alone; Valid / Rejected / Conflicting / Blocked come from the host's sequential build report (BuildResult) — PlanBuilder is fail-fast and stateful, so the first bad field is Rejected / Conflicting and every later field is Blocked. Rejected (the field's own shape / tag) is told apart from Conflicting (a clash with another field) by the rejection message ([classifyConflict]).
Why a Model instead of a *mappingplan.Plan ¶
A mappingplan.Plan has no constructor other than PlanBuilder and no setters: it is the validated *output* of AddField / AddUnderscoreField / Finish, built from a DTO's Go type + lw: tags (via marshallgen's go/ast front-end, or marshallreflect). It also has no serialised form. So the widget's editable state is a Model whose rows mirror the PlanBuilder *input* sequence: each edit re-runs the builder, and the genuine front-end validator is the validation feedback — no rules are reimplemented here.
The rebuild itself is injected by the host through Input.Recompute (see the mappingplanview demo), so this package depends only on the lightweight mappingplan data model, not on the marshallgen / dql back-ends.
Editing is exploratory (no write-back) ¶
The Model is authored in-widget and never written back to Go source. A Plan built by reflection mirrors a compiled type whose tags are immutable at runtime, so round-tripping edits to source is out of scope; the value here is seeing what a given lw: tagging validates to and compiles to.
Still deferred ¶
- Carrier channels (mixed* / *parametrized) — require a paired carrier sibling field the editor does not model yet; the channel picker offers the four Cut-1 channels only.
- Per-field Conflicting attribution for cross-field failures that surface only at Finish (channel mixing, carrier pairing): the builder error names the field in structured data the rendered message drops, so those stay plan-level in the global verdict rather than colouring one card.
(The SQL read-back preview and the syntax-highlighted codeview panes, once listed here as v2 work, have since shipped: the output panes are highlighted codeview jobs rebuilt per recompute, and the SQL artefacts come from dql.Generate against a seeded schema. See ADR-0066.)
Index ¶
Constants ¶
This section is empty.
Variables ¶
var PackageProps = packageprops.Props{ WASMWASI: packageprops.WASMBlocked, WASMJS: packageprops.WASMBlocked, WASMFreestanding: packageprops.WASMBlocked, }
PackageProps records this package's curated properties (ADR-0080). Seeded by `boxer code analysis golang wasmsurvey props generate`; curate by hand. The same group's `props verify` reconciles it.
Functions ¶
func Render ¶
func Render(in Input)
Render draws the whole widget as a single dock area: the editor pane on the left and one generated-output pane (Go / SQL / JSON) per Output on the right. The split is the initial preset — the user can drag panes around and the layout persists (egui_dock). Call once per frame.
Types ¶
type BuildResult ¶
type BuildResult struct {
// FirstFailIdx is the index into Model.Fields of the first field AddField
// rejected, or -1 when every AddField succeeded. PlanBuilder is fail-fast
// and stateful: fields before it were accepted (→ Valid), the field at it is
// Rejected / Conflicting, and fields after it were never reached (→ Blocked).
FirstFailIdx int
// FirstFailErr is the AddField error at FirstFailIdx (nil when FirstFailIdx
// is -1). Its message classifies Rejected vs Conflicting and seeds the chip
// reason + inspector History.
FirstFailErr error
// FinishErr is set when every AddField passed but Finish failed — a
// plan-level cross-field rejection that does not pin to a single field, so
// it is surfaced in the global verdict rather than per-field. nil otherwise.
FinishErr error
}
BuildResult is the host's report of one sequential PlanBuilder pass over the model's fields — the input each per-field machine derives its plan-standing from. The host owns the build (and the marshallgen / dql back-ends); the widget only turns this report into states, so no validation rules are reimplemented here.
type FieldRow ¶
type FieldRow struct {
GoField string // DTO field name (ignored for IsConst rows)
IsOption bool // option.Option[T] — presence, orthogonal to the value type
Membership string // lw: first segment; "" ⇒ plain column
Section string // lw: second segment (plain column name when Membership == "")
Column string // lw: sub-column suffix after ':' (e.g. beginIncl / endExcl)
Channel mappingplan.MembershipChannel // one of the four Cut-1 channels in v1
Unit bool // ,unit
IsConst bool // declared on a `_` field as ,const=<value>
ConstValue string // the constant value
// IsTuple marks a dynamic-membership tuple row (ADR-0103): a
// slice-of-struct DTO field mapping N attributes into ONE section, each
// element carrying its own membership. The row's GoField / Section stay
// meaningful (the outer field + its section); Membership is unused (the
// membership is per-element data); TupleStructType names the element
// struct; TupleElems are the element struct's fields. One tuple row is
// one PlanBuilder call (AddTupleSliceField), so the per-field FSM chip
// carries the whole tuple's verdict.
IsTuple bool
TupleStructType string
TupleElems []*TupleElemRow
// contains filtered or unexported fields
}
FieldRow is one editable row of a Model. Its fields mirror the inputs mappingplan.PlanBuilder.AddField / AddUnderscoreField take, so the host's Recompute can turn a row straight into a builder call:
- Membership == "" → a plain column (Section names id/ts/naturalKey/expiresAt).
- IsConst → a `_`-field constant (AddUnderscoreField with ,const=).
- otherwise → an lw:-tagged value field (AddField).
func (*FieldRow) LWTag ¶
LWTag assembles the lw: struct-tag *value* this row represents — the string PlanBuilder parses via SplitLW. Plain columns produce ",<col>"; const rows produce "<memb>,<sec>,const=<value>"; value fields produce "<memb>,<sec>[:<col>][,unit][,<channel>]". The default channel (LowCardRef) contributes no flag, matching mappingplan.MembershipChannel.String. A tuple row's outer tag is the bare section name (SplitTupleOuterLW).
func (*FieldRow) SetGoType ¶
SetGoType seeds the row's value type from a Go source-type spelling — a convenience for examples and the default new-row type that mirror a Go DTO; the editor itself authors the canonical directly. An unmapped spelling leaves the type empty (the editor then shows it invalid).
func (*FieldRow) Shape ¶
func (r *FieldRow) Shape() goplan.FieldShape
Shape returns the FieldShape this row describes, ready to hand to PlanBuilder.AddField. The value type is authored canonically (typeModel). When the type is not currently usable — the formula bar does not parse (canonicaltypeedit.Model.BarError), or the parsed type fails IsValid — Shape yields a nil Canonical so AddField rejects the field and the sequential build halts here: the field then reads incomplete and every field after it blocked, rather than the build silently proceeding on the last type that happened to parse. Carrier types are not modelled in v1, so CarrierType stays "".
func (*FieldRow) TupleElemSpecs ¶ added in v0.0.12
TupleElemSpecs converts the row's elements into the goplan.TupleElem list AddTupleSliceField takes, in declaration order.
type FieldState ¶
type FieldState uint8
FieldState is one field's validity standing — the state space of its per-field fsmview.Machine. Empty / Incomplete are decided by the widget from the row alone; Valid / Rejected / Conflicting / Blocked come from the host's sequential build report (BuildResult). See [deriveState].
const ( // StateEmpty is the zero value so a fresh row's machine starts here. StateEmpty FieldState = iota // nothing authored yet StateIncomplete // partially authored, not buildable on its own StateValid // accepted by PlanBuilder in the full sequence StateRejected // rejected for this field's own shape / tag StateConflicting // rejected for clashing with another field StateBlocked // an earlier field stopped the build before this one )
type Input ¶
type Input struct {
// Ids is the widget ID stack. Render opens its own IdScope via
// Ids.PrepareStr(ScopeKey) and derives a stable per-row scope from each
// row's uid.
Ids *c.WidgetIdStack
// ScopeKey scopes every id Render emits. Pass a stable short string per
// call site (e.g. "mpv").
ScopeKey string
// Model is the editable state, mutated in place by the editor controls.
Model *Model
// Recompute rebuilds the output panes from the Model. The host supplies it
// (it owns the mappingplan / marshallgen / dql wiring); Render calls it at
// most once per frame, only when the Model is dirty, reporting back through
// Model.SetOutputs / Model.SetInvalid. The host must also call it once at
// init so the dock's initial split has the output tab ids to place.
Recompute func(*Model)
// FillHost tells Render its host already gives it a bounded height, so it
// must fill that rect rather than floor to dockMinHeight. The floor is a
// scroll-host device (see dockMinHeight): the gallery is a vertically-
// unbounded ScrollArea, so without a floor the dock collapses. Inside a
// host that is ALREADY bounded and often shorter than the floor — a
// dock-tab leaf — forcing the floor overflows the leaf and the nested dock
// paints across the neighbouring panes (worse once a pane scrolls). Bounded
// hosts set this true; the gallery leaves it false. See schemaview.Input,
// which carries the same field, and the imzero2 SKILL "Gallery Scroll-Host
// Layout" section for both sides of the pattern.
FillHost bool
}
Input is the per-frame render state for the mappingplan playground.
type Model ¶
type Model struct {
Kind string // entity kind from the `_` field's kind: tag
PackageName string // DTO package (header cosmetics in the preview)
KindType string // DTO struct type name
Fields []*FieldRow
// panes are the generated output artifacts shown as dock tabs, set by the
// host's Recompute via SetOutputs; ErrText/Valid carry the verdict.
ErrText string // PlanBuilder / emit error when !Valid
Valid bool
// contains filtered or unexported fields
}
Model is the editable state of the playground: the plan identity, the ordered field rows, and the most recent preview the host computed.
func NewModel ¶
NewModel returns an empty Model marked dirty so the first frame computes a preview. Seed it with AddRow.
func (*Model) AddElem ¶ added in v0.0.12
func (m *Model) AddElem(r *FieldRow) *TupleElemRow
AddElem appends a fresh element to a tuple row (with a stable uid from the model's shared counter) and returns it for the caller to populate. Marks the model dirty. The element defaults to a value field; flip IsMembership (and pick a verbatim Channel) for the membership element.
func (*Model) AddRow ¶
AddRow appends a fresh row with a stable uid and returns it for the caller to populate. Marks the model dirty.
func (*Model) SetBuildResult ¶
func (m *Model) SetBuildResult(r BuildResult)
SetBuildResult records the host's build report and refreshes every field's derived validity state + reason. Called by the host's Recompute alongside SetOutputs / SetInvalid; the per-frame render then mirrors each row's machine to its refreshed state.
func (*Model) SetInvalid ¶
SetInvalid records a failed recompute: the error text, no source, invalid verdict. Called by the host's Recompute.
func (*Model) SetOutputs ¶
SetOutputs records a successful recompute: the generated output panes and a valid verdict. The highlighted codeview job per pane is built here — the recompute is dirty-gated, so once per edit, not per frame, and c.CodeView splices each job's bytes into the frame (no retained-element accumulation). Called by the host's Recompute.
func (*Model) SetQueryable ¶
SetQueryable records whether the host's read-back (the dql SQL artefacts in the demo) generated for the current plan — it refines a valid plan into PlanQueryable (true) vs PlanSchemaMismatch (false). The host's Recompute calls it on the success path; SetInvalid resets it. A host with no read-back stage simply never sets it, so its valid plans read SchemaMismatch.
type Output ¶
type Output struct {
TabID uint64
Title string
Lang OutputLang
Source string
}
Output is one generated artifact the host hands the widget to show as a dock tab. TabID must be stable across frames — it keys the persistent dock layout. Adding a new output format (e.g. the dql SQL artefacts) is just another Output; the widget code is format-agnostic.
type OutputLang ¶
type OutputLang uint8
OutputLang selects the codeview syntax highlighter for an output pane.
const ( LangGo OutputLang = iota LangSQL LangJSON )
type PlanState ¶
type PlanState uint8
PlanState is the whole plan's standing in the compile pipeline — the state space of the plan-level fsmview.Machine shown beside the verdict. It tracks how far the plan gets through build → marshal → query:
- Empty: no fields.
- Incomplete: a field isn't ready (Empty/Incomplete), so the plan can't build.
- Invalid: the build failed — a field is rejected/conflicting, or Finish (cross-field) / emit failed; there is no usable Plan.
- SchemaMismatch: a valid Plan that emits a Go codec + Plan IR, but the SQL read-back does not generate against the bound schema (Plan ⊄ schema, ADR-0066) — built, but not queryable.
- Queryable: the whole pipeline succeeds — builds, emits, AND the read-back generates.
The intermediate build/emit micro-stages are not separate states: recompute runs the pipeline synchronously, so the plan settles directly into one of these terminal conditions each edit. SchemaMismatch vs Queryable needs a host signal (the host owns the read-back); see Model.SetQueryable.
type TupleElemRow ¶ added in v0.0.12
type TupleElemRow struct {
GoField string
IsMembership bool
Column string // value fields: sub-column ("" targets "value")
// Channel is the membership element's wire channel. ADR-0103 mandates an
// explicit verbatim channel (`,verbatim` / `,highCardVerbatim`) — ref and
// carrier channels cannot carry a per-element membership — so the picker
// offers exactly the verbatim pair.
Channel mappingplan.MembershipChannel
// MembBytes picks the membership field's Go type: string (false) or
// []byte (true) — the two shapes AddTupleSliceField accepts.
MembBytes bool
// contains filtered or unexported fields
}
TupleElemRow is one field of a tuple row's element struct — either THE `@membership` field (IsMembership) carrying each attribute's membership value, or a value field mapping one sub-column of the tuple's section. Mirrors goplan.TupleElem the way FieldRow mirrors AddField inputs, so the host's Recompute hands the row straight to AddTupleSliceField.
func (*TupleElemRow) ElemLWTag ¶ added in v0.0.12
func (e *TupleElemRow) ElemLWTag(section string) string
ElemLWTag assembles the element's lw: tag for AddTupleSliceField (SplitTupleElemLW grammar): `@membership,<channel flag>` for the membership element, `<section>[:<column>]` for a value element. section is the tuple row's section (value-element tags repeat it, ADR-0103 D1).
func (*TupleElemRow) SetCanonical ¶ added in v0.0.12
func (e *TupleElemRow) SetCanonical(ct string)
SetCanonical seeds a value element's canonical directly (e.g. "u32h" for a container sub-column) — hosts seeding example tuples need it because a container type has no scalar Go spelling for SetGoType.
func (*TupleElemRow) SetGoType ¶ added in v0.0.12
func (e *TupleElemRow) SetGoType(goType string)
SetGoType seeds a value element's canonical from a Go source-type spelling — the same convenience FieldRow.SetGoType provides.
func (*TupleElemRow) Shape ¶ added in v0.0.12
func (e *TupleElemRow) Shape() goplan.FieldShape
Shape returns the element's goplan.FieldShape: the authored canonical for a value element (nil when the type editor does not parse, so the builder rejects and the sequential build halts here — same contract as FieldRow.Shape), or the membership field's string / []byte scalar.