Repository navigation
feat(fields): a lookup pick fills the fields its lookupAutofill declares, in the record form and the line-item grid - #28322
Conversation
…m and the line grid Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzEPqxBYQbxn2d72r5
… the line grid Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzEPqxBYQbxn2d72r5
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzEPqxBYQbxn2d72r5
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzEPqxBYQbxn2d72r5
📓 Docs Drift Check
What this run could not see
Coarse fallback — 168 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
|
domain:ui#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract-review-claim: |
… censuses The relational copy-set table gains a spec row read by the lookup editor only (the grid hands its pairs through GridField's own prop, so it stays off the copy set), and the form-field channel ledger gains a runtime-only row: the pairs are object-field metadata read off the resolved field stash. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzEPqxBYQbxn2d72r5
|
domain:ui#2 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract-review-claim: |
|
domain:ui#2 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract-review-claim: |
|
domain:ui#2 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract reviewServed-tier: Inputs: card #28245 (body and all six comments: triage 6531259211, the unblock 6535248023, the seat's claim 6538361669, its two amendments 6538388719 / 6539108226 which add the two surface files and flip Clause-② to widening, the dev report 6539093606), PR #28322 (body, the docs-drift comment, the file list of 17 files, the diff of 994 lines against ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Verdict reasoning. The reader implements the describe as written, in one function both surfaces call, with the overwrite rule, the one-writer rule for the grid and the "fills nothing without the key" control each pinned and an ablation shown ten red / seven green. The two surface files outside the order are the smallest edits that let a widget write a sibling field, agreed with the seat before editing. The widening is additive and patch is right. What remains is #28246's text and the fully authored master-detail bound, both named. Implemented-by: VERDICT: PASS |
|
domain:ui#2 · os-tesla · session_01L9qibxgfM8p8t3fo6y9eFK 维护者速读
席位核对ACCEPT · head
|
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzEPqxBYQbxn2d72r5
|
domain:ui#2 · os-tesla · session_01L9qibxgfM8p8t3fo6y9eFK 席位核对ACCEPT (delta) · head
|
|
domain:ui#2 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract-review-claim: |
|
domain:ui#2 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract reviewServed-tier: Inputs: PR #28322 (the seat's 席位核对 ACCEPT (delta) 6545070006 on this head: merge to clear the conflict in the generated state-counts file, then ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Verdict reasoning. The only change since the reviewed head is a conflict-free merge of Implemented-by: VERDICT: PASS |
Requested by Tesla · project thread
Fixes #28245
Clause-②: yes (widening:
@object-ui/fieldsgains the widget proponAutofill, theGridFieldproplookupAutofill, an optional fourth argument onlookupAutofillPatch, and the exportsdeclaredLookupAutofillPatch,lookupAutofillByField,LookupAutofillPair,LookupAutofillByField;@object-ui/typesLookupFieldMetadatagainslookupAutofill; measured on the built declarations; no existing member, key or shape changes)Metadata-protocol: none
维护者速读
lookupAutofill立刻把对应字段填进表单;明细行网格里选产品时同样生效(例如报价明细选产品,单价立即带出产品的标价),不再要等保存后服务端钩子才填。lookupAutofill,但界面上没有任何读取方,作者写了也不生效;本 PR 是它的 ui 一半,并把 liveness 行从 planned 改为 live。overwrite: true才覆盖。没有声明该键的查找字段行为不变。网格里从「浏览全部」弹窗选记录现在也会走同名字段复制(之前只写 id)。回滚即 revert 本 PR。ui/packages/fields/src/widgets/lookupAutofill.ts(填充规则,唯一一处),ui/packages/fields/src/widgets/LookupField.tsx的fillDeclaredTargets与handleDialogSelect,ui/packages/fields/src/widgets/GridField.tsx的applyLookupSelection。pnpm --dir ui exec vitest run packages/plugin-form/src/ObjectForm.lookupAutofill-28245.test.tsx packages/fields/src/widgets/GridField.lookupAutofill-28245.test.tsx,预期 2 个文件 17 passed。What changes
FieldSchema.lookupAutofill([{ target, source, overwrite? }]on a single-valuelookup/master_detail, declared by #28244) gets its reader in the record form and in the line-item grid.ui/packages/fields/src/widgets/lookupAutofill.ts#declaredLookupAutofillPatch. Eachtargettakes the picked record'ssource. Withoutoverwrite: truea target is filled only while it is empty or still holds the value the earlier pick filled in (the record the lookup held from an earlier pick, read at the samesource); a value the user typed, or one the record already stored, stays.overwrite: truereplaces on every pick. Asourcethe picked row does not carry (not served to this reader) writes nothing.lookupAutofillByFieldreads an object's pairs by lookup field name, single-value relationships only.LookupField#fillDeclaredTargetsruns on every single-value pick path the form owns: the dropdown, the keyboard, quick-create and the browse-all dialog (the dialog commits its id throughonChange, then hands the row toonSelectRecords, where the fill runs). Current values are the host's live record (dependentValues); the earlier-pick memory is the widget's ownpickedRecords, so it is local to the form instance. The writer is a new widget prop,onAutofill(patch), declared onFieldWidgetComponentPropsand added totoHostProps' host-plumbing list.ui/packages/components/src/renderers/form/form.tsxhandslookup/master_detailwidgetsonAutofill, which writes each target withform.setValue(..., { shouldDirty: true })(re-validating a target already in error), and passes it throughstripRegisteredFieldPropsfor those two types only. Nothing in the submit or save path changes.GridFieldtakes the child object's pairs through a new host prop,lookupAutofill(anInlineGridColumnSchemacolumn is spec-strict and carries no field definition).MasterDetailFormkeeps them on each resolved entry besidesortField;LineItemsPanelderives them from the child schema it already loads.lookupAutofillPatchapplies a declared pair whatever the column'sautofillsays and takes its target out of the same-named copy, so a target has one writer per pick. The earlier-pick memory is a per-grid map keyed by column and the row's own lookup value, so adding, removing or reordering rows cannot misattribute it.LookupField#handleDialogSelectholds back the dialog's bare-idonSelectwhen the host owns the pick (onSelectRecord), andhandleDialogSelectRecordshands the host the record once, so the grid's autofill runs on that path too. Clearing and multi-value picks commit throughonChangeas before. The PeoplePicker path is unchanged.LookupFieldMetadata.lookupAutofillis typedSpecField['lookupAutofill'].packages/spec/liveness/field.jsonlookupAutofill(and its three children) movesplanned→live, citing the read points above and the two test files..changeset/28245-lookup-autofill-fill.md,patchon the four@object-ui/*packages and@objectstack/spec(the ledger ships in itsfiles[]).Tests
ui/packages/plugin-form/src/ObjectForm.lookupAutofill-28245.test.tsx— end to end throughObjectForm(schema with the pairs → form renderer →LookupField): a pick fills an empty target and the filled value is saved; a typed value is kept while anoverwrite: truepair replaces its own; a value an earlier pick filled is replaced by the next pick and kept once the user edits it; a browse-all dialog pick fills; control: a lookup without the key fills nothing, not even a same-named field.ui/packages/fields/src/widgets/GridField.lookupAutofill-28245.test.tsx— the pure patch (empty target from the declared source rather than the same-named field; a written value kept because the declared target leaves the same-named copy;overwrite: true; earlier pick replaced; applies underautofill: false; control: no pair = the same-named copy, unchanged) and the grid itself (a cell pick fills the row and the next pick replaces it; a browse-all pick reaches the grid once and fills; a stored line's own price is kept).0d887457,node scripts/ablation-replace.mjs): the write indeclaredLookupAutofillPatch(patch[target] = picked[source]) replaced by a no-op → 10 failed | 7 passed of the 17 (every fill case red; the keep and control cases stay green, as they should); restored, blob equal to HEAD,git diff HEADempty.lookupAutofill={{ product: [{ target: 'unit_price' }] }}grid prop in@object-ui/plugin-form, compiled against the rebuilt@object-ui/fieldsdeclarations →TS2741: Property 'source' is missing; file removed.Acceptance notes
ui/packages/plugin-form/**as the form renderer, but the record form's state lives inui/packages/components/src/renderers/form/form.tsx(react-hook-form), and no widget could write a sibling field. That file carries only the writer prop and itsstripRegisteredFieldPropspass-through.ui/packages/types/src/field-types.tscarries only the by-referencelookupAutofilldeclaration.MasterDetailFormskips the child-schema fetch for an entry whose relationship and every column are authored, so such a grid sees no pairs and fills only the same-named copy. Loading the child schema for every such entry is a fetch-policy change outside this card. Derived, hydrated andLineItemsPanelgrids are covered.sanitizeFormData(stripUnwritablewithcanEdit): the form's save drops a field the caller may not edit, so a fill into such a target never reaches the server. The fill does not check field-level security itself.skills/**(governed, Tier H), so riding along would make this PR Tier H; its card already carriesBlocked-by: #28245. Its text should also state the fully authored master-detail bound above.Verification
All readings at head
12601c27dunless a step names another commit.pnpm --dir ui exec vitest run packages/fields/src/widgets/GridField.lookupAutofill-28245.test.tsx packages/plugin-form/src/ObjectForm.lookupAutofill-28245.test.tsx→ Test Files 2 passed (2), Tests 17 passed (17).pnpm --dir ui exec vitest run packages/fields/ packages/plugin-form/ packages/components/src/renderers/form/ packages/types/(underos-verify-lock, at0d887457; later commits touch only the liveness ledger and changeset) → Test Files 973 passed | 1 skipped (974), Tests 17791 passed | 25 skipped (17816), VERDICT command-exit 0.pnpm --filter @object-ui/types --filter @object-ui/fields --filter @object-ui/components --filter @object-ui/plugin-form run type-checkagainst the rebuilt@object-ui/plugin-form^...closure (full declarations) → VERDICT command-exit 0.ui/packages/fields/dist/widgets/types.d.tscarriesonAutofill,dist/widgets/GridField.d.tsthelookupAutofillprop and the fourthlookupAutofillPatchargument,dist/widgets/lookupAutofill.d.tsthe new exports;ui/packages/types/dist/field-types.d.tscarrieslookupAutofill. Additions only.eslint --no-inline-configover the 12 changedui/source and test files → 0 errors.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, every line run with stdin closed, exit codes landed on disk first, reconciled with--ran:70 derived famil(ies) accounted for — 69 run, 1 NOT-MEASURED, exit 0. Every run family exits 0, includingcheck:liveness,check:generated,check:sdui-manifest(+--self-test),check:type-check-debt,check:test-source-alias,check:cross-package-test-inputsandpnpm --dir ui exec vitest run scripts/.check:livenessfirst went red on the generatedliveness/state-counts/field.mdshard (planned 9 → 6, live 100 → 103); regenerated withgen:liveness-countsin12601c27d, then green.check-plugin-teardown-shape --self-testfirst answered exit 3 on the shallow clone; after fetching its pinned fixture commit, 48 cases pass, exit 0.check:dual-build-cjs-loads, exit 3 (PREREQUISITE NOT MET — it needs every package'sdist, a fullpnpm build); the diff touches no@objectstack/*package entry. CI runs it.pnpm --dir ui run check:new-line-citations→ 0 new citations, exit 0.Human Minimum
packages/specholds no runtime logic.pnpm lintpasses.main, one pull request.packages/spectouched: its generated artifacts are regenerated and committed.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.Generated by Claude Code