Repository navigation
fix(ui-core): read a bare-string predicate as CEL shorthand, as the spec and server do - #27584
objectstack-fleet[bot] wants to merge 8 commits into
Conversation
…pec and server do Claude-Session: https://claude.ai/code/session_01L6bhgVn2m1FLfCPDLtAfE3 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6bhgVn2m1FLfCPDLtAfE3 Co-authored-by: Claude <noreply@anthropic.com>
…g only Claude-Session: https://claude.ai/code/session_01L6bhgVn2m1FLfCPDLtAfE3 Co-authored-by: Claude <noreply@anthropic.com>
A bare-string predicate is CEL shorthand, so every pin that relied on the
legacy JS reading of a bare string now asserts the CEL verdict: `===` and
data-key misses fault and take each surface's fault policy, while the
adapter-only `data.*` legs keep their coverage through `${…}` templates.
Adds the changeset and refreshes two SchemaRenderer comments.
Claude-Session: https://claude.ai/code/session_01L6bhgVn2m1FLfCPDLtAfE3
Co-authored-by: Claude <noreply@anthropic.com>
… test Claude-Session: https://claude.ai/code/session_01L6bhgVn2m1FLfCPDLtAfE3 Co-authored-by: Claude <noreply@anthropic.com>
The console stops accepting bare-string predicate spellings only the legacy JavaScript grammar reads, so the changeset declares the narrowing arm and kind, takes a minor bump, carries the BREAKING banner and states its ADR-0087 disposition. Claude-Session: https://claude.ai/code/session_01L6bhgVn2m1FLfCPDLtAfE3 Co-authored-by: Claude <noreply@anthropic.com>
CI surfaced three suites that still pinned a bare string on the legacy JS grammar: the page header's legacy-dialect fallback, the useCondition fault reasons and host-data case, and the record alert's loading-frame diagnostic filter. Each now asserts the CEL verdict with a CEL control. Claude-Session: https://claude.ai/code/session_01L6bhgVn2m1FLfCPDLtAfE3 Co-authored-by: Claude <noreply@anthropic.com>
|
domain:ui#1 · os-sales · session_01L6bhgXU26UBEjd1KKHgNEx Contract reviewServed-tier: Inputs: card #27457 body and its 5 comments (triage 6491385506, dispatch 6495413455, claims 6495627500 and 6498534084, dev report 6498493316), PR #27584 body, its 24-file list and the net diff against ① Derived judgmentsAccept-set, console reading of a BARE string in a predicate slot (nav
② Semver level
③ Boundary flagsDev report 6498493316 is on head
④ Design judgmentHeld against the code it cites and the accepted scenario; tried to break on fault policy, missing roots, list paths and double evaluation.
Findings (none must-fix):
Implemented-by: VERDICT: PASS Rendered 2026-10-29T14:39Z. Generated by Claude Code |
…pelling Claude-Session: https://claude.ai/code/session_01L6bhgVn2m1FLfCPDLtAfE3 Co-authored-by: Claude <noreply@anthropic.com>
|
domain:ui#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK Regen-provenance: 6499697983 · The one commit after the reviewed head ( Generated by Claude Code |
|
domain:ui#1 · os-sales · session_01L6bhgXU26UBEjd1KKHgNEx 席位核对 · ACCEPT · 2026-10-29T15:09ZHead-sha:
|
domain:ui#1 · os-sales · session_01L6bhgXU26UBEjd1KKHgNEx
Requested by Sales · project thread
Fixes #27457
Clause-②: no (narrowing)
Metadata-protocol: narrowed
Skills-docs: follow-up #27587
维护者速读
visible、页面visibleWhen、动作visible/disabled等)当作 CEL 求值,与{ dialect: 'cel' }信封走同一个引擎;只有${…}模板仍走旧 JS 求值器。'org_owner' in current_user.positions、size(page.items) > 0这类 CEL 写法在控制台报错,导航项对所有人隐藏、页面区块不渲染(ADR-0032:只有一种表达式语言)。===、.includes()、.length、读不存在的data.*键)现在会故障并按各入口原有故障策略处理(导航/显示门隐藏,disabled置为禁用);仓内无此类已编写元数据(实测见下)。回滚即 revert 本 PR。ui/packages/core/src/evaluator/ExpressionEvaluator.ts(if (!unwrappedEnvelope)分支)、ui/packages/core/src/evaluator/predicateInput.ts(字符串分支)、ui/packages/core/src/evaluator/listConditional.ts(行谓词路由)。pnpm --dir ui exec vitest run packages/app-shell/src/providers/__tests__/navigationVisibility.bareCel-27457.test.ts packages/react/src/__tests__/SchemaRenderer.bareCelVisibleWhen-27457.test.tsx,预期 8 个测试全绿。What changes
The spec defines a bare string in a predicate slot as shorthand for
{ dialect: 'cel', source }(ExpressionInputSchema/EvaluatedExpressionInputSchema), and the server reads it that way. The console read it on the legacy JS grammar, so a CEL-only predicate faulted: a nav entryvisible: "'org_owner' in current_user.positions"was withheld from everyone, and a page blockvisibleWhen: "size(page.items) > 0"never rendered, while the same source as an envelope worked.ExpressionEvaluator.evaluateCondition(@object-ui/core): a bare string now takes the CEL route (evaluateCelCondition), the same one acelenvelope takes, with the sameonFault/throwOnErrorpolicy. Only a${…}template and thesourceof an envelope whose dialect is notcelstay on the legacy path.toPredicateInput: a bare string normalizes to{ dialect: 'cel', source }instead of being wrapped as${…}; an already-${…}string is returned untouched as before.evalRowPredicate(listConditional.ts): only a${…}template is routed to the legacy engine; every other bare string is CEL. The one-time legacy-syntax warning still fires for any legacy marker.ExpressionProvider.tsxandSchemaRenderer.tsxwhere they described the old bare-string route.Why this is the normalization point:
evaluateConditionis the single funnel every console predicate reader reaches (SchemaRenderer, ExpressionProvider's nav and visibility gates, ActionEngine, the hooks), and several of them hand it the authored value without normalizing first. Reading the bare string there is the console's equivalent of the server'snormalizeExpressioninput step;toPredicateInputemits the same envelope for the callers that normalize first, so both orders reach one engine. No legacy path remains for the shorthand.Branch name: this card was worked in a platform-assigned card-thread branch (
claude/project-thread-054wyf), not theclaude/issue-27457-SLUGshape; the claim names this branch.Tests
app-shell/src/providers/__tests__/navigationVisibility.bareCel-27457.test.ts: the navvisiblebare string'org_owner' in current_user.positionsis true for the holder and false for the member, with no fault line; the envelope control reaches the same verdicts;evaluateVisibilityagrees; a legacy-only===spelling is withheld with one report, and its==control is shown.react/src/__tests__/SchemaRenderer.bareCelVisibleWhen-27457.test.tsx: pagevisibleWhensize(page.items) > 0is shown with items and hidden without,'org_owner' in current_user.positionsshows the holder and hides the member, the envelope control agrees, and a legacy-only===spelling is withheld with one report.ActionEngine.visibility,ActionRunner.disabledGate,declaredPredicate,predicateInput,listConditional,actionPredicate.parity,useExpression,record-alert.rowBinding,record-block-record-reach,SchemaRenderer.nodeGateDataPredicate,SchemaRenderer.productionPredicateFaultWarning,SchemaRenderer.disabledGateFaultDiagnostic,DeclaredActionsBar. Legs that pinned the adapter-onlydata.*diagnostic keep their coverage through the${…}template spelling, which still reads on the legacy engine.Acceptance notes
===,!==,?.,??,.includes(…),.length, or reading adata.*key the scope does not hold) now faults instead of evaluating. Measured over the repository: 1604 bare predicate literals, 127 do not parse as CEL; the 54 outside tests are i18n label strings, lint-script fixture prose, regex-escaped zod description examples and one className, none of them an authored predicate. The 73 in tests are fixtures, flipped above where they pinned the legacy reading. The changeset carries a BREAKING note that points authors to the CEL spelling and lists no FROM-to-TO rewrites (ADR-0087 dispositionnot-required (no-migration-prescription)); the per-spelling rewrite guidance lands in the skills under follow-up skills/objectui teaches JavaScript-syntax bare predicates (===, !, raw JS operators) that the console will read as CEL after #27457 #27587.data.*diagnostic (objectui#5687) fires only on a clean evaluation, so it is now reached only through${…}templates; a baredata.*miss reports on the fault leg instead (pinned).throwOnErrorcallers (dev-mode visibility gate,DeclaredActionsBar), a CEL fault reports the genericCEL predicate failed to evaluate: SOURCEline, whileonFaultcallers report[kind] message(for exampleNo such key: status). This split already existed for envelopes and is unchanged here.ui/packages/core/src/evaluator/listConditional.tsis outside the claim's listed file surface. Same defect class (its row-predicate router sent bare strings with legacy markers to the JS engine), mechanical, no other claim on the file, same gate families; the ablation below shows its test turns red without it.ExpressionEvaluator.evaluatePlainConditionstill reads a bare string on the legacy path.Verification
Ablations (each through scripts/ablation-replace.mjs under the verify lock, restored to HEAD after)
ExpressionEvaluator.tsanchorif (!unwrappedEnvelope) {replaced withif (false) {(legacy route restored): 6 failed, all in the two acceptance files (nav 3, page 3).predicateInput.tsstring branch replaced withreturn wrapIfBare(value);: 5 failed acrossActionEngine.visibility,declaredPredicate,predicateInput,useExpression.listConditional.tsroutersource.includes('${')replaced withisLegacyDialectSource(source): 1 failed (the bare===CEL parse-fault report).Local test runs
@object-ui/coreplus every edited and new test file, one run under the verify lock: 227 files, 4326 passed, 27 skipped, 2 failed inSchemaRenderer.nodeGateDataPredicate(dev-line reason text); those two assertions were corrected and the file re-run: 24/24 passed.type-check) of@object-ui/core,@object-ui/react,@object-ui/app-shell,@object-ui/plugin-detail,@object-ui/console: exit 0.Gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 34 commands on this diff; each was run with its exit code recorded, pluspnpm check:changeset-fixedfor the.changesetroster. All 35 exit 0.--ranreconciliation: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN.Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_01L6bhgVn2m1FLfCPDLtAfE3