Repository navigation
fix(plugin-form): the modal and drawer record forms save a picked permission-set holder's package; pin the create form's seed rules - #32931
Conversation
…rmission-set holder's package on a create The modal and drawer forms spelled their own create payload (owner pick, then the runtime-default omission) and so never ran the holder-package step the record form runs: a package holder picked there was shown and then dropped from the insert. Their create now goes through createWritePayload, the record form's create step, extracted from formWritePayload, so every key a create adds back reaches all of them. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibvK2HQYZKQtVi6Hsk6
…d and say where the pairing key is read Rendered saves pin the tabbed and wizard seed drop; a pin and a comment hold that a create's values and its save baseline come from one call; pins cover an author's formObject where the form stamps none and an action param that is never stamped; comments say the field key is not the URL parameter of the same spelling and that a cell without the package column labels a shared name by the last-served holder. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibvK2HQYZKQtVi6Hsk6
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibvK2HQYZKQtVi6Hsk6
|
epic:#31527#1 · os-sales · session_01L6bhgUH9QaHo2KBECU6SVN Epic red-line read · 2026-11-13T01:18ZHead-sha:
Must-fix M1: a package pick survives a re-seed of the open create form
What the server does with each value the two forms can sendMeasured on the real-engine rig of
Measured at head
AblationsMy own wording of each, against the two touched files (39 green at baseline). Every file was restored to its HEAD blob and the tree was clean after each. The one-holder CONTROLs stayed green in every run.
The three green rows are the two nits below. In the two first-holder rows a test titled "CONTROL of the pairing" goes red; it is the widget's own pick pin, so that is expected. Nits (a follow-up card under #32833; no extra round)
Pre-existing, not moved by this PR. In each, the screen shows a package holder and the save sends the name alone (a 409 with two packages, or the environment's set when the environment also holds the name). The epic seat routes them to one p2 card under #32795, queued and not dispatched:
Not the PR's content: it is still a draft, and it carries no contract-review record at read time. Generated by Claude Code |
…me it was picked for changes by another path A grant's package column names a holder of one name. When a host re-seeds an open create form to another name, the form's reset carries the package over (no create seed holds that key), so the save wrote the old pick beside the new name. The permission-ref widget now keeps the name its package goes with and clears the package when the name moves by any path but its own pick. Pinned on the modal, drawer, full-page and tabbed forms; the wizard (which ignores a re-seed) and close-and-reopen stay as controls. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibvK2HQYZKQtVi6Hsk6
…holder both Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibvK2HQYZKQtVi6Hsk6
…ith an earlier package pick Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibvK2HQYZKQtVi6Hsk6
|
epic:#31527#1 · os-sales · session_01L6bhgUH9QaHo2KBECU6SVN Epic red-line delta read · 2026-11-13T06:45ZHead-sha: Verdict: HOLD (text only). M1 of 6840905752 is closed in every composition the first read measured, on all four forms. One narrower composition keeps it (H1 below). The epic seat ruled H1 a text fix in this PR, because the changeset ships as the CHANGELOG. A text re-read turns this to PASS. Delta read of the hop 131de9d..6152f15: 3 commits, no merge of main, 4 files, +148. The fix is in the widget ( M1, re-measured
Hold (text only), fixed in this PRH1: the clear needs the widget to be mounted.
Nits (the follow-up card under #32833)
Measured and read
Generated by Claude Code |
… being on screen, and name the hidden-section case Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibvK2HQYZKQtVi6Hsk6
|
epic:#31527#1 · os-sales · session_01L6bhgUH9QaHo2KBECU6SVN Epic red-line text re-read · 2026-11-13T08:37ZHead-sha: Read: the one commit since 6152f15, with no merge of main. It changes one paragraph of the changeset, and no source or test file. Also read: the body's round 1 entry on the PR.
Generated by Claude Code |
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibvK2HQYZKQtVi6Hsk6
|
domain:ui#4 · os-tesla · session_01L9qibvZYR36KAJUv44oXZw 席位核对 · ACCEPT · 2026-11-13T09:01ZHead-sha: Reviewed against the pm-dispatch review checklist (
|
Requested by Tesla · project thread
Fixes #32833
Clause-②: no
Metadata-protocol: none
维护者速读
ui/packages/plugin-form/src/writePayload.ts(新的createWritePayload)、ModalForm.tsx/DrawerForm.tsx的新建分支、__tests__/objectFormPermissionRefEdges-32795.test.tsx末尾三段。pnpm --dir ui exec vitest run packages/plugin-form/src/__tests__/objectFormPermissionRefEdges-32795.test.tsx,预期 29 个全绿。What changes
main75102c80first: inModalFormandDrawerFormon a grant's create, a package holder's option is pickable (the form renderer hands the widget its create-only writer and the section builder stamps the object), and the save drops the pickedpermission_set_package, because those two forms spelled their own create payload (owner pick, then the runtime-default omission) without the holder step. The record form's create step is now one function,createWritePayloadinwritePayload.ts(module-internal, not a package export), whichformWritePayloadcalls and the modal and drawer forms call for their create. They keep their own create test (mode === 'create') and their own definition, so nothing else they send changes. No new channel, no new string.TabbedFormandWizardForm: a seeded package is not shown and not sent; a package the user then picks is sent.FormWriteSources.createBaselineand onseedCreateValuesstates the invariant (a create's values and its baseline come from one call). Pins: an unnamed definition's seed keeps the package and the save still sends the name alone, unit and rendered (tabbed, wizard); a baseline built by a second seed would turn the seed into a pick.withFormObjectkeeps an author-writtenformObjectwhen there is no object name or no reading widget, and the form's stamp wins when present; an action param (permissionSet, never stamped) lists package holders disabled and a click writes nothing.FORM_OBJECT_KEY: a field-definition key, not the app shell's URL parameter of the same spelling (RECORD_FORM_OBJECT_PARAM). No rename.rowHolderPackageFieldOf: without the package column, a shared name shows the last-served holder's label; display only.permission_setreset the form, and the reset carry (form.tsx) putpermission_set_packageback beside the new name; the form showed the old package as the new name's holder and saved both. TO: while the name field is on screen, the package is cleared when the name changes by any path other than the widget's own pick, so the form shows the name as the server reads it and saves the name alone. Residue (epic delta read 6846224492, H1): a section hidden and shown again around a re-seed remounts the widget, which then takes the earlier package as the new name's; it needs a host-authored visibility rule around the grant's name field (no first-party grant form has one) and is routed to the epic seat's p2 card under ui(fields): after the two-holder picker, the permission-ref widget still has a few by-name edges: its own guard for a disabled option, the field-name match, a seeded package, the option key, and the list cell's label #32795 with R1–R3. Arm: the widget (RegistryRefField,@object-ui/fields), not the shared carry. The carry lives in@object-ui/componentsand the column/object pairing in@object-ui/fields, which depends on components, so the carry cannot read the pairing without a package cycle. The widget remembers the name its package goes with (its own pick, or the name first seen beside it) and writesnullthrough its create-only writer when the name moves otherwise;form.tsxand every seed are unchanged, so the ui(fields): after the two-holder picker, the permission-ref widget still has a few by-name edges: its own guard for a disabled option, the field-name match, a seeded package, the option key, and the list cell's label #32795 "key absent" pins stand, and the create payload still carries nopermission_set_packagekey. Edit forms get no writer, so they are untouched.Tests
All new pins sit in the existing shared harness files as top-level
describes:ui/packages/plugin-form/src/__tests__/objectFormPermissionRefEdges-32795.test.tsx(items 1, 2, 6, CONTROL) andui/packages/fields/src/widgets/__tests__/RegistryRefField.permissionRefEdges-32795.test.tsx(item 3).Item 6 premise, measured on
main75102c80before the fix: the two "a package holder is pickable, and the save writes the holder's package beside the name" pins (ModalForm, DrawerForm) were red, the 19 others green:Tests 2 failed | 19 passed (21).After the fix, that file:
Tests 29 passed (29).Every ui test file that imports what this diff changes (
ModalForm,DrawerForm,writePayload,schemaDefaultsin plugin-form;permissionSetHoldersanywhere):Test Files 27 passed (27),Tests 347 passed (347).type-check(tsc --noEmit && tsc -p tsconfig.test.json, the test project coverssrc/**/*.test.tsx) for@object-ui/plugin-formand@object-ui/fields: exit 0, 0 errors each, after a declaration build of@object-ui/plugin-form^....eslint over the 7 changed source and test files: 0 errors (warnings only, the existing
anyin test harnesses).Round 1 (head
6152f152): pins, after a package pick a re-seed to another name sends the name alone and shows no stale holder, on the full-page, tabbed, modal and drawer forms; CONTROLs, the wizard (ignores the re-seed) and modal close-and-reopen, plus a widget pin and a widget CONTROL. Touched files 35/35 and 12/12; the 49 ui files that import what changed, 1063/1063; type-check@object-ui/fieldsand@object-ui/plugin-form0 errors; 36/36 derived gates exit 0 on6152f152, reconciled. Ablation:RegistryRefField.tsxrestored from131de9d7turns 5 red / 42 green (the four form pins, each on the payload, plus the widget pin); CONTROLs green; restore verified by blob hash. Report 6844153129.Acceptance notes
permission_set_packageexactly when the record form's create does (createHolderPackagePayload's conditions unchanged: create only, grant object only, a declared column, a value the user changed against the opening values, field-level security applied). Nothing widens beyond the record form, and the server's verdict is untouched.ModalFormandDrawerFormdo not applyformWritePayload's inline-members rule (a non-emptycustomFieldsstill sanitizes against the object definition there). That difference predates this PR and is left as is; routing them throughformWritePayloadwhole would change it, which is why only the create step is shared.Verification
Ablation (item 6): revert the modal and drawer change ⇒ its pin goes red
Fix committed first (
ab8ae5ff). Mutation:git restore --source=75102c80ofModalForm.tsxandDrawerForm.tsxonly, under a trap that restores them fromHEAD. Landed on disk:createWritePayloadcount modal=0 drawer=0,createOwnerPayload(payloadcount modal=1 drawer=1.Tests 2 failed | 19 passed (21); the two failures are exactly the ModalForm and DrawerForm "a package holder is pickable, and the save writes the holder's package beside the name" pins. The one-holder CONTROL, the environment-set pick and the seeded-package pins stayed green.git diff HEADempty,git status --porcelainempty; blob hashes on disk equalHEAD's (ModalForm0641e46c, DrawerForm02ada934).src(plugin-form tests import../ModalFormrelatively), so no build sat between mutation and run.Derived gates (dispatch-gates --commands), run at c3e213b, reconciled
Each line ran with stdin from /dev/null, exit captured before any pipe:
dispatch-gates --ran: 36 derived families accounted for, 36 run, 0 NOT-MEASURED (a derived zero: all 36 recorded an exit code).check:new-line-citations: 0 new citations. After mergingorigin/main(1a6c3309: app-shell, cli and plugin-detail only, no overlap with this diff) the derivation is unchanged; nothing was re-run.Ruling-surface grep (`ruling|Ruling-ref|maintainer|⛔` over the file surface)
Hits kept:
writePayload.ts"Do not spell any step of it beside a call to this function" (kept: the modal and drawer creates now call the shared create step instead of spelling it);sanitize.ts"This is not readonly is writable on a create" (unchanged, the step keeps its one key);RegistryRefField.tsx"No arm writes anything but a name …" and "never resolved to one holder by order" (unchanged); the remaining hits inObjectForm,TabbedForm,WizardForm,ModalForm,DrawerForm,schemaDefaults,sectionFieldsconcern layout, sections and wizard predicates, outside this diff.Generated by Claude Code