Skip to content

Form generation cannot render nested aggregates (flat actions only) #25

Description

@Yaraslaut

Summary

Form generation is documented as flat-only. From docs/spec/forms/forms.md:

A member that is itself an aggregate is emitted by glaze into $defs and referenced by
$ref; the generator does not recurse into that definition, so its sub-members receive
none of the x-* annotations and are not part of any synthesised required
array … Actions meant to drive a generated form must therefore be flat … Nesting is
not a documented form-generation path.

That is a clear and honest boundary. This issue is about what it costs, and whether
recursing is worth doing.

Where it bites

Domains where a form maps onto a measurement or a document tend to be naturally nested,
and the nesting is not incidental — it is the shape of the data:

struct Measurement {
    struct Specimen {                 // repeated sub-record
        double massDry = 0.0;
        double massWet = 0.0;
    };

    struct Record {
        static constexpr std::string_view Name = "Record";

        struct Payload {
            std::string operatorName;         // flat  -> renders
            double temperature = 0.0;         // flat  -> renders
            Specimen reference;               // nested -> $ref, no annotations
            std::vector<Specimen> replicates; // nested -> $ref, no annotations
        };

        struct Result { bool ok = false; };
        using ReturnType = Result;
        Payload payload;
    };
};

operatorName and temperature render. reference and replicates land in $defs
with no x-* and no required, so the generated form is silently incomplete — the
fields are simply absent rather than visibly broken.

The workarounds both have real costs:

  • Flatten the actionreferenceMassDry, referenceMassWet, … The action type stops
    reflecting the domain, and repeated sub-records (std::vector<Specimen>) cannot be
    flattened at all without a fixed maximum count.
  • Hand-write the form — then the schema-driven generator is unused for exactly the
    screens that would benefit most from it.

What recursing would need

Sketching the questions rather than a design:

  • Annotation reach. mergeSchemaExtras patches dom["properties"][name] for each
    top-level member. Recursing means walking into the $def a $ref points at and
    patching there — and deciding how x-order composes across levels.
  • required inside $defs. A nested definition currently gets no required array.
    Synthesising one needs the same isStdOptional rule applied one level down.
  • Repeated aggregates. std::vector<Sub> is the interesting case: the renderer needs
    add/remove affordances and per-row indices, which flat forms never required.
  • Depth limit. Probably worth capping at one or two levels rather than arbitrary
    recursion, to keep generated forms comprehensible.
  • Opt-in. Existing flat actions must render identically; recursion could be gated by
    an annotation on the nesting member.

Filing this to see whether recursion is considered in scope for the generator, or whether
flat-only is a deliberate long-term boundary. Either answer is useful — if it is
deliberate, saying so explicitly in the spec would help people decide sooner whether to
adopt the generator at all.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions