What to build
A single label seam in stack-core that answers "what one field represents a row, and what text is its label" — used everywhere the admin UI shows a row as a reference (relationship cells, dropdown options, page headings).
Two entry points sharing one resolver, so the field chosen for projection can never drift from the field used for rendering:
getLabelFieldName(listConfig): string — resolves ui.labelField ?? 'name' ?? 'title' ?? 'id' (first field that exists on the list).
getItemLabel(listConfig, item): string — reads the resolved field off the row, falls back to id when the field is absent (e.g. stripped by field-level access).
Add ui.labelField?: string to the list config type, exported both functions from the @opensaas/stack-core root entry point. This is the Label field / Item label vocabulary now recorded in CONTEXT.md.
No consumer changes in this slice — this is the foundation two downstream slices build on.
Acceptance criteria
Blocked by
None - can start immediately
What to build
A single label seam in
stack-corethat answers "what one field represents a row, and what text is its label" — used everywhere the admin UI shows a row as a reference (relationship cells, dropdown options, page headings).Two entry points sharing one resolver, so the field chosen for projection can never drift from the field used for rendering:
getLabelFieldName(listConfig): string— resolvesui.labelField ?? 'name' ?? 'title' ?? 'id'(first field that exists on the list).getItemLabel(listConfig, item): string— reads the resolved field off the row, falls back toidwhen the field is absent (e.g. stripped by field-level access).Add
ui.labelField?: stringto the list config type, exported both functions from the@opensaas/stack-coreroot entry point. This is the Label field / Item label vocabulary now recorded inCONTEXT.md.No consumer changes in this slice — this is the foundation two downstream slices build on.
Acceptance criteria
ui.labelFieldaccepted on the list config and validated (references a declared scalar field when set)getLabelFieldNameandgetItemLabelexported from the root entry pointlabelField→name→title→idgetItemLabelfalls back toidwhen the resolved field is missing from the row(ui.labelField, fields present, row)asserting the projection⇄render agreement (the fieldgetLabelFieldNamereturns is the fieldgetItemLabelreads)Blocked by
None - can start immediately