Skip to content

[P2] objectui @object-ui/types hand-declares five action vocabularies the spec owns — only ActionType is derived, and ActionParam has already drifted #4074

Description

@os-zhuang

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:

spec export line
ACTION_LOCATIONS / ActionLocationSchema / ActionLocation 290 / 300 / 301
ActionSchema (incl. the component enum) 387 / 408
ActionParamSchema 130
Action = z.infer<typeof ActionSchema> 746
ActionParam = z.infer<typeof ActionParamSchema> 747
ActionInput = z.input<typeof ActionSchema> 748

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:

@object-ui/types symbol what the comment says what the code does
ActionType derived (#2231/#2901) z.infer<typeof SpecActionType>
ActionLocation + ACTION_LOCATIONS + ActionLocationSchema "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/ui ActionSchema.component." hand-written 4-member union
ActionParamFieldType "Aligned with the field types available in @objectstack/spec ActionParamSchema." 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.

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'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

  1. Re-export ActionLocation / ACTION_LOCATIONS / ActionLocationSchema and derive ActionComponent from ActionSchema.shape.component — mechanical, no behaviour change, and it makes the existing comments true.
  2. 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.
  3. 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.
  4. Add a parity guard, the way feat(kernel): kernel:bootstrapped anchor + fix everyone auto-bind ordering (#2926 follow-ups) #2989 did for the view-operator table: assert ActionParamFieldTypeFieldType and that every ActionParamDef field exists in ActionParamSchema. 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.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions