Skip to content

fix(app-shell): declare the spec defaults lockRecord and interrupting already apply - #9339

Draft
os-tesla wants to merge 4 commits into
mainfrom
claude/issue-9277-declared-default-lockrecord-interrupting
Draft

fix(app-shell): declare the spec defaults lockRecord and interrupting already apply#9339
os-tesla wants to merge 4 commits into
mainfrom
claude/issue-9277-declared-default-lockrecord-interrupting

Conversation

@os-tesla

Copy link
Copy Markdown
Collaborator

Fixes #9277

approval.config.lockRecord and boundary_event.boundaryConfig.interrupting now declare the defaultValue the installed spec already applies, so the two checkboxes stop asserting the opposite of what the runtime does.

Two table lines, six pin rows, one exact-count pin moved.

The spec probe triage could not run — it reads CONFIRMING

@objectstack/spec was not installed at the triage seat, so both spec-side claims arrived NOT MEASURED. Run here against the installed 17.4.0. The probe prints the resolved package.json path and version before it parses anything, so a missing install would have been an instrumented failure, never a quiet negative answer.

probe materialised for an omitted key
ApprovalNodeConfigSchema.safeParse({ approvers: [{ type: 'user', value: 'u1' }] }) lockRecord: true
FlowNodeSchema.safeParse(... boundary_event ...).data.boundaryConfig interrupting: true

Positive controls in the same output — this is what makes the two absences a reading rather than a dead probe. The approval parse returns behavior: 'first_response', onEmptyApprovers: 'admin_rescue' and maxRevisions: 3 alongside lockRecord, and the form declares a matching 'first_response' / 'admin_rescue' / '3' for all three; the boundary parse returns eventType: 'error' alongside interrupting, and the form declares 'error' for it.

So the declared value is 'true' on both — String(specDefault), derived from the installed spec, never typed from taste, exactly as the defaultValue doc comment requires.

The probe was taken past the first accepted value

Prompted by objectui#9336, which the sibling seat found by doing this: I probed both boolean values of each key, and each against every sibling enum value (all four behavior values, all four eventType values). No sibling constraint on either key — true and false are both accepted everywhere, and an omitted key materialises true everywhere.

That negative carries a positive control that fires: the same probe run asserts EndConfigSchema, and it does surface the objectui#9336 superRefine, quoting both halves of it. A probe that can see that constraint and sees none here is a reading.

This also matters to the pin: the stored-false rows assert a shape the spec genuinely accepts.

The harm, and why "declares nothing" was not neutral

Since objectui#8451 the boolean control seeds its checked state from defaultValue, so an absent declaration does not render as "no claim" — it renders as unchecked. The screen was already asserting something, and it was false:

  • Lock record unchecked told the author the record stays editable while the request is pending. The runtime locks it.
  • Interrupting unchecked told the author the boundary event leaves its host activity running. It cancels it.

Note on how this survived: the canvas node seeder in flow-canvas-parts.tsx already writes lockRecord: true and interrupting: true into a freshly dropped node, so palette-created nodes stored the key and drew correctly. Only nodes that omit the key — imported flows, JSON-authored nodes, older drafts — rendered the lie. The seeder's values and the spec's agree with the declarations added here.

Where the pin count came from

⚠️ The card wrote "10 to 12" assuming objectui#9278 does not exist. I did not use that number as a constant. Read live, immediately before opening this pull request:

origin/main = a686403b3
  FlowNodeInspector.declaredDefault.test.tsx
    'exactly ten fields declare a defaultValue'                   <- base = TEN
    'the select-kind half of the declaration surface - seven of the ten'

Base ten, plus this card's two = twelve. Re-read three times as origin/main advanced under me (0f3d153, then 80830ab, then a686403) — the pin read ten at all three, because objectui#9278's PR objectui#9337 is still open and unmerged.

The select-kind count on the same pin, which the PM flagged separately. Its numerator does not move here: both fields added are boolean-kind, so the select-kind half stays at seven, while its denominator follows the field count. That line now reads seven of the twelve — base-plus-own arithmetic applied to the half that actually moved.

Reddening this pin is expected, not routed around: its own comment says a change that adds a further declaration moves the line. The comment is updated with the count, and I added a standing note that the number is a live base plus the landing card's own additions, naming both cards, so whoever lands second re-reads rather than inherits. PR objectui#9337 added an equivalent instruction to the same comment — the two are compatible in wording and intent.

If objectui#9337 lands first, the base becomes eleven and the correct answer here is thirteen (and the select-kind line becomes eight of the thirteen). That is a re-read, not an arithmetic I can pre-commit.

Ablation leg

Run from the committed state, one declaration removed at a time. Each leg proves the mutation reached the disk before reading any result (declaration count 4 to 3 in the file, plus a blob hash differing from the HEAD blob), and proves the restore by blob-hash equality with the HEAD blob and an empty git diff HEAD — never by an exit code. A trap restores on any early exit.

leg reddens stays green
drop lockRecord's declaration approval.lockRecord declares ..., approval.lockRecord draws a CHECKED box ..., the exact-count pin both interrupting rows
drop interrupting's declaration boundaryConfig.interrupting declares ..., boundaryConfig.interrupting draws a CHECKED box ..., the exact-count pin both lockRecord rows

The seeding assertion is the one the acceptance names, and it reddens on the rendered control:

AssertionError: the RENDERED box carries the state the runtime applies
  - the defect was that it drew the opposite: expected false to be true
AssertionError: the RENDERED box says the host activity IS cancelled,
  which is what the runtime does: expected false to be true

Zero cross-contamination in either direction, so each leg measures its own field rather than knocking the file over.

The seeding rows read the spec rather than the table, which is why dropping a declaration reddens them on the control the author sees instead of only on the declaration row above.

And the pin was written before the code

The "unmodified" arm is the real base tree, not a reconstruction: the six new rows plus the moved count were committed to the working tree first and run against untouched flow-node-config.ts, giving 5 failed | 23 passed, with the two seeding rows failing expected false to be true — the defect itself, read on screen. The two stored-false rows passed on the base tree, correctly: an undeclared boolean already draws unchecked for a stored false.

Verification

what result
vitest run packages/app-shell/ (6 shards, all under the shared verify lock) 691 files, 6728 passed, 1 skipped
turbo run type-check for app-shell + all three dependents 39 tasks successful
turbo run lint for the same set 5 tasks successful, 0 errors in every package
ablation legs A and B, restore proven as above
scripts/__tests__/check-changeset-claims.test.ts (names flow-node-config) 25 passed

Gate family derived by hand from package.json plus .github/workflows/ — this repo has no dispatch-gates script. All green: check:control-bytes, check-changeset-presence, check:changeset-fixed, check:changeset-no-major, check:changeset-claims (report-only, see below), check:new-line-citations, check:designer-field-key-parity, check:i18n-designer-parity, check:i18n-drift, check:i18n-dead-keys, check:comment-mask-corpus, check:test-path-roots, check:vi-mock-specifiers, quick-reference:check, check:unreferenced-sources.

Two readings worth stating rather than hiding:

  • half-state-patrol is not in this diff's gate family. My first invocation spelled it scripts/check-half-states.mjs and exited 1 with MODULE_NOT_FOUND — that is NOT MEASURED, not a red gate. The real script is scripts/pm/check-half-states.mjs, and it sweeps the issue board, not the tree.
  • A root-level eslint . --no-inline-config reports 95 errors across 79 files. That is the configuration CI never uses, in the shape AGENTS.md warns about for prettier: the gate is per-package eslint . through turbo run lint, which reports 0 errors everywhere. My two changed files are 0 errors, 0 warnings in both invocations.

The dependent-set membership read

Done here rather than inherited, because a declared narrowing can be the load-bearing one:

depends on @object-ui/app-shell  ->  apps/console                    (declares type-check)
                                     examples/byo-backend-console    (declares type-check)
                                     examples/console-starter        (declares type-check)
                                     packages/app-shell itself       (declares type-check)
apps/site                        ->  does NOT depend on app-shell

@object-ui/site is out of reach structurally, not because it is "a docs app": its @object-ui/* dependencies are the plugin family, components, core, fields, layout, react, types and example-schema-catalog — app-shell is not among them. All three real dependents were type-checked and linted.

Blast radius beyond the package (objectui#9273)

  • examples/schema-catalog/: out of reach, measured. It contains no flow-node metadata at all — no approval, boundary_event, lockRecord or interrupting anywhere in its sources. Nothing there can render these two controls.
  • Consumers of the changed module, enumerated rather than assumed: 25 files import flow-node-config, of which two live outside metadata-admin (providers/ExpressionProvider.tsx and scripts/__tests__/check-changeset-claims.test.ts). That is exactly why the whole package was run in shards instead of narrowing to the inspectors directory.
  • Neither field is a showWhen controller (25 controllers exist in the table, and neither key is referenced by any of them), so declaring a default here cannot move which fields are on screen. The controllerAdmits read site is untouched.
  • Count-shaped prose: the two counts on the acceptance pin are the ones this change moves, and both are updated. See below for one I looked at and deliberately left alone.

What check:changeset-claims asked, and the answer

It flagged .changeset/6499-inactive-values-retained.md, whose body names flow-node-config.ts and claims "all 33 gated fields across the descriptor tables". The paragraph did not go false: showWhen occurrences in that file are 36 on the base and 36 at this head, this change adds no gate, and neither new declaration belongs to a gated field. No correction needed.

Measured, reported, deliberately NOT repaired here

  • loop.config.iteratorVariable's placeholder is wrong. The form hints currentItem; LoopConfigSchema.safeParse({ collection: '{x}' }) applies item. They disagree, so an author writing a currentItem reference against the form's own hint gets an unresolved reference. map.config.iteratorVariable hints item and the spec also applies item — those agree. Both are text controls, which read placeholder and never defaultValue, so this is not the same repair and is out of scope for this round per the dispatch. Measured and handed back for filing.
  • screen.waitForInput is the offline table's one remaining undeclared boolean, and it is legitimately undeclared: ScreenConfigSchema.safeParse({}) returns {}, materialising no default. Not a third instance of this class.
  • A stale count in a test comment. FlowNodeInspector.declaredDefault.test.tsx justifies one row with "the offline table has no undeclared boolean to measure — it carries exactly two boolean fields and both declare one". Measured: the table carries five boolean fields (screen.waitForInput, approval.lockRecord, approval.escalation.enabled, approval.escalation.notifySubmitter, boundary_event.boundaryConfig.interrupting), three of them undeclared before this change. The sentence was already false on the base tree, and it is a symptom of precisely the blind spot this card closes. Left untouched: it is another card's rationale, the row it justifies still measures something real, and objectui#9337 is editing this same file right now.
  • .changeset/6830-flownode-select-declared-default.md says "Seven of the ten declaring fields are select-kind". This change moves the denominator. Left as written: it is a historical measurement in another card's pending release note, check:changeset-claims excludes counts from its scope by a measured decision, and any number written there now would be falsified again by objectui#9337. Flagged rather than edited.

Coordination

  • objectui#9337 (for objectui#9278) edits this same file and this same pin. It is open, draft and unmerged. It computed eleven from the same base of ten. Whichever lands second meets a red pin and re-derives — that is the designed behaviour, not a conflict to route around.
  • PR objectui#9279 (for objectui#9109) adds the UNDECLARED_REGISTER containing exactly these two rows (approval:config / lockRecord and boundary_event:boundaryConfig / interrupting). It has not landed: measured 0 hits for UNDECLARED_REGISTER on origin/main, with controllerAdmits at 12 hits and defaultValue at 8 hits in the same files as the positive control that the grep could fire. Per the acceptance, no register row was invented here. ⚠️ For the PM's ordering: if objectui#9279 lands after this change, its two register rows describe a divergence that no longer exists, and that ledger re-measures itself — so it will need those rows dropped on its side.
  • FlowConfigField.defaultValue's doc comment was left alone on purpose: objectui#9279 rewrites that exact block.

Changeset

patch, matching objectui#8451 and objectui#6620, the two nearest precedents. Justified by measurement: no prop, option or metadata key is added and no authored document changes meaning — what changes is that two rendered controls stop contradicting the runtime. major is unavailable in this repo's single fixed group, and this is not breaking, so no **BREAKING** carrier is needed.

Session reference, as a code span so it survives a body edit: https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ

🤖 Generated with Claude Code

https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ


Generated by Claude Code

… already apply

`approval.config.lockRecord` and `boundary_event.boundaryConfig.interrupting`
declared no `defaultValue` while the installed `@objectstack/spec` (17.4.0)
materialises `true` for both keys when a node omits them.

Since the boolean config control began seeding its checked state from
`defaultValue`, declaring nothing is not a missing claim on these two controls
— it is the wrong one. Both boxes drew UNCHECKED: the inspector told the author
the record stays editable while an approval is pending (the runtime locks it)
and that a boundary event leaves its host activity running (it cancels it).

Both values are derived from the installed spec, and the acceptance pin
re-derives them from `ApprovalNodeConfigSchema` / `FlowNodeSchema` at assertion
time rather than restating the literal, so an upstream flip reddens instead of
drifting. Three rows per field: the declaration against the contract, the
RENDERED checkbox (which reads the spec directly, so dropping a declaration
reddens it on the control the author sees), and the stored-`false` row that an
always-checked control fails.

The exact-count declaration pin moves with them, count and comment together.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 52 chunks) 3116.4 KB 3134.8 KB
Main entry chunk (gzip) 144.3 KB 350 KB
Entry file index-CBFq-cEP.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.69KB 6.21KB
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) 502.06KB 115.19KB
core (index.js) 8.52KB 3.41KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 211.58KB 58.68KB
fields (index.js) 247.91KB 62.51KB
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) 32.15KB 10.49KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.95KB
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) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
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) 49.04KB 13.93KB
plugin-charts (index.js) 71.52KB 19.98KB
plugin-chatbot (index.js) 195.35KB 46.52KB
plugin-dashboard (index.js) 131.24KB 34.61KB
plugin-designer (index.js) 215.95KB 44.33KB
plugin-detail (index.js) 253.51KB 65.88KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.79KB 34.19KB
plugin-gantt (index.js) 166.95KB 41.04KB
plugin-grid (index.js) 211.58KB 57.48KB
plugin-kanban (index.js) 46.00KB 14.30KB
plugin-list (index.js) 112.59KB 27.66KB
plugin-map (index.js) 20.43KB 6.81KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.93KB
plugin-timeline (index.js) 30.07KB 8.74KB
plugin-tree (index.js) 9.55KB 3.32KB
plugin-view (index.js) 84.43KB 20.80KB
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) 94.03KB 31.02KB
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 (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.66KB 2.50KB
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) 14.82KB 4.99KB
types (ai.js) 0.20KB 0.17KB
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) 4.73KB 2.28KB
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) 14.27KB 5.47KB
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

…aration pin

objectui#9337 (card objectui#9278) landed while this branch was open and added
`end.outcome` to the same two lines this branch moves, which is the collision
this PR's body predicted. Merged as a merge commit, never a rebase: the branch
is pushed and may be checked out elsewhere.

`flow-node-config.ts` auto-merged — the two sides add to different node types.
The conflict was `FlowNodeInspector.declaredDefault.test.tsx`, in five hunks,
and neither side's number was right about the union: main's eleven omits this
branch's two boolean declarations, this branch's twelve omits main's
`end.outcome`.

Resolved by RE-DERIVING off `FLOW_NODE_CONFIG`, not by picking a side. Swept
`fieldsForNodeType` over the same 24 node types the pin sweeps:

  13 fields declare a defaultValue (8 select, 4 boolean, 1 number)
   8 of the 13 are select-kind

Both one-sided resolutions were run first and both reddened for the right
reason: main's file verbatim misses `approval.lockRecord` and
`boundary_event.boundaryConfig.interrupting`; this branch's file verbatim
misses `end.outcome`.

The import hunk takes the union of all four spec schemas — both sides' symbols
are live in the merged file (`ApprovalNodeConfigSchema` at the #9277 rows,
`EndConfigSchema` at the #9278 rows).

Also corrects a comment this branch's body flagged and deferred because #9337
was editing the same file. That reason has expired. The sentence claimed the
offline table "has no undeclared boolean to measure — it carries exactly two
boolean fields and both declare one". Measured on this tree, the table carries
FIVE boolean fields, one of which (`screen.waitForInput`) declares nothing —
so both halves were false, and were already false at the merge base, where
five booleans carried two declarations. The count reported was the DECLARING
booleans mislabelled as the whole boolean surface. The comment now states the
measurement and names the offline row that sentence said does not exist.

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

The merge commit's comment names `screen.waitForInput` as the offline
undeclared boolean the old sentence claimed did not exist. Naming it without
measuring it lays a trap: the next reader sees an undeclared boolean in a file
whose whole subject is missing declarations, and declares one.

Measured on the installed `@objectstack/spec` (17.4.0): `waitForInput` is
`z.boolean().optional()` with no `.default(...)`. An omitted key materialises
nothing, so under this file's own stated convention — a `defaultValue` mirrors
what the spec applies to an omitted key — there is nothing for a declaration
to mirror, and writing one would invent a default the runtime never applies.

That is the opposite direction from `approval.config.lockRecord` and
`boundary_event.boundaryConfig.interrupting`, where the spec does materialise
`true` and the declaration was therefore missing. Comment only; no assertion
moves.

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

Copy link
Copy Markdown
Collaborator Author

Round 2 — merged origin/main, and the predicted collision resolved by re-derivation

The contingency this body wrote has happened. PR objectui#9337 (card objectui#9278) landed in 2b1d0de9f, so the base moved out from under the count pin while this branch was open.

Merged as a merge commit (eb6ca6e92), never a rebase or an amend — the branch was already pushed.

  • merged head pushed: 7cae0d19c2219d5501880cf2bed4bec4016a45aa
  • merge commit parents: 6e1b4c415 (this branch) + b2bb8134d (origin/main)

The conflict, and why neither side was right

flow-node-config.ts auto-merged cleanly — the two cards add to different node types. The conflict was FlowNodeInspector.declaredDefault.test.tsx, in five hunks. Both sides were correct about their own base and wrong about the union.

Reproduced first, both directions, before resolving anything. Each side's file was checked out verbatim against the merged flow-node-config.ts:

leg pin title it carried reddened because
origin/main's file verbatim exactly eleven fields declare a defaultValue derived set adds approval.lockRecord and boundary_event.boundaryConfig.interrupting
this branch's file verbatim exactly twelve fields declare a defaultValue derived set adds end.outcome
AssertionError: expected [ 'approval.behavior', ...(12) ] to deeply equal [ 'approval.behavior', ...(10) ]
+   "approval.lockRecord",
+   "boundary_event.boundaryConfig.interrupting",

AssertionError: expected [ 'approval.behavior', ...(12) ] to deeply equal [ 'approval.behavior', ...(11) ]
+   "end.outcome",

The numbers were RE-DERIVED, not computed as 11 + 2

Swept off FLOW_NODE_CONFIG through fieldsForNodeType, over the same 24 node types the pin sweeps (FLOW_NODE_TYPE_OPTIONS plus the five off-picker types), on the merged tree:

declaringCount = 13
byKind         = { select: 8, boolean: 4, number: 1 }
selectCount    = 8

11 + 2 also gives 13. The two agreed; that is a result, not the method — the number here came off the table. The comment now says so, and says the next lander re-reads rather than inherits.

The two count lines, against each parent:

# vs this branch's pre-merge head 6e1b4c415
-  it('exactly twelve fields declare a defaultValue, and these are they', () => {
+  it('exactly thirteen fields declare a defaultValue, and these are they', () => {
-      'the select-kind half of the declaration surface — seven of the twelve',
+      'the select-kind half of the declaration surface — eight of the thirteen',
+      'end.outcome',

# vs origin/main b2bb8134d
-  it('exactly eleven fields declare a defaultValue, and these are they', () => {
+  it('exactly thirteen fields declare a defaultValue, and these are they', () => {
-      'the select-kind half of the declaration surface — eight of the eleven',
+      'the select-kind half of the declaration surface — eight of the thirteen',
+      'boundary_event.boundaryConfig.interrupting',

The select-kind numerator held at eight: this branch's two additions are both boolean-kind, so only the denominator moved. The import hunk took the union of all four spec schemas — every one of them is live in the merged file (ApprovalNodeConfigSchema and FlowNodeSchema at the objectui#9277 rows, EndConfigSchema and FlowNodeSchema at the objectui#9278 rows).

The stale comment this body deferred — decided, not deferred again

This body left a false sentence in place because "objectui#9337 is editing this same file right now". That reason expired when objectui#9337 landed, so it was decided this round: the sentence is still false on the merged tree, and it was corrected.

It claimed the offline table "has no undeclared boolean to measure — it carries exactly two boolean fields and both declare one". Measured by the same sweep:

boolean fields declaring undeclared
merge base 0f3d15314 5 2 3
merged tree 5 4 1

Both halves false, and false at the merge base too — the "two" was the declaring booleans mislabelled as the whole boolean surface. The comment now carries the measurement and names screen.waitForInput as the undeclared one.

A second commit (7cae0d19c) then measured why it is undeclared, because naming it without that is a trap: the installed @objectstack/spec 17.4.0 types waitForInput as z.boolean().optional() with no .default(...), so nothing is materialised for an omitted key and there is no spec answer for a declaration to mirror. It is not another instance of this card's class, and the comment says so in as many words.

Verification on the merged tree

All heavy runs through the shared verify lock; VERDICT lines quoted, never a bare exit code.

what verdict
vitest run packages/app-shell/src/views/metadata-admin/ VERDICT command-exit 0254 files, 2689 passed, 1 skipped
the count pin, named ✓ ... exactly thirteen fields declare a defaultValue, and these are they
the select-kind pin, named ✓ ... every select-kind declaring field renders its declared default
turbo run type-check — app-shell + all three dependents VERDICT command-exit 039 tasks successful
turbo run lint — same set VERDICT command-exit 05 successful, 0 errors
whole file after the second commit VERDICT command-exit 032 passed (32), lint warnings unchanged at 2995

Gates re-run on the merged tree, all exit 0: check-changeset-fixed, check-changeset-no-major, check-changeset-claims (report-only), check-changeset-presence (7 changesets for 13 published source files), check-control-bytes, check-test-path-roots, check-new-cross-file-line-citations.

check:changeset-claims re-flagged .changeset/6499-inactive-values-retained.md for its "all 33 gated fields" claim. Still true: this merge adds no showWhen group, and the mechanical pin behind that count is inside the 254 green files.

Dependent-set membership read — redone here, not inherited

Read off every manifest naming the package under change:

depends on @object-ui/app-shell  ->  apps/console                  (type-check: YES)
                                     examples/byo-backend-console  (type-check: YES)
                                     examples/console-starter      (type-check: YES)
apps/site (@object-ui/site)      ->  declares type-check, but does NOT name app-shell

Worth stating precisely, because the conclusion and the reason come apart: @object-ui/site does declare a type-check script. It is out of the set because it is not a dependent at all — its @object-ui/* dependencies are the plugin family plus components, core, fields, layout, react, types and example-schema-catalog. Structurally out of reach, not script-less.

Untouched, deliberately

Still draft, no auto-merge, not enqueued, and needs:contract-review neither added nor removed — all of that belongs to the PM.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 52 chunks) 3116.8 KB 3134.8 KB
Main entry chunk (gzip) 144.4 KB 350 KB
Entry file index-gwSuqRjJ.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.69KB 6.21KB
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) 502.06KB 115.19KB
core (index.js) 8.52KB 3.41KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 211.58KB 58.68KB
fields (index.js) 247.91KB 62.51KB
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) 32.15KB 10.49KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.95KB
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) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
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) 49.04KB 13.93KB
plugin-charts (index.js) 71.52KB 19.98KB
plugin-chatbot (index.js) 195.35KB 46.52KB
plugin-dashboard (index.js) 131.24KB 34.61KB
plugin-designer (index.js) 215.95KB 44.33KB
plugin-detail (index.js) 253.51KB 65.88KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.79KB 34.19KB
plugin-gantt (index.js) 166.95KB 41.04KB
plugin-grid (index.js) 211.58KB 57.48KB
plugin-kanban (index.js) 46.00KB 14.30KB
plugin-list (index.js) 112.59KB 27.66KB
plugin-map (index.js) 20.43KB 6.81KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.93KB
plugin-timeline (index.js) 30.07KB 8.74KB
plugin-tree (index.js) 9.55KB 3.32KB
plugin-view (index.js) 84.43KB 20.80KB
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) 94.03KB 31.02KB
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 (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.66KB 2.50KB
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) 14.82KB 4.99KB
types (ai.js) 0.20KB 0.17KB
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) 4.73KB 2.28KB
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) 14.27KB 5.47KB
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

Copy link
Copy Markdown
Collaborator Author

Contract review

Head judged: ec66474d65a2e043c17d884cae818d739ad292e2 (this seat merged origin/main in after the re-derive round; 7cae0d19c was behind).

① Derived judgments

# move judged
1 approval.config.lockRecord gains defaultValue: 'true' correct, and derived rather than typed
2 boundary_event.boundaryConfig.interrupting, same correct
3 the spec probe was taken PAST the first accepted value this is what makes the negative a reading
4 the exact-count pin re-derived to thirteen / eight of the thirteen correct, and arrived at the right way
5 the stale :326 sentence fixed with a measurement correct, and it was worse than reported

1 & 2 — what the declaration asserts, and why "declares nothing" was not neutral. Since objectui#8451 the boolean control seeds its checked state from defaultValue, so an absent declaration does not render as "no claim" — it renders as unchecked. The screen was already asserting something and it was false: Lock record unchecked told the author the record stays editable while the request is pending (the runtime locks it); Interrupting unchecked told them the boundary event leaves its host activity running (it cancels it). Both declarations are String(specDefault) off the installed @objectstack/spec 17.4.0, never typed from taste, and the pin re-derives them from that schema rather than restating the literal.

3 — the leg I most want on the record. The probe ran both boolean values of each key against every sibling enum value (all four behavior, all four eventType), and it carries a control that FIRES: the same run asserts EndConfigSchema and does surface objectui#9336's superRefine, quoting both halves. ⭐ A probe that can see a constraint of that shape and sees none here is a reading, not a dead probe. The positive controls sit in the same parse output — behavior: 'first_response', onEmptyApprovers: 'admin_rescue', maxRevisions: 3 beside lockRecord; eventType: 'error' beside interrupting — so an empty answer could not have passed for a negative one.

4 — how thirteen was reached. objectui#9337 landed mid-flight and took the pin's base from ten to eleven, which turned the merge into a real conflict in five hunks. The seat did not pick a side:

  • It reproduced both one-sided resolutions against the merged flow-node-config.ts first — origin/main's file fails exactly eleven, this branch's file fails exactly twelve — each mutation proven on disk with cmp before it was read and the conflicted file restored by an EXIT/INT/TERM trap. ⇒ picking either side was mechanically excluded before any number was written.
  • It then re-derived off the tree: a throwaway probe imported fieldsForNodeType and swept the same 24 node types the pin sweeps, giving declaringCount=13, byKind={select:8, boolean:4, number:1}.

⭐ And it derived that before reading my prediction back. I had predicted 13 and eight of the thirteen; the report says so explicitly and adds that 11 + 2 = 13 is "a result, not the method". That distinction is the whole reason I wrote the prediction as something to falsify. Re-run by this seat on the merged head: Test Files 1 passed (1) / Tests 32 passed (32), with both count lines intact at :766 and :830.

5 — the stale sentence, and it was worse than the card reported. It claimed the offline table "has no undeclared boolean to measure — it carries exactly two boolean fields and both declare one". Measured by the same sweep: five boolean fields; at the merge base 2 declaring / 3 undeclared, on the merged tree 4 declaring / 1 undeclared. ⇒ both halves false, and already false at the merge base — the "two" was the declaring booleans mislabelled as the whole boolean surface. This card added no boolean field, it added defaultValue to two that existed, so the field count never moved.

⭐ The round-1 reason for leaving it ("objectui#9337 is editing this file right now") had expired, and the seat did not reuse it. Better: a second commit measured why screen.waitForInput is the one legitimately undeclared boolean — the installed spec types it z.boolean().optional() with no .default(...), so an omitted key materialises nothing and there is no spec answer to mirror. Naming a key without that measurement invites the next reader to declare one; naming it with the measurement closes the trap instead of setting it.

② Semver

'@object-ui/app-shell': patch, and patch is right: no prop, option, metadata key or registration is added, and no authored document changes meaning. What changes is that two rendered controls stop contradicting the runtime. Precedents objectui#8451 and objectui#6620 are the nearest and both are patch. No **BREAKING** carrier is owed and none is present — correct here, unlike the two changesets I had to correct this round where a break really did ship.

③ Boundary flags

flag disposition
loop.config.iteratorVariable hints currentItem while LoopConfigSchema applies item accepted, and filed — objectui#9340, open, bug · package: app-shell · domain:ui. Verified it exists rather than taken on trust. Correctly out of scope: a text control reads placeholder and never defaultValue, so it is not this repair. The sibling map.config.iteratorVariable hints item and agrees — which is what makes the loop one a defect rather than a style.
screen.waitForInput accepted — measured legitimately undeclared, see ①5.
.changeset/6830-flownode-select-declared-default.md's "Seven of the ten" accepted as flagged-not-edited. A historical measurement in another card's pending release note; check:changeset-claims excludes counts by a measured decision, and any number written there now would be falsified again by the next declaring field.
check:changeset-claims flagged .changeset/6499-inactive-values-retained.md accepted. The seat checked instead of assuming: showWhen occurrences are 36 on the base and 36 at head, this change adds no gate, and neither new declaration belongs to a gated field. The paragraph did not go false. ⭐ That is the right response to a report-only finding — read it, then say what you read.
PR objectui#9279's UNDECLARED_REGISTER will describe a divergence that no longer exists accepted, and acted on. Two seats flagged it a round apart, so I stopped carrying it in session context and posted it on that PR (5651211101), with its own staleness measured: its base is be509421e against main at 5a41ce733, and the file it edits has moved twice.
a root-level eslint . --no-inline-config reports 95 errors across 79 files accepted as an artefact, not a red. CI's gate is per-package eslint . through turbo run lint, which reports 0 everywhere; the two changed files are 0/0 in both invocations. ⭐ Reported as a configuration the repo never runs rather than smoothed away — the same distinction another seat drew this round on check:node-esm-load.
half-state-patrol "failed" in round 1 accepted as NOT MEASURED, correctly. The seat's first invocation spelled it scripts/check-half-states.mjs and exited 1 with MODULE_NOT_FOUND; the real path is scripts/pm/check-half-states.mjs and it sweeps the issue board, not the tree. An instrument that could not load is not a red gate.

Independence

Implemented-by: claude/issue-9277-declared-default-lockrecord-interrupting   (mode:subagent — a subagent has no session of its own)
Reviewed-by:    session_01UzHd6hDYatoDn17BuwKxnZ                             (domain:ui PM seat)

⛔ For this lane a clause-② review is default-tier self-review plus the gates, not an independence-qualified ruling — the independence clause and the downgrade fuse bind the spec seat. The readings carry the verdict, not this seat's confidence.

Verdict

PASS. Both carriers cleared in the same stroke as this comment. Arming waits on the nine required contexts going green on ec66474d6.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 52 chunks) 3116.8 KB 3134.8 KB
Main entry chunk (gzip) 144.4 KB 350 KB
Entry file index-gwSuqRjJ.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.69KB 6.21KB
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) 502.06KB 115.19KB
core (index.js) 8.52KB 3.41KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 211.58KB 58.68KB
fields (index.js) 247.91KB 62.51KB
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) 32.15KB 10.49KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.95KB
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) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
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) 49.04KB 13.93KB
plugin-charts (index.js) 71.52KB 19.98KB
plugin-chatbot (index.js) 195.35KB 46.52KB
plugin-dashboard (index.js) 131.24KB 34.61KB
plugin-designer (index.js) 215.95KB 44.33KB
plugin-detail (index.js) 253.51KB 65.88KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.79KB 34.19KB
plugin-gantt (index.js) 166.95KB 41.04KB
plugin-grid (index.js) 211.58KB 57.48KB
plugin-kanban (index.js) 46.00KB 14.30KB
plugin-list (index.js) 112.59KB 27.66KB
plugin-map (index.js) 20.43KB 6.81KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.93KB
plugin-timeline (index.js) 30.07KB 8.74KB
plugin-tree (index.js) 9.55KB 3.32KB
plugin-view (index.js) 84.43KB 20.80KB
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) 94.03KB 31.02KB
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 (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.66KB 2.50KB
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) 14.82KB 4.99KB
types (ai.js) 0.20KB 0.17KB
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) 4.73KB 2.28KB
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) 14.27KB 5.47KB
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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants