Repository navigation
fix(spec): project the react-blocks contract through projectPublishedJsonSchema - #20304
Merged
objectstack-fleet[bot] merged 2 commits intoSep 27, 2026
Conversation
…onSchema The react-tier contract generator read each block's spec schema through a bare z.toJSONSchema, outside the published-projection choke point, so the refinement override every published JSON Schema artifact carries was not applied to the schemas behind skills/objectstack-ui/references/react-blocks.md. Route it through projectPublishedJsonSchema. Measured: of the three schema-backed blocks, ObjectForm and ObjectChart project byte-identically either way; ListView diverges only under conditionalFormatting[].condition and bulkActionDefs[].visible (the EvaluatedExpression envelope's non-blank source and source-or-ast refinements), neither of which is in ListView's dataProps allow-list, so the regenerated react-blocks.md is byte-identical. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV
With the react-tier contract generator projecting through projectPublishedJsonSchema, its direct-call allowance in published-projection-choke-point.test.ts no longer describes the tree: remove the row and the prose that named it, and hold the generator to the helper the way build-schemas.ts and build-openapi.ts are held (PUBLISHED_PRODUCERS). The choke point's own docblock named the generator as a direct caller outside the helper; it now lists it as the fifth producer. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
objectstack-fleet
Bot
deleted the
claude/issue-19100-react-blocks-published-projection
branch
September 27, 2026 21:03
This was referenced Sep 27, 2026
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.
Fixes #19100
Clause-②: no
What this does
packages/spec/scripts/build-react-blocks-contract.tsread each block's spec schema through a barez.toJSONSchema(schema, { unrepresentable: 'any' }). That call sat outside the published-projection choke point, so the refinement override that every published JSON Schema artifact carries was not applied to the schemas behindskills/objectstack-ui/references/react-blocks.md. The generator now projects throughprojectPublishedJsonSchema(schema, { unrepresentable: 'any' }). Its allowance row inpublished-projection-choke-point.test.tsand the prose naming it are removed.Measured outcome: branch (A), byte-identical. The shipped
react-blocks.mdis not in this diff, and noskills/**path is touched.Measurement (taken before any repair, on
3f86dc52f2)The shipped file, regenerated with the package's own generator (
pnpm --filter @objectstack/spec gen:react-blocks)The shipped file has no hunk.
check:react-blockson the final tree:1 generated files in sync with packages/spec.JSON projection of the 3 spec-backed block schemas, bare call vs
projectPublishedJsonSchemaFormViewSchemacaec4524119dbe87caec4524119dbe87ListViewSchema7dad3d8b8b3dd51c773bae792a7d435eChartConfigSchemab1974a1cf703a528b1974a1cf703a528The fourth block (
Block) has no spec schema; it is overlay only.The divergent schema is
ListViewSchema, at exactly two JSON paths:properties/conditionalFormatting/items/properties/condition/anyOf/1properties/bulkActionDefs/items/properties/visible/anyOf/1Both are the object branch of
EvaluatedExpressionInputSchema, which isEvaluatedExpressionSchema. The bare call dropped two declared refinements there:NON_BLANK_STRINGonsource(packages/spec/src/shared/expression.zod.ts:172). Through the override it projects as"minLength": 1, "pattern": "\\S".requiredOneOf(['source', 'ast'])onExpressionSchema(expression.zod.ts:105), whichEvaluatedExpressionSchemainherits viasafeExtend. Through the override it projects asallOf: [{ anyOf: [{ required: ["source"] }, { required: ["ast"] }] }].ObjectForm and ObjectChart are identical because their projections reach no Expression envelope at all: zero
dialectand zerosourcenodes, so the override has nothing to write.Why the markdown does not move: the generator keeps only the props named in the block's
dataPropsallow-list (build-react-blocks-contract.ts:93). ListView's list (packages/spec/src/ui/react-blocks.ts:305) istype, data, columns, sort, searchableFields, userFilters, pagination, grouping, rowHeight, selection, rowActions, inlineEdit. It names neitherconditionalFormattingnorbulkActionDefs, so both divergent subtrees are filtered out before any row is rendered. So no sentence on the AI-facing page is wrong today. The change makes the generator project the way the other published producers do; it does not alter what the page says.Full ListView projection diff (only these hunks):
(The
allOfbody is shown compacted; the emitted JSON is the same structure, pretty-printed.)The diff
packages/spec/scripts/build-react-blocks-contract.ts: importprojectPublishedJsonSchema, drop thezodimport, route the one projection through the helper, and update the header comment.packages/spec/scripts/published-projection-choke-point.test.ts:build-react-blocks-contract.tsrow fromDECLARED_DIRECT_CALLS, so the allowance goes from 3 rows to 2 (the choke point itself plus the representability probe);PUBLISHED_PRODUCERS, so the pin also asserts that it imports the helper, the same holdbuild-schemas.tsandbuild-openapi.tsare under.packages/spec/scripts/lib/refinement-projection.ts, comment only: the choke point's docblock listed this generator as a directz.toJSONSchemacaller outside the helper, which this change makes false. It is now producer 5 of 5, and the "outside" bullet names only the CLI'sos generate.Governed-surface routing
node scripts/pm/check-governed-merges.mjs --branch claude/issue-19100-react-blocks-published-projectiongives exit 0:0 of 3 path(s) hit the register ... NOT governed, size 58 changed lines. Noskills/**path is in the diff.About the generated-artifact exception: the same predicate, run with
skills/objectstack-ui/references/react-blocks.mdadded to the list, gives exit 3, and the exception does not lift the path:the tree under test modifies the generator this exception trusts — the path stays governed ... land the generator change and the artifact regeneration as separate PRs. So if the bytes had changed, this PR would have been Tier H, with no exception route available. They did not change, so the question does not arise.Changeset
None.
skip-changesetapplies because nothing here ships:@objectstack/spec'sfiles[]isdist, json-schema, liveness, prompts, llms.txt, README.md, src/**/*.zod.ts, CHANGELOG.md, api-surface, spec-changes.json, andscripts/is not in it.Verification (final tree
c64a07ed94)pnpm --filter @objectstack/spec exec vitest run --project repo scripts/published-projection-choke-point.test.ts: 6/6 passed, VERDICT command-exit 0.vitest run --project localonscripts/refinement-projection.test.tsandsrc/ui/react-blocks.test.ts: 2 files, 90 tests passed.pnpm --filter @objectstack/spec typecheck(tsc --noEmit,check:scripts-typecheck,check:test-typecheck): VERDICT command-exit 0.pnpm --filter @objectstack/spec test(--project local): 551 files passed and 1 skipped; 16253 tests passed, 1 skipped, 1 todo; VERDICT command-exit 0.check:react-blocks: exit 0, in sync.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands, 61 commands): 56 exit 0, includingcheck:react-blocks,check:authorable-surface,check:liveness,check:pm-governed-merges,check:pm-dispatch-gates,check:nul-bytes,check:comment-mask-adoptionandcheck:test-source-alias. 5 are NOT MEASURED (exit 3, PREREQUISITE NOT MET):check:dts-closure,check:dual-build-cjs-loads,check:lean-entry-closure,check:sourcemap-no-sources-contentandcheck:type-check-debt. Each needs a whole-workspacedist/build, which this diff cannot move, because no changed file ships. They are declared to CI.dispatch-gates --ranreconciliation: 61 derived, 56 run, 5 NOT-MEASURED (derived from the recorded exit 3), 0 unrun.eslint --no-inline-config --format jsonon the 3 changed files): 3 files linted, 0 errors, 0 warnings. The narrowing excludes nothing: the ESLint config never enables type-aware linting (noparserOptions.project, no typed rules, as stated ateslint.config.mjs:326-328), so this diff cannot change the verdict on any untouched file. The fullpnpm lintrun is CI's.repovitest project (32 other files). It is outside the package'spnpm testscript, and my one local attempt was cut off by my own timeout after a 256 s lock wait. Its one file related to this diff, the choke-point pin, ran green on its own (above).Reverse verification (one-time, not kept as test files)
Both runs used
node scripts/ablation-replace.mjsagainst the committed treec64a07ed94. Each restore was proven by blob equality with HEAD (224f9f42639e) and an emptygit diff HEAD.no producer in scripts/ reaches z.toJSONSchema directlyfails with"build-react-blocks-contract.ts: 1 direct call(s), declared 0"(1 failed, 5 passed).every published producer imports the helper it is required to project throughfails onbuild-react-blocks-contract.ts(1 failed, 5 passed).Acceptance notes
z.toJSONSchema. That is still true at the mechanism level, because the helper is the call that reaches it, so the ADR is not edited here. It is a governed surface and outside this card.conditionalFormattingorbulkActionDefsever join ListView'sdataProps, the row type still renders from the top-level node (object[]), which the override does not change.layoutaccepts only vertical | horizontal — theinlineandgridarms retired (#20221) #20262 also regenerates one row ofreact-blocks.md. This PR does not touch that file, so the two cannot conflict.Generated by Claude Code