test(test-support,app-shell,plugin-detail,data-objectstack): one array-element reader for three disagreeing walks - #7423
Merged
Conversation
…y-element reader for three disagreeing walks (#5872 class 2) Adds `arrayElementSchema` to `@object-ui/test-support` and converts the three hand-written array-element walks onto it, plus one `.unwrap().options` read. The walk itself moves to `spec-zod-wrappers.ts` so `enumOptions` and the new reader share ONE implementation; measured 0 differences for `enumOptions` over 1386 spec nodes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
Contributor
✅ Console Performance Budget
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
Size Limits
|
os-project-manager
marked this pull request as ready for review
September 3, 2026 01:11
os-project-manager
deleted the
claude/issue-5872-zod-reader-classes-2-4
branch
September 3, 2026 01:26
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #5872 — classes (2) and (4) only. Class (1) landed in PR #6047; class (3) was ruled on 2026-08-31 (objectui#6923) and is untouched here. The card stays open.
Verified at
3f06af818.Re-derived census first, because the card says to
The card's own instrument, re-run on
main@24d4a21ffoverpackages/ apps/ examples/:31 matching lines in 23 files across 7 packages —
typesx11,app-shellx5,plugin-detailx2,componentsx2,plugin-ganttx1,data-objectstackx1,corex1. The 2026-08-31 census recorded roughly 18 across 7 packages withtypesx8 andapp-shellx5.app-shellheld;typesgrew from 8 to 11. The premise holds — this population is real and it is still growing — so the round proceeded.What converged, and the measurement behind each choice
Class (2) was three different spellings of one question, and they disagreed about every choice a shared reader has to make. So the reader records which site each choice came from:
undefinedfor a node that is not an arrayclientValidation.optOutstoBeDefined()assertion depends on the discrimination; answering with the node itself deletes a non-vacuity check in silencerecordDetailsInputs/block-configzod@4.4.3putsunwrap()onZodArrayitself, so a reader that unwrapped its own answer would descend into an array-of-arrays and report the wrong entry shape_def.typeread only behind_def.typeName === 'ZodArray'recordDetailsInputs, guarded_def.typeis the type-name STRING'array', so the hand copy's last limb was a landmine:listedShapeKeys('array')is[], the exact quiet empty set this card family exists to stopPer-site set identity, measured against the installed pin (
@objectstack/spec@17.2.0,zod@4.4.3) — every element compared by reference identity, not just by derived keys:plugin-detail/.../recordDetailsInputs.spec-parity.test.tsunwrap()), 4 element spellingssections/hideFields/fieldselements identical by reference; derived key sets identical (["name","label","columns","fields"],[],[])app-shell/.../previews/__tests__/block-config.test.ts_defonlyspecSectionKeysidentical:["name","label","columns","fields"]app-shell/.../clientValidation.optOuts.test.ts_zod.def, type-gatedsharingRules/connectorselements identical by reference; 16- and 30-key shapes identical;checks()counts identical (0 / 1); array-vs-not discrimination swept over all 43ObjectStackSchemamembers with zero divergencedata-objectstack/.../metadata-client.overlayScope.test.ts(class 4)["org","env"]both waysThe dropped limb from
clientValidation.optOuts— its second unwrap of the element — is a measured no-op: of the 35 array members ofObjectStackSchema, zero have a wrapped element, and a wrapped one now fails loudly at theshapeKeyscomparison rather than quietly.enumOptionsis unchanged, and that was measured rather than arguedThe walk moved into
spec-zod-wrappers.tsso the new reader is not a second copy of it. A differential harness ran the pre-changeenumOptionsand the post-change one over 1386 nodes reachable from all 12@objectstack/specsubpath modules (334 of which answer non-empty):The one added limb (a
def.getter()thunk forz.lazy()) is unreachable on this pin, becauseZodLazyalso exposesunwrap(), which the first limb takes. All 33 in-treeenumOptionsconsumers were run.The site deliberately NOT converted, with the measurement
app-shell/.../LayeredDiff.overlayScope.test.tsx:31is one of the two "seventh spelling" sites the census flagged —Schema.shape.key.unwrap().options, no cast, no guard. Converting it would delete a live compile-time guard, and this is the reverse verification that says so. Predicted direction recorded before the run: turns red. Applying the conversion and runningapp-shell'stsconfig.test.json:At that site the absence of a cast is the guard:
SPEC_SCOPESinherits its element type from the spec schema, so the day the spec adds a scope,layeredWith(scope)stops compiling and someone has to add the zh-CN label. A shared reader returnsstring[], and any cast that silences today's error silences that future one too. The mutation was proven on disk by anchored token counts and the restore by state (git diff HEADempty, blob hash equal toHEAD), under atrapwith absolute paths.Other sites left alone, each for a reason rather than for budget: the union-arm readers (
types/spec-subschema-parity.test.ts,plugin-detail/.../recordHighlightsInputs.spec-parity.test.ts) ask a different question —ZodUnion.optionsis an array of arm schemas, soenumOptionswould hand them schema objects behind astring[]annotation, and the reader's docblock now says so;components/.../toast-button-variant-parity.test.tsdeliberately throws where the shared reader answers[];plugin-detail/.../recordRelatedListInputs.spec-parity.test.tswalks until a shape is reachable, which no shared reader expresses and which a walk-to-the-end reader would overshoot on an array;types/object-view-spec-parity.test.ts:78says "Deliberately ONE unwrap, not a loop" in its own comment; theflow-*sites belong to class (3).Non-vacuity
The duty
spec-enum-options.tsnames is carried into the new reader's docblock and checked per site.block-configalready hadexpect(specSectionKeys, 'could not read ...').not.toEqual([]);recordDetailsInputsalready hadexpect(specSectionKeys().length).toBeGreaterThan(0);clientValidation.optOutsalready hadexpect(element, '... must be an array collection').toBeDefined();metadata-client.overlayScopealready hadtoEqual(['org','env']), which an empty array fails. None was dropped and none had to be added.LayeredDiff.overlayScopehas none — its module-scope.unwrap().optionsfails loudly instead — which is a second reason converting it as-is would have been a regression.Ablation — the new calibration suite can actually fail
Both legs proven on disk by anchored token counts before the run and restored by state afterwards, under
trap ... EXIT INT TERMwith absolute paths.@object-ui/test-supporthas no build step and itsexportsmap points at./src/index.ts, so the mutated bytes are the bytes that ran; there is nodistthat could serve a stale copy._def.typeName === 'ZodArray'guardundefined_def.typeSTRING as if it were a schema"Restore proof both legs:
git diff HEADreports 0 changed paths andgit hash-objectequals theHEADblob83e125a6e....Gates
check:eager-closureis unmoved, measured on a real console build rather than asserted:frameworkis 523,823 bytes against a 524,000 ceiling — 177 bytes of headroom, the exact repo-wide figure the lane is blocked on. The diff is test files plus@object-ui/test-support, which isprivate: true, has no build, and is imported by nothing outside test files andscripts/*.mjsgates.Green:
check:control-bytes,check:phantom-deps,check:self-import,check:readme-exports,check:eager-closure,check:spec-symbols,check:dist-completeness,check:published-dist,check:published-tsconfig-exclude,check:esm-specifiers,check:vi-mock-specifiers,check:vi-mock-inherit,check:side-effects-array,check:doc-fences,check:spec-floors,check-changeset-presence. The repo-wide lint farm ran in full —turbo run lint, 47 of 47 tasks successful, 0 errors.type-checkgreen for all four affected packages, andtsc --listFilesconfirms every edited file is inside the program that reported clean.The changeset carries an EMPTY frontmatter, which the presence gate names as the explicit exemption for a diff that releases nothing: "4 source file(s) of 3 released package(s) changed, and this change declares 1 changeset(s) ... declared as releasing nothing".
Test evidence:
vitest run, 37 files / 538 tests passed at3f06af818.Generated by Claude Code