Skip to content

fix(app-shell): the condition builder's is-empty / is-not-empty compile a null-or-empty check on a non-boolean field, and the Rules tab passes field types (objectui#11894) - #11935

Merged
objectstack-fleet[bot] merged 10 commits into
mainfrom
claude/issue-11894-typed-empty-ops
Oct 8, 2026
Merged

objectstack-fleet[bot] merged 10 commits into
mainfrom
claude/issue-11894-typed-empty-ops

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #11894

Clause-②: no

What this changes

Studio's condition builder typed none of its value-less operators. is true / set compiled to the bare subject and is false / empty to ! plus the subject, whatever the field was. CEL's !, and a bare && operand, take a bool. So on a date, text or lookup field the predicate could not be evaluated, and a server-evaluated condition refused the write it guarded. The benchmark rule (record.status == 'done' && !record.due_date) refused every write to a done ticket with no such overload: !null, or !string when the ticket had a due date.

ConditionBuilder (packages/app-shell/src/views/metadata-admin/inspectors/ConditionBuilder.tsx):

  • Typed by the field catalog. A subject's value is read from its catalog row:
    • boolean when the type is in the spec's BOOLEAN_VALUE_TYPES (by reference);
    • otherwise the row of the ruled "is empty" table that the spec's expandEmptyOperator({ type, multiple }) assigns it. No local copy of the table.
  • Boolean field: reads is true / is false and compiles as before (S / !S).
  • Any other declared field: reads is not empty / is empty and compiles to a null-or-empty check:
    • null_only: S == null / S != null;
    • text: S == null || S == '' and its complement;
    • multi_value: S == null || size(S) == 0 and its complement;
    • a check of more than one clause is parenthesised when it is joined with other rows.
  • Undeclared subject (a context subject, a flow variable, a catalog row with no type): keeps today's words and today's form. Nothing on the row says which check it needs, and the dispatch ruled against guessing.
  • formula field: undeclared on purpose. Its value takes its returnType, which no catalog row carries. This follows the spec's own boolean comparand door, which defers a formula without a readable returnType. It also keeps "is true" on a boolean formula compiling as it did.
  • New row: Add condition seeds equals instead of truthy. S == '' is well typed for every value.
  • parse and stored conditions:
    • parse reads every new emission back into the row it was built as. It splits at top-level joins and respects parentheses and string literals. A lone compound check reads as one row, not two comparisons.
    • A stored !field on a typed non-boolean field no longer round-trips, so it opens in the raw editor exactly as written. This is the component's existing round-trip-or-raw rule, and the same non-retroactive shape ConditionBuilder.referenceValue.test.tsx pins. ⛔ No silent rewrite on load or on a later edit.
  • Fetched catalogs. When a catalog fetched by objectName lands after the first render, the stored value is re-read under the declared types, unless the author has already edited it. A builder-made check then reopens as rows, and a stored !field opens in the raw editor, the same way at a mount that fetches its fields as at one that is handed them.
  • Why == null and not has(): the server evaluates a validation rule or a hook condition over a record whose declared fields are all present. A field with no value holds null, so has() is true for an empty field. objectql's rule-validator contract says the same: test emptiness with != null, and keep has() for an undeclared key. Measured with the formula engine: has(record.due_date) on { due_date: null } is true.

Four new designer rows (en, zh) in metadata-admin/i18n.ts: engine.inspector.condition.op.isTrue / isFalse / isNotEmpty / isEmpty. The two existing rows stay for undeclared subjects. No language-pack key, no export, no published type member: ConditionBuilder.d.ts is not reachable from dist/index.d.ts (declaration-graph walk over the published 17.7.0 tarball; SchemaForm.d.ts reached, as the control).

The in-place producer change: the Studio Rules tab

The benchmark's own mount handed the builder no types. ObjectValidationsPanel's FieldOpt carried { name, label, hidden } only, so every field there read as undeclared and the rule still saved as !record.due_date. FieldOpt now carries type and multiple. Its fields memo copies e.def.type when it is a string, and multiple: e.def.multiple === true, the shape useObjectFields and ResourceEditPage already read.

The claim was amended for exactly this: card comment 6055312520 adds FieldOpt and the fields memo of studio-design/ObjectValidationsPanel.tsx, plus one pin beside it, to the File surface. No other region of that file differs from main.

The other mounts already passed the type: hook condition, action visible / disabled, page-block visibility and flow conditions through useObjectFields, and the schema-form condition widget through ResourceEditPage's catalog. None of them passes multiple, so the changeset says where the multi-value arm reaches a multi-capable type.

Evidence (head ec8411f3)

Pins evaluate the CEL the builder emits with @objectstack/formula's ExpressionEngine.evaluate(expr, { record, previous }), the call objectql's checkPredicate makes:

  • ConditionBuilder.typedEmptyOps-11894.test.tsx (26 tests):
    • the benchmark rule on a date field is true with no due date and false with one;
    • text "is empty" is true for '' and for null;
    • the multi-value arm on tags, and on a lookup with multiple: true against a single lookup;
    • controls: a boolean compiles as before; an undeclared field and a formula keep !S / S;
    • lossless round trip for eight shapes; a new row seeds equals; the fetched-catalog re-read;
    • instrument control: the old emission faults (no such overload) in the same engine, both ways.
  • ObjectValidationsPanel.typedEmptyOps-11894.test.tsx (5 tests), mounting the Rules tab:
    • building "due date is empty" writes record.status == 'done' && record.due_date == null, which evaluates true with no due date and false with one;
    • that rule reloads as the "is empty" row;
    • the old builder's !record.due_date opens in the raw editor verbatim;
    • a lookup declared multiple builds "is empty" as record.status == 'done' && (record.watchers == null || size(record.watchers) == 0), which evaluates true for [] and for null and false for a member;
    • control: a boolean field on the same draft writes !record.urgent / record.urgent.

Ablations, each through objectstack scripts/ablation-replace.mjs in wrap mode. Each mutation was proven on disk (anchor count and blob), and each restore was proven: the blob equals HEAD and git diff HEAD is empty. No build leg in either: both pins import the subject by relative path, not through a package export.

  • compileRow's falsy arm emits !S again (at d235d45): the builder pin went 10 failed / 16 passed, the date pin among the reds. Expected direction: red.

  • The type copy is dropped from the Rules tab memo (at ec8411f3): the panel pin went 5 failed / 5, the benchmark pin among the reds. The multiple pin and the boolean control are red too: without a type every field there reads as undeclared, and the operators are worded by type.

  • The multiple copy is dropped from the same memo (at 4cc894f): 1 failed / 4 passed. The red one is the multiple pin (received record.status == 'done' && record.watchers == null). That line is unchanged since.

  • Tests at 00a030d, before the later merges of main, under the shared verify lock: 374 files / 3416 tests passed. The set is every app-shell test whose import closure reaches ConditionBuilder.tsx, plus the app-shell tests that read the designer i18n table. The closure is derived from static relative imports, import(), side-effect imports and vi.mock specifiers, so it is blind to a test that reaches the builder through a package specifier or reads sources from disk.

  • Tests at ec8411f3, after merging main 1c51e973: 17 files / 177 tests passed. They are every ObjectValidationsPanel*.test.tsx, every ConditionBuilder*.test.tsx (objectui#11895 changed nine of them), and useObjectFields.draftOverlay-11895. The full package suite is CI's.

  • Bundle Analysis on ec8411f3: success. Console Performance Budget: eager closure 3308.0 KB gzip (336 chunks) against a budget of 3312.0 KB, PASS; main entry chunk 71.3 KB against 350 KB.

  • pnpm --filter @object-ui/app-shell type-check: exit 0 at ec8411f3, after a turbo build of its dependency closure. It runs tsc -p tsconfig.test.json too, which types both new test files.

  • pnpm exec eslint: 0 errors at ec8411f3. Across the panel and both pins there is 1 warning, in the panel: the pre-existing set-state-in-effect that main's own ObjectValidationsPanel.tsx blob carries. ConditionBuilder.tsx carries the 7 warnings of its baseline (measured at d235d45; the file is unchanged since).

  • check:changeset-claims, check:pending-changeset-literals, check:new-line-citations, check:control-bytes, check:test-path-roots, check:i18n-keys, check:i18n-drift, check:i18n-designer-parity, check:vi-mock-specifiers, check-changeset-presence, check-changeset-no-major: all exit 0 at ec8411f3.

  • check:spec-floors: NOT MEASURED locally (it needs a workspace build). The fact it judges was read directly: @objectstack/spec@17.6.0, app-shell's declared floor, exports BOOLEAN_VALUE_TYPES, expandEmptyOperator and EmptyOperatorArm from ./data.

  • The full app-shell package suite and a browser pass are NOT MEASURED locally; CI runs the farm. The eager budget is the Bundle Analysis reading above.

Acceptance notes

  • class: a · reach: named producer: Studio ConditionBuilder's compare rows (greater than, less than, and the at-least / at-most symbols) emit an unguarded ordering comparison such as record.amount greater-than 100; evaluated with @objectstack/formula ExpressionEngine.evaluate on a record whose amount is null it faults (no such overload: dyn of null greater-than int), and a date field does the same, so a server-evaluated validation rule built that way refuses every write where the field is empty (fail-closed) · evidence: engine probe at objectui d235d45 with formula 17.7.0; the objectql rule-validator docblock records that ordering comparisons still fault over a total record and that a lint null-guard gate exists for it · dedupe words: condition builder greater than null field no such overload · ConditionBuilder ordering operator null guard · validation rule compare empty field refuses write
  • carrier: this branch's next round (or the PR author) · noted, not filed: useObjectFields defaults a field def with no string type to text, so such a field takes the text arm here; a spec-valid field always declares a type, so this was not measured as reachable; adjacent to objectui#11895's field-source region
  • carrier: objectui#11895's field-source change (useObjectFields) or this branch's next round · noted, not filed: neither useObjectFields (ObjectFieldInfo) nor ResourceEditPage's catalog carries multiple, so a lookup, user, select or file field declared multiple true reaches the builder as a single-value type and its is-empty check is S == null, which is well typed but reads an empty list as not empty; tags, multiselect and checkboxes are multi-value by type and are right. The list filter builder documents the same type-only limit.

Implemented by Claude Code, session https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8 (the dispatching seat's session).


Generated by Claude Code

claude added 5 commits October 8, 2026 06:39
…ompile a null-or-empty check on a non-boolean field (objectui#11894)

The two value-less operators are typed by the field catalog: a boolean field
keeps "is true" / "is false" (bare subject / `!`), any other declared field
gets "is not empty" / "is empty", compiled by the spec's `expandEmptyOperator`
arm (null only; null or ''; null or an empty list). An undeclared subject keeps
the untyped pair and its emission. A new row starts on `equals`. `parse` reads
every new emission back into the same row; a stored `!field` on a non-boolean
field no longer round-trips and opens in the raw editor verbatim.

Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8
Co-authored-by: Claude <noreply@anthropic.com>
… field's type (objectui#11894)

ObjectValidationsPanel's field list carried names, labels and hidden flags
only, so at the validation-rule mount every field read as undeclared and the
benchmark rule still saved as `!record.due_date`. FieldOpt now carries the
field's `type` and `multiple`, copied from the draft the way useObjectFields
and ResourceEditPage already read them. Pin through the panel; changeset
re-read against the final diff (the multi-value sentence is qualified).

Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8
Co-authored-by: Claude <noreply@anthropic.com>
claude added 3 commits October 8, 2026 08:51
Resolves ObjectValidationsPanel's FieldOpt and fields memo as a union with
objectui#11931: `type` (now read by the New menu's presets and by the
condition builder), `system` and `multiple`.

Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8
Co-authored-by: Claude <noreply@anthropic.com>
… condition builder (objectui#11894)

Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8
Co-authored-by: Claude <noreply@anthropic.com>
…g objectui#11931

The Rules tab passes `type` since objectui#11931; this change adds `multiple`
there.

Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

❌ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 336 chunks) 3308.0 KB 3307.0 KB
Main entry chunk (gzip) 71.3 KB 350 KB
Entry file index-Cp7o1vF1.js —
Status FAIL —

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.

Which half objected:

Eager-closure half Verdict
Aggregate closure ceiling ❌ over its ceiling
Per-chunk ceilings ✅ pass
Per-chunk membership (declared packages) ✅ pass
Ceiling sensitivity (headroom) ✅ pass
Ceiling freshness (checkout vs. base branch) ✅ pass

📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 17.82KB 6.58KB
app-shell (runtime-config.js) 22.59KB 7.89KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
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) 27.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 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) 586.28KB 141.14KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 240.39KB 67.09KB
fields (index.js) 266.92KB 67.47KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 38.37KB 10.31KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 41.50KB 11.82KB
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.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
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) 16.04KB 3.92KB
plugin-calendar (index.js) 53.39KB 15.52KB
plugin-charts (index.js) 84.71KB 23.25KB
plugin-chatbot (index.js) 199.63KB 47.46KB
plugin-dashboard (index.js) 144.82KB 39.17KB
plugin-designer (index.js) 231.46KB 48.87KB
plugin-detail (index.js) 248.57KB 65.47KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 176.62KB 45.75KB
plugin-gantt (index.js) 179.17KB 45.07KB
plugin-grid (index.js) 248.06KB 68.77KB
plugin-kanban (index.js) 52.17KB 16.37KB
plugin-list (index.js) 117.45KB 29.33KB
plugin-map (index.js) 27.24KB 9.03KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 39.10KB 11.81KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 91.07KB 22.93KB
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.81KB 3.58KB
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) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
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) 1.13KB 0.65KB
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) 5.78KB 2.70KB
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 (node-slots.js) 7.18KB 2.34KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
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) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.25KB
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

claude added 2 commits October 8, 2026 11:05
objectui#11931 was reverted on main (3c888c6), so ObjectValidationsPanel's
FieldOpt and fields memo carry this change's own `type` and `multiple` again;
`system` and the presets are gone with the revert.

Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8
Co-authored-by: Claude <noreply@anthropic.com>
…ain after the objectui#11931 revert

The builder pin's metadata-client stub answers `withPreviewDrafts`, which
useObjectFields calls since objectui#11895. The panel pin and the changeset
no longer name the reverted presets: this change is again what makes the
Rules tab pass `type`, as well as `multiple`.

Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 336 chunks) 3308.0 KB 3312.0 KB
Main entry chunk (gzip) 71.3 KB 350 KB
Entry file index-BnL5pmCY.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) 17.82KB 6.58KB
app-shell (runtime-config.js) 22.59KB 7.89KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
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) 27.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 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) 587.16KB 141.51KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 240.39KB 67.09KB
fields (index.js) 266.92KB 67.47KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 41.50KB 11.82KB
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.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
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) 16.04KB 3.92KB
plugin-calendar (index.js) 53.39KB 15.52KB
plugin-charts (index.js) 84.71KB 23.25KB
plugin-chatbot (index.js) 199.63KB 47.46KB
plugin-dashboard (index.js) 144.20KB 38.95KB
plugin-designer (index.js) 231.46KB 48.87KB
plugin-detail (index.js) 248.57KB 65.47KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 176.62KB 45.75KB
plugin-gantt (index.js) 179.17KB 45.07KB
plugin-grid (index.js) 248.06KB 68.79KB
plugin-kanban (index.js) 52.17KB 16.37KB
plugin-list (index.js) 117.58KB 29.37KB
plugin-map (index.js) 27.24KB 9.03KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 39.10KB 11.81KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 91.07KB 22.93KB
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.81KB 3.58KB
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) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
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) 1.13KB 0.65KB
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) 5.78KB 2.70KB
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 (node-slots.js) 7.18KB 2.34KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
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) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.25KB
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 October 8, 2026 11:40
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 8, 2026 11:40
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 8, 2026
Merged via the queue into main with commit 247f503 Oct 8, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-11894-typed-empty-ops branch October 8, 2026 11:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants