Repository navigation
fix(app-shell): record form and modal form bind systemPermissions, so hasCapability answers on object fields (objectui#13083) - #13094
Merged
objectstack-fleet[bot] merged 2 commits intoOct 19, 2026
Conversation
…ty answers on object fields RecordFormPage and AppContent's record-form modal build their field-list evaluator by hand and passed `permissions` without `systemPermissions`, so a field `visibleWhen` naming `current_user.hasCapability(NAME)` faulted (and failed open) there while page predicates answered. Each site now reads `useExpressionSystemPermissions()` beside `useExpressionPermissions()` and hands it on, the way the ExpressionProvider scope does. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB
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
|
This was referenced Oct 19, 2026
objectstack-fleet
Bot
deleted the
claude/issue-13083-form-evaluator-capabilities
branch
October 19, 2026 23:15
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 #13083
Clause-②: no
What changed
An object field whose
visibleWhennamescurrent_user.hasCapability('NAME')is now answered on the record form page and in the console's global record-form modal.The two surfaces build their field-list evaluator by hand, because each sits above the
ExpressionProviderit mounts. Each passedpermissionsbut notsystemPermissions, so the engine refused everyhasCapabilitycall on a field, and the fail-open field gate showed the field to holders and non-holders alike, with a diagnostic. Page nodes, navigation and actions already answered through the provider (objectui#13006).Each site now reads
useExpressionSystemPermissions()besideuseExpressionPermissions()and passes it tocreateExpressionEvaluatorassystemPermissions, with the same name and shape the provider uses:packages/app-shell/src/views/RecordFormPage.tsx(the record form page'sexpressionEvaluator);packages/app-shell/src/console/AppContent.tsx(the modal form'sexpressionEvaluator).Nothing is re-derived from
MePermissionsProvidera second way. A not-loaded answer staysundefined, so the predicate still faults loudly; it is never turned intofalseortrue. No export, key or prop moves, and@object-ui/core's evaluator is unchanged.Every hand-built evaluator under
packages/app-shell/srccreateExpressionEvaluator(call sites outside tests, read at the branch base6058bf8b71:ExpressionProvideritself (the provider's own scope): already passessystemPermissions.useExpressionContext()'s no-provider default: binds onlyuserandfeatures, deliberately; outside the provider there is no loaded answer, so leaving both permission inputs unbound is the documented "not loaded" state.RecordFormPageandAppContent: the two sites this PR changes.No third site carries the omission.
Tests
Two new suites drive the real components under a real
MePermissionsProviderwith a payload, reading the field list the page hands toObjectFormand the modal hands toModalForm:packages/app-shell/src/views/RecordFormPage.hasCapability-13083.test.tsxpackages/app-shell/src/console/__tests__/AppContent.hasCapability-13083.test.tsxEach has three arms on a field gated on
current_user.hasCapability('manage_org_users'):AppContent mounts in a unit test, as
AppContent.declaredVisibilityKeys.test.tsxalready does, so the modal arms go through the real component rather than a copy of its evaluator.Reverse verification
The direction was predicted before running. The fix was committed first (
0ae227d904). Each site's hand-off line was then deleted withscripts/ablation-replace.mjs(objectstack checkout), whose anchor must hit once. Both suites ran each time.RecordFormPage: anchor 1 to 0, blob7358a6fba6d4tod8e9897a7276. The record-form HOLDER and NON-HOLDER arms went red. The other 4 tests stayed green: the modal suite and the not-loaded control. Restored by blob hash:7358a6fba6d4equals HEAD, andgit diff HEADis empty.AppContent: anchor 1 to 0, blob787a250edc4dto3587782483d2. The modal HOLDER and NON-HOLDER arms went red. The other 4 tests stayed green. Restored by blob hash:787a250edc4dequals HEAD, andgit diff HEADis empty.The HOLDER arm goes red through its silence assertion: the field is shown, but the fault is reported. The NON-HOLDER arm goes red because the field is shown.
Local verification (final head
73bc2d5e84)pnpm --filter '@object-ui/app-shell^...' build(the dependency closure), thenpnpm --filter @object-ui/app-shell type-check, which runstsc --noEmitandtsc -p tsconfig.test.json: exit 0.--listFilesOnlyconfirms that both new test files are insidetsconfig.test.json.hasCapability-13006andcurrentUserCan-4421: 50 files in three batches, 413 tests passed,--maxWorkers=1, under the shared lock.eslint --no-inline-configon the 4 changed source and test files: 0 errors. The new test files have 0 warnings. The 2 production files have only warnings that were already there, none of them on a changed line.check:phantom-deps,check:self-import,check:unreferenced-sources,check:doc-example-readers,check:handler-key-reads,check:side-effects-array,check:element-data-source-declaration,check:spec-symbols,check:action-forward-parity,check:designer-field-key-parity,check:icon-record-names,check:i18n-keys,check:i18n-drift,check:i18n-designer-parityandcheck:control-bytes: each exited 0.scripts/check-hand-rolled-comment-mask.mjs,scripts/check-changeset-fixed.mjs,scripts/check-changeset-presence.mjsandcheck:new-line-citations(0 new citations): each exited 0.Left to CI: the full
packages/app-shelltest directory. The dispatch kept the lock clear of whole-package runs.Changeset:
.changeset/13083-form-evaluator-capabilities.md(@object-ui/app-shellpatch).Generated by Claude Code