Skip to content

fix(react): a config-bag data object carrying a source field reaches the renderer whole - #10347

Merged
objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-10288-properties-source-collapse
Sep 24, 2026
Merged

objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-10288-properties-source-collapse

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #10288
Clause-②: yes

What was wrong

SchemaRenderer's two per-value config-bag loops (properties, and its legacy alias props) handed every non-predicate value to ExpressionEvaluator.evaluate, whose first step unwraps ANY object with a string source to that bare string. So an authored data object that merely has a field called source was silently replaced by that one string: action:button with properties.bodyExtra: { source: 'web', campaign: 'spring' } reached the runner as bodyExtra: "web". The same object at node level, which no loop visits, arrived intact.

Mechanism, printed at origin/main 7616d893 (PM zone-2 assumption 1: holds)

  • ExpressionEvaluator.evaluate, first branch: if (expression && typeof expression === 'object' && typeof (expression as any).source === 'string') { expression = (expression as any).source as string; }
  • SchemaRenderer.tsx: preservePredicateEnvelope is PREDICATE_CHAIN_KEYS.has(key) && isCelEnvelope(value) ? value : evaluate(value), reached through evaluateConfigValue from both the properties loop and the props loop. params is the one key walked leaf by leaf (paramsBag.ts, objectui#7867).

Route: (a), narrowing what counts as an envelope, sited at the loop

Four readings, taken before choosing:

  1. The spec's envelope shape, read from the INSTALLED @objectstack/spec 17.4.0 (dist/expression.zod-*.d.ts): ExpressionSchema declares dialect as a required enum (cel / cron / template) and source optional. Every spec envelope carries dialect.
  2. Who authors a dialect-less { source } on a non-predicate key on purpose. Searched examples/**, apps/**, content/docs/**, skills/** and every test fixture under packages/** for a source key whose value is a ${…} template, and for object literals opening with source: and carrying no dialect. Result: none on a non-predicate key. The dialect-less hits are either a predicate key (visible: { source: '${…}' }, which ExpressionWire declares) or DATA that happens to have a source field (flow edges, identity-import column mappings, reference-source unions, interfaceConfig: { source }, a drill-down filter on a source field). Control for the search: the same greps return the known predicate-key fixtures (SchemaRenderer.predicateEnvelopeDeclared.test.tsx, base-schema-predicate-envelope-7530.test.ts).
  3. Docs. The dialect-less { dialect?, source } form is documented only on predicate keys: visible / hidden / disabled (content/docs/api/schema-reference.md), action condition, and the form *When / visibleOn keys. No page teaches an envelope as the value of a non-predicate config key.
  4. Precedent in this repo. @object-ui/core's isRuntimeDefault (validation/server-owned-value.ts) already applies exactly this shape test, "Same shape test the engine applies before handing a default to ExpressionEngine": { dialect: string, source: string } is an instruction, { source } without dialect is a literal (pinned in FormPage.test.ts, "seeds an object default that is NOT an Expression envelope").

The readings agree, and no documented authoring form changes meaning, so this is route (a) and not a needs_decision.

Why not route (b), walking every object value leaf by leaf. It would deepen evaluation for every object value on both channels. Keeping those loops shallow is the objectui#4799 decision, and objectui#7867 took the deep walk for params only. It would also stop a spec { dialect: 'template', source } envelope on a non-predicate key from resolving to its value: it would come back as an envelope with an interpolated source. The new control row "a spec template envelope on a non-predicate key still collapses to its interpolated value" pins today's reading. So (b) is a contract change, not a narrower fix.

Why at the loop and not inside ExpressionEvaluator.evaluate. evaluate's own signature declares { dialect?: string; source?: string }. The loop is also where objectui#9100 / #9107 sited the predicate protection. And a census of evaluate's direct callers (git grep over packages/*/src, tests excluded) finds no other caller that hands it an arbitrary data object: ActionRunner passes confirmText / redirect / target / a script string, useExpression has no caller, and evaluateCondition has its own separate unwrap. Narrowing inside core would also reach the predicate keys in the bag, where the dialect-less form IS declared. So @object-ui/core is unchanged, and the radius is the two loops.

The change (packages/react/src/SchemaRenderer.tsx only)

  • isExpressionEnvelope(value): a config-bag object with a string dialect AND a string source.
  • isDataObjectValue(key, value): not a predicate-chain key, a config-bag object, and not an expression envelope.
  • evaluateConfigValue hands such a value over as authored. Otherwise it behaves exactly as before, for both loops: the params walk, then preservePredicateEnvelope, then evaluate.

The one value whose meaning changes is an object on a config-bag key outside the predicate chain and params, with a string source and no string dialect: it used to collapse to its evaluated source, and it now arrives whole. The following are unchanged:

  • strings;
  • non-object values and arrays;
  • { dialect: 'template' } and other dialect-carrying envelopes;
  • every predicate-chain key (the CEL envelope is still preserved; a dialect-less { source } is still evaluated);
  • params.

Diagnostic and walker (zone-2 item 3): no change needed, one radius kept. Evaluation of non-params objects stays per-value and shallow, which is the radius unevaluatedExpression.ts scans: it reads string values only, plus the params leaves through the one paramsBag.ts walker. paramsBag.ts already walks an envelope as a plain object and never collapses one, which agrees with this route.

Census: object-valued inputs on this path

Instrument. ComponentRegistry.getAllConfigs() with the whole registration graph loaded (@object-ui/components plus apps/console's register-plugins, the pair public-contract.test.ts loads), keeping every input whose type carries an object arm. Run as a scratch vitest file at 7616d893, not committed. Reading: 441 configs, 1681 inputs, 122 object-arm rows. The rows collapse to 80 distinct type + key pairs, because plugin types register twice (namespaced and bare), and to 42 distinct keys. record:alert.visible is a predicate key and is left on the predicate path.

  • action:bar: actions, systemActions
  • action:group: actions
  • action:menu: actions
  • element:button: action, label (string|object)
  • element:record_picker: dataSource, emptyText (string|object), label (string|object), placeholder (string|object)
  • element:text: content (string|object)
  • element:text_input: description (string|object), label (string|object), placeholder (string|object)
  • page:card: title (string|object)
  • page:header: subtitle (string|object), title (string|object)
  • plugin-dashboard:dashboard: dateRange, description (string|object), header, label (string|object)
  • plugin-dashboard:object-metric: aggregate, compareTo, dataSource, drillDown, trend
  • plugin-dashboard:object-pivot: dataSource
  • plugin-detail:detail-view: data, dataSource, footer, header
  • plugin-form:embeddable-form: dataSource
  • plugin-form:form-analytics: metrics
  • plugin-form:object-form: dataSource, initialData, initialValues, mobile, submitBehavior
  • plugin-form:object-master-detail-form: dataSource, initialData, initialValues
  • plugin-grid:object-grid and view:grid: data, dataSource, exportOptions, grouping, navigation, operations, pagination, rowColor, selection
  • plugin-list:list-view and view:list: dataSource, options
  • plugin-view:object-view: listViews, navigation
  • record:alert: action, body (string|object), title (string|object)
  • record:chatter, record:discussion: feed
  • record:line_items: dataSource
  • record:related_list: add, dataSource
  • ui:filter-builder: value
  • ui:form: defaultValues
  • ui:header-bar: rightContent, search
  • view:filter-ui: values
  • view:form: dataSource

The instrument's blind spot, and its second half. A key a renderer reads off the node without declaring it as an input never reaches getAllConfigs(). The card's own bodyExtra is one such key: action:button declares no object-arm input at all. From the action:button / action:icon forward lists, the undeclared object-valued keys are bodyExtra, bodyShape, patch, resultDialog, onSuccess and toast. params is already walked. Undeclared reads in other renderers are not enumerable this way, and the fix does not need them to be: the loop reads a key's name only to recognise params and the predicate chain.

Where a source field is ordinary, not exotic. These values are keyed by FIELD NAME, so a record with a source column (lead source) collapses: ui:form.defaultValues, object-form / object-master-detail-form initialValues / initialData, detail-view.data, view:filter-ui.values, and the action patch.

Pins

  • End-to-end probe, packages/components/src/renderers/action/__tests__/action-config-bag-source-key-10288.test.tsx. It drives the real SchemaRenderer, the real action:button and the real ActionRunner, then reads the ActionDef a registered handler received.
  • Fix-site pin, parametrised over the census, packages/react/src/__tests__/SchemaRenderer.configBagDataObject-10288.test.tsx:
    • 47 keys × both channels. The 41 census keys plus the 6 forwarded action keys each assert the object arrives whole: in schema.properties and hoisted onto the node, or in schema.props.
    • One row pins the behaviour change itself: a dialect-less { source: '${data.status}' } on a non-predicate key arrives as data.
    • Four controls: a string template still interpolates; a template envelope on a non-predicate key still resolves; a dialect-less { source } on visible is still an expression (ExpressionWire, objectui#7530); a CEL envelope on visible is still preserved (objectui#9100).
    • The key table is written as a SAMPLE of the census, not a live copy, and the file says so.

Ablation: restore the old collapse, with on-disk proof

Run with node ../objectstack/scripts/ablation-replace.mjs in wrap mode (restore armed on exit, INT and TERM) at a8caa917. The anchor was the body of isDataObjectValue, and the replacement prefixed false &&.

  • Mutation: anchor x1 → x0, replace x0 → x1, blob 41aaf62af3f1 → 7ec420a01564, "ok mutation landed".
  • Run (the two new pins plus SchemaRenderer.predicateEnvelopeConfigBag, SchemaRenderer.enablementEnvelopeConfigBag and action-enablement-cel-envelope): Tests 96 failed | 39 passed (135).
  • Restore: blob after restore 41aaf62af3f1 equals blob at HEAD 41aaf62af3f1, and git diff HEAD is empty.
  • The same ablation at 17e6e0e9 gave the same 96 failed | 39 passed.
  • The probe's red leg is this ablation on the committed tree. The probe was not run on a separate fix-less commit.
  • No build step sits in the ablation's path: the root vitest.config.mts aliases @object-ui/react to packages/react/src.

Tests and gates at a8caa917 (after merging origin/main 4215ed76)

  • pnpm exec vitest run --maxWorkers=2 packages/react/ packages/core/src/evaluator/ packages/components/src/renderers/action/, from the repo root through the verify lock: Test Files 130 passed (130), Tests 2003 passed (2003), VERDICT command-exit 0. This covers every SchemaRenderer* suite, the paramsBag / unevaluatedExpression suites, packages/core/src/evaluator/** and the objectui#7867 pin action-params-templates-7867.
  • The @object-ui/components build closure was built first, then type-check (tsc --noEmit && tsc -p tsconfig.test.json) passed for @object-ui/core, @object-ui/react and @object-ui/components, with VERDICT command-exit 0. tsc -p tsconfig.test.json --listFilesOnly lists each new test file once.
  • pnpm check:control-bytes: OK. pnpm check:new-line-citations: 0 new. node scripts/check-changeset-presence.mjs: passes, 1 changeset declared. pnpm changeset:check: passes.
  • pnpm check:changeset-claims names 4 pending changesets that cite SchemaRenderer.tsx (5926, 7319, 7415, 9959). Each paragraph was re-read, and each describes a region this diff does not touch, so all four still hold.
  • node scripts/check-doc-expression-carriage.mjs: exit 0, controls pass. pnpm check:component-surface-parity: exit 0 (report-only). Both parse SchemaRenderer.tsx.
  • eslint --no-inline-config on the 3 touched files: 0 errors. The 19 warnings are all in SchemaRenderer.tsx, and the same count comes from the base blob of that file linted through stdin. This is a targeted run, NOT a narrowing claim: repo-wide pnpm lint and the full pnpm test farm are NOT MEASURED locally and are left to CI.

Changeset: @object-ui/react minor (Clause-②; corrected in round 2 at be7515cb)

It is a minor, not a patch. The PR declares Clause-②: yes, and objectui's 版本号策略 releases its own behaviour-changing work as minor with the semantics in the body. Round 1 declared patch. The contract review of a8caa917 failed that one item (code, pins, census and exports passed), and round 2 corrects it with a changeset-only commit.

  • Nothing in the published API is added or removed.
  • What an authored value evaluates to changes for one shape. ExpressionEvaluator.evaluate's signature admits a dialect-less { source }, and these loops handed it every value except a params bag and a CEL envelope on a predicate key. So { source: '${…}' } on a config-bag key outside the predicate chain and params now arrives as the object, uninterpolated, and the unevaluated-expression diagnostic does not report the ${…} inside it. To keep such a value evaluated, write the bare string '${…}' or { dialect: 'template', source: '${…}' }; both still interpolate on that path, and both are pinned as controls.
  • The spec's envelope always carries dialect, @object-ui/core's isRuntimeDefault already reads a dialect-less { source } as a literal, and the census above found no author relying on the collapse.

The @object-ui/components file in this diff is a test, and nothing it contains ships.

Acceptance notes

  • ExpressionEvaluator.evaluate still unwraps any { source: string } for its direct callers. The caller census above finds none that passes a data object today, so it is left as is. Noted, not filed.
  • A dialect-less { source: '${…}' } written on a non-predicate key now arrives as data, and the unevaluated-expression diagnostic does not report the ${…} inside it. That matches its stated radius (string values only, plus params leaves). Noted as a boundary, not a finding.
  • ComponentRegistry.getAllConfigs() returns each plugin type twice (namespaced and bare), which is why the raw census has 122 rows for 80 pairs. Observation only.

Draft by the dispatched os-dev run for the domain:ui seat #1, session session_01BA3nKVUwKQJf8DBxrSVtNC (claim 5819813547).


Generated by Claude Code

…o the renderer whole

SchemaRenderer's per-value `properties` / `props` loops passed every
non-predicate value to `ExpressionEvaluator.evaluate`, whose first step
unwraps ANY object with a string `source` to that bare string. An authored
data object such as `bodyExtra: { source: 'web', campaign: 'spring' }`
therefore reached the runner as "web" (objectui#10288).

On a non-predicate key, an object now reaches `evaluate` only when it is a
spec Expression envelope (it carries a string `dialect`, which
`@objectstack/spec`'s ExpressionSchema requires). Every other object is
handed over as authored. Predicate-chain keys are unchanged: the CEL
envelope is still preserved (objectui#9100 / #9107) and the dialect-less
`{ source }` ExpressionWire form is still evaluated there.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
… across the census

The end-to-end probe drives `properties.bodyExtra: { source, campaign }`
through the real SchemaRenderer, action:button and ActionRunner, with the
node-level spelling as its control and the objectui#9100 / #9107 CEL
predicate rows on the same mount path. The fix-site pin is parametrised
over the census of object-valued inputs on this path, on both channels,
plus controls for what the loops still evaluate.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
…ct fix

Declares the `@object-ui/react` patch with the behaviour change stated
plainly, and points the guard's docblock at the two pins that measure it.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
…ed module variable

`react-hooks/globals` refuses a component that reassigns a variable
declared outside it; the probe now reports what it was handed to a
`vi.fn` and the assertions read its last call.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 4 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/5926-empty-action-visible-when.md

  • names packages/react/src/SchemaRenderer.tsx → packages/react/src/SchemaRenderer.tsx — edited by this change

    visibleWhen is not a per-block concern in this platform. It is enforced once, generically, in packages/react/src/SchemaRenderer.tsx: shouldHide tests visibleWhen ahead of the hoisted visible (objectui#5454), sets _hidden, and the _hidden early return fires before the registry dispatches. A block renderer cannot ignore the gate, because it never sees the node.

.changeset/7319-dead-schemarenderer-retired.md

  • names packages/react/src/SchemaRenderer.tsx → packages/react/src/SchemaRenderer.tsx — edited by this change

    packages/components/src/SchemaRenderer.tsx was a 28-line component carrying the same export name as the real renderer in packages/react/src/SchemaRenderer.tsx. Nothing reached it: it is absent from the package barrel, no file in the repo imports it by any form, and the package's exports map has no subpath that resolves to it.

.changeset/7415-action-type-input-rename.md

  • names SchemaRenderer.tsx → packages/react/src/SchemaRenderer.tsx — edited by this change

    Census of the authored corpus, measured before the rename, not assumed. Across 4,797 files in examples/, apps/console/, content/docs/ and packages/: zero authored nodes set the input. 517 JSON files (433 of them the schema catalog) contain 2,410 type-bearing nodes and no action:button / action:icon node at all — control: 127 plain button nodes on the same walk. 207 parsed fenced JSON blocks in md/mdx carry 5 action:button nodes, all of them { type, label, icon?, action } — the discriminator plus the action channel, none setting an execution type — control: 28 plain button nodes, and the count independently matches the corpus census already recorded in SchemaRenderer.tsx ("action:button (5 nodes)"). No docs page documents the input; no catalog entry uses the components. So the rename breaks no authored document in this repo.

.changeset/9959-refused-data-prop-warn-reset.md

  • names SchemaRenderer.tsx → packages/react/src/SchemaRenderer.tsx — edited by this change

    The dedupe Set behind the objectui#9571 / objectui#9758 diagnostic — "the authored data key was NOT passed to the component as a React prop" — lived in SchemaRenderer.tsx with nothing able to clear it, while every other dev diagnostic in this package's utils/ directory pairs its Set with an exported test-only reset. propsBagDiagnostic.ts states the consequence in its own reset's docblock: the second test to assert the same warning reads the first test's dedupe entry and sees silence, "a green run that checked nothing". The absence direction is the dangerous one — a test asserting this warning did NOT fire for a type#id an earlier test already warned for is green whatever the renderer does, and it reports that green as coverage.

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Compared the checked-out tree with 78b572f60 (merge-base with origin/main): 3 file(s) changed outside .changeset/, read against 1324 pending declaration(s) that publish a body (1896 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3040.2 KB 3104.5 KB
Main entry chunk (gzip) 147.8 KB 350 KB
Entry file index-apLpJpXx.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.68KB 6.20KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 541.41KB 129.42KB
core (index.js) 8.94KB 3.59KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 222.47KB 61.83KB
fields (index.js) 253.32KB 63.99KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.22KB 2.26KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 33.36KB 10.88KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.15KB 11.05KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 14.81KB 3.63KB
plugin-calendar (index.js) 51.44KB 14.62KB
plugin-charts (index.js) 71.82KB 20.13KB
plugin-chatbot (index.js) 198.27KB 47.18KB
plugin-dashboard (index.js) 133.06KB 35.21KB
plugin-designer (index.js) 216.12KB 44.37KB
plugin-detail (index.js) 260.65KB 67.82KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 146.34KB 37.48KB
plugin-gantt (index.js) 167.99KB 41.37KB
plugin-grid (index.js) 215.46KB 58.88KB
plugin-kanban (index.js) 49.30KB 15.39KB
plugin-list (index.js) 113.90KB 28.11KB
plugin-map (index.js) 21.74KB 7.07KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.68KB 8.95KB
plugin-tree (index.js) 10.56KB 3.71KB
plugin-view (index.js) 85.18KB 21.05KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 114.58KB 37.60KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

The change alters what one authored shape evaluates to (a dialect-less
`{ source }` on a config-bag key outside the predicate chain and `params`),
so under objectui's version policy it is a minor with the semantics in the
body. The closing paragraph now states that change and how to keep such a
value evaluated.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3041.1 KB 3104.5 KB
Main entry chunk (gzip) 147.9 KB 350 KB
Entry file index-D6eoi7Ft.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.68KB 6.20KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 541.41KB 129.42KB
core (index.js) 8.94KB 3.59KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 222.47KB 61.83KB
fields (index.js) 253.70KB 64.09KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.22KB 2.26KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 33.36KB 10.88KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.15KB 11.05KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 14.81KB 3.63KB
plugin-calendar (index.js) 51.44KB 14.62KB
plugin-charts (index.js) 71.84KB 20.13KB
plugin-chatbot (index.js) 198.27KB 47.18KB
plugin-dashboard (index.js) 133.06KB 35.21KB
plugin-designer (index.js) 216.12KB 44.37KB
plugin-detail (index.js) 260.65KB 67.82KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 146.34KB 37.48KB
plugin-gantt (index.js) 168.31KB 41.45KB
plugin-grid (index.js) 215.46KB 58.88KB
plugin-kanban (index.js) 49.30KB 15.39KB
plugin-list (index.js) 114.13KB 28.14KB
plugin-map (index.js) 21.74KB 7.07KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.68KB 8.95KB
plugin-tree (index.js) 10.56KB 3.71KB
plugin-view (index.js) 85.79KB 21.35KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 114.58KB 37.60KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 24, 2026 19:41
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 24, 2026
Merged via the queue into main with commit 2b5f509 Sep 24, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-10288-properties-source-collapse branch September 24, 2026 19:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

1 participant