Skip to content

Relation labels show ids: the list primary column ignores __display, and a target whose @display is a relation yields ref(<id>) #197

Description

@rrrodzilla

Summary

Relation labels (<field>__display, #32) work for the common case, a to-one or to-many relation whose target's @display names a scalar field, shown in a column cell or on the detail page. Two other shapes show ids instead of names:

  1. A relation shown in the list's primary column. The primary cell always renders the raw value through formatFieldValue, so a relation there shows the target id even though the API returned <field>__display. A relation becomes primary through an explicit @list(primary), and also automatically when the schema's own @display names a relation field. That is the natural choice for a join entity.
  2. A target whose @display names a relation field. The server renders the display value with DynamicValue's Display impl, so __display is the literal string ref(<id>). The generated detail and list pages then show ref(product_01...). The edit-form RelationSelect for such a target labels each option with the raw id. This affects every relation that points at such a schema, including derived inverse collections (-> X[]), which is where it typically shows up.

Version checked

v0.45.0 release binary (x86_64 Linux, PostgreSQL build), source at tag v0.45.0 (10ec403), PostgreSQL 16.

Reproduction

@display("name")
schema Product {
    name: text required
}

@display("title")
schema Order {
    title: text required
    lines: -> OrderLine[]
}

@display("product")
schema OrderLine {
    order:   -> Order required
    product: -> Product required
}

Create a Product "Blue widget", an Order "First order" and an OrderLine pointing at both, then read them (GET .../entities/{id} and list give the same result):

GET .../OrderLine/entities/<id>
{"order":"order_01...","product":"product_01...","order__display":"First order","product__display":"Blue widget"}

GET .../Order/entities/<id>
{"lines":["orderline_01..."],"title":"First order","lines__display":["ref(product_01m3d41b6yevprsbadszjnera2)"]}

Generated site (schemaforge site generate -s schemas -o app):

  • /app/order-line: @display("product") promotes product to the primary column. The generated cell is formatFieldValue(row.original.product, { kind: "relation_one" }), which renders product_01..., although product__display is "Blue widget".
  • /app/order/<id>: the Lines row shows a link labelled ref(product_01m3d41b6yevprsbadszjnera2).
  • Any edit form with a -> OrderLine picker labels each option product_01....

Shapes that work: order/product as @list(column) cells and on the OrderLine detail page, and stored or derived -> X[] where X's @display is a scalar.

Expected

  • A relation in the primary column shows its __display label, as a column cell does, while still linking to the row's own detail page.

  • For a target whose @display is a relation, either:

    • the server resolves one more hop, so lines__display is ["Blue widget"]; or
    • the DSL rejects @display on a relation field (or on anything that is not a scalar) at parse time, so the problem cannot be declared.

    At the very least, __display should never be the internal ref(...) rendering.

Source

  • crates/schema-forge-cli/templates/site/src/app/pages/list.generated.tsx.jinja:78-101: the primary branch handles enum specially and sends everything else, relations included, to formatFieldValue. The column branch (:106) has, at :114-143, the relation_one / relation_many + relation_display_field cases that read __display.
  • crates/schema-forge-cli/src/commands/site/context.rs:195-209: @display("<field>") auto-promotes that field to primary, even when it is a relation.
  • crates/schema-forge-acton/src/routes/entities.rs:1875-1876: display_value_to_string(display_value) on the target's display field. :2397-2405: non-scalar values fall back to other.to_string().
  • crates/schema-forge-core/src/types/dynamic_value.rs:76: Self::Ref(id) => write!(f, "ref({id})").
  • crates/schema-forge-core/src/types/schema_definition.rs:119-124 and the @display parser (crates/schema-forge-dsl/src/parser.rs:433): no check on the named field's type.
  • labelFor in the vendored src/components/ui/relation-select.tsx (crates/schema-forge-cli/src/commands/site/vendor.rs:557): uses row[displayField] as the label when it is a string, which a relation id is.

Suggested fix

  1. In list.generated.tsx.jinja, give the primary branch the same relation cases as column: label from __display (falling back to the id), link to the row's own detail page.

  2. Pick one for relation-valued @display:

    • Resolve: when the target's display field is itself a to-one relation, run a second batched lookup against that relation's target, applying the same tenant and authorization filtering, and use its display value.
    • Reject: refuse @display on relation, array, composite, JSON and file fields at parse time, with a message suggesting a @computed text field.

    Either way, display_value_to_string should not fall through to Display for Ref/RefArray.

  3. Add a list-template test for a primary relation, and an API test for a display field that is a relation.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions