fix(spec): the form option-value refusal and the options describe name the derive path for enum rows - #19906
fix(spec): the form option-value refusal and the options describe name the derive path for enum rows#19906objectstack-fleet[bot] wants to merge 3 commits into
Conversation
…-value bound The form field's `options` describe states the rule for a metadata-form row whose key is a spec enum: omit `options`, the control derives the members from the served JSON Schema, and their meanings go in `helpText`. `defineForm`'s module-load refusal of an unspellable inline option value keeps the system-identifier grammar message and appends that remedy. The value bound and every schema shape are unchanged. Claude-Session: https://claude.ai/code/session_013RDBh5DqXd2xnLwvHLgLFr Co-authored-by: Claude <noreply@anthropic.com>
… add changeset Claude-Session: https://claude.ai/code/session_013RDBh5DqXd2xnLwvHLgLFr Co-authored-by: Claude <noreply@anthropic.com>
Generator output of `check:generated --fix` (check:docs was the one stale artifact); not hand-edited. Claude-Session: https://claude.ai/code/session_013RDBh5DqXd2xnLwvHLgLFr Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin d6cb502ded297515ef59efd4f96844633c18e64e && git checkout d6cb502ded297515ef59efd4f96844633c18e64e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 44ce049a8c52b533f8a4ae195c0dca697309e400 ebd7fc2fa84030ffac014e61262f72a364f56596 && git checkout -B drift-repro 44ce049a8c52b533f8a4ae195c0dca697309e400 && git merge --no-ff ebd7fc2fa84030ffac014e61262f72a364f56596
node scripts/docs-audit/affected-docs.mjs --json 44ce049a8c52b533f8a4ae195c0dca697309e400
|
|
CI red on head
Generated by Claude Code |
Fixes #19678
Clause-②: no
Executes ruling comment
5793380467on the card (batch #217 item 5, letter 不动 + 声明, maintainer 「217 同意」):Item 3 is untouched here. No value bound, no schema shape, no key and no export moves.
What changed
FormFieldSchema.options(packages/spec/src/ui/view.zod.ts, theFormFieldBaseSchemarow) keeps its per-optiondefaultsentence and now adds: On a metadata form (schema-bound, built bydefineForm), a row whose key is a spec enum omitsoptions: the control derives the members from the served JSON Schema, and their meanings go inhelpText. An optionvalueis a lowercase system identifier, so an enum member carrying a hyphen or a capital cannot be listed here at all. The TSDoc above the row says the same thing and names the ruling.defineFormnow callsFormViewSchema.safeParse. When the parse fails, it throws aZodErrorwith the parse's own issues. Only one thing changes: a grammar refusal (invalid_formatortoo_small) at an inline option'svalue(path endingoptions.INDEX.value, also when nested inside the field-row union'serrors) keeps its message and gets this sentence after it: An enum member carrying a hyphen, a capital or a single character cannot be a form optionvalue, which is a lowercase system identifier. When this row edits a spec enum, omitoptions: the control derives the members from the served JSON Schema, and their meanings go inhelpText. No issue is added, removed or re-coded.content/docs/references/ui/view.mdx. Two table rows changed (theoptionsrow of the two FormField tables). This ischeck:generated --fixoutput, andcheck:docswas the only stale artifact out of 15. No other artifact moved: api-surface, authorable-surface, json-schema manifest and declaration-map were all up to date..changeset/19678-form-option-enum-derive-remedy.md,@objectstack/spec: patch.Where the refusal lives (found by content), and why the remedy is attached at
defineFormSystemIdentifierSchema's regex message, declared inpackages/spec/src/shared/identifiers.zod.ts(lines 104 and 107 on base). It reaches the form face throughSelectOptionSchema.value(data/field.zod.ts).FormSelectOptionSchemareuses that value by reference, and theproperty schemas are shared BY REFERENCEpin inform-select-option.test.tsholds it there.defineForm(ui/view.zod.ts), through itsFormViewSchema.parse. All 17packages/spec/src/**/*.form.tsmodules call it at module scope.Field.select.options) and three object-storage names. For those, "omitoptions, derive from the served JSON Schema" is the wrong advice. A form-face-only message would need a secondvalueschema, and that breaks the by-reference derivation the ruling cites. A zod error map on a parent object cannot rewrite the issue either, because the regex check's ownerrorresolves first.defineFormis the one door where the remedy is true: it stampsdata.provider: 'schema'on every form it builds. So the sentence is appended there, and only there.Measured first, on
origin/main@dabf8d795edefineForm({ schemaId: 'action', type: 'simple', sections: [{ label: 'X', fields: [{ field: 'openIn', options: [{ label: 'New tab', value: 'new-tab' }] }] }] }): aZodErrorfromdefineForm, with oneinvalid_unionissue atsections.0.fields.0. Its object branch carries{ code: 'invalid_format', format: 'regex', pattern: '/^[a-z][a-z0-9_.]*$/', path: ['options', 0, 'value'] }with this message, verbatim:System identifier must be lowercase, starting with a letter, and may contain letters, numbers, underscores, or dots (e.g., "user_profile" or "order.created")perRecordandsystem-datagave the same issue shape and the same text. A one-character value givestoo_smallwithSystem identifier must be at least 2 characters.view.zod.ts:3235on base):Options for select/multiselect/radio/checkboxes fields (per-option \default` is not accepted here — declare the pre-selected choice on the object definition). It does not name a JSON Schema,helpTextor omittingoptions`.Tests
New file
packages/spec/src/ui/form-option-enum-derive.test.ts(16 tests). Its assertions name subjects (omittingoptions, the JSON Schema,helpText) rather than whole sentences:new-tab,perRecord,system-data(invalid_format) andx(too_small) names the derive path. The grammar message is kept verbatim ahead of the remedy, and it is read live off the object face. A nested row (compositefields) gets the same remedy.new_tabis still accepted.z.toJSONSchema(FormFieldSchema)), names the derive path and keeps the per-optiondefaultsentence.Ablation (one-shot, at
2aa26de218, throughscripts/ablation-replace.mjsunder the verify lock): the anchorthrow new z.ZodError(withOptionValueDeriveRemedy(parsed.error.issues));was replaced withthrow parsed.error;(today's behaviour). Anchor count went x1 → x0 and the blob went998be839→9ebe5eb7. Result:Tests 9 failed | 7 passed (16). The 9 red are exactly the remedy and nested-row assertions. The firing control, verdict, scope and describe tests stayed green. Restore: blob after restore998be839== HEAD, andgit diff HEADwas empty.Suite runs, all at
ebd7fc2fa8(the PR head):@objectstack/specvitest run --project localTest Files 527 passed (527)·Tests 15517 passed | 1 todo (15518)@objectstack/specvitest run --project repoTest Files 35 passed (35)·Tests 602 passed (602)@objectstack/spectypecheck(tsc + scripts + test layer)@objectstack/platform-objectsvitest runTest Files 54 passed (54)·Tests 883 passed (883)(after building its dependency closure; the first attempt could not load 13 files becausemetadata-core/formula/corehad nodist, which is a prerequisite and not a finding)@objectstack/platform-objectstypecheck@objectstack/speccheck:generatedAll 15 generated artifacts are up to dateDownstream readers of the old text:
git grepoverpackages/for the old describe sentence and forSystem identifier mustfound no test that pins either one. The only reader of the old describe was the generatedview.mdx, which is regenerated above.Gates:
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsatebd7fc2fa8derived 107 commands. The--ranreconciliation reports107 derived famil(ies) accounted for — 104 run, 3 NOT-MEASURED. All 104 that ran exited 0. Four of them first exited 3 and went green after the@objectstack/objectql...closure was built:check:doc-formula-expressions,check:doc-security-posture,check:docs-transcript-drift,check:lean-entry-closure. NOT MEASURED, left to CI:check:skill-examples: needs the builtclient/client-reactclosure (35 packages).check:dual-build-cjs-loads: needs every workspace package built.check:type-check-debt: needs the wholepackages/*build thatlint.ymldoes.Changeset:
patchRuntime text in a released package changes. The
defineFormrefusal ships in@objectstack/spec'sdist, and the describe is served in the JSON Schema. That is a released-package change, so there is a changeset. It isClause-②: no: every value accepted or refused before is accepted or refused now, and nothing an author can write is added or removed. So it takes the checklist'spatch, notminor.Sibling PRs
checkViewFilterRuleValueShape/ViewFilterRuleSchema) is not touched.limit#19809's region (PaginationConfigSchema, the per-kind Gallery / Timeline / Kanban / AddRecord configs,rowLimitKey) is not touched.FormFieldBaseSchema.optionsrow anddefineFormplus the helpers after it. The nearest fix(spec): one row bound per view — retire the unpublished per-kind viewlimit#19809 hunk is theCalendarConfigtype exports, about 150 lines abovedefineForm.content/docs/references/ui/view.mdxmoves here too. This PR changes only the twooptionsrows. fix(spec): one row bound per view — retire the unpublished per-kind viewlimit#19809 regenerates the same page. If the two collide, the page is regenerated, never hand-merged.Acceptance notes
optionslists across 11 of the 17 metadata forms. Each row's key was resolved in the served JSON Schema (z.toJSONSchemaofgetMetadataTypeSchema(type), input side), atdabf8d795e.object.managedBy(4:system-data,engine-owned,append-only,better-auth),action.execution(perRecord) andaction.openIn(new-tab).helpText: objectfields.valueDomain,fields.deleteBehavior(lookup row),fields.returnType,fields.summaryOperations.function,ownership,sharingModel,editMode,lifecycle.class,lifecycle.storage.strategy,lifecycle.storage.unit; fieldreturnType,summaryOperations.function; hookbody.language,onError,runAs; actionmode,body.language,operation; pagetype,interfaceConfig.recordAction; agentsurface; skillsurface; permissionmanagedBy; email_templatecategory.fields.typeomitssecretanduser, and the two master_detaildeleteBehaviorrows (objectfields.deleteBehavior, fielddeleteBehavior) omitset_null.optionson a spec-enum row, and the new describe states it that way.object.form.tscarries a comment from #19188 split: 47 top-level zod-only keys are scalar controls needing one form row each #19331 that says the opposite for five of its rows ("Each enum gets an explicitoptionslist because the bare member reads as a word…"). This PR converts nothing, as dispatched. Whether the 24 full-list rows convert, and how the 3 subsets are expressed, is left for a follow-up.defineForm(a stack'sviewmetadata withdata: { provider: 'schema' }, parsed at compose or publish) still gets the bare grammar message. The ruling names the module-load refusal. The object-field option face is unchanged by design.origin/main(44ce049a8c). None of them touchespackages/spec, and none of them touches any file in this diff, so the branch was not updated.Generated by Claude Code