You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[P2] objectui @object-ui/types hand-declares five action vocabularies the spec owns — only ActionType is derived, and ActionParam has already drifted #4074
Found while closing #3856 (objectui#2990). That issue predicted a TypeScript reader in objectui "should already fail to compile" once @objectstack/spec 17 removed action.execute — and it didn't. This is why, and the fallback was the smaller half of it.
Filed separately rather than widening objectui#2990, which deleted the dead alias.
The shape of it
@objectstack/spec/ui (packages/spec/src/ui/action.zod.ts) owns the whole action vocabulary and exports it under names objectui could import directly:
objectui's packages/types/src/ui-action.ts imports exactly one of them — ActionType — and hand-declares the rest, while the doc comments claim spec canonicity:
"Single source of truth lives in @objectstack/spec/ui as ACTION_LOCATIONS + ActionLocationSchema + ActionLocation. Re-export here…"
re-declares all three by hand
ActionComponent
"Canonical definition from @objectstack/spec/uiActionSchema.component."
hand-written 4-member union
ActionParamFieldType
"Aligned with the field types available in @objectstack/specActionParamSchema."
hand-written 16-member union
ActionParam
—
hand-written 9-field interface
ActionSchema
—
hand-written interface
ActionLocation and ActionComponent happen to match spec today, so those two are drift-prone rather than drifted. ActionParam is the one that already broke.
ActionParam has already drifted — two ways
1. type admits 16 of the spec's 49 field types. spec's ActionParamSchema.type is FieldType.optional(), and FieldType (packages/spec/src/data/field.zod.ts:13) has 49 members. objectui's ActionParamFieldType is a hand-picked 16 — a strict subset. So a spec-valid param typed lookup, multiselect, currency, percent, user, image, tags, json, code, markdown, richtext, radio, checkboxes, location, address, signature, … fails tsc against @object-ui/types.
2. It cannot express 13 of the 22 param capabilities objectui itself implements.@object-ui/core's ActionParamDef (packages/core/src/actions/ActionRunner.ts:277) declares 22 fields; the public ActionParam declares 9. Missing: visible, accept, maxSize, multiple, referenceTo, displayField, idField, descriptionField, titleFormat, lookupColumns, lookupFilters, lookupPageSize, dependsOn — every one of which ActionParamDialog renders through the shared field-widget renderer (objectui ADR-0059), as ActionParamDef's own comment states.
Note ActionParamDef.type is plain string. So the two objectui readers of one spec concept disagree with the spec and with each other: unbounded in core, 16 members in types, 49 in spec.
Also structural: spec makes name / label / type all optional, because field references an existing object field and inherits its label/type/validation/options. objectui's ActionParam makes all three required and omits field / objectOverride entirely — spec's primary way to declare a param is unrepresentable. And spec's option labels are I18nLabelSchema; objectui's are plain string, so a localized option label is a type error.
This is precisely the failure mode #2231/#2901 already fixed once, for the sibling symbol in the same file. That fix's own comment records it:
The previous union claimed to be the "canonical definition from @objectstack/spec" and was missing form — so a host app typing against @object-ui/types got a type error on type: 'form' even though ActionRunner.executeForm implements it.
Same file, same claim, same outcome — five symbols later.
#3856 asked whether the compiler should already have rejected action.execute, "because if it still compiles, something in objectui is reading a looser type than the spec's." It still compiled, because no action type in objectui is bound to the spec's z.infer except ActionType. Removing a key from ActionSchema in spec 17 produced zero signal in objectui; objectui#2990 had to find the readers by grep and delete the declarations by hand.
Derivation is the only thing that makes the next spec removal audible. Until then every tombstone in spec (#3855, #3883) is a silent no-op against objectui's public types, and each one costs a manual grep-and-delete PR.
Suggested shape
Re-export ActionLocation / ACTION_LOCATIONS / ActionLocationSchema and derive ActionComponent from ActionSchema.shape.component — mechanical, no behaviour change, and it makes the existing comments true.
Derive ActionParam from ActionParamSchema. This is a breaking widening: name/label/type become optional and field/objectOverride appear, so host apps destructuring param.name need a look. Worth pairing with narrowing ActionParamDef.type from string to FieldType so core and types finally agree.
Decide whether ActionSchema (the interface) becomes ActionInput or stays an objectui-side view. It carries genuinely objectui-only fields (openIn, and the renderer-facing docs), so a full swap may not be right — but the fields that do mirror spec should be derived rather than restated.
Found while closing #3856 (objectui#2990). That issue predicted a TypeScript reader in objectui "should already fail to compile" once
@objectstack/spec17 removedaction.execute— and it didn't. This is why, and the fallback was the smaller half of it.Filed separately rather than widening objectui#2990, which deleted the dead alias.
The shape of it
@objectstack/spec/ui(packages/spec/src/ui/action.zod.ts) owns the whole action vocabulary and exports it under names objectui could import directly:ACTION_LOCATIONS/ActionLocationSchema/ActionLocationActionSchema(incl. thecomponentenum)ActionParamSchemaAction = z.infer<typeof ActionSchema>ActionParam = z.infer<typeof ActionParamSchema>ActionInput = z.input<typeof ActionSchema>objectui's
packages/types/src/ui-action.tsimports exactly one of them —ActionType— and hand-declares the rest, while the doc comments claim spec canonicity:@object-ui/typessymbolActionTypez.infer<typeof SpecActionType>ActionLocation+ACTION_LOCATIONS+ActionLocationSchema@objectstack/spec/uiasACTION_LOCATIONS+ActionLocationSchema+ActionLocation. Re-export here…"ActionComponent@objectstack/spec/uiActionSchema.component."ActionParamFieldType@objectstack/specActionParamSchema."ActionParamActionSchemaActionLocationandActionComponenthappen to match spec today, so those two are drift-prone rather than drifted.ActionParamis the one that already broke.ActionParamhas already drifted — two ways1.
typeadmits 16 of the spec's 49 field types. spec'sActionParamSchema.typeisFieldType.optional(), andFieldType(packages/spec/src/data/field.zod.ts:13) has 49 members. objectui'sActionParamFieldTypeis a hand-picked 16 — a strict subset. So a spec-valid param typedlookup,multiselect,currency,percent,user,image,tags,json,code,markdown,richtext,radio,checkboxes,location,address,signature, … failstscagainst@object-ui/types.2. It cannot express 13 of the 22 param capabilities objectui itself implements.
@object-ui/core'sActionParamDef(packages/core/src/actions/ActionRunner.ts:277) declares 22 fields; the publicActionParamdeclares 9. Missing:visible,accept,maxSize,multiple,referenceTo,displayField,idField,descriptionField,titleFormat,lookupColumns,lookupFilters,lookupPageSize,dependsOn— every one of whichActionParamDialogrenders through the shared field-widget renderer (objectui ADR-0059), asActionParamDef's own comment states.Note
ActionParamDef.typeis plainstring. So the two objectui readers of one spec concept disagree with the spec and with each other: unbounded incore, 16 members intypes, 49 in spec.Also structural: spec makes
name/label/typeall optional, becausefieldreferences an existing object field and inherits its label/type/validation/options. objectui'sActionParammakes all three required and omitsfield/objectOverrideentirely — spec's primary way to declare a param is unrepresentable. And spec's option labels areI18nLabelSchema; objectui's are plainstring, so a localized option label is a type error.This is precisely the failure mode #2231/#2901 already fixed once, for the sibling symbol in the same file. That fix's own comment records it:
Same file, same claim, same outcome — five symbols later.
Why this is the real finding of #3856
#3856 asked whether the compiler should already have rejected
action.execute, "because if it still compiles, something in objectui is reading a looser type than the spec's." It still compiled, because no action type in objectui is bound to the spec'sz.inferexceptActionType. Removing a key fromActionSchemain spec 17 produced zero signal in objectui; objectui#2990 had to find the readers by grep and delete the declarations by hand.Derivation is the only thing that makes the next spec removal audible. Until then every tombstone in spec (#3855, #3883) is a silent no-op against objectui's public types, and each one costs a manual grep-and-delete PR.
Suggested shape
ActionLocation/ACTION_LOCATIONS/ActionLocationSchemaand deriveActionComponentfromActionSchema.shape.component— mechanical, no behaviour change, and it makes the existing comments true.ActionParamfromActionParamSchema. This is a breaking widening:name/label/typebecome optional andfield/objectOverrideappear, so host apps destructuringparam.nameneed a look. Worth pairing with narrowingActionParamDef.typefromstringtoFieldTypesocoreandtypesfinally agree.ActionSchema(the interface) becomesActionInputor stays an objectui-side view. It carries genuinely objectui-only fields (openIn, and the renderer-facing docs), so a full swap may not be right — but the fields that do mirror spec should be derived rather than restated.ActionParamFieldType≡FieldTypeand that everyActionParamDeffield exists inActionParamSchema. The derivation is what fixes it; the guard is what keeps it fixed.Step 1 is safe on its own. Steps 2–3 are breaking and want their own changeset.
Refs #3856 (objectui#2990), #2231 / #2901 / objectui#2944 (ea7f477 "retire the five forks that shadowed a @objectstack/spec vocabulary"), objectui#2974, objectui#2989, #3855, #3883, #3903.