Skip to content

test(plugin-form,console): pin the member shape of object-form's initialValues, initialData and submitBehavior - #9759

Merged
os-sales merged 1 commit into
mainfrom
claude/issue-8071-member-pins-batch-1
Sep 18, 2026
Merged

os-sales merged 1 commit into
mainfrom
claude/issue-8071-member-pins-batch-1

Conversation

@os-sales

Copy link
Copy Markdown
Collaborator

Refs #8071

Clause-②: no

objectui#8071 slice 10. Three object-form keys leave MEMBER_PIN_EXEMPTIONS and gain real per-block member pins. MEMBER_PIN_EXEMPTION_CEILING follows 31 -> 28 in the same commit, as every slice of that card owes.

⚠️ First: the dispatch's own premise was FALSIFIED, and the card body is the reason

This batch was dispatched to convert the three "near-miss" keys the card body flags — object-form.fields, object-grid.exportOptions, object-grid.bulkActions / bulkActionDefs — plus page:accordion.items, page:tabs.items, record:path.stages, record:alert.action, element:button.action as follow-on candidates.

All nine have been pinned since slices 1-7. Measured on origin/main f73521f93 by TypeScript AST walk of the constant, not by reading the card:

MEMBER_PIN_EXEMPTIONS on origin/main = 31, in five blocks
  object-grid.                15
  object-form.                 7
  object-master-detail-form.   6
  object-kanban.               2   (held: `dataSource` is not a block member at all)
  record:related_list.actions  1   (NO_READ_SITE_TO_PIN — a different debt)

The card body's enumerated 58-key list has been stale since PR #8737 merged on 2026-09-09, and issuecomment-5599611864 already recorded that the authoritative list is the constant, never the card's prose. It has now mis-aimed a dispatch a second time. That note is repeated inside the constant's own docblock in this PR so the next dispatcher reads it where the numbers live.

⇒ the batch was re-selected from the live table. object-kanban (held on a stated question) and record:related_list.actions (a different debt) were left alone; object-form was taken because it is the smallest of the three open multi-key blocks.

What was converted, and the renderer read pinned for each

object-form.initialValues and object-form.initialData — NEW file

packages/plugin-form/src/__tests__/objectFormInitialMembers-8071.test.tsx

Read site: ObjectForm.tsx, the two sites that seed initialData state (the inline-fields effect and the create branch of fetchInitialData), both spelled setInitialData(schema.initialData || schema.initialValues || {}).

  1. a MEMBER is a FIELD NAME and its value is that control's opening value;
  2. ⭐ the two keys are chosen between as whole objects||, never a merge. With both authored, every initialValues member is dropped, including the ones initialData says nothing about. Nothing declared distinguishes that from { ...initialValues, ...initialData }, which is what "alternate spelling … read FIRST" reads like and what a per-member precedence would mean;
  3. the sharp edge: || tests the OBJECT's truthiness, so an empty initialData shadows a populated initialValues completely;
  4. non-vacuity control: with neither key authored the same controls render empty.

Both keys are registered type: 'object' and typed Record<string, any>, so every object parses on both declared sides — the read site is the whole member contract.

object-form.submitBehavior — EXISTING file, GROWN before it was registered

packages/plugin-form/src/ObjectForm.submitBehavior.test.tsx

This is a genuine near miss, unlike two of the three the card named at slice 1: its whole subject is the key and it drives the real renderer. But it left two members of the read unasserted, so it was strengthened rather than credited as found.

Read site: ObjectForm.tsx's switch (behavior.kind). Every member is touched once there — url and delayMs ?? 0 on redirect, title and message on thank-you (both guarded on the discriminant, so no other arm reaches them), and nothing at all on continue or next-record.

  • delayMs is a MEMBER, not a constant. Collapsing behavior.delayMs ?? 0 to a bare 0 removes the readable pause without changing any destination, so every other row in this file and in ObjectForm.submitRedirect.test.tsx stays green. Pinned on the number the wait is armed with, with the same declaration minus the member as the lit control — without it, an empty armed would be indistinguishable from a spy that never sees this renderer's timers.
  • next-record is a declared arm that reads NO members. It shares the confirmation-panel body with thank-you by falling through, so an edit giving it its own arm or dropping it into continue's no-op would leave a spec-legal authored value doing something else with nothing red.

Ablations — every pin shown FAILING, restore proven by blob

Each from the committed state, trap … EXIT INT TERM, restore by git checkout HEAD -- path (never a bare checkout), on-disk proof read before any result.

leg mutation anchor before/after result
1 schema.initialData || schema.initialValues || {}{ ...(schema.initialValues ?? {}), ...(schema.initialData ?? {}) } (the plausible "improvement") 2 → 0, injected 2 exit 1 — rows 3 and 4 red, rows 1/2/5 green by construction
2a delayMs: behavior.delayMs ?? 0delayMs: 0 1 → 0, injected 1 exit 1 — the delayMs row red, 6 passed
2b case 'next-record' moved into continue's no-op arm verified at :1105-1106 exit 1 — the next-record row red, 6 passed

Blobs: HEAD 42b7a9b0855c5a53276af4f1d9b3842ad6a0899d; mutated 30e2c464… / e50a99be… / ce8521db…; restored 42b7a9b0… = the HEAD blob in all three legs, git diff HEAD empty each time. Restored runs: pin A 5 passed, pin B 7 passed.

⚠️ Rows 1, 2 and 5 of pin A survive leg 1 by construction and the file says so — a merge and a || agree when only one key is authored. Not every row is presented as if it reddened.

Exemption ledger — measured by AST, not by arithmetic

TypeScript AST walk (ts.createSourceFileVariableDeclaration → initializer unwrapped through as / satisfies / parens → direct PropertyAssignment children):

                                origin/main (f73521f93)   this branch
MEMBER_PIN_EXEMPTIONS                    31                   28
MEMBER_PIN_EXEMPTION_CEILING             31                   28
MEMBER_PINS                              60                   63
NEWLY_JUDGED_UNPINNED_MEMBERS             2                    2

Exemptions −3, pins +3 — they move together, so the three keys were converted, not dropped. Instrument control: nonPropertyAssignmentMembers is [] on both trees, so no spread, getter or shorthand hides inside a span the counter treats as flat.

object-form is not closed: customFields, dataSource, mobile and sections remain — the two-bite shape slice 9 said every remaining block now has. NEWLY_JUDGED_UNPINNED_MEMBERS and record:related_list.actions are untouched by this slice.

Gates — exit codes captured to disk before any pipe

gate exit
apps/console registry-inputs-spec-parity.test.ts (the gate this card serves) 0 — 199 passed
pnpm --filter @object-ui/console test 0 — 101 files, 1158 tests
pnpm --filter @object-ui/plugin-form test 0 — 93 files, 902 passed / 1 skipped
pnpm --filter @object-ui/plugin-form type-check 0
pnpm --filter @object-ui/console type-check 0
pnpm --filter @object-ui/plugin-form lint · @object-ui/console lint 0 · 0 (0 errors)
the repo's 61 check:* scripts 60 × 0, one caveat below
check-governed-queue-guard.mjs --test over the four changed paths 0 — NOT GOVERNED, so no maintainer-summary section is owed

⚠️ check:node-esm-load exited 1 on the first run, and it is not this diff. Its own provenance leg refused 2 of 37 entries because turbo's shared cache replayed artifacts built by a different worktree (/home/user/objectui-issue-9659@object-ui/auth, @object-ui/react-runtime), neither of which this diff touches. The script names that condition and prescribes the remedy itself; re-run with its own --force-build: exit 0, 37 of 37 gradable entries built by this tree.

Typecheck coverage proven, not assumed: tsc -p tsconfig.test.json --listFiles lists both ObjectForm.submitBehavior.test.tsx and objectFormInitialMembers-8071.test.tsx, so the greens are not the excluded-tests trap.

Control bytes: grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' over all four changed files — no match (and check:control-bytes exit 0).

Changeset grade — MEASURED from a build, with both controls

Empty-frontmatter changeset. Both packages were built and their published files[] output grepped:

marker corpus hits
objectFormInitialMembers packages/plugin-form/dist 0
Saved — next one, from initialValues packages/plugin-form/dist 0
MEMBER_PIN_EXEMPTION_CEILING, AWAITING_A_PIN apps/console/dist 0
submitBehavior (positive control) packages/plugin-form/dist 7 files
object-form (positive control) apps/console/dist 12 files

Two zeros with a lit control on the same instrument and corpus. One near-miss chased down: registry-inputs-spec-parity matches 1 file in apps/console/dist, and it is vendor-objectstack-*.js@objectstack/spec's own prose naming this gate file, not a byte of this diff.

Serial constraint — re-taken immediately before the first write

Enumerated over the 11 open PRs (the dispatch's table listed 3; this lane's tables have been short before). Intersection at FILE level: empty. Nothing open touches apps/console/src/__tests__/registry-inputs-spec-parity.test.ts or any packages/plugin-form/src/**. PR #8941 touches packages/plugin-form/package.json only. Root package.json is held by PR #9752 in the queue — this batch does not need it.

Out of scope — handed back, not filed and not fixed

  • The 233 array/object inputs on blocks with no spec contract entry at all are explicitly not this card. Nothing here widens into them.
  • ⚠️ An empty initialData silently discarding a populated initialValues is pinned as the renderer's behaviour and deliberately not endorsed. Changing it is a renderer change, and Clause-② for this batch is no — so it is reported to the dispatching seat rather than filed or repaired here.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UanLVj6xvbS6puBCewLr8L


Generated by Claude Code

…nitialValues`, `initialData` and `submitBehavior`

objectui#8071 slice 10. Three keys leave `MEMBER_PIN_EXEMPTIONS` and gain real
per-block member pins; `MEMBER_PIN_EXEMPTION_CEILING` follows 31 -> 28 in the
same change.

`initialValues` / `initialData` get a new file: the two keys are chosen between
as whole objects (`schema.initialData || schema.initialValues`), so with both
authored every `initialValues` member is dropped rather than merged, and an
empty `initialData` shadows a populated `initialValues` entirely. The plausible
`{ ...initialValues, ...initialData }` spelling reds rows 3 and 4.

`submitBehavior` already had a file whose subject is the key and which drives
the real renderer, but it left two members of the read unasserted, so it was
grown before being registered: `delayMs` is pinned on the number the redirect
wait is armed with (with the same declaration minus the member as the lit
control), and `next-record` on reaching the confirmation panel rather than
`continue`'s no-op.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UanLVj6xvbS6puBCewLr8L
@github-actions

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 1 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/8067-component-input-member-kind.md

  • names apps/console/src/__tests__/registry-inputs-spec-parity.test.tsapps/console/src/__tests__/registry-inputs-spec-parity.test.ts — edited by this change

    A registration's type: 'array' said a value was a list and stopped there, so a member that drifted from @objectstack/spec was invisible to every layer that reads a declaration. page:header.actions is the measured cost: the contract declares z.array(z.string()) ("Action IDs"), the renderer read the members as ActionDef objects, and the repo-wide parity gate in apps/console/src/__tests__/registry-inputs-spec-parity.test.ts stayed green for the whole life of the drift because both sides carried the key and neither could say what was inside it. What settled it was a maintainer ruling, not a test — and even after the fix, "these are ids" survived only as English in the registration's description.

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Compared the checked-out tree with 2414e3751 (merge-base with origin/main): 3 file(s) changed outside .changeset/, read against 1135 pending declaration(s) that publish a body (1656 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3049.2 KB 3104.5 KB
Main entry chunk (gzip) 145.7 KB 350 KB
Entry file index-DnyzLIkC.js
Status PASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.69KB 6.21KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 545.93KB 130.69KB
core (index.js) 8.94KB 3.59KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 215.98KB 59.97KB
fields (index.js) 249.27KB 62.92KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.22KB 2.26KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 32.15KB 10.49KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.83KB 10.95KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.26KB 1.99KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 14.81KB 3.63KB
plugin-calendar (index.js) 49.92KB 14.22KB
plugin-charts (index.js) 71.49KB 19.99KB
plugin-chatbot (index.js) 195.34KB 46.51KB
plugin-dashboard (index.js) 131.44KB 34.65KB
plugin-designer (index.js) 215.94KB 44.33KB
plugin-detail (index.js) 253.29KB 65.88KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.71KB 34.16KB
plugin-gantt (index.js) 167.62KB 41.26KB
plugin-grid (index.js) 212.64KB 57.91KB
plugin-kanban (index.js) 48.10KB 14.94KB
plugin-list (index.js) 112.73KB 27.69KB
plugin-map (index.js) 21.48KB 6.99KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.41KB 11.93KB
plugin-timeline (index.js) 30.07KB 8.74KB
plugin-tree (index.js) 10.58KB 3.72KB
plugin-view (index.js) 85.04KB 21.01KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 104.82KB 34.67KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.66KB 2.50KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 14.82KB 4.99KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 14.04KB 5.36KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-sales
os-sales marked this pull request as ready for review September 18, 2026 02:52
@os-sales
os-sales added this pull request to the merge queue Sep 18, 2026
Merged via the queue into main with commit 56762e7 Sep 18, 2026
38 checks passed
@os-sales
os-sales deleted the claude/issue-8071-member-pins-batch-1 branch September 18, 2026 03:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants