feat(spec): declare rows (multiline editors) and options[].description as authorable field-metadata keys (objectui#6140 / objectui#6153, spec half) - #13669
Conversation
…n as authorable field-metadata keys objectui#6140 (maintainer ruling 2026-08-25, Option A) + objectui#6153 (inherited per key): both keys were consumed-but-undeclared — objectui's RichTextField/TextAreaField read rows through an as-any, LookupField searches option description — while the strict publish door refused them. Declared for the measured consumption sets, the #11566 template: superRefine refuses rows outside textarea/markdown/html/richtext; house count discipline refuses 0/negative/fractional rows. dependsOn deliberately NOT declared (the canonical depends_on exists; widget fix rides the objectui half). The four inert editor keys stay undeclared, pinned. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PBjwYLS6BciTQW3c9xQiD2
…dataForms rows entries node scripts/check-i18n-bundles.mjs --write (merge mode) — the two new form rows of objectui#6140 land as metadataForms keys; translated locales receive the source text per merge-mode contract and still need translating. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PBjwYLS6BciTQW3c9xQiD2
…n after the field.zod.ts insertion (census --fix) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PBjwYLS6BciTQW3c9xQiD2
…note options[].description; regenerate state counts The liveness walk found the newly declared rows key UNCLASSIFIED. Entry follows the placeholder precedent (consumers-shipped-first, cross-repo objectui evidence with line cites). options note now names description (objectui#6153). state-counts.md regenerated: field 88->89 live. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PBjwYLS6BciTQW3c9xQiD2
📓 Docs Drift CheckThis PR changes 3 package(s): 11 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 — 129 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 db94ee67f420d4dd1b25f84f692ca46a62621887 && git checkout db94ee67f420d4dd1b25f84f692ca46a62621887
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6b285eca42a780636ea416a916e0de228f797fc1 5a4b5e4b2d96ce94875aa19fb7ef6421201b2719 && git checkout -B drift-repro 6b285eca42a780636ea416a916e0de228f797fc1 && git merge --no-ff 5a4b5e4b2d96ce94875aa19fb7ef6421201b2719
node scripts/docs-audit/affected-docs.mjs --json 6b285eca42a780636ea416a916e0de228f797fc1
|
…vert the shipped-form census pins in place Patch round on PR #13669's red head 83eeba1, two signatures: - driver-sql builtin-column-collision (#12015): the classification pin demands every FieldSchema key be classified deliberately; rows joins as 'presentation' (read by objectui's TextAreaField/RichTextField, never by the DDL). CI red 'unclassified FieldSchema key(s): [rows]' -> green. - packages/lint validate-predicate-path-refs census pins: the two new visibleWhen-gated rows entries move the corpus 49->51 (shipped-form predicates) and 17->18 (object form's data.type-rooted predicates); inverted in place with the reason recorded, per the #13216 precedent. The ==-literal census stays 42 (the new rows use 'data.type in [...]'). Maintainer ruled the open question A: the four-type multiline set stands. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PBjwYLS6BciTQW3c9xQiD2
…merged tree Discharges the regeneration owed by the preceding merge commit: `node scripts/check-system-context-census.mjs --fix` re-anchored the one line-rotted citation; the gate is green on the merged tree.
|
@评审链(接手本 PR 落地的席位)——派发席状态探询,单次不刷屏: 本 PR 自 07:52Z 你方 main-sync + 清标后已 33/33 check 全绿(08:15Z 起),但至今仍停在 draft,>1 小时无动作。它是三张卡的关键路径:objectui 半边(#6140/#6153 收口)与 #13632 / #13671(同热文件 两个走法任选,请回一个:
无回应下本席不碰分支;下一探针(~09:50Z)若仍无动静,将升级到维护者决策箱。 Generated by Claude Code |
|
os-dev-report {
"issue": "PR#13669-sync",
"status": "done",
"branch": "claude/issue-6140-field-metadata-declared-keys-spec",
"pr": "https://github.com/objectstack-ai/objectstack/pull/13669",
"premise_still_valid": true,
"summary": "Main-sync conflict resolution round, no behavior changes. Exactly ONE conflicted file: content/docs/permissions/system-context.mdx (generated census anchor table -> generated-artifact class). Why it text-conflicted despite its merge=os-regen routing: the routing line was added on main by 13646 AFTER this branch forked, and the merge reads gitattributes from the branch-side worktree, which lacked it -- expected git behavior, not a bug. Resolution followed the os-regen-merge.sh sequence by hand (the script cannot resume an in-progress merge; merge-base b8fc7280 was captured pre-merge): took origin/main's side as placeholder, committed the merge FIRST (865d63b4), then regenerated on the merged tree -- gen:schema, gen:docs, gen:liveness-counts came back byte-identical (nothing silently dropped; the only both-sides-moved regen path was the census page), census --fix re-anchored exactly 1 line (field.zod.ts:1451 -> 1514, the branch's own insertion shift) committed as 5a4b5e4b, i18n bundles verified already in sync (no --write needed). All ruled shapes preserved and pinned: rows four-type applicability set, dependsOn refused, four inert editor keys refused; shard entries data/Field:rows, data/SelectOption:description, ui/FormSelectOption:description and the liveness live entry for rows all present post-regen. No source-file conflicts, no takes-one-side judgment calls arose. Main moved again mid-run (eb717a12 -> 936aa2d3, 9 commits, none touching packages/spec): verified the pushed branch merges cleanly with the new tip (git merge-tree --write-tree exit 0) AND that the census page 3-way TEXT-merges clean (git merge-file exit 0, zero markers) since GitHub merges without the custom driver -- mergeable_state should flip clean without another sync round. Pushed 9c2f8667..5a4b5e4b. Did not touch ready/draft state or labels per dispatch.",
"tests": "All at final head 5a4b5e4b, union run AFTER the final commit. 28-pin file via verify-lock: 'Test Files 1 passed (1)', 'Tests 28 passed (28)'. Full spec suite via verify-lock: 'Test Files 445 passed (445)', 'Tests 11912 passed (11912)'. Spec typecheck: 'check:test-typecheck: OK -- @objectstack/spec's test layer compiles under packages/spec/tsconfig.test.json'. check:generated: 'All 14 generated artifacts are up to date.' Census gate: 'check-system-context-census: OK -- 109 elevation read sites in 20 packages across 45 files, all anchored; 145 anchors resolve, 27 declared non-read.' i18n: 'check-i18n-bundles: OK (9 package(s) -- all bundles in sync, no undeclared authoring keys).' dispatch-gates.mjs (no paths, derived at 5a4b5e4b, 23-path change set vs merge base eb717a12): 60 derived commands run, 58 exit 0, 2 exit 3 = self-declared NOT MEASURED refusals, not reds (check-test-completeness parses a CI test log; scripts/pm/check-half-states.mjs needs a GitHub route this container lacks -- its pnpm check:pm-half-states self-test half exits 0). check:nul-bytes OK (7588 files). dispatch-gates flagged 3 stale gate scripts vs the then-newer main tip; none of the three matched this card's paths (verified by grep over the derivation output). check-dev-prereqs initially refused PREREQUISITE NOT MET (workspace not built), satisfied by a full turbo build under the lock, then exit 0. All exits captured before any pipe.",
"mcp_calls": "3 -- 1 comment write, 1 planned comment read-back, 1 planned PR mergeable-state read (PR body itself read via the zero-quota page-payload channel)",
"open_questions": [],
"out_of_scope_findings": []
}Generated by Claude Code Generated by Claude Code |
Part of objectstack-ai/objectui#6140
Part of objectstack-ai/objectui#6153
The SPEC HALF of the ruled two-card family. Maintainer ruling 2026-08-25 on objectui#6140 (verbatim: 「就全部接受,然后继续下一批」) = Option A: declare
rowsso validation and the running widget finally agree, aligning theTextareaFieldMetadataprecedent. objectui#6153 inherited the frame per key:options[].descriptionis genuinely consumed and gets declared;dependsOnis NOT declared (resolved objectui-side against the canonicaldepends_on). The objectui half (types/widget/docs) dispatches after this lands. Clause-② is ruled yes on both cards, so this PR parks as DRAFT +needs:contract-review— it is not awaiting more code, it is awaiting contract review.What lands
rows(positive integer) onFieldSchema—packages/spec/src/data/field.zod.ts. Both keys were consumed-but-undeclared: objectui'sRichTextField(the one widget behind themarkdown/html/richtextregistry keys, objectui#5498) readsrichField?.rows || 8through anas any,TextAreaFieldreadstextareaField?.rows || 4— while the strict publish door refused the key on every type. Declared with the spec:maxLengthis authorable on every field type and validated as no more than a number —maxLength: 0andmaxLength: 12.5parse cleanly #11566 template: a module-localMULTILINE_EDITOR_FIELD_TYPESset plus a superRefine that refuses the key outside it, message enumerating the set itself.description(string) onSelectOptionSchema— same file. objectui'sLookupFieldsearches it on authored static options (opt.description && opt.description.toLowerCase().includes(q), LookupField.tsx:526) andrecordToOptionemits the same key for fetched options. The object.form options repeater has offered adescriptioninput all along; the declaration makes that offer honest. It flows intoFormSelectOptionSchemamechanically via the Narrow the per-optiondefaultkey OUT of the form-view options vocabulary — the object-field face keeps it (enforced, #7246), the form-view face parses-and-ignores it (ruled on objectui#6263) #12868 Omit-by-reference construction (pinned there).rowsfor exactly the accepted set (field.form.ts,object.form.ts), the spec:maxLengthis authorable on every field type and validated as no more than a number —maxLength: 0andmaxLength: 12.5parse cleanly #11566/spec:minLengthstill has the pre-#11566 defect pair — validated as no more than a number, authorable on every field type #11949 forms-convergence discipline.data/Field:rows,data/SelectOption:description,ui/FormSelectOption:description), i18n bundles (merge mode,check-i18n-bundles.mjs --write— translated locales received source text and still need translating), liveness state-counts (gen:liveness-counts), plus a census--fixre-anchor ofsystem-context.mdx(pure line rot from the field.zod.ts insertion) and a liveness ledger entry classifyingfield/rowslive with cross-repo evidence (theplaceholderprecedent shape)..changeset/field-rows-and-option-description-declared.md,@objectstack/specminor — additive accepted-set expansion, following the declare-the-consumed-members precedent of the recent spec expansion changesets (spec-save-meta-item-request-membersis the shape template; the in-tree ruling precedents are spec:maxLengthis authorable on every field type and validated as no more than a number —maxLength: 0andmaxLength: 12.5parse cleanly #11566/spec:minLengthstill has the pre-#11566 defect pair — validated as no more than a number, authorable on every field type #11949/signature/qrcodehave nomaxLengthenforcement anywhere, so they cannot join the TEXT family — a data-URI signature is refused at 255 chars and the declared bound binds nothing #11875). Non-breaking, so no ADR-0087 marker is owed (gate green).packages/spec/src/data/field-rows-option-description.test.ts— 28 pins: accept pins preserving values through parse on all four types and through the real select-field and lookup-field doors; shape refusals (0 / -5 / 2.5); wrong-type refusals with the custom issue at path rows; publish-door controls proving the refusal is GONE for exactly the ruled keys and STILL PRESENT for undeclared neighbours — the four inert editor keys (toolbar/preview/minHeight/maxHeight) on a markdown field, andicon/dependsOnon the option shape.The derived judgments contract review should look at
rowsapplicability set = textarea, markdown, html, richtext. The RULED pair is markdown/html. textarea and richtext are derived members, from the measured consumption set: the same two widget reads serve all four types, the spec refuses the key on EVERY type today, and declaring the ruled pair while still refusing the precedent type's own declared key would manufacture a fresh declared-vs-enforced split on exactly the type the ruling aligns to. Trimming to the literal pair is a two-line change if review prefers it.rowsvalue shapeint().min(1). objectui'sTextareaFieldMetadataspellsrows?: numberonly because a TS interface cannot say more; the house count discipline (spec:Field.scaleaccepts meaningless declarations (2.5,-1) — now that scale is enforced, malformed declarations should be refused at authoring time #8321/spec:maxLengthis authorable on every field type and validated as no more than a number —maxLength: 0andmaxLength: 12.5parse cleanly #11566: a count of 0, -5 or 2.5 has no defined meaning) is applied, matchingmaxLength/minLengthone key above. No upper bound invented.descriptionreaches the form-view option face by the Narrow the per-optiondefaultkey OUT of the form-view options vocabulary — the object-field face keeps it (enforced, #7246), the form-view face parses-and-ignores it (ruled on objectui#6263) #12868 Omit construction — one vocabulary by design; splitting the faces for this key would need its own ruling.Explicitly NOT here
dependsOn— the inherited ruling resolves it objectui-side (widget reads the canonical declareddepends_on); a camelCase twin is not licensed. Pinned refused.rows; none of them was found declared anywhere in spec (fence check clean). Pinned refused.Evidence (local, at
83eeba1b)field.zod.tsreverted to base7483afdd(mutation proved on disk: 0 grep hits for the set and the key), pin file went 22 failed / 6 passed — exactly the six control pins staying green — then restore via checkout of HEAD, proved by emptygit diff HEADand matchinggit hash-objectvs the HEAD blob, back to 28/28.pnpm lint(eslint, no inline config): exit 0, full run, no narrowing — ate665729f; the one later commit touches onlyliveness/field.json+state-counts.md.check:i18nOK (9 packages in sync),check:i18n-coverageOK (602 baselined, none new),check:livenessgreen ("every governed-type property … is classified"), census OK (145 anchors resolve), plus the dispatch-gates derived families (doc gates, changeset gates, published-files, dual-build-cjs, type-check ratchets, nul-bytes…) all exit 0 — verdict lines archived in the run log.check:test-completenesslocally NOT MEASURED by design (it parses a CI test log; its own output says to record exactly that).Landing
DRAFT +
needs:contract-reviewby ruling (Clause-②). The cards close on the objectui half — thePart oflines above are deliberate, and objectui#6153 remains open here as well.Generated by Claude Code
Generated by Claude Code