Docs

The closed 9-type vocabulary every LCAP consumer resolves against. Type drives the default component; @ui/component overrides it within the type's allowed slug list.

Field types

Every LCAP-aware annotation row carries a closed 9-type vocabulary in field_type. The resolver maps each type to a default React component; ui_config_json["@ui/component"] overrides the default within the type's allowed slug list.

The vocabulary is closed by design — adding a new type ships with the SDK, not at runtime. Admins who need a fully custom component author an LLM-authored extension instead.

The matrix

field_typeDefault componentWire shapeNotes
string<TextInput />string (≤ max_length)Single-line text.
longtext<TextArea />string (no length cap unless declared)Multi-line.
number<NumberInput />numberHonors min/max/@ui/step.
boolean<Checkbox />boolean
timestamp<DatePicker />ISO-8601 stringStorage stays UTC; display via @ui/timezone.
enum<Select />string from options_json
fsm_state_ref<FsmStatePicker />string (state name)Free-text input by default; <select> when options_json carries a curated list.
file_refsummary spanFileRef JSONRead-only summary; rich preview lives in the Card block.
relationship<RelationshipDisplay />string (id) or { id, display_path }Annotation-only in v0. No graph fetch, no inline picker.

NULL field_type (pre-LCAP rows) degrades to string — existing tenants who haven't started using LCAP keep working.

@ui/component overrides per type

When the default component isn't enough, set ui_config_json["@ui/component"] to one of the allowed slugs. Mismatched combos are rejected at write time (the backend's PUT validation refuses, returning 422) and at render time (the resolver console-warns and falls back to the type's default).

field_typeAllowed @ui/component slugs
stringtext (default), password, email, url, phone, slug
longtexttextarea (default), richtext, markdown, code
numbernumber (default), currency, percent, slider, rating
booleancheckbox (default), switch, radio
timestampdate (default), datetime, time, daterange
enumselect (default), radio, tags
fsm_state_reffsm_state_picker
file_reffile_payload
relationshipdisplay (default), id_only

richtext / markdown / code resolve to lazy peer packages (@fastyoke/lcap-richtext, @fastyoke/lcap-markdowneditor, @fastyoke/lcap-codeeditor). When the peer package isn't installed, the resolver falls back to a plain <TextArea /> with a one-time console.warn.

Storage shapes

Storage shape is the wire format — what lands in entity_records.data_payload[field_key]. Display formatting is applied by the resolver only at render time; storage stays canonical so an export script that bypasses <SmartField /> gets sortable, parseable values.

  • string, longtext — UTF-8 string. The legacy text alias for longtext is still accepted by the SDK helper.
  • number — JSON number. Currency components render with Intl.NumberFormat at display time; the stored value is the raw number. Percent components store decimal (0.07); the display layer multiplies by 100 for the user.
  • boolean — JSON boolean.
  • timestamp — ISO-8601 UTC string. @ui/timezone shifts display only.
  • enum — string from options_json[].value. Empty options_json falls back to a permissive string schema.
  • fsm_state_ref — string state name. Same wire shape as a free-text field; the rendering hint is what makes it special.
  • file_ref — object: { ref, filename?, mime?, size_bytes? }. Matches the shipped Forms v2 file-upload pipeline. Optional uploads accept null.
  • relationship — string id (typically a record id from another entity).

zod refinements

The shipped entityAnnotationToZod helper (@fastyoke/sdk) maps each type to the right zod leaf with constraints from the annotation row applied:

  • required: truez.string().min(1) for stringy types, z.number() (must be present) for numeric.
  • max_length.max(n) on string leaves.
  • min / max.min(...).max(...) on number leaves.
  • options_json (for enum) → z.enum([...values]).
  • timestamp → string with a Date.parse refinement so a malformed value surfaces before the write hook fires.
  • @ui/component = "percent"[0, 1] storage range by default; explicit min/max overrides for "growth %" cases.
  • @ui/decimal_places: NparseFloat(v.toFixed(N)) === v refinement (rejects extra precision; handles IEEE-754 quirks like 0.07.toFixed(2) === '0.07').
  • file_refz.object({ ref, filename?, mime?, size_bytes? }) .nullable().
  • fsm_state_ref / relationship → string id (with .min(1) when required).

Cross-references

  • See annotations for how to set field_type + ui_config_json from the admin UI.
  • See formatting for the full set of number / timestamp formatting keys.
  • See ui-config reference for the exhaustive list of @ui/* keys.