Skip to content

ui: investigate value widgets backed by per-Element state #225

Description

@linkdata

Question

The proposed one-state-slot-per-Element design lets ui.Template keep its mutable ownership generation off the jaws.UI value. Investigate whether the same definition/state split can safely make more standard lib/ui widgets value-typed, so containers can reuse rebuilt equal widgets without sharing state between their Elements.

This revisits #160, which found that sharing an input widget also shared Input.tag and Input.Last and could leave peer controls stale. PR #167 resolved that by documenting one live Element per stateful widget. Per-Element state may allow the stronger behavior safely instead of relying only on that restriction.

Important design distinction

Do not assume getters/setters should simply move into the Element slot.

  • The slot should primarily hold mutable Element-local state: input tag/Last, container reconciliation tag/mu/contents, and ownership generations.
  • The widget value still needs a definition and a reuse identity before its Element and slot exist. A getter, setter, or container normally participates in that identity. Moving it only into state can make distinct widgets compare equal and be conflated by ContainerHelper.
  • If an interface-valued getter/setter/container remains in a value widget, its dynamic value must be comparable and equal to itself. The current pointer widgets accept implementations that are not. Preserve a pointer escape hatch or design an additive explicit value form rather than silently narrowing accepted inputs.
  • Storing a non-comparable definition in the slot is viable only if the UI value has a separate comparable identity and a reused Element can receive the current definition. That is a broader keyed-identity/refresh design, not something the slot provides by itself.
  • Application state remains in the synchronized getter/setter. The Element slot must not become a duplicate source of domain truth.
  • Each converted family would claim the Element's sole state slot. Composition/delegation must follow the one-claimer contract and reject contention.

Candidate inventory

Strongest candidate: typed inputs

Text, Password, Textarea, Checkbox, Radio, Number, Range, and Date retain Element-local state in Input.tag and Input.Last. Investigate moving those fields into an inputState while retaining the bind.Setter[T] as the widget definition.

Account for:

  • independent last-value snapshots and dirty tags when one supported value backs multiple Elements;
  • the existing string/bool/float/date deduplication, parse-error, non-finite-value, and dirtying behavior;
  • update-only use through RequestWriter.Register;
  • Input.Last being exported, so removing or changing it is an API compatibility decision;
  • synchronization equivalent to the current atomic.Value behavior.

Larger candidate: ContainerHelper widgets

Container, Tbody, and Select retain ContainerHelper.tag, mu, and contents. Investigate a per-Element containerState that continues to implement recursive ownership cleanup.

Account for render rollback, append failure cleanup, duplicate matching/order, deleted-child recreation, nested ownership, and independent reconciliation pools when one value backs multiple Elements. Select must also preserve option-before-value ordering, input dirtying, and its supported pointer-form reassignment of ContainerHelper.Container after construction.

ContainerHelper is also an exported embedding helper. Moving its own state must not silently grant every external embedding widget multi-Element support; each concrete type still needs to opt in and account for its own and its delegates' state.

Probably separate or out of scope

  • The HTML-inner widgets and Img already retain no Element-specific state and document multi-Element support. Making their concrete values implement jaws.UI is receiver/API work; the slot does not solve a non-comparable HTMLGetter or Getter.
  • JsVar mixes Element-local dirtyTag with shared mutation/broadcast synchronization. JawsGetTag has no Element argument, JawsSet supports a nil Element, setMu orders shared mutations and broadcasts, and ClientCheck may be a function. Do not move those fields per Element without a separate design.
  • Option and Register are already values. RadioElement is not a jaws.UI and spans separate radio/label Elements.
  • Tag resolution and snapshotting remain in Resolve getter tags once during render and preserve the registered tag set #224.

Investigation deliverables

  • Produce a matrix for every exported lib/ui widget: immutable/shared definition, mutable per-Element state, dynamic comparability requirements, supported pointer/value forms, and slot-claim/delegation implications.
  • Decide whether inputs and ContainerHelper should support multiple live Elements when their underlying setter/container is itself safe, or merely become value constructors for equal-value container reuse.
  • Define a source-compatible API strategy. Existing constructors return pointers; changing their result types is breaking even if value receivers are added.
  • Define how pointer-form field mutation after registration behaves when the Element stores a copied value or state definition.
  • Prototype the input family first unless the inventory finds a blocker.
  • Split implementation into per-family issues if the state layouts or compatibility strategies differ.

For any implementation, require regression tests showing that two live Elements have independent state, rebuilt equal children retain their Jids and emit no Append/Remove/Order, and pointer forms preserve currently supported runtime-incomparable getter/setter/container implementations. Preserve supported failure cleanup and update-only Register paths; do not invent pre-render input-event behavior that Register explicitly does not support. Performance-motivated changes need committed b.ReportAllocs benchmarks and before/after benchstat; benchmark both unchanged and changed updates so slot overhead is visible rather than hidden by large renders.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions