fix(react): the unevaluated-expression diagnostic lists properties, the channel its sibling recommends - #7955
Merged
Conversation
… the channel its sibling recommends
Two dev diagnostics in `@object-ui/react` answered the same authoring question
two ways, and one node can trip both. `propsBagDiagnostic` tells the author
"`props` is NOT hoisted onto the node — only `properties.*` is … Write them
under `properties` instead"; `unevaluatedExpression` enumerated "channels that
do evaluate and read back today" as `content` or host-side resolution only,
omitting `properties`.
The enumeration was the wrong half, measured rather than inferred from the
`COMPAT` label on the hoist. No retirement is on record for `properties`:
`@objectstack/spec@17.2.0` calls the vocabulary carried there "ALIVE — this is
not dead surface to retire under ADR-0049", keeps `PageComponentSchema.properties`
as the open carrier on purpose, gates it at the authoring door, and tombstones
no part of it via `retiredKey()`. In this repo `props`, not `properties`, is the
spelling annotated as the legacy alias.
Reproduced through the real renderers on this branch's base: `badge` with
`properties.label = "${data.status}"` renders `completed` with variant classes,
`card` with a conditional `properties.className` renders `border-red-500`, and
the same keys under `props` render an empty badge body and no `border-red-500`.
Diagnostic text only — no evaluation, hoist or schema behaviour changes.
Pinned by a derived assertion: the channel parsed out of the `props` message
must appear in the enumeration parsed out of the other, so a reword of either
cannot reintroduce the contradiction. objectui#7849
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
objectui#7849 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
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-sam
marked this pull request as ready for review
September 6, 2026 04:47
os-sam
deleted the
claude/issue-7849-properties-diagnostic-consistency
branch
September 6, 2026 05:02
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 #7849
What was wrong
Two dev-build diagnostics in
@object-ui/reactanswered the same authoringquestion two ways, and one node can trip both:
utils/propsBagDiagnostic.ts— "propsis NOT hoisted onto the node — onlyproperties.*is. … Write them underpropertiesinstead (or at node level,where the node's schema declares the key)."
utils/unevaluatedExpression.ts— "Channels that do evaluate and read backtoday:
content, or resolve the value in the host before handing the schemato SchemaRenderer."
An author whose node tripped both was told to use
propertiesby the first and,by the second, that the only channels that work are
contentor host-sideresolution.
Note the card pointed at
packages/react/src/SchemaRenderer.tsx; the twomessages actually live in the two
utils/modules above.Direction: measured, not inferred from the
COMPATlabelThe card left two readings open — the enumeration is incomplete, or
propertiesis a shim on the way out and the recommendation is the bug. Deciding that from
the word
COMPATon the hoist would be an inference, so it was measuredinstead. No retirement is on record for
properties:docs/adr/**properties,deprecat,retire,sunsetpropertiesbag. Hits are unrelated retirements (FAB,sys_*,requiredWhen).@objectstack/spec@17.2.0retiredKey()tombstonesretiredKey×props/properties@objectstack/specsrc/ui/component.zod.tspropertiesPageComponentSchema.propertiesis stillz.record(z.string(), z.unknown())" — the carrier is unchanged on purpose, and it is gated at the authoring door byvalidate-component-props.ts.@deprecatedmarkers@deprecated×propertiesproperties.legacy×props/propertiespropsbag, minus every key the canonicalpropertiesbag also …" and "(propsis an annotated legacy alias)" (SchemaRenderer.tsx)..changeset/*.md(960 files), allCHANGELOG.mddeprecat,retire,sunset,hoistproperties.propertiesbag or the hoist⇒ the enumeration was the wrong half. This PR adds
propertiesto it, andchanges nothing else.
Reproduction of the card's three readings
Re-run on this branch's base through the real renderers (
packages/components/src/renderers/**)driven by
SchemaRenderer, rendered DOM:All three reproduce the card exactly, so both readings stood and the retirement
measurement is what picked the direction.
The fix
formatUnevaluatedExpressionMessagenow reads:The wording is borrowed from the sibling diagnostic on purpose ("hoisted onto
the node", "a renderer declared as
({ schema })") — one hoist, one vocabulary,no invented terms.
The pin, and its positive control
packages/react/src/__tests__/diagnosticChannelConsistency.test.tsis aderived assertion, not two hard-coded strings: the channel is parsed back
out of the message
propsBagDiagnosticactually emits, the enumeration isparsed out of the other, and the test asserts the first appears in the second.
Reword either message and the pin still asks the question that matters. Both
regexes are asserted to have matched before they are compared, so a silent miss
cannot pass as green.
Positive control — contradiction reinstated (
properties.*dropped from theenumeration), mutation confirmed on disk before the run
(
removed_text_count=0,injected_text_count=1, blob4a476fe1→fa52deb7):Exactly one test red, and it names itself. Restore proven by state, not by
exit code:
git diff HEADempty and the blob back to HEAD's4a476fe1efb027d8cdb5e456ee78cc5139e3e865. The script carried an absolute-pathtrap … EXIT INT TERM.Verification — all at final commit
888ebec86, clean tree@object-ui/reactpnpm testRUN v4.1.10 /home/user/objectui-issue-7849· Test Files 73 passed · Tests 876 passed@object-ui/reactpnpm run type-checktsc --noEmit && tsc -p tsconfig.test.json, 0TS####lines--listFiles: both changed files present in a program (source in both, pin file in the test program)eslint .overpackages/react--format json), 0 errors; my two files 0/0 (the 347 warnings are pre-existing elsewhere in the package)check-changeset-presence.mjscheck-changeset-fixed.mjscheck-changeset-no-major.mjsmajorbumpcheck-changeset-overwrite.mjscheck-control-bytes.mjsChangeset grade was not reasoned about —
check-changeset-presence.mjswasrun and its verdict followed: it demanded one and scored "minor at most, never
major"; a dev diagnostic an author reads does change in a published package, so
patch.Scope
nothe
propertiesruntime is untouched.content/docs/page touched (the card's zero-properties-examplesconstraint holds), no
packages/types/, no governance surface.checker demanded).
🤖 Generated with Claude Code
Generated by Claude Code