refactor(types)!: the action sub-vocabularies derive from @objectstack/spec (objectstack#4074) - #3003
Merged
Merged
Conversation
…k/spec (objectstack#4074) `packages/types/src/ui-action.ts` imported exactly one of the spec's action vocabularies — `ActionType`, derived in #2231/#2901 — and hand-declared the rest under doc comments claiming spec canonicity. `ActionLocation`'s comment said the single source of truth was `@objectstack/spec/ui` and that this file "re-export"s it, while the code re-DECLARED a parallel union, `as const` tuple, and `z.enum`. That is why objectstack#3856 predicted a compile error when spec 17 removed `action.execute` and there wasn't one: nothing here was bound to the spec's `z.infer`, so a key removal upstream produced no signal in objectui. Already drifted, not merely drift-prone. `ActionParamSchema.type` is `FieldType.optional()` and `FieldType` carries 49 members; the hand-written `ActionParamFieldType` listed 16. A spec-valid param typed `lookup`, `multiselect`, `currency`, `user`, `tags` or `json` failed `tsc` against this package even though the dialog renders it — the same failure `ActionType` had before it was derived (missing `form` while `executeForm` implemented it). - `ActionLocation` / `ACTION_LOCATIONS` / `ActionLocationSchema` are now the spec's own three symbols. The latter two stay VALUE exports: #2561 decision (a) drops spec/ui's `…Schema` names from this package but explicitly keeps these, so they must not sit in an `export type` block that erases them. - `ActionComponent` is `NonNullable<Action['component']>`, read off the spec's resolved `Action` rather than `ActionSchema.shape.component` — spec exports `ActionSchema` as a `lazySchema` proxy that does not forward `.shape`. - `ActionParamFieldType` is the spec's `FieldType`, with `ACTION_PARAM_FIELD_TYPES` as a runtime witness: a type alias erases, so identity against `FieldType.options` is what catches a future re-fork. - `ActionParam` gains the 13 optional capability fields it could not express (`visible`, `accept`, `maxSize`, `multiple`, and the lookup-picker group), all already declared by core's `ActionParamDef` and mapped into the shared field renderer by app-shell's `paramToField.ts` (ADR-0059). A blind derivation would have broken authored metadata: `paramToField.ts`'s `PARAM_TYPE_ALIASES` still accepts `checkbox`, `reference` and `datetime-local`, none of which is a spec `FieldType`. They are now declared as `ObjectUiLocalParamFieldType` and `ActionParam.type` takes `ResolvableParamFieldType` (spec ∪ local) — the shape this file already uses for `navigation`, so the dialect is visible to an importer instead of hidden in a `Record<string, string>` in another package. Also corrects this guard file's own claim. Its header said the `satisfies` checks "are the real enforcement"; they are not. Every package tsconfig excludes test files by glob and there is no vitest `typecheck` project, so no `tsc` invocation reads that file — reverting `ActionParamFieldType` to the 16-member fork produces zero type errors. The #4074 cases are therefore runtime identity and membership checks, which do fail against the pre-fix source. BREAKING CHANGE: `ActionParamFieldType` widens from 16 members to 49, so an exhaustive `switch` over a param `type` stops being exhaustive. All 16 previous members remain valid, so no authored metadata breaks. The new `ActionParam` fields are optional and additive. Refs objectstack#4074, objectstack#3856, #2231, #2901, #2561, #2944 Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014DBMo8gnFduzgpuSHd5Eje
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
os-zhuang
marked this pull request as ready for review
July 30, 2026 09:09
os-zhuang
added a commit
that referenced
this pull request
Jul 30, 2026
… guards actually run (objectstack#4074) (#3009) `spec-derived-unions.test.ts` — the file that exists to stop a spec-derived union from being re-forked into a hand-written copy — was not being compiled by anything. Its header claimed the `satisfies` checks in it "are the real enforcement"; measured, not assumed: reverting `ActionParamFieldType` from the spec's `FieldType` back to a hand-written subset produced ZERO type errors. It now produces TS1360 on the `satisfies` line. `tsconfig.json` excludes test files — correctly, since it is the package build (`rootDir` / `composite` / `declaration`), so tests would emit into dist — and no other `tsc` invocation read them. The sibling guards over `ChartType`, `ReportType`, `ActionType` and `PageType` were inert for the same reason, so the anti-regression mechanism left behind by #2944 / #2901 was not running in CI. The fix is `packages/types/tsconfig.test.json`, chained from the package's `type-check` script — the shape this package already uses for `tsconfig.examples.json`: a separate, emit-free project. Kept separate rather than deleting the exclude so the BUILD stays honest: the reexport guard's source scan needs `types: ["node"]`, and folding that into `tsconfig.json` would let package source reference Node APIs and still compile, in a package that ships to browsers. Turning it on surfaced 39 pre-existing errors: - `p2-spec-exports.test.ts` (22): imported eight `…Schema` names as types that #2561 decision (a) removed — contradicting the sibling guard that asserts their absence — and typed parse-INPUT fixtures as parsed OUTPUT (these schemas `.default()` several fields). Now `z.input<>`, the distinction spec itself draws with `ActionInput`. - `app-creation-types` / `system-fields` (3): imported the package by its own name; turbo's `type-check` is `dependsOn: ["^build"]`, so the package's own dist does not exist when it runs. Now the relative import all siblings use. - `p1-spec-alignment.test.ts` (14): excluded WITH a written reason — real debt, not hygiene. Every error sits in a test named "should accept <shape>" whose purpose is asserting the type accepts that shape, and the type rejects it; the shapes are real and handled (core's `foldSharing` et al.) but only as untyped input. Tracked on objectstack#4074. Also corrects an overstatement in #3003: 33 of 41 package tsconfigs exclude tests, not all — 5 packages already type-check theirs. Refs objectstack#4074, #3003, #2561, #2944, #2901, #2911 Co-Authored-By: Claude <noreply@anthropic.com>
This was referenced Jul 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements steps 1 and 4 of objectstack-ai/objectstack#4074, filed off the back of objectstack#3856 / #2990.
What was wrong
packages/types/src/ui-action.tsimported exactly one of the spec's action vocabularies —ActionType, derived in #2231/#2901 — and hand-declared the rest under doc comments claiming spec canonicity.ActionLocation's comment was the clearest case:The code below it re-declared a parallel union,
as consttuple, andz.enum. That is why objectstack#3856 predicted a compile error when spec 17 removedaction.executeand there wasn't one: nothing here was bound to the spec'sz.infer, so a key removal upstream produced no signal in objectui.Already drifted, not merely drift-prone
ActionParamSchema.typeisFieldType.optional(), andFieldTypecarries 49 members. The hand-writtenActionParamFieldTypelisted 16 — a strict subset. So a spec-valid param typedlookup/multiselect/currency/user/tags/jsonfailedtscagainst this package even though the dialog renders it. That is verbatim the failureActionTypehad before it was derived (missingformwhileActionRunner.executeFormimplemented it), recorded in that fix's own comment.Second drift:
@object-ui/core'sActionParamDefdeclares 22 fields; the publicActionParamdeclared 9. The 13 missing ones are all consumed — app-shell'sparamToField.tsmaps them into the shared field-widget renderer (ADR-0059).Changes
ActionLocation/ACTION_LOCATIONS/ActionLocationSchemaare now the spec's own three symbols. The latter two stay value exports: @object-ui/types: decide the@objectstack/spec/uire-export shape + bump spec pin to ^15 #2561 decision (a) drops spec/ui's…Schemanames from this package's surface but explicitly keeps these two (asserted inspec-ui-schema-reexports.test.ts), so they must not sit in anexport typeblock that would erase them toundefined.ActionComponent→NonNullable<Action['component']>. Read off the spec's resolvedActionrather thanActionSchema.shape.component, because spec exportsActionSchemaas alazySchemaproxy that does not forward.shape(its_defcarries onlytype,in,outunder zod v4).ActionParamFieldType→ the spec'sFieldType, plusACTION_PARAM_FIELD_TYPESas a runtime witness. A type alias erases, so identity againstFieldType.optionsis the only thing that catches a future re-fork; it doubles as the list a param-type dropdown should render.ActionParamgains the 13 optional capability fields it could not express:visible,accept,maxSize,multiple, and the lookup-picker group (referenceTo,displayField,idField,descriptionField,titleFormat,lookupColumns,lookupFilters,lookupPageSize,dependsOn).A blind derivation would have broken authored metadata
paramToField.ts'sPARAM_TYPE_ALIASESstill acceptscheckbox→boolean,reference→lookup,datetime-local→datetime. None is a specFieldType, so derivingActionParamFieldTypealone would have turned live authored params into type errors.They are now declared as
ObjectUiLocalParamFieldType/OBJECTUI_LOCAL_PARAM_FIELD_TYPES, andActionParam.typetakesResolvableParamFieldType(spec ∪ local) — the same shape this file already uses fornavigationviaObjectUiLocalActionType/RunnableActionType, and for the same stated reason: a dialect hidden inside aRecord<string, string>in another package is invisible to an importer.The guard file's claimed enforcement did not exist
spec-derived-unions.test.ts's header said itssatisfieschecks "are the real enforcement". They are not, and this PR corrects the comment. Every package tsconfig excludes test files by glob (packages/types/tsconfig.json) and there is no vitesttypecheckproject, so notscinvocation reads that file at all. Measured, not assumed: revertingActionParamFieldTypeto the 16-member fork produced zero type errors.So the #4074 cases are written as runtime identity and membership checks instead. This is pre-existing and wider than this PR — the
ChartType/ReportType/ActionType/PageTypesatisfieschecks in the same file are equally inert, meaning the anti-regression mechanism left behind by #2944/#2901 is not running in CI. Flagged for its own issue rather than fixed here; making tests type-checked will surface a backlog that needs its own estimate.Verification
AssertionError: … Object.is equality(a faithful copy fails, which is the point) and the alias guard fails on the missing const. 9 passed / 2 failed. Thesatisfiesadditions did not fail, which is precisely how the inert-enforcement finding above was discovered.pnpm type-check— 76/76 tasks.pnpm vitest run— 714 files / 8361 tests passed, 1 file / 24 tests skipped.packages/typessuites: 16 files / 246 tests. Action suites across core + components + app-shell: 13 files / 263 tests.eslinton changed files: 0 errors (7 pre-existinganywarnings in untouched regions; the diff adds none).@objectstack/specconfirmed a realdependenciesentry, not a devDependency — required, since the value re-exports make it a runtime dep where it was previously type-only.tscalso caught a bug in this change mid-review: makingActionLocationa pure re-export removed it as a local name, breakingActionSchema.locations?: ActionLocation[]. Fixed by importing it alongside the re-export.Breaking
ActionParamFieldTypewidens from 16 members to 49, so an exhaustiveswitchover a paramtypein a host app stops being exhaustive. All 16 previous members remain valid, so no authored metadata breaks. The addedActionParamfields are optional and additive.Not in scope
objectstack#4074 steps 2–3 stay open, both breaking in ways that need their own migration note:
ActionParam'sname/label/typeare required here where the spec makes them optional, and thefield/objectOverridefield-reference form (spec's primary way to declare a param) remains unrepresentable. Narrowing@object-ui/core'sActionParamDef.typefromstringis deferred with them — it is the same decision, and objectstack#4075 covers that reader's looseness generally.Refs objectstack#4074, objectstack#3856, objectstack#4075, #2990, #2231, #2901, #2561, #2944
Generated by Claude Code