Skip to content

docs(skills): the objectstack-data field-types rule teaches percentDisplay and lookupAutofill, and the fields page teaches lookupAutofill - #28487

Merged
os-zhuang merged 3 commits into
mainfrom
claude/project-thread-w041mp
Nov 1, 2026
Merged

os-zhuang merged 3 commits into
mainfrom
claude/project-thread-w041mp

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Tesla · project thread

Fixes #28256
Fixes #28246

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:objectstack-data 技能的字段类型表 percent 行新增 percentDisplay(不填或 'bar' 画进度条,'plain' 只显示数字,用于比率和概率);lookup / master_detail 行列出 lookupAutofill,表下新增一段教它(选中一条记录后,把它的指定字段带入本记录,默认不覆盖用户已填的值);文档站 fields.mdx 的关系字段小节同步教 lookupAutofill。
  • 为什么改:两个键的规范与控制台都已落地(Percent field shows a plain percentage when percentDisplay is plain: PercentCellRenderer reads the key #28255 的渲染、Lookup pick fills the declared fields in the record form and the line-item grid (lookupAutofill, ui half of #27945) #28245 的表单填值),但技能和文档都没教,AI 作者写概率字段仍会得到一条进度条,写报价行也不知道选产品可以带出标价。
  • 风险与回滚:纯文档改动,不动代码;技能文件的 token 上限余量为零,新增内容以删除三段重复示例支付(Select with Default、Email with Uniqueness、Lookup (Many-to-Many)),并把 vectorConfig 提示并入文件顶部「不要臆造键」列表,被删的规则在技能别处仍各有一处;回滚即 revert 本 PR。
  • 先看:skills/objectstack-data/rules/field-types.md(Relational 表下的 lookupAutofill 段、percent 行、三处删除),content/docs/data-modeling/fields.mdx(新小节 Lookup-pick autofill),.changeset/28256-data-skill-percent-display-lookup-autofill.md。
  • 两分钟验证:node scripts/check-skills-token-ratchet.mjs,预期退出 0,field-types.md 读数 3206/3207。
  • 需要你做的:受管面(skills/**,Tier H)——席位完成契约复审后,由授权账号 Approve,再由席位落地。

What changes

Two p3 skill-teaching cards folded into one skills/objectstack-data PR, one commit per card.

#28256 — the percent row teaches percentDisplay (skills/objectstack-data/rules/field-types.md, Numbers table). The row's Config column now lists percentDisplay — the read-only face: unset or 'bar' draws a progress bar, 'plain' the number alone, for a rate or a probability. Read against packages/spec/src/data/field.zod.ts:1597 (z.enum(['bar', 'plain']).optional(), the describe) and the renderer ui/packages/fields/src/index.tsx:978-988 (PercentCellRenderer: only the literal plain drops the bar; every read-only host draws this one renderer, the relay sites ui/packages/plugin-grid/src/objectGridGenerateColumns.tsx:358,622,815 and ui/packages/plugin-detail/src/fieldEnrichment.ts:230 copy the key).

#28246 — the Relational section teaches lookupAutofill. The lookup and master_detail rows list the key, and one paragraph under the table teaches it: the pair shape [{ target, source, overwrite? }], single-value lookup / master_detail only (field.zod.ts:2647-2667, the superRefine); a pick copies the picked record's source into this object's target, in the form and the line-item grid (ui/packages/fields/src/widgets/LookupField.tsx:1061-1085 fillDeclaredTargets, ui/packages/components/src/renderers/form/form.tsx:3984-3991 onAutofill, ui/packages/fields/src/widgets/GridField.tsx:283-300 lookupAutofillPatch); the example [{ target: 'unit_price', source: 'list_price' }] is the describe's own (field.zod.ts:2105); target is a field of this object an author writes — not the lookup, readonly or computed — named once (packages/spec/src/data/object.zod.ts:2255-2300 refuseDishonestLookupAutofill); source is a declared, non-internal field of reference the target can hold (packages/spec/src/stack.zod.ts:4156-4225 collectLookupAutofillErrors, the type rule lookupAutofillTypeMismatch at 4124); the overwrite rule — a pick fills an empty target or an earlier pick's fill, overwrite: true always replaces (ui/packages/fields/src/widgets/lookupAutofill.ts declaredLookupAutofillPatch, the describe at field.zod.ts:2106); client-side only (field.zod.ts:2107); in a grid a declared target leaves the column's same-named autofill copy (GridField.tsx:290-294).

content/docs/data-modeling/fields.mdx (the lookup section only, triage's one-PR exception, comment 6531273599): the relationship property table gains a lookupAutofill row, the code block gains a product-lookup example, and a new #### Lookup-pick autofill subsection before Referential integrity carries the same rules in full — the target rule, the source rule with the type table (same type and multiplicity; a string-valued source into a text target; number and currency into each other; a relationship only into a relationship over the same object — stack.zod.ts:4124-4150), the overwrite rule, client-side only, the grid autofill relation, and the bound that a master-detail collection whose relationship and every column are authored loads no child fields and so sees no pairs (ui/packages/plugin-form/src/MasterDetailForm.tsx:429-435; named in PR #28322's acceptance notes as the text this card should state). It links the generated Field reference, which already carries both keys.

Changeset .changeset/28256-data-skill-percent-display-lookup-autofill.md: @objectstack/skills patch, both declaration lines.

Token ceiling — paid by deletion, never by re-wrap

field-types.md sat at exactly its check-skills-token-ratchet ceiling (3207 = ceil(bytes / 4), zero headroom), so every added byte is paid inside the file. Deleted, each with the home that still carries the rule:

  • ### Select with Default (251 bytes) — restated the Selection section's own option example in this file (label, value, color, default: true).
  • ### Email with Uniqueness (329 bytes) — the unique scope and the field-level unique: true reading live in rules/indexing.md § Unique scope — always state it (ADR-0120).
  • ### Lookup (Many-to-Many) (117 bytes) — the multi-value lookup lives in SKILL.md (the Many-to-Many row and the multiple: true blockquote); its max: 10 read as a selection cap, while FieldSchema.max is the numeric bound (field.zod.ts:1532).
  • The vector paragraph (132 bytes) — its one rule, that vectorConfig is refused at parse, folds into the invented-key list in the blockquote at the top of the file (theme, rows, vectorConfig, fileAttachmentConfig); the table row and the example already say dimensions is the flat key.

No line was re-wrapped to buy room.

Readings (bytes · ceil(bytes/4) tokens · lines), base 768ec4bf → head 0984e3e9:

Surface Before After
skills/objectstack-data/rules/field-types.md 12826 B · 3207 tok · 305 lines 12824 B · 3206 tok · 273 lines (ceiling 3207)
skills/objectstack-data bundle, all 15 .md files 163627 B · 40907 tok 163625 B · 40907 tok
all skills/*/SKILL.md 226235 B 226235 B (unchanged)

Tests

Documentation only — no test file changes. The gates below are the verification; the skill's eval fixtures are unchanged (no eval case was asked) and the eval gate was run.

Acceptance notes

Verification

All readings at head 0984e3e9 (= origin/main 768ec4bf + the two commits).

  • Derived gates — node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack → 67 families (the docs gates included); every line run with stdin closed, exit codes landed on disk first, then dispatch-gates.mjs --repo objectstack-ai/objectstack --ran FILE → ✓ dispatch-gates --ran: 67 derived famil(ies) accounted for — 66 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3). 66 families exit 0, among them check-skills-token-ratchet (+ --self-test), check:skill-identifier-liveness, check:corpus-claim-drift, check:doc-authoring, check:docs-spec-enumerations, check:doc-anchors, check:docs-single-h1, check:role-word, check:pm-skill-id-lint, check:pm-governed-merges, @objectstack/spec check:docs / check:liveness / check:skill-examples (after turbo run build --filter='@objectstack/client-react...' under the verify lock), check:closing-keyword-parity, check:empty-changeset, check:changeset-no-major.
    • NOT MEASURED: pnpm check:dual-build-cjs-loads, exit 3 (PREREQUISITE NOT MET — it needs every package's dist, a full pnpm build); the diff touches no package entry. CI runs it.
  • Skill evals — pnpm --dir ui run check:skill-eval-tokens --self-test (29 cases) and pnpm --dir ui run check:skill-eval-tokens → exit 0, Every must_contain token is taught by its own skill bundle (the objectstack-data evals still find every token they grade).
  • Tier — node scripts/pm/dispatch-gates.mjs --tier on the two surfaces: claude-fable-5-1 MANDATORY (skills/**, no one-line exit); this thread runs on that tier.
  • Changed lines — 112 (+66 / −46, 3 files), under the 3000 human-merge threshold (read by --ran).

Dogfood: not run — the title carries no ! and Clause-② carries no (narrowing) arm.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (No schema touched.)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes (CI-owned repo scan; no .ts touched).
  • 4. One issue, one branch off an up-to-date main, one pull request. (Two cards, folded by the dispatch order; one branch, one PR.)
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: its generated artifacts are regenerated and committed. (Not touched.)
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.
  • The Before Submitting list in CONTRIBUTING.md is done.

Generated by Claude Code

…aches percentDisplay

The `percent` row of `skills/objectstack-data/rules/field-types.md` now lists
`percentDisplay`: unset or `'bar'` draws the progress bar, `'plain'` prints the
number alone, for a rate or a probability. The addition is paid inside the
file's token ceiling by deleting the `Select with Default` example, which
restated the Selection section's own option example (label, value, color,
default).

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibvc9vjAiGhyhnLTp34
…ofill on a lookup pick

`skills/objectstack-data/rules/field-types.md` lists `lookupAutofill` on the
`lookup` and `master_detail` rows and teaches it under the Relational table:
the pair shape, which targets a pick fills, which sources it copies, the
overwrite rule, that the fill is client-side only, and that a declared target
leaves a line-item grid column's same-named `autofill` copy. Paid inside the
file's token ceiling by deleting the `Email with Uniqueness` example (the
unique scope lives in `rules/indexing.md`), the `Lookup (Many-to-Many)` example
(the multi-value lookup lives in `SKILL.md`, and its `max: 10` is the numeric
bound, not a selection cap) and the vector paragraph, whose `vectorConfig` trap
folds into the invented-key list at the top of the file.

`content/docs/data-modeling/fields.mdx` gains the key in the relationship
property table, a product-lookup example and a `Lookup-pick autofill` section
with the same rules in full, pointing at the generated Field reference.

Changeset: `@objectstack/skills` patch.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibvc9vjAiGhyhnLTp34
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · os-tesla · session_01L9qibzCtJt8Q2Z63fyRNix

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 0984e3e91e582d31c6beeafec3703f4debe2f756
Local-runs: none
Scope: skills/objectstack-data/rules/field-types.md + content/docs/data-modeling/fields.mdx (docs review face) packages/spec/src/data/field.zod.ts (percentDisplay 1597 and its TSDoc, lookupAutofill 2092-2110, the superRefine 2641-2667, InlineGridColumnSchema.autofill 1191, max 1532, FieldSchema as strictObject 1275) packages/spec/src/data/object.zod.ts (refuseDishonestLookupAutofill 2251-2335) packages/spec/src/stack.zod.ts (lookupAutofillTypeMismatch 4110-4152, collectLookupAutofillErrors 4177-4228, called from validateCrossReferences 4986 inside buildDefinedStack 6142) packages/spec/src/data/percent-scale.ts (percentScaleOf) ui/packages/fields/src/index.tsx (PercentCellRenderer 973-988) ui/packages/fields/src/widgets/lookupAutofill.ts (whole) ui/packages/fields/src/widgets/LookupField.tsx (autofillPairs, fillDeclaredTargets 1050-1085) ui/packages/components/src/renderers/form/form.tsx (onAutofill 3980-3991; no onSelectRecord passed) ui/packages/fields/src/widgets/GridField.tsx (lookupAutofillPatch 283-300, the pick at 894-895) ui/packages/plugin-form/src/MasterDetailForm.tsx (the fully-configured branch 1257-1259, lookupAutofillByField 1358 and 1375) skills/objectstack-data/SKILL.md (41-44, 297, 310-320, 366) skills/objectstack-data/rules/indexing.md (46-80) skills/objectstack-data/evals/objects-fields-relationships.json skills/objectstack-ui/SKILL.md:102 content/docs/references/data/field.mdx (73, 98, 237) scripts/check-skills-token-ratchet.mjs (convention 44-75, ceiling row 690) .changeset/28256-data-skill-percent-display-lookup-autofill.md .changeset/config.json packages/skills/package.json

Inputs read: cards #28256 and #28246 (body and every comment, the os-dev-report 6550627372 included), PR #28487 body, file list and the net diff origin/main (768ec4bf) → refs/review/pr-28487 (two commits, 3 files, +66/−46), and the check-runs on the head. The dispatching seat's own conclusions were not an input.

① Derived judgments

  • Spec accept-set: unchanged. No file under packages/spec moves; percentDisplay (z.enum(['bar','plain']).optional(), no parse-time type restriction) and lookupAutofill (strictObject pairs, .min(1).optional(), superRefine to single-value lookup/master_detail) are exactly as published on main. Right.
  • Public declaration surface: unchanged. No export, no generated artifact, no ui/ type touched. Clause-②: no is the right reading. Right.
  • Published content of @objectstack/skills changes (packages/skills syncs skills/** into its dist, sync-catalog.mjs): field-types.md gains two taught keys and loses three example headings and one paragraph; the file is 12826 → 12824 bytes, 305 → 273 lines. A patch-level content change. Right.
  • The skill's taught key set widens by two keys that exist and are read on main: percentDisplay (renderer PercentCellRenderer draws it on every read-only host; the liveness row flipped live in PR feat(console): a percent field with percentDisplay plain shows the percentage with no progress bar #28315) and lookupAutofill (form onAutofill, LookupField.fillDeclaredTargets, GridField.lookupAutofillPatch; PR feat(fields): a lookup pick fills the fields its lookupAutofill declares, in the record form and the line-item grid #28322). Neither sentence advertises something the runtime does not deliver. Right.
  • The skill's example set narrows by three headings and the blockquote's invented-key list widens by vectorConfig. Each deletion judged in ④: no rule without another home is lost. Right.
  • content/docs/data-modeling/fields.mdx publishes nothing from a released package (docs site only; the references/ auto-gen boundary is untouched and the page links the generated /docs/references/data/field, which already carries both keys at lines 73 and 98). No meta.json change is owed (no new page). Right.
  • Governed surface: skills/** is a Tier H register row; this record is the contract-tier review of record and the landing still needs an authorized approval and every check green. The PR is draft, authored by the fleet bot from the base repo (not a fork). Right.

② Semver level

.changeset/28256-data-skill-percent-display-lookup-autofill.md: '@objectstack/skills': patch, body carries Clause-②: no and Metadata-protocol: none as line-initial declarations; the PR body carries the same pair. Kind none ⇒ patch is the level the Post-Task Checklist prescribes; nothing else in the diff publishes. No (narrowing) arm, no ! in the title, so no dogfood battery is owed. Matches what the diff publishes.

Clause-②: no

③ Boundary flags

The os-dev-report (comment 6550627372 on #28256) carries open_questions: [] — nothing to answer. Its flags, each answered:

  • NOT MEASURED pnpm check:dual-build-cjs-loads (exit 3, needs a full build). The diff touches no package entry, no .ts; CI's Lint · repo & tooling gates carries it and is still in_progress on this head. Accepted as a CI-owned reading; not a gap in the review.
  • out_of_scope_findings[0]: the percent row's When-to-Use text "Percentage values (0-100)" does not state the storage rule (percentScaleOf: a fraction unless max is above 1; scale's describe: a stored 0.25 reads 25%). Confirmed on main and pre-dating both cards. It is a metadata-authoring trap in the Prime Directive chore: version packages #10 sense — an author reading "0-100" on a fraction-stored field writes 25 and sees 2500% — so it is escalated to the seat to file as its own card (dedupe words: percent row storage, percentScaleOf skill), not left as a PR note. Not this PR's defect; not a blocker here.
  • Not-touched list (evals/*, references/_index.md under open PR feat(spec): one override declaration for code and Studio, with the .custom pairing helpers and the closed overridable-type list (ADR-0146) #28467, skills/objectstack-ui): the objectstack-ui line "autofill: false opts one column out of the typeahead copy" (SKILL.md:102) stays consistent with the new text — both describe the grid's same-named copy. Confirmed correct as is. No eval case was asked by either card; the one eval token that touches a deleted example (unique: 'organization', objects-fields-relationships.json:30) is still taught in rules/indexing.md.
  • The feat(fields): a lookup pick fills the fields its lookupAutofill declares, in the record form and the line-item grid #28322 acceptance note asking this card to state the fully-authored master-detail bound is honoured in fields.mdx (judged in ④).
  • CI families at report time: in_progress. Still true at review time — see the check-run line in ④.

No flag escalates beyond the one card-to-file above.

④ Design judgment

Check-runs on 0984e3e9 (the gate verdicts): success — Governed Surface Queue Guard, Check Changeset, Check PR Size, Build Core, Test UI, Lint · ESLint, Lint · PM dispatch-gates, the three Lint · scoped jobs, Spec property liveness, Check Documentation Links, PR-body guards, Dogfood Regression Gate, Auto Label, Plan Test Core shards, filter. in_progress — Build Docs, Lint · CI/release & engine gates, Lint · docs & code guards, Lint · repo & tooling gates (the job that carries check-skills-token-ratchet), Live E2E UI, Test Core 1/8–3/8, Type Check · consumer gates / debt ledger / source gates / workspace. skipped (path-filtered) — Build & E2E UI, Lint UI, Type Check UI, the UI shards, the opt-in harnesses. No check has failed. The ratchet and the docs guards are therefore not yet verdicts; my own reading of the tree is below.

skills/objectstack-data/rules/field-types.md — every teaching sentence against the spec and the code

  • percent row: "percentDisplay — the read-only face: unset or 'bar' draws a progress bar, 'plain' the number alone, for a rate or a probability." Values: z.enum(['bar','plain']) — exact. Default: the describe says absent = the renderer default, a bar; PercentCellRenderer returns the bare formatted span only on the literal 'plain', every other value keeps the bar — exact. Read-only face: the describe scopes it to list cells and the record page and says display only; the renderer is the one every read-only host draws — exact. The row lists the key on percent only; the spec accepts it at parse on any type (no type restriction, by the TSDoc's own words), and the row makes no refusal claim, so nothing is over-taught. Holds.
  • lookup / master_detail rows list lookupAutofill: the superRefine accepts the key on exactly those two types and refuses multiple: true; user and tree are not listed. Holds.
  • The paragraph under the Relational table, clause by clause: shape [{ target, source, overwrite? }] and "single-value lookup / master_detail" — the strictObject and the superRefine, exact. "A pick copies the picked record's source into this object's target" and the example [{ target: 'unit_price', source: 'list_price' }] — the describe's own example. "In the form and the line-item grid" — form.tsx hands onAutofill and passes no onSelectRecord, so LookupField reads the pairs in a record form; GridField applies them in the line grid. "target: a field of this object an author writes (not the lookup, readonly or computed), named once" — the five refusals of refuseDishonestLookupAutofill, exact. "source: a declared, non-internal field of reference the target can hold" — collectLookupAutofillErrors plus lookupAutofillTypeMismatch, exact (the stack pass also waives the platform's injected audit columns, which the sentence does not mention — a narrower teaching, never a false one). "A pick fills an empty target or an earlier pick's fill; overwrite: true always replaces" — declaredLookupAutofillPatch's three-way condition, exact. "Client-side only" — the describe's own clause; nothing on the write path reads it. "In a grid a declared target leaves the same-named autofill copy" — lookupAutofillPatch skips declaredTargets in the same-named loop, exact. Holds in every clause.
  • The blockquote now names vectorConfig among keys "REFUSED at parse (unrecognized_keys)": FieldSchema is a strictObject and vectorConfig appears nowhere in packages/spec/src, so the refusal is exactly the unrecognised-key one. Holds.

The three deletions and the folded paragraph — does each rule keep a home?

  • ### Select with Default (type: 'select', required, options with default: true and color): the Selection section of the same file keeps the options example with default: true and color (lines 82-87) and the table lists options (value, label, color, default). No rule lost.
  • ### Email with Uniqueness (state the scope per ADR-0120; 'organization' per-org and NULL-safe; 'global' installation-wide; field-level bare true means 'organization' and stays valid): rules/indexing.md § "Unique scope — always state it (ADR-0120)" carries all four — the two scopes and COALESCE NULL-safety at lines 53-54 and 70-74, the field-level true reading at 75-77 — and SKILL.md:366 routes "unique scope" to that file. The email row still lists unique as a config. No rule lost.
  • ### Lookup (Many-to-Many) (multiple: true, max: 10): SKILL.md:310 ("multi-value lookup (multiple: true) — an array column of ids") and the blockquote at 316-320 carry the multi-value lookup. The max: 10 line taught nothing true: FieldSchema.max is "Maximum value… checked on the WRITTEN value" and no selection-cap key exists on a lookup, so the deletion removes a misleading example rather than a rule. No rule lost.
  • The vector paragraph ("no vectorConfig block — refused at parse; dimensions is the flat key"): the refusal half now lives in the blockquote; the vector row ("dimensions (flat sibling, e.g. 1536)") and the Vector example ("flat sibling") carry the other half. No rule lost.

Re-wrap check: the only pre-existing line whose break moved is the blockquote's, and it moved to insert vectorConfig (net +bytes), not to win room; the lookupAutofill paragraph is new text; every other hunk is a whole-block deletion or a one-cell table edit. No line was re-wrapped to buy room.

Token reading (a reading of the tree, no gate run): wc -c on git show of the file — main 12826 bytes = ceil(12826/4) = 3207; head 12824 bytes = 3206; the pinned ceiling at check-skills-token-ratchet.mjs:690 is 3207. Within the ceiling by one token, as the PR states; the gate itself is still running in CI.

Attempts to break it, none landing as must-fix: (a) teaching percentDisplay on a type the renderer never draws — the row scopes it to percent; (b) a user field inheriting the key by "stored identically to lookup" — the row does not list it and the spec refuses it; (c) a master_detail with multiple: true — the paragraph says single-value; (d) the grid sentence read as "the column's autofill governs declared pairs" — the wording "leaves the same-named copy" says the opposite, matching the code.

content/docs/data-modeling/fields.mdx (docs face, the lookup section only — triage's one-PR exception, comment 6531273599)

  • The code-block example, the property-table row ("single-value lookup / master_detail only") and the subsection's target rule (not the lookup, not readonly, not formula/summary/autonumber/expression, named once, "the object's parse refuses anything else") match refuseDishonestLookupAutofill exactly. The overwrite rule and the client-side-only paragraph are the describe's own. The grid paragraph matches lookupAutofillPatch. The link target /docs/references/data/field exists and carries both keys. Holds.
  • "os validate / os build judge the source against the referenced object" — true, and the describe's own words; defineStack judges it too (validateCrossReferences runs inside buildDefinedStack), so the sentence under-states the refusal rather than mis-states it. Nit, no change required.
  • Should-fix 1 — the source type rule omits multiplicity. The page says the rules are carried "in full" and lists "the same type, a string-valued field into a text target, number and currency into each other, a relationship only into a relationship over the same object". lookupAutofillTypeMismatch's first refusal is "one holds a list and the other a single value" (a multiple: true or multiselect/checkboxes/tags source into a single-value target, or the reverse). The PR body says "same type and multiplicity"; the page says "the same type". Add "and multiplicity" (or "a list only into a list"). Nothing taught is false; one refusal is unlisted.
  • Should-fix 2 — the master-detail bound is worded looser than the code. The page: "A master-detail collection whose relationship and every column are authored loads no child fields." MasterDetailForm.tsx:1257-1259: the fetch is skipped when relationshipField is set and every column carries a type (columnsTyped); an authored column without type triggers hydration, and such a grid does see the pairs (line 1358). Say "every column carries its type" — the feat(fields): a lookup pick fills the fields its lookupAutofill declares, in the record form and the line-item grid #28322 acceptance note itself spells it "FK + every column typed". Harmless direction (an author expecting no fill gets one), so should-fix.
  • Heading level #### sits beside the existing #### Referential integrity; no tracker number appears in either hunk (check:doc-authoring); the identifiers named (percentDisplay, lookupAutofill, target, source, overwrite, autofill) all exist on main.

Out of scope, for the seat

  • The percent row's "(0-100)" storage trap (③ above) — file a card; it is the file's pre-existing text, not this PR's.

No must-fix. Two should-fixes on the docs face (multiplicity in the type rule; "every column typed" in the bound), both one-phrase edits that leave the skill file and its token count untouched.

Implemented-by: claude/project-thread-w041mp
Reviewed-by: session_01L9qibzCtJt8Q2Z63fyRNix

VERDICT: PASS

…the master-detail bound names its two conditions

Two should-fixes from the contract review of the fields page's Lookup-pick
autofill section: the source type rule now says "the same type and
multiplicity (a list only into a list)", the first refusal of the stack's
type rule; and the bound reads "whose relationshipField is set and whose
every column carries its type", the two conditions under which the
master-detail renderer skips the child-schema fetch.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibvc9vjAiGhyhnLTp34
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · os-tesla · session_01L9qibvc9vjAiGhyhnLTp34

Verification update · head 153207ee · 2026-11-01T00:01Z

One commit on top of 0984e3e9, for the contract review's two should-fixes (record 6550726537), touching content/docs/data-modeling/fields.mdx only:

  • the source type rule now reads "the same type and multiplicity (a list only into a list)" — the first refusal of lookupAutofillTypeMismatch (packages/spec/src/stack.zod.ts:4124-4150);
  • the master-detail bound now reads "whose relationshipField is set and whose every column carries its type" — the two conditions under which the renderer skips the child-schema fetch (ui/packages/plugin-form/src/MasterDetailForm.tsx:1257-1259).

skills/objectstack-data/rules/field-types.md is unchanged since 0984e3e9 (3206 of 3207 tokens).

Readings on 153207ee:

  • The 67-family sweep derived by node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack was run again in full (stdin closed, exit codes landed on disk first): 66 exit 0, 1 NOT MEASURED (pnpm check:dual-build-cjs-loads, exit 3, PREREQUISITE NOT MET — needs a full pnpm build; CI runs it). dispatch-gates.mjs --repo objectstack-ai/objectstack --ran FILE → ✓ dispatch-gates --ran: 67 derived famil(ies) accounted for — 66 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3).
  • CI on 153207ee: every check-run completed, Lint & Repo Gates, TypeScript Type Check, Test Core, Build Core, Governed Surface Queue Guard, Live E2E UI, Build Docs, Check Changeset, PR-body guards all success; the ui lanes are path-skipped.
  • Mergeable against origin/main 9b4d1933 (clean merge-tree).

The PR body's Verification section still names 0984e3e9: the relay's issue_patch refuses a body whose first line is not a seat signature, and this body's first two lines are the harness's attribution block, so the dev leaves the body as written and records the new head here.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · os-tesla · session_01L9qibzCtJt8Q2Z63fyRNix

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 153207eec637c67a3405ac550adc4b428a36d182
Local-runs: none
Scope: skills/objectstack-data/rules/field-types.md + content/docs/data-modeling/fields.mdx (docs review face) packages/spec/src/data/field.zod.ts (percentDisplay 1597 and its TSDoc, lookupAutofill 2092-2110, the superRefine 2641-2667, InlineGridColumnSchema.autofill 1191, max 1532, FieldSchema as strictObject 1275) packages/spec/src/data/object.zod.ts (refuseDishonestLookupAutofill 2251-2335) packages/spec/src/stack.zod.ts (lookupAutofillTypeMismatch 4110-4152, collectLookupAutofillErrors 4177-4228, called from validateCrossReferences 4986 inside buildDefinedStack 6142) packages/spec/src/data/percent-scale.ts (percentScaleOf) ui/packages/fields/src/index.tsx (PercentCellRenderer 973-988) ui/packages/fields/src/widgets/lookupAutofill.ts (whole) ui/packages/fields/src/widgets/LookupField.tsx (autofillPairs, fillDeclaredTargets 1050-1085) ui/packages/components/src/renderers/form/form.tsx (onAutofill 3980-3991; no onSelectRecord passed) ui/packages/fields/src/widgets/GridField.tsx (lookupAutofillPatch 283-300, the pick at 894-895) ui/packages/plugin-form/src/MasterDetailForm.tsx (the fully-configured branch 1257-1259, lookupAutofillByField 1358 and 1375) skills/objectstack-data/SKILL.md (41-44, 297, 310-320, 366) skills/objectstack-data/rules/indexing.md (46-80) skills/objectstack-data/evals/objects-fields-relationships.json skills/objectstack-ui/SKILL.md:102 content/docs/references/data/field.mdx (73, 98, 237) scripts/check-skills-token-ratchet.mjs (convention 44-75, ceiling row 690) .changeset/28256-data-skill-percent-display-lookup-autofill.md .changeset/config.json packages/skills/package.json

Inputs read: cards #28256 and #28246 (body and every comment, the os-dev-report 6550627372 included), PR #28487 body, file list and the net diff origin/main (768ec4bf) → refs/review/pr-28487 at 153207ee (three commits, 3 files, +67/−46), the delta 0984e3e9..153207ee (one commit, content/docs/data-modeling/fields.mdx only, +5/−4), and the check-runs on the new head. This record supersedes the one rendered on 0984e3e9: the skill file and the changeset are byte-identical across the two heads (blobs ee9aff8c and 109d265c), so every judgment on them is carried forward unchanged and re-affirmed here; the delta is judged in ④. The dispatching seat's own conclusions were not an input.

① Derived judgments

  • Spec accept-set: unchanged. No file under packages/spec moves; percentDisplay (z.enum(['bar','plain']).optional(), no parse-time type restriction) and lookupAutofill (strictObject pairs, .min(1).optional(), superRefine to single-value lookup/master_detail) are exactly as published on main. Right.
  • Public declaration surface: unchanged. No export, no generated artifact, no ui/ type touched. Clause-②: no is the right reading. Right.
  • Published content of @objectstack/skills changes (packages/skills syncs skills/** into its dist, sync-catalog.mjs): field-types.md gains two taught keys and loses three example headings and one paragraph; the file is 12826 → 12824 bytes, 305 → 273 lines. A patch-level content change. Right.
  • The skill's taught key set widens by two keys that exist and are read on main: percentDisplay (renderer PercentCellRenderer draws it on every read-only host; the liveness row flipped live in PR feat(console): a percent field with percentDisplay plain shows the percentage with no progress bar #28315) and lookupAutofill (form onAutofill, LookupField.fillDeclaredTargets, GridField.lookupAutofillPatch; PR feat(fields): a lookup pick fills the fields its lookupAutofill declares, in the record form and the line-item grid #28322). Neither sentence advertises something the runtime does not deliver. Right.
  • The skill's example set narrows by three headings and the blockquote's invented-key list widens by vectorConfig. Each deletion judged in ④: no rule without another home is lost. Right.
  • content/docs/data-modeling/fields.mdx publishes nothing from a released package (docs site only; the references/ auto-gen boundary is untouched and the page links the generated /docs/references/data/field, which already carries both keys at lines 73 and 98). No meta.json change is owed (no new page). The delta commit changes two phrases of this page and nothing else. Right.
  • Governed surface: skills/** is a Tier H register row; this record is the contract-tier review of record and the landing still needs an authorized approval. The PR is draft, authored by the fleet bot from the base repo (not a fork). Right.

② Semver level

.changeset/28256-data-skill-percent-display-lookup-autofill.md (unchanged by the delta): '@objectstack/skills': patch, body carries Clause-②: no and Metadata-protocol: none as line-initial declarations; the PR body carries the same pair. Kind none ⇒ patch is the level the Post-Task Checklist prescribes; nothing else in the diff publishes. No (narrowing) arm, no ! in the title, so no dogfood battery is owed. Matches what the diff publishes.

Clause-②: no

③ Boundary flags

The os-dev-report (comment 6550627372 on #28256) carries open_questions: [] — nothing to answer. Its flags, each answered:

  • NOT MEASURED pnpm check:dual-build-cjs-loads (exit 3, needs a full build). The diff touches no package entry, no .ts; CI's Lint · repo & tooling gates carries it and is now success on 153207ee. Closed.
  • out_of_scope_findings[0]: the percent row's When-to-Use text "Percentage values (0-100)" does not state the storage rule (percentScaleOf: a fraction unless max is above 1; scale's describe: a stored 0.25 reads 25%). Confirmed on main and pre-dating both cards. It is a metadata-authoring trap in the Prime Directive chore: version packages #10 sense — an author reading "0-100" on a fraction-stored field writes 25 and sees 2500% — so it is escalated to the seat to file as its own card (dedupe words: percent row storage, percentScaleOf skill), not left as a PR note. Not this PR's defect; not a blocker here.
  • Not-touched list (evals/*, references/_index.md under open PR feat(spec): one override declaration for code and Studio, with the .custom pairing helpers and the closed overridable-type list (ADR-0146) #28467, skills/objectstack-ui): the objectstack-ui line "autofill: false opts one column out of the typeahead copy" (SKILL.md:102) stays consistent with the new text — both describe the grid's same-named copy. Confirmed correct as is. No eval case was asked by either card; the one eval token that touches a deleted example (unique: 'organization', objects-fields-relationships.json:30) is still taught in rules/indexing.md.
  • The feat(fields): a lookup pick fills the fields its lookupAutofill declares, in the record form and the line-item grid #28322 acceptance note asking this card to state the fully-authored master-detail bound is honoured in fields.mdx, now in the code's own two conditions (judged in ④).
  • CI families in_progress at report time. No longer: every check-run on 153207ee is completed — see the check-run line in ④.

No flag escalates beyond the one card-to-file above.

④ Design judgment

Check-runs on 153207ee (the gate verdicts): every check-run is completed; none failed. success — Lint & Repo Gates, TypeScript Type Check, Test Core (and shards 1/8–3/8), Build Core, Governed Surface Queue Guard, Test UI, Live E2E UI, Build Docs, Check Changeset, Check PR Size, Check Documentation Links, Spec property liveness, Dogfood Regression Gate, Lint · ESLint, Lint · PM dispatch-gates, Lint · CI/release & engine gates, Lint · docs & code guards, Lint · repo & tooling gates (carries check-skills-token-ratchet), the three Lint · scoped jobs, Type Check · consumer gates / debt ledger / source gates / workspace, PR-body guards, Auto Label, Plan Test Core shards, filter. skipped (path-filtered or opt-in) — Lint UI, Type Check UI, Build & E2E UI, Build UI Deps, UI Published Artifacts, the UI and dogfood shards, Dogfood Verify CLI, Temporal Conformance, Packed-tarball smoke, 18.x harness. The commit status API still reads pending (no statuses posted; check-runs are the carriers).

The delta 0984e3e9..153207ee — content/docs/data-modeling/fields.mdx only

  • Should-fix 1 resolved. The source rule now reads "the same type and multiplicity (a list only into a list), a string-valued field into a text target, number and currency into each other, a relationship only into a relationship over the same object". lookupAutofillTypeMismatch (stack.zod.ts:4124-4152) refuses first when isList(target) !== isList(source) — a list being multiple: true or an inherently multi type (multiselect, checkboxes, tags) — and only then applies the type pairings; "a list only into a list" states exactly that refusal, and the parenthesis implies its mirror (a single value only into a single-value target), which is the same inequality. Holds.
  • Should-fix 2 resolved. The bound now reads "A master-detail collection whose relationshipField is set and whose every column carries its type loads no child fields, so it sees no pairs and keeps only the same-named copy." MasterDetailForm.tsx:1257-1259: columnsTyped is true only when d.columns is non-empty and every column has a truthy type, and if (d.relationshipField && columnsTyped) return { ...entry, status: 'ready' } — the entry is returned without lookupAutofill, which is only set on the two hydrated branches (1358, 1375). The two named conditions are the code's two conditions, in the author's own key names (relationshipField is the MasterDetailDetailConfig key; type the column key). Holds.
  • The delta re-breaks two wrapped lines to carry the new words; it touches no other sentence, no heading, no anchor, and carries no tracker number. The skill file is not in the delta, so its token reading (3206 against the 3207 ceiling) stands and the ratchet is now a green verdict rather than a reading.

skills/objectstack-data/rules/field-types.md — every teaching sentence against the spec and the code (unchanged blob; carried forward)

  • percent row: "percentDisplay — the read-only face: unset or 'bar' draws a progress bar, 'plain' the number alone, for a rate or a probability." Values: z.enum(['bar','plain']) — exact. Default: the describe says absent = the renderer default, a bar; PercentCellRenderer returns the bare formatted span only on the literal 'plain', every other value keeps the bar — exact. Read-only face: the describe scopes it to list cells and the record page and says display only; the renderer is the one every read-only host draws — exact. The row lists the key on percent only; the spec accepts it at parse on any type (no type restriction, by the TSDoc's own words), and the row makes no refusal claim, so nothing is over-taught. Holds.
  • lookup / master_detail rows list lookupAutofill: the superRefine accepts the key on exactly those two types and refuses multiple: true; user and tree are not listed. Holds.
  • The paragraph under the Relational table, clause by clause: shape [{ target, source, overwrite? }] and "single-value lookup / master_detail" — the strictObject and the superRefine, exact. "A pick copies the picked record's source into this object's target" and the example [{ target: 'unit_price', source: 'list_price' }] — the describe's own example. "In the form and the line-item grid" — form.tsx hands onAutofill and passes no onSelectRecord, so LookupField reads the pairs in a record form; GridField applies them in the line grid. "target: a field of this object an author writes (not the lookup, readonly or computed), named once" — the five refusals of refuseDishonestLookupAutofill, exact. "source: a declared, non-internal field of reference the target can hold" — collectLookupAutofillErrors plus lookupAutofillTypeMismatch, exact (the stack pass also waives the platform's injected audit columns, which the sentence does not mention — a narrower teaching, never a false one). "A pick fills an empty target or an earlier pick's fill; overwrite: true always replaces" — declaredLookupAutofillPatch's three-way condition, exact. "Client-side only" — the describe's own clause; nothing on the write path reads it. "In a grid a declared target leaves the same-named autofill copy" — lookupAutofillPatch skips declaredTargets in the same-named loop, exact. Holds in every clause.
  • The blockquote now names vectorConfig among keys "REFUSED at parse (unrecognized_keys)": FieldSchema is a strictObject and vectorConfig appears nowhere in packages/spec/src, so the refusal is exactly the unrecognised-key one. Holds.

The three deletions and the folded paragraph — does each rule keep a home? (carried forward)

  • ### Select with Default (type: 'select', required, options with default: true and color): the Selection section of the same file keeps the options example with default: true and color (lines 82-87) and the table lists options (value, label, color, default). No rule lost.
  • ### Email with Uniqueness (state the scope per ADR-0120; 'organization' per-org and NULL-safe; 'global' installation-wide; field-level bare true means 'organization' and stays valid): rules/indexing.md § "Unique scope — always state it (ADR-0120)" carries all four — the two scopes and COALESCE NULL-safety at lines 53-54 and 70-74, the field-level true reading at 75-77 — and SKILL.md:366 routes "unique scope" to that file. The email row still lists unique as a config. No rule lost.
  • ### Lookup (Many-to-Many) (multiple: true, max: 10): SKILL.md:310 ("multi-value lookup (multiple: true) — an array column of ids") and the blockquote at 316-320 carry the multi-value lookup. The max: 10 line taught nothing true: FieldSchema.max is "Maximum value… checked on the WRITTEN value" and no selection-cap key exists on a lookup, so the deletion removes a misleading example rather than a rule. No rule lost.
  • The vector paragraph ("no vectorConfig block — refused at parse; dimensions is the flat key"): the refusal half now lives in the blockquote; the vector row ("dimensions (flat sibling, e.g. 1536)") and the Vector example ("flat sibling") carry the other half. No rule lost.

Re-wrap check: in the skill file the only pre-existing line whose break moved is the blockquote's, and it moved to insert vectorConfig (net +bytes), not to win room; the lookupAutofill paragraph is new text; every other hunk is a whole-block deletion or a one-cell table edit. No line was re-wrapped to buy room.

Token reading (a reading of the tree): wc -c on git show of the file — main 12826 bytes = ceil(12826/4) = 3207; head 12824 bytes = 3206; the pinned ceiling at check-skills-token-ratchet.mjs:690 is 3207. Within the ceiling by one token, and Lint · repo & tooling gates is green on this head.

Attempts to break it, none landing as must-fix: (a) teaching percentDisplay on a type the renderer never draws — the row scopes it to percent; (b) a user field inheriting the key by "stored identically to lookup" — the row does not list it and the spec refuses it; (c) a master_detail with multiple: true — the paragraph says single-value; (d) the grid sentence read as "the column's autofill governs declared pairs" — the wording "leaves the same-named copy" says the opposite, matching the code; (e) on the new docs text, a multiselect source into a multiple: true lookup target — "a list only into a list" passes the multiplicity gate, and the relationship rule then refuses it (a record id fills only a relationship over the same object), which the page states in the next clause.

content/docs/data-modeling/fields.mdx (docs face, the lookup section only — triage's one-PR exception, comment 6531273599)

  • The code-block example, the property-table row ("single-value lookup / master_detail only") and the subsection's target rule (not the lookup, not readonly, not formula/summary/autonumber/expression, named once, "the object's parse refuses anything else") match refuseDishonestLookupAutofill exactly. The source rule, with multiplicity, matches lookupAutofillTypeMismatch. The overwrite rule and the client-side-only paragraph are the describe's own. The grid paragraph and the master-detail bound match lookupAutofillPatch and MasterDetailForm.tsx:1257-1259. The link target /docs/references/data/field exists and carries both keys. Holds.
  • "os validate / os build judge the source against the referenced object" — true, and the describe's own words; defineStack judges it too (validateCrossReferences runs inside buildDefinedStack), so the sentence under-states the refusal rather than mis-states it. Nit, no change required.
  • Heading level #### sits beside the existing #### Referential integrity; no tracker number appears in any hunk (check:doc-authoring, green); the identifiers named (percentDisplay, lookupAutofill, target, source, overwrite, autofill, relationshipField, type) all exist on main.

Out of scope, for the seat

  • The percent row's "(0-100)" storage trap (③ above) — file a card; it is the file's pre-existing text, not this PR's.

No must-fix. No should-fix outstanding: the two from the 0984e3e9 record are applied in the delta and verified against the code.

Implemented-by: claude/project-thread-w041mp
Reviewed-by: session_01L9qibzCtJt8Q2Z63fyRNix

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · os-tesla · session_01L9qibzCtJt8Q2Z63fyRNix

维护者速读

  • 改了什么:objectstack-data 技能的字段类型规则补教两个已经上线的键。percent 一行加 percentDisplay:不写或写 'bar' 只读时画进度条,'plain' 只显示数字,适合比率和概率。lookup / master_detail 加 lookupAutofill:选中一条关联记录后,把它的某个字段填进本对象的某个字段,并写清楚哪些目标、哪些来源会被拒、什么时候覆盖。文档页 content/docs/data-modeling/fields.mdx 的 lookup 一节同步加上这个键。
  • 为什么改:两个键的渲染和表单填充都已合入(PR feat(console): a percent field with percentDisplay plain shows the percentage with no progress bar #28315、feat(fields): a lookup pick fills the fields its lookupAutofill declares, in the record form and the line-item grid #28322),技能和文档还没教,AI 写元数据时不会用,或者用错。
  • 风险与回滚:只改技能文本和一页文档,不碰运行时。为了不超出这个文件的 token 上限,删了三段与别处重复的示例,复审逐条确认每条规则在别处都还有出处。回滚即 revert。
  • 先看:skills/objectstack-data/rules/field-types.md 的 Relational 表下面那段 lookupAutofill。
  • 两分钟验证:git grep -n -E "percentDisplay|lookupAutofill" skills/objectstack-data/ content/docs/data-modeling/fields.mdx。
  • 需要你做的:改的是 skills/**,属于 Tier H,需要 os-zhuang(或 hotlong)批准后才能合并。契约复审 PASS 记录 6550923529,Head-sha 153207ee。

席位意见:契约复审 PASS。第一轮提了两条文档措辞的建议修改(来源规则漏了单值和多值必须一致;主从表的边界条件),dev 已在新 head 上改好,复审重新确认。复审另外发现 percent 一行原有的「0-100」写法会误导作者(没写 max 的字段实际存的是 0–1 小数),已单独立卡 #28489,排在本 PR 之后。可以批准。

席位核对

ACCEPT · seat domain:skills#1 · head 153207ee · 2026-11-01T00:05Z

  1. Shape: Fixes #28256 and Fixes #28246, draft, base main, 3 files (field-types.md, fields.mdx lookup section only, a patch changeset for @objectstack/skills), +67/−46, one commit per card plus the should-fix commit. No spec or code path.
  2. Ruling: none on either card; triage's routes as written, including the one-PR docs exception for fields.mdx (6531273599).
  3. Contract review: PASS 6550923529 at this head (supersedes 6550726537 at 0984e3e9; both should-fixes applied and verified). Clause-②: no, Metadata-protocol: none.
  4. Gates / check-runs: dev report 6550627372 and verification update 6550888736: 67 derived families, 66 exit 0, 1 NOT MEASURED locally (check:dual-build-cjs-loads), green in CI. On head 153207ee: 44 check-runs, 32 success, 12 skipped, 0 failed or running; mergeable, clean. field-types.md at 3206 of its 3207-token ceiling, paid by deleting three duplicated examples (each rule's other home named in the review record).
  5. Accepted deviations: the PR body's verification line still names 0984e3e9; the new head is recorded in 6550888736 (the relay refuses an unsigned body patch). Out of scope: the percent row's "(0-100)" storage trap, filed as skills(objectstack-data): the percent row says 'Percentage values (0-100)' but a percent field stores a fraction unless it declares max above 1 #28489 (serial after this PR).
  6. Landing: check-governed-merges --pr 28487 reports GOVERNED, Tier H (skills/**). The seat readies the PR and the relay's governed-review leg requests the approvers. No auto-merge is armed by the seat. After an authorized approval, the seat lands it, or the approver enqueues it.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 1, 2026 00:06
@os-zhuang
os-zhuang added this pull request to the merge queue Nov 1, 2026
Merged via the queue into main with commit 1f1a369 Nov 1, 2026
45 checks passed
@os-zhuang
os-zhuang deleted the claude/project-thread-w041mp branch November 1, 2026 03:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tooling

Projects

None yet

2 participants