Found while implementing #5542. Filed unassigned as an observation, for PM triage — the
card's declared surface was FormFieldSpec only, and this is deliberately not widened
into it.
Mechanism
#5040 and #5542 are about one contract having several independent hand-written
descriptions. #5542 converged the leaf of that contract — the field spec. The two
containers above it have exactly the same shape of duplication, and nobody has
counted them.
Both files describe the same artifact, @objectstack/spec's FormView, and both say so:
| site | declaration | how it says it is the spec's FormView |
|---|
packages/app-shell/src/views/metadata-admin/SchemaForm.tsx:629 | FormViewSpec | docstring: "Lightweight shape of the spec FormView we consume" |
apps/console/src/components/FormPage.tsx:89 | FormViewSpec | header comment chain, and the identical type union |
packages/app-shell/src/views/metadata-admin/SchemaForm.tsx:660 | FormSectionSpec | element type is FormFieldSpec |
apps/console/src/components/FormPage.tsx:271 | FormSectionSpec | element type is FormFieldSpec |
Line numbers verified on the merged ref cad512fe1, before #5542's branch.
The tell that these are one contract and not two coincidences: both FormViewSpec
declarations spell a character-identical six-member union, in the same order —
type?: 'simple' | 'tabbed' | 'wizard' | 'split' | 'drawer' | 'modal';
— and both name the element type of FormSectionSpec.fieldsFormFieldSpec. That is
the same evidence #5542 used to rule the field spec one contract rather than two layers.
They have already drifted, which is the point
Unlike the field spec — where the console's copy was a clean 9-of-26 subset with no
disagreements — these two differ in both directions, so neither is a subset of the
other:
FormSectionSpec: app-shell has description, visibleWhen, visibleOn; console has
none of those. Console's columns admits the string arm
(1 | 2 | 3 | 4 | '1' | '2' | '3' | '4'), app-shell's admits numbers only. Same key,
two different answers about what an author may write.FormViewSpec: console has label, groups, sharing, submitBehavior; app-shell
has none of those and stops at type plus sections.
So this is not a hypothetical future drift, the way the field spec's was at filing time
— it is drift that has already happened and that nothing can see, because each
description is only ever checked against itself. That is #5040's mechanism, unresolved,
one level up from where #5542 closed it.
Why file it rather than fold it into #5542
#5542's scope fence was explicit and correct: the field spec, its pin, and one
changeset, with the converged form-spec.ts a read-only reuse target. Converging the
containers is a materially bigger call than converging the leaf, because the two
containers genuinely serve different surfaces — sharing and submitBehavior are
public-form concerns the metadata-admin designer has no use for, and columns' string
arm is a tolerance for hand-authored layouts. So unlike the field spec, "same contract,
import it" is not obviously the answer here; "narrower layer, rename and pin the
subset" may well be, and for FormViewSpec the honest answer might be a third thing —
that both should read the spec's own FormViewSchema type rather than either hand copy.
That is a triage decision with an architecture behind it, not a mechanical convergence,
which is why this is a finding and not a queued card.
Severity note
No user-reachable failure. Recording it because a reader of #5040 and #5542 would
reasonably conclude the class is now closed for this contract, and it is not — the leaf
is pinned, the containers are not. Severity is triage's call.
Boundary
Found while implementing #5542. Filed unassigned as an observation, for PM triage — the
card's declared surface was
FormFieldSpeconly, and this is deliberately not widenedinto it.
Mechanism
#5040 and #5542 are about one contract having several independent hand-written
descriptions. #5542 converged the leaf of that contract — the field spec. The two
containers above it have exactly the same shape of duplication, and nobody has
counted them.
Both files describe the same artifact,
@objectstack/spec'sFormView, and both say so:packages/app-shell/src/views/metadata-admin/SchemaForm.tsx:629FormViewSpecFormViewwe consume"apps/console/src/components/FormPage.tsx:89FormViewSpectypeunionpackages/app-shell/src/views/metadata-admin/SchemaForm.tsx:660FormSectionSpecFormFieldSpecapps/console/src/components/FormPage.tsx:271FormSectionSpecFormFieldSpecLine numbers verified on the merged ref
cad512fe1, before #5542's branch.The tell that these are one contract and not two coincidences: both
FormViewSpecdeclarations spell a character-identical six-member union, in the same order —
— and both name the element type of
FormSectionSpec.fieldsFormFieldSpec. That isthe same evidence #5542 used to rule the field spec one contract rather than two layers.
They have already drifted, which is the point
Unlike the field spec — where the console's copy was a clean 9-of-26 subset with no
disagreements — these two differ in both directions, so neither is a subset of the
other:
FormSectionSpec: app-shell hasdescription,visibleWhen,visibleOn; console hasnone of those. Console's
columnsadmits the string arm(
1 | 2 | 3 | 4 | '1' | '2' | '3' | '4'), app-shell's admits numbers only. Same key,two different answers about what an author may write.
FormViewSpec: console haslabel,groups,sharing,submitBehavior; app-shellhas none of those and stops at
typeplussections.So this is not a hypothetical future drift, the way the field spec's was at filing time
— it is drift that has already happened and that nothing can see, because each
description is only ever checked against itself. That is #5040's mechanism, unresolved,
one level up from where #5542 closed it.
Why file it rather than fold it into #5542
#5542's scope fence was explicit and correct: the field spec, its pin, and one
changeset, with the converged
form-spec.tsa read-only reuse target. Converging thecontainers is a materially bigger call than converging the leaf, because the two
containers genuinely serve different surfaces —
sharingandsubmitBehaviorarepublic-form concerns the metadata-admin designer has no use for, and
columns' stringarm is a tolerance for hand-authored layouts. So unlike the field spec, "same contract,
import it" is not obviously the answer here; "narrower layer, rename and pin the
subset" may well be, and for
FormViewSpecthe honest answer might be a third thing —that both should read the spec's own
FormViewSchematype rather than either hand copy.That is a triage decision with an architecture behind it, not a mechanical convergence,
which is why this is a
findingand not a queued card.Severity note
No user-reachable failure. Recording it because a reader of #5040 and #5542 would
reasonably conclude the class is now closed for this contract, and it is not — the leaf
is pinned, the containers are not. Severity is triage's call.
Boundary
duplicate declaration phrasings). [app-shell] metadata-admin 的
FormFieldSpec没有声明dependsOn,但两个 widget 都读它 —— 类型化的 form spec 写不出这两个 widget 的必备配置 #5040 and [finding] A THIRD inline copy of the form-field authoring contract lives in apps/console FormPage.tsx — objectui#5040 converged only the app-shell two #5542 are the field spec; nothing coversthe containers.
predicate in the console renderer) and Console buildSections drops the field-level maxLength override while its own docstring promises overrides win — the input gets the object's ceiling, not the form's #5595 (dropped
maxLengthoverride).